Files
solution-erp/.claude/agent-memory/implementer-backend/archive/2026-07.md

14 KiB
Raw Blame History

Archive 2026-07 — implementer-backend (L2, moved-not-cut từ L1 @S161 curate)

Verbatim entries dời khỏi MEMORY.md L1 khi chạm ~93% autoinject_cap (25.600B). KHÔNG nén — nguyên văn.

  • S134 (07-17) PE lũy kế TẠM TÍNH BE [wf_58302bd5-b9f INV-2.A · return sạch 0-garble] [engine: opus-worker]: +ComputePendingAsync + PePendingAccumulation (PeBudgetAccumulator.cs pure-add +68/0) + PePendingPriorPeDto + PeBudgetSummaryDto append-cuối 5 field (2 construction site: :946 frozen-defaults 0/0/0/0/null · :985 live-fill) + D4 audit-note TRONG ApplyBudgetSnapshotOnFinalizeAsync (Action=Update KHÔNG Transition — #49 tránh UI đọc 2 lần chuyển-phase; insertion-only +30/0; signature GIỮ → 5 call-site 0-diff, site cuối line-shift :1112→:1142 content bất động). Build slnx 0/0 · 0 mig · ComputeAsync body 0-diff (A1). Learned: (1) insertion-only diff (grep '^-' = 0 real-removal) = bằng-chứng MẠNH NHẤT "thân method/call-site bất động" — mạnh hơn đọc-mắt, chống báo-số-sai kiểu #53; (2) derived-audit thêm vào helper KHÔNG cần đổi signature nếu tính nội bộ từ field entity sẵn có (giữ call-site 0-diff); (3) accumulator ns ≠ DTO ns (...Dtos) → cần using tường minh dù cùng project. Tag [s134, pe-luyke-tam-tinh, computepending, insertion-only-proof, d4-changelog-update-not-transition]

  • S133 (07-17) PE budget-freeze BE [wf_4bef08b8-730 INV-B · #53 return-fail → disk-truth + em-main-solo hoàn tất; entry on-behalf]: lane sửa XONG 6 file + PeBudgetAccumulator.cs mới (Domain 11 props ApprovedBudget* + Config precision/maxlength + DTO append BudgetFrozen=false cuối positional-record + display-gate frozen/live 2-nhánh trong GetPurchaseEvaluationQuery + helper ApplyBudgetSnapshotOnFinalizeAsync gọi 4-site :862/:901/:934/:1109 + 2 terminal-guard block-CẢ-Admin) rồi CHẾT TRƯỚC bước sinh migration → em-main solo: scaffold Mig 67 sạch 11 AddColumn (0 RenameColumn #63) + backfill Sql() OUTER APPLY ×3 khớp accumulator TỪNG điều kiện (Suppliers/Quotes = BaseEntity KHÔNG soft-delete filter → SQL không thêm IsDeleted trên s/q; WorkItemId-null → 4 lũy kế NULL mirror helper, SnapshotAt vẫn set) + apply 2 DB — Dev lòi drift Mig 66 committed-chưa-apply-local, catch-up luôn — + filter test 44/44 (T0 RED→GREEN không sửa test). Phát hiện đáng giữ của lane: TransitionAsync KHÔNG SaveChanges nội bộ :537 → snapshot flush qua SaveChanges của caller (mirror ApprovedPrice pattern); khuôn backfill THẬT Mig 61/65 = plain Sql() KHÔNG C# provider-guard (SQLite EnsureCreated không replay = guard kiến trúc). Tag [s133, pe-budget-freeze, mig67, 53-writelane-disk-recover]

  • (FIFO @S167 — 4 entry 2026-07-12/13 dời verbatim từ L1 khi hook read-limit-guard bắn @22.4KB; moved-not-cut: S116 PE giá ÂM · S113 Supplier import v2 Mig 64 · S112 PE sign-off · S112 Supplier Excel-import Mig 63.)

  • 2026-07-13 (S116 PE cho nhập giá ÂM — submit-guard 1-line BE, owner anh Kiệt PROD-UAT-live, NO mig/FE/test, spec deterministic exact-line → ACCEPT Case 1): Giá âm ô "Số tiền báo giá hạng mục" (form.thanhTien→Quote.ThanhTien) = phát-sinh-giảm/hoàn-tiền NCC = giá-trị THẬT (KHÔNG lỗi). Rule "có giá là được": ≥1 quote-selected khác 0 (âm/dương) → gửi được; CHỈ all-zero (chưa báo giá) mới chặn. THAY DUY NHẤT PurchaseEvaluationWorkflowService.cs:206 submit-guard selectedThanhTien.Sum() <= 0.All(x => x == 0). Guard Count==0 (chưa chọn NCC) ở if phía trên GIỮ RIÊNG → else if chỉ chạy khi list non-empty → .All(==0) = "mọi quote chọn đúng bằng 0". Message giữ "Đơn vị được chọn chưa có giá chào thầu". KHÔNG đụng (owner R1/R3/R-CEO): validator ≥0 khác (budget/price PeFeatures/PeWorkItemBudget/PeSuggestedPrice) GIỮ · clamp CreateContractFromEvaluationFeatures.cs:88-100 GIỮ (Contract.GiaTri cho âm R1) · CEO-threshold winnerQuoteTotal NET GIỮ · KHÔNG thêm validator quote. DB nhận âm sẵn (Mig 65, decimal(18,2) 0 CHECK, 0 validator). Build SolutionErp.Api 0 warn/0 err (Api ref Infrastructure → file compiled). ⚠️ Comment :193-195 ("tổng ≤0"/"D5 giữ >0"/"winner phải có giá") giờ STALE vs all-zero-logic — KHÔNG sửa (owner "THAY DUY NHẤT :206"), flag owner quyết. LESSON: domain "âm là giá-trị-thật" → presence-check dùng .All(==0) KHÔNG Sum()<=0/>=0 (sum-guard nhầm âm-lớn với chưa-nhập). Tag [s116, pe-negative-quote, submit-guard-1line, all-zero-not-sum, co-gia-la-duoc, no-mig, stale-comment-flagged].

  • 2026-07-12 (S113 Supplier import v2 BE — spec wf_e7896ca6 fable-clone-ensemble, owner anh Kiệt, Mig 64, D1/D2/D3 pre-chốt → ACCEPT Case 1+2; ⚠️ return-1 garbled #53 → coordinator SendMessage-resume, xong solo): IsPublic publish-gate + dedup MST-primary. Mig 64 AddSupplierPublishState (Supplier +bool IsPublic NOT NULL default false + Sql("UPDATE Suppliers SET IsPublic=1") backfill 22 prod + DropIndex→CreateIndex đổi Code-unique filter [IsDeleted]=0 AND [Code]<>'' cho draft-row Code="" coexist; no new table 89). Service SupplierExcelImportService: (a) re-bake ExpectedHeaderTokens BYTE-EXACT raw-\n form = single-source cho validator(NormalizeHeader collapse \n→space)+BuildTemplate (spec §② 30 token, c7 trailing-space); (b) dedup MST-primary + Code-backstopBuildMstIndex(NormalizeMst=Trim+strip-inner-ws \s+→"" OrdinalIgnoreCase, blank→skip) precedence existing-MST→existing-Code→batch-MST→batch-Code, countedUpdates HashSet<Supplier> tránh double-count, blank-MST KHÔNG vào batchByMst (2 blank=2 insert); (c) BỎ hard-error blank-Code (=draft), GIỮ Name+all-or-nothing; (d) New IsPublic=!blank(Code), update-path KHÔNG đụng IsPublic; +MstMissing(soft-warn) +DraftCount; (e) BuildTemplate() ClosedXML sheet "Sheet1" 30 token ROW4 WrapText+FreezeRows. PublishSupplierCommand(Id,MaNcc?,Publish): set Code+unique-CI-check + gate Publish→Code-non-empty ConflictException, KHÔNG UpdateSupplierCommand (#73 clobber). API THẬT: GET /api/suppliers/import/template(xlsx "Mau-Database-NCC.xlsx") · POST /api/suppliers/{id}/publish body{maNcc?,publish} · GET /api/suppliers?published={t/f} filter · SupplierDto +bool IsPublic append-CUỐI (sửa 2 projection List+Get). D3 authz: import preview/confirm/template/publish [Roles=Admin,CatalogManager][Policy="Suppliers.Update"] (khớp FE PermissionGuard). DbInit +IsPublic=true 4 real+18 sample. Build slnx 0/0, apply BOTH DB (Dev catch-up+Design). KHÔNG commit/FE/test. LESSON: S112 CI-dedup-at-service mở rộng → dual-key MST-primary; blank-key KHÔNG index. Tag [s113, supplier-import-v2, mig64, ispublic-publish-gate, dedup-mst-primary-code-backstop, blank-code-draft, buildtemplate-single-source, publish-cmd-no-#73].

  • 2026-07-12 (S112 PE sign-off close-review implement — wf_7df56b89-7bb GO-WITH-ADJ, owner anh Kiệt PROD-UAT-live, 4-region/1 file PurchaseEvaluationWorkflowService.cs, NO mig/FE/test → ACCEPT Case 1): C1 A1 (ApplyDrafterBypassOnSubmitAsync): người soạn KÝ TAY ô cấp mình — k=MaxminOwn=Min(drafterSlots.Order), bypass <=k<minOwn (chỉ NV cấp dưới THUẦN), XÓA canned-opinion UPSERT (không tự ký hộ ai) + dead maxLevelOrder/ownSlot; advance 3-nhánh→1 if(minOwn>1){pointer=minOwn} STOP tại minOwn (người soạn tự ApproveV2Async); minOwn=1→bypass rỗng pointer giữ (0,1); non-contiguous Cấp1+Cấp3 ký tay đúng thứ tự. C2 EDGE-5 hard-lock: top TransitionAsync sau isAdmin/isSystem TRƯỚC mọi branch → if(fromPhase is DaDuyet or TuChoi && !isAdmin && !isSystem) throw Conflict (chặn non-admin lật phiếu terminal; Admin+System MIỄN); KHÔNG đụng EnsureCanRejectV2Async. C3 no self-finalize (owner oQ1 permission-driven). C4 opinion-history (ApproveV2Async UPSERT else): old.Comment→Changelog (Workflow/Update/EntityId=level.Id) TRƯỚC overwrite chỉ khi non-empty — NO mig/bảng/endpoint. Build slnx 0/0. Infra 4 FAIL/401 pass: fail=C1 (PeSubmitGuardAndBypassTests 9/10/12/14 assert OLD auto-sign/terminal-on-submit); C2/C4 break 0 test → test-specialist ADD. LESSON: behavior-change write-site → run consumer tests, hand VERIFIED fail-list (KHÔNG sửa test). Tag [s112, pe-signoff, a1-sign-own-level, edge5-hardlock, opinion-history-changelog, consumer-test-verified-handoff].

  • 2026-07-12 (S112 Supplier Excel-import Phase B BE — wf_e2f23a84 invest-ensemble → wf_75d6149b close-review GO-WITH-ADJ, owner anh Kiệt, commit e100ef0, Mig 63 → ACCEPT Case 1/2): Service SupplierExcelImportService (preview + confirm 2-stage) + 2 endpoint (/suppliers/import/preview [415 non-xlsx] · /suppliers/import/confirm [401 auth]) + Mig 63 AddSupplierImportSourceFields (Supplier +SourceUpdatedAt datetime2?+SourceUpdatedBy nvarchar(200)? provenance cột 29/30; 2 AddColumn nullable no-new-table 89). FE 2-app byte-identical (SupplierImportDialog). LESSONS: (1) dedup Code = OrdinalIgnoreCase ở SERVICE — DB unique-index CI-collation lật (SQLite fixture BINARY KHÔNG bắt, prod SQL Server CI bắt) → dedup KHÔNG dựa DB; (2) parser absolute cell-index (row-4 header-fingerprint), KHÔNG CellsUsed() (column-drift); (3) all-or-nothing gate TRƯỚC mutate (1 hard-error→Committed=false DB unchanged); (4) fill-nulls SKIP Code/Name/Type (chỉ điền field trống); (5) .Normalize(FormC) cho real-file NFD (close-review adjust bake 30 RealFileHeaderTokens byte-verify). SURPRISE: Approach-B (extend seed) ĐÃ ship 4 NCC prod (Mig 62) → spec "partial" SAI; import ghi entity TRỰC TIẾP (fill-where-non-empty) KHÔNG qua UpdateSupplierCommand (tránh NULL-clobber #73). Shipped Run #496 PASS, reviewer GO-WITH-ADJ (BE 6/6 axes). Tag [s112, supplier-excel-import, mig63, ordinalignorecase-dedup-at-service, abs-cell-index-parser, all-or-nothing, fill-nulls-safe, normalize-formc-nfd, direct-entity-write].

@S152 R1 retention-note [moved-not-cut @S185 refine-A1, verbatim]

  • Integration testing multiple components = việc test-specialist, KHÔNG phải mình — mình scaffold + compile-check; assertion/integration test cross-component (SQLite fixture, authz regression) do test-specialist own (tiền lệ S67 HRM test-after +23, S69 +20 đều test-specialist). Nhận viết integration test = double-touch tests/ sai split. Tag [s152, r1-retention, refuse-integration-testing-components]

@S187 curate — 2 entry dời từ L1 (hook read-limit-guard bắn @23.7KB; moved-not-cut, verbatim)

  • S161 (07-29) ARC W1+W2+W3đ1 KHKK — 3 lượt/3 wave, chi tiết = sub-md run-folder runs/2026-07-29-S161-khkk-{w1-schema,w2-crud,w3-duyet}/ (tổng ~47KB ghi-từng-mục; cả 3 lượt return #53 nhưng 0 mất): W1 Mig 69 7-bảng (UNIQUE filtered #57 · cascade-1-hop · apply 2 DB 0-drift · sys.tables 96) + seeder SeedKeHoachKyKetAccessAsync UPGRADE-if-exists+CanDelete + CodeGen KHKK/{YYYY}/{Seq:D3} WorkflowAppCodeSequences + 3 site AWV2Admin — ship a8027c0 cicd 6/6. W2 mega-file CRUD 15-endpoint policy-15/15 + 4 quyết D1-D4 khai-lead-duyệt; 5 bẫy né ghi sổ: codegen-TRƯỚC-Add (S88) · SUM so w.Id KHÔNG w.SupplierId · Remove trước Add(changelog) (T26) · subquery-Any-2-tầng vỡ-SQL-lúc-chạy · giao-1-grep-cùng-lớp-ra-3 (S122) — ship 6cbc6ad cicd 6/6 (reviewer 12F: D1 "FE sẽ khớp" = giả-định-không-phải-phép-đo → 6 điểm đứt, lead vá). W3đ1 skeleton test-before: switch-throw từng nhánh (đỏ-THẬT không đỏ-compile) + rào-FK-547-bằng-VẮNG-MẶT-dep (không inject IChangelogService) + §6 tự đối-chứng chéo hợp-đồng FE 5/5 TRƯỚC bàn giao (bài W2 → nghi thức) + §7 bàn-giao ctor-CUỐI. Tag [s161, khkk-w1-w2-w3d1, mig69, upgrade-if-exists, rao-vang-mat-dep, doi-chung-cheo, gia-dinh-vs-phep-do]

  • S139 (07-17) Governance wave adap-3-bản 2-lane [wf_f9981ae5-903 · L1 CLEAN · L2 #53-garble → sub-1.md ON-BEHALF em-main verify-2-nguồn] [engine: opus-worker]: L1 = MASTER-CHECKLIST 2-tầng in 4 cửa (ss Phase-3 · se Phase-4 "TỔNG-HỢP CHỐT" · pause ý-6 · tiep §3-bis bold-block KHÔNG heading) + C1 2-câu đóng-băng bề-mặt-đo (§L.b(c) prepend + mirror §2.1.2) + C6i do-record ×2 + luật-số sàn-5 §L.b(j)(iv) — PASS 6/6 (heading 26/28/7/14 bất-biến · 0 checklist-file mới · BƯỚC-0 intact ×8 · 10/10 config-token preserved · add-only 26+/2). L2 = +C6 cite-2-nấc + H24-4 pending-flip vào governance-detectors.ps1 (+260/0 add-only) — fault-inject 13/13 2-chiều (C6 7/7 lane chạy; H24-4 6/6 em-main hoàn tất — lane chết GIỮA fix harness-bug), live-run TOTAL 45 GIỮ + 2 net mới 0-flag count-riêng + exit-0 + ASCII 0-byte>127 + clean-diff 0-FOREIGN-line. Learned: (1) heading-invariant proof = block chèn toàn >/bullet + grep '^+#'=none (mạnh hơn đọc-mắt, họ insertion-only S134); (2) test-harness PS1 cần CÙNG kỷ-luật naming như production — bẫy $h/$H case-insensitive cắn chính harness dù script-đích đã né (:813); (3) 2 lane cùng chạm bề-mặt-đo (L1 sửa file nằm trong scan-scope L2) → số đo giữa-chừng trôi, clean-diff PHẢI cùng-snapshot. Tag [s139, adap-3-ban, master-checklist-4-cua, c6-cite-2-nac, h24-4-pending-flip, faultinject-13-13, 53-writelane-onbehalf]