Creating a Pending dispatch now stages VendorId FieldChanged from the previous
assignment so field lock and concurrency checks run like a live vendor PATCH.
Cancelled, Canceled, and Refused primaries are not live company assignments.
VendorId PATCH now inserts a Pending dispatch instead of mutating the refused row.
SH-44's user story is about not silently orphaning active work. The
confirmation dialog tells the operator, but nothing told the system, so
a deactivation that left work orders open was indistinguishable from one
that had none.
VendorService now takes an ILogger and writes a warning naming the
vendor, the user and the number of work orders left open whenever the
guard is cleared by confirmation. Both the update and delete paths are
covered; nothing is logged when there was nothing to leave open.
SH-44 and SH-82 both left "blocks, or requires explicit confirmation" to
be decided with the team, and the implementation took the blocking
branch. SH-254 settles it the other way: the approved design offers
"Deactivate anyway" beside the list of open work orders.
Deactivation with open work orders is now permitted, but only when the
caller says it has shown them: ConfirmOpenWorkOrders on the update DTO
and a confirmOpenWorkOrders query parameter on the delete route. Absent
the flag the existing guard still throws, so nothing deactivates by
accident and no caller loses the check by omission.
confirmOpenWorkOrders is a required parameter on DeleteVendorAsync
rather than an optional one, so every call site states its intent.
Three contract gaps found reviewing the frontend consumer:
- Revoking an auto-approved uplift never restored the dispatch NTE. Create
raises NTE for both auto-approved and approved requests, but revoke restored
it only for Approved, so the allowance was freed while the NTE stayed raised
and every create -> auto-approve -> revoke cycle compounded the inflation.
Revoke now compensates for NoApprovalRequired symmetrically.
- Revoke and cancel had no work-order lifecycle check, so a direct API call
could still mutate uplifts on a Completed or Canceled work order; the board
dialog's read-only state is UX only. Both now reject terminal work orders in
the service.
- WorkOrderBoardCancelService read the pending-uplift list outside any gate, so
an in-flight create could commit after that read and leave a pending uplift on
a Canceled work order. The cancel flow now runs inside the same per-work-order
gate as create, so the pending read, withdrawal and status audit serialize
against it.
Two review findings on the additive PATCH path:
- AddTechniciansAsync can rename via CompanyFields.Name and write NormalizedName
against the unique index, but the save had no guard. A colliding rename
surfaced as an unhandled 500 from the PATCH action instead of a stable client
conflict. Pre-check the normalized name against other live companies and throw
VendorRosterDuplicateNameException, with a scoped catch around the save for the
race where a competing rename commits in between. The controller maps it to a
409 alongside the existing concurrency conflict.
- Empty-payload validation only rejected a null CompanyFields, so an all-blank
CompanyFields object was forwarded as a company update, bumping RowVersion and
rewriting every technician's LastModificationTime without changing any company
data. Blank fields now collapse to no company change, and a request with
neither technicians nor a real company value fails validation.
PATCH /api/vendor-company-roster/{companyId} inserts the submitted
technicians and optionally updates company fields. Technicians absent
from the payload are never removed or deactivated, so the Add Vendor
flow can no longer soft-delete an existing roster via the full-snapshot
PUT. Stale rowVersion still 409s; unknown company 404s. POST (create)
and PUT (reconcile) behaviour is unchanged.
The 422 still told dispatchers to update Due Date. Point the remedy at Schedule On and make the SH-121 successor visible to EF so G6 lineage is complete.
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.