F4.0 · numeración INSP y lifecycle explícito

This commit is contained in:
2026-09-07 19:41:29 -03:00
parent cbab839935
commit eb89680c32
3 changed files with 115 additions and 27 deletions
@@ -4,14 +4,15 @@ export async function nextInspectionVisitCode(
manager: EntityManager,
plannedStartAt: Date,
): Promise<string> {
const [yearRow] = (await manager.query(`
SELECT EXTRACT(
YEAR FROM $1::timestamptz AT TIME ZONE 'America/Argentina/Mendoza'
)::integer AS year
`, [plannedStartAt])) as Array<{ year: number }>;
const year = Number(yearRow?.year);
if (!Number.isInteger(year) || year < 2000 || year > 9999) {
throw new Error('Unable to derive inspection visit year');
const [dateRow] = (await manager.query(`
SELECT
EXTRACT(YEAR FROM $1::timestamptz AT TIME ZONE 'America/Argentina/Mendoza')::integer AS year,
TO_CHAR($1::timestamptz AT TIME ZONE 'America/Argentina/Mendoza', 'DD-MM-YY') AS "datePart"
`, [plannedStartAt])) as Array<{ year: number; datePart: string }>;
const year = Number(dateRow?.year);
const datePart = dateRow?.datePart;
if (!Number.isInteger(year) || year < 2000 || year > 9999 || !datePart) {
throw new Error('Unable to derive inspection visit date');
}
await manager.query(
@@ -20,15 +21,25 @@ export async function nextInspectionVisitCode(
);
const [sequenceRow] = (await manager.query(`
SELECT COALESCE(MAX(RIGHT(code, 6)::integer), 0)::integer AS sequence
FROM inspection_visits
WHERE code LIKE $1
AND code ~ ('^INS-' || $2::text || '-[0-9]{6}$')
`, [`INS-${year}-%`, year])) as Array<{ sequence: number }>;
SELECT COALESCE(MAX(candidate.sequence), 0)::integer AS sequence
FROM (
SELECT SUBSTRING(code FROM '^INSP-([0-9]{5})-[0-9]{2}-[0-9]{2}-[0-9]{2}$')::integer AS sequence
FROM inspection_visits
WHERE EXTRACT(
YEAR FROM COALESCE(planned_start_at, created_at) AT TIME ZONE 'America/Argentina/Mendoza'
)::integer = $1
AND code ~ '^INSP-[0-9]{5}-[0-9]{2}-[0-9]{2}-[0-9]{2}$'
UNION ALL
SELECT RIGHT(code, 6)::integer AS sequence
FROM inspection_visits
WHERE code LIKE $2
AND code ~ ('^INS-' || $1::text || '-[0-9]{6}$')
) candidate
`, [year, `INS-${year}-%`])) as Array<{ sequence: number }>;
const sequence = Number(sequenceRow?.sequence ?? 0) + 1;
if (sequence > 999999) {
if (sequence > 99999) {
throw new Error(`Inspection visit sequence exhausted for ${year}`);
}
return `INS-${year}-${String(sequence).padStart(6, '0')}`;
return `INSP-${String(sequence).padStart(5, '0')}-${datePart}`;
}
@@ -13,7 +13,6 @@ import {
import { CurrentAuth } from '../auth/decorators/current-auth.decorator';
import { RequirePermissions } from '../authorization/decorators/require-permissions.decorator';
import type { AuthPrincipal, RequestWithContext } from '../common/http/request-context';
import { ChangeInspectionVisitStatusDto } from './dto/change-inspection-visit-status.dto';
import { CloseInspectionVisitDto } from './dto/close-inspection-visit.dto';
import { CreateInspectionVisitDto } from './dto/create-inspection-visit.dto';
import { ExcludeInspectionVisitAssetDto } from './dto/exclude-inspection-visit-asset.dto';
@@ -135,17 +134,6 @@ export class InspectionVisitsController {
return this.visits.replaceTeam(id, dto, principal, request);
}
@Put(':id/status')
@RequirePermissions('inspections.manage')
changeStatus(
@Param('id', new ParseUUIDPipe({ version: '4' })) id: string,
@Body() dto: ChangeInspectionVisitStatusDto,
@CurrentAuth() principal: AuthPrincipal,
@Req() request: RequestWithContext,
) {
return this.visits.changeStatus(id, dto, principal, request);
}
@Post(':id/start')
@RequirePermissions('inspections.execute')
start(