- Shipped
- August 27, 2026 at 10:13 AM UTC
- Author
- kamo
- Commit
- ad1c391
SW6b Task 4. Labour, parts, time, vendors, sign-off and notes were React `useState` on `commerce/service/[id]` and lost everything on reload. They now write to the endpoints SW6b Task 3 landed. The client mirrors the three breaking changes in `ServiceJobApi.java`: * `JobInput` loses `labourTotal`, `materialTotal`, `total`, `signedOff` and `signoffAt`. The totals are recomputed server-side from the job's lines on every line write, and sign-off goes through `PATCH /{uid}/signoff`, which stamps its own clock. Leaving them on the general update was a second door onto five columns the server owns, and the second door is the one nobody checks. * `JobView` gains `lines`, `notes` and `vendors`. **Null means "not loaded", empty means "none"** — `listJobs` leaves all three null so the grid does not run three queries per row, and every tab tells the two apart rather than drawing "no lines" over a job that has twelve. * The proxy exports all five verbs. POST and DELETE were deliberately absent while a job had no children; a line and a vendor engagement are created and removed here, so both are mapped now. **************** asserts the set in both directions and was widened with them. Every mutation answers with the whole detail `JobView` and each handler hands that straight to `adopt`. Nothing merges a write's effect by hand: the three totals are the server's and no client arithmetic reproduces them. `adopt` deliberately does NOT re-seed the sign-off draft — SW6a's page re-seeded every local field from each response, so adding a line silently discarded whatever the engineer had typed into the completion write-up. Only the load and a successful sign-off save re-seed it. The notes tab is ported from the markup salvaged out of the dead work-order dialog, reusing its five existing keys. Two departures, both recorded in the code: the note author is an id and the tab says "You" for the current member and omits the author line for anyone else, because no endpoint resolves a batch of member ids to names and a bare 19-digit number under a heading that says "author" helps nobody; and the salvage's `.reverse()` is dropped, because the server already answers newest-first and reversing that puts the oldest on top. `commerceVendorDirectory.ts` is new and exists because `posApi.getVendors()` cannot be used for the vendor picker: `POSController.getVendors` answers with the `Vendor` entity, whose `uid` is a ~19-digit `unique_rowid()` written as a bare JSON number, so `JSON.parse` rounds it before any component sees it and `posApi.Vendor` duly declares `uid: number`. Engaging a vendor with a rounded id names a row that does not exist. This module reads the response as text and quotes long `"uid"` values before parsing. The narrow fix is a DTO that sends the id as a string, as `ServiceJobApi` does; until that exists this is the honest read. Dictionary landed first: kamo-translation-dictionary 0eaab740.