Repository navigation
fix(sales): a no-deposit quotation puts the deal on the no-deposit payment path - #1173
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…e is compare-and-set; no deposit button on a 0% quotation (RED) Review follow-ups on #1173, written before the fix: - F1 (real Postgres, DealQuotationConfirmOrderIntegrationTest): a confirmed 0% DEAL_DIRECT deal whose approved revision says 50% goes back to REQUIRED (reason names the revision, one more POLICY_CHANGED) and the ticket-route draft bills 0.50; the reverse (50% -> 0% revision) moves to NOT_REQUIRED. Wrong-way-round: a human CREDIT_CUSTOMER and a human NOT_REQUIRED are kept, with no event for the attempt. - F2 (real Postgres, TicketRepositoryIntegrationTest): syncDepositPolicyFromQuotation is a compare-and-set. 0 rows when the policy is not the expected one, when a person set it (deposit_policy_set_by not null), or when payment_status is not CUSTOMER_CONFIRMED. - Mock: the same follow in dealQuotations.approve; a person's NOT_REQUIRED is kept. - F4: canCreateDepositNoticeFromQuotation refuses an accepted 0% quotation; the PR page hides the button. TicketRepository gains syncDepositPolicyFromQuotation as a stub that does today's unconditional write, so the tests compile and fail on their assertions. The fix commit replaces it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n, as a compare-and-set Review follow-ups on #1173 (owner: "do what makes sense for CRM"). Sales/CRM business-logic change, authorised under the sales-flow redesign. - F1: TicketService#followQuotationDepositPolicy is the one place the system writes the policy. 0% -> NOT_REQUIRED, any deposit -> REQUIRED, only while payment_status is CUSTOMER_CONFIRMED and only on a policy no person set (deposit_policy_set_by IS NULL; waiveDeposit always records its actor). It runs at the first confirmation (as before) and now also when a DEAL_DIRECT quotation is approved (DealQuotationService#approve), so a revision that adds a deposit no longer strands the deal and a revision to 0% leaves the deposit path. WAIVED, CREDIT_CUSTOMER and a typed NOT_REQUIRED are never touched. PRICING_REQUEST origin is not wired: R8 allows one accepted quotation per deal, so its policy is fixed at confirmOrder. - F2: TicketRepository#syncDepositPolicyFromQuotation is a compare-and-set on the expected policy, set_by IS NULL and CUSTOMER_CONFIRMED; 0 rows writes no event. The system write now leaves deposit_policy_set_by NULL (the event still names the actor). - F3 (comments only, no test): OrderConfirmationService's "no path syncs" note, the confirmOrderFromDirectQuotation step list, and confirmCustomerForPromotedQuotation's effects list. - F4: canCreateDepositNoticeFromQuotation takes the page's new-engine quotation and refuses an accepted 0% one, so the PR page no longer offers a button the server 409s. - Mock: followQuotationDepositPolicy replaces syncNoDepositPolicy (confirmOrder, promoteToDeal and now dealQuotations.approve); setDepositPolicy records depositPolicySetBy (not served). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Independent Opus review of the follow-up commits d0dc513 + e87ad7e (2026-10-23): APPROVE WITH NOTES, none blocking. Notes 1 and 4 applied as 174d7a9 + 831fb0e. Still DRAFT pending two owner rulings (notes 2, 3) and the pre-merge
Notes needing an owner ruling: Info: a revision to 0% after a non-zero confirmation now syncs to NOT_REQUIRED; a stale 50% draft is then refused by |
…it draft Code-review item 4 on #1170. The ticket route drafted a 0.00 notice for a ไม่รับมัดจำ quotation (the PR route already 409s), and a DEAL_DIRECT deal has only the ticket route, so the deal then stuck (confirmDepositPaid and waiveDeposit 409). createDraftFromQuotation now refuses when the governing quotation's percent is 0, with the PR route's own message, moved to DepositNoticeService.zeroDepositRefusal so both routes share one string. Scoped to the governing row's percent so #1173's deposit-policy guard composes with it. A legacy row is never 0 (its percent is the constant 50). Mock mirrors it with mockZeroDepositRefusal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…it draft Code-review item 4 on #1170. The ticket route drafted a 0.00 notice for a ไม่รับมัดจำ quotation (the PR route already 409s), and a DEAL_DIRECT deal has only the ticket route, so the deal then stuck (confirmDepositPaid and waiveDeposit 409). createDraftFromQuotation now refuses when the governing quotation's percent is 0, with the PR route's own message, moved to DepositNoticeService.zeroDepositRefusal so both routes share one string. Scoped to the governing row's percent so #1173's deposit-policy guard composes with it. A legacy row is never 0 (its percent is the constant 50). Mock mirrors it with mockZeroDepositRefusal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…it payment path (RED) Real-Postgres and mock tests for the owner ruling of 2026-10-22, written before the fix: - confirmOrder / confirmOrderFromDirectQuotation on a 0% (ไม่รับมัดจำ) quotation sets deposit_policy NOT_REQUIRED with a POLICY_CHANGED event naming the quotation, opens the import-request and final-payment gates, and both deposit-draft routes 409. - Wrong-way-round: a 30% quotation leaves REQUIRED; a CREDIT_CUSTOMER deal stays CREDIT_CUSTOMER. - A WAIVED deal 409s on both draft routes on the policy. - issue() refuses a 0-baht DRAFT: stays DRAFT, no document number consumed. PaymentTrackIntegrationTest's issue-on-WAIVED test now drafts while the deal is still REQUIRED and waives afterwards, because createDraft is about to refuse a bypass-policy deal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…yment path Owner ruling 2026-10-22. Sales/CRM payment-track change, authorised under the sales-flow redesign. - TicketService.applyCustomerConfirmation: at the first real confirmation, a confirmed quotation with deposit_percent 0 moves a REQUIRED deal to NOT_REQUIRED (system reason naming the quotation, same POLICY_CHANGED event waiveDeposit writes). CREDIT_CUSTOMER / WAIVED are kept. The import-request and final-payment gates then take the bypass path without a manual waiver. confirmCustomer gains a 4-arg overload; confirmCustomerForPromotedQuotation takes the quotation's number and percent. OrderConfirmationService.confirmOrder passes the accepted new-engine quotation's; DealQuotationService.confirmOrderFromDirectQuotation passes the direct one's. - DepositNoticeService.createDraftFromQuotation: 409 on a bypass policy, for both draft routes. - DepositNoticeService.issue: 409 on a 0-baht (or null-amount) draft before any number is taken. - mockApi.js mirrors all three. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e is compare-and-set; no deposit button on a 0% quotation (RED) Review follow-ups on #1173, written before the fix: - F1 (real Postgres, DealQuotationConfirmOrderIntegrationTest): a confirmed 0% DEAL_DIRECT deal whose approved revision says 50% goes back to REQUIRED (reason names the revision, one more POLICY_CHANGED) and the ticket-route draft bills 0.50; the reverse (50% -> 0% revision) moves to NOT_REQUIRED. Wrong-way-round: a human CREDIT_CUSTOMER and a human NOT_REQUIRED are kept, with no event for the attempt. - F2 (real Postgres, TicketRepositoryIntegrationTest): syncDepositPolicyFromQuotation is a compare-and-set. 0 rows when the policy is not the expected one, when a person set it (deposit_policy_set_by not null), or when payment_status is not CUSTOMER_CONFIRMED. - Mock: the same follow in dealQuotations.approve; a person's NOT_REQUIRED is kept. - F4: canCreateDepositNoticeFromQuotation refuses an accepted 0% quotation; the PR page hides the button. TicketRepository gains syncDepositPolicyFromQuotation as a stub that does today's unconditional write, so the tests compile and fail on their assertions. The fix commit replaces it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n, as a compare-and-set Review follow-ups on #1173 (owner: "do what makes sense for CRM"). Sales/CRM business-logic change, authorised under the sales-flow redesign. - F1: TicketService#followQuotationDepositPolicy is the one place the system writes the policy. 0% -> NOT_REQUIRED, any deposit -> REQUIRED, only while payment_status is CUSTOMER_CONFIRMED and only on a policy no person set (deposit_policy_set_by IS NULL; waiveDeposit always records its actor). It runs at the first confirmation (as before) and now also when a DEAL_DIRECT quotation is approved (DealQuotationService#approve), so a revision that adds a deposit no longer strands the deal and a revision to 0% leaves the deposit path. WAIVED, CREDIT_CUSTOMER and a typed NOT_REQUIRED are never touched. PRICING_REQUEST origin is not wired: R8 allows one accepted quotation per deal, so its policy is fixed at confirmOrder. - F2: TicketRepository#syncDepositPolicyFromQuotation is a compare-and-set on the expected policy, set_by IS NULL and CUSTOMER_CONFIRMED; 0 rows writes no event. The system write now leaves deposit_policy_set_by NULL (the event still names the actor). - F3 (comments only, no test): OrderConfirmationService's "no path syncs" note, the confirmOrderFromDirectQuotation step list, and confirmCustomerForPromotedQuotation's effects list. - F4: canCreateDepositNoticeFromQuotation takes the page's new-engine quotation and refuses an accepted 0% one, so the PR page no longer offers a button the server 409s. - Mock: followQuotationDepositPolicy replaces syncNoDepositPolicy (confirmOrder, promoteToDeal and now dealQuotations.approve); setDepositPolicy records depositPolicySetBy (not served). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e button A null percent is not ไม่รับมัดจำ: the PDF prints it as 30% (#1170), so only an explicit 0% hides the button. Review note 1 on the follow-up; a falsy check would have passed every existing test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…it notice that cannot exist Review note 4 on the follow-up. No test: copy-only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…er rebase Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…policy follows the quotation; pin the whole 0% walk Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
…'s quotation guards on a REQUIRED deal; pin mock blank percent as 30 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXmTJDVga17Rn5az4YBjZ2
831fb0e to
b46d6af
Compare
|
Rebased onto develop (incl. #1170, 50cc572). Ploy rulings applied: 0% skips the deposit path automatically; blank new-engine % = 30 in the policy sync ( 🤖 Generated with Claude Code |
…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
…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
What and why
Owner ruling (Ploy, 2026-10-22): a quotation that says ไม่รับมัดจำ (deposit percent 0, full payment) puts the deal on the no-deposit payment path automatically.
Before this change, the deal's deposit action 409'd on a 0% quotation (#1166's guard, kept), but
sales.ticket.deposit_policystayedREQUIRED. The import-request and final-payment gates stayed closed until someone calledwaiveDepositby hand with a typed reason. A max-effort review also found thatDepositNoticeService.issue()never checked the amount, so a 0-baht DRAFT made through the ticket route (which reads the quotation's 0% per ticket) could be issued as a numbered notice.Contract / business-logic changes (sales/CRM, authorised under the sales-flow redesign)
TicketService.applyCustomerConfirmation, reached fromOrderConfirmationService.confirmOrderandDealQuotationService.confirmOrderFromDirectQuotation) now setsdeposit_policy = NOT_REQUIREDwhen the confirmed quotation'sdeposit_percentis exactly 0 and the policy is stillREQUIRED. It writes a system reasonใบเสนอราคา <number> ระบุไม่รับมัดจำ (ชำระเต็มจำนวน)(deposit_policy_set_by= the confirming user) and the samePOLICY_CHANGEDeventwaiveDepositwrites.CREDIT_CUSTOMERandWAIVEDare never overwritten. It does not go throughwaiveDeposit, whose write-access and reason checks are for people.null -> CUSTOMER_CONFIRMEDadvance. That edge is the same on both paths (PaymentTrack), so every later advance readsNOT_REQUIREDand walks the bypass path (CUSTOMER_CONFIRMED -> AWAITING_FINAL_PAYMENT -> FULLY_PAID).requireImportRequestIssuableandcanConfirmFinalPaymentNowalready admit bypass +CUSTOMER_CONFIRMED.CustomerQuotationconfirmation passes none, so it does not sync.TicketService.confirmCustomer(ticketId, actor)keeps its signature (the HTTP routePOST /api/tickets/{id}/confirm-customeris unchanged). A new overloadconfirmCustomer(ticketId, quotationNumber, quotationDepositPercent, actor)is added.confirmCustomerForPromotedQuotationnow takes the quotation's number and percent; its only caller isconfirmOrderFromDirectQuotation.DepositNoticeService.createDraftFromQuotation(bothPOST /api/tickets/{id}/deposit-notice/draftandPOST /api/pricing-requests/{id}/deposit-notice) answers 409ดีลนี้ไม่ต้องเก็บมัดจำ (นโยบายมัดจำ <policy>) จึงสร้างใบแจ้งรับมัดจำไม่ได้whenDepositPolicy.bypassesDepositNotice(policy). This is checked after the role, owner (fix(sales): owner check on the ticket-route deposit-notice draft #1171), status and lifecycle checks. fix(sales): pricing-request มัดจำ draft accepts the new-engine ACCEPTED quotation of that pricing request #1166's percent guard on the PR route is kept as a second check for deals confirmed before this change. A full-payment DEAL_DIRECT deal therefore cannot draft a deposit notice on either route, which is intended: it has no deposit.issue()refuses a 0-baht notice.POST /api/deposit-notices/{id}/issueanswers 409ใบแจ้งรับมัดจำนี้มียอดมัดจำ 0 บาท จึงออกเอกสารไม่ได้whendepositAmountis null or ≤ 0. This is checked next topaymentTrackReadyand before a document number is taken.canCreateDepositNoticealready returns false on a bypass policy. No change.mockApi.js): the same sync onpricingRequests.confirmOrderanddealQuotations.promoteToDeal, the policy 409 ondepositNotices.createDraftandpricingRequests.createDepositNoticeFromQuotation, and the amount 409 ondepositNotices.issue. Messages match Java.The deal page needs no UI change:
DealDepositPanelalready renders a bypass policy as "ข้ามขั้นตอนนี้ — นโยบายมัดจำคือ … ()".1. Files changed
backend/src/main/java/th/co/glr/hr/ticket/TicketService.javaapplyCustomerConfirmation. New 4-argconfirmCustomeroverload; the 2-arg form delegates to it.confirmCustomerForPromotedQuotationtakes the quotation's number and percent.backend/src/main/java/th/co/glr/hr/orderconfirmation/OrderConfirmationService.javaconfirmOrderpasses the accepted new-engine quotation's number and percent toconfirmCustomer.backend/src/main/java/th/co/glr/hr/dealquotation/DealQuotationService.javaconfirmOrderFromDirectQuotationpasses the direct quotation's number and percent.backend/src/main/java/th/co/glr/hr/deposit/DepositNoticeService.javacreateDraftFromQuotation. 0-baht 409 inissue(). One stale comment updated.frontend/src/api/mockApi.jssyncNoDepositPolicy,requireDepositNoticePath, issue amount check).backend/src/test/java/th/co/glr/hr/dealquotation/DealQuotationOutcomeIntegrationTest.javabackend/src/test/java/th/co/glr/hr/dealquotation/DealQuotationConfirmOrderIntegrationTest.javazeroDepositDirectDraft()helper.backend/src/test/java/th/co/glr/hr/ticket/PaymentTrackIntegrationTest.javadepositNoticeServiceIssue_onWaivedPolicyDeal_…now drafts while the deal is REQUIRED and waives afterwards, becausecreateDraftnow refuses a WAIVED deal. Same assertion:issue()409s on a bypass policy.frontend/src/api/mockApi.depositNotices.test.jsfrontend/src/api/mockApi.promoteToDeal.test.js2. Commands run
3. Tests / build results
The integration tests ran on real Postgres (Testcontainers, Docker up), with
-Dtest.fork.count=1andcleanevery time. Fullmvnw verifywas not run locally; CI gates it.expected: "NOT_REQUIRED" but was: "REQUIRED"×2, andExpecting code to raise a throwable×2 (a ticket-route draft on a WAIVED deal was created; a 0-baht notice was issued and numbered). The two wrong-way-round tests passed, as expected. 4 new mock tests failed on assertions (expected 'REQUIRED' to be 'NOT_REQUIRED'×2,promise resolved … instead of rejecting×2).38e228ae(fix(sales): owner check on the ticket-route deposit-notice draft #1171): DealQuotationOutcomeIntegrationTest 39/0/0, DealQuotationConfirmOrderIntegrationTest 32/0/0, PaymentTrackIntegrationTest 14/0/0, OrderConfirmationIntegrationTest 13/0/0, DepositNoticeServiceTest 48/0/0, TicketServiceTest 210/0/0. Total 356 pass, 0 fail, 0 error.clean, and run green.mockApi.depositNotices+mockApi.promoteToDeal+contract.test.js30/30 after the rebase. Related UI and mock suites (TicketDetailPage, DepositNoticePage, PricingRequestDetailPage, mockApi.dealQuotationOutcome, mockApi.storedImportRequestRollup) 471/471 before the rebase.eslinton the changed files is clean.npm run buildwas not run (no build-affecting change).4. Authz evidence
No authz change. The policy sync runs inside each confirmation path's existing gate (the owner for
confirmOrder;requireEditAccessfor the direct confirmation). The new 409s refuse a state; they do not widen or narrow who may act.5. Known risks
deposit_policy = REQUIRED. That covers every deal confirmed on a 0% quotation since fix(sales): pricing-request มัดจำ draft accepts the new-engine ACCEPTED quotation of that pricing request #1166 went live today, and any earlier one. They still need a one-off backfill (or a manual waiver per deal) to open the import-request and final-payment gates. The fix(sales): pricing-request มัดจำ draft accepts the new-engine ACCEPTED quotation of that pricing request #1166 percent guard and the newissue()amount check stop them from billing a 0-baht deposit in the meantime. Their ticket route can still create a 0-baht DRAFT, but it cannot be issued.issue()accepts it. The sync runs only on the first confirmation (paymentStatus == null), so a later 0% revision does not flip the policy either.deposit_policy_set_by. That is a sales_manager or grant holder when one of them confirms a direct quotation.POST /api/tickets/{id}/confirm-customerroute passes no quotation, so it never syncs. In practice it is reached only after a bridge that has already confirmed the customer (an idempotent no-op), or on legacy ticket-native quotations, which have no 0% option.38e228ae(fix(sales): owner check on the ticket-route deposit-notice draft #1171). The policy check sits after fix(sales): owner check on the ticket-route deposit-notice draft #1171's owner check and the status and lifecycle checks, in Java and in the mock. fix(sales): the deposit notice bills what the customer's latest answered quotation printed (blank new-engine % = 30, legacy = 50, one row for lines and %) #1170 (fix/deposit-null-percent-matches-pdf) editsOrderConfirmationService'ssourceDepositPercentline,DepositNoticeRepository, the same IT class and the mock test file. This branch does not touch those lines, and its new tests sit in different spots.6. Test coverage (test-first)
Tests were committed first (
test(sales): … (RED)) and run red before any production code changed.Real Postgres (Testcontainers, through the real services and repositories)
DealQuotationOutcomeIntegrationTest.confirmOrder_zeroDepositQuotation_setsNotRequired_opensTheImportGate_andBothDraftRoutes409confirmOrder→ policyNOT_REQUIRED, reason names the quotation and ไม่รับมัดจำ, exactly onePOLICY_CHANGEDevent naming it, paymentCUSTOMER_CONFIRMED,requireImportRequestIssuablepasses, ticket route 409 on the policy, PR route 409, zero deposit rowsexpected: "NOT_REQUIRED" but was: "REQUIRED"…confirmOrder_thirtyPercentQuotation_leavesTheDepositPolicyRequired(wrong-way-round)REQUIRED, reason null, noPOLICY_CHANGED, import gate still 409…confirmOrder_zeroDepositQuotationOnACreditCustomerDeal_keepsCreditCustomer(wrong-way-round)CREDIT_CUSTOMER+ 0% → staysCREDIT_CUSTOMER, reason unchanged, only the waiver's event…depositDraft_onAWaivedDeal_bothRoutes409OnThePolicy_andWriteNothingไม่ต้องเก็บมัดจำ, zero rows (30% keeps #1166's percent guard out of the way)Expecting code to raise a throwable(ticket route drafted)…issue_zeroAmountDraft_409_staysDraft_andConsumesNoNumberissue()→ 4090 บาท, row still DRAFT,doc_numbernull,document_sequenceunchanged, payment stillCUSTOMER_CONFIRMEDExpecting code to raise a throwable(issue numbered the 0-baht notice)DealQuotationConfirmOrderIntegrationTest.zeroDepositDirectQuotation_confirmsOntoTheNoDepositPath_andFinalPaymentIsNoLongerBlockedconfirmOrderFromDirectQuotation→NOT_REQUIRED, reason names the quotation, onePOLICY_CHANGED, ticket-route draft 409,confirmFinalPayment(account) walks the bypass path toFULLY_PAIDexpected: "NOT_REQUIRED" but was: "REQUIRED"PaymentTrackIntegrationTest.depositNoticeServiceIssue_onWaivedPolicyDeal_…(revised)issue()'s bypass-policy 409; the draft is now made while REQUIRED and the deal waived afterwardsMutation results (each guard removed in turn; only the new/revised methods run,
cleaneach time):quotationFirstGhost_…_andDepositDraftUnlocks(its 50% direct deal gets flipped, so its deposit draft 409s). Both pin "a deposit-bearing quotation keeps REQUIRED".Mock (
VITE_USE_MOCKS=true, not evidence about Java behaviour)mockApi.depositNotices.test.js›confirmOrder on a 0% quotation sets depositPolicy NOT_REQUIRED, and both draft routes 409expected 'REQUIRED' to be 'NOT_REQUIRED'a 30% quotation leaves REQUIRED; once waived, both draft routes 409 on the policypromise resolved … instead of rejectingmockApi.depositNotices.issue refuses a 0-baht notice›409s, and the draft stays DRAFT with no numberpromise resolved … instead of rejectingmockApi.promoteToDeal.test.js›a 0% (ไม่รับมัดจำ) quotation confirms the deal onto depositPolicy NOT_REQUIREDexpected 'REQUIRED' to be 'NOT_REQUIRED'Deliberately not covered
CustomerQuotationconfirmations: they carry no percent and never sync. No test, because there is no 0% legacy quotation to drive.waiveDeposit: both run under the confirmation's own locks and transaction. Not exercised.🤖 Generated with Claude Code