# 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