- Shipped
- August 27, 2026 at 4:56 AM UTC
- Author
- kamo
- Commit
- fb0ad0a
Replaces the SW5 placeholder pane with the real authoring surface: three sub-views on one tab — books, categories, entries — following the SubView switcher MarketTaxShippingTab and MarketDiscountsTab already use for tax classes and price lists. Categories and entries stay disabled until a book is chosen, and the chosen book's name and version stay on screen while they are open. labourMinutes is deliberately the loudest field on the screen. It is the one number that does two jobs — it prices the line and it sizes the visit — and it is the whole reason a task book beats a price list. So it gets its own column ahead of price, its own framed block at the top of the entry editor with a live "1h 30m" readout, and a warning above the table counting entries that are missing it. A book of priced entries with no durations is a price list with extra steps, and nothing downstream can tell you that has happened. Also lands the two pieces the surface could not work without: - **************** No proxy covered this path: commerce/quotes has a catch-all, but it is scoped to /commerce/quotes, so every call would have 404'd in the browser. The new route exports PUT as well as GET/POST/DELETE — the quotes route it is modelled on has no PUT because quotes update with PATCH, and every task-book update is a @PutMapping, so copying it verbatim would have made every read work and every save answer 405. - app/lib/serviceTaskBookApi.ts, mirroring ServiceTaskBookApi.java. Every id is a string in both directions: defaultParts carries commerce_offerings.uid values, 19-digit unique_rowid() Longs that lose their low digits to a JS double. BigDecimal stays a number — the wire mapper gives Long a JS-safe serializer and leaves BigDecimal alone. Two API behaviours shape the form and are recorded next to the code that works around them. Listing with marketUid also filters isActive = TRUE, so an authoring surface using it could never show a book you had just retired; this lists unfiltered and narrows by market itself. And a null field means "leave it alone" on update, so blank numeric inputs are omitted rather than sent as null. Pinned by a source-level contract test: the client's paths and verbs against ServiceTaskBookController's mappings, the proxy's exported verbs against the ones the client uses, and the id types against string. The cross-repo half skips without a SecurityService checkout; the rest always runs.