shoc-backend/docs/adr/work-orders-board-api.md

5.4 KiB

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:


Decisão

1. Endpoint dedicado (sem projection=)

Criar endpoint separado para o Schedule Board. Não alterar GetWorkOrderList.

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:

{
  "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:

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:

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