shoc-frontend-new/docs/work-orders/board-search-api.md

211 lines
11 KiB
Markdown
Raw Normal View History

# 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.