F4.2 · bloquear Acta con urgencia y plazo congelado
This commit is contained in:
@@ -1,16 +1,32 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
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 {
|
||||
InspectionActUrgency,
|
||||
InspectionDeadlineBasis,
|
||||
InspectionDeadlineDayType,
|
||||
InspectionDeadlinePolicy,
|
||||
InspectionNonWorkingDay,
|
||||
} from '../database/entities';
|
||||
import type { UpdateInspectionDeadlinePolicyDto } from './dto/update-inspection-deadline-policy.dto';
|
||||
import type { UpsertInspectionNonWorkingDayDto } from './dto/upsert-inspection-non-working-day.dto';
|
||||
|
||||
export interface InspectionDeadlineSnapshot {
|
||||
urgency: InspectionActUrgency;
|
||||
days: number;
|
||||
dayType: InspectionDeadlineDayType;
|
||||
basis: InspectionDeadlineBasis;
|
||||
capturedAt: string;
|
||||
}
|
||||
|
||||
export interface InspectionDeadlineLockResult {
|
||||
snapshot: InspectionDeadlineSnapshot;
|
||||
baseOn: string | null;
|
||||
dueOn: string | null;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class InspectionDeadlinesService {
|
||||
constructor(
|
||||
@@ -68,6 +84,151 @@ export class InspectionDeadlinesService {
|
||||
});
|
||||
}
|
||||
|
||||
async snapshotForLock(
|
||||
manager: EntityManager,
|
||||
urgency: InspectionActUrgency,
|
||||
occurredAt: Date,
|
||||
): Promise<InspectionDeadlineLockResult> {
|
||||
const [policy] = (await manager.query(`
|
||||
SELECT
|
||||
urgency,
|
||||
days,
|
||||
day_type AS "dayType",
|
||||
basis
|
||||
FROM inspection_deadline_policies
|
||||
WHERE urgency = $1
|
||||
FOR SHARE
|
||||
`, [urgency])) as Array<{
|
||||
urgency: InspectionActUrgency;
|
||||
days: number;
|
||||
dayType: InspectionDeadlineDayType;
|
||||
basis: InspectionDeadlineBasis;
|
||||
}>;
|
||||
if (!policy) {
|
||||
throw new NotFoundException({
|
||||
code: 'INSPECTION_DEADLINE_POLICY_NOT_FOUND',
|
||||
message: 'No existe una política de plazo para la urgencia seleccionada',
|
||||
});
|
||||
}
|
||||
|
||||
const snapshot: InspectionDeadlineSnapshot = {
|
||||
urgency: policy.urgency,
|
||||
days: Number(policy.days),
|
||||
dayType: policy.dayType,
|
||||
basis: policy.basis,
|
||||
capturedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
if (policy.basis === InspectionDeadlineBasis.GEDO_LOAD_DATE) {
|
||||
return { snapshot, baseOn: null, dueOn: null };
|
||||
}
|
||||
|
||||
const [base] = (await manager.query(`
|
||||
SELECT TO_CHAR(
|
||||
$1::timestamptz AT TIME ZONE 'America/Argentina/Mendoza',
|
||||
'YYYY-MM-DD'
|
||||
) AS day
|
||||
`, [occurredAt])) as Array<{ day: string }>;
|
||||
if (!base?.day) {
|
||||
throw new BadRequestException({
|
||||
code: 'INSPECTION_ACT_DATE_INVALID',
|
||||
message: 'No se pudo determinar la fecha del Acta para calcular el plazo',
|
||||
});
|
||||
}
|
||||
const dueOn = await this.calculateDueOn(
|
||||
manager,
|
||||
base.day,
|
||||
snapshot.days,
|
||||
snapshot.dayType,
|
||||
);
|
||||
return { snapshot, baseOn: base.day, dueOn };
|
||||
}
|
||||
|
||||
async calculateDueOn(
|
||||
manager: EntityManager,
|
||||
baseOn: string,
|
||||
days: number,
|
||||
dayType: InspectionDeadlineDayType,
|
||||
): Promise<string> {
|
||||
if (!Number.isInteger(days) || days < 1 || days > 365) {
|
||||
throw new BadRequestException({
|
||||
code: 'INSPECTION_DEADLINE_DAYS_INVALID',
|
||||
message: 'La cantidad de días del plazo no es válida',
|
||||
});
|
||||
}
|
||||
|
||||
if (dayType === InspectionDeadlineDayType.CALENDAR) {
|
||||
const [row] = (await manager.query(`
|
||||
SELECT ($1::date + $2::integer)::text AS "dueOn"
|
||||
`, [baseOn, days])) as Array<{ dueOn: string }>;
|
||||
return row.dueOn;
|
||||
}
|
||||
|
||||
const [row] = (await manager.query(`
|
||||
SELECT candidate.day::text AS "dueOn"
|
||||
FROM (
|
||||
SELECT generated::date AS day
|
||||
FROM generate_series(
|
||||
$1::date + 1,
|
||||
$1::date + (($2::integer * 3) + 31),
|
||||
interval '1 day'
|
||||
) generated
|
||||
WHERE EXTRACT(ISODOW FROM generated) BETWEEN 1 AND 5
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM inspection_non_working_days holiday
|
||||
WHERE holiday.day = generated::date
|
||||
AND holiday.enabled = true
|
||||
)
|
||||
ORDER BY generated
|
||||
LIMIT 1 OFFSET ($2::integer - 1)
|
||||
) candidate
|
||||
`, [baseOn, days])) as Array<{ dueOn: string }>;
|
||||
if (!row?.dueOn) {
|
||||
throw new BadRequestException({
|
||||
code: 'INSPECTION_DEADLINE_CALCULATION_FAILED',
|
||||
message: 'No se pudo calcular el vencimiento con el calendario configurado',
|
||||
});
|
||||
}
|
||||
return row.dueOn;
|
||||
}
|
||||
|
||||
async applyGedoOfficialization(
|
||||
manager: EntityManager,
|
||||
actId: string,
|
||||
officializedOn: string,
|
||||
): Promise<string | null> {
|
||||
const [act] = (await manager.query(`
|
||||
SELECT
|
||||
deadline_days AS "days",
|
||||
deadline_day_type AS "dayType",
|
||||
deadline_basis AS "basis"
|
||||
FROM inspection_acts
|
||||
WHERE id = $1
|
||||
FOR UPDATE
|
||||
`, [actId])) as Array<{
|
||||
days: number | null;
|
||||
dayType: InspectionDeadlineDayType | null;
|
||||
basis: InspectionDeadlineBasis | null;
|
||||
}>;
|
||||
if (!act || act.basis !== InspectionDeadlineBasis.GEDO_LOAD_DATE) return null;
|
||||
if (!act.days || !act.dayType) {
|
||||
throw new BadRequestException({
|
||||
code: 'INSPECTION_ACT_DEADLINE_SNAPSHOT_MISSING',
|
||||
message: 'El Acta no conserva la política de plazo requerida',
|
||||
});
|
||||
}
|
||||
const dueOn = await this.calculateDueOn(manager, officializedOn, Number(act.days), act.dayType);
|
||||
await manager.query(`
|
||||
UPDATE inspection_acts
|
||||
SET deadline_base_on = $2::date,
|
||||
deadline_due_on = $3::date,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1
|
||||
`, [actId, officializedOn, dueOn]);
|
||||
return dueOn;
|
||||
}
|
||||
|
||||
async listNonWorkingDays(year?: string) {
|
||||
const parsedYear = year === undefined ? null : Number(year);
|
||||
if (parsedYear !== null && (!Number.isInteger(parsedYear) || parsedYear < 2000 || parsedYear > 2200)) {
|
||||
|
||||
Reference in New Issue
Block a user