chore: import DH V2 D5.6.4 production baseline
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { DataSource, EntityManager } from 'typeorm';
|
||||
import { administrationAuditContext } from '../administration/common/administration-audit';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import type {
|
||||
AuthPrincipal,
|
||||
RequestWithContext,
|
||||
} from '../common/http/request-context';
|
||||
import {
|
||||
AssetDataOrigin,
|
||||
AssetVersionChangeType,
|
||||
AuditAction,
|
||||
} from '../database/entities';
|
||||
import { AssetHistoryService } from './asset-history.service';
|
||||
import type { UpdateAssetProvenanceDto } from './dto/update-asset-provenance.dto';
|
||||
|
||||
export interface AssetProvenanceView {
|
||||
assetId: string;
|
||||
origin: AssetDataOrigin;
|
||||
sourceName: string | null;
|
||||
sourceReference: string | null;
|
||||
observedAt: Date | null;
|
||||
notes: string | null;
|
||||
verifiedAt: Date | null;
|
||||
verifiedBy: string | null;
|
||||
verifiedByUsername: string | null;
|
||||
updatedAt: Date;
|
||||
updatedBy: string | null;
|
||||
updatedByUsername: string | null;
|
||||
}
|
||||
|
||||
function assetNotFound(): NotFoundException {
|
||||
return new NotFoundException({
|
||||
code: 'ASSET_NOT_FOUND',
|
||||
message: 'Activo no encontrado',
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AssetProvenanceService {
|
||||
constructor(
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly audit: AuditService,
|
||||
private readonly history: AssetHistoryService,
|
||||
) {}
|
||||
|
||||
get(assetId: string): Promise<AssetProvenanceView> {
|
||||
return this.load(this.dataSource.manager, assetId);
|
||||
}
|
||||
|
||||
async update(
|
||||
assetId: string,
|
||||
dto: UpdateAssetProvenanceDto,
|
||||
principal: AuthPrincipal,
|
||||
request: RequestWithContext,
|
||||
): Promise<AssetProvenanceView> {
|
||||
this.validateSource(dto);
|
||||
return this.dataSource.transaction(async (manager) => {
|
||||
const before = await this.load(manager, assetId, true);
|
||||
await manager.query(
|
||||
`UPDATE assets SET
|
||||
data_origin = $2,
|
||||
source_name = $3,
|
||||
source_reference = $4,
|
||||
source_observed_at = $5,
|
||||
source_notes = $6,
|
||||
provenance_verified_at = NULL,
|
||||
provenance_verified_by = NULL,
|
||||
provenance_updated_at = CURRENT_TIMESTAMP,
|
||||
provenance_updated_by = $7,
|
||||
updated_at = CURRENT_TIMESTAMP,
|
||||
updated_by = $7
|
||||
WHERE id = $1`,
|
||||
[
|
||||
assetId,
|
||||
dto.origin,
|
||||
dto.sourceName?.trim() || null,
|
||||
dto.sourceReference?.trim() || null,
|
||||
dto.observedAt ? new Date(dto.observedAt) : null,
|
||||
dto.notes?.trim() || null,
|
||||
principal.userId,
|
||||
],
|
||||
);
|
||||
const versionNumber = await this.history.capture(
|
||||
manager,
|
||||
assetId,
|
||||
AssetVersionChangeType.PROVENANCE_UPDATED,
|
||||
principal,
|
||||
request,
|
||||
);
|
||||
const updated = await this.load(manager, assetId);
|
||||
await this.audit.record(
|
||||
{
|
||||
...administrationAuditContext(principal, request),
|
||||
action: AuditAction.ASSET_PROVENANCE_UPDATED,
|
||||
entityType: 'asset_provenance',
|
||||
entityId: assetId,
|
||||
beforeData: { ...before },
|
||||
afterData: { ...updated },
|
||||
metadata: { versionNumber, verificationCleared: before.verifiedAt !== null },
|
||||
},
|
||||
manager,
|
||||
);
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
|
||||
async verify(
|
||||
assetId: string,
|
||||
principal: AuthPrincipal,
|
||||
request: RequestWithContext,
|
||||
): Promise<AssetProvenanceView> {
|
||||
return this.dataSource.transaction(async (manager) => {
|
||||
const before = await this.load(manager, assetId, true);
|
||||
if (before.verifiedAt) return before;
|
||||
await manager.query(
|
||||
`UPDATE assets SET
|
||||
provenance_verified_at = CURRENT_TIMESTAMP,
|
||||
provenance_verified_by = $2,
|
||||
provenance_updated_at = CURRENT_TIMESTAMP,
|
||||
provenance_updated_by = $2,
|
||||
updated_at = CURRENT_TIMESTAMP,
|
||||
updated_by = $2
|
||||
WHERE id = $1`,
|
||||
[assetId, principal.userId],
|
||||
);
|
||||
const versionNumber = await this.history.capture(
|
||||
manager,
|
||||
assetId,
|
||||
AssetVersionChangeType.PROVENANCE_VERIFIED,
|
||||
principal,
|
||||
request,
|
||||
);
|
||||
const verified = await this.load(manager, assetId);
|
||||
await this.audit.record(
|
||||
{
|
||||
...administrationAuditContext(principal, request),
|
||||
action: AuditAction.ASSET_PROVENANCE_VERIFIED,
|
||||
entityType: 'asset_provenance',
|
||||
entityId: assetId,
|
||||
beforeData: { ...before },
|
||||
afterData: { ...verified },
|
||||
metadata: { versionNumber },
|
||||
},
|
||||
manager,
|
||||
);
|
||||
return verified;
|
||||
});
|
||||
}
|
||||
|
||||
private validateSource(dto: UpdateAssetProvenanceDto): void {
|
||||
const requiresNamedSource = dto.origin === AssetDataOrigin.PROVIDED_DOCUMENT
|
||||
|| dto.origin === AssetDataOrigin.IMPORT;
|
||||
if (requiresNamedSource && !dto.sourceName?.trim()) {
|
||||
throw new BadRequestException({
|
||||
code: 'PROVENANCE_SOURCE_REQUIRED',
|
||||
message: 'La documentación recibida y las importaciones requieren identificar la fuente',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async load(
|
||||
manager: EntityManager,
|
||||
assetId: string,
|
||||
lock = false,
|
||||
): Promise<AssetProvenanceView> {
|
||||
const [row] = (await manager.query(
|
||||
`SELECT
|
||||
asset.id AS "assetId",
|
||||
asset.data_origin AS origin,
|
||||
asset.source_name AS "sourceName",
|
||||
asset.source_reference AS "sourceReference",
|
||||
asset.source_observed_at AS "observedAt",
|
||||
asset.source_notes AS notes,
|
||||
asset.provenance_verified_at AS "verifiedAt",
|
||||
asset.provenance_verified_by AS "verifiedBy",
|
||||
verifier.username AS "verifiedByUsername",
|
||||
asset.provenance_updated_at AS "updatedAt",
|
||||
asset.provenance_updated_by AS "updatedBy",
|
||||
updater.username AS "updatedByUsername"
|
||||
FROM assets asset
|
||||
LEFT JOIN users verifier ON verifier.id = asset.provenance_verified_by
|
||||
LEFT JOIN users updater ON updater.id = asset.provenance_updated_by
|
||||
WHERE asset.id = $1
|
||||
${lock ? 'FOR UPDATE OF asset' : ''}`,
|
||||
[assetId],
|
||||
)) as AssetProvenanceView[];
|
||||
if (!row) throw assetNotFound();
|
||||
return row;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user