Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
907bd56
feat(sales): deal page offers the next party to quote after an accept…
waritctd Oct 22, 2026
ec817a1
refactor(sales): trim duplicates left by the quote-next-party change
waritctd Oct 22, 2026
638b6e5
chore(deps-dev): bump vitest to 4.1.12 so the audit gate passes
waritctd Oct 22, 2026
3e0e2f0
test(pricing-requests): give the three-save modal test a 20 s timeout
waritctd Oct 22, 2026
1e4ef01
test(sales): ticket-route deposit draft refuses a non-owning sales rep
waritctd Oct 22, 2026
416dc3b
fix(sales): owner check on the ticket-route deposit-notice draft
waritctd Oct 22, 2026
4bec63d
test(sales): ticket-route deposit draft checks the owner before the d…
waritctd Oct 22, 2026
93a7ed4
fix(sales): check the deposit-draft owner before the deal status
waritctd Oct 22, 2026
38e228a
Merge pull request #1171 from waritctd/fix/deposit-draft-ticket-route…
waritctd Oct 22, 2026
52531d2
refactor(shell): rename importDealRedirectFor to dealRedirectFor
waritctd Oct 22, 2026
6f13315
test(shell): pin account's /tickets deal links landing on /finance/de…
waritctd Oct 22, 2026
0f9fe6e
fix(shell): account's deal links land on การเงิน under the self-servi…
waritctd Oct 22, 2026
fe5138d
Merge pull request #1174 from waritctd/fix/account-deal-link-redirect…
waritctd Oct 23, 2026
523ae29
chore(deps): bump zod + override undici to clear high-severity adviso…
YXmaze Oct 26, 2026
6515aa3
Merge pull request #1177 from waritctd/chore/audit-fix-undici-zod
YXmaze Oct 26, 2026
7c4234d
feat(catalog): refresh import profiles for the 2026/2027 batch (CDE, …
YXmaze Oct 21, 2026
e530b31
fix(db): use anonymous dollar-quote in V200 so Flyway doesn't parse t…
YXmaze Oct 26, 2026
0668435
feat(pricing): CEO sets อัตรากำไร per product line, not one combined …
YXmaze Oct 20, 2026
f6267e0
feat(pricing): per-line margin on new-form NET + realized-margin read…
YXmaze Oct 21, 2026
c40f4b7
feat(pricing): make the new-form NET per-line margin a live input (no…
YXmaze Oct 21, 2026
0d6f342
feat(pricing): per-line margin also drives ราคาพิเศษ (SPECIAL_SQM), s…
YXmaze Oct 26, 2026
5b502c4
fix(pricing): address #1164 ultrareview findings
YXmaze Oct 26, 2026
82e130a
fix(ui): stop ticket sticky-chrome jitter (condense off a sentinel, n…
YXmaze Oct 19, 2026
03e3145
fix(ui): smooth the sticky-chrome collapse (hysteresis + gate + one a…
YXmaze Oct 20, 2026
94edfec
fix(ui): address #1162 review nits on the deal-header sticky chrome
YXmaze Oct 26, 2026
3ddba23
feat(pricing): Sales-entered thickness flows to the catalogue and the…
YXmaze Oct 16, 2026
1935290
test(pricing): make the uncostable fixtures free-text, not catalog-nu…
YXmaze Oct 26, 2026
7acabf5
test(pricing): seed the RFQ factory row the free-text uncostable line…
YXmaze Oct 26, 2026
9d3e37c
docs(repo-map): domain-level Obsidian/agent repo map
YXmaze Oct 16, 2026
09c415e
docs(repo-map): gitignore Obsidian workspace state (.obsidian/)
YXmaze Oct 19, 2026
fd857d4
docs(repo-map): fix 4 review nits + re-sync to origin/develop
YXmaze Oct 26, 2026
fbb30f8
Merge pull request #1151 from waritctd/docs/repo-map
YXmaze Oct 27, 2026
6163b15
Merge pull request #1162 from waritctd/fix/ticket-sticky-chrome-jitter
YXmaze Oct 27, 2026
a2735b1
Merge pull request #1164 from waritctd/feat/ceo-per-line-margin
YXmaze Oct 27, 2026
1a5ee52
Merge pull request #1165 from waritctd/feat/import-profiles-2026-batch
YXmaze Oct 27, 2026
254372d
Merge pull request #1150 from waritctd/feat/sales-thickness-to-catalog
YXmaze Oct 27, 2026
7f299e1
fix(pricing): a line with no resolvable sqm is set aside as uncostabl…
YXmaze Oct 11, 2026
9d868fd
test(pricing): aggregation IT uses linearMPerUnit, not the now-set-as…
YXmaze Oct 26, 2026
c6f065c
Merge pull request #1127 from waritctd/fix/sqm-missing-uncostable
YXmaze Oct 27, 2026
3e877ae
Merge origin/develop (c6f065cf) into claude/stage-skip-develop-debug-…
waritctd Oct 27, 2026
8d068d0
test(sales): pin that a null-percent quotation's deposit notice bills…
waritctd Oct 22, 2026
32f4e87
fix(sales): blank deposit percent on a new-engine quotation bills the…
waritctd Oct 22, 2026
6b5a697
test(sales): pin that a live legacy quotation bills its PDF's 50%, no…
waritctd Oct 22, 2026
a33a80e
fix(sales): a live legacy quotation bills the 50% its PDF prints, nev…
waritctd Oct 22, 2026
c8807ee
docs(sales): firstOrNull Javadoc no longer describes a null the queri…
waritctd Oct 22, 2026
0762fd2
test(sales): fold the null-percent deposit cases into the existing ro…
waritctd Oct 23, 2026
16ca96f
test(sales): pin that the ticket-route deposit notice follows the lat…
waritctd Oct 23, 2026
c0d840c
fix(sales): the deposit notice follows the latest quotation the custo…
waritctd Oct 23, 2026
b26f09a
test(sales): pin that a ticket-level legacy quotation never governs t…
waritctd Oct 23, 2026
3fb6385
docs(sales): name the ACCEPTED-then-DEAL_DIRECT case where the deal p…
waritctd Oct 23, 2026
2b167d6
test(sales): a 0% governing quotation is refused on the ticket route too
waritctd Oct 23, 2026
f56d4a0
fix(sales): refuse a 0% governing quotation on the ticket-route depos…
waritctd Oct 23, 2026
4e8187c
test(sales): approved_price lines do not take the governing quotation…
waritctd Oct 23, 2026
22d4488
fix(sales): default the deposit reference only when the lines came fr…
waritctd Oct 23, 2026
8691a87
test(sales): governing-quotation fixtures that only the ranking, not …
waritctd Oct 23, 2026
bd6255c
test(sales): mock deposit fixture at 40% and a ticket-route set-perce…
waritctd Oct 23, 2026
ef56053
test(sales): the PR page's deposit button shows no "% มัดจำ" input an…
waritctd Oct 23, 2026
e8dd542
fix(sales): delete the dead "% มัดจำ" input on the pricing-request page
waritctd Oct 23, 2026
9a586ee
docs(sales): deposit-percent comments state the rule the code follows
waritctd Oct 23, 2026
be22496
test(sales): pin that caller-supplied lines leave the mock deposit re…
waritctd Oct 27, 2026
3bff49a
test(sales): mock deposit tie-break across both quotation arrays (RED)
waritctd Oct 27, 2026
7a31851
fix(sales): one mock quotation id sequence, so the deposit pick's "la…
waritctd Oct 27, 2026
c2fc558
test(sales): the Java deposit-constant guard ignores commented-out de…
waritctd Oct 27, 2026
e1bc8ab
test(sales): a PRICING_REQUEST quotation is never live as APPROVED in…
waritctd Oct 27, 2026
01ced6b
fix(sales): APPROVED counts as live only on a DEAL_DIRECT quotation i…
waritctd Oct 27, 2026
6becc08
docs(sales): correct three deposit comments and move a misplaced one
waritctd Oct 27, 2026
49f1762
test(sales): delete deposit tests the repository and parameterised te…
waritctd Oct 27, 2026
eb15c14
docs(sales): name the shared mock quotation id counter in a pricing-r…
waritctd Oct 27, 2026
8c96888
fix(sales): pick the next party when the quotation is sent, so the ro…
waritctd Oct 27, 2026
91b43d1
docs(sales): the autofill test Javadoc no longer claims the repositor…
waritctd Oct 27, 2026
708d983
fix(sales): a quotation to redo gets เสนอใบราคา<party> on the deal pa…
waritctd Oct 27, 2026
ddac39f
refactor(sales): clean-review of the route choice and the quotation map
waritctd Oct 27, 2026
32db345
test(sales): tighten one route-choice assertion, name the register li…
waritctd Oct 27, 2026
9b428ac
fix(sales): a cancelled revision draft no longer hides its quotation;…
waritctd Oct 27, 2026
2280d34
test(sales): pin every non-live (origin, status) pair and newer-ACCEP…
waritctd Oct 27, 2026
407e9b1
docs(sales): move the DEAL_DIRECT-revision case to findGoverningQuota…
waritctd Oct 27, 2026
97cd8d1
fix(sales): mock deposit reference is null when the caller supplies t…
waritctd Oct 27, 2026
50cc572
Merge pull request #1170 from waritctd/fix/deposit-null-percent-match…
waritctd Oct 27, 2026
0f18e86
test(sales): a no-deposit quotation must put the deal on the no-depos…
waritctd Oct 22, 2026
0fc315e
fix(sales): a no-deposit quotation puts the deal on the no-deposit pa…
waritctd Oct 22, 2026
f2a32f3
test(sales): deposit policy follows an approved revision; system writ…
waritctd Oct 22, 2026
b5bb5fc
fix(sales): the system's deposit policy follows the approved quotatio…
waritctd Oct 22, 2026
bc616dd
test(sales): pin that a blank deposit percent keeps the deposit-notic…
waritctd Oct 23, 2026
77ed226
fix(sales): a 0% deal's confirmation line no longer points at a depos…
waritctd Oct 23, 2026
e0c3a30
test(sales): reconcile #1170 deposit guards with the policy guard aft…
waritctd Oct 27, 2026
dd533cc
fix(sales): a blank new-engine deposit percent counts as 30 when the …
waritctd Oct 27, 2026
cacff6d
fix(sales): the 15 code-review findings vs main — nothing quoted afte…
waritctd Oct 27, 2026
81acd9e
Merge origin/develop (50cc572b) into claude/stage-skip-develop-debug-…
waritctd Oct 27, 2026
b46d6af
test(sales): pin each deposit route's own 0% guard exactly, incl. #11…
waritctd Oct 27, 2026
4b6d48f
fix(sales): review of cacff6d7 — route choice survives a failed refet…
waritctd Oct 27, 2026
078814c
Merge pull request #1168 from waritctd/claude/stage-skip-develop-debu…
waritctd Oct 27, 2026
124c46c
Merge pull request #1173 from waritctd/fix/zero-deposit-quotation-set…
waritctd Oct 27, 2026
117bc03
test(sales): pin that sales may change the deposit % on a draft notic…
waritctd Oct 23, 2026
9b415d5
feat(sales): sales may change the deposit % on a draft notice; the am…
waritctd Oct 23, 2026
119bf02
test(sales): pin that a deposit-notice update without items keeps the…
waritctd Oct 23, 2026
137f703
fix(sales): a deposit-notice update without items keeps the stored li…
waritctd Oct 23, 2026
180c29a
test(sales): pin that a deposit % must be a whole number, over 100% g…
waritctd Oct 23, 2026
1dafe7b
fix(sales): a deposit % must be a whole number; over 100% gets a Thai…
waritctd Oct 23, 2026
538b5f3
refactor(sales): carry an optional updatedAt stamp on the deposit-not…
waritctd Oct 23, 2026
112e89e
test(sales): pin the deposit-notice issue re-check, stale-save and is…
waritctd Oct 23, 2026
a3669ca
fix(sales): issue re-checks the stored % and amount; a stale or post-…
waritctd Oct 23, 2026
ac95268
test(sales): pin one deposit notice per deal on both create routes an…
waritctd Oct 23, 2026
d7fb0c6
fix(sales): one deposit notice per deal on both create routes; fold t…
waritctd Oct 23, 2026
6832ee1
test(sales): pin the deposit editor's payload and money, the mock's c…
waritctd Oct 23, 2026
561ff39
fix(sales): the deposit editor saves and shows what the backend store…
waritctd Oct 23, 2026
f28cbe4
chore(sales): move the one-notice mock helper and two new tests away …
waritctd Oct 23, 2026
9e0dfdf
chore(sales): qualify ApiException in DepositNoticeRepository so its …
waritctd Oct 23, 2026
eb628c6
chore(release): render.yaml → backend image v2026-10-28 (develop → ma…
waritctd Oct 27, 2026
4d92a57
fix(sales): issuing is refused while another deposit notice on the de…
waritctd Oct 27, 2026
9512462
fix(deposit): panel passes quotation+notices to the create gate, issu…
waritctd Oct 27, 2026
a8512a6
test(deposit): revise tests pinned to the pre-ruling re-issue superse…
waritctd Oct 27, 2026
a143f8a
Merge pull request #1176 from waritctd/feat/deposit-percent-editable-…
waritctd Oct 27, 2026
2dcdced
Merge origin/develop (a143f8a2, #1176) into release/2026-10-28-develo…
waritctd Oct 27, 2026
73a6db4
Merge origin/main (5fef9de1, #1149 monthly attendance email) into rel…
waritctd Oct 27, 2026
0de8602
fix(pricing): no catalog write from typed thickness; missing sqm on a…
waritctd Oct 27, 2026
caeb884
fix(pricing): refuse a manual cost override on a line with no piece c…
waritctd Oct 27, 2026
03c4230
Merge pull request #1180 from waritctd/fix/release-2026-10-28-findings
waritctd Oct 28, 2026
30240cf
Merge origin/develop (03c42305, #1180 release review fixes) into rele…
waritctd Oct 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ private DealQuotationRenderAdapter() {}

private static final ZoneId BANGKOK = ZoneId.of("Asia/Bangkok");

/** The deposit percent the PDF prints when a quotation's {@code deposit_percent} is NULL. The
* deposit notice bills the same default (owner ruling 2026-10-22), so the customer is billed
* what the accepted PDF promised. */
public static final int DEFAULT_DEPOSIT_PERCENT = 30;

/** The percent a new-engine quotation bills and prints: a blank one means {@link #DEFAULT_DEPOSIT_PERCENT}. */
public static int effectiveDepositPercent(Integer stored) {
return stored != null ? stored : DEFAULT_DEPOSIT_PERCENT;
}

/** Overload for callers with no configured bank block — the English document then prints the
* proforma-invoice line instead. Kept so tests and any hand-wired caller need not thread a
* config value they do not care about. */
Expand Down Expand Up @@ -635,7 +645,7 @@ private static String noReturnLine(int number) {
* lead time (dropped entirely, not blanked, when nothing on the document carries one — same
* {@link #dropLeadTimeLineAndRenumber} the tile set uses), and the no-exchange-or-return rule. */
private static List<String> nonTileRemarkLines(DealQuotationDto quotation) {
int depositPct = quotation.depositPercent() != null ? quotation.depositPercent() : 30;
int depositPct = quotation.depositPercent() != null ? quotation.depositPercent() : DEFAULT_DEPOSIT_PERCENT;
String remainderText = "CREDIT".equals(quotation.remainderMode())
? "เครดิต " + (quotation.creditDays() != null ? quotation.creditDays() : 0) + " วัน"
: "ขอรับก่อนส่งมอบสินค้าหรือเมื่อส่งมอบสินค้า";
Expand All @@ -652,7 +662,7 @@ private static List<String> nonTileRemarkLines(DealQuotationDto quotation) {

private static List<String> remarkLines(DealQuotationDto quotation) {
LocalDate offerDate = quotation.offerDate() != null ? quotation.offerDate() : LocalDate.now(BANGKOK);
int depositPct = quotation.depositPercent() != null ? quotation.depositPercent() : 30;
int depositPct = quotation.depositPercent() != null ? quotation.depositPercent() : DEFAULT_DEPOSIT_PERCENT;
String remainderText = "CREDIT".equals(quotation.remainderMode())
? "เครดิต " + (quotation.creditDays() != null ? quotation.creditDays() : 0) + " วัน"
: "ขอรับก่อนส่งมอบสินค้าหรือเมื่อส่งมอบสินค้า";
Expand Down Expand Up @@ -902,7 +912,7 @@ private static String fullPaymentTermEnglishText(String code) {

private static List<String> englishRemarkLines(DealQuotationDto quotation, List<String> bankBlockLines) {
LocalDate offerDate = quotation.offerDate() != null ? quotation.offerDate() : LocalDate.now(BANGKOK);
int depositPct = quotation.depositPercent() != null ? quotation.depositPercent() : 30;
int depositPct = quotation.depositPercent() != null ? quotation.depositPercent() : DEFAULT_DEPOSIT_PERCENT;
// Wording-scan fix 7 (2026-09-17): singular "day credit" at exactly 1 day.
String remainderText;
if ("CREDIT".equals(quotation.remainderMode())) {
Expand Down Expand Up @@ -1029,7 +1039,7 @@ private static String englishNonTileLeadTimeLine(List<DealQuotationItemDto> item

/** The English twin of {@link #nonTileRemarkLines}. */
private static List<String> englishNonTileRemarkLines(DealQuotationDto quotation, List<String> bankBlockLines) {
int depositPct = quotation.depositPercent() != null ? quotation.depositPercent() : 30;
int depositPct = quotation.depositPercent() != null ? quotation.depositPercent() : DEFAULT_DEPOSIT_PERCENT;
// Wording-scan fix 7 (2026-09-17): singular "day credit" at exactly 1 day.
String remainderText;
if ("CREDIT".equals(quotation.remainderMode())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1711,6 +1711,16 @@ public DealQuotationDto approve(long id, ApproveRequest request, UserPrincipal a
quotations.supersede(ancestorId);
ancestorId = ancestor.parentQuotationId();
}
// #1173 review F1: on a confirmed deal the system-owned deposit policy follows the newly
// approved quotation, so a revision that adds or drops the deposit moves the deal with it.
// A no-op before confirmation (confirmOrderFromDirectQuotation applies the confirmed
// quotation's percent then). Null-tolerant for hand-wired tests, like
// #advanceDealToRecipientStage. PRICING_REQUEST origin never reaches this: R8 allows one
// accepted quotation per deal, and its percent is applied at OrderConfirmationService#confirmOrder.
if (ticketService != null) {
ticketService.followQuotationDepositPolicy(approved.ticketId(), approved.number(),
DealQuotationRenderAdapter.effectiveDepositPercent(approved.depositPercent()), actor);
}
tickets.addEventWithDocument(approved.ticketId(), actor.id(), actor.name(), TicketEventKind.QUOTATION_ISSUED,
null, null, "อนุมัติใบเสนอราคา " + approved.number(), RelatedDocumentType.QUOTATION, id);
String message = "ใบเสนอราคา " + approved.number() + " ได้รับอนุมัติแล้ว";
Expand Down Expand Up @@ -2107,9 +2117,10 @@ private String outcomeLabel(String outcome) {
* <li>{@code sales.ticket_item} replaced by the quotation's own goods lines (see
* {@link #ticketItemsFromQuotation}) — the delivery machinery's quantity source; the
* quotation is the agreed order, so it replaces any preliminary lines the deal carried;</li>
* <li>{@code TicketService#confirmCustomerForPromotedQuotation} — the unmodified
* confirmCustomer body: {@code payment_status = CUSTOMER_CONFIRMED}, stage auto-advanced to
* {@code ORDER_RECEIVED}.</li>
* <li>{@code TicketService#confirmCustomerForPromotedQuotation} — confirmCustomer's shared
* body: {@code payment_status = CUSTOMER_CONFIRMED}, the deposit policy following this
* quotation's percent ({@code TicketService#followQuotationDepositPolicy}), stage
* auto-advanced to {@code ORDER_RECEIVED}.</li>
* </ol>
*
* <p><strong>Order of checks</strong>: quotation exists on this engine (404 — a LEGACY
Expand Down Expand Up @@ -2178,7 +2189,8 @@ public PromoteToDealResultDto confirmOrderFromDirectQuotation(long quotationId,
// or stock reservation exists — both key on quotation_issued or later), and the only other
// reference to a ticket_item, pricing_request_item.source_ticket_item_id, is ON DELETE SET NULL.
tickets.replaceItems(ticketId, ticketItemsFromQuotation(fresh));
ticketService.confirmCustomerForPromotedQuotation(ticketId, actor);
ticketService.confirmCustomerForPromotedQuotation(ticketId, fresh.number(),
DealQuotationRenderAdapter.effectiveDepositPercent(fresh.depositPercent()), actor);
return promotionResult(ticketId, quotationId);
}

Expand All @@ -2193,7 +2205,7 @@ private PromoteToDealResultDto promotionResult(long ticketId, long quotationId)
* qty_sqm, unit_basis, raw_unit. NO pricing field is copied (proposed/approved/raw price stay
* null) — those columns carry the legacy ticket-native approval history, and a direct
* quotation's typed price was never "approved" on this ticket in that sense; money documents
* read the quotation itself ({@code DepositNoticeRepository#findDealQuotationItemsForDeposit}).
* read the quotation itself ({@code DepositNoticeRepository#findDealQuotationItems}).
*
* <p>Only goods lines: an {@code ADJUSTMENT} row (a negative discount/surcharge line, quantity −1)
* is money, not something to deliver, and a non-positive quantity would violate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,11 @@ ResponseEntity<byte[]> preview(@PathVariable long docId, HttpSession session) {

// Issue (assign doc number + transition ticket)
@PostMapping("/deposit-notices/{docId}/issue")
Map<String, DepositNoticeDto> issue(@PathVariable long docId, HttpSession session) {
Map<String, DepositNoticeDto> issue(@PathVariable long docId,
@RequestBody(required = false) DepositNoticeIssueRequest body,
HttpSession session) {
UserPrincipal user = sessions.requireUser(session);
return Map.of("depositNotice", service.issue(docId, user));
return Map.of("depositNotice", service.issue(docId, body == null ? null : body.expectedUpdatedAt(), user));
}

// File download
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.List;

public record DepositNoticeDraftRequest(
Expand All @@ -13,7 +14,17 @@ public record DepositNoticeDraftRequest(
@Size(max = 255) String customerAddress,
@Size(max = 255) String projectName,
@Size(max = 255) String reference,
@DecimalMin("0") @DecimalMax("1") BigDecimal depositPercent,
@DecimalMin(value = "0", inclusive = false, message = "เปอร์เซ็นต์มัดจำต้องมากกว่า 0")
@DecimalMax(value = "1", message = "เปอร์เซ็นต์มัดจำต้องไม่เกิน 100") BigDecimal depositPercent,
List<String> notes,
@Valid List<DepositNoticeItemRequest> items
) {}
@Valid List<DepositNoticeItemRequest> items,
// The draft's updatedAt as this client last read it. null skips the stale-save check (older clients).
OffsetDateTime expectedUpdatedAt
) {
/** Without a stamp: creation and the server-side callers. */
public DepositNoticeDraftRequest(String customerName, String customerTaxId, String customerAddress,
String projectName, String reference, BigDecimal depositPercent,
List<String> notes, List<DepositNoticeItemRequest> items) {
this(customerName, customerTaxId, customerAddress, projectName, reference, depositPercent, notes, items, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package th.co.glr.hr.deposit;

import java.time.OffsetDateTime;

/** Optional body of the issue call: the draft's updatedAt as the caller last read it (null skips the check). */
public record DepositNoticeIssueRequest(OffsetDateTime expectedUpdatedAt) {}
Loading
Loading