diff --git a/.claude/agent-memory/reviewer/MEMORY.md b/.claude/agent-memory/reviewer/MEMORY.md index cb91ae7..894345a 100644 --- a/.claude/agent-memory/reviewer/MEMORY.md +++ b/.claude/agent-memory/reviewer/MEMORY.md @@ -18,6 +18,7 @@ - [S114 PE per-hạng-mục BE-financial review](project_s114_pe_multi_ncc_perhangmuc_financial_review.md) — NEEDS-FIX: 6 financial-SUM→IsSelected (0 sót) but DeleteQuote:396 + DeleteDetail:262 KHÔNG re-derive IsWinner → stranded winner → phantom HĐ GiaTri=0. Lesson: derived-invariant re-establish MỌI mutation kể cả DELETE. - [PE per-hạng-mục spec review (winner-truth lần 3)](project_pe_multi_ncc_perhangmuc_specreview.md) — PASS_WITH_ADJ. MUST: CreateContract isSingle→detailsSum (`:76-90`) over-count 1-winner≠cả-gói. Lesson: grep write-path field-NGUỒN (IsSelected) không chỉ field-đọc; cite-range bọc branch-decision. - [S114 PE per-hạng-mục IMPL review (flip lần 3)](project_s114_multi_ncc_perhangmuc_impl_review.md) — NEEDS-FIX. CAUGHT: DeleteQuote (`...Features.cs:371-397`) KHÔNG re-derive IsWinner → xóa quote cuối (nút Xóa FE:2897) VỠ invariant → phantom winner + HĐ giá 0. Lesson: cardinality grep-consumer PHẢI gồm Delete-handler field-nguồn; spec-enum writer KHÔNG đáng tin. SOLID: 5 SUM + non-tautology test + hash ae3788e9. +- [PE negative-quote FE-only spec review (correctness-wire)](project_pe_negative_quote_correctness_review.md) — PASS_WITH_FIXES. R1/R2 no-op verified accurate (CreateContractFromEval:88-100 no-clamp · BE Sum()<=0 :206 + FE :201). MUST: sign-loss — form.thanhTien read DIRECT at POST :2232/:2875+gate :2226; toggle phải re-sign field-chung KHÔNG chỉ flip neg-flag (VndInlineEdit defer-parse-at-own-save không có ở 2 dialog). Consumer inventory COMPLETE 0-break (fmtMoney/toLocaleString neg-safe). Lesson: defer-parse widget→shared-form dialog = sign phải tới field-chung ở MỌI mutation incl toggle. --- @@ -73,6 +74,7 @@ Adversarial pre-commit reviewer SOLUTION_ERP. Read-only verify + live curl prod ## 📅 Recent activity (compressed — full verbatim → `archive/2026-06.md` + `archive/2026-07.md` via `archive/_INDEX.md`) +- **S117 (2026-07-13) PE negative-quote FE-only spec-review (financial-invariant-owner lens) — PASS (0 blocking, 1 confirm + 2 test-notes):** budget-intact VERIFIED — UpsertQuote (`PurchaseEvaluationDetailFeatures.cs:282-366`) writes 0 budget fields; quote ThanhTien flows ONLY into read-only display aggregates (currentProposalTotal/prevSelectedTotal :911-926 + winnerQuoteTotal :1188) — NEVER mutates PeWorkItemBudget. #81 IsWinner + S114 multi-winner SAFE: winner-derive keyed on `IsSelected` boolean, sign-agnostic (negative ThanhTien irrelevant). R1 baked (ContractFeatures.cs:46 GiaTri>=0 untouched=manual path; CreateContractFromEvaluation:88-90 no-clamp=PE-inherit). R2 FE:201/BE:206 BOTH `SUM(ThanhTien where IsSelected)<=0` = IDENTICAL algebraic sum → 0 divergence from negatives. CONFIRM raised: enterable-negatives OPERATIONALIZE net-value driving CEO-escalation gate (winnerQuoteTotal0 submittable" half needs multi-quote seed. **Lesson: 2-tier guard divergence check = compare SUM EXPRESSION not just comparator; derived-flag safety = trace what flag is KEYED-ON (IsSelected≠ThanhTien); "budget intact" true for RECORD but quote-sum still flows to display+governance aggregates.** - **S116 (2026-07-13) outward-email GATE se→ai_infra H-22 WAL restart runtime-verify — PASS (0 over-claim/3 minor):** 6/6 ground-truth re-run MATCH (#5+#6 EXACT: archive-gate 12/12 sub<25.6KB + A7 246/246 pointers; crystallized 382713B/380K-tok/252K headroom). Caveat STRONG+prominent (defers real-persist→S111). Minor: (a)-persist WEAK — `.claude/WAL.md` git-committed 0-drift → survives-restart = git property NOT WAL-mechanism. Lesson: persist-claim on git-tracked WAL → verify tracked+0-drift; mechanism-proof lives at Stop-hook run. → `archive/2026-07.md`. - **S115 (2026-07-13) `/fable-real` spec-review presence-not-age adopt — PASS-WITH-FIXES (0C/2M/5m):** M1 acceptance-grep can't detect its OWN deliverable's miss (narrow regex 1/3 doc-lines → false-PASS; fix bare-token+classify). M2 skip-element on contiguous-prefix cut = correctness-inversion. Lesson: verify EACH regex-alt vs EACH target-line by hand; `parallel()` runtime-builtin robust-under-both = endorse-with-caveat. → `archive/2026-07.md`. - **S101 H19 fable-clone WF2 Lane-A (toggle) — PWC, CONCERN closed @S102:** marker WRITE-ONLY dead artifact — persist-claim cần CẢ reader-side (verify consumer exists, not just producer); reader BƯỚC 0.5b wired S101-cuối. → `archive/2026-07.md`. diff --git a/.claude/agent-memory/reviewer/project_pe_negative_quote_correctness_review.md b/.claude/agent-memory/reviewer/project_pe_negative_quote_correctness_review.md new file mode 100644 index 0000000..d72b5ba --- /dev/null +++ b/.claude/agent-memory/reviewer/project_pe_negative_quote_correctness_review.md @@ -0,0 +1,26 @@ +--- +name: project-pe-negative-quote-correctness-review +description: PE negative-quote FE-only spec review (correctness-wire lens) — PASS_WITH_FIXES; sign-plumbing pitfall replicating deferred-parse pattern into shared-form dialog +metadata: + type: project +--- + +# PE cho phép nhập số ÂM ở quote — correctness-wire lens review (owner anh Kiệt) + +**Verdict: PASS_WITH_FIXES.** Plan FE-only (0 BE, 0 mig) sound; site inventory COMPLETE; R1/R2 no-op verified accurate on disk. 1 must-fix = sign-plumbing pitfall. + +## Verified accurate (spec claims match disk) +- R1 (Contract.GiaTri âm cho phép): `CreateContractFromEvaluationFeatures.cs:88-100` GiaTri = Sum(IsSelected quotes/winner) NO clamp → negative flows through. `ContractFeatures.cs:46` GreaterThanOrEqualTo(0) = separate MANUAL path (unaffected). No-op correct. +- R2 (submit-guard net<=0 giữ): BE `PurchaseEvaluationWorkflowService.cs:206` `selectedThanhTien.Sum() <= 0` + FE `PeDetailTabs.tsx:201` `gia <= 0` — both SUM SIGNED → pure-neg blocks, mixed net>0 passes. No-op correct. +- Submit gate empty→skip: `:2226` parseVnd('')=0, `0!==0` false, `-0!==0` false. Semantics preserved. +- BE accepts neg: UpsertQuoteCommand `PurchaseEvaluationDetailFeatures.cs:272-280` no validator; ThanhTien decimal(18,2) `PurchaseEvaluationConfiguration.cs:131` no CHECK. +- Mirror byte-identical NOW (sha256 fd9a68a5...). 4 test files exist. + +## MUST-FIX (correctness gap in terse plan) +Sign-loss pitfall. `form.thanhTien` is read DIRECTLY as saved value at POST body `:2232`(AddSupplierDialog) + `:2875`(QuoteDialog), submit gate `:2226`, toast `:2239`. VndInlineEdit (`:1014-1021`) works because it keeps abs in LOCAL `text` state + computes signed ONLY at its own "Lưu" onSave — these 2 dialogs have NO separate amount-save button. So spec MUST specify: store SIGNED in form.thanhTien (display Math.abs), and toggle +/- handler MUST re-sign form.thanhTien (not just flip a `neg` flag). If toggle only flips neg + user submits w/o further keystroke → POST sends wrong sign (positive) → neg quote silently saved positive = feature fails. + +## Consumer inventory (Q4 — none break, all neg-safe) +Table cell `:2695/:2699` fmtMoney · giaChaoThau `:1935` toLocaleString · winnerQuoteTotal+threshold `:344/:346/:966` fmtMoney+`<` · submit checklist `:235` gia>0 · budget soft-warning `:1329-1352` uses PRO/CCM suggested NOT quotes. Only blanking helper formatVndInput(`:52` n>0?…:'') confined to 3 inputs; spec correctly leaves global untouched → budget input `:2830` stays >=0. + +## Lesson +Replicating a DEFERRED-PARSE widget (local state + parse-at-own-save) into a SHARED-FORM dialog (value read by outer submit/POST) = sign/magnitude split must reach the shared field at EVERY mutation incl. the toggle, not just the widget's save. Grep every reader of the shared field (POST body + gate + toast) before trusting "reuse pattern X". diff --git a/.claude/workflows/runs/2026-07-13-pe-negative-quote/spec-pe-negative-quote-13-07-2026.md b/.claude/workflows/runs/2026-07-13-pe-negative-quote/spec-pe-negative-quote-13-07-2026.md index 041d647..235010e 100644 --- a/.claude/workflows/runs/2026-07-13-pe-negative-quote/spec-pe-negative-quote-13-07-2026.md +++ b/.claude/workflows/runs/2026-07-13-pe-negative-quote/spec-pe-negative-quote-13-07-2026.md @@ -7,6 +7,8 @@ **Rule chốt (Duy Nguyen xác nhận 11:51):** số âm = khoản trừ/phát-sinh-giảm của **riêng NCC**; **NGÂN SÁCH GIỮ NGUYÊN** — quote âm KHÔNG cộng/trừ vào ngân sách dự án. +**Ngữ cảnh nghiệp-vụ (anh Kiệt S116):** số âm là giá-trị **THẬT** (vd NCC **hoàn tiền** cho mình / phát-sinh-giảm), **KHÔNG phải lỗi** → cấm coi âm là lỗi hay cảnh-báo-đỏ. Nguyên-tắc *"có giá là được"*: có ≥1 giá thật (âm/dương) → gửi được; chỉ **chưa-nhập-gì (selected toàn 0)** mới chặn. ⇒ nền cho R2-revised + R1 (cho HĐ âm). + ## ② Cách implement — **FE + 1 điều-kiện BE** (0 migration · 0 bảng/cột mới) **Đã verify (invest 4-lane hội-tụ):** BE + DB **đã chấp nhận âm sẵn** — `UpsertPurchaseEvaluationQuoteCommand` (`PurchaseEvaluationDetailFeatures.cs:272`) KHÔNG validator, cột `ThanhTien decimal(18,2)` (`PurchaseEvaluationConfiguration.cs:131`) 0 CHECK constraint. Ngân sách (`PeWorkItemBudgets`) chỉ ghi bởi command budget role-gated → **quote âm không đụng ngân sách** (rule thỏa bằng kiến-trúc, 0 code). ⇒ **Sửa FE (gỡ 3 chốt chặn âm) + nới 1 điều-kiện submit-guard BE (R2-revised).**