Commit graph

90 commits

Author SHA1 Message Date
Arthur Bassi
af593fe2d2 fix(work-orders): create a new dispatch when patching vendor on an inactive primary
Cancelled, Canceled, and Refused primaries are not live company assignments.
VendorId PATCH now inserts a Pending dispatch instead of mutating the refused row.
2026-08-20 14:47:04 -03:00
Arthur Bassi
743841d93e fix(work-orders): map primary dispatch status on detail (SH-183) 2026-08-20 13:41:42 -03:00
Arthur Bassi
75d337df0f fix(work-orders): omit inactive primary vendor from board rows (SH-183)
Do not project VendorId/VendorName for Cancelled, Canceled, or Refused primary dispatches, and expose PrimaryDispatchStatus on the board DTO.
2026-08-20 13:22:12 -03:00
Alexandre Brandizzi
0b246724d9 fix: align vendor trades with confirmed taxonomy 2026-08-20 11:44:07 -03:00
arthur.bassi
2222d04fcb Merge remote-tracking branch 'origin/dev' into feature/sh-218-additional-contacts 2026-08-18 20:52:34 -03:00
Alexandre Brandizzi
5386d6129d
Merge branch 'dev' into feature/sh-196-wo-uplifts 2026-08-18 17:46:38 -03:00
Alexandre Brandizzi
d843ac221d
Merge branch 'dev' into feature/sh-218-additional-contacts 2026-08-18 17:46:36 -03:00
arthur.bassi
8c44fa746e merge origin/feature/sh-196-wo-uplifts 2026-08-18 17:44:40 -03:00
Alexandre Brandizzi
2327097d5e fix(work-orders): symmetric NTE release, terminal guard, serialized cancel (SH-196)
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.
2026-08-18 17:41:14 -03:00
arthur.bassi
5ea5a1d702 fix(work-orders): restore NTE on auto-approved revoke and serialize cancel 2026-08-18 17:39:11 -03:00
Alexandre Brandizzi
577b7add31 feat(vendors): server-owned canonical trades vocabulary for SH-249 2026-08-18 12:11:13 -03:00
Arthur Bassi
92a3b3f045 chore(work-orders): merge origin/dev into SH-218 additional contacts
Keep IsAddOn create tests from dev alongside additional-contacts coverage.
2026-08-18 11:53:26 -03:00
arthur.bassi
aeface594a fix(work-orders): serialize uplift create and atomic cancel (SH-196) 2026-08-18 10:20:05 -03:00
Arthur Bassi
3609365939 fix(work-orders): map additionalContacts on detail GET (SH-218)
Copy contacts into MapInfo so slide-over round-trips create/PATCH, and require name plus phone on retained entries while dropping blank placeholders.
2026-08-18 10:03:12 -03:00
Arthur Bassi
4760f3fdd7 fix(work-orders): name Schedule On in PastDueStatusBlocked and register SH-121 no-op
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.
2026-08-18 09:19:27 -03:00
Arthur Bassi
c9a80f5c79 fix(work-orders): restore SH-185 Schedule On Past Due and assert IsAddOn audit
Past Due follows ScheduledDate so Due Date alone cannot set or clear it. Auto-schedule and reschedule tests now expect the third IsAddOn audit field.
2026-08-17 10:36:03 -03:00
Arthur Bassi
fa05b22df6 chore(work-orders): merge SH-121 facets and keep SH-184 IsAddOn migration 2026-08-17 10:28:03 -03:00
arthur.bassi
4c15669aff fix(work-orders): enforce SH-196 cumulative allowance and one pending per WO
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.
2026-08-14 10:36:35 -03:00
Arthur Bassi
17c2e968cd feat(work-orders): board search facets for SH-121/SH-196 2026-08-13 16:34:21 -03:00
Arthur Bassi
36ef0b00f5 feat(work-orders): persist additionalContacts on create, board GET and PATCH (SH-218)
Add JSON column, DTO/mapper, create + PATCH field, board projection, FluentValidation,
and regression tests for additional POC contacts round-trip.
2026-08-13 16:11:36 -03:00
arthur.bassi
b81cfbb005 feat(work-orders): WO-scoped uplift endpoints and board summary (SH-196)
Expose workorders/{id}/uplifts list/create/cancel/revoke for the SH-196 dialog, aggregate upliftSummary on board rows, and add service/controller regression tests.
2026-08-13 14:49:56 -03:00
arthur.bassi
1283bf7349 fix(tests): align IsAddOn board tests with account-scoped dev APIs
Pass ClaimsPrincipal and account resolver args required after rebase onto dev.
2026-08-13 13:45:25 -03:00
Arthur Bassi
97e042f552 feat(work-orders): persist IsAddOn frozen at create (SH-126)
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.
2026-08-13 13:30:09 -03:00
Arthur Bassi
f7ce2ee25d fix(work-orders): align Overdue type rejection test with dueDate message 2026-08-13 13:28:25 -03:00
Arthur Bassi
05dd262e80 fix(work-orders): derive Past Due from DueDate instead of ScheduledDate
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.
2026-08-13 13:28:25 -03:00
Arthur Bassi
29dec00790 fix(work-orders): keep pendingUpliftCount on detail and exclude deleted dispatches
Map board PendingUpliftCount through WorkOrderDetailService.MapInfo and ignore soft-deleted dispatches in the aggregate so SH-188 gating is authoritative for board/search/detail.
2026-08-12 11:50:56 -03:00
Arthur Bassi
aaeeb90f1a feat(work-orders): expose pendingUpliftCount on board rows (SH-188)
Aggregate Pending uplift requests across all dispatches tied to a work
order so the board/search/detail contract can drive the Completed gate.
2026-08-12 09:43:10 -03:00
Arthur Bassi
de0f6da8fb fix(work-orders): scope legacy GET GetComments by account [SH-221]
Pass ClaimsPrincipal into GetCommentsAsync and filter via
GetAllForAccountAsync so account-scoped callers cannot enumerate
cross-tenant comments. ADR + cross-account tests updated.
2026-08-11 15:18:29 -03:00
Arthur Bassi
3c090e2757 fix(work-orders): scope comments and completion-doc by account [SH-221]
Close the remaining SH-221 bypass: board/legacy comments and completion-doc now enforce server-derived account scope, authorize before blob storage, and cover cross-account regressions.
2026-08-11 14:52:02 -03:00
Arthur Bassi
62a4828e2f fix(work-orders): scope legacy list/detail GETs by account [SH-221]
Close the remaining SH-221 read gap so Getworkorders, filtered lists, and GetWorkorderById enforce the same server-derived account boundary as board/media.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 11:57:37 -03:00
Arthur Bassi
bca2e0d50d test(work-orders): fix sync/webhook/recon fixtures for AccountId stamp [SH-221]
Seed resolvable Customer accounts and mock account resolution so create-path CI tests match fail-closed account scope.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 10:54:23 -03:00
Arthur Bassi
1edcf479ae fix(work-orders): apply account scope across create and reads [SH-221]
Stamp WorkOrder.AccountId on all create paths and filter board/list/search/detail by server-derived account claims so scoped callers cannot cross accounts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 10:45:37 -03:00
Arthur Bassi
ea2dedf579 !fix(work-orders): fail-closed media account scope with org_scope claim [SH-221] 2026-08-06 10:26:04 -03:00
Arthur Bassi
fdc315d8fe !feat(work-orders): enforce media account scope and AddMedia freshness [SH-221] 2026-08-06 09:47:34 -03:00
Arthur Bassi
e572786b1c ~docs(work-orders): demote ADR 0001 to Proposed pending CODEOWNERS [SH-116] 2026-08-05 09:57:14 -03:00
Arthur Bassi
8ff4ab1742 fix(work-orders): document single-org media scope (ADR 0001)
Clarify SH-116 tenant scope as board-aligned ApplyBaseScope + claims, and add out-of-org-scope GET/mutation tests for deleted/template/missing WOs.
2026-08-04 16:07:26 -03:00
Arthur Bassi
6b18327d6b fix(work-orders): authorize GET media and forward cancellation
Enforce claims-derived read scope on media list and thread CancellationToken through detail data reads so HTTP cancel stops EF work.
2026-08-04 14:14:37 -03:00
Arthur Bassi
680012d88b fix(work-orders): enforce media role scope and relational concurrency
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.
2026-08-04 11:08:18 -03:00
Arthur Bassi
899da0eb4f fix(work-orders): enforce media auth and base scope on mutations
Require an authenticated ClaimsPrincipal at service entry and filter
tracked work orders with board base scope so deleted/template rows
surface as NotFound without disclosure.
2026-08-04 11:08:18 -03:00
Arthur Bassi
2ec85d1193 fix(work-orders): harden media upload contract for review blockers
Enforce MIME/extension/magic-byte validation, auth and workOrderVersion concurrency, audit on category changes, and validate-before-store with blob compensate.
2026-08-04 11:08:18 -03:00
Alexandre Brandizzi
d073a503d1 feat(work-orders): board completedDate + media categorize contract
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).
2026-08-04 11:08:18 -03:00
Alexandre Brandizzi
f701899a83 fix(work-orders): satisfy producer contract review 2026-07-27 16:33:06 -03:00
Alexandre Brandizzi
27bf81b7f8 fix(work-orders): address procurement review findings 2026-07-27 14:40:17 -03:00
Alexandre Brandizzi
8a2e260177 test: prove SH-133 webhook and admin boundaries 2026-07-25 15:45:52 -03:00
Alexandre Brandizzi
e3c37e54b4 feat: complete SH-133 procurement reconciliation 2026-07-24 22:13:25 -03:00
Alexandre Brandizzi
bdffe77e42 feat: ingest signed procurement work-order webhooks 2026-07-24 21:03:50 -03:00
Arthur Bassi
8f492c0faf
feat(work-orders): allow comment edit and resolve author audit display names (#24)
* 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.

* feat(work-orders): allow comment edit and resolve author audit display names

Add PATCH comment for author/Admin, return authorName, and resolve
AssignTo audit values to user display names.

* fix(work-orders): enforce author-only comment edits per SH-122

Remove the undocumented Admin override so only the original comment author can edit, matching the ticket acceptance criteria.

---------

Co-authored-by: Arthur Bassi <arthur.winiarski.ranger@outlook.com>
Co-authored-by: Alexandre Brandizzi <alex_brandizzi@hotmail.com>
2026-07-24 21:28:44 +00:00
Arthur Bassi
620a36af54
feat(work-orders): enrich board search overdue filters and 0-based paging (#23)
* 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>
2026-07-24 21:12:03 +00: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
Alexandre Brandizzi
78abf84b0d fix(work-orders): finalize PR 22 integration compile fixes 2026-07-24 14:27:52 -03:00