Build and test, architecture, and dependency-review are the required checks
on main. A merge queue only counts checks that ran on the merge_group event,
so each workflow now triggers on it. The architecture gate reads the merge
group's own base and head because github.event.before is empty there. The
dependency-review action cannot diff a merge group, so that event reports the
same check name from a pass-through job; the real review already gated the
pull request before it could be queued.
The uplift detail modal needs the work order's assigned dispatcher, the
requesting vendor's technician and the scheduled date. They now resolve
from the same effective work order and vendor as the existing queue row,
so the modal no longer depends on a separate work-order fetch that
account-scoped staff cannot read.
#144 branched from the SH-210 decision-actions commit before the SH-207/SH-208
read-contract corrections landed, so it re-implemented workOrderClosed,
attachmentCount, decidedByName, and pendingExposureTotal with stale semantics:
it projected WorkerOrderNumber (the CRM external id) instead of InternalWONumber
(what the board renders) and summed raw RequestedNTE, which double-counts the new
NTE total on vendor-portal rows across sequential approvals.
Merge origin/feat/ab/sh-210-uplift-decisions (#141, what lands) in and resolve
every conflict in #141's favour, so those fields and their granted-amount
exposure math now come from #141 rather than being duplicated here. Keep only the
two deltas #144 actually adds on top of #141:
- attachmentCount counts non-deleted UpliftEvidence documents on the dispatch,
not every completion document, so completion photos no longer inflate the chip;
- the queue read resolves the effective work order via the primary-plus-linked
(DispatchWorkOrders) convention the sibling reads use, so a dispatch linked only
through that table surfaces its WO context, closed flag, and exposure. Covered
by an in-memory test and a SQLite relational test that proves the fallback
translates to SQL.
Drop the superseded attachment-count test that asserted completion documents
count, and align the relational test to seed InternalWONumber.
Storing the submitted full name in FirstName while leaving a seeded
LastName intact made an unchanged save project a duplicated name
(e.g. "Legacy Manager Manager"). Leave FirstName/LastName untouched when
the submitted name already matches the stored first+last projection, and
otherwise split the submitted name across FirstName/LastName so renames
round-trip cleanly.
GetPagedAsync resolved WorkOrderNumber/Site/Service, WorkOrderClosed, and
WorkOrderId only through Dispatch.WorkOrderId, so a dispatch linked to its
work order solely through DispatchWorkOrders surfaced blank WO context,
workOrderClosed:false, and zero exposure. Resolve the effective work order
using the same primary-plus-linked convention as GetWorkOrderIdForUpliftAsync
and GetApprovedExposureForWorkOrdersAsync via a single work-order lookup.
ResolveDispatcherScope now admits the Scheduler role, which owns the
viewAllDispatchersOnDashboard permission, so it can load Stats, Workload,
Performance, Regions and Trend instead of being denied.
GetTrendAsync now resolves scope via the shared ResolveDispatcherScope so a
picker DispatcherId selection scopes Trend consistently with the other
endpoints and unauthorized roles fail closed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Break collection initializer entries onto one line each in
WorkOrderBoardRegions and WorkOrderBoardSearchTests so the changed-file
formatting gate (dotnet format --verify-no-changes) passes.
CreateAsync checks FindByEmailAsync and then inserts, so two concurrent
creates with the same email can both pass the check and hit the unique
user-name index. That DbUpdateException was unhandled and surfaced as a
500. Catch it at the CreateAsync call and return the existing
"Email is already in use." message, matching the check-then-insert
converge pattern already used by SetOverrideCoreAsync.