fix(f4): decouple GEDO officialization from notification deadline
This commit is contained in:
@@ -5,7 +5,6 @@ import {
|
|||||||
BadRequestException,
|
BadRequestException,
|
||||||
ConflictException,
|
ConflictException,
|
||||||
Injectable,
|
Injectable,
|
||||||
InternalServerErrorException,
|
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
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 type { AuthPrincipal, RequestWithContext } from '../common/http/request-context';
|
||||||
import {
|
import {
|
||||||
AuditAction,
|
AuditAction,
|
||||||
InspectionActUrgency,
|
|
||||||
InspectionDeadlineBasis,
|
|
||||||
InspectionDeadlineDayType,
|
|
||||||
InspectionReportStatus,
|
InspectionReportStatus,
|
||||||
} from '../database/entities';
|
} from '../database/entities';
|
||||||
import type { CreateInspectionReportFollowUpDto } from './dto/create-inspection-report-follow-up.dto';
|
import type { CreateInspectionReportFollowUpDto } from './dto/create-inspection-report-follow-up.dto';
|
||||||
@@ -169,7 +165,9 @@ export class InspectionReportWorkflowService {
|
|||||||
file!.buffer.length,
|
file!.buffer.length,
|
||||||
sha256,
|
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({
|
await this.audit.record({
|
||||||
...administrationAuditContext(principal, request),
|
...administrationAuditContext(principal, request),
|
||||||
action: AuditAction.INSPECTION_REPORT_OFFICIALIZED,
|
action: AuditAction.INSPECTION_REPORT_OFFICIALIZED,
|
||||||
@@ -182,7 +180,12 @@ export class InspectionReportWorkflowService {
|
|||||||
gedoPdfOriginalName: file!.originalname,
|
gedoPdfOriginalName: file!.originalname,
|
||||||
gedoPdfSha256: sha256,
|
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);
|
}, manager);
|
||||||
return this.getWorkflowView(manager, reportId);
|
return this.getWorkflowView(manager, reportId);
|
||||||
});
|
});
|
||||||
@@ -288,84 +291,6 @@ export class InspectionReportWorkflowService {
|
|||||||
`, [reportId]);
|
`, [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 {
|
private assertPdf(file: UploadedInspectionReportFile | undefined): void {
|
||||||
if (!file?.buffer?.length) {
|
if (!file?.buffer?.length) {
|
||||||
throw new BadRequestException({
|
throw new BadRequestException({
|
||||||
|
|||||||
Reference in New Issue
Block a user