shoc-backend/docs/work-orders/phase-5/README.md

119 lines
4 KiB
Markdown

# Fase 5 — Scheduled Domain Events
**Programa:** Work Orders Board
**Objetivo:** Jobs agendados in-process (`IHostedService`) para `WeekRolled` → `CarriedOver++` com idempotência WO+semana, audit System, cache opcional de `PastDue`, e garantia de que leituras do board continuam on-read como fonte da verdade.
**Depende de:** [Fase 0](../phase-0/README.md), [Fase 1](../phase-1/README.md), [Fase 2](../phase-2/README.md), [Fase 3](../phase-3/README.md), [Fase 4](../phase-4/README.md)
---
## Regra de elegibilidade WeekRolled
```text
sourceWeek = semana operacional encerrada (segunda a sexta UTC)
Elegível se:
- ScheduledDate.Date ∈ [sourceWeekStart, sourceWeekEnd]
- LifecycleStatus ∉ {Complete, Canceled, Closed}
- istemplate != true (ou null)
- Ainda não processado em WorkOrderWeekRolledLedger (WorkOrderId + SourceWeekStart)
```
**Gate PO (pendente):** confirmar se WOs só com `TargetWeek` (sem `ScheduledDate`) entram no carry-over. A implementação atual considera **apenas** WOs com `ScheduledDate` na janela.
---
## Idempotência
Chave composta `(WorkOrderId, SourceWeekStart)` na tabela `WorkOrderWeekRolledLedger`. Re-execução do job ou endpoint admin na mesma semana não duplica `CarriedOver`.
---
## Contrato de audit
Um evento `WeekRolled` por WO processado:
| Campo | Valor |
|-------|-------|
| `EventType` | `System` |
| `ActorType` | `System` |
| `Action` | `WeekRolled` |
| `FieldName` | `CarriedOver` |
| `OldValue` / `NewValue` | numéricos (string) |
| `CorrelationId` | `week:{yyyy-MM-dd}` (segunda da semana fonte) |
Não aciona `ManualEditFlag` nem field locks.
---
## Regra de ouro — isPastDue
Jobs **nunca** são fonte da verdade para `isPastDue`. O board continua calculando via `WorkOrderDerivedFields.IsPastDue` on-read. O cache `OperationalFlags.PastDue` é otimização opcional (`WorkOrderJobs:PastDueCache:Enabled`, default **false**).
---
## Configuração
```json
"WorkOrderJobs": {
"WeekRolled": { "Enabled": true, "RunAtUtc": "00:05", "DayOfWeek": "Monday" },
"PastDueCache": { "Enabled": false, "RunAtUtc": "00:10" }
}
```
Variáveis de ambiente (`.env.example`):
```text
WorkOrderJobs__WeekRolled__Enabled=true
WorkOrderJobs__PastDueCache__Enabled=false
```
---
## Endpoints admin (ops-only)
| Endpoint | Auth | Descrição |
|----------|------|-----------|
| `POST /api/workorders/jobs/week-rolled?sourceWeekStart=2026-06-16` | Admin | Reprocessa semana (segunda-feira). Idempotente. |
| `POST /api/workorders/jobs/past-due-cache` | Admin | Atualiza cache `OperationalFlags.PastDue` |
---
## Código entregue
| Camada | Arquivo |
|--------|---------|
| Ledger | `Data.SeaHavenIndustries/Models/WorkOrderWeekRolledLedger.cs` |
| Migration | `20260624210000_Phase5_DomainEvents.cs` |
| Data | `SeaHaven.DataServices/Implementation/WorkOrderDomainJobDataService.cs` |
| WeekRolled | `SeaHaven.Services/Implementation/WorkOrderWeekRolledService.cs` |
| PastDue cache | `SeaHaven.Services/Implementation/PastDueCacheService.cs` |
| Hosted | `Api.SeaHavenIndustries/HostedServices/*.cs` |
| API | `WorkOrderJobsController` |
| Testes | `SeaHavenIndustries.Tests/WorkOrderWeekRolledTests.cs` |
---
## Monitoramento
Logs estruturados com `CorrelationId` da semana. Ao concluir: `processed`, `skipped`, `failed`, `durationMs`.
**Alerta ops:** se `failed > 0` ou job não executou em 8 dias → investigar + `POST /jobs/week-rolled` manual.
---
## Critérios de aceite
- [x] Job semanal incrementa `carriedOver` para WOs elegíveis da semana anterior
- [x] Re-execução na mesma semana não duplica (ledger WO+semana)
- [x] Audit `WeekRolled` System com old/new `CarriedOver` e `CorrelationId`
- [x] `GET /api/workorders/board` continua com `isPastDue` derivado on-read
- [x] Endpoint admin permite reprocessar semana específica
- [x] Testes cobrem idempotência, terminal skip, audit e on-read correctness
---
## Fora de escopo
- Message queue / worker externo
- Alteração do contrato REST do board para usar `OperationalFlags`
- WOs week-only (`TargetWeek` sem `ScheduledDate`) — gate PO