# 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 " \ "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 ``` DueDate != null AND DueDate < UTC hoje AND LifecycleStatus NOT IN (Completed, Canceled) ``` `DueDate` null → `isPastDue = false`. Independent of `ScheduledDate` (Schedule On). 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. --- ## 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). --- ## Próximo passo **Fase 2** — Inline edit + concorrência (dual RowVersion, audit por campo, PATCH granular).