Files
solution-erp/docs/changelog/sessions/2026-06-12-S60-S62-pe-budget-workitem-softwarning.md
pqhuy1987 5e6dcc1479 [CLAUDE] Docs: S63 closeout S60-62 — re-tier STATUS/HANDOFF + count-flush (Mig 50, 88 bang, 263 test, 64 gotcha) + reconcile stray reviewer + gotcha #63/#64
Viet bu docs cho S60/S61/S62 (ship code prod-verified nhung chua closeout — drift bat o /session-start S63 qua git log).

- Reconcile stray reviewer cwd-misland: MOVE 2 file con fe-admin/.claude -> canonical + pointer (no overwrite 31KB) + xoa stray

- Commit harvest S61/S62: cicd-monitor MEMORY (Run #286) + gotcha #63 (EF RenameColumn sai-semantics) + #64 (Design-DB vs Dev-DB data-migrate)

- Count-flush 4 file: Mig 49->50, tables 93->88, test 240->263 (45D+218I), gotcha 62->64, menu 57->53, Budget module REMOVED->PeWorkItemBudgets

- Session-log bu 2026-06-12-S60-S62-pe-budget-workitem-softwarning.md. Docs-only -> CI skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:41:47 +07:00

10 KiB
Raw Blame History

S60S62 (2026-06-12 → 06-13) — PE workflow polish + ngân sách per-gói-thầu (Mig 50, XÓA module Budget) + soft-warning vượt ngân sách

Closeout muộn (viết bù S63 2026-06-15): 3 session product S60/S61/S62 ship CODE prod-verified nhưng KHÔNG closeout docs cùng lúc (UAT realtime deadline anh Kiệt FDC — đúng pattern "code committed, docs mù" P1). Drift lộ ở /session-start S63 khi git log cho thấy docs dừng S59 (6bf28bf) còn HEAD = 7926c21 (S62) + Mig 50. Nguồn log: 4 commit 37122f07926c21 + cicd-monitor MEMORY (Run #286) + reviewer stray reconcile.

Driver xuyên suốt: anh Kiệt (FDC) UAT realtime trên eoffice prod — chuỗi chỉnh PE module theo phản hồi thực tế. (PE module "CÒN CHỈNH NHIỀU" như CLAUDE.md cảnh báo.)

Net state sau S62: Mig 50 · 88 bảng (was 93 — Mig 50 XÓA module Budget) · 263 test (45 Domain + 218 Infra) · 64 gotcha (#63/#64) · menu 53 (was 57 — gỡ 4 Bg_*) · bundle admin 0xKYGhhf / user C81ZdG9G (Run #286).


S60 (2026-06-12) — 2 commit: ràng buộc gửi duyệt + gỡ "Từ chối"

37122f0 (11:53) — Ràng buộc đủ 4 thông tin mục 3 + bypass người soạn trong chuỗi duyệt

  • Rename mục 3 "Chọn NCC / TP thắng thầu" → "Đơn vị NCC/TP được chọn" (anh Kiệt chốt chữ) ×2 app + wording phụ nhất quán.
  • Guard gửi duyệt đủ CẢ 4 (anh chốt): đơn vị được chọn + giá chào thầu >0 + ngân sách (Budget link HOẶC nhập tay) + bảng so sánh đính kèm.
    • BE ConflictException gộp mọi mục thiếu 1 lần, áp cả Admin (TransitionAsync submit branch).
    • FE pre-check missingForApproval cùng predicate → disable nút + tooltip liệt kê đủ (computeGiaChaoThau extract single-source).
  • Bypass drafter-in-chain (luật GENERIC theo cấp, anh chốt): V2-only, BƯỚC ĐẦU only — người soạn là approver cấp k → auto qua Cấp 1..k khi gửi.
    • Audit 3 tầng: Approval row AutoApprove per cấp + LevelOpinion CHỈ slot chính chủ (KHÔNG gắn chữ ký NV bị skip) + Changelog.
    • Pointer: k<max → Cấp k+1; hết bước → Bước 2 Cấp 1; workflow 1 bước → terminal DaDuyet. TraLai resubmit áp lại idempotent (opinion UPSERT).
  • Test: +14 PeSubmitGuardAndBypassTests (240 → 254 PASS). ⚠️ Reviewer die mid-run (gotcha #53 class) → em main self-gate evidence-checklist PASS 0 blocker.

6db195d (14:30) — Gỡ hành động "Từ chối" (chỉ còn Duyệt / Trả lại)

  • Domain policy: xóa MỌI transition → TuChoi ở cả 4 policy (NccOnly + NccWithPlan + ForV2Schema + FromDefinition) → NextPhases hết trả TuChoi, nút FE tự biến mất.
  • Service guard S60: chặn targetPhase=TuChoi mọi caller kể cả Admin (đứng TRƯỚC mọi branch — spec bỏ hẳn, không escape hatch); message hướng dẫn dùng Trả lại / Xóa nháp.
  • FE ×2 app: filter next.filter(p != TuChoi) PeWorkflowPanel (SHA256 identical); dialog/isCancel giữ dead-safe để flip lại dễ.
  • Enum TuChoi + phiếu TuChoi cũ + tab filter "Từ chối" GIỮ display (data cũ render bình thường). SlaExpiryJob chỉ Contract — PE không auto-TuChoi, không ảnh hưởng.
  • Test 254 → 256 (59 Domain + 197 Infra). Spec-change tests cùng commit (Domain flip BothPolicies_TuChoi_Removed... + NEW V2SchemaPolicy fact; Infra NEW TargetTuChoi_WithRejectDecision_Throws_TuChoiRemoved_S60; guard #45 test cũ giữ PASS).

S61 (2026-06-13 01:07) — 79ef8da Mig 50: ngân sách gói thầu theo Excel anh Kiệt + XÓA module Budget cũ

  • Mig 50 ReplaceBudgetModuleWithPeWorkItemBudgets: bảng mới PeWorkItemBudgets (1 record/cặp Dự án × Hạng mục, UNIQUE filtered [IsDeleted]=0) + drop module Budget cũ + PE/Contracts drop BudgetId + backfill BudgetManualAmount→BudgetPeriodAmount TRƯỚC DropColumn (phiếu UAT giữ số) + DELETE menu/permission Bg_* IN-list children-first.
  • BE: PUT {id}/budget/pro (role Procurement) + {id}/budget/ccm (role CostControl, Adjustment cho phép ÂM) fail-closed Forbidden-TRƯỚC-side-effect + EnsureTrackedAsync race-safe (catch unique → re-fetch winner, lỗi khác rethrow) + auto-create record khi tạo phiếu + budgetSummary DTO (lũy kế trình-trước/chọn-thầu-trước/đề-xuất-kỳ-này + full fallback dự-trù-PRO + canEdit flags) + submit-guard (3) đổi predicate BudgetPeriodAmount → "chưa nhập Ngân sách kỳ này" + PATCH budget-adjust absolute-set 2 field mới + Contract GIỮ BudgetManual* (HĐ nhập tay không đổi) + kế thừa HĐ map BudgetPeriodAmount.
  • FE ×2 app SHA256 identical: bảng "TỔNG HỢP NGÂN SÁCH TRÌNH KÝ" — block A (full đầm + ban hành + V0 hiệu chỉnh + dự trù PRO + ghi chú, editable theo canEditPro/canEditCcm) + block B 9 dòng công thức Excel (5=1+3, 6=2+4, 7=full5, 8 tự nhập default 7, 9=4+8) + tô màu vượt ngân sách #C00000 / âm đỏ / red-soft row8>row7 + "Chưa chọn" khi count=0 + banner phiếu chưa gắn Hạng mục + ô "Ngân sách kỳ này" ở create/header + XÓA pages/components/types budgets + routes + menuKeys + Layout staticMap (4-place).
  • Test: +22 PeWorkItemBudgetTests (auto-create ×3, ensure/race ×2, authz matrix PRO ×5 + CCM ×3, budgetSummary aggregates ×5, adjust ×4) 14 BudgetPolicyTests (xóa theo module) 1 test via-BudgetId → 263 PASS (45 Domain + 218 Infra, 0 fail).
  • database-agent advise adopted: không FK vật lý PE/Contracts→Budgets (DropColumn không cần DropForeignKey) + DropIndex TRƯỚC DropColumn (SQL 5074) + IN-list thay LIKE Bg_% (underscore wildcard + miss root) + không Serializable wrap (nested-tx conflict codegen).
  • Reviewer PASS-with-minor 0 blocker (verdict-first survived); 2 minor đã sửa trước commit (comment adjustMut absolute-set + dead key budgetId). Note: F4 approver-edit-budget UI entry tạm drafter-only, BE vẫn cho approver scope — chờ UAT anh Kiệt.
  • ⚠️ Scaffold-bug caught → 2 gotcha NEW:
    • #63 EF tự sinh RenameColumn(BudgetManualAmount→ExpectedRemainingAmount) SAI semantics (drop+add cùng type → EF heuristic đoán rename) → thay bằng AddColumn + Sql(UPDATE backfill) + DropColumn. SQLite test (EnsureCreated từ model) KHÔNG bắt được.
    • #64 dotnet ef database update áp Design DB (DesignTimeDbContextFactory, 0 rows) ≠ runtime Dev DB → Sql() backfill CHƯA TỪNG chạy trên data thật trước prod. Guard: cicd brief BẮT BUỘC mục DATA-PRESERVE spot-check sau deploy.

S62 (2026-06-13 11:13) — 7926c21: vượt ngân sách = cảnh báo mềm (cho lưu số âm row 8)

  • Root cause: ô "Giá trị thực hiện dự kiến còn lại" (row 8 bảng Tổng hợp) khi giá trị NCC vượt ngân sách → số dư còn lại ra ÂM; BE validator ExpectedRemainingAmount>=0 + FE VndInlineEdit không bật allowNegative → chặn cứng "âm không lưu được" (testing báo qua anh Kiệt).
  • BE: AdjustPurchaseEvaluationBudgetCommandValidator GỠ rule ExpectedRemainingAmount.GreaterThanOrEqualTo(0) → cho lưu số âm (mirror tiền lệ LeaveBalance AllowsNegativeRemaining). GIỮ BudgetPeriodAmount>0 + submit-guard "đã nhập NS kỳ này" không đổi. (PurchaseEvaluationFeatures.cs:317 validator.)
  • FE ×2 app SHA256 identical: (a) allowNegative cho VndInlineEdit row 8; (b) banner amber "Vượt ngân sách — vẫn lưu & gửi duyệt được" trong PeBudgetSummaryTable khi cmpPeriod<0 || cmpFull<0. Tô màu đỏ cũ GIỮ NGUYÊN.
  • Spec change: flip test AdjustBudget_Validator_ExpectedRemainingNegative_FailsValidation_PassesValidation (âm giờ hợp lệ); test BudgetPeriodZero_FailsValidation GIỮ (budget>0 vẫn enforced).
  • Build FE ×2 PASS + test 263 PASS (45 Domain + 218 Infra, 0 fail/skip). Reviewer PASS 0 issue (row8 âm an toàn arithmetic additive-only — row9=row4+row8, cmpFull=fullrow9, no division/sqrt/unsigned-cast; submit guard nguyên; mirror byte-identical; no scope creep).
  • cicd Run #286 sha 7926c21 PASS ~4m41s — bundle ROTATE admin DsGZlNzT→0xKYGhhf + user DTL_bjzQ→C81ZdG9G (FE×2 changed, đúng). DATA-PRESERVE spot-check 8/8 phiếu UAT giữ số (gồm phiếu 1.243.820.600 đ anh Kiệt).

Quality + lessons

  • 3 session deadline-driven KHÔNG closeout docs = drift S59→S62 (Mig/table/test/gotcha/menu/bundle đều lệch). Bắt được ở S63 /session-start qua git log (session-log disk + MEMORY count đều lag). Lesson H1: đầu session luôn git log trước, đừng tin MEMORY count.
  • Reviewer cwd-relative mis-land (S62): reviewer cd fe-admin/ → Write MEMORY relative-path → 3 file rơi fe-admin/.claude/agent-memory/reviewer/ (pattern feedback_agent_cwd_relative_memory_misland S54). Reconcile S63: MOVE 2 file con vào canonical + MERGE pointer (KHÔNG overwrite 31KB) + xóa stray.
  • EF migration data-migrate (gotcha #63/#64): test xanh + "applied-local OK" ≠ migration đúng — SQLite test dựng từ model (không replay migration), ef database update áp Design-DB 0-rows. Guard duy nhất = đọc file migration sau scaffold + cicd DATA-PRESERVE spot-check sau deploy.

Carry / NEXT (cho session sau)

  • test-after guard (deadline trade-off): PeWorkItemBudgetTests đã có; cân nhắc thêm guard LockDemoSampleUsersAsync (S58) + suppliers asymmetric authz (S59) — vẫn pending.
  • F4 approver-edit-budget UI tạm drafter-only (BE cho approver scope) — chờ anh Kiệt UAT chốt mở UI cho approver.
  • schema-diagram §16+ Mig 32-50 ERD debt (giờ +Mig 50 Budget-drop) — monthly audit 2026-07-01.
  • cicd-monitor L1 MEMORY 63.6KB over-cap lần 5 → curate L2 gấp (H2 flag).
  • Bundle/Run S60/S61 run numbers (#283-#285) chưa truy verbatim — nếu cần, đọc cicd-monitor MEMORY recent entries.