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

154 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Fase 2 — Inline Edit + Concorrência
**Programa:** Work Orders Board
**Objetivo:** Edição spreadsheet-style célula a célula no board SHOC, com dual RowVersion, audit granular e regras de domínio no backend.
**Depende de:** [Fase 0](../phase-0/README.md), [Fase 1](../phase-1/README.md)
---
## Endpoints
### `PATCH /api/workorders/{id}/board`
Atualiza um único campo do board com validação otimista de concorrência.
**Autenticação:** Bearer JWT (`[Authorize]`)
**Request body:**
```json
{
"field": "scheduledDate",
"value": "2026-06-25",
"workOrderVersion": "<base64 RowVersion>",
"dispatchVersion": "<base64 RowVersion | null para campos WO-only>",
"primaryDispatchId": 123
}
```
| Campo | Obrigatório | Descrição |
|-------|-------------|-----------|
| `field` | Sim | Nome canônico do campo (case-insensitive) |
| `value` | Sim* | Valor serializado como string (*pode ser vazio para limpar datas) |
| `workOrderVersion` | Sim | `RowVersion` atual da WO (Base64) |
| `dispatchVersion` | Condicional | Obrigatório para campos Dispatch quando dispatch já existe |
| `primaryDispatchId` | Não | Valida que o dispatch pertence à WO |
**Responses:**
| HTTP | Body | Quando |
|------|------|--------|
| 200 | `WorkOrderBoardRowDto` | Sucesso — row completo com versões atualizadas |
| 409 | `WorkOrderBoardConflictDto` + `currentState` | Conflito de versão |
| 422 | `WorkOrderBoardValidationErrorDto` | Regra de domínio violada |
| 400 | `Response` | Erro genérico / argumento inválido |
**Exemplo:**
```bash
curl -X PATCH -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"field":"siteCode","value":"BK5","workOrderVersion":"AQAAAAAAAAA="}' \
"https://localhost:5001/api/workorders/1/board"
```
---
## Campos editáveis
| field (API) | Agregado | Coluna board | Audit FieldName |
|-------------|----------|--------------|-----------------|
| `woNumber` | WorkOrder | WO# | InternalWONumber |
| `workOrderType` | WorkOrder | Type | WorkOrderType |
| `siteCode` | WorkOrder | Site | SiteCode |
| `lifecycleStatus` | WorkOrder | Status | LifecycleStatus |
| `assignTo` | WorkOrder | Assignee | AssignTo |
| `dueDate` | WorkOrder | Due | DueDate |
| `scheduledDate` | WorkOrder | Scheduled | ScheduledDate |
| `targetWeek` | WorkOrder | Scheduled (week-only) | TargetWeek |
| `scheduleWeekOnly` | WorkOrder | Scheduled | ScheduleWeekOnly |
| `vendorId` | Dispatch | Vendor | VendorId |
| `apptDate` | Dispatch | Appt | ApptDate |
| `apptTime` | Dispatch/WO | Appt | ApptTime |
| `docStatus` | WorkOrder | Doc | DocStatus |
**Read-only:** `isPastDue`, `carriedOver`, `rescheduleCount` (badge derivado; count incrementado por regra).
---
## Dual RowVersion
| Agregado | Quando exigir versão |
|----------|---------------------|
| WorkOrder | Sempre (`workOrderVersion`) |
| Dispatch | Campos vendor/appt quando dispatch primário já existe (`dispatchVersion`) |
O GET board retorna `rowVersion`, `dispatchRowVersion` e `primaryDispatchId` em cada row.
**409 currentState:** inclui row recarregado do DB para refresh imediato no FE.
---
## Regras de domínio
| Regra | Comportamento |
|-------|---------------|
| Auto-schedule | `Incomplete` + `scheduledDate` + `assignTo` → `Scheduled` |
| Reschedule | Mudança de `scheduledDate` com data anterior → `rescheduleCount++` |
| Set-once tracking | Primeira `scheduledDate` → `OriginalDate` / `OriginalWeek` |
| Past Due block | Não permite mudar `lifecycleStatus` quando `isPastDue` |
| Cancel read-only | Status terminal (`Canceled`, `Closed`, `Complete`) bloqueia PATCH |
| WO# 11 dígitos | Normalização numérica + unicidade |
| Vendor ADR | Mutação em `Dispatch`; cria dispatch primário se ausente |
---
## Audit
- 1 evento por campo alterado (`FieldChanged`, `StatusChanged`, ou `AssignmentChanged`)
- Side effects (auto-schedule, rescheduleCount) geram eventos adicionais
- Field lock (`WorkOrderFieldLocks`) criado automaticamente em edições manuais
- Eventos Dispatch incluem `dispatchId`
---
## Código entregue
| Camada | Arquivo |
|--------|---------|
| DTOs | `SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs` |
| Helpers | `WorkOrderBoardMutationRules`, `WorkOrderNumberNormalizer`, `WorkOrderBoardFieldNames`, `WorkOrderBoardApptTimeParser` |
| Update service | `SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs` |
| Audit | `IWorkOrderAuditService.StageFieldChanged`, `LogFieldChangedAsync` |
| API | `WorkOrderController` — `PATCH {id}/board` |
| Testes | `WorkOrderBoardMutationRulesTests`, `WorkOrderNumberNormalizerTests`, `WorkOrderBoardUpdateServiceTests`, `WorkOrderBoardConcurrencyTests` |
---
## Gates de aceite
### Desenvolvimento
- [x] `PATCH /api/workorders/{id}/board` para todos os campos da matriz
- [x] Dual RowVersion validado; 409 com `currentState`
- [x] Auto-schedule Incomplete→Scheduled
- [x] RescheduleCount++ em reagendamento
- [x] Bloqueio status quando PastDue; reagendar limpa flag on-read
- [x] WO cancelado/closed rejeita edição (422)
- [x] WO# normalizado 11 dígitos + unicidade
- [x] Vendor/appt muta Dispatch; cria primário se ausente
- [x] 1 audit event por campo; field lock criado
- [x] 51 testes unitários total (25 Fase 2 + 26 Fases 0–1)
### Staging (pendente)
- [ ] Smoke Sync durante edição SHOC
- [ ] UAT dispatcher: editar células reais no board
- [ ] Latência PATCH aceitável (<200ms p95 Tier S)
---
## Próximo passo
**Fase 3** — Criação wizard/inline, soft cancel dedicado, ManualEditFlag na criação.