fix(f4): decouple GEDO officialization from notification deadline
This commit is contained in:
@@ -5,7 +5,6 @@ import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
@@ -15,9 +14,6 @@ import { AuditService } from '../audit/audit.service';
|
||||
import type { AuthPrincipal, RequestWithContext } from '../common/http/request-context';
|
||||
import {
|
||||
AuditAction,
|
||||
InspectionActUrgency,
|
||||
InspectionDeadlineBasis,
|
||||
InspectionDeadlineDayType,
|
||||
InspectionReportStatus,
|
||||
} from '../database/entities';
|
||||
import type { CreateInspectionReportFollowUpDto } from './dto/create-inspection-report-follow-up.dto';
|
||||
@@ -169,7 +165,9 @@ export class InspectionReportWorkflowService {
|
||||
file!.buffer.length,
|
||||
sha256,
|
||||
]);
|
||||
await this.activateNonUrgentDeadline(manager, report.actId, officializedAt);
|
||||
// GEDO oficializa el INF, pero no equivale por sí solo a la notificación
|
||||
// administrativa de un Acta no urgente. El vencimiento queda pendiente
|
||||
// hasta que el procedimiento defina y registre el evento de notificación.
|
||||
await this.audit.record({
|
||||
...administrationAuditContext(principal, request),
|
||||
action: AuditAction.INSPECTION_REPORT_OFFICIALIZED,
|
||||
@@ -182,7 +180,12 @@ export class InspectionReportWorkflowService {
|
||||
gedoPdfOriginalName: file!.originalname,
|
||||
gedoPdfSha256: sha256,
|
||||
},
|
||||
metadata: { reportCode: report.code, actId: report.actId, immutable: true },
|
||||
metadata: {
|
||||
reportCode: report.code,
|
||||
actId: report.actId,
|
||||
immutable: true,
|
||||
deadlineActivation: 'PENDING_NOTIFICATION_EVENT',
|
||||
},
|
||||
}, manager);
|
||||
return this.getWorkflowView(manager, reportId);
|
||||
});
|
||||
@@ -288,84 +291,6 @@ export class InspectionReportWorkflowService {
|
||||
`, [reportId]);
|
||||
}
|
||||
|
||||
private async activateNonUrgentDeadline(
|
||||
manager: EntityManager,
|
||||
actId: string,
|
||||
officializedAt: Date,
|
||||
): Promise<void> {
|
||||
const [act] = await manager.query(`
|
||||
SELECT urgency,deadline_days AS "deadlineDays",deadline_day_type AS "deadlineDayType",
|
||||
deadline_basis AS "deadlineBasis",deadline_at AS "deadlineAt"
|
||||
FROM inspection_acts WHERE id=$1 FOR UPDATE
|
||||
`, [actId]) as Array<{
|
||||
urgency: InspectionActUrgency;
|
||||
deadlineDays: number | null;
|
||||
deadlineDayType: InspectionDeadlineDayType | null;
|
||||
deadlineBasis: InspectionDeadlineBasis | null;
|
||||
deadlineAt: Date | null;
|
||||
}>;
|
||||
if (!act) return;
|
||||
if (act.urgency !== InspectionActUrgency.NON_URGENT) return;
|
||||
if (act.deadlineBasis !== InspectionDeadlineBasis.GEDO_DATE) {
|
||||
throw new ConflictException({
|
||||
code: 'INVALID_NON_URGENT_DEADLINE_BASIS',
|
||||
message: 'El acta no urgente no conserva la regla GEDO necesaria para calcular su vencimiento',
|
||||
});
|
||||
}
|
||||
if (!act.deadlineDays || !act.deadlineDayType) {
|
||||
throw new ConflictException({
|
||||
code: 'MISSING_ACT_DEADLINE_SNAPSHOT',
|
||||
message: 'El acta no conserva la política de plazo aplicada al momento de finalizarse',
|
||||
});
|
||||
}
|
||||
const deadlineAt = await this.calculateDeadline(
|
||||
manager,
|
||||
officializedAt,
|
||||
Number(act.deadlineDays),
|
||||
act.deadlineDayType,
|
||||
);
|
||||
await manager.query(`
|
||||
UPDATE inspection_acts
|
||||
SET deadline_base_at=$2,deadline_at=$3,updated_at=CURRENT_TIMESTAMP
|
||||
WHERE id=$1
|
||||
`, [actId, officializedAt, deadlineAt]);
|
||||
}
|
||||
|
||||
private async calculateDeadline(
|
||||
manager: EntityManager,
|
||||
baseAt: Date,
|
||||
days: number,
|
||||
dayType: InspectionDeadlineDayType,
|
||||
): Promise<Date> {
|
||||
if (dayType === InspectionDeadlineDayType.CALENDAR) {
|
||||
const [row] = await manager.query(`
|
||||
SELECT (
|
||||
(($1::timestamptz AT TIME ZONE 'America/Argentina/Mendoza')::date + $2::integer)::timestamp
|
||||
+ time '23:59:59'
|
||||
) AT TIME ZONE 'America/Argentina/Mendoza' AS due_at
|
||||
`, [baseAt, days]) as Array<{ due_at: Date }>;
|
||||
return row.due_at;
|
||||
}
|
||||
const [row] = await manager.query(`
|
||||
WITH candidates AS (
|
||||
SELECT day::date AS day,
|
||||
COALESCE(override.is_business_day, EXTRACT(ISODOW FROM day)::integer BETWEEN 1 AND 5) AS business
|
||||
FROM generate_series(
|
||||
(($1::timestamptz AT TIME ZONE 'America/Argentina/Mendoza')::date + 1)::timestamp,
|
||||
(($1::timestamptz AT TIME ZONE 'America/Argentina/Mendoza')::date + 730)::timestamp,
|
||||
interval '1 day'
|
||||
) day
|
||||
LEFT JOIN inspection_business_calendar_days override ON override.date=day::date
|
||||
), ranked AS (
|
||||
SELECT day,ROW_NUMBER() OVER (ORDER BY day) AS position FROM candidates WHERE business=true
|
||||
)
|
||||
SELECT ((day::timestamp + time '23:59:59') AT TIME ZONE 'America/Argentina/Mendoza') AS due_at
|
||||
FROM ranked WHERE position=$2 LIMIT 1
|
||||
`, [baseAt, days]) as Array<{ due_at: Date }>;
|
||||
if (!row?.due_at) throw new InternalServerErrorException('No se pudo calcular el vencimiento');
|
||||
return row.due_at;
|
||||
}
|
||||
|
||||
private assertPdf(file: UploadedInspectionReportFile | undefined): void {
|
||||
if (!file?.buffer?.length) {
|
||||
throw new BadRequestException({
|
||||
|
||||
Reference in New Issue
Block a user