Repository navigation
feat(sales): sales may change the deposit % on a draft notice; the amount follows - #1176
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
/code-review max (head ce59224): 10 finders → 22 unique candidates → 21 confirmed / 1 plausible, + 5 from the sweep. Verdict: do NOT merge yet. Items 1, 6–12, 14, 15 are fixable here; 2, 4, 5 need an owner ruling. Full verdicts in the session scratchpad Money / document correctness
Validation / UX Cut at the 15-cap (pre-existing): |
…e and the amount follows (RED) Owner ruling 2026-10-23 reverses the 2026-09-29 lock. Red until DepositNoticeService.update stops overriding the percent, refuses 0, and the page offers an editable field on a draft. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…ount follows Owner ruling 2026-10-23 reverses the 2026-09-29 lock. DepositNoticeService.update now persists the request's percent (null keeps the draft's own, 0 or less is refused with 400); requireDraft still 409s an issued notice. The repository already recomputes the amount from the percent on update. DepositNoticePage shows the % as a whole-number field on a draft, recalculates the amount as sales types, and is read-only once issued. The mock refuses 0 the same way. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
… stored lines and their amount (RED) Today a null or empty items list saves subtotal 0 and deposit 0 while the item rows stay. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…nes and recomputes the amount DepositNoticeRepository.update replaces item rows only when items are sent, but computed the subtotal from the request alone, so a null or empty items list saved subtotal 0 and deposit 0 beside intact rows. It now sums the stored rows in that case. Null and [] both mean keep: rows were never deleted on [], so the safer reading leaves them and makes the money match them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…ets a Thai message, and a cleared field bills 0 (RED) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
… message; the page rounds what sales types DepositNoticeService.update refuses a request percent that is not a whole number of percent (400), because the document prints it rounded: 40.5% showed as 41% while billing 40.5%. The @DecimalMax on DepositNoticeDraftRequest.depositPercent carries a Thai message instead of the English default. DepositNoticePage rounds the typed value so the field, label and amount agree. The mock refuses over 100% and fractional percents the same way. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…ice request; the repository update takes the stored lines it was handed Inert plumbing for the next commits: DepositNoticeDraftRequest gains a nullable expectedUpdatedAt (an 8-argument constructor keeps every existing caller compiling), read by nothing yet. DepositNoticeRepository#update now sums the lines the service already read (doc.items()) instead of re-reading them; same rows, one query fewer. No behaviour change: DepositNoticeServiceTest 50, DepositNoticeControllerTest 9, DepositNoticeIssueGuardIntegrationTest 5, all green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…sued-row refusals, legacy % echo, Thai % floor, sales-only update (RED) RED before the fix (each an assertion on the missing behaviour): - issue refuses a stored 0% / 0.3330 / 1.5 draft and a 0-baht draft, nothing numbered: DepositNoticeIssueGuardIntegrationTest:221 "Expecting code to raise a throwable" - a save with a stale updatedAt stamp is 409 and the first save stands: DepositNoticeIssueGuardIntegrationTest:301 "Expecting code to raise a throwable" - DepositNoticeRepository#update on an ISSUED row refuses before touching the lines: DepositNoticeIssueGuardIntegrationTest:334 "Expecting code to raise a throwable" - a stored 0.3330 sent back unchanged saves: DepositNoticeServiceTest:1084 ApiException "เปอร์เซ็นต์มัดจำต้องเป็นจำนวนเต็ม" - a sales_manager that owns the deal cannot update: DepositNoticeServiceTest:820 "Expecting code to raise a throwable" - -0.05 and 0 get a Thai message: DepositNoticeControllerTest expected "depositPercent เปอร์เซ็นต์มัดจำต้องมากกว่า 0" but was "must be greater than or equal to 0" Guards (green before): the stamp binds through the canonical record constructor; the stored-lines fixture now has two fractional-qty lines with net below unit, so net x qty (75.02), unit x qty (90.00), the rounded amounts (75.03) and nets alone (43.34) differ. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…issue save is refused before the lines; update is sales-only - DepositNoticeService#issue: a stored percent outside a whole 1-100 (0%, 0.3330, 1.5) is 409 before numbering; a 0-baht draft is 409 (same lines as #1173, so the two merge clean). - DepositNoticeRepository#update: honours expectedUpdatedAt (AND updated_at = :expected, null skips it) and checks the header row count; 0 rows -> 409 "ถูกแก้ไขโดยผู้ใช้อื่น" while still a draft, else requireDraft's message, both before the item DELETE/INSERT. Dead null -> 0.50 fallback removed (the service always sets the percent). - DepositNoticeService#update: requireRole(SALES_ROLES) first, like create/issue; the whole-number rule skips a stored percent sent back unchanged. - DepositNoticeDraftRequest: @DecimalMin exclusive with a Thai message. GREEN: DepositNoticeServiceTest 50, DepositNoticeControllerTest 10, DepositNoticeIssueGuardIntegrationTest 6. Mutations, each red on its named test: % re-check narrowed to signum, amount check off, stamp predicate dropped, row count ignored, legacy-echo skip removed, role gate removed, @DecimalMin back to inclusive English, stored-lines sum switched to unitPrice x qty. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…d the folded ส่วนลดพิเศษ row (RED) Owner rulings 2026-10-23 (items 2 and 4 of the #1176 review): - while a notice is ISSUED, the ticket route and the pricing-request route both refuse a new draft with 409 naming that notice, and no row is written: DepositNoticeIssueGuardIntegrationTest:259 "Expecting code to raise a throwable" - a DEAL_DIRECT quotation's ADJUSTMENT row is folded into the goods lines by amount: 2 lines, nets 95.00 / 475.00, subtotal 1,900, and the page-shaped PUT validates and saves: DealQuotationConfirmOrderIntegrationTest:262 expected ("10","100.00","95.00",...) but the draft held ("10","100.00","100.00"), ("2","500.00","500.00"), ("-1","100.00","100.00") Revised for the new rule: PaymentTrackIntegrationTest's revision walk now shows the create route refusing (PaymentTrackIntegrationTest:288, RED) and writes the revision draft through the repository, as the separate re-version change will; RemainingInvoiceServiceIntegrationTest's second-chain fixture does the same (green before and after, 28 tests). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…he ส่วนลดพิเศษ row into the goods lines - DepositNoticeService#createDraftFromQuotation (both the ticket route and OrderConfirmationService#createDepositNoticeFromQuotation funnel here): while a notice is ISSUED, 409 "ดีลนี้ออกใบแจ้งรับมัดจำเลขที่ <no.> แล้ว จึงสร้างฉบับร่างใหม่ไม่ได้". issue()'s DEPOSIT_NOTICE_ISSUED self-loop stays for the separate re-version change. - DepositNoticeRepository#findDealQuotationItems: a DEAL_DIRECT ADJUSTMENT row (qty -1) is no longer copied; foldAdjustments spreads its amount over the goods lines by amount (HALF_UP per share, remainder on the last line with an amount), keeps the descriptions and relabels the per-unit discount. Subtotal unchanged for the review's fixture (1,900); a qty that does not divide a line's new amount can move it by under qty x 0.005 (ponytail comment). GREEN: DepositNoticeIssueGuardIntegrationTest 7, PaymentTrackIntegrationTest 14, DealQuotationConfirmOrderIntegrationTest 31, DepositNoticeServiceTest 51. Added with the fix (proved by mutation, not RED-first): DepositNoticeServiceTest #foldAdjustments_putsTheRoundingRemainderOnTheLastLineWithAnAmount, since the IT's 50/50 split cannot tell a remainder from a plain proportional share. Mutations, each red on its named test: guard filter -> false; fold skipped; remainder off. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…hecks, one notice per deal in the UI, and a confirm before issuing from the deal (RED)
RED before the fix:
- DepositNoticePage: a stored 0.333 saves as 0.33; a net-0 line adds 0 and is sent at 0
(Unable to find "100.10 บาท": the line fell back to its unit price); 100.10 at 33% shows
35.34; a blank เพิ่มรายการ row is dropped; the stamp travels; a cleared % sends null
- DepositNoticePage: a 409 save reloads the draft and toasts the server message (payload had
no expectedUpdatedAt)
- DepositNoticePage: % 0 and 150 are blocked on the % field before the dialog (Unable to find
"กรุณากรอก % มัดจำเป็นจำนวนเต็ม 1–100")
- mock depositNotices: @Valid-shaped messages before the 404/409 lookups ("ไม่พบใบแจ้งรับมัดจำนี้"
instead of 400), the reread straight after the three rejects, the stale stamp, the owner/role
checks on update and issue, no second draft on either route ("promise resolved instead of
rejecting")
- mock promoteToDeal: the deposit draft folds a flat ส่วนลดพิเศษ (got [10, 850, 850, null] and a
qty -1 row)
- canCreateDepositNoticeFromQuotation refuses once an ISSUED notice exists (expected true to be false)
- PricingRequestDetailPage step 6 points at the ISSUED notice (Unable to find link /GLRD69001/)
- DealDepositPanel (TicketDetailPage): ออกเอกสาร opens a dialog with %, deposit, VAT, total
(Unable to find role="dialog"); an ISSUED notice says the deal already has its notice
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…s; one notice per deal in the UI; the deal page confirms before issuing DepositNoticePage: a net-0 line counts as 0 (?? not ||); the stored % is rounded to the whole % the field shows; money rounds per step (round2, now exported from quotationMeta); a blank เพิ่มรายการ row is left out of the save and the issue checks; a cleared % sends null; the draft's updatedAt goes as expectedUpdatedAt and a 409 reloads the draft and toasts the server message; the issue gate has its own whole 1–100 % rule focused on the % field; the confirm body is DepositIssueSummary, shared with DealDepositPanel. DealDepositPanel: ออกเอกสาร opens a ConfirmDialog with the stored %, deposit, VAT and total; an ISSUED notice says the deal already has its notice. canCreateDepositNoticeFromQuotation takes the deal's notices and refuses once one is ISSUED; PricingRequestDetailPage step 6 reads them (existing listByTicket) and links the ISSUED number instead of offering the create. Mock: @Valid-shaped % checks before the lookups, sales-only + owner on update and issue, the stored-% floor, the legacy echo, the stale stamp (updatedAt strictly increases), issue's % and amount checks (amount lines as in #1173), no new draft on either route while one is ISSUED, and the ส่วนลดพิเศษ fold for a flat adjustment. GREEN: DepositNoticePage 15, mockApi.depositNotices 13, mockApi.promoteToDeal 11, pricingRequestMeta 75, PricingRequestDetailPage 277, TicketDetailPage 169, contract + serverContract + quotationMeta. ESLint clean. Mutations, each red on its named test: ?? -> ||, stamp dropped, 409 reseed dropped, cleared % -> 0, blank-row filter off, % rule off, populateForm rounding off, all per-step rounding off, mock owner check off (update and issue), mock validation moved after the lookups, mock stamp off, mock reject leaks the %, mock one-notice guard off, mock fold off, predicate ignores notices, PR page ignores notices, panel line removed, panel issues without the dialog. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…from #1170/#1173's hunks No behaviour change: requireNoIssuedDepositNotice now sits above buildMockDoc (it was adjacent to #1170's rewrite of mockPickTicketQuotationForDepositNotice), and the new predicate and step-6 tests open their describe blocks instead of sharing #1173's insertion points. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…import does not collide with #1170's No behaviour change (DepositNoticeIssueGuardIntegrationTest 7, DepositNoticeServiceTest 51, DepositNoticeControllerTest 10 green with clean). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…al is issued; 2-decimal lines; the quotation's own discount wording kept Owner rulings 2026-10-28 on the #1176 review: - Item 1(a): DepositNoticeService.issue refuses while another notice on the deal is ISSUED ("ดีลนี้ออกใบแจ้งรับมัดจำเลขที่ … แล้ว จึงออกฉบับร่างนี้ไม่ได้"), so a second draft can no longer replace the document the customer already has. Mock mirrors it. - Item 2 ("use 2 decimals for all"): foldAdjustments rounds each share and each new net price to 2 decimals with no remainder pushed onto a last line; the notice total is the sum of its own 2-dp lines and may differ from the quotation by a few satang (documented as the ruled outcome). - A line keeps the quotation's own discount wording and gains "+ ส่วนลดพิเศษ … ต่อหน่วย" only when the fold lowered its price. - The rebase onto develop left a duplicate requireNoIssuedDepositNotice in mockApi.js; one removed. Tests: issue_secondDraftOnSameTicket_…StillRefusedWhileAnotherNoticeIsIssued (IT; red with the guard disabled, green with it), depositNoticeSecondIssue_isRefused_… (PaymentTrack IT), foldAdjustments_keepsEachLinesOwnLabel_…TwoDpLines (unit), mock deposit-notice tests. Green: DepositNoticeServiceTest 50, DepositNoticeIssueGuardIntegrationTest 7, PaymentTrackIntegrationTest 14 (local Postgres), mockApi.depositNotices + deposits 36. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ce59224 to
4d92a57
Compare
…e locks the deal and takes an optional stamp, page refuses cleared rows and keeps stored % - DealDepositPanel: notices were landing in the accepted-quotation slot; pass the deal's quotation (0% hides create) and notices separately. - DepositNoticeService.issue: lock the ticket row before the one-ISSUED guard; optional expectedUpdatedAt (409 if the draft moved on). Dead SUPERSEDE update removed. - DepositNoticePage: save/preview refused when every row is cleared; a stored non-whole % is no longer rewritten; issue sends the stamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de and remainder rounding Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-to-main Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Draft. Owner ruling (Ploy, 2026-10-23): sales can change the deposit % on the deposit notice and the amount recalculates automatically — on a DRAFT only; an issued notice is a customer document (409). This REVERSES the 2026-09-29 ruling that locked the % to the quotation. The % still defaults from the quotation at creation (creation paths untouched; #1170 reworks them). One deposit per deal stays (already enforced: issuing supersedes older ISSUED notices,
DepositNoticeRepository:304, pinned byDepositNoticeIssueGuardIntegrationTest#issue_secondDraftOnSameTicketSucceedsAndSupersedesTheFirst).Change
DepositNoticeService#update: honoursdepositPercenton a draft (null keeps the stored one); refuses ≤ 0 ("เปอร์เซ็นต์มัดจำต้องมากกว่า 0") and non-whole percents ("เปอร์เซ็นต์มัดจำต้องเป็นจำนวนเต็ม") with 400; issued → 409 viarequireDraft.@DecimalMaxmessage now Thai ("เปอร์เซ็นต์มัดจำต้องไม่เกิน 100").DepositNoticeRepository#update: recomputes deposit/VAT/total from the new percent; latent bug fixed: an update without items (null or []) used to zero the subtotal and amount while keeping the rows — it now sums the stored rows.DepositNoticePage.jsx: enabled whole-number % field on a draft (typed value rounded), live recalculation, sendsdepositPercent; read-only once issued; cleared field shows ฿0 (was a silent 50%).depositNotices.updatemirrors draft/issued/zero/fractional/over-100 rules. Stale "locked" comments rewritten.Test-first (each test commit precedes its fix commit; RED quotes)
DepositNoticeServiceTest#update_onADraft_persistsTheCallersDepositPercent(revised from the old "ignoring caller value" pin)expected: 0.40 but was: 0.50…#update_withNoDepositPercent_keepsTheDraftsStoredPercent…#update_refusesAZeroOrFractionalDepositPercent_andWritesNothingExpecting code to raise a throwableDepositNoticeControllerTest#updateRejectsADepositPercentOver100WithAThaiMessageDepositNoticeIssueGuardIntegrationTest#update_onADraft_changesThePercentAndTheAmountFollows(real DB)items=nulland 0.20 withitems=[]keep the rows and recomputeexpected: 0.40 but was: 0.5000;:261 expected: 100.00 but was: 0.00…#update_onAnIssuedNotice_refusesThePercentEditWith409_rowUnchangedDepositNoticePage.test.jsx"lets sales change the deposit percent on a draft…" (revised from the read-only-label test)Unable to find a label … % มัดจำ;200.00 บาท(billed 198.00)…"shows the deposit percent read-only once the notice is issued"mockApi.depositNotices.test.jsupdate cases (revised)promise resolved … instead of rejectingMutations (implementer 10 + reviewer 5), each killed by a named test: override restored; draft check removed; amount not recomputed; VAT/total stale; zero allowed; whole-number check off; Thai message off; page rounding off;
|| 0.5fallback restored; field always disabled; page sends ×100; mock checks off; stored-rows sum replaced by empty stream.Not covered: decimal input beyond rounding (field is whole-number); concurrent updates (pre-existing DELETE/INSERT race); the creation paths (#1170).
Opus review (2026-10-23): APPROVE WITH NOTES — all notes applied
Medium (fractional % → mismatched document) fixed in
ce592248. Checked OK by reading: remaining invoice (DepositNoticeService:689/736),confirmDepositPaid(TicketService:978), finance view and PDF all read the stored amounts of the ISSUED notice; rounding matchescreateDraft; no caller sends[]meaning "clear"; complements #1173's issue guards.Behaviour changes to note: a draft stored at 0% (legacy route) now gets 400 on save until the % is raised (consistent with "0% = no deposit stage"). Pre-existing, left alone:
requireDraftruns before the owner check on update (same asissue()); item DELETE/INSERT race.Merge-tree clean vs develop, #1168, #1170, #1173.
🤖 Generated with Claude Code
https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2