diff --git a/docs/adr/work-orders-board-api.md b/docs/adr/work-orders-board-api.md deleted file mode 100644 index 9501406..0000000 --- a/docs/adr/work-orders-board-api.md +++ /dev/null @@ -1,194 +0,0 @@ -# ADR: Work Orders Board API (Fase 1 read-only) - -**Status:** Proposed — spikes G0, G1, G3 concluídas -**Data:** 2026-06-19 -**Deciders:** Backend + FE (aprovação Fase 1 pending) - ---- - -## Contexto - -O Schedule Board (FE) organiza work orders por `ScheduledDate` em visão semanal. O backend atual expõe `GetWorkOrderList` — listagem CRUD paginada sem janela temporal, sem `siteCode`, sem vendor inline, sem `ScheduledDate` na projeção. - -Spikes de referência: - -- [Consumer Audit](../spikes/consumer-audit-getworkorderlist.md) -- [WorkOrderType](../spikes/work-order-type.md) -- [Search Strategy](../spikes/search-strategy.md) - ---- - -## Decisão - -### 1. Endpoint dedicado (sem `projection=`) - -Criar endpoint **separado** para o Schedule Board. **Não alterar** `GetWorkOrderList`. - -```http -GET /api/workorders/GetWorkOrdersBoard - ?scheduledFrom=2026-06-01 - &scheduledTo=2026-06-07 - &assignee=guid1&assignee=guid2 - &search=BK5 - &status=Open&status=InProgress - &locationId=42 -``` - -Rota alias: `GET /api/WorkOrder/GetWorkOrdersBoard` (consistente com controller existente). - -**Rejeitado:** - -- `GET /api/workorders/table` -- `GET /GetWorkOrderList?projection=table` - -### 2. Window-based fetch (sem paginação Fase 1) - -| Abordagem | Fase 1 | -|-----------|--------| -| `scheduledFrom` + `scheduledTo` | **Obrigatório** | -| `page` / `pageSize` | **Ausente** | -| Limite janela | Max **90 dias** (validação server-side) | - -Response envelope: - -```json -{ - "scheduledFrom": "2026-06-01", - "scheduledTo": "2026-06-07", - "totalCount": 42, - "items": [ - { - "row": { /* WorkOrderBoardRowDto */ }, - "presentation": { /* BoardRowPresentation */ } - } - ] -} -``` - -### 3. DTO — domínio vs presentation - -**`WorkOrderBoardRowDto`** — dados persistidos / joináveis: - -```csharp -public record WorkOrderBoardRowDto( - int Id, - string? WoNumber, - string? SiteCode, - string? LocationLabel, - DateOnly? ScheduledDate, - TimeOnly? ScheduledStart, - DateOnly? DueDate, - string? WorkOrderType, - string? ServiceType, - string? AssigneeId, - string? AssigneeName, - string? VendorCompany, - string? VendorTechnician, - string Status, - string? PocName, - string? PocPhone -); -``` - -**`BoardRowPresentation`** — calculado no mapper backend: - -```csharp -public record BoardRowPresentation( - string StatusDisplay, - bool IsPastDue, - string? WorkOrderTypeDisplay -); -``` - -Mapper: `IWorkOrderBoardMapper` centraliza Past Due e type display. - -### 4. Query EF (Fase 1) - -- Filtro base: `istemplate != true` -- Janela: `ScheduledDate >= from && ScheduledDate < to.AddDays(1)` -- Includes: `Locations`, `AssignToUser`, `Dispatches` → `Vendor` (1 vendor por WO — first active dispatch) -- POC: `WorkOrderContacts` (primary) -- Service type: `Trade` ou category join (mínimo: `Trade`) -- **Não incluir** subquery `lastUpdated` (Comments/AuditLog) - -### 5. Search (conforme spike) - -- Search scoped à janela temporal -- Servidor: `StartsWith` em `InternalWONumber`, `SiteCode` -- Count ≤ **300**: retornar semana inteira; FE filtra title/location/serviceType -- Count > 300: exigir termo indexável ou retornar 400 -- Assignee: `assigneeId[]` multi — **não** filtrar por nome - -Índices (migration separada pós-aprovação): - -- `IX_workOrders_ScheduledDate` (P1) -- `IX_workOrders_InternalWONumber`, `IX_workOrders_SiteCode` (P2, após fix tamanho coluna) - -### 6. WorkOrderType - -- Mapear coluna legada `WorkOrderType varchar(50)` na entidade EF -- Expor em `WorkOrderBoardRowDto` -- `IsPastDue` em presentation — **não** confundir com tipo -- Enum C# adiado até amostragem prod - -### 7. Status mapping - -- API retorna status **canônico** (`Open`, `InProgress`, `Completed`, `Cancelled`, `OnHold`) -- `StatusDisplay` via `IWorkOrderStatusMapper` (workshop G2 separado) -- Sem migration de enum DB - ---- - -## Consequências - -### Positivas - -- 1 HTTP call por mudança de semana (FE) -- Contrato estável para board sem acoplar CRUD legado -- Performance previsível (janela + threshold 300) -- Blazor legado inalterado (EF local) - -### Negativas / trade-offs - -- Duplicação parcial de lógica de listagem vs `GetWorkOrderList` (aceitável — bounded contexts distintos) -- Vendor technician pode ser null Fase 1 se dispatch spike incompleto -- Setup DB local necessário para validação integrada - ---- - -## Compliance com spikes - -| Spike | Gate | Resultado | -|-------|------|-----------| -| Consumer Audit | G0 | `GetWorkOrderList` órfão no repo — endpoint dedicado aprovado | -| WorkOrderType | G1 | Mapear coluna EF; Overdue = presentation | -| Search Strategy | G3 | Window-first, threshold 300, StartsWith identificadores | - ---- - -## Fora de escopo Fase 1 - -- PATCH inline edit -- Completion doc, media, flags, reorder -- Search vendor/tech server-side -- Paginação cross-week -- Alteração de `GetWorkOrderList` - ---- - -## Próximos passos (implementação) - -1. Aprovação explícita **Fase 1 only** -2. Migration: `WorkOrderType` + índice `ScheduledDate` -3. `GetWorkOrdersBoard` em `WorkOrderController` + `WorkOrderDataService` -4. DTOs + `IWorkOrderBoardMapper` -5. FE: `useWorkOrdersBoard({ from, to })` substituindo `RAW_ORDERS` - ---- - -## Referências - -- Gap analysis v3: `work_orders_api_gap_analysis_ce735946.plan.md` -- [`WorkOrderController.cs`](../../Api.SeaHavenIndustries/Controllers/WorkOrderController.cs) -- [`WorkOrderDataService.cs`](../../SeaHaven.DataServices/Implementation/WorkOrderDataService.cs) -- [`DispatchController.List`](../../Api.SeaHavenIndustries/Controllers/DispatchController.cs) — padrão janela temporal diff --git a/docs/auditoria-work-orders-api-vs-frontend.md b/docs/auditoria-work-orders-api-vs-frontend.md deleted file mode 100644 index b8a0ce6..0000000 --- a/docs/auditoria-work-orders-api-vs-frontend.md +++ /dev/null @@ -1,299 +0,0 @@ -# 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. diff --git a/docs/roadmap-work-orders-board.md b/docs/roadmap-work-orders-board.md deleted file mode 100644 index 4af57f4..0000000 --- a/docs/roadmap-work-orders-board.md +++ /dev/null @@ -1,907 +0,0 @@ -# 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. diff --git a/docs/spikes/search-strategy.md b/docs/spikes/search-strategy.md deleted file mode 100644 index 273e32d..0000000 --- a/docs/spikes/search-strategy.md +++ /dev/null @@ -1,181 +0,0 @@ -# Spike Search Strategy — `GetWorkOrdersBoard` - -**Data:** 2026-06-19 -**Gate:** G3 (pré-requisito Fase 1) -**Objetivo:** Definir busca mínima viável para o Schedule Board sem degradar performance. - ---- - -## Estado atual (`GetWorkOrderList`) - -Implementação em `SeaHaven.DataServices/Implementation/WorkOrderDataService.cs` (`GetWorkOrderListPagedAsync`): - -| Aspecto | Comportamento atual | -|---------|---------------------| -| Paginação | `page` / `pageSize` (default 12), sem cap | -| Janela temporal | **Ausente** — sem filtro `ScheduledDate` | -| Search | `Contains` + `ToLower()` em 5 campos | -| Campos search | `InternalWONumber`, `WorkerOrderNumber`, `WorkerOrderTitle`, `Locations.Title`, `Locations.Name` | -| Campos omitidos | `SiteCode`, `Trade`, `Problem`, vendor, dispatcher por ID | -| Joins | `Locations`, `AssignToUser` (AspNetUsers) | -| Vendor/dispatch | **Não incluídos** | -| Projeção extra | Subquery `lastUpdated` via Comments + WorkOrderAuditLogs por linha | - -### Padrão SQL gerado (search) - -```sql -WHERE LOWER(InternalWONumber) LIKE '%term%' - OR LOWER(WorkerOrderNumber) LIKE '%term%' - OR LOWER(WorkerOrderTitle) LIKE '%term%' - OR LOWER(Locations.Name) LIKE '%term%' - ... -``` - -Leading wildcard → table scan em colunas `nvarchar(max)`. - ---- - -## Respostas às perguntas do spike - -| # | Pergunta | Decisão Fase 1 | -|---|----------|----------------| -| S1 | Volume típico por semana | Assumir **< 200 WOs/semana** até medir em prod; threshold fallback = **300** | -| S2 | Campos pesquisáveis mínimos | `internalWONumber`, `siteCode`, `assigneeId[]` (exato), `locationId` | -| S3 | LIKE vs full-text | **Sem full-text** Fase 1; `StartsWith` em WO# e siteCode; evitar `Contains` em title/location no servidor | -| S4 | Escopo | Search **sempre scoped** a `scheduledFrom` / `scheduledTo` (obrigatório no endpoint) | -| S5 | Vendor/tech no search | **Fase 2** — exige join `Dispatches → Vendor` | - ---- - -## Estratégia em 2 camadas - -``` -Request GetWorkOrdersBoard - │ - ├─► 1. Filtro janela ScheduledDate (obrigatório) - │ - ├─► 2. Filtros exatos: status, assigneeId[], locationId - │ - ├─► 3. COUNT no escopo - │ - ├─► count <= 300? - │ ├─ SIM → retornar semana inteira (sem paginação Fase 1) - │ │ search textual extra no FE: title, locationLabel, serviceType - │ └─ NÃO → exigir critério indexável no servidor - │ (woNumber ou siteCode StartsWith) - │ - └─► Response: { scheduledFrom, scheduledTo, totalCount, items[] } -``` - -### Campos Fase 1 vs Fase 2 - -| Campo | Fase 1 (servidor) | Fase 1 (client fallback) | Fase 2 | -|-------|-------------------|--------------------------|--------| -| `internalWONumber` / `workerOrderNumber` | `StartsWith` | — | — | -| `siteCode` | `StartsWith` ou igualdade | — | — | -| `assigneeId` | multi-select exato | — | — | -| `locationId` | exato | — | — | -| `status` | exato / multi | pills client-side | — | -| `workerOrderTitle` | — | client-side se count ≤ 300 | server `Contains` opcional | -| `locationLabel` | — | client-side | — | -| `serviceType` (trade/category) | — | client-side | server join | -| `vendorCompany` / `vendorTechnician` | — | — | join Dispatch→Vendor | -| `assigneeName` | — | client-side | evitar join por nome | - ---- - -## Query plan esperado (Fase 1) - -### Caminho feliz (semana típica, ≤ 300 rows) - -1. **Index seek** em `IX_workOrders_ScheduledDate` (proposto) com range `[scheduledFrom, scheduledTo+1day)` -2. Filtro `istemplate != true` -3. Filtros exatos em `AssignTo`, `LocationId`, `Status` (AND) -4. **Sem** search textual no servidor se `search` vazio -5. Join LEFT `Locations`, `AssignToUser`, LEFT `Dispatches` + `Vendor` (Fase 1 board — vendor no DTO, não no search) -6. Projeção flat para `WorkOrderBoardRowDto` — **sem** subquery `lastUpdated` - -### Caminho search servidor (count > 300 ou user digitou termo indexável) - -1. Mesma janela + filtros exatos -2. AND (`InternalWONumber LIKE 'term%'` OR `SiteCode LIKE 'term%'`) -3. Se count ainda > 300 → HTTP 400 com mensagem "Refine search or narrow date window" - -### Anti-padrões proibidos no board endpoint - -- `pageSize=500` como gambiarra de fetch semanal -- `Contains('%x%')` em múltiplas tabelas sem janela temporal -- Filtro assignee por nome concatenado (`FirstName + LastName`) — usar `assigneeId` -- Subquery correlated Comments/AuditLog por row - ---- - -## Índices recomendados - -Migration separada **após aprovação** desta spike (não incluir na Fase 1 code sem review): - -| Índice | Coluna(s) | Prioridade | Nota | -|--------|-----------|------------|------| -| `IX_workOrders_ScheduledDate` | `ScheduledDate` | **P1** | Filtro de janela do board | -| `IX_workOrders_InternalWONumber` | `InternalWONumber` | P2 | Requer alterar coluna de `nvarchar(max)` → `nvarchar(50)` | -| `IX_workOrders_SiteCode` | `SiteCode` | P2 | Idem — tamanho fixo ~50 | -| `IX_workOrders_AssignTo` | `AssignTo` | existente | Multi-select dispatcher | -| `IX_workOrders_LocationId` | `LocationId` | existente | Filtro location | - -**Full-text index:** não recomendado Fase 1 — volume semanal baixo + fallback client-side suficiente. - ---- - -## Referência de implementação - -Copiar padrão de janela temporal de `DispatchController.List`: - -```csharp -if (dateFrom.HasValue) - q = q.Where(d => d.DispatchedAt >= dateFrom.Value); -if (dateTo.HasValue) -{ - var end = dateTo.Value.Date.AddDays(1); - q = q.Where(d => d.DispatchedAt < end); -} -``` - -Adaptar para `WorkOrder.ScheduledDate` no board endpoint. - ---- - -## Parâmetros propostos — `GetWorkOrdersBoard` - -| Parâmetro | Tipo | Obrigatório | Descrição | -|-----------|------|-------------|-----------| -| `scheduledFrom` | `DateOnly` | **Sim** | Início da semana/janela | -| `scheduledTo` | `DateOnly` | **Sim** | Fim da janela (inclusivo) | -| `search` | `string` | Não | WO# ou siteCode prefix | -| `assignee` | `string[]` | Não | IDs de dispatcher (multi) | -| `status` | `string[]` | Não | Status canônico API | -| `locationId` | `int?` | Não | Filtro location | - -**Limite hard janela:** max **90 dias** (Fase 2 advanced search cross-week). - ---- - -## Threshold e fallback - -| Constante | Valor | Justificativa | -|-----------|-------|---------------| -| `BoardSearchClientSideThreshold` | **300** | Payload ~300 rows × ~500 bytes ≈ 150 KB — aceitável para 1 call/semana | -| Sem paginação Fase 1 | — | Janela semanal é o filtro natural | -| Paginação Fase 2 | cursor/offset | Só quando janela > 1 semana com search global | - ---- - -## Conclusão - -Fase 1 adota **window-first, search-second**: - -1. Janela `ScheduledDate` obrigatória + índice dedicado -2. Search servidor mínimo (`StartsWith` em identificadores) -3. Fallback client-side para campos ricos quando count ≤ 300 -4. Vendor/tech e full-text adiados para Fase 2 - -Esta estratégia desbloqueia `GetWorkOrdersBoard` sem replicar os anti-padrões de `GetWorkOrderListPagedAsync`. diff --git a/docs/work-orders/phase-0/README.md b/docs/work-orders/phase-0/README.md deleted file mode 100644 index 239469e..0000000 --- a/docs/work-orders/phase-0/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# 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) diff --git a/docs/work-orders/phase-0/adr-vendor-source-of-truth.md b/docs/work-orders/phase-0/adr-vendor-source-of-truth.md deleted file mode 100644 index 6dbafa9..0000000 --- a/docs/work-orders/phase-0/adr-vendor-source-of-truth.md +++ /dev/null @@ -1,67 +0,0 @@ -# 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 diff --git a/docs/work-orders/phase-0/audit-event-contract.md b/docs/work-orders/phase-0/audit-event-contract.md deleted file mode 100644 index bb773f4..0000000 --- a/docs/work-orders/phase-0/audit-event-contract.md +++ /dev/null @@ -1,106 +0,0 @@ -# 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: _______ diff --git a/docs/work-orders/phase-0/consumer-inventory.md b/docs/work-orders/phase-0/consumer-inventory.md deleted file mode 100644 index 5d1e35d..0000000 --- a/docs/work-orders/phase-0/consumer-inventory.md +++ /dev/null @@ -1,85 +0,0 @@ -# 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. diff --git a/docs/work-orders/phase-0/dar-domain-architecture-review.md b/docs/work-orders/phase-0/dar-domain-architecture-review.md deleted file mode 100644 index 481a20e..0000000 --- a/docs/work-orders/phase-0/dar-domain-architecture-review.md +++ /dev/null @@ -1,139 +0,0 @@ -# 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 | | | diff --git a/docs/work-orders/phase-0/dar-persisted-vs-derived-matrix.md b/docs/work-orders/phase-0/dar-persisted-vs-derived-matrix.md deleted file mode 100644 index ceef146..0000000 --- a/docs/work-orders/phase-0/dar-persisted-vs-derived-matrix.md +++ /dev/null @@ -1,54 +0,0 @@ -# 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: _______ diff --git a/docs/work-orders/phase-0/data-ownership-model.md b/docs/work-orders/phase-0/data-ownership-model.md deleted file mode 100644 index 1c15bbf..0000000 --- a/docs/work-orders/phase-0/data-ownership-model.md +++ /dev/null @@ -1,75 +0,0 @@ -# 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: _______ diff --git a/docs/work-orders/phase-0/database-drift-report.md b/docs/work-orders/phase-0/database-drift-report.md deleted file mode 100644 index 8d3ce76..0000000 --- a/docs/work-orders/phase-0/database-drift-report.md +++ /dev/null @@ -1,110 +0,0 @@ -# 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. diff --git a/docs/work-orders/phase-0/field-ownership-matrix.md b/docs/work-orders/phase-0/field-ownership-matrix.md deleted file mode 100644 index 7bfe7f7..0000000 --- a/docs/work-orders/phase-0/field-ownership-matrix.md +++ /dev/null @@ -1,86 +0,0 @@ -# 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 | | diff --git a/docs/work-orders/phase-0/manual-edit-flag-design.md b/docs/work-orders/phase-0/manual-edit-flag-design.md deleted file mode 100644 index 548727f..0000000 --- a/docs/work-orders/phase-0/manual-edit-flag-design.md +++ /dev/null @@ -1,87 +0,0 @@ -# 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 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 diff --git a/docs/work-orders/phase-0/migration-dry-run-report.md b/docs/work-orders/phase-0/migration-dry-run-report.md deleted file mode 100644 index 2a815e0..0000000 --- a/docs/work-orders/phase-0/migration-dry-run-report.md +++ /dev/null @@ -1,58 +0,0 @@ -# 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. diff --git a/docs/work-orders/phase-0/phase-0-gates-signoff.md b/docs/work-orders/phase-0/phase-0-gates-signoff.md deleted file mode 100644 index ddc4199..0000000 --- a/docs/work-orders/phase-0/phase-0-gates-signoff.md +++ /dev/null @@ -1,46 +0,0 @@ -# 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 | | | diff --git a/docs/work-orders/phase-0/rowversion-design.md b/docs/work-orders/phase-0/rowversion-design.md deleted file mode 100644 index 9e80ddc..0000000 --- a/docs/work-orders/phase-0/rowversion-design.md +++ /dev/null @@ -1,36 +0,0 @@ -# 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). diff --git a/docs/work-orders/phase-0/smoke-checklist.md b/docs/work-orders/phase-0/smoke-checklist.md deleted file mode 100644 index 0bad17e..0000000 --- a/docs/work-orders/phase-0/smoke-checklist.md +++ /dev/null @@ -1,57 +0,0 @@ -# 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. diff --git a/docs/work-orders/phase-0/volume-discovery-report.md b/docs/work-orders/phase-0/volume-discovery-report.md deleted file mode 100644 index 12c48c3..0000000 --- a/docs/work-orders/phase-0/volume-discovery-report.md +++ /dev/null @@ -1,96 +0,0 @@ -# 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. diff --git a/docs/work-orders/phase-1/README.md b/docs/work-orders/phase-1/README.md deleted file mode 100644 index 5442c49..0000000 --- a/docs/work-orders/phase-1/README.md +++ /dev/null @@ -1,155 +0,0 @@ -# Fase 1 — Weekly Board (leitura) - -**Programa:** Work Orders Board -**Objetivo:** Endpoint window-based para carregar a visão semanal do SHOC (Mon–Fri + Unscheduled) com projeção das 14 colunas operacionais. - ---- - -## Endpoints - -### `GET /api/workorders/board` - -Retorna WOs agendadas na semana + seção **Unscheduled** em uma única chamada. - -**Autenticação:** Bearer JWT (`[Authorize]`) - -**Query params:** - -| Param | Obrigatório | Descrição | -|-------|-------------|-----------| -| `weekStart` | Sim | Segunda-feira da semana (`YYYY-MM-DD`) | -| `weekEnd` | Não | Sexta-feira (default: `weekStart + 4 dias`) | -| `dispatchers` | Não | Lista de GUIDs; use `__unassigned__` para não atribuídos | -| `myWorkOrders` | Não | `true` → filtra `AssignTo == usuário logado` | -| `types` | Não | Valores enum `WorkOrderType` (`PM`, `PO`, `Emergency`, etc.) | -| `search` | Não | Busca contextual (site, WO#, location, dispatcher, trade, vendor, status) | - -**Exemplo:** - -```bash -curl -H "Authorization: Bearer " \ - "https://localhost:5001/api/workorders/board?weekStart=2026-06-22&myWorkOrders=true" -``` - -**Response:** - -```json -{ - "weekStart": "2026-06-22", - "weekEnd": "2026-06-26", - "counts": { "returned": 42, "total": 58 }, - "unscheduled": [ /* WorkOrderBoardRowDto[] */ ], - "scheduled": [ /* WorkOrderBoardRowDto[] — agrupar por dayGroup no FE */ ] -} -``` - -- `counts.total` — WOs agendadas na semana (filtros dispatcher/tipo, **sem** search) -- `counts.returned` — WOs agendadas após aplicar search - -### `GET /api/workorders/lookups/dispatchers` - -Lista dispatchers para filtros do board. - -```json -[ - { "id": "guid", "name": "Jane Doe", "initials": "JD", "color": "#4A90D9" } -] -``` - ---- - -## Matriz coluna → campo API - -| Coluna board | Campos response | -|--------------|-----------------| -| WO# | `woNumber`, `rescheduleCount`, `carriedOver` | -| Type | `workOrderType`, `isPastDue` | -| Site | `siteCode`, `locationName`, `pocName`, `pocPhone`, `pocNotes` | -| Status | `lifecycleStatus`, `lifecycleStatusLabel`, `legacyStatus` | -| Assignee | `dispatcherId`, `dispatcherName`, `initials`, `color` | -| Due | `dueDate` | -| Scheduled | `scheduledDate`, `targetWeek`, `scheduleWeekOnly`, `dayGroup` | -| Vendor | `vendorId`, `vendorName`, `techName`, `techPhone` | -| Appt | `apptDate`, `apptTime` | -| Past Due | `isPastDue` (derivado on-read) | -| Carried | `carriedOver` | -| Reschedule | `rescheduleCount` | -| Doc | `docStatus` | -| Actions | FE only | - ---- - -## Regras de derivação - -### isPastDue - -``` -ScheduledDate < UTC hoje -AND LifecycleStatus NOT IN (Complete, Canceled, Closed) -``` - -Implementação: `SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs` - -### Vendor / Appt - -Projeção read-only via `WorkOrder.PrimaryDispatchId` → `Dispatch` → `Vendor` ([ADR](../phase-0/adr-vendor-source-of-truth.md)). - -### Janela semanal - -WO entra em **scheduled** se: - -- `ScheduledDate` entre `weekStart` e `weekEnd`, **ou** -- `ScheduleWeekOnly == true` e `TargetWeek == weekStart` - -**Unscheduled:** `ScheduledDate == null` e status não terminal. - ---- - -## Código entregue - -| Camada | Arquivo | -|--------|---------| -| DTOs | `SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs` | -| Derived fields | `SeaHaven.Services/Helpers/WorkOrderDerivedFields.cs` | -| Data | `SeaHaven.DataServices/Implementation/WorkOrderBoardDataService.cs` | -| Service | `SeaHaven.Services/Implementation/WorkOrderBoardService.cs` | -| API | `Api.SeaHavenIndustries/Controllers/WorkOrderController.cs` (`board`, `lookups/dispatchers`) | -| Migration | `Data.SeaHavenIndustries/Migrations/20260624180343_Phase1_BoardIndexes.cs` | -| Testes | `SeaHavenIndustries.Tests/WorkOrderDerivedFieldsTests.cs`, `WorkOrderBoardServiceTests.cs` | - ---- - -## Gates de aceite - -### Desenvolvimento (concluído) - -- [x] Endpoint `GET board` — semana + Unscheduled -- [x] Projeção 12/14 colunas de dados -- [x] `isPastDue` derivado on-read -- [x] Vendor via dispatch primário -- [x] Filtros dispatcher / My WOs / tipo / search -- [x] Contador X of Y -- [x] Índices Tier S -- [x] 14 testes unitários board (+ 12 Fase 0 = 26 total) - -### Staging / produção (pendente) - -- [ ] Sign-off CTO/PO (herda gates Fase 0) -- [ ] Dry-run migration Phase0 + Phase1 em staging -- [ ] Tier volume confirmado em staging/prod -- [ ] Smoke checklist Blazor / Sync / Portal -- [ ] Gate Vendor Portal regression (endpoints dispatch inalterados) -- [ ] Latência aceitável com volume real (Tier S: < 25k WOs) -- [ ] Feature flag SHOC board em staging - ---- - -## Coexistência - -Endpoints legados (`GetWorkOrderList`, `GetWorkorderById`, etc.) **não foram alterados**. O board usa contrato novo em rotas separadas. - ---- - -## Próximo passo - -**Fase 2** — Inline edit + concorrência (dual RowVersion, audit por campo, PATCH granular). diff --git a/docs/work-orders/phase-2/README.md b/docs/work-orders/phase-2/README.md deleted file mode 100644 index 584e8ad..0000000 --- a/docs/work-orders/phase-2/README.md +++ /dev/null @@ -1,154 +0,0 @@ -# Fase 2 — Inline Edit + Concorrência - -**Programa:** Work Orders Board -**Objetivo:** Edição spreadsheet-style célula a célula no board SHOC, com dual RowVersion, audit granular e regras de domínio no backend. - -**Depende de:** [Fase 0](../phase-0/README.md), [Fase 1](../phase-1/README.md) - ---- - -## Endpoints - -### `PATCH /api/workorders/{id}/board` - -Atualiza um único campo do board com validação otimista de concorrência. - -**Autenticação:** Bearer JWT (`[Authorize]`) - -**Request body:** - -```json -{ - "field": "scheduledDate", - "value": "2026-06-25", - "workOrderVersion": "", - "dispatchVersion": "", - "primaryDispatchId": 123 -} -``` - -| Campo | Obrigatório | Descrição | -|-------|-------------|-----------| -| `field` | Sim | Nome canônico do campo (case-insensitive) | -| `value` | Sim* | Valor serializado como string (*pode ser vazio para limpar datas) | -| `workOrderVersion` | Sim | `RowVersion` atual da WO (Base64) | -| `dispatchVersion` | Condicional | Obrigatório para campos Dispatch quando dispatch já existe | -| `primaryDispatchId` | Não | Valida que o dispatch pertence à WO | - -**Responses:** - -| HTTP | Body | Quando | -|------|------|--------| -| 200 | `WorkOrderBoardRowDto` | Sucesso — row completo com versões atualizadas | -| 409 | `WorkOrderBoardConflictDto` + `currentState` | Conflito de versão | -| 422 | `WorkOrderBoardValidationErrorDto` | Regra de domínio violada | -| 400 | `Response` | Erro genérico / argumento inválido | - -**Exemplo:** - -```bash -curl -X PATCH -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{"field":"siteCode","value":"BK5","workOrderVersion":"AQAAAAAAAAA="}' \ - "https://localhost:5001/api/workorders/1/board" -``` - ---- - -## Campos editáveis - -| field (API) | Agregado | Coluna board | Audit FieldName | -|-------------|----------|--------------|-----------------| -| `woNumber` | WorkOrder | WO# | InternalWONumber | -| `workOrderType` | WorkOrder | Type | WorkOrderType | -| `siteCode` | WorkOrder | Site | SiteCode | -| `lifecycleStatus` | WorkOrder | Status | LifecycleStatus | -| `assignTo` | WorkOrder | Assignee | AssignTo | -| `dueDate` | WorkOrder | Due | DueDate | -| `scheduledDate` | WorkOrder | Scheduled | ScheduledDate | -| `targetWeek` | WorkOrder | Scheduled (week-only) | TargetWeek | -| `scheduleWeekOnly` | WorkOrder | Scheduled | ScheduleWeekOnly | -| `vendorId` | Dispatch | Vendor | VendorId | -| `apptDate` | Dispatch | Appt | ApptDate | -| `apptTime` | Dispatch/WO | Appt | ApptTime | -| `docStatus` | WorkOrder | Doc | DocStatus | - -**Read-only:** `isPastDue`, `carriedOver`, `rescheduleCount` (badge derivado; count incrementado por regra). - ---- - -## Dual RowVersion - -| Agregado | Quando exigir versão | -|----------|---------------------| -| WorkOrder | Sempre (`workOrderVersion`) | -| Dispatch | Campos vendor/appt quando dispatch primário já existe (`dispatchVersion`) | - -O GET board retorna `rowVersion`, `dispatchRowVersion` e `primaryDispatchId` em cada row. - -**409 currentState:** inclui row recarregado do DB para refresh imediato no FE. - ---- - -## Regras de domínio - -| Regra | Comportamento | -|-------|---------------| -| Auto-schedule | `Incomplete` + `scheduledDate` + `assignTo` → `Scheduled` | -| Reschedule | Mudança de `scheduledDate` com data anterior → `rescheduleCount++` | -| Set-once tracking | Primeira `scheduledDate` → `OriginalDate` / `OriginalWeek` | -| Past Due block | Não permite mudar `lifecycleStatus` quando `isPastDue` | -| Cancel read-only | Status terminal (`Canceled`, `Closed`, `Complete`) bloqueia PATCH | -| WO# 11 dígitos | Normalização numérica + unicidade | -| Vendor ADR | Mutação em `Dispatch`; cria dispatch primário se ausente | - ---- - -## Audit - -- 1 evento por campo alterado (`FieldChanged`, `StatusChanged`, ou `AssignmentChanged`) -- Side effects (auto-schedule, rescheduleCount) geram eventos adicionais -- Field lock (`WorkOrderFieldLocks`) criado automaticamente em edições manuais -- Eventos Dispatch incluem `dispatchId` - ---- - -## Código entregue - -| Camada | Arquivo | -|--------|---------| -| DTOs | `SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs` | -| Helpers | `WorkOrderBoardMutationRules`, `WorkOrderNumberNormalizer`, `WorkOrderBoardFieldNames`, `WorkOrderBoardApptTimeParser` | -| Update service | `SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs` | -| Audit | `IWorkOrderAuditService.StageFieldChanged`, `LogFieldChangedAsync` | -| API | `WorkOrderController` — `PATCH {id}/board` | -| Testes | `WorkOrderBoardMutationRulesTests`, `WorkOrderNumberNormalizerTests`, `WorkOrderBoardUpdateServiceTests`, `WorkOrderBoardConcurrencyTests` | - ---- - -## Gates de aceite - -### Desenvolvimento - -- [x] `PATCH /api/workorders/{id}/board` para todos os campos da matriz -- [x] Dual RowVersion validado; 409 com `currentState` -- [x] Auto-schedule Incomplete→Scheduled -- [x] RescheduleCount++ em reagendamento -- [x] Bloqueio status quando PastDue; reagendar limpa flag on-read -- [x] WO cancelado/closed rejeita edição (422) -- [x] WO# normalizado 11 dígitos + unicidade -- [x] Vendor/appt muta Dispatch; cria primário se ausente -- [x] 1 audit event por campo; field lock criado -- [x] 51 testes unitários total (25 Fase 2 + 26 Fases 0–1) - -### Staging (pendente) - -- [ ] Smoke Sync durante edição SHOC -- [ ] UAT dispatcher: editar células reais no board -- [ ] Latência PATCH aceitável (<200ms p95 Tier S) - ---- - -## Próximo passo - -**Fase 3** — Criação wizard/inline, soft cancel dedicado, ManualEditFlag na criação. diff --git a/docs/work-orders/phase-3/README.md b/docs/work-orders/phase-3/README.md deleted file mode 100644 index 1e09aed..0000000 --- a/docs/work-orders/phase-3/README.md +++ /dev/null @@ -1,174 +0,0 @@ -# Fase 3 — Criação e Cancelamento - -**Programa:** Work Orders Board -**Objetivo:** Criação wizard/inline via contrato do board SHOC, cancelamento soft dedicado, ManualEditFlag na criação, e delete físico restrito a Admin. - -**Depende de:** [Fase 0](../phase-0/README.md), [Fase 1](../phase-1/README.md), [Fase 2](../phase-2/README.md) - ---- - -## Endpoints - -### `POST /api/workorders/board` - -Cria uma WO com contrato alinhado ao board. Suporta wizard (payload completo) e inline row (subset mínimo). - -**Autenticação:** Bearer JWT (`[Authorize]`) - -**Request body:** - -```json -{ - "woNumber": "12345", - "workOrderType": "PM", - "siteCode": "BK5", - "assignTo": "dispatcher-guid", - "dueDate": "2026-07-01", - "scheduledDate": "2026-06-25", - "targetWeek": "2026-06-22", - "scheduleWeekOnly": false, - "vendorId": 5, - "apptDate": "2026-06-26", - "apptTime": "09:00 – 11:00", - "docStatus": "No", - "description": "Leak in break room", - "trade": "HVAC PM", - "locationId": 12, - "pocContactId": 3, - "pocNotes": "Call before arrival" -} -``` - -| Campo | Obrigatório | Descrição | -|-------|-------------|-----------| -| `workOrderType` | Sim | Enum `WorkOrderType` | -| `siteCode` | Sim | Código do site | -| `woNumber` | Não | Se omitido, auto-gera sequencial normalizado 11 dígitos | -| `scheduleWeekOnly` | Não | Se `true`, `targetWeek` é obrigatório | -| `vendorId` | Condicional | Obrigatório quando `apptDate` ou `apptTime` informados | - -**Responses:** - -| HTTP | Body | Quando | -|------|------|--------| -| 200 | `WorkOrderBoardRowDto` | Sucesso — row pronto para inserir no board | -| 400 | `Response` | Validação FluentValidation | -| 409 | `WorkOrderBoardValidationErrorDto` | WO# duplicado | -| 422 | `WorkOrderBoardValidationErrorDto` | Regra de domínio | - -**Exemplo (inline mínimo):** - -```bash -curl -X POST -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{"workOrderType":"PM","siteCode":"BK5"}' \ - "https://localhost:5001/api/workorders/board" -``` - ---- - -### `POST /api/workorders/{id}/cancel` - -Soft cancel — transição para `LifecycleStatus.Canceled` com WO read-only. - -**Autenticação:** Bearer JWT - -**Responses:** - -| HTTP | Body | Quando | -|------|------|--------| -| 200 | `WorkOrderBoardRowDto` | Cancelado (ou já estava cancelado — idempotente) | -| 422 | `WorkOrderBoardValidationErrorDto` | WO `Complete` ou `Closed` | - -**Exemplo:** - -```bash -curl -X POST -H "Authorization: Bearer " \ - "https://localhost:5001/api/workorders/42/cancel" -``` - ---- - -## Regras de domínio na criação - -| Regra | Comportamento | -|-------|---------------| -| Status inicial | `Incomplete` | -| Auto-schedule | `scheduledDate` + `assignTo` → `Scheduled` | -| Set-once tracking | Primeira `scheduledDate` → `OriginalDate` / `OriginalWeek` | -| Week-only | `scheduleWeekOnly=true` + `targetWeek` → aparece na semana no GET board | -| WO# | Manual normalizado 11 dígitos; auto-gerado se omitido | -| Vendor | Cria dispatch primário quando `vendorId` informado | -| ManualEditFlag | Field locks criados para cada campo SHOC preenchido | - ---- - -## Soft cancel vs hard delete - -| Operação | Endpoint | Quem | Efeito | -|----------|----------|------|--------| -| Soft cancel | `POST /{id}/cancel` | Dispatcher | Status `Canceled`, WO permanece, PATCH bloqueado (422) | -| Hard delete | `DELETE DeleteWorkorder` | **Admin only** | Remove registro, anexos e contatos | - -O legado `POST AddWorkorder` (form/Blazor) permanece inalterado. - ---- - -## ManualEditFlag na criação - -Campos preenchidos na criação SHOC recebem lock em `WorkOrderFieldLocks` via audit `FieldChanged`. Sync subsequente em campo lockado gera `SyncRejected` (ver [manual-edit-flag-design.md](../phase-0/manual-edit-flag-design.md)). - -WO criada via Sync/Lambda continua sem locks até edição SHOC. - ---- - -## Audit - -| Evento | Action | -|--------|--------| -| Criação | 1× `Create` + `FieldChanged` por campo preenchido | -| Auto-schedule na criação | `StatusChanged` adicional | -| Cancel | `StatusChanged` → `Canceled` | -| Hard delete Admin | `Delete` | - ---- - -## Código entregue - -| Camada | Arquivo | -|--------|---------| -| DTOs | `SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs` | -| Validação | `SeaHaven.Services/Validation/WorkOrderBoardCreateValidation.cs` | -| Scheduling compartilhado | `SeaHaven.Services/Helpers/WorkOrderBoardFieldMutations.cs` | -| Create service | `SeaHaven.Services/Implementation/WorkOrderBoardCreateService.cs` | -| Cancel service | `SeaHaven.Services/Implementation/WorkOrderBoardCancelService.cs` | -| Audit | `IWorkOrderAuditService.StageCreated`, `LogCreatedAsync` | -| API | `WorkOrderController` — `POST board`, `POST {id}/cancel`, `DELETE` Admin | -| Testes | `WorkOrderBoardCreateServiceTests`, `WorkOrderBoardCancelServiceTests`, `WorkOrderBoardCreateSyncLockTests` | - ---- - -## Gates de aceite - -### Desenvolvimento - -- [x] `POST /api/workorders/board` — Incomplete / auto-schedule / week-only -- [x] WO# auto ou manual 11 dígitos + unicidade (409) -- [x] Vendor/dispatch primário na criação -- [x] Field locks + SyncRejected pós-create SHOC -- [x] `POST /{id}/cancel` idempotente; PATCH bloqueado após cancel -- [x] `DELETE DeleteWorkorder` restrito a Admin -- [x] Legado `AddWorkorder` inalterado -- [x] 68 testes unitários total (17 Fase 3 + 51 Fases 0–2) - -### Staging (pendente) - -- [ ] UAT dispatcher: wizard + inline row -- [ ] Smoke Sync durante criação SHOC -- [ ] Confirmar navegação FE para semana do WO criado - ---- - -## Próximo passo - -**Fase 4** — Busca contextual na semana + advanced search cross-week conforme tier volume. diff --git a/docs/work-orders/phase-4/README.md b/docs/work-orders/phase-4/README.md deleted file mode 100644 index d9b602f..0000000 --- a/docs/work-orders/phase-4/README.md +++ /dev/null @@ -1,162 +0,0 @@ -# Fase 4 — Search contextual + Advanced Search - -**Programa:** Work Orders Board -**Objetivo:** Hardening da busca contextual no board semanal, endpoint de advanced search cross-week (Tier S), índices de suporte e harness de load test. - -**Depende de:** [Fase 0](../phase-0/README.md), [Fase 1](../phase-1/README.md), [Fase 2](../phase-2/README.md), [Fase 3](../phase-3/README.md) - -**Tier vigente (dev):** **S (provisório)** — ver [search-tier-decision.md](./search-tier-decision.md) - ---- - -## Endpoints - -### `GET /api/workorders/board?search=` (hardening) - -Busca contextual na janela semanal. Campos cobertos: - -| Campo | Origem | -|-------|--------| -| Site | `SiteCode` | -| WO# | `InternalWONumber`, `WorkerOrderNumber` | -| Location | `Locations.Name` | -| Dispatcher | `AssignToUser` nome | -| PM | `Trade`, `Problem` | -| Vendor | `PrimaryDispatch.Vendor.CompanyName` | -| Tech | `PrimaryDispatch.Vendor.ContactName` | -| Status legado | `Status` | -| Lifecycle | label enum (`Scheduled`, `In Progress`, etc.) | - -**Regras:** - -- `search` com trim; ignorado se vazio ou < 2 caracteres -- `counts.total` — agendadas na semana **sem** search -- `counts.returned` — agendadas **com** search - -### `GET /api/workorders/board/search` - -Advanced search cross-week paginado. - -**Autenticação:** Bearer JWT - -**Query params:** - -| Param | Tipo | Descrição | -|-------|------|-----------| -| `search` | string | Texto livre (mesmos campos do contextual) | -| `datePreset` | enum | `thisWeek`, `lastWeek`, `thisMonth`, `last3Months`, `nextWeek`, `nextMonth`, `custom` | -| `dateFrom` / `dateTo` | date | Obrigatórios se `datePreset=custom` | -| `sites` | string[] | `SiteCode` | -| `types` | WorkOrderType[] | Tipo WO | -| `dispatchers` | string[] | GUIDs + `__unassigned__` | -| `statuses` | LifecycleStatus[] | Status operacional | -| `pmTypes` | string[] | Match em `Trade`/`Problem` (contains, case-insensitive) | -| `vendorIds` | int[] | Via `PrimaryDispatch.VendorId` | -| `docStatuses` | DocStatus[] | Enum `DocStatus` | -| `myWorkOrders` | bool | Filtro usuário logado | -| `page` | int | Default 1 | -| `pageSize` | int | Default 50; max 100 (Tier S/M) | -| `sortBy` | string | `scheduledDate` (default), `woNumber`, `dueDate` | -| `sortDir` | string | `asc` / `desc` | - -**Response:** `PagedResult` - -```json -{ - "items": [ /* WorkOrderBoardRowDto */ ], - "totalCount": 120, - "page": 1, - "pageSize": 50, - "totalPages": 3, - "hasNext": true, - "hasPrevious": false -} -``` - -**Exemplo:** - -```bash -curl -H "Authorization: Bearer " \ - "https://localhost:5001/api/workorders/board/search?datePreset=thisMonth&sites=BK5&search=HVAC&page=1" -``` - ---- - -## Matriz preset → intervalo de datas - -Base: **segunda-feira ISO** (`WorkOrderSearchDateRangeResolver`). - -| Preset | Intervalo | -|--------|-----------| -| `thisWeek` | Seg–Dom da semana ISO corrente | -| `lastWeek` | Seg–Dom da semana ISO anterior | -| `nextWeek` | Seg–Dom da próxima semana ISO | -| `thisMonth` | 1º–último dia do mês corrente | -| `nextMonth` | 1º–último dia do mês seguinte | -| `last3Months` | Hoje − 3 meses → hoje | -| `custom` | `dateFrom` / `dateTo` (400 se ausentes) | - -**Filtro de data:** `ScheduledDate` no intervalo **OU** `ScheduleWeekOnly && TargetWeek` intersectando o intervalo. Exclui templates e `IsDeleted`. - ---- - -## Código entregue - -| Camada | Arquivo | -|--------|---------| -| Search filter | `SeaHaven.DataServices/Helpers/WorkOrderBoardSearchFilter.cs` | -| Query filters | `SeaHaven.DataServices/Helpers/WorkOrderBoardQueryFilters.cs` | -| Projeção | `SeaHaven.DataServices/Helpers/WorkOrderBoardProjection.cs` | -| Date presets | `SeaHaven.Services/Helpers/WorkOrderSearchDateRangeResolver.cs` | -| Advanced data | `SeaHaven.DataServices/Implementation/WorkOrderAdvancedSearchDataService.cs` | -| Advanced service | `SeaHaven.Services/Implementation/WorkOrderAdvancedSearchService.cs` | -| DTOs | `SeaHaven.Services/DTOs/WorkOrderBoardDTOs.cs` | -| API | `WorkOrderController` — `GET board/search` | -| Migration | `20260624200000_Phase4_SearchIndexes.cs` | -| Testes | `SeaHavenIndustries.Tests/WorkOrderBoardSearchTests.cs` | -| Load test | `scripts/load-test/work-order-search.k6.js` | -| Seed | `scripts/seed-work-orders-search.ps1` | - ---- - -## Gates de aceite - -### Dev (implementação) - -- [x] Helper de search compartilhado + testes por campo -- [x] `GET /board/search` paginado com filtros FE -- [x] Date presets alinhados ao SHOC (ISO Monday) -- [x] Migration índices Phase 4 -- [x] Harness k6 + seed sintético (Tier S local) -- [x] Endpoints legados inalterados -- [x] ~12 testes unitários novos (total ~80) - -### Staging/prod (GO — pendente) - -- [ ] Volume Discovery preenchido — [volume-discovery-report.md](../phase-0/volume-discovery-report.md) -- [ ] Tier assinado — [search-tier-decision.md](./search-tier-decision.md) -- [ ] Load test em staging com volume real -- [ ] Latência dentro do SLO do tier confirmado -- [ ] Smoke Portal/Blazor inalterados - ---- - -## SLOs Tier S (load test local) - -| Cenário | Endpoint | p95 | -|---------|----------|-----| -| Board sem search | `GET /board?weekStart=...` | < 800ms | -| Board com search | `GET /board?search=BK5` | < 1000ms | -| Advanced preset | `GET /board/search?datePreset=thisMonth` | < 1200ms | -| Advanced multi-filter | sites + types + search | < 1500ms | - -Ver [scripts/load-test/README.md](../../scripts/load-test/README.md). - ---- - -## Fora de escopo - -- Full-text search (Tier L) -- Search externo dedicado (Tier XL) -- Lookups PM catalog (`PM_TYPES`) — backlog #17 -- Alterações em endpoints legados diff --git a/docs/work-orders/phase-4/search-tier-decision.md b/docs/work-orders/phase-4/search-tier-decision.md deleted file mode 100644 index da7dcfe..0000000 --- a/docs/work-orders/phase-4/search-tier-decision.md +++ /dev/null @@ -1,51 +0,0 @@ -# Search Tier Decision — Fase 4 - -**Data:** 2026-06-24 -**Status:** Provisório (dev) — aguardando Volume Discovery staging/prod - ---- - -## Tier confirmado - -| Ambiente | Tier | Total WOs | Decisão | -|----------|------|-----------|---------| -| Dev local | **S (provisório)** | < 25k sintético | Implementar SQL filtros + paginação opcional | -| Staging | _pendente_ | _pendente acesso_ | Executar [volume-discovery-report.md](../phase-0/volume-discovery-report.md) | -| Produção | _pendente_ | _pendente acesso_ | Executar queries read-only | - ---- - -## Estratégia por tier - -| Tier | Volume | Estratégia advanced search | -|------|--------|----------------------------| -| **S** | < 25k | SQL filtros compostos + paginação default 50, max 100 | -| **M** | 25k–250k | Paginação **obrigatória** + índices covering | -| **L** | 250k–1M | Spike full-text SQL Server — **não implementar sem spike** | -| **XL** | > 1M | Documentar ARB; search externo — **não implementar** | - ---- - -## Implementação atual (Tier S) - -- `WorkOrderBoardSearchFilter` — filtro textual compartilhado (board + advanced) -- `WorkOrderAdvancedSearchDataService` — query composta com `CountAsync` + `Skip/Take` -- Índices Phase 4: `SiteCode`, `InternalWONumber`, `LifecycleStatus+ScheduledDate` -- Min 2 caracteres em `search` para evitar full-table scan - ---- - -## Próximos passos (gate staging) - -1. Executar `scripts/volume-discovery.ps1` contra staging read-only -2. Preencher tabela §3 do volume report -3. Atualizar este documento com tier real -4. Repetir load test k6 em staging; ajustar `pageSize` max se tier = M - ---- - -## Referências - -- [volume-discovery-report.md](../phase-0/volume-discovery-report.md) -- [phase-4/README.md](./README.md) -- Roadmap §10, §12 Fase 4 diff --git a/docs/work-orders/phase-5/README.md b/docs/work-orders/phase-5/README.md deleted file mode 100644 index a1a3bfb..0000000 --- a/docs/work-orders/phase-5/README.md +++ /dev/null @@ -1,119 +0,0 @@ -# Fase 5 — Scheduled Domain Events - -**Programa:** Work Orders Board -**Objetivo:** Jobs agendados in-process (`IHostedService`) para `WeekRolled` → `CarriedOver++` com idempotência WO+semana, audit System, cache opcional de `PastDue`, e garantia de que leituras do board continuam on-read como fonte da verdade. - -**Depende de:** [Fase 0](../phase-0/README.md), [Fase 1](../phase-1/README.md), [Fase 2](../phase-2/README.md), [Fase 3](../phase-3/README.md), [Fase 4](../phase-4/README.md) - ---- - -## Regra de elegibilidade WeekRolled - -```text -sourceWeek = semana operacional encerrada (segunda a sexta UTC) -Elegível se: - - ScheduledDate.Date ∈ [sourceWeekStart, sourceWeekEnd] - - LifecycleStatus ∉ {Complete, Canceled, Closed} - - istemplate != true (ou null) - - Ainda não processado em WorkOrderWeekRolledLedger (WorkOrderId + SourceWeekStart) -``` - -**Gate PO (pendente):** confirmar se WOs só com `TargetWeek` (sem `ScheduledDate`) entram no carry-over. A implementação atual considera **apenas** WOs com `ScheduledDate` na janela. - ---- - -## Idempotência - -Chave composta `(WorkOrderId, SourceWeekStart)` na tabela `WorkOrderWeekRolledLedger`. Re-execução do job ou endpoint admin na mesma semana não duplica `CarriedOver`. - ---- - -## Contrato de audit - -Um evento `WeekRolled` por WO processado: - -| Campo | Valor | -|-------|-------| -| `EventType` | `System` | -| `ActorType` | `System` | -| `Action` | `WeekRolled` | -| `FieldName` | `CarriedOver` | -| `OldValue` / `NewValue` | numéricos (string) | -| `CorrelationId` | `week:{yyyy-MM-dd}` (segunda da semana fonte) | - -Não aciona `ManualEditFlag` nem field locks. - ---- - -## Regra de ouro — isPastDue - -Jobs **nunca** são fonte da verdade para `isPastDue`. O board continua calculando via `WorkOrderDerivedFields.IsPastDue` on-read. O cache `OperationalFlags.PastDue` é otimização opcional (`WorkOrderJobs:PastDueCache:Enabled`, default **false**). - ---- - -## Configuração - -```json -"WorkOrderJobs": { - "WeekRolled": { "Enabled": true, "RunAtUtc": "00:05", "DayOfWeek": "Monday" }, - "PastDueCache": { "Enabled": false, "RunAtUtc": "00:10" } -} -``` - -Variáveis de ambiente (`.env.example`): - -```text -WorkOrderJobs__WeekRolled__Enabled=true -WorkOrderJobs__PastDueCache__Enabled=false -``` - ---- - -## Endpoints admin (ops-only) - -| Endpoint | Auth | Descrição | -|----------|------|-----------| -| `POST /api/workorders/jobs/week-rolled?sourceWeekStart=2026-06-16` | Admin | Reprocessa semana (segunda-feira). Idempotente. | -| `POST /api/workorders/jobs/past-due-cache` | Admin | Atualiza cache `OperationalFlags.PastDue` | - ---- - -## Código entregue - -| Camada | Arquivo | -|--------|---------| -| Ledger | `Data.SeaHavenIndustries/Models/WorkOrderWeekRolledLedger.cs` | -| Migration | `20260624210000_Phase5_DomainEvents.cs` | -| Data | `SeaHaven.DataServices/Implementation/WorkOrderDomainJobDataService.cs` | -| WeekRolled | `SeaHaven.Services/Implementation/WorkOrderWeekRolledService.cs` | -| PastDue cache | `SeaHaven.Services/Implementation/PastDueCacheService.cs` | -| Hosted | `Api.SeaHavenIndustries/HostedServices/*.cs` | -| API | `WorkOrderJobsController` | -| Testes | `SeaHavenIndustries.Tests/WorkOrderWeekRolledTests.cs` | - ---- - -## Monitoramento - -Logs estruturados com `CorrelationId` da semana. Ao concluir: `processed`, `skipped`, `failed`, `durationMs`. - -**Alerta ops:** se `failed > 0` ou job não executou em 8 dias → investigar + `POST /jobs/week-rolled` manual. - ---- - -## Critérios de aceite - -- [x] Job semanal incrementa `carriedOver` para WOs elegíveis da semana anterior -- [x] Re-execução na mesma semana não duplica (ledger WO+semana) -- [x] Audit `WeekRolled` System com old/new `CarriedOver` e `CorrelationId` -- [x] `GET /api/workorders/board` continua com `isPastDue` derivado on-read -- [x] Endpoint admin permite reprocessar semana específica -- [x] Testes cobrem idempotência, terminal skip, audit e on-read correctness - ---- - -## Fora de escopo - -- Message queue / worker externo -- Alteração do contrato REST do board para usar `OperationalFlags` -- WOs week-only (`TargetWeek` sem `ScheduledDate`) — gate PO diff --git a/docs/work-orders/phase-6/README.md b/docs/work-orders/phase-6/README.md deleted file mode 100644 index 7ec8117..0000000 --- a/docs/work-orders/phase-6/README.md +++ /dev/null @@ -1,159 +0,0 @@ -# Fase 6 — Completion Doc e Slide-over - -**Programa:** Work Orders Board -**Objetivo:** Contrato SHOC para slide-over (`WOSlideOver`): detalhe unificado, `docStatus` WO-level com templates por serviço, e tabs Comments/Audit/Media alinhadas ao frontend. - -**Depende de:** [Fase 0](../phase-0/README.md) … [Fase 5](../phase-5/README.md) - ---- - -## Endpoints - -### `GET /api/workorders/{id}/detail` - -Payload único para abrir o slide-over. - -**Autenticação:** Bearer JWT (`[Authorize]`) - -**Response:** - -```json -{ - "info": { /* WorkOrderDetailInfoDto — board row + description/trade/original* */ }, - "completion": { - "docStatus": "No", - "template": { "id": 1, "name": "HVAC PM Completion", "serviceKey": "HVAC PM", "templateUrl": "..." }, - "signOffName": null, - "signOffAttachment": null, - "signOffSignature": null, - "dispatchSignoffs": [] - }, - "comments": [{ "id": 1, "authorId": "guid", "text": "...", "time": "2026-06-01T12:00:00.0000000Z" }], - "audit": [{ "type": "manual", "dispatcherId": "guid", "action": "FieldChanged", "fieldName": "DocStatus", "oldValue": "No", "newValue": "Yes", "time": "..." }], - "media": [{ "id": 5, "category": "Extra", "url": "...", "isLegacy": false }] -} -``` - -```bash -curl -H "Authorization: Bearer " \ - "https://localhost:5001/api/workorders/42/detail" -``` - ---- - -### `GET /api/workorders/{id}/audit?limit=50` - -Audit tab lazy refresh. Schema FE: `{ type, dispatcherId?, action, fieldName?, oldValue, newValue, time, dispatchId? }`. - ---- - -### `GET /api/workorders/{id}/comments` - -### `POST /api/workorders/{id}/comments` - -```json -{ "text": "Called vendor" } -``` - -Response: `{ id, authorId, text, time, documents? }`. - ---- - -### Completion templates - -| Método | Rota | Auth | Descrição | -|--------|------|------|-----------| -| GET | `/api/workorders/completion-templates` | JWT | Query `serviceKey`, `workOrderType` | -| GET | `/api/workorders/completion-templates/{id}` | JWT | Detalhe | -| POST | `/api/workorders/completion-templates` | Admin | Criar template | -| PUT | `/api/workorders/completion-templates/{id}` | Admin | Atualizar | -| DELETE | `/api/workorders/completion-templates/{id}` | Admin | Soft delete | - -Lookup na WO: `Trade` → fallback `WorkOrderType`. - ---- - -### `POST /api/workorders/{id}/completion-doc` - -Upload PDF preenchido (multipart). - -| Campo form | Obrigatório | -|------------|-------------| -| `file` | Sim | -| `signOffName` | Não | -| `signOffSignature` | Não | -| `workOrderVersion` | Recomendado (Base64 RowVersion) | - -**Regras:** -- WO read-only (`Canceled`/`Complete`/`Closed`) → 422 -- Sucesso → `SignOffAttachment` + `DocStatus=Yes` + audit `FieldChanged` -- Dispatch signoffs → somente leitura no slide-over - -```bash -curl -X POST -H "Authorization: Bearer " \ - -F "file=@completion.pdf" \ - -F "signOffName=Jane Doe" \ - "https://localhost:5001/api/workorders/42/completion-doc" -``` - -**Inline toggle:** `PATCH /api/workorders/{id}/board` com `field=docStatus` (Fase 2). - ---- - -### Media - -| Método | Rota | Descrição | -|--------|------|-----------| -| GET | `/api/workorders/{id}/media` | Lista unificada com `category` | -| POST | `/api/workorders/{id}/media` | multipart: `category` (Before/After/Extra/Completion) + `file` | -| DELETE | `/api/workorders/{id}/media/{mediaId}` | Soft delete (somente attachments com `id > 0`) | - -Legacy columns (`BeforPhotoAttachment`, `AfterPhotoAttachment`, `SignOffAttachment`) aparecem na projeção com `isLegacy: true`. - ---- - -## Schema - -Migration `Phase6_CompletionSlideOver`: - -- Tabela `CompletionDocTemplates` -- Coluna `Category` em `workOrderAttachments` -- Backfill SQL: `SignOffAttachment` preenchido → `DocStatus=Yes` - -Script ops: [`scripts/backfill-docstatus.ps1`](../../scripts/backfill-docstatus.ps1) - ---- - -## Código entregue - -| Camada | Arquivo | -|--------|---------| -| Model | `CompletionDocTemplate.cs`, `WorkOrderMediaCategory` enum | -| Migration | `20260625120000_Phase6_CompletionSlideOver.cs` | -| Data | `WorkOrderDetailDataService`, `CompletionDocTemplateDataService` | -| Services | `WorkOrderDetailService`, `WorkOrderCommentService`, `WorkOrderCompletionService`, `WorkOrderMediaService` | -| DTOs | `WorkOrderDetailDTOs.cs` | -| Helpers | `WorkOrderAuditProjection`, `WorkOrderCommentProjection`, `WorkOrderMediaProjection` | -| API | `WorkOrderController` — rotas `/detail`, `/comments`, `/audit`, `/completion-doc`, `/media`, `/completion-templates` | -| Testes | `WorkOrderPhase6Tests.cs` | - ---- - -## Critérios de aceite - -- [x] `GET /detail` retorna Info + Completion + Comments + Audit + Media no contrato FE -- [x] Coluna COMP DOC do board via `docStatus` no `WorkOrderBoardRowDto` (Fase 1) -- [x] `CompletionDocTemplate` CRUD admin + lookup por `serviceKey`/`workOrderType` -- [x] `POST completion-doc` persiste attachment, define `DocStatus=Yes`, audit -- [x] Comments/Audit tabs sem adapter no FE -- [x] Media com `category`; legado mapeado -- [x] Endpoints legados (`GetWorkorderById`, `GetCommentsByWorkorderId`) inalterados -- [x] Testes: DocStatus PATCH, detail, completion upload, comments, media - ---- - -## Fora de escopo - -- Geração de PDF server-side -- Alteração Vendor Portal checklist/signoff -- Rollout / sunset Blazor (Fase 7) diff --git a/docs/work-orders/phase-7/README.md b/docs/work-orders/phase-7/README.md deleted file mode 100644 index 61cfe2b..0000000 --- a/docs/work-orders/phase-7/README.md +++ /dev/null @@ -1,245 +0,0 @@ -# Fase 7 — Rollout e Produção - -**Programa:** Work Orders Board -**Objetivo:** Piloto SHOC, rollout gradual, monitoramento de coexistência, sunset Blazor WO, cutover Lambda→API e deprecação legado. - -**Depende de:** [Fase 0](../phase-0/README.md) … [Fase 6](../phase-6/README.md) concluídas e validadas em staging. - ---- - -## Gates - -- **Entrada:** [phase-7-gates-signoff.md](phase-7-gates-signoff.md) -- **Saída:** piloto UAT, 100% SHOC, zero P1 × 2 semanas, Blazor sunset, Lambda cutover, ops health ativo - ---- - -## Runbook de rollout (ordem obrigatória) - -1. Fechar gates de entrada Fase 7 -2. Deploy API com ops health + ingest (ingest desabilitado em prod inicialmente) -3. SHOC: feature flag board em staging → piloto 1–2 dispatchers em prod -4. UAT dispatchers ([roteiros abaixo](#uat-dispatchers)) -5. Rollout gradual SHOC até 100% -6. Habilitar dual-run Lambda (`ingest` + Dynamo) -7. Validar drift → cutover Lambda só API -8. `Sync:Enabled=false` -9. `BlazorWorkOrderSunset:Enabled=true` -10. `LegacyEndpoints:DeprecationEnabled=true` + data Sunset -11. Remover Sync/legado após período de aviso (30 dias) - ---- - -## Endpoints Fase 7 - -### `GET /api/workorders/ops/health` - -Saúde operacional para coexistência multi-consumidor. - -**Auth:** Admin JWT - -```json -{ - "lastWeekRolledRunUtc": "2026-06-23T00:05:12Z", - "lastPastDueCacheRunUtc": null, - "lastWeekRolledError": null, - "lastPastDueCacheError": null, - "syncRejectedLast24h": 3, - "fieldLockCount": 142, - "syncEnabled": true, - "ingestEnabled": true, - "legacyDeprecationEnabled": false, - "legacySunsetDate": null, - "checkedAtUtc": "2026-06-25T12:00:00Z" -} -``` - -Ver [cloudwatch-alerts.md](cloudwatch-alerts.md). - ---- - -### `POST /api/workorders/ingest` - -Ingest direto idempotente (substitui ponte DynamoDB para WOs). - -**Auth:** header `X-Ingest-Key` (config `WorkOrderIngest:ApiKey`) - -**Body (single ou batch):** - -```json -{ - "externalWorkOrderId": "ext-wo-12345", - "description": "HVAC unit not cooling", - "woStatus": "new", - "severity": "2", - "customer": "Acme Corp", - "siteCode": "BK5", - "building": "Building A", - "address": "123 Main St", - "dueDate": "2026-07-01T00:00:00Z", - "dateReported": "2026-06-20T10:00:00Z", - "scheduledStart": null, - "sourceEmailS3Key": null, - "createdAt": "2026-06-20T10:00:00Z" -} -``` - -**Batch:** - -```json -{ - "items": [ { "externalWorkOrderId": "...", "description": "..." } ] -} -``` - -**Response:** - -```json -{ - "created": 1, - "updated": 0, - "results": [{ "externalWorkOrderId": "ext-wo-12345", "workOrderId": 42, "created": true }] -} -``` - -Mapeamento completo: [lambda-ingest-discovery.md](lambda-ingest-discovery.md) - ---- - -## Matriz de endpoints - -| Tipo | Exemplos | Política Fase 7 | -|------|----------|-----------------| -| Board (SHOC) | `GET board`, `PATCH {id}/board`, `POST board` | **Produção** | -| Slide-over | `GET {id}/detail`, completion-doc, media | **Produção** | -| Ingest | `POST ingest` | Dual-run → produção | -| Sync | `POST api/Sync/WorkOrders` | Desligar após cutover (`Sync:Enabled`) | -| Legado | `GetWorkOrderList`, `AddWorkorder`, `ChangeStatus` | Deprecation headers | -| Jobs | `POST jobs/week-rolled` | Ops Admin | - ---- - -## Configuração - -### API (`Api.SeaHavenIndustries`) - -```json -{ - "FrontendBaseUrl": "https://shoc.seahaven.com", - "WorkOrderIngest": { - "Enabled": true, - "ApiKey": "${WORKORDER_INGEST_API_KEY}" - }, - "Sync": { - "Enabled": true - }, - "LegacyEndpoints": { - "DeprecationEnabled": false, - "SunsetDate": "2026-12-31" - } -} -``` - -Env vars: ver [.env.example](../../../.env.example) - -### Blazor (`SeaHavenIndustries`) - -```json -{ - "BlazorWorkOrderSunset": { - "Enabled": false, - "ShocBaseUrl": "https://shoc.seahaven.com" - } -} -``` - -Quando `Enabled=true`: nav Work Orders aponta para SHOC; mutações EF bloqueadas; banner nas páginas WO. - ---- - -## UAT dispatchers - -Roteiro manual — marcar cada item antes de expandir rollout. - -| # | Cenário | Pass | -|---|---------|------| -| 1 | Login SHOC; carregar semana (Mon–Fri + Unscheduled) | [ ] | -| 2 | Filtros: dispatcher, My WOs, tipo, busca contextual | [ ] | -| 3 | Inline edit célula (status, assignee, due) | [ ] | -| 4 | Conflito 409 — outro usuário editou; mensagem clara | [ ] | -| 5 | Criar WO wizard (Incomplete → Scheduled auto) | [ ] | -| 6 | Cancel soft; campos read-only pós-cancel | [ ] | -| 7 | Advanced search cross-week | [ ] | -| 8 | Slide-over: detail, comments, audit, media | [ ] | -| 9 | Upload completion doc; docStatus no board | [ ] | -| 10 | Badge PastDue / CarriedOver após segunda-feira | [ ] | - -**Sign-off UAT** - -| Dispatcher | Data | PO | -|------------|------|-----| -| | | | - ---- - -## Piloto e rollout SHOC - -Controle **somente no frontend** (feature flags). Backend não filtra dispatchers. - -Critérios para avançar: - -- Zero P1 na semana do piloto -- `GET board` latência aceitável (Tier S) -- Vendor Portal regression verde - ---- - -## Sunset Blazor - -Ver [data-ownership-model.md](../phase-0/data-ownership-model.md) §3. - -1. Congelar `WorkorderService` (bugfix only) até data PO -2. Habilitar `BlazorWorkOrderSunset:Enabled` -3. Smoke final [smoke-checklist.md](../phase-0/smoke-checklist.md) Blazor EF - ---- - -## Cutover Lambda - -Ver [lambda-ingest-discovery.md](lambda-ingest-discovery.md). - ---- - -## Testes automatizados - -```powershell -dotnet test SeaHavenIndustries.Tests --filter "FullyQualifiedName~WorkOrderPhase7" -``` - -Suite: `WorkOrderPhase7CoexistenceTests.cs` - ---- - -## Critérios de aceite Fase 7 - -- [x] `GET /api/workorders/ops/health` -- [x] `POST /api/workorders/ingest` com API key -- [x] `Sync:Enabled` feature flag -- [x] Legacy deprecation middleware -- [x] Blazor sunset config + guard mutações -- [x] Documentação gates, UAT, Lambda mapping -- [ ] Piloto prod (ops/PO) -- [ ] 100% dispatchers SHOC (ops/PO) -- [ ] Zero P1 × 2 semanas (ops/PO) -- [ ] Lambda cutover executado (ops) - ---- - -## Comunicação - -| Audiência | Mensagem | Quando | -|-----------|----------|--------| -| Dispatchers piloto | Novo board SHOC; suporte dedicado | Início piloto | -| Todos dispatchers | Rollout gradual; treinamento | Durante rollout | -| Usuários Blazor | WO migrou para SHOC; link direto | Sunset Blazor | -| Engenharia | Sync desligado; usar ingest API | Pós-cutover | diff --git a/docs/work-orders/phase-7/cloudwatch-alerts.md b/docs/work-orders/phase-7/cloudwatch-alerts.md deleted file mode 100644 index d83e929..0000000 --- a/docs/work-orders/phase-7/cloudwatch-alerts.md +++ /dev/null @@ -1,40 +0,0 @@ -# Alertas Operacionais — Fase 7 - -**Endpoint de saúde:** `GET /api/workorders/ops/health` (Admin JWT) - ---- - -## Métricas expostas - -| Campo | Fonte | Uso | -|-------|-------|-----| -| `lastWeekRolledRunUtc` | `WorkOrderJobRunState` + ledger | Job segunda-feira | -| `lastPastDueCacheRunUtc` | `WorkOrderJobRunState` | Cache opcional PastDue | -| `syncRejectedLast24h` | `WorkOrderAuditLogs` Action=SyncRejected | Conflito SHOC vs ingest | -| `fieldLockCount` | `WorkOrderFieldLocks` | Adoção edição manual | -| `syncEnabled` | `Sync:Enabled` | Estado ponte Dynamo | -| `ingestEnabled` | `WorkOrderIngest:Enabled` | Ingest direto ativo | - ---- - -## Alertas recomendados (Elastic Beanstalk / CloudWatch) - -| Alerta | Condição | Severidade | -|--------|----------|------------| -| WeekRolled stale | `lastWeekRolledRunUtc` > 8 dias | P1 | -| WeekRolled job error | `lastWeekRolledError` não nulo | P1 | -| SyncRejected spike | `syncRejectedLast24h` > 50 | P2 | -| API 5xx board | ALB/Beanstalk 5xx rate > 1% em `/api/workorders/board` | P1 | -| Ingest auth failures | 401 em `/api/workorders/ingest` > 10/h | P2 | -| Sync disabled em prod sem cutover | `syncEnabled=false` e ingest não validado | P2 | - ---- - -## Verificação manual (ops) - -```powershell -# Com token Admin -curl -H "Authorization: Bearer " https:///api/workorders/ops/health -``` - -Agendar checagem diária durante piloto e rollout. diff --git a/docs/work-orders/phase-7/lambda-ingest-discovery.md b/docs/work-orders/phase-7/lambda-ingest-discovery.md deleted file mode 100644 index b07d789..0000000 --- a/docs/work-orders/phase-7/lambda-ingest-discovery.md +++ /dev/null @@ -1,93 +0,0 @@ -# Lambda Ingest — Descoberta e Mapeamento de Campos - -**Fase:** 7 -**Status:** Documentação de cutover (código Lambda fora deste repositório) - ---- - -## 1. Localização da Lambda - -| Item | Valor | -|------|-------| -| Nome referenciado | `workorder-ingest` | -| Referência código | [TODO.md](../../../TODO.md) L50–51 | -| Fluxo atual | Lambda → DynamoDB (`WorkOrders`) → `POST api/Sync/WorkOrders` | -| Fluxo alvo | Lambda → `POST api/workorders/ingest` (API key) | - -**Ação pendente ops:** localizar repositório/infra AWS (SAM, Terraform, console Lambda) e preencher owner na [phase-7-gates-signoff.md](phase-7-gates-signoff.md). - ---- - -## 2. Tabelas DynamoDB (SyncController) - -| Tabela | Endpoint sync | Uso | -|--------|---------------|-----| -| `WorkOrders` | `POST api/Sync/WorkOrders` | Upsert WO por `work_order_id` | -| `WorkOrderComments` | `POST api/Sync/Comments` | Comentários cliente | -| `VendorReplies` | `POST api/Sync/VendorReplies` | Respostas vendor | - -Cutover Fase 7 foca em **WorkOrders**; comments/replies permanecem no Sync até migração separada. - ---- - -## 3. Mapeamento DynamoDB → API ingest - -Campos lidos em [SyncController.cs](../../../Api.SeaHavenIndustries/Controllers/SyncController.cs) e espelhados em `WorkOrderIngestPayload`: - -| Campo Dynamo | Campo API ingest | Campo SQL | Merge policy (update) | -|--------------|------------------|-----------|------------------------| -| `work_order_id` | `externalWorkOrderId` | `ExternalWorkOrderId` | Chave idempotente | -| `description` | `description` | `Description`, `WorkerOrderTitle` | Sim se não locked | -| `wo_status` | `woStatus` | `Status` (mapeado) | Sim se não locked | -| `severity` | `severity` | `Priority`, `Severity` | Sim se não locked | -| `customer` | `customer` | `Customer` | Direto na criação | -| `site_code` | `siteCode` | `SiteCode` | Sim se não locked | -| `building` | `building` | `Building` | Sim se não locked | -| `address` | `address` | `Locations` (resolve/create) | LocationId | -| `due_date` | `dueDate` | `DueDate` | Sim se não locked | -| `date_reported` | `dateReported` | `DateReported` | Direto | -| `scheduled_start` | `scheduledStart` | `ScheduledStart` | Direto | -| `source_email_s3_key` | `sourceEmailS3Key` | `SourceEmailS3Key` | Direto | -| `created_at` | `createdAt` | `CreatedDate` | Criação only | - -### Mapeamento `wo_status` → SQL `Status` - -| Dynamo | SQL | -|--------|-----| -| `new`, `assigned`, `unknown` | `Open` | -| `in_progress` | `In Progress` | -| `on_hold` | `On Hold` | -| `completed` | `Done` | -| `cancelled` | `Cancelled` | - -### Mapeamento `severity` → `Priority` - -`Sev {severity}` (ex.: `3` → `Sev 3`) - ---- - -## 4. Auth serviço-a-serviço - -| Header | Config | -|--------|--------| -| `X-Ingest-Key` | `WorkOrderIngest:ApiKey` (env `WorkOrderIngest__ApiKey`) | - -Lambda deve enviar o header em cada `POST /api/workorders/ingest`. Não usar JWT de usuário dispatcher. - ---- - -## 5. Estratégia dual-run - -1. **Semana 1–2:** Lambda grava DynamoDB **e** chama API ingest -2. **Validação:** `GET /api/workorders/ops/health` + script amostra `ExternalWorkOrderId` -3. **Cutover:** Lambda só API; `Sync:Enabled=false` -4. **Retire:** backup DynamoDB → desativar tabelas - ---- - -## 6. Critérios de cutover - -- [ ] `POST /api/workorders/board` estável (criação SHOC) -- [ ] Auth ingest testada em staging -- [ ] Zero drift em amostra de 100 WOs -- [ ] Owner Lambda assinou runbook diff --git a/docs/work-orders/phase-7/phase-7-gates-signoff.md b/docs/work-orders/phase-7/phase-7-gates-signoff.md deleted file mode 100644 index 16021e0..0000000 --- a/docs/work-orders/phase-7/phase-7-gates-signoff.md +++ /dev/null @@ -1,55 +0,0 @@ -# Phase 7 — Gates Sign-Off Checklist - -**Programa:** Work Orders Board -**Fase:** 7 — Rollout e Produção - ---- - -## Gate de entrada (GO Fase 7) - -Bloqueia piloto em produção até todos os itens estarem verdes. - -| # | Gate | Artefato / evidência | Status | -|---|------|----------------------|--------| -| 1 | Fases 0–6 validadas em staging | `docs/work-orders/phase-0` … `phase-6` READMEs | [ ] | -| 2 | Sign-off CTO/PO Fase 0 | [phase-0-gates-signoff.md](../phase-0/phase-0-gates-signoff.md) | [ ] | -| 3 | Dry-run migrations Phase0–Phase6 | [migration-dry-run-report.md](../phase-0/migration-dry-run-report.md) | [ ] | -| 4 | Smoke multi-consumidor | [smoke-checklist.md](../phase-0/smoke-checklist.md) | [ ] | -| 5 | Vendor Portal regression | Checklist dispatch checklist/signoff | [ ] | -| 6 | SHOC E2E staging | Contratos phase-1 … phase-6 integrados | [ ] | -| 7 | Tier volume confirmado | [volume-discovery-report.md](../phase-0/volume-discovery-report.md) | [ ] | - -### Checklist PO ([consumer-inventory.md](../phase-0/consumer-inventory.md)) - -- [ ] App mobile externo confirmado (sim/não/N/A) -- [ ] Data sunset Blazor WO definida: _______________ -- [ ] `FrontendBaseUrl` produção → SHOC configurado -- [ ] Owner Lambda cutover nomeado: _______________ - -**Assinatura GO Fase 7** - -| Papel | Nome | Data | -|-------|------|------| -| CTO | | | -| PO | | | - ---- - -## Gates de saída (DONE Fase 7) - -| Gate | Critério | -|------|----------| -| Piloto | 1–2 dispatchers SHOC em prod; UAT assinado | -| Rollout | 100% dispatchers no board SHOC | -| Estabilidade | Zero P1 por 2 semanas consecutivas | -| Blazor | Módulo WO sunset (`BlazorWorkOrderSunset:Enabled`) | -| Lambda | Ingest direto `POST /api/workorders/ingest`; Dynamo/Sync desligados | -| Monitoramento | `GET /api/workorders/ops/health` + alertas documentados | -| Legado | Headers `Sunset`/`Deprecation` nos endpoints legado | - -**Assinatura DONE Fase 7** - -| Papel | Nome | Data | -|-------|------|------| -| CTO | | | -| PO | | | diff --git a/scripts/backfill-docstatus.ps1 b/scripts/backfill-docstatus.ps1 deleted file mode 100644 index 4bcc0e0..0000000 --- a/scripts/backfill-docstatus.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -# Backfill DocStatus from legacy SignOffAttachment (idempotent) -# Run against target SQL Server after Phase6_CompletionSlideOver migration. - -param( - [string]$ConnectionString = $env:ConnectionStrings__DefaultConnection -) - -if ([string]::IsNullOrWhiteSpace($ConnectionString)) { - Write-Error "Set ConnectionStrings__DefaultConnection or pass -ConnectionString" - exit 1 -} - -$sql = @" -UPDATE workOrders -SET DocStatus = 1 -WHERE DocStatus IS NULL - AND SignOffAttachment IS NOT NULL - AND LTRIM(RTRIM(SignOffAttachment)) <> ''; -"@ - -Write-Host "Backfilling DocStatus=Yes where SignOffAttachment exists..." -Invoke-Sqlcmd -ConnectionString $ConnectionString -Query $sql -Write-Host "Done." diff --git a/scripts/seed-work-orders-search.ps1 b/scripts/seed-work-orders-search.ps1 deleted file mode 100644 index a08b18a..0000000 --- a/scripts/seed-work-orders-search.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -# Seed sintético de Work Orders para load test Tier S -# -# Uso: -# $env:SQL_CONNECTION_STRING = "Server=localhost;Database=SeaHaven;..." -# .\scripts\seed-work-orders-search.ps1 -Count 15000 - -param( - [int]$Count = 5000, - [string]$ConnectionString = $env:SQL_CONNECTION_STRING -) - -if ([string]::IsNullOrWhiteSpace($ConnectionString)) { - Write-Error "Defina SQL_CONNECTION_STRING ou passe -ConnectionString." - exit 1 -} - -$sites = @("BK5", "BK6", "BK7", "BK8", "BK9", "LA1", "LA2", "NY1") -$trades = @("HVAC PM", "Plumbing PM", "Electrical PM", "Roof Inspection", "Fire Safety") -$baseDate = Get-Date "2026-01-06" - -Write-Host "Inserindo $Count WOs sintéticas..." -ForegroundColor Cyan - -$batchSize = 500 -$inserted = 0 - -while ($inserted -lt $Count) { - $batch = [Math]::Min($batchSize, $Count - $inserted) - $values = @() - - for ($i = 0; $i -lt $batch; $i++) { - $id = $inserted + $i + 1 - $site = $sites[$id % $sites.Length] - $trade = $trades[$id % $trades.Length] - $weekOffset = $id % 52 - $scheduled = $baseDate.AddDays($weekOffset * 7 + ($id % 5)) - $woNumber = ("1{0:D10}" -f $id) - - $values += "('$woNumber', '$site', '$trade', '$($scheduled.ToString('yyyy-MM-dd'))', 2, 0)" - } - - $sql = @" -INSERT INTO workOrders (InternalWONumber, SiteCode, Trade, ScheduledDate, LifecycleStatus, istemplate, RescheduleCount, CarriedOver, CreatedDate) -VALUES $($values -join ','); -"@ - - sqlcmd -Q $sql -b - if ($LASTEXITCODE -ne 0) { - Write-Error "Falha no batch $inserted" - exit 1 - } - - $inserted += $batch - Write-Host " $inserted / $Count" -} - -Write-Host "Seed concluído." -ForegroundColor Green diff --git a/scripts/spike-status-mapping.sql b/scripts/spike-status-mapping.sql deleted file mode 100644 index 14e371b..0000000 --- a/scripts/spike-status-mapping.sql +++ /dev/null @@ -1,90 +0,0 @@ --- Spike Status Mapping (G2): seed legado + enum compacto + queries de análise --- Executar APÓS dotnet ef database update --- Uso: sqlcmd -S localhost,1433 -U sa -P "SeaHaven_Dev_2026!" -d SeahavenIndustries -C -i scripts/spike-status-mapping.sql - -SET NOCOUNT ON; - --- --------------------------------------------------------------------------- --- 1. Seed status spike (prefixo SPIKE-STATUS-) --- Cobre formatos legado (com espaço) e canônico API (enum ToString) --- --------------------------------------------------------------------------- -IF NOT EXISTS (SELECT 1 FROM workOrders WHERE InternalWONumber LIKE 'SPIKE-STATUS-%') -BEGIN - DECLARE @weekStart date = CAST(GETDATE() AS date); - - INSERT INTO workOrders ( - InternalWONumber, WorkerOrderTitle, SiteCode, Status, Priority, - ScheduledDate, DueDate, Trade, Problem, AssignTo, istemplate, CreatedDate - ) - VALUES - -- Legado Blazor / Sync (com espaço) - ('SPIKE-STATUS-001', 'Status spike - Open legado', 'SS1', 'Open', 'Medium', DATEADD(day, 0, @weekStart), DATEADD(day, 5, @weekStart), 'HVAC', 'Repair', NULL, 0, GETDATE()), - ('SPIKE-STATUS-002', 'Status spike - In Progress legado','SS2', 'In Progress', 'Medium', DATEADD(day, 1, @weekStart), DATEADD(day, 6, @weekStart), 'Plumbing', 'Repair', NULL, 0, GETDATE()), - ('SPIKE-STATUS-003', 'Status spike - On Hold legado', 'SS3', 'On Hold', 'Low', DATEADD(day, 2, @weekStart), DATEADD(day, 10, @weekStart), 'Electrical', 'Hold', NULL, 0, GETDATE()), - ('SPIKE-STATUS-004', 'Status spike - Done legado', 'SS4', 'Done', 'Low', DATEADD(day, 3, @weekStart), DATEADD(day, 4, @weekStart), 'General', 'Complete', NULL, 0, GETDATE()), - ('SPIKE-STATUS-005', 'Status spike - Cancelled legado', 'SS5', 'Cancelled', 'Low', DATEADD(day, 4, @weekStart), DATEADD(day, 8, @weekStart), 'HVAC', 'Cancelled', NULL, 0, GETDATE()), - ('SPIKE-STATUS-006', 'Status spike - UnAssigned legado','SS6', 'UnAssigned', 'Medium', DATEADD(day, 5, @weekStart), DATEADD(day, 7, @weekStart), 'Plumbing', 'Unassigned', NULL, 0, GETDATE()), - -- Canônico API (enum ToString) - ('SPIKE-STATUS-007', 'Status spike - Open canônico', 'SS7', 'Open', 'Medium', DATEADD(day, 0, @weekStart), DATEADD(day, 5, @weekStart), 'HVAC', 'Repair', NULL, 0, GETDATE()), - ('SPIKE-STATUS-008', 'Status spike - InProgress canônico','SS8', 'InProgress', 'High', DATEADD(day, 1, @weekStart), DATEADD(day, 6, @weekStart), 'Plumbing', 'Repair', NULL, 0, GETDATE()), - ('SPIKE-STATUS-009', 'Status spike - OnHold canônico', 'SS9', 'OnHold', 'Low', DATEADD(day, 2, @weekStart), DATEADD(day, 10, @weekStart), 'Electrical', 'Hold', NULL, 0, GETDATE()), - ('SPIKE-STATUS-010', 'Status spike - Completed canônico','SS10','Completed', 'Low', DATEADD(day, 3, @weekStart), DATEADD(day, 4, @weekStart), 'General', 'Complete', NULL, 0, GETDATE()), - ('SPIKE-STATUS-011', 'Status spike - Cancelled canônico','SS11','Cancelled', 'Low', DATEADD(day, 4, @weekStart), DATEADD(day, 8, @weekStart), 'HVAC', 'Cancelled', NULL, 0, GETDATE()), - -- Open sem assignee (derivado Unassigned no board) - ('SPIKE-STATUS-012', 'Status spike - Open unassigned', 'SS12','Open', 'Medium', DATEADD(day, 1, @weekStart), DATEADD(day, 3, @weekStart), 'HVAC', 'Repair', NULL, 0, GETDATE()), - -- Past due candidato (scheduled no passado, status não terminal) - ('SPIKE-STATUS-013', 'Status spike - past due candidate','SS13','In Progress', 'High', DATEADD(day, -3, @weekStart), DATEADD(day, -1, @weekStart), 'HVAC', 'Overdue test', NULL, 0, GETDATE()); - - PRINT 'Seed status spike: 13 work orders inseridos (prefixo SPIKE-STATUS-).'; -END -ELSE - PRINT 'Seed status spike: já existente — pulando INSERT.'; -GO - --- --------------------------------------------------------------------------- --- 2. Distribuição Status (todos os WOs não-template) --- --------------------------------------------------------------------------- -PRINT '--- Distribuição Status ---'; -SELECT Status, COUNT(*) AS Cnt -FROM workOrders -WHERE istemplate IS NULL OR istemplate = 0 -GROUP BY Status -ORDER BY Cnt DESC; - --- --------------------------------------------------------------------------- --- 3. Status x assignee (detectar Unassigned implícito) --- --------------------------------------------------------------------------- -PRINT '--- Status x AssignTo (Unassigned implícito) ---'; -SELECT - Status, - CASE WHEN AssignTo IS NULL OR LTRIM(RTRIM(AssignTo)) = '' THEN 1 ELSE 0 END AS IsUnassigned, - COUNT(*) AS Cnt -FROM workOrders -WHERE istemplate IS NULL OR istemplate = 0 -GROUP BY Status, - CASE WHEN AssignTo IS NULL OR LTRIM(RTRIM(AssignTo)) = '' THEN 1 ELSE 0 END -ORDER BY Status, IsUnassigned; - --- --------------------------------------------------------------------------- --- 4. Órfãos — valores não mapeáveis pelo mapper G2 (ajustar lista conforme spike) --- --------------------------------------------------------------------------- -PRINT '--- Status órfãos (fora do vocabulário conhecido) ---'; -SELECT Status, COUNT(*) AS Cnt -FROM workOrders -WHERE (istemplate IS NULL OR istemplate = 0) - AND Status IS NOT NULL - AND LTRIM(RTRIM(Status)) NOT IN ( - 'Open', 'In Progress', 'On Hold', 'Done', 'Cancelled', 'UnAssigned', 'Unassigned', - 'InProgress', 'OnHold', 'Completed', - 'new', 'assigned', 'in_progress', 'on_hold', 'completed', 'cancelled', 'unknown' - ) -GROUP BY Status -ORDER BY Cnt DESC; - --- --------------------------------------------------------------------------- --- 5. Template query PRODUÇÃO (rodar manualmente em prod/staging) --- --------------------------------------------------------------------------- -PRINT '--- Template query PRODUÇÃO ---'; --- SELECT Status, COUNT(*) AS Cnt FROM workOrders WHERE istemplate IS NULL OR istemplate = 0 GROUP BY Status ORDER BY Cnt DESC; - -GO diff --git a/scripts/spike-work-order-type.sql b/scripts/spike-work-order-type.sql deleted file mode 100644 index 0c18465..0000000 --- a/scripts/spike-work-order-type.sql +++ /dev/null @@ -1,96 +0,0 @@ --- Spike WorkOrderType: coluna legada + seed local + queries de análise --- Executar APÓS dotnet ef database update --- Uso: sqlcmd -S localhost,1433 -U sa -P "SeaHaven_Dev_2026!" -d SeahavenIndustries -C -i scripts/spike-work-order-type.sql - -SET NOCOUNT ON; - --- --------------------------------------------------------------------------- --- 1. Colunas legadas (existem em prod/db.txt, ausentes nas migrations EF) --- --------------------------------------------------------------------------- -IF COL_LENGTH('workOrders', 'WorkOrderType') IS NULL - ALTER TABLE workOrders ADD WorkOrderType varchar(50) NULL; - -IF COL_LENGTH('workOrders', 'AvettaTask') IS NULL - ALTER TABLE workOrders ADD AvettaTask varchar(max) NULL; - -IF COL_LENGTH('workOrders', 'AssignDate') IS NULL - ALTER TABLE workOrders ADD AssignDate date NULL; - -GO - --- --------------------------------------------------------------------------- --- 2. Seed spike (somente se ainda não existir prefixo SPIKE-WO-) --- --------------------------------------------------------------------------- -IF NOT EXISTS (SELECT 1 FROM workOrders WHERE InternalWONumber LIKE 'SPIKE-WO-%') -BEGIN - DECLARE @weekStart date = CAST(GETDATE() AS date); - DECLARE @weekEnd date = DATEADD(day, 6, @weekStart); - - INSERT INTO workOrders ( - InternalWONumber, WorkerOrderTitle, SiteCode, Status, Priority, - ScheduledDate, DueDate, Trade, Problem, WorkOrderType, istemplate, CreatedDate - ) - VALUES - -- NULL WorkOrderType (5) - ('SPIKE-WO-001', 'Spike seed - null type A', 'BK5', 'Open', 'Medium', DATEADD(day, 0, @weekStart), DATEADD(day, 3, @weekStart), 'HVAC', 'Cooling', NULL, 0, GETDATE()), - ('SPIKE-WO-002', 'Spike seed - null type B', 'BK6', 'Open', 'Low', DATEADD(day, 1, @weekStart), DATEADD(day, 5, @weekStart), 'Plumbing', 'Leak', NULL, 0, GETDATE()), - ('SPIKE-WO-003', 'Spike seed - null type C', 'BK7', 'InProgress', 'Medium', DATEADD(day, 2, @weekStart), DATEADD(day, 4, @weekStart), 'Electrical', 'Panel', NULL, 0, GETDATE()), - ('SPIKE-WO-004', 'Spike seed - null type D', 'BK8', 'Open', 'High', DATEADD(day, 3, @weekStart), DATEADD(day, 7, @weekStart), 'HVAC', 'Filter', NULL, 0, GETDATE()), - ('SPIKE-WO-005', 'Spike seed - null type E', 'BK9', 'OnHold', 'Low', DATEADD(day, 4, @weekStart), DATEADD(day, 10, @weekStart), 'General', 'Inspection', NULL, 0, GETDATE()), - -- PM (5) - ('SPIKE-WO-006', 'Spike seed - PM A', 'PM1', 'Open', 'Medium', DATEADD(day, 0, @weekStart), DATEADD(day, 14, @weekStart), 'HVAC', 'Preventive Maintenance', 'PM', 0, GETDATE()), - ('SPIKE-WO-007', 'Spike seed - PM B', 'PM2', 'Open', 'Medium', DATEADD(day, 1, @weekStart), DATEADD(day, 14, @weekStart), 'Plumbing', 'PM Schedule', 'PM', 0, GETDATE()), - ('SPIKE-WO-008', 'Spike seed - PM C', 'PM3', 'InProgress', 'Low', DATEADD(day, 2, @weekStart), DATEADD(day, 21, @weekStart), 'Electrical', 'Quarterly PM', 'PM', 0, GETDATE()), - ('SPIKE-WO-009', 'Spike seed - PM D', 'PM4', 'Open', 'Medium', DATEADD(day, 5, @weekStart), DATEADD(day, 30, @weekStart), 'HVAC', 'Annual PM', 'PM', 0, GETDATE()), - ('SPIKE-WO-010', 'Spike seed - PM E', 'PM5', 'Open', 'Low', DATEADD(day, 6, @weekStart), DATEADD(day, 7, @weekStart), 'General', 'PM', 'PM', 0, GETDATE()), - -- Standard (4) - ('SPIKE-WO-011', 'Spike seed - Standard A', 'ST1', 'Open', 'Medium', DATEADD(day, 0, @weekStart), DATEADD(day, 5, @weekStart), 'HVAC', 'Repair', 'Standard', 0, GETDATE()), - ('SPIKE-WO-012', 'Spike seed - Standard B', 'ST2', 'InProgress', 'High', DATEADD(day, 2, @weekStart), DATEADD(day, 6, @weekStart), 'Plumbing', 'Repair', 'Standard', 0, GETDATE()), - ('SPIKE-WO-013', 'Spike seed - Standard C', 'ST3', 'Open', 'Medium', DATEADD(day, 4, @weekStart), DATEADD(day, 8, @weekStart), 'Electrical', 'Repair', 'Standard', 0, GETDATE()), - ('SPIKE-WO-014', 'Spike seed - Standard D', 'ST4', 'Completed', 'Low', DATEADD(day, 1, @weekStart), DATEADD(day, 2, @weekStart), 'General', 'Repair', 'Standard', 0, GETDATE()), - -- Add-On (3) - ('SPIKE-WO-015', 'Spike seed - Add-On A', 'AO1', 'Open', 'Medium', DATEADD(day, 3, @weekStart), DATEADD(day, 9, @weekStart), 'HVAC', 'Add work', 'Add-On', 0, GETDATE()), - ('SPIKE-WO-016', 'Spike seed - Add-On B', 'AO2', 'Open', 'High', DATEADD(day, 4, @weekStart), DATEADD(day, 10, @weekStart), 'Plumbing', 'Add work', 'Add-On', 0, GETDATE()), - ('SPIKE-WO-017', 'Spike seed - Add-On C', 'AO3', 'InProgress', 'Medium', DATEADD(day, 5, @weekStart), DATEADD(day, 11, @weekStart), 'Electrical', 'Add work', 'Add-On', 0, GETDATE()), - -- Emergency + Corrective (3) - ('SPIKE-WO-018', 'Spike seed - Emergency', 'EM1', 'Open', 'Critical', DATEADD(day, 0, @weekStart), DATEADD(day, 1, @weekStart), 'HVAC', 'Emergency', 'Emergency', 0, GETDATE()), - ('SPIKE-WO-019', 'Spike seed - Corrective A', 'CR1', 'Open', 'High', DATEADD(day, 2, @weekStart), DATEADD(day, 4, @weekStart), 'Plumbing', 'Corrective', 'Corrective', 0, GETDATE()), - ('SPIKE-WO-020', 'Spike seed - Corrective B', 'CR2', 'Open', 'Medium', DATEADD(day, 6, @weekStart), DATEADD(day, 8, @weekStart), 'General', 'Corrective', 'Corrective', 0, GETDATE()); - - PRINT 'Seed spike: 20 work orders inseridos (prefixo SPIKE-WO-).'; -END -ELSE - PRINT 'Seed spike: já existente — pulando INSERT.'; -GO - --- --------------------------------------------------------------------------- --- 3. Queries de análise (entregável spike) --- --------------------------------------------------------------------------- -PRINT '--- Distribuição WorkOrderType (seed + existentes) ---'; -SELECT WorkOrderType, COUNT(*) AS Cnt -FROM workOrders -WHERE istemplate IS NULL OR istemplate = 0 -GROUP BY WorkOrderType -ORDER BY Cnt DESC; - -PRINT '--- % populado ---'; -SELECT - COUNT(*) AS TotalRows, - SUM(CASE WHEN WorkOrderType IS NOT NULL AND LTRIM(RTRIM(WorkOrderType)) <> '' THEN 1 ELSE 0 END) AS Populated, - CAST(100.0 * SUM(CASE WHEN WorkOrderType IS NOT NULL AND LTRIM(RTRIM(WorkOrderType)) <> '' THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0) AS decimal(5,2)) AS PopulatedPct -FROM workOrders -WHERE istemplate IS NULL OR istemplate = 0; - -PRINT '--- Cruzamento Problem/Trade (hipótese derivação) ---'; -SELECT WorkOrderType, Problem, Trade, COUNT(*) AS Cnt -FROM workOrders -WHERE (istemplate IS NULL OR istemplate = 0) - AND (InternalWONumber LIKE 'SPIKE-WO-%' OR WorkOrderType IS NOT NULL) -GROUP BY WorkOrderType, Problem, Trade -ORDER BY WorkOrderType, Cnt DESC; - -PRINT '--- Template query PRODUÇÃO (rodar manualmente em prod/staging) ---'; --- SELECT WorkOrderType, COUNT(*) FROM workOrders GROUP BY WorkOrderType; --- SELECT COUNT(*) AS Total, SUM(CASE WHEN WorkOrderType IS NOT NULL THEN 1 ELSE 0 END) AS WithType FROM workOrders; - -GO diff --git a/scripts/volume-discovery.ps1 b/scripts/volume-discovery.ps1 deleted file mode 100644 index 90612a9..0000000 --- a/scripts/volume-discovery.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -# Volume Discovery — Work Orders -# Executa queries do volume-discovery-report.md contra staging/prod read-only. -# -# Uso: -# $env:SQL_CONNECTION_STRING = "Server=...;Database=...;User Id=...;Password=...;TrustServerCertificate=True" -# .\scripts\volume-discovery.ps1 - -param( - [string]$ConnectionString = $env:SQL_CONNECTION_STRING -) - -function Get-SqlConnectionArgs([string]$cs) { - return @("-C", $cs) -} - -if ([string]::IsNullOrWhiteSpace($ConnectionString)) { - Write-Error "Defina SQL_CONNECTION_STRING ou passe -ConnectionString." - exit 1 -} - -$queries = @{ - TotalWorkOrders = @" -SELECT COUNT(*) AS TotalWorkOrders -FROM workOrders -WHERE IsDeleted IS NULL OR IsDeleted = 0; -"@ - P95ProxyMaxPerWeek = @" -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; -"@ - UnscheduledCount = @" -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'); -"@ -} - -$connArgs = Get-SqlConnectionArgs $ConnectionString - -Write-Host "=== Volume Discovery ===" -ForegroundColor Cyan -Write-Host "" - -foreach ($name in $queries.Keys) { - Write-Host "--- $name ---" -ForegroundColor Yellow - sqlcmd @connArgs -Q $queries[$name] -W - Write-Host "" -} - -Write-Host "Preencha docs/work-orders/phase-0/volume-discovery-report.md e docs/work-orders/phase-4/search-tier-decision.md com os resultados."