2026-07-17 14:39:47 -03:00
# Fase 1 — Weekly Board (leitura)
**Programa:** Work Orders Board
**Objetivo:** Endpoint window-based para carregar a visão semanal do SHOC (Mon– Fri + Unscheduled) com projeção das 14 colunas operacionais.
---
## Endpoints
### `GET /api/workorders/board`
Retorna WOs agendadas na semana + seção **Unscheduled** em uma única chamada.
**Autenticação:** Bearer JWT (`[Authorize]` )
**Query params:**
| Param | Obrigatório | Descrição |
|-------|-------------|-----------|
| `weekStart` | Sim (`BindRequired` ) | Segunda-feira da semana (`YYYY-MM-DD` ). Omitir retorna **400** . |
| `weekEnd` | Não | Default: `weekStart + 4 dias` (sexta). Janela pode ter até 7 dias. |
| `dispatchers` | Não | Lista de GUIDs; use `__unassigned__` para não atribuídos. **Ignorado** quando `myWorkOrders=true` . |
| `myWorkOrders` | Não | `true` → filtra `AssignTo == usuário logado` e tem **precedência** sobre `dispatchers` (não é AND). |
| `types` | Não | Valores enum `WorkOrderType` (`PM` , `PO` , `Emergency` , etc.) |
| `search` | Não | Busca contextual (site, WO#, location, dispatcher, trade, vendor, status) |
**Exemplo:**
```bash
curl -H "Authorization: Bearer < token > " \
"https://localhost:5001/api/workorders/board?weekStart=2026-06-22& myWorkOrders=true"
```
**Response:**
```json
{
"weekStart": "2026-06-22",
"weekEnd": "2026-06-26",
"counts": { "returned": 42, "total": 58 },
"unscheduled": [ /* WorkOrderBoardRowDto[] */ ],
"scheduled": [ /* WorkOrderBoardRowDto[] — agrupar por dayGroup no FE */ ]
}
```
- `counts.total` — WOs agendadas na semana (filtros dispatcher/tipo, **sem** search)
- `counts.returned` — WOs agendadas após aplicar search
- `dayGroup` — `monday` …`friday` para dias úteis; ** `null` no sábado/domingo** (a janela pode incluir fim de semana; o FE deve tratar `dayGroup` nulo)
- Referência de dia / past-due: **UTC** nesta fase (timezone de negócio fica para fase posterior)
### `GET /api/workorders/lookups/dispatchers`
Lista opções para o filtro/dropdown de assignee do board.
**Escopo atual (placeholder):** todos os users com `IsDeleted != true` — ainda **sem** filtro por role. Quando roles de assignee estiverem definidos, este lookup será restrito.
```json
[
{ "id": "guid", "name": "Jane Doe", "initials": "JD", "color": "#4A90D9 " }
]
```
---
## Matriz coluna → campo API
| Coluna board | Campos response |
|--------------|-----------------|
| WO# | `woNumber` , `rescheduleCount` , `carriedOver` |
| Type | `workOrderType` , `isPastDue` |
| Site | `siteCode` , `locationName` , `pocName` , `pocPhone` , `pocNotes` |
| Status | `lifecycleStatus` , `lifecycleStatusLabel` , `legacyStatus` |
| Assignee | `dispatcherId` , `dispatcherName` , `initials` , `color` |
| Due | `dueDate` |
| Scheduled | `scheduledDate` , `targetWeek` , `scheduleWeekOnly` , `dayGroup` |
| Vendor | `vendorId` , `vendorName` , `techName` , `techPhone` |
| Appt | `apptDate` , `apptTime` |
| Past Due | `isPastDue` (derivado on-read) |
| Carried | `carriedOver` |
| Reschedule | `rescheduleCount` |
| Doc | `docStatus` |
| Actions | FE only |
---
## Regras de derivação
### isPastDue
```
2026-07-28 14:01:22 -03:00
DueDate != null
AND DueDate < UTC hoje
AND LifecycleStatus NOT IN (Completed, Canceled)
2026-07-17 14:39:47 -03:00
```
2026-07-28 14:01:22 -03:00
`DueDate` null → `isPastDue = false` . Independent of `ScheduledDate` (Schedule On).
2026-07-17 14:39:47 -03:00
Implementação: `SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs`
### Vendor / Appt
Projeção read-only via `WorkOrder.PrimaryDispatchId` → `Dispatch` → `Vendor` ([ADR ](../phase-0/adr-vendor-source-of-truth.md )).
### Janela semanal
WO entra em **scheduled** se:
- `ScheduledDate` entre `weekStart` e `weekEnd` , **ou**
- `ScheduleWeekOnly == true` e `TargetWeek == weekStart`
**Unscheduled:** `ScheduledDate == null` e status não terminal (`LifecycleStatusSets.Terminal` ).
---
## Código entregue
| Camada | Arquivo |
|--------|---------|
| DTOs | `SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs` |
| Derived fields | `SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs` |
| Data | `SeaHaven.DataServices/Implementation/WorkOrderBoardDataService.cs` |
| Service | `SeaHaven.Services/Implementation/WorkOrderBoardService.cs` |
| API | `Api.SeaHavenIndustries/Controllers/WorkOrderController.cs` (`board` , `lookups/dispatchers` ) |
| Migration | `Data.SeaHavenIndustries/Migrations/20260701120000_Phase1_BoardIndexes.cs` |
| Testes | `SeaHavenIndustries.Tests/WorkOrderDerivedFieldsTests.cs` , `WorkOrderBoardServiceTests.cs` |
---
## Gates de aceite
### Desenvolvimento (concluído)
- [x] Endpoint `GET board` — semana + Unscheduled
- [x] Projeção 12/14 colunas de dados
- [x] `isPastDue` derivado on-read
- [x] Vendor via dispatch primário
- [x] Filtros dispatcher / My WOs / tipo / search
- [x] Contador X of Y
- [x] Índices Tier S
- [x] 14 testes unitários board (+ 12 Fase 0 = 26 total)
### Staging / produção (pendente)
- [ ] Sign-off CTO/PO (herda gates Fase 0)
- [ ] Dry-run migration Phase0 + Phase1 em staging
- [ ] Tier volume confirmado em staging/prod
- [ ] Smoke checklist Blazor / Sync / Portal
- [ ] Gate Vendor Portal regression (endpoints dispatch inalterados)
- [ ] Latência aceitável com volume real (Tier S: < 25k WOs)
- [ ] Feature flag SHOC board em staging
---
## Coexistência
Endpoints legados (`GetWorkOrderList` , `GetWorkorderById` , etc.) **não foram alterados** . O board usa contrato novo em rotas separadas.
---
2026-08-13 13:35:28 -03:00
## Add-On indicator (SH-184)
- Coluna `IsAddOn` persistida; legacy `WorkOrderType.AddOn` (7) backfilled.
- **Create:** cutoff preview (scheduled) ou hint manual quando unscheduled (`request.IsAddOn` ).
- **Patch schedule** (`scheduledDate` , `targetWeek` , `scheduleWeekOnly` ): servidor **recalcula** `IsAddOn` e audita mudanças.
- **Schedule cleared:** `IsAddOn = false` .
- Search `Types=AddOn` matches legacy type 7 **or** `IsAddOn` .
Contrato FE: [shoc-frontend-new PR #102 ](https://github.com/Sea-Haven-Industries/shoc-frontend-new/pull/102 ).
---
2026-07-17 14:39:47 -03:00
## Próximo passo
**Fase 2** — Inline edit + concorrência (dual RowVersion, audit por campo, PATCH granular).