feat(r2.1): socle backend exploitation — machine à états, bilan codé, demandes 1-1

- migration r2_exploitation (10 tables) : WorkOrder (référence séquentielle,
  horodatages), WorkOrderEvent, Request (1-1, motif de rejet), ReferenceValue,
  InterventionReport (6 FK), TaskTemplate/ChecklistItem, Meter/MeterReading
- contrat : 15 opérations (41 total) ; la table des transitions et les champs
  requis du bilan vivent dans @siop/shared ; la fiche OT expose
  allowedTransitions + closureBlockers (messages métier)
- API : machine à états stricte ; garde de clôture (bilan 3 champs requis +
  checklist sans tâche en attente) ; approbation → OT lié 1-1 (409 si déjà
  traitée) ; rejet à motif obligatoire ; scoping « voir autre » sur listes et
  accès directs (404 sans fuite) ; validation des valeurs de bilan par champ ;
  « personne bloquée » triée en tête côté API
- seed : 31 valeurs de référentiels, 8 gabarits (parachute réglementaire),
  OT/demandes/compteurs de la maquette — idempotent
- 45 tests verts (95 % stmts / 79 % branches) dont la recette officielle
  rejouée de bout en bout ; smoke test sur build de prod

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-16 13:56:07 +01:00
parent 54d926e9f4
commit f7702e4252
23 changed files with 5568 additions and 4 deletions

View File

@@ -0,0 +1,110 @@
import {
Body,
Controller,
Get,
HttpCode,
Param,
ParseUUIDPipe,
Patch,
Post,
Put,
} from '@nestjs/common';
import {
AssigneesUpdateSchema,
ChecklistPatchSchema,
CommentCreateSchema,
ReportUpsertSchema,
TransitionRequestSchema,
WorkOrderCreateSchema,
type AssigneesUpdate,
type ChecklistPatch,
type CommentCreate,
type ReportUpsert,
type TransitionRequest,
type WorkOrderCreate,
} from '@siop/shared';
import {
AuthenticatedUser,
CurrentUser,
} from '../auth/current-user.decorator';
import { ZodValidationPipe } from '../common/zod-validation.pipe';
import { RequirePermission } from '../permissions/require-permission.decorator';
import { WorkOrdersService } from './work-orders.service';
@Controller('work-orders')
export class WorkOrdersController {
constructor(private readonly workOrders: WorkOrdersService) {}
@Get()
@RequirePermission('WORK_ORDERS', 'view')
list(@CurrentUser() user: AuthenticatedUser) {
return this.workOrders.list(user);
}
@Get(':id')
@RequirePermission('WORK_ORDERS', 'view')
get(@Param('id', ParseUUIDPipe) id: string, @CurrentUser() user: AuthenticatedUser) {
return this.workOrders.get(id, user);
}
@Post()
@RequirePermission('WORK_ORDERS', 'create')
create(
@Body(new ZodValidationPipe(WorkOrderCreateSchema)) body: WorkOrderCreate,
@CurrentUser() user: AuthenticatedUser,
) {
return this.workOrders.create(body, user);
}
@Post(':id/transition')
@HttpCode(200) // le contrat : 200, l'état change — rien n'est « créé »
@RequirePermission('WORK_ORDERS', 'edit')
transition(
@Param('id', ParseUUIDPipe) id: string,
@Body(new ZodValidationPipe(TransitionRequestSchema)) body: TransitionRequest,
@CurrentUser() user: AuthenticatedUser,
) {
return this.workOrders.transition(id, body, user);
}
@Post(':id/comments')
@RequirePermission('WORK_ORDERS', 'edit')
comment(
@Param('id', ParseUUIDPipe) id: string,
@Body(new ZodValidationPipe(CommentCreateSchema)) body: CommentCreate,
@CurrentUser() user: AuthenticatedUser,
) {
return this.workOrders.comment(id, body.message, user);
}
@Put(':id/assignees')
@RequirePermission('WORK_ORDERS', 'edit')
setAssignees(
@Param('id', ParseUUIDPipe) id: string,
@Body(new ZodValidationPipe(AssigneesUpdateSchema)) body: AssigneesUpdate,
@CurrentUser() user: AuthenticatedUser,
) {
return this.workOrders.setAssignees(id, body, user);
}
@Put(':id/report')
@RequirePermission('WORK_ORDERS', 'edit')
upsertReport(
@Param('id', ParseUUIDPipe) id: string,
@Body(new ZodValidationPipe(ReportUpsertSchema)) body: ReportUpsert,
@CurrentUser() user: AuthenticatedUser,
) {
return this.workOrders.upsertReport(id, body, user);
}
@Patch(':id/checklist/:itemId')
@RequirePermission('WORK_ORDERS', 'edit')
patchChecklist(
@Param('id', ParseUUIDPipe) id: string,
@Param('itemId', ParseUUIDPipe) itemId: string,
@Body(new ZodValidationPipe(ChecklistPatchSchema)) body: ChecklistPatch,
@CurrentUser() user: AuthenticatedUser,
) {
return this.workOrders.patchChecklist(id, itemId, body, user);
}
}