docs(work-orders): add Phase 0 governance documentation

This commit is contained in:
Arthur Bassi 2026-06-29 17:22:31 -03:00
parent 5d5b3234c4
commit 65c1cdc7e9
17 changed files with 2340 additions and 0 deletions

View file

@ -0,0 +1,299 @@
# Auditoria — API Work Orders vs Frontend (WorkOrders.tsx)
**Data:** 2026-06-19
**Fonte da verdade (FE):** `seahaven.desing/src/pages/WorkOrders.tsx`
**Backend auditado:** `seaheven.api`
---
## Resumo executivo
| Métrica | Valor |
|---------|-------|
| Compatibility Score | **24/100** |
| Backend Readiness | **Not Ready** |
| Confidence | **High** |
A API atual foi construída para **CRUD legado** (listagem paginada, detalhe rico, dispatch). O frontend `WorkOrders.tsx` define um **Schedule Board operacional** (visão semanal, 14 colunas, edição inline, advanced search, completion doc WO-level). Os contratos são fundamentalmente diferentes.
**O que funciona hoje:** id, dueDate, location (parcial), CRUD básico, detalhe rico, comments, audit field-based, mídia WO-level, reatribuição, dispatch.
**O que falta:** listagem window-based, 14 colunas board, PATCH inline, campos derivados, advanced search, completion doc WO-level, jobs Past Due/Carried Over, status 10 labels, WO# 11 dígitos.
---
## Endpoints que existem hoje
| Endpoint | O que entrega |
|----------|----------------|
| `GET GetWorkOrderList` | Lista **paginada** (default 12): id, número, título, location, priority, status, dueDate, assignedTo, lastUpdated |
| `GET Getworkorders` / `GetworkordersDD` | Lista legada para admin/dropdown |
| `GET GetFilteredWorkorder` / `2` | Filtros legados (assignee, location, priority, status, due date buckets) — carrega tudo em memória |
| `GET GetWorkorderById` / `{id}` | Detalhe: título, descrição, datas, status, problem, trade, POC, attachments, comments, audit, dispatches |
| `POST AddWorkorder` | Criação via form: título, descrição, assignee, dueDate, location, priority, mídia, contacts/categories |
| `POST/PUT EditWorkorder` | Update parcial via form (subset mapeado ao service) |
| `POST ChangeStatus` | Muda status (string livre) + audit |
| `POST ChangeAssignment` | Reatribui dispatcher + audit |
| `POST AddComment` / `AddCommentJson` | Comentário + upload opcional |
| `GET GetComments` / `GetCommentsByWorkorderId` | Lista comentários |
| `DELETE DeleteWorkorder` | **Delete físico** (não soft cancel) |
| Dispatch endpoints | Vendor, checklist, signoff, verify — **nível dispatch**, não board |
**Lookups relacionados (outros controllers):** `Common/GetUsers`, `Location/GetLocationList`, `Vendor/GetVendorList` — paginados, contrato diferente do FE.
**Documentado mas não implementado no código:** `GET GetWorkOrdersBoard`, `IWorkOrderBoardMapper`, `IWorkOrderStatusMapper` (ver `docs/adr/work-orders-board-api.md`).
---
## 1. Listagem da tela principal (visão semanal)
| Frontend espera | API entrega hoje | Gap |
|-----------------|------------------|-----|
| 1 call por semana (`weekStart` / `weekEnd`) | Paginação `page` / `pageSize` sem janela temporal | Sem fetch window-based |
| WOs agrupados por dia (Mon–Fri) | Lista flat paginada | Sem `scheduledDate` na projeção de listagem |
| Seção **Unscheduled** sempre visível no topo | Só WOs da página atual | Sem WOs sem data retornados independente da semana |
| `targetWeek` (scheduling week-only) | Campo inexistente na entidade/API | Não implementado |
| Contador `X of Y` da semana ativa | `totalCount` global paginado | Contagem inadequada para o board |
| Skeleton Mon–Fri vazios | N/A (FE-only) | OK — responsabilidade do FE |
**Evidência BE:** `WorkOrderDataService.GetWorkOrderListPagedAsync` — projeção L163-183, sem `ScheduledDate`, `SiteCode`, vendor, type.
---
## 2. Colunas da tabela (`COLS`)
Definidas no FE em `WorkOrders.tsx` L4234-4249.
| Coluna FE | Campo(s) FE | API hoje | Gap |
|-----------|-------------|----------|-----|
| Grip | drag reorder | Ausente | FE local-only; BE não persiste ordem |
| Flag | flag pessoal | Ausente | FE session-only |
| **SITE** | `site`, `pocName`, `pocPhone`, `pocNotes` | List: `location` (name); Detail: POC via `WorkOrderContacts` | Sem `siteCode` na listagem; sem POC na lista |
| **WO** | `woNumber`, `rescheduleCount`, `carriedOver` | `internalWONumber` / `workerOrderNumber` | Formato ≠ 11 dígitos; sem badges ↻ ↷N |
| **TYPE OF WO** | `type` (PM / Reactive / Emergency / Add-On / Overdue) | Não exposto (`WorkOrderType` em `db.txt`, não mapeado em EF) | Coluna inoperante |
| **ASSIGNED TO** | `dispatcherId` + avatar/cor | `assignedTo` (nome completo); `AssignTo` = GUID Identity | Sem id/initials/color para avatar |
| **SCHEDULE ON** | `scheduledOn`, `targetWeek` | Detail: `scheduledDate`; **list: ausente** | Listagem sem data de agendamento |
| **DUE DATE** | `dueDate` | `dueDate` | **OK** na listagem |
| **SERVICE** | `pm` | Detail: `trade` / `problem`; list: ausente | Coluna vazia na lista |
| **VENDOR** | `company`, `tech`, `techPhone` | Detail: `dispatches` → vendor; list: ausente | Coluna vazia na lista |
| **APPT TIME** | `apptTime` (ex: "07:00 – 09:00") | Entidade: `ScheduledStart`; sem `ScheduledEnd` | Janela de horário incompleta |
| **STATUS** | 10 labels + overlay Past Due | String legada DB + enum 5 valores API | Sem mapping para status operacionais do FE |
| **COMP DOC** | `docStatus` (Yes / No / NN) | Ausente no WO | Completion existe só no **dispatch** (`VerifyDispatch`) |
| Actions | View / Edit | `GetWorkorderById` separado | Detalhe existe; contrato diferente |
---
## 3. Filtros e busca
### Barra principal (FE L5083-5099)
| Frontend | API hoje | Gap |
|----------|----------|-----|
| **Dispatcher** multi-select + `__unassigned__` + default usuário logado | `assignee` single string; `__unassigned` suportado em `GetWorkOrderList` | Multi-select e default "My WOs" não cobertos |
| **Semana ativa** | Sem filtro `ScheduledDate` | Filtro semanal inexistente |
| **Tipo** (All / PM / Reactive / Emergency / Add-On / Overdue) | Sem filtro por work order type | Inexistente |
| **Busca contextual** na semana (site, wo#, dispatcher, location, pm, company, tech, status) | Search em 5 campos (`InternalWONumber`, `WorkerOrderNumber`, `Title`, `Location`), escopo global paginado | Campos e escopo incompatíveis |
### Advanced Search (FE L4255-4324)
| Frontend | API hoje | Gap |
|----------|----------|-----|
| Lista plana cross-week | Ausente | Sem endpoint dedicado |
| Date range (this-week, last-week, this-month, last-3-months, next-week, next-month, custom) | `GetFilteredWorkorder` com due date buckets legados | Semântica diferente |
| Filtros: sites[], types[], dispatchers[], statuses[], pmTypes[], vendorTechs[], docs[] | Parcial em endpoints legados | Cobertura incompleta |
---
## 4. Edição inline (spreadsheet-style)
| Frontend | API hoje | Gap |
|----------|----------|-----|
| PATCH por campo (site, wo#, type, dispatcher, schedule, due, service, vendor, appt, status…) | `EditWorkorder` (form multipart) + `ChangeStatus` + `ChangeAssignment` separados | Sem update granular por campo |
| Auto-schedule: `Incomplete` → `Scheduled` quando data + dispatcher | Ausente no BE | Regra `maybeAutoSchedule` só no FE |
| `rescheduleCount++` ao mudar `scheduledOn` | Campo inexistente | Badge ↻ impossível |
| Limpar `isPastDue` ao reagendar para data futura | Campo derivado inexistente | Past Due não limpa via API |
| Bloquear mudança de status se `isPastDue` | `ChangeStatus` aceita qualquer string | Sem validação 422 |
| WO# único 11 dígitos normalizado | Gerador `WO-{yyyyMMdd}-{random}` ou sync sequencial `10000001` | Formato e unicidade incompatíveis |
| WO cancelado read-only | Sem enforcement no BE | Só lógica no FE (`WOSlideOver` L2380) |
**Evidência BE:** `WorkOrderController.Editworkorder` L182-192 mapeia subset para `UpdateWorkOrderDTO` — ignora `Problem`, `Trade`, `ScheduledDate`, `Source` apesar de existirem em `EditWorkorder_DTO`.
---
## 5. Criação de WO
| Frontend | API hoje | Gap |
|----------|----------|-----|
| Wizard `NewWOWizard` + inline `InlineRow` | `POST AddWorkorder` | Campos do wizard majoritariamente ausentes |
| Campos: site, type, scheduledOn, targetWeek, pm, company, tech, appt, POC | `CreateWorkOrderDTO`: title, description, assignTo, dueDate, location | Contrato incompleto |
| Status inicial `Incomplete` | Cria com `WorkOrderStatus.Open` | Status inicial diferente |
| `scheduleWeekOnly` / `targetWeek` | Ausente | Week-only scheduling impossível |
| Navegação automática para semana do WO criado | Depende de listagem semanal | Listagem incompatível |
---
## 6. Cancelamento
| Frontend | API hoje | Gap |
|----------|----------|-----|
| Soft cancel → status `Canceled` | `POST ChangeStatus(id, "Canceled")` manual | Sem endpoint `POST cancel` dedicado |
| WO cancelado não editável | Sem bloqueio no BE | Enforcement só no FE |
| Delete permanente (Admin) | `DELETE DeleteWorkorder` — delete físico | Comportamento diverge do soft cancel FE |
---
## 7. Slide-over (`WOSlideOver`)
| Tab FE | API hoje | Gap |
|--------|----------|-----|
| **Info** | `GET GetWorkorderById` | Omite `siteCode`; sem campos derivados FE (`isPastDue`, `rescheduleCount`, etc.) |
| **Comments** | `AddComment` + `GetCommentsByWorkorderId` | Formato FE `{authorId, text, time}` vs BE `{Commenttext, FirstName, Documents}` |
| **Audit Log** | `WorkOrderAuditLog` retornado no detalhe | Schema `{fieldName, oldValue, newValue, action}` vs FE `{type: manual\|system, dispatcherId?, action, time}` |
| **Completion Doc** | `SignOffName` / `SignOffAttachment` no WO; `VerifyDispatch` no dispatch | Sem template por service type, PDF, `docStatus` WO-level |
| **Extra Docs / Media** | `BeforPhoto`, `AfterPhoto`, `workOrderAttachments` | Sem `MediaFile.category`; sem API extra docs dedicada |
---
## 8. Regras de sistema (background jobs)
| Frontend assume | API hoje | Gap |
|-----------------|----------|-----|
| Job Past Due: `scheduledOn < hoje` + não terminal → `isPastDue=true` | Ausente (zero `BackgroundService` / Hangfire / Quartz no repo) | Flag nunca setada automaticamente |
| Job Carried Over: virada de semana → `carriedOver++` | Ausente | Contador ↷N nunca incrementado |
| Type Overdue promotion | Ausente | Filtro/tipo Overdue não automático |
| Sync APM | `SyncController` POST manual (DynamoDB → SQL) | On-demand, não integrado à UX do board |
---
## 9. Lookups (catálogos)
| FE usa (mock estático) | API relacionada | Gap |
|------------------------|-----------------|-----|
| `DISPATCHERS` (id, name, initials, color) | `Common/GetUsers` | Sem initials/color; paginado; ids são GUIDs |
| `SITE_OPTIONS` (code → city, state) | `Location/GetLocationList` | Sem lookup por site code; sem mapeamento BK5 → Dallas |
| `PM_TYPES` | Ausente | Sem catálogo de service types |
| `TECHNICIANS` (name, company, phone) | `Vendor/GetVendorList` | Sem endpoint technicians |
| `WEEK_RANGES` (Mon–Fri + flag LIVE) | Ausente | Sem endpoint weeks |
---
## Contrato de dados — campo a campo
| Campo FE (`WorkOrder`) | Campo BE | Status |
|------------------------|----------|--------|
| `id` | `WorkOrder.Id` | SUPPORTED |
| `woNumber` | `InternalWONumber` / `WorkerOrderNumber` | PARTIALLY_SUPPORTED — formatos conflitantes |
| `site` | `SiteCode` | PARTIALLY_SUPPORTED — entidade tem; API omite na listagem |
| `type` | — (`WorkOrderType` só em SQL) | NOT_SUPPORTED |
| `dispatcherId` | `AssignTo` (GUID) | PARTIALLY_SUPPORTED |
| `scheduledOn` | `ScheduledDate` | PARTIALLY_SUPPORTED — list omite |
| `dayGroup` / `dayLabel` | — | NOT_SUPPORTED (derivado) |
| `location` | `Locations.Name` | SUPPORTED (via join) |
| `pm` | `Trade` / `Problem` | PARTIALLY_SUPPORTED |
| `company` / `tech` | `Dispatches` → `Vendor` | PARTIALLY_SUPPORTED — não inline na lista |
| `techPhone` | — | NOT_SUPPORTED |
| `apptTime` | `ScheduledStart` (sem end) | PARTIALLY_SUPPORTED |
| `status` | `Status` (string livre) | PARTIALLY_SUPPORTED — 10 vs 5+ formatos |
| `docStatus` | — | NOT_SUPPORTED |
| `pocName` / `pocPhone` / `pocNotes` | `WorkOrderContacts` | PARTIALLY_SUPPORTED — notes ausente |
| `dueDate` | `DueDate` | SUPPORTED |
| `rescheduleCount` | — | NOT_SUPPORTED |
| `carriedOver` | — | NOT_SUPPORTED |
| `originalWeek` / `originalDate` | — | NOT_SUPPORTED |
| `isPastDue` | — | NOT_SUPPORTED (derivado) |
| `targetWeek` | — | NOT_SUPPORTED |
---
## Regras de negócio — FE vs BE
| Regra FE | Evidência FE | BE hoje | Status |
|----------|--------------|---------|--------|
| Auto-schedule Incomplete → Scheduled | `maybeAutoSchedule` L500-506 | Ausente | NOT_SUPPORTED |
| Reschedule incrementa contador | `updateScheduledOn` L4856 | Ausente | NOT_SUPPORTED |
| Reschedule limpa Past Due | L4851-4857 | Ausente | NOT_SUPPORTED |
| Status bloqueado em Past Due | `StatusCell` L1120-1159 | `ChangeStatus` sem validação | NOT_SUPPORTED |
| WO# único 11 dígitos | `findDuplicateWONumber` L4897 | Gerador random; `InternalNumberExistsAsync` sem normalização | NOT_SUPPORTED |
| Cancel → Canceled, read-only | `cancelWO` L4946; `WOSlideOver` L2380 | `ChangeStatus` manual | PARTIALLY_SUPPORTED |
| Carried over job semanal | Audit derivado L2414-2416 | Sem job | NOT_SUPPORTED |
| Completion doc por tipo de serviço | `CompDocDialog` L3902+ | Dispatch verify only | NOT_SUPPORTED (WO-level) |
| Emergency/Reactive → media flow | `isMediaWO` L332-334 | Mídia WO genérica | PARTIALLY_SUPPORTED |
---
## Diagrama — contrato atual vs esperado
```
HOJE (GetWorkOrderList) FRONTEND (WorkOrders.tsx)
───────────────────── ─────────────────────────
Paginação 12/page 1 call = semana inteira
Sem ScheduledDate Agrupamento Mon–Fri
Sem siteCode, type, vendor 14 colunas preenchidas
Sem unscheduled semantics Seção Unscheduled fixa
5 status legados 10 status + Past Due overlay
CRUD admin Schedule Board operacional
EditWorkorder (form) PATCH inline por célula
```
---
## Cobertura por área
| Área | Cobertura estimada |
|------|-------------------|
| Listagem board (visão semanal) | ~5% |
| Colunas da tabela (14) | ~15% |
| Filtros e busca | ~10% |
| Edição inline | ~0% |
| Criação (wizard/inline) | ~25% |
| Slide-over (detalhe) | ~40% |
| Completion doc WO-level | ~0% |
| Background jobs | ~0% |
| Lookups/catálogos | ~20% |
---
## Critical blockers
1. **Sem API window-based** para visão semanal + Unscheduled — tela principal não carrega.
2. **Contrato de listagem incompatível** com 14 colunas do board.
3. **Campos persistidos ausentes** — `rescheduleCount`, `carriedOver`, `targetWeek`, `docStatus`, `isPastDue`.
4. **Sem PATCH inline** — edição spreadsheet-style impossível.
5. **Advanced search cross-week inexistente.**
6. **Status 10 valores + Past Due** sem mapping layer implementado.
7. **Completion doc WO-level inexistente** — coluna COMP DOC inoperante.
8. **WO# 11 dígitos único** não suportado.
9. **Jobs Past Due / Carried Over ausentes.**
---
## Non-critical gaps
- Reorder intra-dia persistido (FE já local-only)
- Flags pessoais (FE session-only)
- Paginação 12/24/48/96 (FE não implementou)
- Bulk select (FE não implementou)
- `[Authorize]` comentado no `WorkOrderController` (risco de segurança)
---
## Referências
- Frontend: `seahaven.desing/src/pages/WorkOrders.tsx`
- Controller: `Api.SeaHavenIndustries/Controllers/WorkOrderController.cs`
- Data service: `SeaHaven.DataServices/Implementation/WorkOrderDataService.cs`
- Entidade: `Data.SeaHavenIndustries/Models/WorkerOrder.cs`
- ADR (proposto, não implementado): `docs/adr/work-orders-board-api.md`
- Spikes: `docs/spikes/consumer-audit-getworkorderlist.md`, `search-strategy.md`, `status-mapping.md`, `work-order-type.md`
---
## Classificação final
| Métrica | Valor |
|---------|-------|
| Compatibility Level | **Minimally Compatible** |
| Backend Readiness | **Not Ready** |
| Confidence | **High** |
Este documento reporta apenas **findings** — gaps entre o que a API entrega hoje e o que o frontend especifica. Não inclui propostas de implementação.

View file

@ -0,0 +1,907 @@
# Roadmap de Implementação — Work Orders API vs Frontend Board
**Documento:** Relatório consolidado de arquitetura e entrega
**Fonte da verdade (frontend):** `seahaven.desing/src/pages/WorkOrders.tsx`
**Backend:** `seaheven.api`
**Auditoria base:** [auditoria-work-orders-api-vs-frontend.md](auditoria-work-orders-api-vs-frontend.md)
**Estado atual:** Compatibility Score **24/100** — backend **Not Ready**
**Nota técnica do programa:** **9.0/10** (meta kickoff enterprise **9.5+**)
**Escopo:** capacidades de negócio, domínio, governança, fases, riscos e validação — sem design de API, schema ou código
---
## 1. Executive Summary
### 1.1 Readiness geral
O backend é **maduro para CRUD legado, dispatch, vendor portal, checklist/sign-off e audit**, mas foi construído para **listagem paginada administrativa**, não para o **Schedule Board operacional** que o frontend define. A tela principal do board **não pode funcionar** com os contratos atuais.
| Dimensão | Avaliação |
|----------|-----------|
| Tela principal (visão semanal) | ~5% |
| 14 colunas do board | ~15% |
| Edição inline | ~0% |
| Completion doc WO-level | ~0% |
| Background jobs | ~0% |
| Detalhe / slide-over | ~40% |
| **Média ponderada** | **~24%** |
### 1.2 Problemas arquiteturais principais
1. **Paradigma de contrato incompatível** — paginação global vs. janela semanal com seção Unscheduled fixa.
2. **Projeção de listagem insuficiente** — 9 das 14 colunas do board não são alimentadas pela listagem atual.
3. **Ausência de camada de domínio operacional** — campos derivados, tipo de WO e status operacionais ausentes ou não expostos.
4. **Database drift** — `WorkOrderType` existe no SQL legado mas não no EF; possíveis outras colunas desalinhadas.
5. **Modelo de mutação inadequado** — form multipart vs. edição granular por célula.
6. **Sem automação de sistema** — zero infraestrutura de jobs; regras do FE nunca executam no servidor.
7. **Cinco consumidores paralelos** — SHOC, Blazor EF, Vendor Portal, Sync/Lambda, e-mails — sem governança de ownership.
8. **Duas trilhas paralelas** — Blazor acessa EF direto; API REST é integração do SHOC.
### 1.3 Maior risco do programa
**Governança de ownership de dados** — quem pode alterar cada campo entre SHOC, Vendor Portal, Sync, Blazor e Jobs. A arquitetura de domínio resolve fontes da verdade; a governança operacional é o gate final.
### 1.4 Decisões de produto registradas
- **Novas funcionalidades do board** → somente via API REST para o frontend React (SHOC).
- **Blazor (`SeaHavenIndustries`)** → não recebe board, inline edit, jobs, advanced search nem completion doc WO-level; manutenção mínima até sunset do módulo WO.
- **Vendor Portal** → permanece ativo; não-regressão obrigatória.
- **DynamoDB/Sync** → ponte temporária; aposentar após SHOC ingerir direto ([TODO.md](../TODO.md)).
### 1.5 Complexidade e esforço
| Métrica | Valor |
|---------|-------|
| Complexidade geral | **Alta** |
| Esforço total | **XL** (6–9 meses, 2 squads; ou 4–6 meses, 3 squads) |
| Fase 0 — Fundação domínio + governança | L |
| Fase 1 — Board semanal | L |
| Fase 2 — Edição inline + concorrência | L |
| Fase 3 — Criação e cancelamento | M |
| Fase 4 — Busca avançada | M |
| Fase 5 — Domain events (jobs) | M |
| Fase 6 — Completion doc e slide-over | M |
| Fase 7 — Rollout e produção | M |
### 1.6 Gates bloqueantes da Fase 0
Nenhuma implementação de board inicia sem:
1. Domain Architecture Review (DAR) aprovado
2. **Data Ownership Model** assinado
3. **Field Ownership Matrix** assinada
4. **Audit Event Contract** aprovado
5. Volume Discovery Report (sem premissa de volume)
6. Database drift report (SQL vs EF)
7. Spike vendor/dispatch
8. Data migration dry-run
9. RowVersion multi-agregado definido
---
## 2. Capability Gap Assessment
### 2.1 Weekly Scheduling Board — **Critical**
- **Atual:** paginação sem janela temporal, sem Unscheduled.
- **Desejado:** 1 call/semana, Mon–Fri + Unscheduled, contador `X of Y`.
### 2.2 Work Order Lifecycle — **High**
- **Atual:** form parcial, status `Open`, delete físico.
- **Desejado:** wizard/inline completo, `Incomplete`, soft cancel read-only.
### 2.3 Inline Editing — **Critical**
- **Atual:** `EditWorkorder` multipart; endpoints separados.
- **Desejado:** update granular por célula com regras embutidas.
### 2.4 Search & Filtering — **High**
- **Atual:** 5 campos, escopo global, assignee single-select.
- **Desejado:** busca contextual na semana + advanced search cross-week.
### 2.5 Status Management — **Critical**
- **Atual:** enum 5 valores + string livre; sem Past Due.
- **Desejado:** 10 labels + flag Past Due separada; auto-schedule; bloqueios.
### 2.6 Completion Documentation — **High**
- **Atual:** sign-off básico; verify só no dispatch.
- **Desejado:** `docStatus` WO-level (Yes/No/NN); templates por serviço.
### 2.7 Vendor Management (board) — **High**
- **Atual:** vendor só no detalhe via dispatch.
- **Desejado:** colunas VENDOR/APPT na lista; edição via dispatch primário.
### 2.8 Scheduling Intelligence — **Critical**
- **Atual:** `ScheduledDate` no detalhe; sem `targetWeek`, `ScheduledEnd`.
- **Desejado:** agendamento semântico completo; badges ↻ e ↷N.
### 2.9 Audit & Tracking — **Medium**
- **Atual:** audit field-based legado.
- **Desejado:** `{type: manual|system, dispatcherId?, action, time}` + eventos de sistema.
### 2.10 Background Automation — **Critical** (como otimização, não fonte da verdade)
- **Atual:** zero jobs.
- **Desejado:** WeekRolled, cache opcional PastDue; domínio recalcula on-read.
### 2.11 Lookup Data — **High**
- **Atual:** lookups paginados incompatíveis; sem PM types, technicians.
- **Desejado:** dispatchers (initials/color), sites por code, PM types, technicians.
### 2.12 Reporting / Contadores — **Medium**
- **Atual:** `totalCount` global.
- **Desejado:** `X of Y` semanal.
### 2.13 Security — **High**
- **Atual:** `[Authorize]` comentado no `WorkOrderController`.
- **Desejado:** auth ativa; default "My WOs".
---
## 3. Dependency Map
```mermaid
flowchart TD
subgraph phase0 [Fase0_Fundacao]
DAR[DAR e 3 artefatos]
DOM[Agregados dominio]
VOL[Volume Discovery]
OWN[Data Ownership]
end
subgraph phase1 [Fase1_Board]
B1[Listagem semanal]
B2[14 colunas]
end
subgraph phase2 [Fase2_Mutacao]
M1[Inline edit]
M2[Concorrencia]
end
subgraph phase3 [Fase3_Lifecycle]
L1[Criacao wizard]
L2[Cancelamento]
end
subgraph phase45 [Fase4_5]
S1[Search]
E1[Domain events]
end
subgraph phase6 [Fase6_Completion]
C1[DocStatus slideover]
end
DAR --> DOM
DOM --> B1
OWN --> M1
VOL --> B1
B1 --> B2 --> M1
M1 --> L1
B2 --> S1
DOM --> E1
B2 --> C1
```
**Ordem obrigatória:** Fase 0 → Board → Inline → Lifecycle → Search/Events → Completion → Rollout.
---
## 4. Domain Model
### 4.1 Agregados e limites (anti God Entity)
```mermaid
flowchart TB
subgraph root [Aggregate Root WorkOrder]
CORE[Core Lifecycle Assignment]
SCH[Scheduling slice]
TRK[Tracking slice]
COMP[Completion slice]
ANA[Analytics slice]
end
subgraph separate [Agregado separado]
DISP[Dispatch fonte vendor]
end
AUD[WorkOrderAuditLog append-only]
CORE --> SCH
CORE --> TRK
CORE --> ANA
CORE --> COMP
CORE --> DISP
CORE --> AUD
```
| Agregado | Responsabilidade |
|----------|------------------|
| **WorkOrder (core)** | Id, WoNumber, Type, LifecycleStatus, SiteCode, LocationId, DueDate, AssignTo, Title, RowVersion |
| **Scheduling slice** | ScheduledDate, ScheduledStart/End, TargetWeek, ScheduleWeekOnly |
| **Tracking slice** | OriginalWeek, OriginalDate (set-once) |
| **Analytics slice** | RescheduleCount, CarriedOver (contadores históricos) |
| **Completion slice** | DocStatus, refs attachments |
| **Dispatch** | Vendor, tech, status portal — **fonte da verdade vendor** |
| **Audit** | Rastreabilidade imutável |
**Proibido:** `VendorId`, `TechName`, `TechPhone` canônicos na WO.
### 4.2 Scheduling Aggregate Growth Watchlist
Campos que **não** entram em Scheduling sem ARB review: `MoveReason`, `MoveUser`, `MoveCategory`, `MoveSource`. Metadados de movimentação → Audit. Gate: slice Scheduling > 8 campos operacionais → ARB obrigatório.
### 4.3 Campos reaproveitados (sem nova coluna)
`SiteCode`, `ScheduledDate`, `DueDate`, `AssignTo`, `Trade`/`Problem`, `LocationId`, POC via `WorkOrderContacts`.
### 4.4 Database drift conhecido
| Coluna SQL | Ação |
|------------|------|
| `WorkOrderType` | Mapear no Core |
| `AvettaTask` | Inventariar; mapear ou deprecar |
| `AssignDate` | Inventariar; mapear ou deprecar |
Gate Fase 0: diff completo SQL vs `ApplicationDbContextModelSnapshot`.
### 4.5 Entidades relacionadas
| Entidade | Mudança | Fase |
|----------|---------|------|
| `WorkOrderContacts` | `Notes` (pocNotes) | 0 |
| `WorkOrderAuditLog` | EventType Manual/System/Vendor/Sync | 0 |
| `ApplicationUser` | Initials, Color | 0–1 |
| `Locations` | SiteCode | 0–1 |
| `WorkOrderAttachments` | Category | 2–6 |
| `WorkOrderEnums` | LifecycleStatus, WorkOrderType, DocStatus, OperationalFlags | 0 |
| `Dispatch` | Proteção regression | contínuo |
Fora de escopo imediato: `FollowUps`, `Quotes`, `PMSchedules`, `Employee`, `WorkOrderCategories` no board.
---
## 5. Domain Architecture Review (DAR)
### 5.1 Fonte da verdade por conceito
| Conceito | Fonte da verdade | Proibido |
|----------|------------------|----------|
| Status | `LifecycleStatus` (10 valores) | String livre; status composto |
| Past Due | `OperationalFlags.PastDue` | Misturar no enum lifecycle |
| Vendor/Tech | Dispatch primário | Duplicar na WO |
| Schedule | Scheduling slice | Duplicar no core |
| Completion | Completion.DocStatus | Só inferir de dispatch |
| Reschedule/Carried | Analytics + audit | Job blind overwrite |
| WO# | Campo canônico único | Dois números sem regra |
| POC | WorkOrderContacts | Só no detalhe |
### 5.2 Matriz Persistido vs Derivado
| Campo FE | Persistido | Derivado | Fonte / Regra |
|----------|------------|----------|---------------|
| `lifecycleStatus` | Sim | Não | Core |
| `isPastDue` | Não | **Sim** | `ScheduledDate < hoje` AND NOT terminal; job = cache opcional |
| `type` Overdue | Não | **Sim** | Regra type + schedule + status |
| `carriedOver` | Sim | Não | Métrica histórica; incremento via `WeekRolled` |
| `rescheduleCount` | Sim | Parcial | Domain event em mudança de `ScheduledDate` |
| `docStatus` | Sim | Não | Completion slice |
| `dayGroup`, `apptTime` | Não | Sim | Calculados na leitura |
| vendor/tech | Via Dispatch | Projeção | Join dispatch primário |
**Regra de ouro:** Jobs nunca são a única fonte da verdade. Leitura sempre recalcula derivados.
### 5.3 Lifecycle Status vs Operational Flags
```text
LifecycleStatus → enum único (10 valores FE)
OperationalFlags → PastDue, (futuro: Escalated)
```
Filtro status = LifecycleStatus. Overlay Past Due = flag. Bloqueio de edição = validação sobre flag.
### 5.4 CarriedOver — justificativa
| | `isPastDue` | `carriedOver` |
|--|-------------|---------------|
| Natureza | Estado pontual | **Métrica histórica acumulada** |
| Persistir | Não (derivado) | **Sim** (contador) |
| Por quê | Calculável da data atual | Não recuperável só do schedule atual |
Invariante: incremento **somente** via domain event `WeekRolled`.
### 5.5 Vendor / Dispatch — spike obrigatório (Fase 0)
1. WO mantém `PrimaryDispatchId`
2. Colunas VENDOR = projeção do dispatch primário
3. Edição inline vendor = mutação no Dispatch
4. WO sem dispatch → edição cria dispatch primário
Entregável: ADR "Vendor Source of Truth". Critério: zero `Tech*` canônicos na WO.
### 5.6 Jobs — domínio primeiro
| Job | Papel |
|-----|-------|
| Past Due diário | Cache refresh opcional; on-read sempre correto |
| Carried Over semanal | Dispara `WeekRolled` → incrementa contador + audit system |
| Promoção Overdue | Derivado on-read |
| Audit | Síncrono em toda mutação desde Fase 0 |
### 5.7 Database drift investigation
Diff SQL vs EF; classificar: mapear, deprecar, mover para agregado satélite. Tabelas: `workOrders`, `Comments`, `Locations`, `Dispatch`.
---
## 6. Data Ownership Model
### 6.1 Ownership por ator
| Ator | Pode alterar | Não pode alterar |
|------|--------------|------------------|
| **SHOC** | Lifecycle, Schedule, Assignment, Completion, vendor via Dispatch, POC, WO# | Checklist/signoff vendor |
| **Vendor Portal** | Dispatch status, checklist, signoff, comments | Schedule, lifecycle, assignment, DueDate |
| **Sync/Lambda** | Campos ingest (Description, ExternalId, etc.) — só owner Sync | Campos SHOC-owned após 1ª edição manual |
| **Blazor** | Campos legados até sunset | Campos novos board |
| **Jobs** | Incremento CarriedOver; audit system | Lifecycle, Schedule, Dispatch |
### 6.2 Field Ownership Matrix
| Campo | Agregado | Owner escritor | Sync sobrescreve? | Conflito SHOC vs Sync |
|-------|----------|----------------|-------------------|----------------------|
| LifecycleStatus | Core | SHOC | Não após 1ª edição SHOC | SHOC vence |
| AssignTo | Core | SHOC | Não após 1ª edição SHOC | SHOC vence |
| ScheduledDate / janela | Scheduling | SHOC | Não | SHOC vence |
| TargetWeek | Scheduling | SHOC | Não | SHOC vence |
| DueDate | Core | SHOC + Sync | Sim se nunca editado SHOC | SHOC se ManualEditFlag |
| Description | Core | SHOC + Sync | Merge / SHOC priority | SHOC se editado |
| SiteCode / Location | Core | SHOC + Sync | Sim na criação; não após SHOC | SHOC após flag |
| WorkOrderType | Core | SHOC | Sim na criação ingest | SHOC após flag |
| WoNumber | Core | SHOC | Não | SHOC only |
| Vendor / tech | Dispatch | SHOC + Vendor | Não | Domínios separados |
| Checklist/signoff | Dispatch | Vendor | Não | Vendor vence |
| DocStatus | Completion | SHOC | Não | SHOC only |
| RescheduleCount / CarriedOver | Analytics | Domain events | Não | N/A |
| ExternalWorkOrderId | Core | Sync | Sim (idempotente) | Sync only |
**ManualEditFlag:** primeira edição SHOC marca campo; Sync subsequente skip + audit `SyncRejected`.
### 6.3 Sync merge — cenários
| Cenário | Comportamento |
|---------|---------------|
| Sync DueDate + SHOC ScheduledDate | Merge — campos distintos |
| Sync Status + SHOC Status | SHOC vence; sync skip |
| Sync AssignTo + SHOC AssignTo | SHOC vence |
| Sync Description + SHOC Description | SHOC vence se ManualEditFlag |
| Sync cria WO novo | Full ingest |
| Sync atualiza por ExternalWorkOrderId | Field-level merge por matriz |
---
## 7. Concurrency Strategy
### 7.1 Atores
Dispatcher (SHOC), Vendor (Portal), Sync/Lambda, Blazor (congelado), Jobs.
### 7.2 RowVersion multi-agregado
| Agregado | RowVersion | Quando muda |
|----------|------------|-------------|
| **WorkOrder (root)** | No Core | Mutação Core, Scheduling, Completion, Analytics na mesma transação |
| **Dispatch** | Próprio | Mutação vendor/tech/checklist/status dispatch |
| **Projeção board** | N/A | FE envia `workOrderVersion` + `dispatchVersion` |
**Propagação:**
- SHOC edita ScheduledDate → bump WO.RowVersion
- SHOC edita vendor → bump Dispatch.RowVersion
- Vendor edita checklist → bump Dispatch.RowVersion only
- Sync altera Description → bump WO se merge aplicado
- DocStatus → bump WO (Completion na mesma transação)
Conflito → 409 com `currentState` para refresh.
### 7.3 Concurrency Ownership Rules
| Cenário | Resultado |
|---------|-----------|
| SHOC vs SHOC (mesmo campo) | 409; último RowVersion válido |
| SHOC vs Sync (mesmo campo) | Vence owner do campo |
| SHOC vs Sync (campos diferentes) | Merge |
| Vendor vs Sync | Domínios separados |
| SHOC vs Vendor | Merge se campos distintos; impossível mesmo conceito por design |
| Job WeekRolled vs SHOC reschedule | Idempotência WO+semana |
| Blazor vs SHOC | SHOC vence; Blazor congelado |
### 7.4 Testes
Fase 2: conflito paralelo (2 dispatchers, sync durante edição); dual RowVersion; audit count = fields changed.
---
## 8. Audit Event Contract
### 8.1 Princípios
- Toda mutação gera audit **síncrono** desde Fase 0
- Edição inline = **1 evento por campo**
- Audit para investigação operacional, não só compliance
### 8.2 Schema por evento
| Campo | Obrigatório |
|-------|-------------|
| WorkOrderId | Sim |
| EventType | Sim: Manual / System / Sync / Vendor |
| Action | Sim: FieldChanged, StatusChanged, WeekRolled, SyncRejected |
| FieldName | Sim para inline |
| OldValue, NewValue | Sim |
| ActorId, ActorType | Sim para Manual |
| Timestamp | Sim UTC |
| CorrelationId | Recomendado |
| DispatchId | Quando evento é no dispatch |
### 8.3 Mutação → eventos
| Mutação | Eventos |
|---------|---------|
| ChangeStatus | 1× StatusChanged |
| Inline cell | 1× FieldChanged por campo |
| Auto-schedule | StatusChanged + FieldChanged se aplicável |
| Reschedule | FieldChanged + RescheduleCount + audit analytics |
| WeekRolled | System WeekRolled + CarriedOver old/new |
| Sync skip | Sync SyncRejected |
| Vendor checklist | Vendor FieldChanged no Dispatch |
### 8.4 Contrato FE (Audit tab)
`{ type, dispatcherId?, action, fieldName?, oldValue, newValue, time }`
---
## 9. Data Migration Plan
### 9.1 Status
| Legado | LifecycleStatus alvo |
|--------|------------------------|
| Open | Incomplete |
| InProgress | In Progress |
| Completed | Complete |
| Cancelled | Canceled |
| OnHold | On Hold |
| Desconhecido | Incomplete + NeedsReview |
Processo: dry-run staging; 100% classificados; `LegacyStatus` read-only 1 release; rollback documentado.
### 9.2 WO#
| Formato legado | Estratégia |
|----------------|------------|
| Sequencial sync `10000001` | Normalizar 11 dígitos |
| `WO-{date}-{random}` | Coexistência; novos só 11 dígitos |
| Duplicatas | Resolução manual pré-go-live |
### 9.3 WorkOrderType
Mapear SQL existente → enum; NULL → default PO; Overdue = derivado.
### 9.4 Rollback
Migrations reversíveis; feature flag SHOC; snapshot pré-migration.
---
## 10. Volume Discovery e Search Scalability
### 10.1 Volume Discovery (Fase 0 — gate Fase 1)
**Sem premissa de volume.** Métricas obrigatórias:
| Métrica | Gate |
|---------|------|
| Total WOs produção/staging | Obrigatório |
| WOs/semana (p95) | Obrigatório |
| WOs Unscheduled | Obrigatório |
| Crescimento mensal | Desejável |
### 10.2 Tiers e impacto
| Tier | Total WOs | Listagem semanal | Advanced search |
|------|-----------|------------------|-----------------|
| **S** | < 25k | Índices simples | SQL filtros compostos |
| **M** | 25k–250k | Índices covering | Paginação obrigatória |
| **L** | 250k–1M | Read model candidato | Full-text |
| **XL** | > 1M | Materialized view | Search dedicado (ARB) |
Fases 1–3: índices conservadores compatíveis com qualquer tier. Fase 4: implementação conforme tier + load test.
---
## 11. Coexistência Multi-Consumidor
### 11.1 Inventário
| Consumidor | Conexão | Status |
|------------|---------|--------|
| **SHOC React** (`seahaven.desing`) | REST JWT | Em desenvolvimento — **único alvo novas features** |
| **Blazor** (`SeaHavenIndustries`) | EF direto, não REST | Ativo — manutenção mínima; sunset WO |
| **Vendor Portal UI** | REST `X-Vendor-Token` | API ativa; UI externa |
| **SyncController + Lambda** | DynamoDB → SQL | Ponte temporária |
| **E-mails SendGrid** | Deep links SHOC/Portal | Ativo |
| **Mobile** | — | Sem evidência — confirmar com PO |
```mermaid
flowchart TB
SHOC[SHOC React] -->|REST JWT| API[Api REST]
VPortal[Vendor Portal] -->|X-Vendor-Token| API
Lambda[Lambda ingest] --> DynamoDB[(DynamoDB)]
DynamoDB --> Sync[SyncController]
Sync --> DB[(SQL Server)]
API --> DB
Blazor[Blazor EF] -->|legado| DB
```
**Nota:** DynamoDB é **ingestão externa**, não banco operacional. SQL Server é fonte para board, Blazor e Portal.
### 11.2 Matriz de coexistência
| Consumidor | Novas features board | Schema | Sunset |
|------------|---------------------|--------|--------|
| SHOC | Recebe tudo | Consome novos campos | Destino final |
| Blazor | Não recebe | Colunas nullable; backward compatible | Módulo WO congela |
| Vendor Portal | Não usa board | Dispatch intacto | Mantém |
| Sync/Lambda | Indireto | Defaults para novos campos | Após cutover API |
| E-mails | Links SHOC | FrontendBaseUrl prod | Atualizar templates |
### 11.3 Trilhas de entrega
- **Trilha A SHOC:** fases 0–7; feature flags; piloto dispatchers
- **Trilha B Blazor:** smoke EF pós-migration; sem board; bugfix crítico only
- **Trilha C Vendor Portal:** regression suite cada release que toca WO/Dispatch
- **Trilha D Lambda:** manter até SHOC produção; cutover Fase 7; critério: criação WO + auth serviço estáveis
### 11.4 Endpoints legado vs board
- Legado (`GetWorkOrderList`, etc.): manter durante coexistência; congelar contrato
- Board: novos contratos; não estender legado com hacks
- Deprecação: após 100% dispatchers SHOC + sunset Blazor WO
### 11.5 Ordem de rollout
1. Fase 0 staging — gates + migrations
2. Gate Blazor smoke EF
3. Fase 1 staging — SHOC board feature flag
4. Gate Vendor Portal regression
5. Fases 2–6 incrementais
6. Piloto 1–2 dispatchers
7. Rollout gradual
8. Sunset Blazor WO
9. Cutover Lambda → API
10. Deprecação legado + SyncController
### 11.6 Monitoramento coexistência
Métricas por consumidor; drift DynamoDB vs SQL; adoção SHOC vs Blazor; alertas job failure.
### 11.7 Checklist pré-go-live PO
- App mobile externo?
- Data sunset Blazor WO
- FrontendBaseUrl produção → SHOC
- Owner Lambda cutover
---
## 12. Implementation Phases
### Fase 0 — Domain Foundation + Governança
**Objetivo:** Kickoff enterprise — domínio, ownership, volume, audit antes de board.
**Escopo:** DAR; 3 artefatos (Ownership, Field Matrix, Audit Contract); Volume Discovery; drift SQL/EF; spike vendor; agregados Core+Scheduling+Tracking+Analytics+Completion; enums; RowVersion root+Dispatch; migration dry-run; ManualEditFlag design; audit baseline ChangeStatus/ChangeAssignment; smoke Blazor/Sync/Portal.
**Não inicia:** Board API, inline edit, jobs.
**Validação:** 9 gates assinados; audit granularidade testada; tier volume definido.
---
### Fase 1 — Weekly Board (leitura)
**Objetivo:** Tela principal carrega semana operacional.
**Escopo:** Listagem window-based; Unscheduled; 14 colunas como projeção; `X of Y`; filtros (dispatcher multi, `__unassigned__`, My WOs, tipo, semana); `isPastDue` derivado on-read; vendor via dispatch primário; índices conforme tier volume.
**Validação:** 1 call = semana + Unscheduled; 12/14 colunas mínimo; gate Vendor Portal regression.
---
### Fase 2 — Inline Edit + Concorrência
**Objetivo:** Edição spreadsheet com regras e conflitos tratados.
**Escopo:** Update granular; dual RowVersion; audit 1 evento/campo; auto-schedule; rescheduleCount++; bloqueio PastDue flag; cancel read-only; WO# 11 dígitos; testes concorrência.
---
### Fase 3 — Criação e Cancelamento
**Escopo:** Wizard/inline completo; Incomplete inicial; targetWeek; soft cancel; ManualEditFlag na criação; delete admin documentado.
---
### Fase 4 — Search
**Escopo:** Busca contextual semana; advanced search cross-week conforme tier; load test; sem implementar advanced sem tier definido.
---
### Fase 5 — Scheduled Domain Events
**Escopo:** WeekRolled → CarriedOver++; cache opcional PastDue; idempotência; monitoramento falha; on-read sempre correto.
---
### Fase 6 — Completion Doc e Slide-over
**Escopo:** docStatus WO-level; templates por serviço; Comments/Audit/Media alinhados ao FE.
---
### Fase 7 — Rollout e Produção
**Escopo:** Piloto; rollout gradual; sunset Blazor WO; cutover Lambda; deprecação legado; UAT; zero P1 duas semanas; dashboards saúde.
---
## 13. Critical Path Analysis
### Must Have
1. Listagem window-based + Unscheduled
2. Projeção 14 colunas
3. Agregados domínio + enums
4. Update granular por campo
5. Status 10 labels + flag PastDue
6. Lookups
7. Criação wizard
8. WO# 11 dígitos
9. DAR + 3 artefatos Fase 0
### Should Have
10. Domain events CarriedOver
11. Advanced search
12. Regras mutação completas
13. Soft cancel enforcement
14. docStatus WO-level
15. Auth ativa
### Nice to Have
16. Reorder intra-dia persistido (FE local)
17. Flags pessoais session-only
18. Paginação 12/24/48/96
19. Bulk select
---
## 14. Frontend Impact Assessment
| Gap | UX | Severidade | Workaround |
|-----|-----|------------|------------|
| Sem listagem semanal | Tela inutilizável | Critical | Mock FE |
| 14 colunas incompletas | Board ilegível | Critical | Nenhum em prod |
| Sem inline edit | Core inoperante | Critical | Modal legado |
| Sem advanced search | WOs históricos invisíveis | High | Busca por ID |
| Status incompatíveis | Decisões erradas | Critical | Hardcode FE |
| Sem jobs (derivados) | Badges stale se só client | High | On-read BE resolve PastDue |
| Sem docStatus | COMP DOC vazia | High | Ocultar coluna |
| WO# formato errado | Criação bloqueada | Critical | — |
| Ownership Sync vs SHOC | Overwrite silencioso | Critical | Field Matrix Fase 0 |
| Auth desabilitada | Risco segurança | High | API gateway |
---
## 15. Business Rules Assessment
| Regra | Status | Impacto |
|-------|--------|---------|
| Auto-schedule Incomplete→Scheduled | Unsupported | Status incorreto |
| Reschedule → rescheduleCount++ | Unsupported | Badge ↻ ausente |
| Reschedule limpa PastDue (derivado) | Unsupported | Flag stale até re-read |
| Status bloqueado se PastDue | Unsupported | Bypass via API |
| WO# único 11 dígitos | Unsupported | Duplicatas |
| Cancel read-only | Partial | Edição pós-cancel |
| WeekRolled → carriedOver | Unsupported | Badge ↷N ausente |
| Completion doc por serviço | Unsupported | Workflow manual |
| Week-only scheduling | Unsupported | Wizard incompleto |
---
## 16. Data Contract Readiness
| Área | Readiness % | Gap principal |
|------|-------------|---------------|
| List Views board | 5% | 14 colunas, schedule, vendor, flags |
| Detail slide-over | 40% | Derivados, docStatus, schema |
| Search | 10% | Cross-week, date ranges |
| Lookups | 20% | initials, site code, PM, tech |
| Scheduling | 25% | targetWeek, ScheduledEnd |
| Status | 30% | 10 labels + flag |
| Completion | 0% | docStatus, templates |
| Comments | 60% | Schema |
| Audit | 50% | type system, granularidade |
| Media | 45% | category |
**Média ponderada: ~28%**
---
## 17. Testing Strategy
### Functional
Board semanal, 14 colunas, filtros, inline por campo, regras negócio, criação/cancelamento, docStatus, status+flag, ownership rejection sync.
### Integration
SHOC E2E por fase; Vendor Portal regression; Sync; auth; dual RowVersion.
### Regression
Endpoints legados; Blazor smoke EF; Portal checklist/signoff.
### UAT
Roteiros dispatchers reais; sign-off PO por fase.
### Data validation
WO# migration; status mapping; derivados on-read vs contadores; referencial.
### Migration validation
Dump produção espelho; antes/depois contagens; rollback por fase.
### Fase-specific
- **0:** volume queries; audit granularidade; sync skip
- **2:** concurrency suite; 409 handling
- **4:** load test tier
- **5:** job failure injection; on-read correctness
---
## 18. Risk Matrix
| Risco | Prob. | Impacto | Mitigação |
|-------|-------|---------|-----------|
| Board inoperante em prod | Alta | Crítico | Fase 1 gate; E2E |
| Ownership ambíguo Sync vs SHOC | Alta | Crítico | Field Ownership Matrix |
| God Entity creep | Média | Alto | Growth Watchlist; ARB |
| Conflito cross-agregado | Média | Alto | RowVersion root + Dispatch |
| Job falha estado errado | Alta | Alto | Derivados on-read |
| Vendor drift Dispatch vs WO | Alta | Crítico | Spike; projeção only |
| Status migration incorreta | Média | Crítico | LegacyStatus; dry-run |
| Volume desconhecido → índices errados | Alta | Alto | Volume Discovery Fase 0 |
| Audit inútil investigação | Média | Médio | Event Contract |
| Regressão Vendor Portal | Média | Crítico | Suite dedicada |
| Blazor vs SHOC divergência | Alta | Alto | Congelar Blazor WO |
| Auth bloqueia integrações | Média | Alto | Inventário consumidores |
| CarriedOver recalculado errado | Baixa | Médio | Só via WeekRolled |
| Scheduling aggregate creep | Média | Alto | Watchlist §4.2 |
---
## 19. Prioritized Backlog
Ordenado por sequência de implementação.
| # | Item | P | Fase | Dep | Complex. | Risco |
|---|------|---|------|-----|----------|-------|
| 1 | DAR completo + sign-off CTO | P0 | 0 | — | M | Alto |
| 2 | Data Ownership Model | P0 | 0 | — | S | Alto |
| 3 | Field Ownership Matrix + ManualEditFlag | P0 | 0 | 2 | M | Alto |
| 4 | Audit Event Contract + baseline | P0 | 0 | — | M | Médio |
| 5 | Volume Discovery Report | P0 | 0 | — | S | Alto |
| 6 | Database drift SQL vs EF | P0 | 0 | — | M | Médio |
| 7 | Spike vendor/dispatch source of truth | P0 | 0 | — | M | Alto |
| 8 | Agregados domínio (anti God Entity) | P0 | 0 | 1 | L | Alto |
| 9 | Matriz Persistido vs Derivado | P0 | 0 | 1 | S | Médio |
| 10 | RowVersion root + Dispatch | P0 | 0 | 8 | M | Alto |
| 11 | Enums Lifecycle + Flags + WorkOrderType | P0 | 0 | 8 | M | Médio |
| 12 | Data migration dry-run | P0 | 0 | 8 | M | Alto |
| 13 | Inventário consumidores + coexistência | P0 | 0 | — | S | Médio |
| 14 | Smoke Blazor EF + Sync + Portal | P0 | 0 | 8 | M | Alto |
| 15 | Normalização WO# 11 dígitos | P0 | 0 | 12 | M | Alto |
| 16 | Reativar auth endpoints WO | P0 | 0 | — | S | Médio |
| 17 | Lookups (dispatchers, sites, PM, tech) | P0 | 0–1 | — | M | Baixo |
| 18 | Listagem window-based + Unscheduled | P0 | 1 | 8,11,17 | L | Alto |
| 19 | Projeção 14 colunas | P0 | 1 | 18 | L | Alto |
| 20 | isPastDue derivado on-read | P0 | 1 | 19 | S | Baixo |
| 21 | Vendor via dispatch primário | P0 | 1 | 7,19 | M | Alto |
| 22 | Filtros principais + My WOs | P0 | 1 | 18 | M | Médio |
| 23 | Contador X of Y | P1 | 1 | 18 | S | Baixo |
| 24 | Sync merge por field ownership | P0 | 0–1 | 3 | M | Alto |
| 25 | Update granular inline | P0 | 2 | 19 | L | Alto |
| 26 | Audit 1 evento por campo | P0 | 2 | 4,25 | M | Médio |
| 27 | Concurrency integration tests | P0 | 2 | 10,25 | M | Alto |
| 28 | Auto-schedule Incomplete→Scheduled | P0 | 2 | 25 | M | Médio |
| 29 | RescheduleCount++ domain event | P0 | 2 | 25 | S | Baixo |
| 30 | Bloqueio status se PastDue flag | P0 | 2 | 25 | S | Baixo |
| 31 | Cancel read-only enforcement | P1 | 2 | 25 | S | Baixo |
| 32 | Criação wizard/inline completa | P0 | 3 | 25 | M | Médio |
| 33 | Week-only scheduling targetWeek | P1 | 3 | 32 | M | Médio |
| 34 | Soft cancel dedicado | P1 | 3 | 31 | S | Baixo |
| 35 | Busca contextual semana | P1 | 4 | 19 | M | Baixo |
| 36 | Advanced search cross-week | P1 | 4 | 5,19 | L | Médio |
| 37 | Load test search por tier | P0 | 4 | 5 | M | Alto |
| 38 | WeekRolled domain event + CarriedOver | P1 | 5 | 8 | M | Médio |
| 39 | Cache opcional PastDue | P2 | 5 | 20 | S | Baixo |
| 40 | docStatus WO-level | P1 | 6 | 8 | M | Médio |
| 41 | Completion templates por serviço | P2 | 6 | 40 | L | Médio |
| 42 | Slide-over Comments/Audit/Media | P2 | 6 | 4,40 | M | Baixo |
| 43 | Regression suite legado + Portal | P0 | cont. | — | M | Alto |
| 44 | UAT dispatchers por fase | P0 | cont. | — | S | Médio |
| 45 | Piloto + rollout gradual SHOC | P0 | 7 | 1–42 | M | Alto |
| 46 | Sunset Blazor WO | P1 | 7 | 45 | S | Médio |
| 47 | Cutover Lambda → API | P1 | 7 | 32 | M | Alto |
| 48 | Deprecação endpoints legado + Sync | P2 | 7 | 45 | M | Médio |
| 49 | Monitoramento coexistência | P1 | 7 | 45 | S | Baixo |
---
## 20. Cronograma indicativo
```mermaid
gantt
title Work Orders Board
dateFormat YYYY-MM-DD
section Fundacao
DAR_arteFatos_dominio :2026-07-01, 8w
section Board
Listagem_14colunas :2026-09-01, 8w
section Mutacao
Inline_concorrencia :2026-11-01, 8w
Criacao_cancelamento :2026-11-15, 6w
section Inteligencia
Search :2027-01-01, 6w
Domain_events :2027-01-15, 4w
section Completion
DocStatus_slideover :2027-02-15, 6w
section Producao
Piloto_rollout :2027-04-01, 8w
```
---
## 21. Stakeholders
| Papel | Responsabilidade |
|-------|------------------|
| CEO / Sponsor | Investimento XL; prioridade SHOC sobre Blazor |
| Product Owner | Aceite por fase; semana operacional; regras ambíguas |
| CTO / Arquiteto | DAR; 3 artefatos; ownership; cutover Lambda |
| Backend | Fases 0–6 domínio e contratos |
| Frontend SHOC | Integração; feature flags; remover mocks |
| QA | Testes §17; UAT; concurrency |
| Documentação | Contratos; guia consumidores legados |
| Operações | Jobs; alertas; monitoramento coexistência |
---
## 22. Critérios de prontidão para kickoff (9.5+)
| Área | Critério |
|------|----------|
| Domain Design | DAR aprovado; agregados; Watchlist |
| Source of Truth | Field Ownership Matrix assinada |
| Data Governance | Data Ownership Model completo |
| Concurrency | RowVersion root + Dispatch; testes Fase 2 |
| Sync | Cenários explícitos; ManualEditFlag |
| Audit | Event Contract; 1 evento/campo inline |
| Migration | Dry-run; LegacyStatus rollback |
| Volume | Discovery Fase 0; tier definido |
| Jobs | Domain events; on-read correto se job falha |
| Coexistência | Inventário; trilhas; smoke Blazor/Portal/Sync |
---
## 23. Conclusão
Programa **viável e de grande porte (XL)**. O caminho crítico é:
```text
Resolver domínio e governança (Fase 0)
→ Board semanal (Fase 1)
→ Inline edit + concorrência (Fase 2)
→ Paridade funcional progressiva (Fases 3–6)
→ Rollout seguro (Fase 7)
```
O documento integra: gap assessment da auditoria, modelo de domínio com agregados, governança de ownership, concorrência multi-agregado, migração de dados, coexistência SHOC/Blazor/Portal/Lambda, e backlog executável — pronto para Architecture Review Board e kickoff de implementação enterprise.
**Próximo passo imediato:** executar Fase 0 — produzir e assinar os três artefatos (Data Ownership Model, Field Ownership Matrix, Audit Event Contract) antes de qualquer migration de schema.

View file

@ -0,0 +1,32 @@
# Fase 0 — Work Orders Foundation
Documentação e gates da Fase 0 (Domain Foundation + Governança).
## Governança
- [DAR — Domain Architecture Review](dar-domain-architecture-review.md)
- [Matriz Persistido vs Derivado](dar-persisted-vs-derived-matrix.md)
- [Data Ownership Model](data-ownership-model.md)
- [Field Ownership Matrix](field-ownership-matrix.md)
- [ManualEditFlag Design](manual-edit-flag-design.md)
- [Audit Event Contract](audit-event-contract.md)
## Discovery
- [Volume Discovery Report](volume-discovery-report.md)
- [Database Drift Report](database-drift-report.md)
- [Consumer Inventory](consumer-inventory.md)
- [ADR Vendor Source of Truth](adr-vendor-source-of-truth.md)
## Validação
- [RowVersion Design](rowversion-design.md)
- [Migration Dry-Run Report](migration-dry-run-report.md)
- [Smoke Checklist](smoke-checklist.md)
- [Gates Sign-Off](phase-0-gates-signoff.md)
## Código entregue
- Migration: `Data.SeaHavenIndustries/Migrations/20260624163145_Phase0_DomainFoundation.cs`
- Serviços: `IWorkOrderAuditService`, `IWorkOrderFieldLockService`, `ISyncFieldMergePolicy`
- Testes: `SeaHavenIndustries.Tests` (12 testes)

View file

@ -0,0 +1,67 @@
# ADR — Vendor Source of Truth
**Status:** Accepted (Fase 0 spike)
**Decisores:** Backend + Arquiteto
---
## Contexto
O board SHOC exibe colunas VENDOR e APPT na listagem semanal. Hoje vendor existe apenas no agregado Dispatch; a entidade WorkOrder não possui referência ao dispatch primário.
---
## Decisão
1. **WorkOrder.PrimaryDispatchId** (nullable FK → Dispatches) identifica o dispatch canônico para projeção board.
2. Colunas VENDOR/APPT = **projeção read-only** via join no dispatch primário (Vendor.Name, Dispatch.ScheduledDate).
3. Edição inline vendor (Fase 2) muta **Dispatch**, não WorkOrder. Bump `Dispatch.RowVersion`.
4. WO sem dispatch: primeira edição vendor cria dispatch primário e seta `PrimaryDispatchId`.
5. **Proibido** adicionar VendorId, TechName, TechPhone na tabela workOrders.
---
## Query de projeção (spike)
```sql
SELECT wo.Id,
wo.InternalWONumber,
d.Id AS DispatchId,
v.Name AS VendorName,
d.ScheduledDate AS ApptDate,
d.Status AS DispatchStatus
FROM workOrders wo
LEFT JOIN Dispatches d ON d.Id = wo.PrimaryDispatchId
LEFT JOIN Vendors v ON v.Id = d.VendorId
WHERE wo.IsDeleted IS NULL OR wo.IsDeleted = 0;
```
Implementação C# em `DispatchDataService.GetPrimaryDispatchProjectionAsync(workOrderId)` (Fase 1).
---
## Seleção do dispatch primário
| Cenário | Regra |
|---------|-------|
| PrimaryDispatchId setado | Usar esse dispatch |
| Null + exatamente 1 dispatch | Auto-set PrimaryDispatchId na migration backfill |
| Null + N dispatches | Usar dispatch mais recente por DispatchedAt; log para revisão manual |
| Zero dispatches | Vendor columns vazias no board |
---
## Consequências
- **Positivo:** Zero drift vendor WO vs Dispatch; Portal regression isolada.
- **Negativo:** Join extra na listagem board — mitigado por índice em PrimaryDispatchId.
- **Fase 0:** Coluna + FK + backfill script no dry-run; endpoint board na Fase 1.
---
## Critérios de aceite spike
- [x] ADR documentado
- [x] Query prototipada
- [ ] PrimaryDispatchId na migration Phase0
- [ ] Backfill documentado no dry-run report

View file

@ -0,0 +1,106 @@
# Audit Event Contract — Work Orders
**Fase:** 0
**Status:** Aprovado para implementação baseline
**Schema EF:** `WorkOrderAuditLog` estendido
---
## 1. Princípios
- Toda mutação gera audit **síncrono** desde Fase 0.
- Edição inline (Fase 2) = **1 evento por campo**.
- Audit serve investigação operacional, não só compliance.
---
## 2. Schema por evento
| Campo | Tipo | Obrigatório |
|-------|------|-------------|
| Id | int | Sim (PK) |
| WorkOrderId | int | Sim |
| EventType | enum string | Sim: Manual, System, Sync, Vendor |
| Action | string | Sim: FieldChanged, StatusChanged, WeekRolled, SyncRejected, AssignmentChanged |
| FieldName | string | Sim para inline / field change |
| OldValue | string | Sim |
| NewValue | string | Sim |
| UserId / ActorId | string | Sim para Manual |
| ActorType | enum string | Sim: Dispatcher, Vendor, System, Sync |
| CreatedAt | DateTime UTC | Sim |
| CorrelationId | string | Recomendado |
| DispatchId | int? | Quando evento é no dispatch |
**Compatibilidade:** colunas `Action` legada mapeada para novo `Action`; `UserId` = ActorId para Manual.
---
## 3. Enums
```csharp
AuditEventType: Manual | System | Sync | Vendor
AuditActorType: Dispatcher | Vendor | System | Sync
AuditActionType: FieldChanged | StatusChanged | AssignmentChanged | WeekRolled | SyncRejected | Create | Delete
```
---
## 4. Mutação → eventos
| Mutação | Eventos |
|---------|---------|
| ChangeStatus | 1× StatusChanged (EventType=Manual) |
| ChangeAssignment | 1× AssignmentChanged (FieldName=AssignTo) |
| Inline cell (Fase 2) | 1× FieldChanged por campo |
| Auto-schedule (Fase 2) | StatusChanged + FieldChanged se aplicável |
| Reschedule (Fase 2) | FieldChanged + analytics |
| WeekRolled (Fase 5) | System WeekRolled + CarriedOver |
| Sync skip | Sync SyncRejected |
| Vendor checklist | Vendor FieldChanged + DispatchId |
---
## 5. Contrato FE (Audit tab)
```typescript
interface AuditEntry {
type: 'manual' | 'system' | 'sync' | 'vendor';
dispatcherId?: string;
action: string;
fieldName?: string;
oldValue: string;
newValue: string;
time: string; // ISO UTC
dispatchId?: number;
}
```
Mapeamento API → FE:
| API EventType | FE type |
|---------------|---------|
| Manual | manual |
| System | system |
| Sync | sync |
| Vendor | vendor |
---
## 6. Baseline Fase 0 (endpoints)
| Endpoint | Garantia |
|----------|----------|
| POST ChangeStatus | 1 audit StatusChanged; Old/New preenchidos |
| POST ChangeAssignment | 1 audit AssignmentChanged; nomes usuário em Old/New |
Implementação via `IWorkOrderAuditService`.
---
## 7. Critérios de aceite
- [ ] Migration estende WorkOrderAuditLog
- [ ] ChangeStatus/ChangeAssignment usam serviço central
- [ ] Testes assertam 1 evento por mutação
**Assinatura Backend Lead:** _________________ Data: _______

View file

@ -0,0 +1,85 @@
# Consumer Inventory — Work Orders
**Fase:** 0
**Referência:** roadmap §11
---
## 1. Inventário de consumidores
| Consumidor | Conexão | Código principal | Campos mutados | Audit hoje? |
|------------|---------|------------------|----------------|-------------|
| **SHOC React** | REST JWT | `Api.SeaHavenIndustries/Controllers/WorkOrderController.cs` | Status, AssignTo, CRUD parcial via Service | Parcial (ChangeStatus/Assignment) |
| **Blazor** | EF direto | `SeaHavenIndustries/Data/Services/WorkorderService.cs` | CRUD completo, comments, status | **Não** |
| **Vendor Portal** | REST token | `Api.SeaHavenIndustries/Controllers/VendorPortalController.cs` | Dispatch checklist, signoff, status | Sim (ad hoc) |
| **Sync/Lambda** | DynamoDB → SQL | `Api.SeaHavenIndustries/Controllers/SyncController.cs` | Upsert WO por ExternalWorkOrderId | **Não** |
| **Dispatch API** | REST | `DispatchController.cs` | Dispatch create/update/verify | Sim |
| **E-mail SendGrid** | Deep links | `Helper/SendMessage` | Nenhum (read-only links) | N/A |
---
## 2. Diagrama
```mermaid
flowchart TB
SHOC[SHOC_React] -->|REST_JWT| API[Api_REST]
VPortal[Vendor_Portal] -->|X_Vendor_Token| API
Lambda[Lambda_ingest] --> DynamoDB[(DynamoDB)]
DynamoDB --> Sync[SyncController]
Sync --> DB[(SQL_Server)]
API --> DB
Blazor[Blazor_EF] -->|legado| DB
```
---
## 3. Trilhas de entrega (coexistência)
| Trilha | Escopo Fase 0+ |
|--------|----------------|
| **A — SHOC** | Fases 0–7; feature flags; piloto dispatchers |
| **B — Blazor** | Smoke EF pós-migration; bugfix crítico only |
| **C — Vendor Portal** | Regression checklist cada release WO/Dispatch |
| **D — Lambda/Sync** | Manter até SHOC produção; merge policy Fase 0 |
---
## 4. Endpoints legado vs board
| Tipo | Exemplos | Política |
|------|----------|----------|
| Legado | GetWorkOrderList, ChangeStatus | Congelar contrato |
| Board (Fase 1+) | GetWorkOrdersBoard | Novos contratos separados |
---
## 5. Gaps Fase 0 endereçados
| Gap | Mitigação |
|-----|-----------|
| Blazor sem audit | Congelar; não expandir |
| Sync overwrite | ISyncFieldMergePolicy + ManualEditFlag |
| Auth WO desabilitada | Reativar `[Authorize]` WorkOrderController |
| WO# inconsistente | Documentar normalização 11 dígitos (Fase 2 execução) |
---
## 6. Checklist pré-go-live PO
- [ ] App mobile externo confirmado?
- [ ] Data sunset Blazor WO definida?
- [ ] FrontendBaseUrl produção → SHOC?
- [ ] Owner Lambda cutover nomeado?
---
## 7. Auth — consumidores e tokens
| Consumidor | Auth |
|------------|------|
| SHOC | JWT Bearer |
| Sync | JWT Bearer (`[Authorize]` no SyncController) |
| Vendor Portal | X-Vendor-Token (rotas públicas dispatch) |
| Blazor | Cookie Identity (app separado) |
Reativar auth em WorkOrderController exige SHOC enviar JWT em todas as chamadas WO.

View file

@ -0,0 +1,139 @@
# DAR — Domain Architecture Review (Work Orders)
**Fase:** 0
**Status:** Draft para sign-off CTO
**Referência:** [roadmap-work-orders-board.md](../../roadmap-work-orders-board.md) §4–5
---
## 1. Objetivo
Formalizar o modelo de domínio operacional do Schedule Board antes de qualquer migration de schema board ou contrato de listagem semanal.
---
## 2. Agregados e limites (anti God Entity)
```mermaid
flowchart TB
subgraph root [AggregateRoot_WorkOrder]
CORE[Core_Lifecycle_Assignment]
SCH[Scheduling_slice]
TRK[Tracking_slice]
COMP[Completion_slice]
ANA[Analytics_slice]
end
subgraph separate [Agregado_separado]
DISP[Dispatch_fonte_vendor]
end
AUD[WorkOrderAuditLog_append_only]
CORE --> SCH
CORE --> TRK
CORE --> ANA
CORE --> COMP
CORE --> DISP
CORE --> AUD
```
| Agregado / Slice | Responsabilidade | Persistência Fase 0 |
|------------------|------------------|---------------------|
| **Core** | Id, WoNumber, Type, LifecycleStatus, SiteCode, LocationId, DueDate, AssignTo, Title, RowVersion, PrimaryDispatchId | Colunas em `workOrders` |
| **Scheduling** | ScheduledDate, ScheduledStart/End, TargetWeek, ScheduleWeekOnly | Colunas em `workOrders` |
| **Tracking** | OriginalWeek, OriginalDate (set-once) | Colunas em `workOrders` |
| **Analytics** | RescheduleCount, CarriedOver | Colunas em `workOrders` |
| **Completion** | DocStatus, refs attachments | Coluna DocStatus + attachments existentes |
| **Dispatch** | Vendor, tech, status portal | Tabela `Dispatches` + RowVersion |
| **Audit** | Rastreabilidade imutável | `WorkOrderAuditLogs` |
**Proibido:** `VendorId`, `TechName`, `TechPhone` canônicos na entidade WorkOrder. Vendor sempre via Dispatch primário.
---
## 3. Fonte da verdade por conceito
| Conceito | Fonte da verdade | Proibido |
|----------|------------------|----------|
| Status operacional | `LifecycleStatus` (10 valores FE) | String livre `Status` como canônico |
| Past Due | Derivado on-read (`ScheduledDate < hoje` AND NOT terminal) | Misturar no enum lifecycle |
| Vendor / Tech | Dispatch primário (`PrimaryDispatchId`) | Duplicar na WO |
| Schedule | Scheduling slice | Duplicar no core sem slice lógico |
| Completion | `DocStatus` | Inferir só de dispatch signoff |
| Reschedule / CarriedOver | Analytics + domain events | Job blind overwrite |
| WO# | `InternalWONumber` / `WorkerOrderNumber` com regra 11 dígitos (Fase 2+) | Dois números sem regra |
| POC | `WorkOrderContacts` + Notes | Só no detalhe sem contato |
---
## 4. Lifecycle Status vs Operational Flags
```text
LifecycleStatus → enum único (10 valores alinhados ao frontend)
OperationalFlags → PastDue (derivado on-read; cache opcional Fase 5)
LegacyStatus → string original read-only (migration Fase 0)
```
Filtro de status no board = `LifecycleStatus`. Overlay Past Due = flag derivada. Bloqueio de edição = validação sobre flag (Fase 2).
### Mapeamento legado → LifecycleStatus
| Legado (`Status`) | LifecycleStatus |
|-------------------|-----------------|
| Open | Incomplete |
| InProgress / In Progress | InProgress |
| Completed / Complete | Complete |
| Cancelled / Canceled | Canceled |
| OnHold / On Hold | OnHold |
| Desconhecido | Incomplete + NeedsReview |
---
## 5. CarriedOver — justificativa
| | isPastDue | carriedOver |
|--|-----------|-------------|
| Natureza | Estado pontual | Métrica histórica acumulada |
| Persistir | Não (derivado) | Sim (contador) |
| Incremento | N/A | Somente via domain event `WeekRolled` (Fase 5) |
---
## 6. Scheduling Aggregate Growth Watchlist
Campos que **não** entram em Scheduling sem ARB review:
- `MoveReason`, `MoveUser`, `MoveCategory`, `MoveSource`
Metadados de movimentação → Audit Event Contract.
**Gate:** slice Scheduling > 8 campos operacionais → ARB obrigatório.
Contagem Fase 0: ScheduledDate, ScheduledStart, ScheduledEnd, TargetWeek, ScheduleWeekOnly, OriginalWeek, OriginalDate (tracking separado) — dentro do limite.
---
## 7. Jobs — domínio primeiro
| Job | Papel |
|-----|-------|
| Past Due diário | Cache refresh opcional; on-read sempre correto |
| Carried Over semanal | Dispara `WeekRolled` → incrementa contador + audit system |
| Promoção Overdue type | Derivado on-read |
| Audit | Síncrono em toda mutação desde Fase 0 |
**Regra de ouro:** Jobs nunca são a única fonte da verdade.
---
## 8. Critérios de aceite (sign-off CTO)
- [ ] Zero campos vendor canônicos na WO
- [ ] Agregados documentados e refletidos no schema Fase 0
- [ ] Persistido vs derivado validado com PO (ver `dar-persisted-vs-derived-matrix.md`)
- [ ] Watchlist Scheduling registrada
- [ ] Blazor congelado — sem novos campos board via EF direto
**Assinaturas**
| Papel | Nome | Data |
|-------|------|------|
| CTO / Arquiteto | | |
| Product Owner | | |

View file

@ -0,0 +1,54 @@
# DAR — Matriz Persistido vs Derivado
**Fase:** 0
**Status:** Draft para validação PO + CTO
---
## Matriz de campos (board + domínio)
| Campo FE / conceito | Persistido | Derivado | Fonte / Regra |
|---------------------|------------|----------|---------------|
| lifecycleStatus | Sim | Não | Core — `LifecycleStatus` enum |
| isPastDue | Não | **Sim** | `ScheduledDate < UTC hoje` AND NOT status terminal |
| type (Overdue) | Parcial | **Sim** | Regra type + schedule + status |
| workOrderType | Sim | Não | Core — enum `WorkOrderType` |
| carriedOver | Sim | Não | Analytics — incremento via `WeekRolled` only |
| rescheduleCount | Sim | Parcial | Analytics — incremento em mudança ScheduledDate |
| docStatus | Sim | Não | Completion slice |
| dayGroup | Não | Sim | Calculado na leitura a partir de ScheduledDate |
| apptTime | Não | Sim | Projeção Dispatch primário ScheduledDate/Start |
| vendorName / tech | Não | Sim (projeção) | Join Dispatch primário |
| targetWeek | Sim | Não | Scheduling slice |
| scheduledEnd | Sim | Não | Scheduling slice |
| originalWeek / originalDate | Sim | Não | Tracking — set-once na primeira agenda |
| operationalFlags.pastDue | Não* | Sim | *Cache opcional Fase 5; on-read authoritative |
| legacyStatus | Sim (read-only) | Não | Valor string original pré-migration |
---
## Regras de derivação (on-read)
### isPastDue
```text
isPastDue = ScheduledDate.HasValue
AND ScheduledDate.Value.Date < DateTime.UtcNow.Date
AND LifecycleStatus NOT IN (Complete, Canceled)
```
### type Overdue (derivado)
WO com type != Overdue pode exibir badge Overdue quando isPastDue && regras de negócio PO confirmadas.
---
## Validação PO
| Pergunta | Resposta PO |
|----------|-------------|
| Past Due separado do lifecycle status? | Sim |
| carriedOver persiste mesmo após reschedule? | Sim |
| Vendor nunca duplicado na WO? | Sim |
**Assinatura PO:** _________________ Data: _______

View file

@ -0,0 +1,75 @@
# Data Ownership Model — Work Orders
**Fase:** 0
**Status:** Draft para sign-off CTO
---
## 1. Princípios
1. **SHOC (React)** é o único destino de novas features do board.
2. **SQL Server** é fonte operacional para board, Blazor e Portal.
3. **DynamoDB/Sync** é ponte temporária de ingestão externa.
4. Conflito entre atores resolve-se pela Field Ownership Matrix, não por last-write-wins global.
---
## 2. Ownership por ator
| Ator | Conexão | Pode alterar | Não pode alterar |
|------|---------|--------------|------------------|
| **SHOC** | REST JWT (`api/WorkOrder`) | Lifecycle, Schedule, Assignment, Completion, vendor via Dispatch, POC, WO# | Checklist/signoff vendor |
| **Vendor Portal** | REST `X-Vendor-Token` | Dispatch status, checklist, signoff, comments vendor | Schedule, lifecycle, assignment, DueDate |
| **Sync/Lambda** | `POST api/Sync/WorkOrders` | Campos ingest (Description, ExternalId, SiteCode na criação, etc.) | Campos SHOC-owned após ManualEditFlag |
| **Blazor** | EF direto (`WorkorderService`) | Campos legados existentes até sunset | Campos novos board; **congelado** |
| **Jobs** | Domain events (Fase 5+) | Incremento CarriedOver; audit System | Lifecycle, Schedule, Dispatch |
---
## 3. Política Blazor (sunset)
- Manutenção mínima: bugfix crítico only.
- Não recebe: board, inline edit, jobs, advanced search, completion doc WO-level.
- Schema: colunas novas Fase 0 são **nullable** — Blazor continua funcionando sem conhecer novos campos.
- Conflito Blazor vs SHOC: **SHOC vence**; Blazor não deve escrever campos board após go-live Fase 1.
- Smoke test EF obrigatório pós-migration (ver checklist smoke).
---
## 4. Política Vendor Portal
- Dispatch intacto; zero regressão checklist/signoff/verify.
- Vendor não muta WorkOrder core — apenas Dispatch e comments associados.
- Audit EventType = `Vendor` para mutações portal.
---
## 5. Política Sync/Lambda
- Upsert por `ExternalWorkOrderId` (idempotente).
- Field-level merge conforme Field Ownership Matrix.
- Campo com ManualEditFlag → skip + audit `SyncRejected`.
- Cutover Lambda → API direta: Fase 7.
---
## 6. Regras de conflito (resumo)
| Cenário | Vencedor |
|---------|----------|
| SHOC vs SHOC (mesmo campo) | 409 RowVersion; último commit válido |
| SHOC vs Sync (mesmo campo) | SHOC se ManualEditFlag |
| SHOC vs Sync (campos distintos) | Merge |
| Vendor vs Sync | Domínios separados |
| SHOC vs Vendor (mesmo conceito) | Impossível por design (vendor = Dispatch) |
| Blazor vs SHOC | SHOC vence; Blazor congelado |
---
## 7. Critérios de aceite
- [ ] Matriz por ator revisada por CTO
- [ ] PO confirma política Blazor sunset
- [ ] Sync merge referencia Field Ownership Matrix
**Assinatura CTO:** _________________ Data: _______

View file

@ -0,0 +1,110 @@
# Database Drift Report — SQL vs EF
**Fase:** 0
**Data:** 2026-06-24
**EF Snapshot:** `Data.SeaHavenIndustries/Migrations/ApplicationDbContextModelSnapshot.cs`
**Script legado:** `Api.SeaHavenIndustries/db.txt`
---
## 1. Metodologia
1. Colunas EF extraídas do `ApplicationDbContextModelSnapshot` (entidade `WorkOrder`).
2. Colunas SQL legado extraídas de `db.txt` DDL `[WorkOrders]`.
3. Classificação: **mapear**, **deprecar**, **satélite**, **OK**.
---
## 2. Tabela workOrders — drift conhecido
| Coluna SQL (legado) | No EF? | Classificação | Ação Fase 0 |
|---------------------|--------|---------------|-------------|
| WorkOrderType | **Não** | mapear | Adicionar `WorkOrderType` int nullable → enum |
| AvettaTask | **Não** | inventariar | Adicionar coluna nullable; uso TBD com PO |
| AssignDate | **Não** | inventariar | Adicionar coluna nullable; possível alias AssignDate tracking |
| Nome tabela WorkOrders vs workOrders | EF usa `workOrders` | OK | Manter EF naming; SQL Server case-insensitive |
---
## 3. Colunas EF (workOrders) — baseline
Presentes no snapshot e mapeadas:
InternalWONumber, ExternalWorkOrderId, WorkerOrderNumber, WorkerOrderTitle, Description, Customer, SiteCode, Building, Severity, DateReported, ScheduledStart, ScheduledDate, CompletedDate, Source, SourceEmailS3Key, Problem, Trade, SubTrade, VendorNTE, AssignTo, DueDate, Priority, Status, LocationId, PO, TT, Attachments, BeforPhoto*, AfterPhoto*, SignOff*, istemplate, audit fields (CreatedDate, IsDeleted, etc.)
---
## 4. Novas colunas Fase 0 (migration)
| Coluna | Tipo | Slice |
|--------|------|-------|
| LifecycleStatus | int nullable | Core |
| LegacyStatus | nvarchar (cópia Status) | Core |
| WorkOrderType | int nullable | Core |
| PrimaryDispatchId | int nullable FK | Core |
| RowVersion | rowversion | Core |
| TargetWeek | date nullable | Scheduling |
| ScheduledEnd | datetime2 nullable | Scheduling |
| ScheduleWeekOnly | bit nullable | Scheduling |
| OriginalWeek | date nullable | Tracking |
| OriginalDate | date nullable | Tracking |
| RescheduleCount | int default 0 | Analytics |
| CarriedOver | int default 0 | Analytics |
| DocStatus | int nullable | Completion |
| AvettaTask | nvarchar max nullable | Legado SQL |
| AssignDate | date nullable | Legado SQL |
---
## 5. Dispatch
| Item | EF | Ação Fase 0 |
|------|-----|-------------|
| RowVersion | Ausente | Adicionar |
| Demais colunas | OK | Manter |
---
## 6. WorkOrderAuditLog
| Coluna | EF atual | Ação Fase 0 |
|--------|----------|-------------|
| EventType | Ausente | Adicionar |
| ActorType | Ausente | Adicionar |
| DispatchId | Ausente | Adicionar nullable |
| CorrelationId | Ausente | Adicionar nullable |
---
## 7. WorkOrderContacts
| Coluna | EF | Ação Fase 0 |
|--------|-----|-------------|
| Notes | Ausente | Adicionar nvarchar nullable (pocNotes) |
---
## 8. ApplicationUser (AspNetUsers)
| Coluna | Ação Fase 0 |
|--------|-------------|
| Initials | nvarchar(8) nullable |
| Color | nvarchar(16) nullable |
---
## 9. Tabelas revisadas — sem drift crítico adicional
- `Comments` — OK
- `Locations` — OK (SiteCode via WO.SiteCode)
- `DispatchWorkOrders` — OK
---
## 10. Recomendações
1. **Eliminar dual-path:** `db.txt` catch-up não deve ser usado após migration EF Phase0; documentar em runbook.
2. **WorkOrderType:** mapear valores SQL existentes → enum na migration data script (dry-run).
3. **AvettaTask / AssignDate:** manter nullable; PO valida uso antes de exposição board.
**Gate:** Diff assinado por Backend antes de merge migration.

View file

@ -0,0 +1,86 @@
# Field Ownership Matrix — Work Orders
**Fase:** 0
**Depende de:** [data-ownership-model.md](data-ownership-model.md)
**Status:** Draft para sign-off CTO + PO
---
## Legenda
- **Owner escritor:** ator autorizado a criar/alterar o valor canônico.
- **Sync sobrescreve?:** se ingest DynamoDB pode substituir após WO existir.
- **ManualEditFlag:** primeira edição SHOC bloqueia Sync no campo (ver [manual-edit-flag-design.md](manual-edit-flag-design.md)).
---
## Matriz completa
| Campo | Agregado | Owner escritor | Sync sobrescreve? | Conflito SHOC vs Sync |
|-------|----------|----------------|-------------------|----------------------|
| LifecycleStatus | Core | SHOC | Não após 1ª edição SHOC | SHOC vence |
| LegacyStatus | Core | — (read-only) | Não | N/A |
| AssignTo | Core | SHOC | Não após ManualEditFlag | SHOC vence |
| ScheduledDate | Scheduling | SHOC | Não | SHOC vence |
| ScheduledStart | Scheduling | SHOC | Parcial (ingest inicial) | SHOC após flag |
| ScheduledEnd | Scheduling | SHOC | Não | SHOC vence |
| TargetWeek | Scheduling | SHOC | Não | SHOC vence |
| ScheduleWeekOnly | Scheduling | SHOC | Não | SHOC vence |
| DueDate | Core | SHOC + Sync | Sim se nunca editado SHOC | SHOC se ManualEditFlag |
| Description | Core | SHOC + Sync | Sim se nunca editado SHOC | SHOC se ManualEditFlag |
| WorkerOrderTitle | Core | SHOC + Sync | Sim se nunca editado SHOC | SHOC se ManualEditFlag |
| SiteCode | Core | SHOC + Sync | Sim na criação; não após SHOC | SHOC após flag |
| Building | Core | SHOC + Sync | Sim na criação | SHOC após flag |
| LocationId | Core | SHOC + Sync | Sim na criação | SHOC após flag |
| WorkOrderType | Core | SHOC + Sync | Sim na criação ingest | SHOC após flag |
| InternalWONumber / WoNumber | Core | SHOC (Sync só na criação) | Não | SHOC only |
| ExternalWorkOrderId | Core | Sync | Sim (idempotente key) | Sync only |
| VendorId / VendorName | Dispatch | SHOC + Vendor | Não | Domínios separados |
| Dispatch Status | Dispatch | Vendor + SHOC | Não | Por contexto |
| Checklist / Signoff | Dispatch | Vendor | Não | Vendor vence |
| DocStatus | Completion | SHOC | Não | SHOC only |
| RescheduleCount | Analytics | Domain events | Não | N/A |
| CarriedOver | Analytics | Jobs (WeekRolled) | Não | N/A |
| OriginalWeek / OriginalDate | Tracking | SHOC (set-once) | Não | SHOC vence |
| POC / WorkOrderContacts.Notes | Contacts | SHOC | Não | SHOC vence |
| Priority / Severity | Core | SHOC + Sync | Sim se nunca editado | SHOC após flag |
| Status (legado string) | Core | — (deprecated write) | Não | Migrar para LifecycleStatus |
| Trade / Problem / SubTrade | Core | SHOC | Parcial Sync ingest | SHOC após flag |
| Customer / Source | Core | Sync + SHOC | Sim na criação | SHOC após flag |
| Attachments / Photos | Core | SHOC + Vendor | Não overwrite mútuo | Por domínio |
---
## Campos board (14 colunas) — ownership resumido
| Coluna board | Fonte | Owner |
|--------------|-------|-------|
| WO# | Core.InternalWONumber | SHOC |
| Type | Core.WorkOrderType | SHOC |
| Site | Core.SiteCode | SHOC |
| Status | Core.LifecycleStatus | SHOC |
| Assignee | Core.AssignTo | SHOC |
| Due | Core.DueDate | SHOC + Sync |
| Scheduled | Scheduling.ScheduledDate | SHOC |
| Vendor | Dispatch (primário) | SHOC via Dispatch |
| Appt | Dispatch.ScheduledDate | Dispatch |
| Past Due | Derivado | — |
| Carried | Analytics.CarriedOver | Jobs |
| Reschedule | Analytics.RescheduleCount | Domain |
| Doc | Completion.DocStatus | SHOC |
| Actions | — | SHOC UI |
---
## Critérios de aceite
- [ ] PO valida campos board vs matriz
- [ ] Sync merge implementado referencia esta matriz
- [ ] ManualEditFlag design aprovado
**Assinaturas**
| Papel | Data |
|-------|------|
| CTO | |
| PO | |

View file

@ -0,0 +1,87 @@
# ManualEditFlag — Design
**Fase:** 0
**Implementação:** tabela satélite `WorkOrderFieldLocks`
---
## 1. Problema
Sync/Lambda faz upsert overwrite em campos que dispatchers editam no SHOC. Precisamos saber, por campo, se houve edição manual SHOC para rejeitar ingest conflitante.
---
## 2. Solução escolhida: tabela satélite
```text
WorkOrderFieldLocks
Id (PK)
WorkOrderId (FK)
FieldName (string, max 64)
LockedAt (UTC)
LockedByUserId (nullable — null = system/bootstrap)
UNIQUE (WorkOrderId, FieldName)
```
**Alternativa descartada:** bitmask — difícil de auditar e estender.
---
## 3. Comportamento
| Evento | Ação |
|--------|------|
| SHOC edita campo X pela 1ª vez | INSERT lock (WorkOrderId, FieldName) |
| Sync tenta atualizar campo X com lock | Skip update; audit `SyncRejected` |
| Sync atualiza campo Y sem lock | Apply merge normal |
| WO novo (Sync create) | Sem locks; full ingest |
| Blazor edita campo legado | **Não** cria lock Fase 0 (gap conhecido; Blazor congelado) |
---
## 4. Campos elegíveis a lock (SHOC writers)
Definidos em `SyncFieldMergePolicy.ShocOwnedFields`:
- LifecycleStatus, AssignTo, ScheduledDate, ScheduledEnd, TargetWeek, ScheduleWeekOnly
- DueDate, Description, WorkerOrderTitle, SiteCode, Building, LocationId
- WorkOrderType, DocStatus, Trade, Problem, Priority, InternalWONumber
---
## 5. Audit em rejeição
```json
{
"eventType": "Sync",
"action": "SyncRejected",
"fieldName": "DueDate",
"oldValue": "2026-06-01",
"newValue": "2026-06-15",
"actorType": "Sync"
}
```
---
## 6. API interna
```csharp
interface IWorkOrderFieldLockService
{
Task LockFieldAsync(int workOrderId, string fieldName, string? userId);
Task<bool> IsLockedAsync(int workOrderId, string fieldName);
}
```
Implementação: `WorkOrderFieldLockService` em `SeaHaven.Services`.
Lock criado automaticamente por `IWorkOrderAuditService` em eventos `Manual` + `FieldChanged` / `StatusChanged`.
---
## 7. Critérios de aceite
- [ ] Tabela criada na migration Phase0
- [ ] Sync consulta locks antes de overwrite
- [ ] SyncRejected aparece no audit log

View file

@ -0,0 +1,58 @@
# Migration Dry-Run Report — Phase 0
**Migration:** `20260624163145_Phase0_DomainFoundation`
**Status:** Pronto para execução em staging
---
## 1. Procedimento
1. Restore backup produção → staging espelho
2. Snapshot pré-migration:
```sql
SELECT Status, COUNT(*) FROM workOrders GROUP BY Status;
SELECT COUNT(*) AS Total FROM workOrders;
```
3. Executar:
```powershell
dotnet ef database update --project Data.SeaHavenIndustries --startup-project Api.SeaHavenIndustries
```
4. Validar pós-migration:
```sql
SELECT COUNT(*) FROM workOrders WHERE LegacyStatus IS NOT NULL;
SELECT LifecycleStatus, COUNT(*) FROM workOrders GROUP BY LifecycleStatus;
SELECT COUNT(*) FROM workOrders WHERE PrimaryDispatchId IS NOT NULL;
```
5. Smoke Blazor + API legado + Sync sample
6. Rollback (se falha):
```powershell
dotnet ef database update 20260417195316_AddDispatchVerification --project Data.SeaHavenIndustries --startup-project Api.SeaHavenIndustries
```
---
## 2. Data scripts incluídos na migration
- `LegacyStatus` ← cópia de `Status`
- `LifecycleStatus` ← mapeamento legado (Open→Incomplete, etc.)
- `PrimaryDispatchId` ← dispatch mais recente por WO
---
## 3. Resultados (preencher em staging)
| Métrica | Antes | Depois |
|---------|-------|--------|
| Total WOs | | |
| Com LegacyStatus | | |
| Com LifecycleStatus | | |
| Com PrimaryDispatchId | | |
| Rollback testado | | Sim/Não |
---
## 4. Notas
A migration inclui reconciliação de drift em `Locations`, `Regions`, `Departments` detectada pelo EF — revisar impacto em staging antes de produção.
**WO# normalização 11 dígitos:** documentada para Fase 2; não executada nesta migration.

View file

@ -0,0 +1,46 @@
# Phase 0 — Gates Sign-Off Checklist
**Programa:** Work Orders Board
**Fase:** 0 — Domain Foundation + Governança
---
## 9 Gates bloqueantes
| # | Gate | Artefato | Status |
|---|------|----------|--------|
| 1 | DAR aprovado | [dar-domain-architecture-review.md](dar-domain-architecture-review.md) | Documentado |
| 2 | Data Ownership Model | [data-ownership-model.md](data-ownership-model.md) | Documentado |
| 3 | Field Ownership Matrix | [field-ownership-matrix.md](field-ownership-matrix.md) | Documentado |
| 4 | Audit Event Contract | [audit-event-contract.md](audit-event-contract.md) | Implementado baseline |
| 5 | Volume Discovery | [volume-discovery-report.md](volume-discovery-report.md) | Queries prontas; tier S provisório |
| 6 | Database drift | [database-drift-report.md](database-drift-report.md) | Documentado |
| 7 | Spike vendor/dispatch | [adr-vendor-source-of-truth.md](adr-vendor-source-of-truth.md) | ADR + query |
| 8 | Migration dry-run | [migration-dry-run-report.md](migration-dry-run-report.md) | Procedimento pronto |
| 9 | RowVersion multi-agregado | WO + Dispatch + ConcurrencyExceptionFilter | Implementado |
---
## Implementação código (Fase 0)
- Migration `Phase0_DomainFoundation`
- Enums: LifecycleStatus, WorkOrderType, DocStatus, AuditEventType, etc.
- `IWorkOrderAuditService`, `IWorkOrderFieldLockService`, `ISyncFieldMergePolicy`
- `[Authorize]` reativado em WorkOrderController
- Projeto `SeaHavenIndustries.Tests`
---
## GO Fase 1 — pendente
- [ ] Sign-off CTO/PO nos artefatos
- [ ] Dry-run staging executado
- [ ] Volume tier confirmado em staging/prod
- [ ] Smoke checklist verde
**Assinatura GO Fase 1**
| Papel | Nome | Data |
|-------|------|------|
| CTO | | |
| PO | | |

View file

@ -0,0 +1,36 @@
# RowVersion Multi-Agregado — Design
**Fase:** 0 — Gate #9
---
## Agregados
| Agregado | Coluna | Comportamento |
|----------|--------|---------------|
| WorkOrder | `RowVersion` rowversion | Bump em mutação WO (schedule, status, assignment) |
| Dispatch | `RowVersion` rowversion | Bump em mutação vendor/checklist/signoff |
---
## HTTP 409
`ConcurrencyExceptionFilter` captura `DbUpdateConcurrencyException` e retorna:
```json
{
"status": "Conflict",
"message": "The record was modified by another user. Refresh and retry.",
"code": 409
}
```
---
## Regras (Fase 2 testes completos)
- SHOC edita ScheduledDate → bump WO.RowVersion
- SHOC edita vendor → bump Dispatch.RowVersion
- Vendor edita checklist → bump Dispatch.RowVersion only
Testes de concorrência paralela: Fase 2 (`Concurrency integration tests` backlog #27).

View file

@ -0,0 +1,57 @@
# Smoke Test Checklist — Multi-Consumidor (Fase 0)
**Ambiente:** Docker local (`docker compose up -d` + `scripts/setup-local-db.ps1`)
---
## Blazor EF
| # | Teste | Pass |
|---|-------|------|
| 1 | `/workorderlist` carrega sem exception | [ ] |
| 2 | Abrir detalhe WO existente | [ ] |
| 3 | Campos novos nullable não quebram binding | [ ] |
---
## API REST (legado)
| # | Teste | Pass |
|---|-------|------|
| 1 | POST login → JWT | [ ] |
| 2 | GET work order list com Bearer token | [ ] |
| 3 | POST ChangeStatus com audit no log | [ ] |
| 4 | POST ChangeAssignment.tex Assignment com audit | [ ] |
| 5 | 401 sem token (auth reativada) | [ ] |
---
## Sync
| # | Teste | Pass |
|---|-------|------|
| 1 | POST `api/Sync/WorkOrders` com JWT (mock/staging DynamoDB) | [ ] |
| 2 | Upsert idempotente por ExternalWorkOrderId | [ ] |
| 3 | Campo locked → SyncRejected no audit | [ ] |
---
## Vendor Portal
| # | Teste | Pass |
|---|-------|------|
| 1 | Checklist update flow | [ ] |
| 2 | Signoff flow | [ ] |
| 3 | Dispatch RowVersion presente (schema) | [ ] |
---
## Automação local
```powershell
dotnet test SeaHavenIndustries.Tests
dotnet build
./scripts/setup-local-db.ps1
```
Testes unitários cobrem: LifecycleStatusMapper, audit baseline, sync merge rejection.

View file

@ -0,0 +1,96 @@
# Volume Discovery Report — Work Orders
**Fase:** 0 — Gate Fase 1
**Data:** 2026-06-24
**Ambiente:** Local dev (Docker) + queries para staging/prod
---
## 1. Objetivo
Definir tier S/M/L/XL antes de índices e estratégia de search (Fase 4). **Sem premissa de volume.**
---
## 2. Queries obrigatórias
Executar em staging ou produção read-only:
```sql
-- Total WOs
SELECT COUNT(*) AS TotalWorkOrders
FROM workOrders
WHERE IsDeleted IS NULL OR IsDeleted = 0;
-- WOs por semana (ISO) — p95
WITH WeeklyCounts AS (
SELECT DATEPART(iso_week, ScheduledDate) AS IsoWeek,
YEAR(ScheduledDate) AS IsoYear,
COUNT(*) AS Cnt
FROM workOrders
WHERE ScheduledDate IS NOT NULL
AND (IsDeleted IS NULL OR IsDeleted = 0)
GROUP BY DATEPART(iso_week, ScheduledDate), YEAR(ScheduledDate)
)
SELECT MAX(Cnt) AS P95ProxyMaxPerWeek,
AVG(Cnt * 1.0) AS AvgPerWeek
FROM WeeklyCounts;
-- Unscheduled (não terminal)
SELECT COUNT(*) AS UnscheduledCount
FROM workOrders
WHERE ScheduledDate IS NULL
AND (IsDeleted IS NULL OR IsDeleted = 0)
AND Status NOT IN ('Completed', 'Complete', 'Cancelled', 'Canceled');
-- Crescimento mensal
SELECT YEAR(CreatedDate) AS Y, MONTH(CreatedDate) AS M, COUNT(*) AS Cnt
FROM workOrders
WHERE CreatedDate IS NOT NULL
GROUP BY YEAR(CreatedDate), MONTH(CreatedDate)
ORDER BY Y DESC, M DESC;
```
---
## 3. Resultados
| Métrica | Local Docker (dev) | Staging | Produção |
|---------|-------------------|---------|----------|
| Total WOs | *executar após seed* | _pendente acesso_ | _pendente acesso_ |
| WOs/semana p95 | — | _pendente_ | _pendente_ |
| Unscheduled | — | _pendente_ | _pendente_ |
| Crescimento mensal | — | _pendente_ | _pendente_ |
**Nota:** Ambiente local inicia vazio. Tier provisório para desenvolvimento: **S** (< 25k). Confirmar com query em staging antes do GO Fase 1.
---
## 4. Classificação tier (§10.2 roadmap)
| Tier | Total WOs | Listagem semanal | Advanced search |
|------|-----------|------------------|-----------------|
| **S** | < 25k | Índices simples | SQL filtros compostos |
| **M** | 25k–250k | Índices covering | Paginação obrigatória |
| **L** | 250k–1M | Read model candidato | Full-text |
| **XL** | > 1M | Materialized view | Search dedicado (ARB) |
**Tier definido para Fase 1:** **S (provisório dev)** — revisar ao obter métricas staging.
---
## 5. Impacto Fase 0–1
- Fases 1–3: índices conservadores compatíveis com qualquer tier.
- Fase 4: implementação search conforme tier confirmado + load test.
---
## 6. Script local
```powershell
./scripts/setup-local-db.ps1
# Executar queries acima via sqlcmd ou DBCode contra localhost:1433
```
**Gate:** Preencher coluna Staging/Produção antes do GO Fase 1.