shoc-backend/docs/adr/work-orders-board-api.md

194 lines
5.4 KiB
Markdown

# ADR: Work Orders Board API (Fase 1 read-only)
**Status:** Proposed — spikes G0, G1, G3 concluídas
**Data:** 2026-06-19
**Deciders:** Backend + FE (aprovação Fase 1 pending)
---
## Contexto
O Schedule Board (FE) organiza work orders por `ScheduledDate` em visão semanal. O backend atual expõe `GetWorkOrderList` — listagem CRUD paginada sem janela temporal, sem `siteCode`, sem vendor inline, sem `ScheduledDate` na projeção.
Spikes de referência:
- [Consumer Audit](../spikes/consumer-audit-getworkorderlist.md)
- [WorkOrderType](../spikes/work-order-type.md)
- [Search Strategy](../spikes/search-strategy.md)
---
## Decisão
### 1. Endpoint dedicado (sem `projection=`)
Criar endpoint **separado** para o Schedule Board. **Não alterar** `GetWorkOrderList`.
```http
GET /api/workorders/GetWorkOrdersBoard
?scheduledFrom=2026-06-01
&scheduledTo=2026-06-07
&assignee=guid1&assignee=guid2
&search=BK5
&status=Open&status=InProgress
&locationId=42
```
Rota alias: `GET /api/WorkOrder/GetWorkOrdersBoard` (consistente com controller existente).
**Rejeitado:**
- `GET /api/workorders/table`
- `GET /GetWorkOrderList?projection=table`
### 2. Window-based fetch (sem paginação Fase 1)
| Abordagem | Fase 1 |
|-----------|--------|
| `scheduledFrom` + `scheduledTo` | **Obrigatório** |
| `page` / `pageSize` | **Ausente** |
| Limite janela | Max **90 dias** (validação server-side) |
Response envelope:
```json
{
"scheduledFrom": "2026-06-01",
"scheduledTo": "2026-06-07",
"totalCount": 42,
"items": [
{
"row": { /* WorkOrderBoardRowDto */ },
"presentation": { /* BoardRowPresentation */ }
}
]
}
```
### 3. DTO — domínio vs presentation
**`WorkOrderBoardRowDto`** — dados persistidos / joináveis:
```csharp
public record WorkOrderBoardRowDto(
int Id,
string? WoNumber,
string? SiteCode,
string? LocationLabel,
DateOnly? ScheduledDate,
TimeOnly? ScheduledStart,
DateOnly? DueDate,
string? WorkOrderType,
string? ServiceType,
string? AssigneeId,
string? AssigneeName,
string? VendorCompany,
string? VendorTechnician,
string Status,
string? PocName,
string? PocPhone
);
```
**`BoardRowPresentation`** — calculado no mapper backend:
```csharp
public record BoardRowPresentation(
string StatusDisplay,
bool IsPastDue,
string? WorkOrderTypeDisplay
);
```
Mapper: `IWorkOrderBoardMapper` centraliza Past Due e type display.
### 4. Query EF (Fase 1)
- Filtro base: `istemplate != true`
- Janela: `ScheduledDate >= from && ScheduledDate < to.AddDays(1)`
- Includes: `Locations`, `AssignToUser`, `Dispatches` → `Vendor` (1 vendor por WO — first active dispatch)
- POC: `WorkOrderContacts` (primary)
- Service type: `Trade` ou category join (mínimo: `Trade`)
- **Não incluir** subquery `lastUpdated` (Comments/AuditLog)
### 5. Search (conforme spike)
- Search scoped à janela temporal
- Servidor: `StartsWith` em `InternalWONumber`, `SiteCode`
- Count ≤ **300**: retornar semana inteira; FE filtra title/location/serviceType
- Count > 300: exigir termo indexável ou retornar 400
- Assignee: `assigneeId[]` multi — **não** filtrar por nome
Índices (migration separada pós-aprovação):
- `IX_workOrders_ScheduledDate` (P1)
- `IX_workOrders_InternalWONumber`, `IX_workOrders_SiteCode` (P2, após fix tamanho coluna)
### 6. WorkOrderType
- Mapear coluna legada `WorkOrderType varchar(50)` na entidade EF
- Expor em `WorkOrderBoardRowDto`
- `IsPastDue` em presentation — **não** confundir com tipo
- Enum C# adiado até amostragem prod
### 7. Status mapping
- API retorna status **canônico** (`Open`, `InProgress`, `Completed`, `Cancelled`, `OnHold`)
- `StatusDisplay` via `IWorkOrderStatusMapper` (workshop G2 separado)
- Sem migration de enum DB
---
## Consequências
### Positivas
- 1 HTTP call por mudança de semana (FE)
- Contrato estável para board sem acoplar CRUD legado
- Performance previsível (janela + threshold 300)
- Blazor legado inalterado (EF local)
### Negativas / trade-offs
- Duplicação parcial de lógica de listagem vs `GetWorkOrderList` (aceitável — bounded contexts distintos)
- Vendor technician pode ser null Fase 1 se dispatch spike incompleto
- Setup DB local necessário para validação integrada
---
## Compliance com spikes
| Spike | Gate | Resultado |
|-------|------|-----------|
| Consumer Audit | G0 | `GetWorkOrderList` órfão no repo — endpoint dedicado aprovado |
| WorkOrderType | G1 | Mapear coluna EF; Overdue = presentation |
| Search Strategy | G3 | Window-first, threshold 300, StartsWith identificadores |
---
## Fora de escopo Fase 1
- PATCH inline edit
- Completion doc, media, flags, reorder
- Search vendor/tech server-side
- Paginação cross-week
- Alteração de `GetWorkOrderList`
---
## Próximos passos (implementação)
1. Aprovação explícita **Fase 1 only**
2. Migration: `WorkOrderType` + índice `ScheduledDate`
3. `GetWorkOrdersBoard` em `WorkOrderController` + `WorkOrderDataService`
4. DTOs + `IWorkOrderBoardMapper`
5. FE: `useWorkOrdersBoard({ from, to })` substituindo `RAW_ORDERS`
---
## Referências
- Gap analysis v3: `work_orders_api_gap_analysis_ce735946.plan.md`
- [`WorkOrderController.cs`](../../Api.SeaHavenIndustries/Controllers/WorkOrderController.cs)
- [`WorkOrderDataService.cs`](../../SeaHaven.DataServices/Implementation/WorkOrderDataService.cs)
- [`DispatchController.List`](../../Api.SeaHavenIndustries/Controllers/DispatchController.cs) — padrão janela temporal