# Work Orders Board & Search API Frontend integration contract for weekly board and advanced search filters. ## Base | Item | Valor | | ------------ | ------------------------------------------- | | Prefixo | `/api/workorders` (alias: `/api/WorkOrder`) | | Auth | `Authorization: Bearer {token}` (JWT) | | Content-Type | Não necessário (GET) | Paths no FE: `API_PATHS.workOrder.board`, `boardSearch`, `lookupsDispatchers`. ## Contrato (board semanal vs search) | Superfície | Endpoint | O que volta | | ------------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Week + Day (AAP) | `GET /board?weekStart&weekEnd` | Só WOs com data (ou `ScheduleWeekOnly` + `TargetWeek`) **nessa semana**. `unscheduled: []`. Sem `page`/`pageSize`. | | Contagens do board | `counts.total` / `counts.returned` | Scheduled da semana (antes/depois do search da barra). Nunca `0` se há rows. | | Lista flat | `GET /board/search` | Envelope paginado. `page` 0-based. `pageSize` default 100, **max 200**. | | Fila sem data / sem dispatcher | `GET /board/search` | Filtros (`dispatchers=__unassigned__`, statuses, janela Custom). **Não** entra no `/board`. | O AAP não tem pin Unassigned na tabela. Dated sem assignee entram no dia. Undated só em Advanced Filters / filtro Dispatcher. ## Quando usar qual endpoint | Modo FE | Endpoint | Quando | | --------------------------------------- | -------------------------- | ------------------------------------------------ | | Barra principal (`advApplied === null`) | `GET /board` | Semana agendada + dispatchers + tipo + search | | Filtros avançados aplicados | `GET /board/search` | Todos os filtros avançados + paginação/ordenação | | Lookup dispatchers | `GET /lookups/dispatchers` | Popular multi-select de dispatchers | **Não misture parâmetros:** `weekStart`/`weekEnd` só no `/board`; `datePreset` só no `/search`. ## Query builders (FE) - [`board-query-params.ts`](../../src/domain/work-orders/utils/board-query-params.ts) — `toBoardQueryParams`, `toBoardSearchQueryParams` - Arrays na query: repetição simples (`types=2&types=6`), via `toUrlSearchParams` em `work-orders-api.ts` ## Barra → `GET /board` | FE state | API param | Regra | | --------------- | ---------------------- | ----------------------------------------------------------- | | `search` | `search` | Enviar só se `length >= 2`; com 1 char, filtrar client-side | | `weekMonday` | `weekStart` | ISO `YYYY-MM-DD` (segunda) | | — | `weekEnd` | `weekStart + 4` (sexta) | | `dispatcherIds` | `dispatchers` | `__unassigned` → `__unassigned__` | | "My WOs" | `dispatchers={userId}` | Equivalente a `myWorkOrders=true` | | `typeFilter` | `types` / `overdue` | Tipos reais em `types`; past-due via `overdue=true` (OR) | ## Advanced → `GET /board/search` | FE state | API param | Regra | | ---------------- | --------------------- | ------------------------------------------- | | `page` | `page` | **0-based** (primeira página = `0`) | | `pageSize` | `pageSize` | Default `100`, max `200` | | `sortBy` | `sortBy` | `scheduledDate` \| `woNumber` \| `dueDate` | | `sortDir` | `sortDir` | `asc` \| `desc` | | `search` | `search` | `>= 2` chars | | `dateRange` | `datePreset` | PascalCase (ver abaixo) | | `customFrom/To` | `dateFrom` / `dateTo` | Obrigatórios se `datePreset=Custom` | | `sites` (IDs UI) | `sites` | **Site codes**, não location ID | | `types` | `types` | Integers de `WorkOrderType` reais | | overdue (UI) | `overdue` | `true` = past-due (`isPastDue`); ver abaixo | | `dispatchers` | `dispatchers` | `__unassigned` → `__unassigned__` | | `statuses` | `statuses` | Integers 1–10 | | `pmTypes` | `pmTypes` | Strings (labels Problem dropdown) | | `vendorTechs` | `vendorIds` | Integers (resolvidos por companyName) | | `docs` | `docStatuses` | No=2, Yes=1, NN=3 | | `severities` | `severities` | Integers `1`–`5` (Emergency/Reactive) | | `rescheduled` | `rescheduled` | `true` = reschedule count ≥ 2 | | `carriedOver` | `carriedOver` | `true` = carried-over count ≥ 2 | | `addOn` | `addOn` | `true` = `isAddOn` indicator | | `flagColors` | `flagColors` | Multi `#RRGGBB` (board flag palette) | | `internalOnly` | `internalOnly` | `true` = WO# starts with `SH` | | `hasUplift` | `hasUplift` | `true` = WO has standing uplift | | `upliftStatuses` | `upliftStatuses` | With `hasUplift`; see below | ### `severities` Multi-select SEV 1–5. Applies to Emergency/Reactive work orders with a transcribed APM severity. ### Indicators (`rescheduled`, `carriedOver`, `addOn`) Boolean flags matching board badge thresholds (counters ≥ 2 for reschedule/carried-over). `addOn` filters the `isAddOn` indicator (not a WO type). ### `flagColors` Repeated `#RRGGBB` values from the fixed board flag palette (`WorkOrderFlagColors`). ### `internalOnly` When `true`, only work orders whose number starts with `SH` (internal placeholder). ### Uplift filters | FE state | API param | Regra | | ---------------- | ---------------- | ------------------------------------------------ | | `hasUplift` | `hasUplift` | `true` = WO has non-cancelled/non-revoked uplift | | `upliftStatuses` | `upliftStatuses` | Optional refinement when `hasUplift=true` | Allowed `upliftStatuses`: `pending`, `approved`, `auto_approved`, `rejected` (excludes cancelled/revoked). Exemplo: pending uplifts only — `hasUplift=true&upliftStatuses=pending`. ## Backend producer (`shoc-backend` `dev`) These facet keys are handled on `GET /board/search` in `shoc-backend` `origin/dev` ([#45](https://github.com/Sea-Haven-Industries/shoc-backend/pull/45), [#65](https://github.com/Sea-Haven-Industries/shoc-backend/pull/65)). There is no frontend deployment fence. - `IsAddOn` is owned by migration `20260730150000_WoIsAddOn` (legacy type-7 backfill). - `20260813193000_SH121_BoardSearchFacets` is a **no-op** so both PRs can land without a duplicate column. - Pagination matches this contract: `page` is 0-based; `pageSize` default is 100, max is 200. - `GET /board` returns only scheduled-in-week rows. `unscheduled` is always `[]`. Undated / global Unassigned live on `GET /board/search`. - `counts.total` / `counts.returned` are scheduled-in-week (before/after the bar search). - Aveta (`avetaOnly`) remains omitted until `avetaRequired` is confirmed. ### `datePreset` | FE key | API value | | --------------- | ------------- | | `this-week` | `ThisWeek` | | `last-week` | `LastWeek` | | `this-month` | `ThisMonth` | | `last-3-months` | `Last3Months` | | `next-week` | `NextWeek` | | `next-month` | `NextMonth` | | `custom` | `Custom` | ### `types` — WorkOrderType Somente tipos reais de work order. **Não** use sentinel `99` para overdue. | Label FE | API | | --------- | -------------------------------------------- | | PM | `2` | | Emergency | `3` | | Reactive | `6` | | Add-On | `7` | | Other | tipo real do enum (não é filtro de past-due) | ### `overdue` — past-due | FE / UI | API param | Regra | | ------- | -------------- | ----------------------------------------- | | Overdue | `overdue=true` | Filtra work orders past-due (`isPastDue`) | `types` e `overdue` combinam com **OR**: uma WO entra no resultado se corresponder a algum `types` **ou** estiver overdue (quando `overdue=true`). Exemplos: - Só PM e Emergency: `types=2&types=3` - Só past-due: `overdue=true` - PM **ou** past-due: `types=2&overdue=true` ### `statuses` — LifecycleStatus Incomplete=1, Pending=2, Scheduled=3, En Route=4, On Site=5, In Progress=6, Completed=7, Rescheduled=8, Canceled=9, Pending Quote=10. ### `docStatuses` Pending (`No`)=2, Uploaded (`Yes`)=1, N/N (`NN`)=3. ## Respostas ### `/board` ```json { "weekStart": "2026-07-14", "weekEnd": "2026-07-18", "counts": { "total": 42, "returned": 10 }, "scheduled": [], "unscheduled": [] } ``` ### `/board/search` ```json { "items": [], "totalCount": 150, "page": 0, "pageSize": 100, "totalPages": 2, "hasPrevious": false, "hasNext": true } ``` ## Erros HTTP | Situação | Status | | --------------------------------------------------- | ------ | | `weekEnd < weekStart` ou janela > 7 dias | 400 | | `datePreset=Custom` sem datas / `dateTo < dateFrom` | 400 | | `sortBy` inválido | 400 | | Sem token / expirado | 401 | Body típico: `{ "status": "Error", "message": "..." }` — o FE exibe `message` via `ApiError`. ## Add-On indicator See [addon-indicator.md](./addon-indicator.md) (SH-184): backend recalculates `isAddOn` on schedule move/clear with audit; FE only reflects the API value. ## Validação UI O side sheet de filtros avançados valida Custom (From/To obrigatórios e `dateTo >= dateFrom`) antes de aplicar, para evitar 400 desnecessário.