F2.3: crear servicio de hallazgos desde Inventario

This commit is contained in:
2026-09-07 08:25:14 -03:00
parent 1c5efa8806
commit dd8ea8539b
@@ -0,0 +1,107 @@
import { ConflictException, Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import type { AuthPrincipal, RequestWithContext } from '../common/http/request-context';
import { FindingCatalogService } from '../inspection-findings/finding-catalog.service';
import type { CreateInspectionFindingDto } from '../inspection-findings/dto/create-inspection-finding.dto';
import { InspectionFindingsService } from '../inspection-findings/inspection-findings.service';
import type { CreateFieldFindingDto } from './dto/create-field-finding.dto';
import { FieldInventoryService } from './field-inventory.service';
interface DraftActRow {
id: string;
code: string;
status: string;
}
@Injectable()
export class FieldFindingsService {
constructor(
private readonly dataSource: DataSource,
private readonly fieldInventory: FieldInventoryService,
private readonly catalog: FindingCatalogService,
private readonly findings: InspectionFindingsService,
) {}
async options(visitId: string, assetId: string, principal: AuthPrincipal) {
const gate = await this.fieldInventory.requireReadyForFinding(visitId, assetId, principal);
const act = await this.requireDraftAct(visitId);
const [catalog, findings] = await Promise.all([
this.catalog.listApplicableForAsset(assetId, {}),
this.findings.listForAct(act.id),
]);
return {
context: gate.context,
act,
capture: gate.capture,
catalog,
findings: findings.data.filter((finding) => finding.assetId === assetId),
canAddAnother: true,
};
}
async list(visitId: string, assetId: string, principal: AuthPrincipal) {
const gate = await this.fieldInventory.requireReadyForFinding(visitId, assetId, principal);
const act = await this.requireDraftAct(visitId);
const findings = await this.findings.listForAct(act.id);
return {
context: gate.context,
act,
capture: gate.capture,
data: findings.data.filter((finding) => finding.assetId === assetId),
};
}
async create(
visitId: string,
assetId: string,
dto: CreateFieldFindingDto,
principal: AuthPrincipal,
request: RequestWithContext,
) {
const gate = await this.fieldInventory.requireReadyForFinding(visitId, assetId, principal);
const act = await this.requireDraftAct(visitId);
const payload: CreateInspectionFindingDto = {
assetId,
catalogItemId: dto.catalogItemId ?? null,
customTitle: dto.customTitle ?? null,
customLegalBasis: dto.customLegalBasis ?? null,
description: dto.description,
severity: dto.severity,
correctionDueOn: dto.correctionDueOn ?? null,
};
const finding = await this.findings.create(act.id, payload, principal, request);
return {
context: gate.context,
act,
capture: gate.capture,
finding,
canAddAnother: true,
};
}
private async requireDraftAct(visitId: string): Promise<DraftActRow> {
const rows = await this.dataSource.query(`
SELECT id, code, status
FROM inspection_acts
WHERE visit_id = $1::uuid
AND status = 'DRAFT'
ORDER BY created_at DESC, id DESC
LIMIT 2
`, [visitId]) as DraftActRow[];
if (rows.length === 0) {
throw new ConflictException({
code: 'FIELD_FINDING_DRAFT_ACT_REQUIRED',
message: 'La inspección no tiene un Acta borrador abierta para registrar hallazgos',
});
}
if (rows.length > 1) {
throw new ConflictException({
code: 'FIELD_FINDING_MULTIPLE_DRAFT_ACTS',
message: 'La inspección tiene más de un Acta borrador. Debe resolverse antes de continuar',
});
}
return rows[0];
}
}