From 7676718cbe04c7f2ec4f76c4af952e98f670e616 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Mon, 13 Jul 2026 16:11:50 +0700 Subject: [PATCH] wal: flush 20260713T1611 --- .../runs/2026-07-13-pe-negative-quote/run.md | 2 +- .../spec-pe-negative-quote-13-07-2026.md | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.claude/workflows/runs/2026-07-13-pe-negative-quote/run.md b/.claude/workflows/runs/2026-07-13-pe-negative-quote/run.md index 9f3ae3a..27840f8 100644 --- a/.claude/workflows/runs/2026-07-13-pe-negative-quote/run.md +++ b/.claude/workflows/runs/2026-07-13-pe-negative-quote/run.md @@ -7,7 +7,7 @@ ## Stages - [x] Stage 1 — invest (fable-clone ensemble, 4× investigator-codebase) → **DONE 4/4 clean 0-garble** (`wf_eaf52972-d18`, ~11', 516K tok). Kết luận: **FE-ONLY** (BE+DB đã nhận âm; ngân-sách bất-biến bằng kiến-trúc). Spec `spec-pe-negative-quote-13-07-2026.md` ghi xong. -- [x] gate — anh Kiệt CHỐT: R1 = CHO HĐ âm (no-op) · R2 = GIỮ chặn net≤0 (no-op). Implement = thuần FE + test. +- [x] gate — anh Kiệt CHỐT: R1 = CHO HĐ âm (no-op) · R2 = **NỚI guard** `Sum()<=0`→`All(==0)` (anh hỏi lại: 1-NCC-âm phải gửi được, mâu thuẫn R1 → refine). Implement = FE + 1 điều-kiện BE guard + test. - [!] Stage 2 — review (fable-clone ensemble, 3× reviewer) - [ ] Stage 3 — review (fable-real, reviewer deep-pass) - [ ] gate — owner-decisions (nếu invest/review lòi ra) 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 0776c05..041d647 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,30 +7,31 @@ **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. -## ② Cách implement — **FE-ONLY** (0 BE · 0 migration · 0 bảng/cột mới) +## ② 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). ⇒ **CHỈ sửa FE + gỡ đúng 3 chốt chặn âm.** +**Đã 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).** -**4 thay đổi FE (mirror 2 app `fe-user` + `fe-admin` byte-identical — SHA-mirror bắt buộc):** +**5 thay đổi (mirror 2 app `fe-user` + `fe-admin` byte-identical — SHA-mirror bắt buộc):** 1. **Input quote — AddSupplierDialog** `PeDetailTabs.tsx:2365` — thêm `neg` state + nút toggle **±**, hiển thị `formatVndInput(Math.abs(thanhTien))`, lưu `neg ? -parseVnd : parseVnd`. **Tái dùng pattern có sẵn `VndInlineEdit:1003`** (prop `allowNegative` + `neg` state `:1015` + toggle `:1025-1037` + save `neg?-n:n` `:1019`). KHÔNG reinvent. 2. **Input quote — QuoteDialog** `:2917` — cùng pattern; `neg` init từ dấu của `existing.thanhTien` (mở lại quote âm phải hiện đúng, không trắng ô). 3. **Submit gate — AddSupplierDialog** `:2226` `if (detailId && form.thanhTien > 0)` → `!== 0` (âm tạo quote; rỗng/0 vẫn skip = "chưa báo giá"). Toast `:2239` cùng sửa. 4. **KHÔNG đụng** `parseVnd`/`formatVndInput` (dùng-chung ô ngân sách cùng file `:51-52`) + **KHÔNG** truyền allowNegative cho ô ngân-sách `thanhTienNganSach:2830` (giữ ≥0). +5. **Submit-guard R2-revised (BE + FE mirror):** `PurchaseEvaluationWorkflowService.cs:206` `selectedThanhTien.Sum() <= 0` → `selectedThanhTien.All(x => x == 0)` (hoặc `!Any(x => x != 0)`) + FE mirror `PeDetailTabs.tsx:201` (`gia <= 0` → all-zero). Chặn **CHỈ khi chưa nhập giá nào**; có ≥1 giá thật (âm/dương, kể cả 1 NCC 1 hạng mục âm) → CHO gửi. Message giữ nguyên. `Count==0` (chưa chọn NCC) vẫn chặn riêng, không đổi. **Semantics giữ:** rỗng/0 = "chưa báo giá" (skip tạo quote); âm = quote thật (≠ rỗng). `formatVndInput(-n)→''` là lý do PHẢI tách magnitude+dấu (không chỉ nới parseVnd). **Tests (regression-lock invariant tài chính — test-specialist):** 1. **Ngân sách giữ nguyên**: upsert quote âm → assert `PeWorkItemBudget` amounts UNCHANGED (`PeWorkItemBudgetTests`). 2. `winnerQuoteTotal` = SUM mixed +/- selected (`PeMultiWinnerTests`). -3. submit-guard: winner thuần-âm → blocked; mixed net>0 → submittable (`PeSubmitGuardAndBypassTests`). +3. submit-guard R2-revised: **all-zero → blocked** (giữ test `:104`); **thuần-âm / net-âm → SUBMITTABLE** (có giá thật, kể cả 1 NCC 1 hạng mục âm); mixed net-any → submittable (`PeSubmitGuardAndBypassTests`). 4. CCM-threshold với total giảm do âm (`PeCcmThresholdFinalizeTests`). ## ✅ Owner-decisions (anh Kiệt CHỐT S116 via AskUser) - **R1 — Contract.GiaTri âm: CHO PHÉP** (khuyến nghị). Luồng PE→HĐ giữ số âm; **KHÔNG thêm clamp/floor**; validator tạo-tay `ContractFeatures.cs:46` (GiaTri≥0) GIỮ nguyên (đường khác). → **code no-op** (allow = không chặn thêm). -- **R2 — submit-guard net≤0: GIỮ CHẶN** (khuyến nghị). Guard `PurchaseEvaluationWorkflowService.cs:206` `Sum()<=0` + FE mirror `:201` GIỮ nguyên. → **code no-op**. +- **R2 — submit-guard: NỚI (anh Kiệt refine S116)** — đổi chặn `Sum()<=0` → chặn CHỈ khi TẤT CẢ selected = 0 (`All(==0)`). Có ≥1 giá thật (âm/dương, **kể cả 1 NCC 1 hạng mục âm**) → CHO gửi. Lý do: rào cũ `Sum()<=0` bắt nhầm ca 1-NCC-âm → **mâu thuẫn R1** (cho HĐ âm nhưng không gửi được phiếu tạo ra nó). → **BE `:206` + FE mirror `:201` (fix nhỏ 2-tier).** - R3 (default): BgVat/ChuaVat giữ nguyên (FE chỉ expose input âm cho thanhTien). -→ **Cả 2 chốt = giữ hành-vi hiện tại ⇒ implement thuần 4 thay-đổi FE + 4 test, 0 đụng BE.** +→ **R1 no-op + R2 nới-guard ⇒ implement = 4 thay-đổi FE + 1 điều-kiện BE (submit-guard) + 4 test.** ## ③ Checklist (vai · deliverable · acceptance ĐO ĐƯỢC) | # | Vai | Deliverable | Acceptance đo được | @@ -38,7 +39,8 @@ | C1 | implementer-frontend | ± toggle + neg-state cho 2 input quote (`:2365`, `:2917`) × 2 app | Nhập `-5.000.000` → ô hiện `-5.000.000` (KHÔNG trắng); reopen quote âm hiện đúng dấu; SHA `fe-user`==`fe-admin` | | C2 | implementer-frontend | Submit gate `:2226` `>0`→`!==0` + toast `:2239` × 2 app | Thêm NCC với quote âm → tạo quote (không bị nuốt im lặng); rỗng/0 vẫn "chỉ tạo NCC" | | C3 | implementer-frontend | Ô ngân-sách `:2830` GIỮ ≥0 (không allowNegative) | Nhập `-` vào ngân sách hạng mục → bị chặn/không nhận | +| C3b | implementer-backend + implementer-frontend | Submit-guard R2-revised: BE `:206` `Sum()<=0`→`All(==0)` + FE mirror `:201` × 2 app | Phiếu **1 NCC 1 hạng mục âm → GỬI được**; phiếu selected toàn 0 → vẫn chặn "chưa báo giá" | | C4 | test-specialist | 4 test regression-lock (ngân-sách-giữ-nguyên + winnerTotal + submit-guard + CCM-threshold) | `dotnet test` xanh; test #1 chứng budget bất-biến khi quote âm | | C5 | reviewer + cicd | build 2-app + deploy | `npm build` 2-app pass; bundle rotate; prod nhập âm được (anh UAT incognito) | -**Trạng thái sau implement dự kiến:** Mig 65 (GIỮ) · 89 bảng (GIỮ) · test 486→~490 · FE 2-app bundle rotate. FE-only → CI chạy (path-filter không skip vì `.tsx`). +**Trạng thái sau implement dự kiến:** Mig 65 (GIỮ) · 89 bảng (GIỮ) · test 486→~490 · FE 2-app bundle rotate + BE 1 điều-kiện guard. → CI chạy (path-filter không skip).