Locations store State as either a two-letter postal code or a full state
name (the location list filter expands codes to both forms via
UsStateCodes.ExpandStorageValues, and dev fixtures carry State = "indiana").
DashboardRegions.Resolve only matched the two-letter key, so every location
stored as a full name fell into Unmapped/Other and the East/Central/West/
California bars undercounted.
Normalise the stored value through a new UsStateCodes.ToCode, which accepts a
code or a full name and returns the canonical postal code, before the zone
lookup. Extend the region test with full-name storage forms and add an
invariant asserting every US state and its full-name form resolves to a
canonical bucket.
SH-320: the WO number normalizer stripped every non-digit, so a manually
entered SH placeholder (e.g. SH00001) was saved as 00000000001 on both
create and patch. Keep the SH prefix, and reject replacing a saved real
APM number with an SH placeholder.
The media allowlist refused any upload whose multipart part had an empty or
application/octet-stream Content-Type before looking at the extension or the
bytes. Browsers take that header from File.type, which mobile browsers leave
empty when the OS cannot classify a picked file, while the client-side gate
already accepts such files on extension alone. A real JPG/MP4/MOV could pass
the dialog and still be refused by the API.
Only an undetermined type now falls back to the extension. The resolved type
still goes through the SH-171 document/category rule, the extension pairing,
and the magic-byte signature check, so an octet-stream .pdf stays refused for
Completion, Before and After, and a declared type is never overridden.
Incomplete and Scheduled are derived by the server. A direct lifecycleStatus
PATCH may only restate the status derivation already produced; any other
request to move a work order into an automatic state returns the stable
AutomaticLifecycleStatus validation error and leaves status and audit untouched.
The completion-document endpoint persisted whatever file it received: the
only checks were non-null, non-empty, and a 30 MB request limit. Its sibling
media endpoint has enforced a MIME allowlist, MIME-to-extension pairing, and
a magic-byte signature check since SH-116.
Validate before the file reaches storage, so a rejected upload leaves nothing
behind. An undetermined content type is accepted only alongside a .pdf name
and a %PDF- signature, because the browser leaves File.type empty when the OS
cannot classify the file and the completion-doc dialog already allows that.
Auto-approval now uses the WO-scoped $500/$5,000 Emergency cap instead of dispatch NTE, rejects a second open request across dispatches, and cancelling a WO withdraws pending uplifts with audit.
Expose workorders/{id}/uplifts list/create/cancel/revoke for the SH-196 dialog, aggregate upliftSummary on board rows, and add service/controller regression tests.
Add set-once IsAddOn with server cutoff at create, board DTO exposure, legacy type-7 backfill, and Types=AddOn search compat. Aligns with FE PR #61 frozen contract.
Past Due must track the deadline (Due Date), not Schedule On. Keep dueDate and scheduledDate PATCH mutations independent so rescheduling alone does not clear Past Due.
Derive staff vs technician scope from claims (Assigned for User), map
DbUpdateConcurrencyException to a stable 409, and add SQLite competing-write
tests for categorize-vs-categorize and categorize-vs-delete.
Enforce MIME/extension/magic-byte validation, auth and workOrderVersion concurrency, audit on category changes, and validate-before-store with blob compensate.
Expose completedDate on PATCH /workorders/{id}/board so CompDoc can leave legacy EditWorkorder. Allow optional media category on upload, PATCH category afterward, and enforce JPG/PNG/MP4/MOV allowlist (SH-116).
* feat(work-orders): enrich board search overdue filters and 0-based paging
* fix(work-orders): align stacked services with CI build
* fix(tests): pass userDataService in comment service unit test
* fix(work-orders): use dedicated overdue query flag
Stop treating WorkOrderType.Other as an overdue sentinel. Board and advanced search now accept overdue=true while types=Other filters real Other rows; combining both uses OR.
* test(work-orders): cover overdue date/status boundary and Other type-filter
Lock the PR #23 overdue regression boundary through the public advanced
search service. Prove overdue filtering is driven by past-due date plus
non-terminal status, not by the WorkOrderType.Other sentinel:
- Other + future/not-completed excluded from overdue
- past-due + Scheduled included; past-due + Completed/Canceled excluded
- types=[PM, Other] keeps real Other rows and does not pull past-due rows
- assert 0-based paging (Page=0) is preserved alongside overdue/type filters
---------
Co-authored-by: Arthur Bassi <arthur.winiarski.ranger@outlook.com>
Co-authored-by: Alexandre Brandizzi <alex_brandizzi@hotmail.com>