F3.2: resolver Hallazgos contra Acta explícita

This commit is contained in:
2026-09-07 16:12:03 -03:00
parent 45ca6a783e
commit 203443d9c6
@@ -36,9 +36,15 @@ export class FieldFindingsService {
private readonly findings: InspectionFindingsService,
) {}
async options(visitId: string, assetId: string, principal: AuthPrincipal) {
async options(
visitId: string,
assetId: string,
actId: string | undefined,
principal: AuthPrincipal,
) {
const gate = await this.requireGate(visitId, assetId, principal);
const act = await this.requireDraftAct(visitId);
const act = await this.requireDraftAct(visitId, actId);
const assetIncludedInAct = await this.actContainsAsset(act.id, assetId);
const [catalog, findings] = await Promise.all([
this.catalog.listApplicableForAsset(assetId, {}),
this.findings.listForAct(act.id),
@@ -48,21 +54,30 @@ export class FieldFindingsService {
context: gate.context,
act,
capture: gate.capture,
assetIncludedInAct,
catalog,
findings: findings.data.filter((finding) => finding.assetId === assetId),
canAddAnother: true,
canAddAnother: assetIncludedInAct,
actSelectionMode: actId ? 'EXPLICIT' : 'LEGACY_SINGLE_DRAFT',
};
}
async list(visitId: string, assetId: string, principal: AuthPrincipal) {
async list(
visitId: string,
assetId: string,
actId: string | undefined,
principal: AuthPrincipal,
) {
const gate = await this.requireGate(visitId, assetId, principal);
const act = await this.requireDraftAct(visitId);
const act = await this.requireDraftAct(visitId, actId);
const findings = await this.findings.listForAct(act.id);
return {
context: gate.context,
act,
capture: gate.capture,
assetIncludedInAct: await this.actContainsAsset(act.id, assetId),
data: findings.data.filter((finding) => finding.assetId === assetId),
actSelectionMode: actId ? 'EXPLICIT' : 'LEGACY_SINGLE_DRAFT',
};
}
@@ -74,7 +89,15 @@ export class FieldFindingsService {
request: RequestWithContext,
) {
const gate = await this.requireGate(visitId, assetId, principal);
const act = await this.requireDraftAct(visitId);
const act = await this.requireDraftAct(visitId, dto.actId);
if (!await this.actContainsAsset(act.id, assetId)) {
throw new ConflictException({
code: 'FIELD_FINDING_ASSET_NOT_IN_ACT',
message: 'Agregá este Inventario al Acta seleccionada antes de registrar el Hallazgo',
actId: act.id,
assetId,
});
}
const payload: CreateInspectionFindingDto = {
assetId,
catalogItemId: dto.catalogItemId ?? null,
@@ -91,6 +114,7 @@ export class FieldFindingsService {
capture: gate.capture,
finding,
canAddAnother: true,
actSelectionMode: dto.actId ? 'EXPLICIT' : 'LEGACY_SINGLE_DRAFT',
};
}
@@ -268,7 +292,40 @@ export class FieldFindingsService {
};
}
private async requireDraftAct(visitId: string): Promise<DraftActRow> {
private async actContainsAsset(actId: string, assetId: string): Promise<boolean> {
const [row] = await this.dataSource.query(`
SELECT EXISTS (
SELECT 1 FROM inspection_act_assets
WHERE act_id=$1::uuid AND asset_id=$2::uuid AND included=true
) AS included
`, [actId, assetId]) as Array<{ included: boolean }>;
return Boolean(row?.included);
}
private async requireDraftAct(visitId: string, requestedActId?: string): Promise<DraftActRow> {
if (requestedActId) {
const [act] = await this.dataSource.query(`
SELECT id,code,status
FROM inspection_acts
WHERE id=$1::uuid AND visit_id=$2::uuid
`, [requestedActId, visitId]) as DraftActRow[];
if (!act) {
throw new NotFoundException({
code: 'FIELD_FINDING_ACT_NOT_FOUND',
message: 'El Acta seleccionada no pertenece a esta inspección',
});
}
if (act.status !== 'DRAFT') {
throw new ConflictException({
code: 'FIELD_FINDING_ACT_NOT_DRAFT',
message: 'Los Hallazgos nuevos sólo pueden agregarse a un Acta en borrador',
actId: act.id,
actStatus: act.status,
});
}
return act;
}
const rows = await this.dataSource.query(`
SELECT id, code, status
FROM inspection_acts