Repository navigation
fix(sales): pricing-request มัดจำ draft accepts the new-engine ACCEPTED quotation of that pricing request - #1166
Merged
Merged
Conversation
…otation (reproduces the 409) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…engine ACCEPTED quotation The route read only legacy CustomerQuotation rows, so every deal quoted through DealQuotationService.createFromPricingRequest got 409 at ขั้น 10. It now falls back to the pricing request's new-engine ACCEPTED quotation and lets createDraft source items and percent exactly as the ticket-level route does. ISSUED-only still 409s; owner gate unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…est's accepted quotation (reproduces I-1/I-2) Two red tests against d247e6a: - createDepositNoticeFromQuotation_newEngineAcceptedWithAnOpenRevisionDraft_stillDraftsFromTheAcceptedOne (I-2): an open revision draft outranks the accepted quotation by id, so the "latest for this pricing request" lookup 409s although Q1 is accepted. - createDepositNoticeFromQuotation_newEngineAccepted_whenASiblingPricingRequestHasALegacyIssuedQuotation_draftsThisQuotationsLines (I-1): with items = null, createDraft picks a sibling pricing request's legacy ISSUED quotation first, so the draft carries PR-A's lines under PR-B's number. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of THIS pricing request, not the ticket's latest Review round 1 on d247e6a: - I-2: the new-engine lookup took the pricing request's LATEST quotation and filtered for ACCEPTED, so an open revision draft (higher id) hid an accepted Q1 and the route 409'd. New DealQuotationRepository#findAcceptedForPricingRequest selects the ACCEPTED row directly. - I-1: items = null let createDraft source lines ticket-wide, legacy first, so a sibling pricing request's legacy ISSUED quotation supplied the lines under this pricing request's number. Items now come from the accepted quotation itself (DepositNoticeService#itemsFromDealQuotation -> new DepositNoticeRepository#findDealQuotationItems, extracted unchanged from findDealQuotationItemsForDeposit, which now delegates to it). - The percent is still createDraft's ticket-scoped, legacy-first pick: a known limitation shared with the ticket route, stated in the Javadoc. - Javadoc corrections: m-1 (changelog prose dropped), m-2 (percent paragraph names the legacy branch), m-3 (PRICING_REQUEST origin does have revisions), m-4 (engine exclusivity is per pricing request, so legacy-first is a real preference on a mixed ticket). Tests (red on d247e6a, green here): - createDepositNoticeFromQuotation_newEngineAcceptedWithAnOpenRevisionDraft_stillDraftsFromTheAcceptedOne - createDepositNoticeFromQuotation_newEngineAccepted_whenASiblingPricingRequestHasALegacyIssuedQuotation_draftsThisQuotationsLines Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…EPTED quotation (reproduces the mock 409) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mirror the new-engine quotation branch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n picks; pin the ticket-level fallback Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ine; fix the fallback-case comment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m narration Comment-only. The dealQuotations field and setter docs now name the deposit route's new-engine branch as a second user (and that an unwired instance answers 409 there). Drops two narration comments the calls already say, states the 409 rule directly, rewords findDealQuotationItems' circular Javadoc, and reflows one ragged wrap in findDealQuotationDepositPercent's Javadoc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e commit sha from the test comment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n — implementation plan The plan the branch was executed from (subagent-driven). Its "items = null" shape for the new-engine branch was superseded during review by a pricing-request-scoped lookup with items from that quotation; the commits and their tests carry the final shape, the plan carries the reasoning and the coverage list. No test: docs-only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…icing request's quotation (reproduces the sibling's percent) createDepositNoticeFromQuotation_newEngineAccepted_whenASiblingPricingRequestHasALegacyIssuedQuotation_draftsThisQuotationsLinesAndPercent (renamed from ..._draftsThisQuotationsLines): the new-engine quotation carries 30%, the sibling pricing request's legacy ISSUED row carries 70%, and the draft must bill 0.30. Red on a32b02b: 0.7000, because createDraft's ticket-scoped, legacy-first percent lookup reads the sibling's row. No code path writes deposit_percent on a legacy row today, so the test seeds it through jdbc; without the seed the lookup already falls through to this quotation's own percent. The issuedNewOriginQuotation/acceptedNewOriginQuotation/upsertWithValidityDays helpers gain a deposit-percent overload; the existing call shapes are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pted new-engine quotation's own The new-engine drive can now set the quotation's depositPercent before submit; the first new-engine case sets 30 and expects the draft to carry 0.3. Red on the current mock: 0.5, because the route takes payload.depositPercent ?? 0.5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…quotation percent, not the ticket's Cloud review: the new-engine branch took reference and items from this pricing request's ACCEPTED quotation, but the percent still came from createDraft's ticket-scoped, legacy-first lookup, so a sibling pricing request's legacy row carrying a percent would be billed under this quotation's number. - DepositNoticeService#createDraftFromQuotation(ticketId, req, sourceDepositPercent, actor): a non-null source percent is the draft's percent; createDraft keeps its signature and delegates with null (per-ticket lookup, unchanged). - OrderConfirmationService#createDepositNoticeFromQuotation passes the chosen new-engine quotation's own depositPercent. The legacy branch passes null: CustomerQuotationDto carries no percent, so it keeps today's derivation. - mockApi pricingRequests.createDepositNoticeFromQuotation mirrors it: the new-engine branch bills newEngine.depositPercent / 100; the legacy branch and the ticket-level createDraft are unchanged. Test: createDepositNoticeFromQuotation_newEngineAccepted_whenASiblingPricingRequestHasALegacyIssuedQuotation_draftsThisQuotationsLinesAndPercent (red 0.7000, now 0.30) and the mock new-engine case (red 0.5, now 0.3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d; qualify the percent Javadoc The 70% legacy seed now asserts it updated exactly one row, so the test cannot pass vacuously if the seed misses. The createDepositNoticeFromQuotation Javadoc now says the source quotation's percent wins only when it sets one; a null percent still falls back to the per-ticket lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…gine quotation
A 0% ("ไม่รับมัดจำ", full payment) quotation has nothing to bill. The new-engine branch
of createDepositNoticeFromQuotation passed depositPercent 0 straight into the draft,
producing a 0-baht notice that nothing downstream refuses to issue (issue() reads the
ticket's deposit_policy, which no path syncs from the quotation). Now 409 with the
quotation number. Mock mirrored.
Test-first: DealQuotationOutcomeIntegrationTest
.createDepositNoticeFromQuotation_newEngineAcceptedZeroDeposit_409AndNoDraft (real DB,
red: "Expecting code to raise a throwable" → green, class 33/33) and
mockApi.depositNotices.test.js "rejects a 0% (full-payment) new-engine quotation with
409 and creates no draft" (red: promise resolved with a draft → green, file 9/9).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the handoff CLAUDE.md retired handoff docs in 2026-07; this was the first file under docs/superpowers and already described the superseded first design. No test: docs-only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ine deposit test fixtures mockDepositNoticeItemsFromDealQuotation built the whole deal-quotation DTO to read six fields that come straight off the stored row (mockDealQuotationItemDto only adds ceo* / priceChangedFromCeo on top of the spread). The test drops a redundant id after a spread, two conditional spreads the mock already gates on != null, and folds five single-field expects into one toMatchObject. No behaviour or message change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…of each reason Comment-only. The "accepted, not latest" reasoning now lives once, on DealQuotationRepository#findAcceptedForPricingRequest; the resolved-columns paragraph moves onto findDealQuotationItems, the method that actually reads them; createDraft's inline comment stops restating createDraftFromQuotation's Javadoc; one misplaced clause in createDepositNoticeFromQuotation's Javadoc moves next to the sentence it qualifies. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Oct 22, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
QA finding 2026-10-18 on the develop stack: a deal quoted through the new engine (
POST /pricing-requests/{id}/deal-quotations, the only stage-advancing quotation path since #1153) reaches ขั้น 10 but cannot get a ใบแจ้งยอดเงินรับมัดจำ from the UI. BothDealDepositPanelandDepositNoticePagecallPOST /api/pricing-requests/{id}/deposit-notice, which read only LEGACYCustomerQuotationrows and answered 409 "ยังไม่มีใบเสนอราคาที่ลูกค้ายอมรับสำหรับคำขอราคานี้" for every such deal.What changes (sales/CRM: API behaviour change, stated explicitly)
Contract changes on
POST /api/pricing-requests/{id}/deposit-notice(OrderConfirmationService#createDepositNoticeFromQuotation):PRICING_REQUEST-origin (new-engine) quotation. Reference = that quotation's number; items = that quotation'ssales.quotation_itemlines; percent = that quotation's owndeposit_percent(null → the existing per-ticket lookup, then 0.50).ใบเสนอราคา <no.> ระบุไม่มีมัดจำ (ชำระเต็มจำนวน) จึงออกใบแจ้งยอดเงินรับมัดจำไม่ได้instead of drafting a 0-baht notice.POST /api/tickets/{id}/deposit-notice/draftkeep their previous behaviour (same per-ticket percent derivation, no 0% guard).Files changed
backend/.../dealquotation/DealQuotationRepository.java— newfindAcceptedForPricingRequest(ACCEPTED, newest by id; "latest by id" is wrong because an open revision DRAFT can outrank the accepted row); corrected the false "no revision chain" Javadoc onfindLatestForPricingRequest.backend/.../deposit/DepositNoticeRepository.java—findDealQuotationItems(quotationId)extracted fromfindDealQuotationItemsForDeposit(same SQL/mapper);findDealQuotationDepositPercentJavadoc corrected (engines are exclusive per pricing request, not per ticket).backend/.../deposit/DepositNoticeService.java—createDraftFromQuotation(ticketId, req, Integer sourceDepositPercent, actor);createDraftdelegates with null;itemsFromDealQuotation(quotationId).backend/.../orderconfirmation/OrderConfirmationService.java— the new-engine branch + 0% guard described above.backend/src/test/.../DealQuotationOutcomeIntegrationTest.java— 6 new cases (below) + fixture overloads.frontend/src/api/mockApi.js— mirrors the PR route's new-engine branch, the 0% refusal, afindDealQuotationItemsmapper, and the ticket-level new-engine item fallback.frontend/src/api/mockApi.depositNotices.test.js— 4 new cases (below), new-engine drive helpers.Commands run
cd backend && ./mvnw -B -q -Dtest=DealQuotationOutcomeIntegrationTest -Dtest.fork.count=1 test(real Postgres via Testcontainers)cd frontend && npx vitest run src/api/mockApi.depositNotices.test.js src/api/contract.test.js && npx eslint src/api/mockApi.js src/api/mockApi.depositNotices.test.jsmvnw verify/npm test/npm run build: CI only (targeted runs locally)./code-review maxrun; findings carried into Known risks.สร้างใบแจ้งยอดเงินรับมัดจำ→ draft with reference QT-2026-0036-1, 30% (the quotation's own),ออกเอกสาร→ GLRD69004 ISSUED, no 4xx.Tests / build results
DealQuotationOutcomeIntegrationTest: 33/33 on 59f623e (integration tests RAN, real Postgres).auditfailure on 1d390bf was an npm advisory, not this change; develop's vite bump is merged in at b26e95c).Authz evidence
No authz change. The PR route still runs
requireOwnerbefore any lookup;…_nonOwnerSales_cannotReach(real DB) pins 403 + no row for a non-owningsalesuser on the new-engine case. Note (pre-existing, not touched here, from the max review): the ticket-level draft routePOST /api/tickets/{id}/deposit-notice/drafthas no owner/viewer check — anysalesuser can draft on another rep's deal. Unverified by any test; needs its own fix.Known risks (from
/code-review max, all confirmed; none fixed here)deposit_percenton an ACCEPTED new-engine quotation bills 0.50 while the PDF printed 30% (renderer null→30). New on this route (develop 409'd), pre-existing on the ticket route. Needs an owner ruling: shared null→30 constant, orsubmit()refusing a null percent.findDealQuotationItemscopies rawqty/final_unit_price: EN per-sqm rows (pieces × per-sqm price), EN/USD rows (THB + 7% VAT hardcoded), NET-mode double rounding, and DEAL_DIRECT ADJUSTMENT rows (qty −1 → PUT 400) — all pre-existing on the ticket route, now reachable through this route for ACCEPTED PR-origin quotations.recordOutcomehas no open-revision guard (ACCEPTED Q1 + ISSUED Q2 possible);findForPricingRequeststill picks newest-by-id and hides Q1's outcome controls once a revision exists — pre-existing since 84ebf7e (develop only).OrderConfirmationServicetest instances never callwireDealQuotationRepository; an unwired instance answers the original 409 on the new-engine branch.payload.depositPercent ?? 0.5when the chosen quotation has no percent (Java ignores the payload), and its ticket-route 0% check keys on the cross-array picker.Test coverage
Backend (
DealQuotationOutcomeIntegrationTest, real Postgres):…_newEngineAcceptedQuotation_createsTheSameDraftTheTicketRouteWould— reference = accepted number, items/percent equal the ticket route's, one DEPOSIT_NOTICE_DRAFTED_FROM_QUOTATION event. Red before fix (409).…_newEngineAcceptedWithAnOpenRevisionDraft_stillDraftsFromTheAcceptedOne— Q2 DRAFT outranks Q1 by id; draft references Q1. Red before fix.…_newEngineAccepted_whenASiblingPricingRequestHasALegacyIssuedQuotation_draftsThisQuotationsLinesAndPercent— sibling legacy row (forgeddeposit_percent = 70) supplies neither lines nor percent; draft at 0.30. Red before fix (sibling's percent).…_newEngineAcceptedZeroDeposit_409AndNoDraft— 0% + fullPaymentTerm → 409, no row. Red before fix.…_newEngineIssuedNotAccepted_still409and…_newEngine_nonOwnerSales_cannotReach— regression pins, green on develop too (ISSUED-only already 409'd;requireOwnerfires before any lookup).Mock (
mockApi.depositNotices.test.js): reference/items/percent/discount label from the accepted new-engine quotation (red before: 409); ticket-level createDraft reads the same new-engine lines (red before: no items); ISSUED-only still 409 (pin); 0% → 409 and no draft (red before: drafted).Deliberately NOT covered: legacy-ACCEPTED + v2 ISSUED sibling carrying a percent (pre-existing behaviour, risk 2); EN/USD/NET-rounding/ADJUSTMENT item shapes (risk 3, pre-existing); the ticket-level owner gap (pre-existing, needs its own authz PR); UI e2e (verified by the hand walk above, not by
e2e-real).🤖 Generated with Claude Code