wal: flush 20260713T1605

This commit is contained in:
pqhuy1987
2026-07-13 16:05:44 +07:00
parent 5dc29cd23f
commit 6356def8c0
3 changed files with 59 additions and 23 deletions

View File

@ -17,22 +17,10 @@ Read-only INTERNAL audit SOLUTION_ERP codebase. Tools: Read, Grep, Glob, Bash +
---
## 🆕 2026-07-12 (S114-ens) — PE multi-winner PER-HANG-MUC invest, LENS product-history (fable-clone, RETURN-only)
De: cho 2 NCC-TP trung CUNG 1 hang-muc, moi NCC 1 gia rieng; bang so sanh chon nhieu winner. **KEY history:** multi-winner Mig58/59 (S87/S88) DA SHIP nhung = **WHOLE-SUPPLIER** granularity `PurchaseEvaluationSupplier.IsWinner` (:28), toggle per-supplier-column `PeDetailTabs.tsx:2746`, tooltip "chon 2 = lien danh". winnerQuoteTotal (`PurchaseEvaluationFeatures.cs:1189-1196`) = SUM ThanhTien MOI quote cua supplier IsWinner qua MOI hang-muc **KHONG the split per-hang-muc** (A win item1-2, B win item3) vi all-or-nothing per supplier + double-count neu 2 winner cung quote 1 item. **`PurchaseEvaluationQuote.IsSelected` (:15, per hang-muc×NCC, "1 row=1 NCC chao 1 hang-muc") = latent hook TU Mig12 day-1 nhung DEAD** (persisted+echoed DTO+seeded, 0 consumer financial; FE hardcode `isSelected:false` never-toggle :2283/2916). Yeu cau moi = re-key winner-truth Supplier.IsWinnerQuote.IsSelected = **CARDINALITY CHANGE #2** (feedback_cardinality_change_grep_consumers S87 da dinh 1 lan, sot 2 BE Block-B). Consumers IsWinner: ~9 BE (winnerQuoteTotal :1193 · Block-B :908-922 · submit-guard `WorkflowService:192-209` · ccm-threshold :885-908 · create-contract `CreateContractFromEvaluation:56/89-93/189`) + ~8 FE + 7 test file (PeMultiWinner/SelectWinnerClear/CreateContractMultiWinner/ListWinnerNames/WorkItemBudget/CcmThresholdFinalize/SubmitGuard). Contract inherit = 1 /winner-supplier, GiaTri per-winner (single=detailsSum legacy). Budget per-phieu (PE.WorkItemId single) khong doi cau truc. **#1 decision anh Kiet:** N-win 1 hang-muc = split KHOI LUONG hay full-price-moi-ben? (schema THIEU per-winner volume field Quote.ThanhTien tinh tren full KL). Tag [s114, pe-multiwinner-per-hangmuc, isselected-dead-hook-mig12, cardinality-change-2, whole-supplier-vs-per-cell].
## 🆕 2026-07-12 (S113) — Supplier import v2 invest ensemble (5-lane, Opus MAX, RETURN-only) `[engine: fable-clone-ensemble · Opus-S113]`
Em-main harvest @closeout (H2 5 lane RETURN-only; content `runs/2026-07-12-S113-supplier-import-v2-invest/` journal.jsonl + spec).
- **task:** invest cách làm Supplier import v2 (R1-R7) 5 lăng-kính CÙNG vai investigator-codebase: schema-data-model · import-service · fe-ux · public-visibility-consumers · dedup-validation. `wf_e7896ca6-901`.
- **verdict:** 4/5 structured PASS + 1 lane `public-visibility-consumers` **#53-garble** em-main-solo grep recover (consumers = PE picker `PeDetailTabs:2222` + `ContractCreatePage:321` + dashboard, đều qua ListSuppliersQuery).
- **learned:** Conflation gốc `Supplier.Code` comment " NCC" nhưng S112 nhồi c4 viết-tắt vào R3 muốn tách. Ensemble hội tụ 6 điểm (IsPublic-bool · dedup-MST+Code-backstop · backfill · PublishCommand-riêng · BE-template · re-bake-token) + chẻ đôi D1 (schema-lens=A vs import-lens=B) anh chốt A.
- **surprise:** visibility-lane garble ĐÚNG lane quan-trọng nhất (R4/R6 ẩn draft); recover-lane spec-flag NHƯNG FE implement vẫn sót wiring 4-picker reviewer bắt draft-leak. Prod 37 NCC (không 22 ước). Tag `[s113, fable-clone-ensemble, supplier-import-v2, ma-ncc=code, visibility-garble-recover, dedup-mst]`.
## 🆕 2026-07-12 (S112) — 2 fable-clone invest ensemble (Opus MAX, RETURN-only) `[engine: fable-clone-ensemble · Opus-S112]`
Em-main harvest @closeout (H2 gate lanes RETURN-only, content verbatim `runs/2026-07-12-S112-{supplier-excel-import,pe-signoff-save-advance}/sub-invest-*.md`).
- **task:** (A) Supplier Excel-import mapping/approach/edge `wf_e2f23a84` · (B) PE sign-off save-advance current-flow/change-design/reject `wf_fd8f2bfe`; 3 lane/ensemble.
- **verdict:** cả 2 PASS_WITH_CONCERNS. Supplier 27/27 field bijective (30 cột27, 2 audit-col=owner-call). PE design = **sửa 1 CHỖ** gỡ/gate `ApplyDrafterBypassOnSubmitAsync`.
- **learned:** dedup Code `Ordinal` vs DB unique-index **CI-collation** normalize case TRƯỚC dedup+store (MAJOR, cả-3-lane-SÓT, reviewer bắt); PE "comment-own-boxsaveadvance" cơ-chế ĐÃ sẵn, chặn = bypass-feature S60 cần đảo.
- **surprise:** Supplier Approach-B (extend seed) ĐÃ SHIP prod (Mig 62, 4 NCC live invest bắt spec-"partial" SAI); PE EDGE-5 latent→**REACHABLE** (drafter forge Reject phiếu DaDuyet của mình). sub-file naming swapped (harvest reads content). Tag `[s112, fable-clone-ensemble, supplier-mapping, pe-bypass-a1, edge5-reachable]`.
## 🆕 Recent invest (S112-S114 stubs — verbatim → git history + `archive/2026-07.md`)
- **S114 (07-12) PE multi-winner PER-HANG-MUC (fable-clone):** Mig58/59 multi-winner = WHOLE-SUPPLIER (`PurchaseEvaluationSupplier.IsWinner`); `Quote.IsSelected` per hang-muc×NCC = latent-hook Mig12 nay LIVE (Mig65 backfill re-key winner-truth IsWinnerIsSelected = CARDINALITY-CHANGE #2). winnerQuoteTotal=SUM(Quote WHERE IsSelected) `PeFeatures.cs:1188`. #1 open: N-win 1 hang-muc split KHOI-LUONG hay full-price (schema thieu per-winner volume). Tag `[s114, pe-multiwinner-per-hangmuc, quote-isselected-live]`.
- **S113 (07-12) Supplier import v2 (5-lane ensemble):** selection consumers qua ListSuppliersQuery (`PeDetailTabs:2222`+`ContractCreatePage:321`+dashboard); chot IsPublic-bool + dedup-MST+Code + fail-closed default. Prod 37 NCC. 1 lane #53-garbleem-main recover. Tag `[s113, supplier-import-v2, ma-ncc=code]`.
- **S112 (07-12) Supplier Excel-import + PE sign-off (fable-clone):** Supplier 27/27 bijective (Mig62 SHIPPED prod); dedup Code CI-collation normalize-case TRUOC store; PE bypass = go/gate `ApplyDrafterBypassOnSubmitAsync`; EDGE-5 drafter-forge-Reject REACHABLE. Tag `[s112, supplier-mapping, pe-bypass-a1, edge5-reachable]`.
## 📋 Patterns proven (apply confidently)
@ -87,13 +75,14 @@ Bearer từ `POST api.solutions.com.vn/api/auth/login` → status matrix expecte
## 📅 Recent activity (compressed — full verbatim → `archive/2026-06.md` via `archive/_INDEX.md`)
- **2026-07-12 PE per-HANGMUC multi-winner invest LENS BE-consumer-audit (fable-clone ensemble, RETURN-only):** **3 cardinality axes coexist — request ≠ existing multi-winner.** (A) `PE.SelectedSupplierId` = single whole-phiếu winner (legacy). (B) **Mig 58 "multi-winner" = WHOLE-SUPPLIER liên-danh** (`PurchaseEvaluationSupplier.IsWinner` bool): N suppliers each win ENTIRE package → 1 full contract each. NOT per-hangmuc. (C) **`PurchaseEvaluationQuote.IsSelected`** (per hangmuc×supplier, each row own price BgVat/ChuaVat/ThanhTien) = maps EXACTLY to anh Kiệt request BUT **INERT** — written `PeDetailFeatures.cs:308,332` + DbInit:1257, displayed DTO `PeFeatures.cs:1236`, **ZERO money/contract read** (grep confirm). **5 MONEY consumers ALL key off supplier-IsWinner** → over-count for per-hangmuc: R1 winnerQuoteTotal `PeFeatures.cs:1189-1196` · R2 submit-guard `PeWorkflowService.cs:196-211` · R3 **CCM-threshold-finalize FINANCIAL GATE** `PeWorkflowService.cs:885-895` (< CeoThreshold) · R4 CreateContract giaTri `CreateContractFromEvaluationFeatures.cs:56,84-93` (single=detailsSum vs multi=quote-sum INCONSISTENCY) · R5 budget-compare block-B `PeFeatures.cs:907-927`. Write-site SelectWinner `PeDetailFeatures.cs:400-431` = supplier-level list only, NO detail dim. ~6 test files break (PeMultiWinnerTests/PeWorkItemBudgetTests/CreateContractFromEvaluationMultiWinnerTests/PeCcmThresholdFinalizeTests/PeSubmitGuardAndBypassTests/PeSelectWinnerClearTests). Per-hangmuc = re-key money truth IsWinnerQuote.IsSelected (classic cardinality-grep-consumers trap). Tag `[pe-multiwinner, per-hangmuc, quote-isselected-inert, iswinner-whole-supplier, ccm-threshold-gate, cardinality-5-consumers]`.
- **2026-07-12 (S112+ Supplier-import-v2 LENS-3 FE-dialog-UX invest `wf` fable-clone):** Task R2/R3/R7 FE. Ground-truth: SupplierImportDialog.tsx + SuppliersPage.tsx **SHA-identical 2-app** (`6313529c`/`e9143d93`) but types/master.ts DRIFTS (fe-user thiếu `Department.parentId` Mig51, Supplier-portion vẫn mirror). Dialog = 2-step previewconfirm, table READ-ONLY (`:284-325`), **round-trips `preview.rows` verbatim** (`:190`+`:132-134`) per-row edit ĐÃ khả-thi cho R3 (mutate `row.code`confirm). Confirm gate `errorCount>0` disable (`:191`). **R2:** 0 template endpoint tồn tại; `ExpectedHeaderTokens` 30-token baked `SupplierExcelImportService.cs:35-68` = single-source cho BE-gen .xlsx row-4 (test Case6 chứng round-trip); blob-download precedent `PeDetailTabs.tsx:3273-79` (`api.get responseType:'blob'`+createObjectURL). **R7:** SuppliersPage cols code/name/type/taxCode/phone/actions (`:174-206`) **KHÔNG có status/public col**; `Supplier` type `status` (biz-status public) NHƯNG **0 field isPublic/visibility** (grep⌀) R4/R7 cần field MỚI. **Mã NCC=Code** (Supplier.cs:7 comment + col4 TÊN-VIẾT-TẮTCode). **authz-mismatch:** import BE `[Roles=Admin,CatalogManager]` (`SuppliersController.cs:64,77`) vs FE btn gate `Suppliers/Create` (mở-all S59) non-admin thấy nút403; template sẽ kế-thừa. NO new menuKey/route. Tag `[s112, supplier-import-v2, fe-lens3, sha-mirror-drift-parentId, expectedheadertokens-template, no-ispublic-field, import-authz-mismatch]`.
- **2026-07-13 PE quote-NEGATIVE feature LENS Budget-consumers (ensemble, RETURN-only):** anh Kiệt: cho nhập SỐ ÂM ô `form.thanhTien` (báo giá/hạng mục) dialog "Thêm NCC"; RULE = âm=khoản-TRỪ NCC, **ngân sách GIỮ NGUYÊN**. ⭐ **CRITICAL confirm: quote KHÔNG mutate `PeWorkItemBudgets`** — budget chỉ ghi bởi `UpdatePeBudgetPro/CcmCommandHandler` (role-gated PRO/CCM, `PeWorkItemBudgetFeatures.cs:105-107/175-177`) + Adjust/SetCcmBudget; quote write-site (`PurchaseEvaluationDetailFeatures.cs:308-345`) chỉ đụng quote-field + re-derive `IsWinner`, NEVER budget. **5 consumer đọc `SUM(ThanhTien WHERE IsSelected)`:** (1) winnerQuoteTotal DISPLAY `PeFeatures.cs:1188-1191` no-clamp (2) CCM-threshold `PeWorkflowService.cs:884-893` guard `>=ceoThreshold` reject HIGH ko-reject-âm→âm cho CCM-finalize dễ hơn (3) giaTri CreateContract PER-WINNER `CreateContractFromEvaluation:88-90,100` → Contract.GiaTri CÓ THỂ ÂM, **BYPASS** `CreateContractCommandValidator.GiaTri>=0` (`ContractFeatures.cs:46`, chỉ áp manual-create path) (4) "So-với-ngân-sách" block-B DISPLAY `PeFeatures.cs:904-947` budget read AsNoTracking untouched, soft-warn only (5) **submit-guard `PeWorkflowService.cs:196-209` = GUARD DUY NHẤT REJECT: `if Sum()<=0 → "chưa có giá chào thầu"`** — WHOLE-phiếu total, per-item âm OK miễn net>0. **NO quote validator tồn tại** (grep⌀ `UpsertPurchaseEvaluationQuoteCommandValidator`) → âm đã flow BE 0-block, chỉ FE chặn. Keep-≥0 fields có validator: BudgetPeriodAmount/ApprovedPriceAmount/ProInitial/InitialAmount. ĐÃ allow-âm sẵn (unrelated): ProAdjustment/Adjustment/ExpectedRemainingAmount (`PeFeatures.cs:345-348` mirror-LeaveBalance). Tag `[s115?, pe-negative-quote, budget-not-mutated, submit-guard-sum-le-0, giaTri-negative-bypass-validator, no-quote-validator]`.
- **[→ git 07-12 · superseded by S115 entry above] PE per-HANGMUC multi-winner LENS BE-consumer-audit:** 3 cardinality axes — `SelectedSupplierId` single (legacy) · Mig58 `IsWinner`=WHOLE-SUPPLIER liên-danh · `Quote.IsSelected` per hangmuc×supplier (S114 made LIVE). 5 money consumers keyed off IsSelected now (winnerQuoteTotal/submit-guard/ccm-threshold/giaTri/budget-block-B). ~6 test files. Tag `[pe-multiwinner, per-hangmuc, quote-isselected, cardinality]`.
- **[→ git 07-12] Supplier-import-v2 LENS-3 FE-dialog-UX:** SupplierImportDialog+SuppliersPage SHA-identical 2-app; dialog 2-step preview→confirm round-trips `preview.rows` → per-row edit feasible; `ExpectedHeaderTokens` 30-token baked `SupplierExcelImportService.cs:35-68` = template single-source; Mã NCC=Code; import authz-mismatch BE `[Roles=Admin,CatalogManager]` vs FE `Suppliers/Create` open. Tag `[s112, supplier-import-v2, fe-lens3, import-authz-mismatch]`.
- **[→ git+session-log 07-07] S105 BVAAU cross-repo auth RCA:** Q1 root-cause = FE Home MÙ session (bundle cũ relative `/api` trên site TĨNH `web.config` loại `^/api/``/api/auth/me` 404 → anonymous); fix API_BASE-absolute `51c69fb` LIVE. Q2 CRM1 hở 2 tầng (FE soft + API `enforceCrmGate:false`) → flip `AUTH_ENFORCE_CRM_GATE=true`. Q3 Zalo "User_ID" KHÔNG có UI. Class **"hub+site-tĩnh relative-`/api` chết im prod"**; #53-garble-7 → SendMessage-resume recover. *(verbatim → archive/2026-07.md)*
- **[→ 07-12 LENS-3 entry above + git 07-06] S104 Supplier 30-field 🅰️ schema-invest WF `wf_5ac2cdb0`:** SUPERSEDED anchors giữ: ClosedXML 0.105.0 = Excel-standard (KHÔNG OpenXml-raw, `csproj:8`); reuse-existing-cols>parallel; `UpdateSupplierCommand.cs:49-57` blind-set=latent#73; seed fill-nulls-if-exists (`DbInitializer.cs:2781`); `SupplierDto` hand-projected 2-site (`GetSupplierQuery.cs:21`+`ListSuppliersQuery.cs:48-50`); `Dialog.tsx:48` max-h-70vh; extract `ui/PathLink.tsx`. Chốt Mig 62 +18 nullable. *(verbatim → archive/2026-07.md)*
- **[→ git+session-log 07-03 + adap-reports] S101 H19-adopt Lane-2:** map reviewer=review-gate / investigator=ground-truth-lane; 12-pt tier-checklist ALL✅ (11/11 inherit, C3 no-`[1m]`-in-frontmatter); toggle-marker MUST copy `.gitignore:87` last-match placement; VACUOUS-MET all-inherit+Fable. *(verbatim → archive/2026-07.md)*
- **[→ git+session-log 07-02 + adap-reports] S100 H18-adopt WF1:** ⭐ ghost-wire-class **config-key-defined-but-unread** (`budget.json:54-55` script re-hardcode same-value→no-flag; catch=grep-key-across-scripts); cron=ghost-wire (CronList=0); `_ledger` orphan-catch 0/15-lifetime. *(verbatim → archive/2026-07.md)*
- **2026-07-12 (S112-b) Supplier import-v2 LENS-4 public/visibility recon (anh Kiệt FDC, RETURN-only):** ⭐ **3 SELECTION consumers ALL hit `GET /suppliers` (ListSuppliersQuery)** → (1) master `SuppliersPage.tsx:83` ×2-app (admin, MUST see draft opt-in) (2) PE add-supplier dropdown `PeDetailTabs.tsx:2222` pageSize=1000 (3) Contract create `ContractCreatePage.tsx:321` pageSize=200. **DISPLAY-of-existing (join by stored SupplierId MUST NOT filter)**: ContractFeatures:293/392/434 · ContractWorkflowService:168/373 · ContractExcelExporter:44 · GetDashboardStats:42 (TopSuppliers group-over-contracts → draft never appears) · PeFeatures:560/728/874/952 · CreateContractFromEvaluation:63/168. **WRITE-GUARD backstops** (validate-supplier-exists on attach → add draft-reject): `ContractFeatures.cs:64` + `PurchaseEvaluationSupplierFeatures.cs:49`. **NO IsPublic field today**; `SupplierStatus` enum = business (DangHoatDong/Blacklist/NgungHopTac) NOT visibility. **SupplierConfiguration.cs:47/50** = Code unique-filtered `[IsDeleted]=0` + IsRequired + `HasQueryFilter(!IsDeleted)` only. c4 Tên-viết-tắt→Code (SupplierExcelImportService:249) + import HARD-requires Code (:103-108) → conflicts R3 "Mã NCC nhập sau, Excel không có cột". DTO hand-projected 2-site (List+Get). Rec: fail-closed default (GET public-only → dropdowns unchanged, S87/S88-safe) + stored `IsPublic bit`. Tag `[s112b, supplier-visibility, lens4, fail-closed-default, no-global-queryfilter]`.
- **[→ git 07-12 · Mig64 IsPublic shipped since] Supplier import-v2 LENS-4 public/visibility recon:** 3 SELECTION consumers ALL hit `GET /suppliers` ListSuppliersQuery (SuppliersPage:83 · PeDetailTabs:2222 · ContractCreatePage:321); DISPLAY-of-existing joins by stored SupplierId MUST NOT filter (ContractFeatures/PeFeatures/Dashboard); write-guard backstops `ContractFeatures.cs:64`+`PurchaseEvaluationSupplierFeatures.cs:49`. Rec fail-closed default + stored `IsPublic bit`. Tag `[s112b, supplier-visibility, fail-closed-default]`.
- **[all verbatim → archive/2026-07.md] S98 verify-lane (detector 28→25, 8 derived-stale=FP vs canonical 61/89/440/79; grep exact misses README variant) · S97 feedback-audit (#77/#78/#79 `gotchas.md:1301/1317/1325`; #53-garble×6=cicd MEMORY-self-target ~17.5KB; Mig60 wrong-table Approvals→Mig61 Changelogs) · 07-01 Supplier-predecessor SUPERSEDED by 07-06 · S93 MFE denom=marks/AS/guards dedup-AS-xref=29, coverage exact-id OR ≥2-content-word.**
- **PE FROZEN-Mig59 patchwork + go-live recon cluster (S87→S89, 8 entries verbatim → `archive/2026-06.md`):** key catches — AREA-4 **LATENT #73-class BUG** HoSoLink absolute-set `PurchaseEvaluationFeatures.cs:280` but PeHeaderForm PUT OMITS→clears (2 PUT mutually destructive, no preserve-test) · AREA-3 AllowApproverFinalize AUTO(Mig58)+finalizeByCcmDelegation OPT-IN(Mig54) coexist AUTO-first, ApplyApprovedPrice allow-list {Ncc,ProMin,ProMax,Ccm} · AREA-2 budget-matrix band-aid `PeSuggestedPriceFeatures.cs:153-199` PRO=drafter vs CCM=role authz-asymm · AREA-6 FE all 6-comp+3-pages SHA-mirror 2-app, #70 stale-echo · S89 AllowApproverFinalize-NOTE **GAP BE-field-MỚI** (flag only `currentLevelOptions`, DTO no-carry→expose EndsBeforeCeo) · 06-24 winner=SINGLE `SelectedSupplierId` derived-3-site, terminal=per-Level bool · 06-23 urgent authz asymmetric, PE-create dropdown `PeWorkspaceCreateView.tsx:94`.
@ -105,7 +94,8 @@ Bearer từ `POST api.solutions.com.vn/api/auth/login` → status matrix expecte
- **[→ archive/2026-06.md + git]** S52 P11-D/E/F 6-gap recon · S50 HrmConfigs add-kind 11-spot · S51 gotcha #57 EXT reachability (curated S59).
- **[→ archive/2026-05-q4.md + git d2f52ba]** S29-S37: clean-room > NamGroup-port verified 4× · Pattern 12-bis cross-module mirror · FK+freetext dual-write.
- **2026-07-10 (S108 first-real-run H19) `[engine: fable-real-single · Fable-S108]`** (em-main harvest B3, GAP-2/3): Task cite-audit 3 artifact S108 (fable-real-runbook 610-line + adap-request hot-reload + email-draft), ~111 anchor. Verdict **PARTIAL — 103/111 exact**: 7 off-by-1/2 (cụm reviewer:37-56 + investigator:46/:77 — systematic quanh code-fence) + 1 drift-note-stale (runbook:123 “9 vai” trong khi engine :328 đã 10) + email-fact FALSE **bắt trúng bẫy honesty** (hmw.js:31 chưa-sửa lúc audit → em-main vá thật sau verdict). Learned: worker-draft cite lệch ±1-2 quanh fence → audit MỞ ĐÚNG DÒNG đừng tin range · drift-note về file khác re-check ngay-trước-commit (canonical có thể được patch song song). Surprise: **meta-drift 2 tầng** — đoạn cảnh-báo-drift tự drift vì engine update TRƯỚC khi note viết xong. Return dòng-1 VERDICT-header chuẩn, 0 garble.
- **2026-07-13 PE negative-quote LENS edge/precedent/test (fable-clone, RETURN-only; complements Budget-consumers entry ↑):** ⭐ BE+DB đã cho âm; blocker = FE helper + submit-guard. (a) `parseVnd` `PeDetailTabs.tsx:51` strip `[^\d]` → minus mất; `formatVndInput:52` `n>0?:''` → âm+0 đều blank (lẫn "chưa báo giá"); AddSupplier gate `thanhTien>0` :2226 skip quote khi ≤0 (empty≡0 by-design); inline QuoteCellDialog :2870 always-POST (store 0 OK); fe-admin SHA-identical :51/:52/:2226. (b) precedent = OMIT validator: `ExpectedRemainingAmount` PurchaseEvaluationFeatures.cs:348 + LeaveBalance `AdjustmentDays` LeaveBalanceFeatures.cs:107; Quote `UpsertPurchaseEvaluationQuoteCommand` DetailFeatures.cs:272 = NO validator (grep⌀) → BE nhận âm sẵn. (c) 0 test assert ThanhTien≥0. (d) gotcha #81 orthogonal (IsWinner==Any(IsSelected) bool≠sign → no phantom). (e) NO DB CHECK (`HasCheckConstraint` grep⌀ whole Infra; col decimal(18,2) config:131). HARD-INTERACTION submit-guard `Sum()<=0` `PurchaseEvaluationWorkflowService.cs:206`+FE:201 → net-≤0 BLOCKED "chưa có giá"; che CCM-threshold:891. NS-giữ-nguyên architectural (quote never write PeWorkItemBudgets; âm chỉ shift winnerQuoteTotal:1188). Tag `[pe-neg-quote,parsevnd-strip-minus,sum<=0-guard,no-check-constraint,omit-validator,iswinner-orthogonal]`.
- **[→ git 07-10] S108 first-real-run H19 cite-audit:** 103/111 exact; class = worker-draft cite lệch ±1-2 quanh code-fence (audit MỞ ĐÚNG DÒNG đừng tin range) + drift-note-stale + email-fact honesty-trap. 0 garble.
- **2026-07-12 (S111 H22 WAL deep-pass) `[engine: fable-real-single · Fable-S111]`:** H22 WAL adopt → READY_WITH_NUANCES + spec 3-mục (em-main gov/settings · w1 pause+tiep · w2 start/end delta · w3 hook+README). Chốt: (1) `.claude/settings*.json` KHÔNG tồn tại → hooks-entry CREATE mới; hook = STAGED-until-verified (S100 ghost-wire, chỉ claim khi thấy `wal:` commit phiên mới). (2) engine`:402` no-OS-hook = MÔ-TẢ (CAVEAT) không phải luật cấm; `:116` E.3 "hook fails-open ≠ gate" → persist-only TƯƠNG THÍCH, update :402; ACTIVE-MARKS 0 mark cấm-hook; H5 start`:41` chỉ cấm hook-switch MODEL. (3) AS-6 KHÔNG tăng: 2 runtime .json gitignored (`.gitignore:124-125`), hook commit LOCAL — push-set ≡ hôm nay. (4) cmd-PATH Git Bash trước WSL, bash 5.2.26 OK. (5) `.gitattributes` exists broadcasts-scoped → append `*.sh`; repo 0 .sh = zero churn. (6) 16 command 0 trùng pause/tiep. (7) Stop-hook fire chỉ TURN-boundary → chết giữa workflow-turn = vật-liệu DISK uncommitted, /tiep đọc cả git status/disk. (8) run.md thiếu taskList snapshot. Learned: "luật cấm hook" phải phân MÔ-TẢ vs LUẬT trước khi đòi carve-out. L1@19.8KB → suggest em-main curate.

View File

@ -6,8 +6,9 @@
**Pipeline anh chỉ định:** `/fable-clone invest+review``/fable-real review` → hmw Opus 4.8 MAX → commit + deploy.
## Stages
- [!] Stage 1 — invest (fable-clone ensemble, 4× investigator-codebase multi-lens) → em synthesize spec 3-mục
- [ ] Stage 2 — review (fable-clone ensemble, reviewer)
- [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.
- [!] 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)
- [ ] Stage 4 — implement (hmw Opus 4.8 MAX)
@ -23,4 +24,5 @@
1. FE-surface · 2. BE-surface · 3. Budget-consumers · 4. Edge-precedent-tests
## wf run-id
- Stage 1 invest: `wf_eaf52972-d18` (task `wslm7eymc`) — 4 lane investigator-codebase, launched 2026-07-13, HMW-ON, lead Opus 4.8
- Stage 1 invest: `wf_eaf52972-d18` (task `wslm7eymc`) — 4 lane investigator-codebase, DONE 4/4 clean, HMW-ON, lead Opus 4.8
- Stage 2 review: `wf_4824f158-b66` (task `wjhpvoyum`) — 3 lane reviewer (correctness-wire · mirror-completeness · financial-invariant-owner)

View File

@ -0,0 +1,44 @@
# Spec — PE cho phép nhập SỐ ÂM ô "Số tiền báo giá cho hạng mục" (2026-07-13, S116)
> Engine: `/fable-clone investigator-codebase` 4-lane ensemble (`wf_eaf52972-d18`, 4/4 clean 0-garble) → lead verify + synthesize. Owner anh Kiệt (Zalo ⇄ Duy Nguyen 2026-07-13).
## ① Tính-năng / đề-bài
Ô **"Số tiền báo giá cho hạng mục"** (React state `form.thanhTien``Quote.ThanhTien`) trong dialog **"Thêm NCC vào phiếu"** + dialog sửa giá (QuoteDialog) của module PE hiện **không nhập được số âm**. Yêu cầu: **cho nhập âm** (NCC có phát-sinh-giảm giá trị do có làm trước).
**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)
**Đã 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.**
**4 thay đổi FE (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).
**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`).
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**.
- 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.**
## ③ Checklist (vai · deliverable · acceptance ĐO ĐƯỢC)
| # | Vai | Deliverable | Acceptance đo được |
|---|---|---|---|
| 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 |
| 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`).