Commit graph

17 commits

Author SHA1 Message Date
Alexandre Brandizzi
564bd70301 fix(dashboard): scope Trend by picker and admit Scheduler to dispatcher picker
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>
2026-09-17 03:37:36 -03:00
Alexandre Brandizzi
f564e1b112 Merge remote-tracking branch 'origin/dev' into feat/ab/sh-347-dashboard-contract
# Conflicts:
#	Api.SeaHavenIndustries.Tests/DashboardServiceTests.cs
2026-09-17 02:18:22 -03:00
Alexandre Brandizzi
dab39e78be Merge remote-tracking branch 'origin/dev' into feat/ab/sh-348-dashboard-region 2026-09-16 22:06:49 -03:00
Alexandre Brandizzi
dca8d898cb feat(dashboard): add scoped metrics contract (SH-347) 2026-09-16 21:37:17 -03:00
Adam Moussa
f6bd779fe0
Merge branch 'dev' into feat/ab/sh-353-dashboard-kpis-stacked 2026-09-16 20:00:32 -04:00
Alexandre Brandizzi
93ecbbd410 test(dashboard): guard Stats wire object for KPI counts (SH-353)
The added service test asserts DashboardStatsDTO, which already carried
ScheduledTomorrow, PendingUplifts and AvetaPending, so it does not guard
the regression that was actually shipped: the DashboardController Stats
anonymous response dropping those keys and leaving the tiles empty.

Add DashboardControllerTests.GetStats_SerialisesKpiCountsOnWireObject,
which drives the controller and asserts the three keys are present on the
serialised wire object, so re-dropping any of them fails a test.

Also reword the pending-uplifts comment in DashboardServiceTests: the
Pending-only-vs-ChangesRequested exclusion is the behaviour the code
ships today, an open product call, not a settled review decision.
2026-09-16 20:25:45 -03:00
Alexandre Brandizzi
22308f21f6 fix(dashboard): resolve region zones from full state names (SH-348)
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.
2026-09-16 20:15:20 -03:00
Alexandre Brandizzi
104439c239 fix(dashboard): serialise KPI counts in Stats response (SH-353)
GetKpiCountsAsync computes ScheduledTomorrow, PendingUplifts and
AvetaPending and DashboardStatsDTO carries them, but the Stats
endpoint's anonymous wire object never serialised them, so the tiles
had nothing to read. Add scheduledTomorrow, pendingUplifts and
avetaPending to the response.

Add a DashboardServiceTests case covering GetKpiCountsAsync end to end:
it asserts all three counts and pins the pendingUplifts semantics to
Status "Pending" only, excluding "ChangesRequested" (which is back with
the vendor, not awaiting an approval decision).
2026-09-16 20:07:31 -03:00
Alexandre Brandizzi
5877f7817f fix(dashboard): bucket trend by calendar ScheduledDate (SH-350)
ScheduledDate is persisted as a midnight calendar value (board create writes
request.ScheduledDate.Value.Date; board patch writes the parsed .Date into a
datetime2 column with no offset). GetTrendAsync treated it as a UTC instant and
converted to America/New_York, so midnight became 19:00/20:00 the previous day
and every board-scheduled work order fell into the prior bucket: a job scheduled
today read as yesterday and overdue, and the Today bucket showed zero.

Bucket by DateOnly.FromDateTime(scheduled.Date) with no conversion, matching
DashboardMetrics and WorkOrderDerivedFields, so Trend and Stats agree on the
same rows. Rewrite the daily and yearly trend tests to assert calendar-date
classification (the removed conversion had encoded the shift into their
expectations) and add a regression test that a midnight-today work order stays
in the Today bucket and is not overdue.
2026-09-16 19:57:09 -03:00
Alexandre Brandizzi
9d7efa34a6 feat(dashboard): add trend metrics (SH-350) 2026-09-16 18:42:26 -03:00
Alexandre Brandizzi
3358ea5058 feat(dashboard): add vendor insights (SH-349) 2026-09-16 18:10:42 -03:00
Alexandre Brandizzi
d4afcced87 feat(dashboard): add regional work order metrics (SH-348) 2026-09-16 18:03:38 -03:00
Alexandre Brandizzi
0194748e3b feat(dashboard): add dispatcher performance (SH-347) 2026-09-16 17:58:08 -03:00
Alexandre Brandizzi
9b39d0e5ec feat(dashboard): add dispatcher workload (SH-346) 2026-09-16 17:43:49 -03:00
Alexandre Brandizzi
a2e749c72d feat(dashboard): add core dashboard metrics (SH-343) 2026-09-16 17:28:21 -03:00
Alexandre Brandizzi
efd13b6f60 fix(dashboard): scope stats to authenticated accounts (SH-336) 2026-09-16 17:12:03 -03:00
Alexandre Brandizzi
7d245eb717
refactor: enforce backend boundaries and optimize dispatch (#30)
* refactor(api): enforce service and data-service boundaries

* refactor(api): complete feature service boundaries

* refactor(identity): enforce service and data boundaries

* refactor(vendors): enforce service and data boundaries

* refactor(workorders): enforce service and data boundaries

* refactor(backend): enforce architecture and optimize dispatch

* style(backend): format changed architecture files

* fix(architecture): address backend review follow-ups

* fix(backend): sanitize exception disclosure in changed API endpoints

Replace raw exception-message disclosure (ex.Message) returned to API
callers with a stable sanitized public message plus correlated structured
internal logging, across the endpoints changed in this PR.

- Add SanitizedErrors helper: logs the original exception at Error with a
  generated correlation id and returns a stable public message referencing
  it so support can trace without exposing internals.
- Inject ILogger<T> into the 14 changed controllers and route every
  ex.Message/dbex.Message disclosure through the helper, preserving status
  codes, response shapes, and business data (e.g. OpenWorkOrders).
- Leave FluentValidation (vex.Errors) and existing fixed-message catches
  untouched; out-of-scope controllers (Account/Contact/Employee/Asset/
  PMSchedule) are unchanged.
- Add focused tests proving internal exception text is not returned and
  that Error logging carrying the original exception is invoked.

* fix(architecture): abstract job run state access

* style: format board update service

* test: use collection assertion idiom
2026-07-24 17:35:34 -03:00