Record adversarial review results and fixes for service notes, search, paging, and migration hardening.
9.8 KiB
Staff Review Findings — Work Orders (Fase 2)
Adversarial reviews per delivery. Do not treat preliminary inventário notes as final.
Entrega A — Structured Service Notes / POC / Tech-Vendor
How it works
- Create
POST /api/workorders/board: FluentValidation → resolveprimaryService/trade→ serializeextraServicesJSON → persist POC/service/tech/vendor onWorkOrder; if vendor/appt present, createDispatchand store tech/vendor notes there instead of WO. - Patch
PATCH .../boardwithpm|serviceNotes|extraServices. - Read: board projection prefers WO POC fields over
WorkOrderContacts; tech phone = Dispatch.TechPhone → WO.TechPhone → Vendor.Phone. Detail exposesserviceNotes,extraServices,vendorNotes.
Domain invariants
- Extra services require a primary service on create.
- Tech/vendor notes live on dispatch when a vendor dispatch exists; otherwise on WO.
- Board POC free-text overrides linked contact projection.
Half-done
- No board PATCH for
pocName/pocPhone/pocNotes/techPhone/vendorNotes(create-only for those). ParseExtraServicesduplicated in board + detail services.- Create validation file has excessive blank lines (noise).
- Test
PatchField_ExtraServicesWithoutPrimary_Throwsdisagrees with promotion logic inApplyExtraServices.
Findings
A1 — ExtraServices without primary: test vs promotion
- Severity: High
- File:
SeaHaven.Services/Implementation/WorkOrderBoardUpdateService.cs(~294–298); testWorkOrderBoardUpdateServiceTests.cs(~331) - Explanation: Create requires primary when extras exist; PATCH promotes first extra to
Tradewithout auditing Trade. Test expectsInvalidValue. - Scenario: Legacy WO with empty Trade; FE patches
extraServicesonly. - Repro: PATCH
extraServices=["Filter change"]on WO with null Trade. - Fix: Reject when Trade empty (align create + test); do not promote silently.
- When: Now (blocks green CI / domain consistency).
A2 — ExtraServices JSON can exceed nvarchar(2000)
- Severity: High
- File: Create
SerializeExtraServices; UpdateApplyExtraServices - Explanation: Per-item max 128; no check on serialized JSON length vs column 2000.
- Scenario: Many extras →
DbUpdateException500. - Repro: PATCH/create with ~20×128-char labels.
- Fix: After serialize, throw
InvalidValueif length > 2000. - When: Now.
A3 — Create does not cap ExtraServices JSON length / per-item in serializer
- Severity: Medium
- File:
WorkOrderBoardCreateService.SerializeExtraServices - Explanation: FluentValidation caps each item at 128 but create serializer does not re-validate length or total JSON; update path has item length check, create does not.
- Fix: Share normalize helper; enforce item + JSON caps on create.
- When: Now (with A2).
A4 — POC / tech / vendor notes not patchable on board
- Severity: Medium
- File:
WorkOrderBoardFieldNames.cs/ UpdateService - Explanation: Create persists structured POC/tech/vendor; no PATCH fields → FE cannot edit after create without another API.
- When: Later if FE has another edit path; otherwise before FE ship.
A5 — Dual POC sources can diverge
- Severity: Low
- File: CreateService + Projection
- Explanation:
PocContactId+ free-text both allowed; board prefers free-text; contact row may hold different notes. - When: Later (document or clear contact notes when free-text set).
A6 — Duplicate ParseExtraServices
- Severity: Low
- File:
WorkOrderBoardService.cs,WorkOrderDetailService.cs - When: Later (extract helper).
Verdict A
Merge blocked until A1–A3 fixed. A4 is a product gap for FE edit flows.
Entrega B — Flag Color
How it works
PATCH flagColor → normalize #RRGGBB → whitelist palette → persist on WO → project on board/detail. Distinct from dispatcher avatar color.
Half-done
- None material. Migration + model + PATCH + tests present.
Findings
B1 — No finding Critical/High
Palette enforcement and clear semantics are consistent with tests.
B2 — Snapshot/model coupled with Entrega A
- Severity: Low (process)
- Explanation: Same
WorkerOrder/ snapshot file as service notes; branch split needs careful ordering. - When: During organize.
Verdict B
OK to merge after A schema lands (or same branch ordered commits). No code blockers unique to B.
Entrega C — Search / overdue / paging
How it works
Advanced search: normalize page (≥0), pageSize≤100, resolve Mon–Fri presets, whitelist sortBy, apply type/overdue/unscheduled filters, project board rows. Board GET maps ArgumentException → 400.
Half-done / risks
- Shared
PagedResultflipped to 0-based helpers while Location/Contact/etc. still use 1-basedpagedefaults.
Findings
C1 — PagedResult 0-based breaks 1-based APIs
- Severity: Critical
- File:
SeaHaven.Services/DTOs/ContactDTOs.cs - Explanation:
HasPrevious/HasNextassume 0-based; Location etc. passpage=1on first page →HasPrevious=trueincorrectly; last-pageHasNextwrong for 0-based search too if mixed. - Scenario: Any paged Location/Contact/Vendor list UI using helpers.
- Fix: Restore 1-based helpers by default; opt-in 0-based for WO search only (
PageBaseor dedicated DTO). - When: Now.
C2 — Unscheduled always included in date range
- Severity: High
- File:
WorkOrderBoardQueryFilters.ApplyDateRangeFilter - Explanation: Every advanced search with a date range also returns all unscheduled non-terminal WOs → unbounded result growth.
- Scenario: Narrow custom range + large backlog of undated WOs.
- Fix: Explicit
includeUnscheduledor cap; confirm FE intent. - When: Confirm with FE; fix before prod if unintentional.
C3 — WorkOrderType.Other = overdue sentinel
- Severity: High (domain)
- File:
WorkOrderBoardQueryFilters.ApplyTypeAndOverdueFilter - Explanation: Cannot filter real
Othertypes viatypes=99. - When: Document now; long-term separate
isPastDuequery param.
C4 — Label search label.Contains(s) matches short tokens broadly
- Severity: Medium
- File:
WorkOrderBoardSearchFilter - When: Later / monitor perf.
C5 — Overdue uses UTC date vs possibly local scheduled dates
- Severity: Medium
- File: QueryFilters / search filter
- When: Later if US sites report off-by-one day.
C6 — Search helper methods not EF-translatable (FIXED)
- Severity: Critical
- File:
WorkOrderBoardSearchFilter.Apply - Explanation: Refactor extracted
Matches*private methods; EF Core InMemory/SQL cannot translate them → all text searches throw at runtime. - Repro: Any
searchwith length ≥ 2 on board/search. - Fix: Inline predicates in the
Whereexpression (applied). - When: Now — fixed during Fase 2.
Verdict C
Merge blocked on C1 (fixed via PageBase) and C6 (fixed via inline predicates). C2 needs product confirmation. Prefer separate PR.
Entrega D — Comments edit + display names
How it works
GetDisplayNamesByIdsAsync → comment/audit projections. PATCH comment: author or Admin; General+WorkOrder only; read-only WO blocked.
Half-done
- Comment edit not written to audit log.
Findings
D1 — Comment updates leave no audit trail
- Severity: Medium
- File:
WorkOrderCommentService.UpdateCommentAsync - When: Later if compliance requires it; note in PR.
D2 — No max length on comment text on update
- Severity: Low (pre-existing on create)
- When: Later.
Verdict D
OK to merge with D1 noted. No Critical/High code defects found.
Entrega E — Sites + Vendor ContactName
How it works
GET /api/locations/sites?search= → Id/Code/City/State (Code ← Locations.Name). Vendor Dropdown adds contactName.
Findings
E1 — GetSites unbounded
- Severity: Medium
- File:
LocationDataService.GetSiteOptionsAsync - Explanation: No Take/limit; large Locations table → heavy payload.
- When: Later or add Take(500) if volume unknown.
E2 — No soft-delete filter
- Severity: Low
- Explanation: Locations inherit audit entity; if soft-delete used elsewhere, sites may include deleted.
- When: Later if soft-delete applies to Locations.
Verdict E
OK to merge independently.
Entrega F1 — Phase4 migration hardening
How it works
Before creating indexes, alter SiteCode/InternalWONumber to nvarchar(450).
Findings
F1-1 — Editing already-shipped migration
- Severity: High (ops)
- File:
20260624200000_Phase4_SearchIndexes.cs - Explanation: Environments that already applied old
Upwill not re-run. Failed partial applies need manual repair or a new migration. - When: Verify
__EFMigrationsHistoryper environment before merge. Prefer new corrective migration if any env already applied.
Verdict F1
Conditional merge after env check. Apply on feat/work-orders-phase-4 only.
Entrega F2 — Phase6 migration hardening
How it works
Idempotent SQL for Category column, CompletionDocTemplates, index; EF attributes for discovery.
Findings
F2-1 — Same applied-migration caveat as F1
- Severity: High (ops)
- When: Verify env history; idempotent SQL helps partial DBs if migration re-run manually, not if EF skips applied migrations.
Verdict F2
Conditional merge on slideover branch after env check.
Cross-cutting mandatory fixes before organize
- A1 — ExtraServices without primary throws
- A2/A3 — JSON length ≤ 2000
- C1 — PagedResult 1-based default + WO search 0-based opt-in