Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-invest-fable-b1a.md
2026-07-31 11:35:31 +07:00

18 KiB
Raw Blame History

sub-invest-fable-b1a — SPEC TỔNG QUÁT CÁC WAVE chuỗi 4 GĐ theo SOL-CCM-SP-002 (ATTEMPT 2)

Engine /fable-real single deep-pass. Attempt-1 chết ngược-#53 (207K token, 0 byte đĩa). Lượt này: ghi từng section ngay khi xong.

A. Kế thừa từ S157/S160

  1. O-1 (S157): "cấu trúc trình ký GIỐNG, khác nội dung" ⇒ KHKK tái dùng khung duyệt V2 (ApprovalWorkflow>Step>Level OR-of-N + LevelOpinions UPSERT); dựng mới = NỘI DUNG phiếu. O-2: SLA = tham khảo, 0 engine.
  2. Bẫy Proposal-lite (S157 §B.1): ProposalFeatures.cs:427-439 phá OR-of-N (flatten global-level, match 1 user) ⇒ service duyệt PHẢI copy ContractWorkflowService.ApproveV2Async:217-394, Proposal chỉ làm khuôn CRUD/menu/FE.
  3. Ranh GĐ (owner @S159, S160 F-09): GĐ2=b.7→12 · GĐ3=b.13→18 (tận dụng Contract V2) · GĐ4=b.19→21 ký cứng upload-only. Bản cứng vật lý SINH ở b.17-18 ∈ GĐ3; GĐ4 hiển thị readonly phần nó không tạo (F-11).
  4. GĐ4 = 1-MỐC (owner chốt @S161, skill contract-workflow): form đủ → duyệt nếu có → upload bản cứng = xong; 0 enum-extend, badge hasSealedCopy. Đề xuất 4-mốc S160 F-14 đã bị owner thay bằng 1-mốc — KHÔNG hồi sinh.
  5. Hdc_* = 7-leaf-per-ContractType (S160 lane-2 §3 sửa grounding): loop DbInitializer.cs:1862-1867, KHÔNG phải bộ 6-leaf; HopDongCung+Hdc_* NGOÀI MenuKeys.All ⇒ 0 policy riêng, dùng root-policy Contracts.{Action}.
  6. 3 lỗ đường ống Contract (S156/S160 §1): GetEligiblePhases (ContractFeatures.cs:334-351) + inbox PhaseActorRoles 100% legacy ⇒ approver V2 không xem/inbox được HĐ ChoDuyet (W6) · bridge PE→HĐ pin V1-only + không đọc giá KHKK (CreateContractFromEvaluationFeatures.cs:68-71,88-90) (W5/W7) · guard trình đòi Drafter/DeptManager ⇒ PMH 403.
  7. Mặt tấn công thật: Reject-trước-guard ContractWorkflowService.cs:49-66 (Approve có guard, Reject không) + DeleteContract guard Phase >= DangInKy so sánh SỐ ⇒ chặn xóa cả 9/10/98/99 — HĐ dry-run trọn vòng không xóa được qua API (S160 §4 HỐ-1).
  8. "MB" không gốc quy định (S160 F-06/F-21): RG-001 v02 chỉ 4 viết tắt (HĐTP/HĐGK/NCC/HĐDV); codegen tự chế "MB". FO-002.01 tick-box 6 loại ≠ 7 ContractType (F-23).
  9. Đã LAND sau 2 invest đó (S161-S162, phải kế thừa KHÔNG dựng lại): Mig 69 = 7 bảng ContractSigningPlan* (89→96) · mã phiếu KHKK/{YYYY}/{Seq:D3} bỏ token loại · menu Khkk_G1 + 6 leaf (DbInitializer.cs:1780-1787) · KHKK KHÔNG inherit — grant từng leaf (:2107, chạy SAU revoke) · 15 policy CRUD+Dossier (W2) · duyệt V2 3 trạm PRO/CCM/CEO ApplicableType=10 (W3) · cây 4-folder usePipelineStages ×2 app (S162) · gotcha #85 gate FE đúng key policy KeHoachKyKet.
  10. Còn nợ từ S161: W5 cầu KHKK→HĐ chưa làm ⇒ pe.contractId là đường duy nhất tới GĐ3; phiếu liên-danh N-HĐ chỉ nối HĐ[0] (CreateContractFromEvaluationFeatures.cs:145). W4 (gate/checklist) + W9 (authz) defer.
  11. Dry-run: dự án test riêng ZZTEST (seq per-prefix riêng, ngoài seed ⇒ không resurrect #75/#76); prod Contracts = 0 active + 0 deleted (lead đo S160 — sổ "7 HĐ V1" STALE); notification bắn user thật, dặn team; login prod parse field accessToken.

B. Ground-truth code (đo 07-31)

  1. ContractSigningPlan header (Domain/ContractSigningPlans/ContractSigningPlan.cs:15-53): MaKeHoach gen NGAY LÚC TẠO (:17-22), PurchaseEvaluationId/ProjectId/DepartmentId/DrafterUserId loose-Guid, con-trỏ đôi StepIndex+LevelOrder (:35-37), SLA hiển thị tham khảo O-2 (:39-42). KHÔNG có field nhóm-duyệt/hạng-mục/loại-HĐ nào.
  2. ContractSigningPlanLine = per-NCC-winner, KHÔNG per-hạng-mục (ContractSigningPlanLine.cs:12-29): SupplierId + PeReferenceAmount snapshot + ProposedAmount + ApprovedAmount? + ContractId? (C6, chờ W5). UNIQUE filtered (PlanId, SupplierId) WHERE IsDeleted=0. Model "duyệt hạng mục" MỚI chưa có chỗ đứng — xem C2.
  3. ApplicableType hiện dùng 1-10, slot 11+ trống (Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs:53-71); comment :60 chốt trục "mỗi module 1 type". ContractSigningPlan = 10 (:70).
  4. POST workflow mới deactivate MỌI active CÙNG type ("only ONE active per type" — ApprovalWorkflowV2AdminFeatures.cs:339-343) — 8 nhóm = 8 Code cùng type 10 thì chỉ 1 IsActive. NHƯNG vô hại cho KHKK: create validator CHỈ check ApplicableType==10, KHÔNG check IsActive/IsUserSelectable (ContractSigningPlanFeatures.cs:209-220); picker FE lọc isUserSelectable=true trong bucket type-10 (KhkkCreatePage.tsx:57-64) — IsUserSelectable độc lập IsActive, PATCH per-workflow (ApprovalWorkflowV2AdminFeatures.cs:1164-1177).
  5. KHKK service CỐ Ý KHÔNG port CeoApprovalThreshold/AllowApproverFinalize (comment ContractSigningPlanWorkflowService.cs:44) ⇒ set ngưỡng trên workflow type-10 hôm nay = config-lie #78 (0 consumer). Cột sẵn ApprovalWorkflow.cs:43.
  6. Menu Khkk = 1 group + 6 leaf (DbInitializer.cs:1780-1787: Khkk_G1 "1. Kế hoạch ký kết HĐ (NCC-TP)" + WfView/List/Create/Pending/Approved/Deleted — leaf key KHÔNG infix G1). Grant explicit 13/13 role SeedKeHoachKyKetAccessAsync (:2110, :2415-2424, chạy SAU revoke). Inherit switch vẫn đúng 4 root, KHKK ngoài (GetMyMenuTreeQuery.cs:70-74); IsVisible+DisplayLabel pass-through DTO (:88).
  7. "Quy trình duyệt (Mới)" seed chỉ 2 leaf DuyetNcc/DuyetNccPhuongAn (DbInitializer.cs:1801-1803); 0 workflow type-10 nào được seed (grep ContractSigningPlan DbInitializer = 0 hit ngoài menu) — 8 workflow nhóm sẽ phải seed mới hoặc admin tạo tay. Designer đã nhận deep-link /system/approval-workflows-v2/ContractSigningPlan (ApprovalWorkflowsV2Page.tsx:159-162); fe-admin Layout.tsx:166-172 resolvePath AwV2_ chỉ biết 3 code cũ.
  8. Cây 4-folder: usePipelineStages.ts (MIRROR SHA256 ×2 app, :3) — GĐ2 nối khkkByPeId (:110-121), GĐ3/4 nối qua pe.contractId ĐƠN (:214-217, liên-danh chỉ HĐ[0]), GĐ4 = c.hasSealedCopy (:231), c.type CÓ SẴN trên leaf (:240 navigate /hard-copies?type=). PipelineStage = {n, leaves[], content?, count?} (PipelineStageFolders.tsx:58-72) — leaves PHẲNG, muốn sub-folder nhóm thì hoặc thêm groups? vào type hoặc truyền content ReactNode (khuôn stage1Content :66-69). GĐ2 group-by cần field nhóm trên KhkkListItemDto (chưa có); GĐ3/4 group-by c.type = thuần FE.
  9. Sidebar admin = menu-tree ĐỘNG từ AuthContext.menu + filter client filterForAdmin (fe-admin/Layout.tsx:299,:315,:186-190); hiện isAdminHidden chỉ ẩn Ct_* (:182-184). ⇒ "Ẩn hết bên Admin, chừa HỆ THỐNG + Quy trình duyệt (Mới)" = mở rộng hàm này (đảo blacklist→whitelist) — 1 file FE, 0 BE, revert 1 commit, KHÔNG đụng IsVisible (IsVisible là cột DB chung ăn CẢ fe-user).
  10. Khuôn seed catalog: SeedRealMasterDataAsync (DbInitializer.cs:2901+) = tuple-array + per-Code idempotent, UNGATED (:128) — khuôn đúng cho bảng danh mục ~85 dòng. HardCopiesPage.tsx ĐÃ là trang thật ×2 app (GĐ4 W7 landed, đọc ?type= :64-67 theo hook comment).

C. 8 treo → 8 verdict

# Treo (draft §6) Verdict Evidence / đề-xuất
1 Cardinality "đưa vào loại HĐ" [OWNER-GATE OG-1] — đề-xuất default: GỘP theo cặp (NCC × dòng-danh-mục): N hạng-mục-đã-duyệt cùng NCC cùng dòng SP-002 → 1 HĐ; nhiều Line trỏ chung 1 ContractId ContractSigningPlanLine.cs:25 ContractId per-line, KHÔNG UNIQUE ⇒ N-line→1-HĐ đã hợp khuôn; bảng 3.5 SP-002 nhiều dòng gợi gộp; default đảo được (1-1 = mỗi line 1 HĐ, cùng máy)
2 Phiếu per NHÓM hay per HẠNG-MỤC Per NHÓM — 1 phiếu = 1 nhóm-duyệt, chứa N hạng-mục (Lines repurpose per-hạng-mục) Workflow pin ở HEADER (ContractSigningPlan.cs:35-37) ⇒ chuỗi duyệt chạy per-phiếu; per-hạng-mục = N phiếu × 4 trạm = spam duyệt, trái "cùng 1 nhóm duyệt" (lời 7). Lines hiện per-NCC (ContractSigningPlanLine.cs:12-29) → Mig thêm CatalogEntryId+TenHangMuc, drop/recreate UNIQUE (cardinality change ⇒ grep consumers — module trẻ, consumer đếm được: Features + service + bridge-W5 + FE types)
3 8 workflow nhóm ↔ ApplicableType 1 type=10 + 8 workflow (Code KHKK-N1..N8), KHÔNG mint type 11-18 Create chỉ check type (ContractSigningPlanFeatures.cs:209-220), picker lọc isUserSelectable (KhkkCreatePage.tsx:57-64) ⇒ only-ONE-active-per-type (ApprovalWorkflowV2AdminFeatures.cs:339-343) VÔ HẠI; trục type = module (ApprovalWorkflow.cs:60), nhóm = DATA nghiệp vụ ⇒ cột ApprovalGroup int trên plan + validator line-cùng-nhóm
4 Vai trò catalog (TBP.CCM, QS…) lưu đâu 2 tầng: catalog lưu chuỗi vai TEXT (hiển thị/in), workflow V2 Levels lưu NGƯỜI THẬT — KHÔNG dựng bảng role-map mới V2 match = đích danh ApproverUserId (skill + ContractSigningPlanFeatures.cs:190-192); AppRoles không có TBP.EQU/QS/MEP-SM ⇒ đẻ role mới = chế thêm, trái lời 9. Map vai→user khi seed 8 workflow [OG-2 roster]
5 NĐUQ ngưỡng tiền Cột sẵn CeoApprovalThreshold per-workflow (= per-nhóm) NHƯNG KHKK 0 consumer CỐ Ý ⇒ đợt này = cột THAM KHẢO trên danh mục (TEXT), máy enforce = wave opt-in sau [OG-3] ApprovalWorkflow.cs:43 cột sống; ContractSigningPlanWorkflowService.cs:44 comment "KHÔNG PORT 2 nhánh kết thúc sớm" ⇒ admin set hôm nay = config-lie #78
6 Mục "1. KHKK (NCC-TP)" cũ GIỮ key Khkk_G1 + 6 leaf key cũ, ĐỔI LABEL thành nhóm 1; nhóm 2-8 = key MỚI infix Khkk_G{n}_* (48 leaf tổng, chấp nhận bất-đối-xứng tên G1) Đổi key = mồ côi 13 row/key (S155); label đổi qua seed Label + labelBackfill khuôn DbInitializer.cs:1918-1924 (S155 — upsert không đụng Label; DisplayLabel chỉ ăn fe-user). Phiếu thử cũ (nếu có): ApprovalGroup=NULL, hiện ở cây + list không-filter, KHÔNG migrate
7 Admin ẩn = cơ chế gì fe-admin filterForAdmin client-filter, đảo blacklist→whitelist (chừa System subtree + ApprovalWorkflowsV2) — KHÔNG dùng IsVisible Sidebar admin render ĐỘNG từ menu-tree (fe-admin/Layout.tsx:299,:315), filter :182-190 hiện chỉ ẩn Ct_*; IsVisible là cột DB CHUNG ăn cả fe-user (GetMyMenuTreeQuery.cs:88) ⇒ ẩn admin-only bằng DB sẽ tắt nhầm eOffice. Ẩn ≠ xóa ✓ (revert 1 commit)
8 B2 trùng số "7" ×2 + "TPB/TBP" dấu "/" Extract file gốc SOL-CCM-SP-002 bằng office-document = việc ĐẦU wave master-data; "/" đọc là OR-of-N (khớp V2 nhiều Level cùng Order); trùng-số → đánh mã lại B2-01..B2-14, nếu extract vẫn mơ hồ → [OG-5] OR-of-N = ApprovalWorkflow.cs:81-94 (S157 xác nhận); số ~85 dòng = đếm từ ảnh, số chính xác PHẢI từ file gốc (draft §2)

D. SPEC TỔNG QUÁT WAVE

Nhãn K1-K8 (tránh đụng W1-W9 của S161/S160 — W1-W3+W7-W8-tương-đương ĐÃ LAND, W5/W6 tái xuất trong K7). Thứ tự = phụ thuộc: K1 (master-data) đứng TRƯỚC mọi wave UI ăn nó (K2, K4, K5); K8 = dry-run E2E cuối bắt buộc. Mỗi wave deploy độc lập.

K Tên · mục tiêu 1 câu Phạm vi BE/FE/mig/seed Khuôn tái dùng (lời 9: nhân khung, đổi form) Định-nghĩa-XONG đo được Cụm
K1 Master-data danh mục HĐ SOLUTION — bảng catalog ~85 dòng SP-002 (mã A1..C3 · tên · nhóm-duyệt 1-8 · người-ký · chuỗi-nháy · ghi-chú NĐUQ) admin CRUD được BE: entity + config + CQRS + controller per-action policy; Mig 70 CreateTable filtered-unique Code #57; seed SeedContractCatalogAsync per-Code idempotent ungated; FE: leaf mới dưới Master/Catalogs ×2 app Khuôn SeedRealMasterDataAsync (DbInitializer.cs:2901+) + catalog 4-leaf KIND_CONFIG; extract file gốc = việc ĐẦU wave (treo 8) Bảng đủ N dòng == số extract từ file gốc (khai N khi extract xong); CRUD admin 200; dotnet test PASS + test seed idempotent (chạy 2 lần không nhân đôi); restart API không nhân row 1
K2 Phiếu KHKK mang HẠNG MỤC + NHÓM — Lines per-NCC → per-hạng-mục, phiếu có ApprovalGroup BE: Mig 71 (plan +ApprovalGroup int?; Lines +CatalogEntryId Guid? +TenHangMuc + DropIndex UNIQUE(PlanId,SupplierId) → UNIQUE(PlanId,SupplierId,CatalogEntryId) filtered); validator line-cùng-nhóm-phiếu; DTO; FE: form Lines editor pick dòng danh mục lọc theo nhóm ×2 app Cardinality change ⇒ grep MỌI consumer Lines (Features/service/bridge/FE types); row cũ backfill CatalogEntryId=NULL = "hạng mục tổng" giữ số Tạo phiếu nhóm X + N hạng mục qua UI; line khác nhóm bị 409; phiếu cũ mở được nguyên vẹn; test PASS (+ test validator ÂM) 1
K3 8 workflow nhóm + designer — mỗi nhóm 1 workflow Code KHKK-N1..N8 type-10, Levels đích danh từ roster [OG-2] BE: seed 8 workflow (khuôn SeedSampleApprovalWorkflowsV2Async DbInitializer.cs:171) IsUserSelectable=true; create/update pin lọc workflow đúng nhóm (Code map ApprovalGroup); FE: picker lọc theo nhóm (KhkkCreatePage.tsx:57-64 + filter Code); menu AwV2 leaf KHKK (:1801-1803 + fe-admin Layout.tsx:166-172 +code) Designer type-10 ĐÃ nhận deep-link (ApprovalWorkflowsV2Page.tsx:159-162) — 0 designer mới; chuỗi trạm per nhóm theo draft §2 bảng 8 nhóm 8 workflow tồn tại qua GET /approval-workflows-v2?applicableType=10; phiếu nhóm 3 KHÔNG pin được workflow nhóm 5 (409); duyệt 3-4 trạm 1 phiếu nhóm bất kỳ chạy hết → DaDuyet 1
K4 Sidebar eOffice fan-out 8 nhóm × 6 leaf — relabel G1 + thêm G2-G8 (48 leaf) Seed menu (DbInitializer.cs:1780-1787 extend + labelBackfill G1) + grant seeder (SeedKeHoachKyKetAccessAsync:2415 extend — KHKK KHÔNG inherit GetMyMenuTreeQuery.cs:70-74); FE ×2 app: regex staticMap Khkk_G{n}_* + list pages nhận ?group=n; 0 mig (menu = seed DB, restart API — S155) 6-chỗ-mirror S155 (menu + 2 site permission + regex Layout ×2 app); gate FE = đúng key policy KeHoachKyKet #85, KHÔNG OR key con 8 mục + 48 leaf hiện đúng thứ tự; bấm từng leaf list lọc đúng ApprovalGroup; user 13/13 role không dính 403 rải UI; key cũ Khkk_* còn nguyên (0 mồ côi) 2
K5 Cây toàn trình sub-folder nhóm/loại — GĐ2 group-by approvalGroup, GĐ3+GĐ4 group-by c.type (7 loại), thay "Chưa có dữ liệu" BE: KhkkListItemDto +approvalGroup (1 field); FE ×2 app: usePipelineStages.ts + PipelineStageFolders.tsx thêm tầng groups (hoặc content ReactNode khuôn stage1) — MIRROR SHA256 sửa 1 bên PHẢI copy PipelineStage type PipelineStageFolders.tsx:58-72; GĐ3/4 đã có c.type (usePipelineStages.ts:240); sidebar ↔ cây = MỘT bộ nhóm hai mặt (draft §3.iii) Cây gói thầu có phiếu GĐ2 hiện sub-folder nhóm đúng; GĐ3/4 hiện sub-folder theo loại HĐ khớp sidebar; 2 app SHA-identical 2 file pipeline; npm run build ×2 PASS 2
K6 Admin thu gọn — ẩn mục nghiệp vụ cũ, chừa HỆ THỐNG + "Quy trình duyệt (Mới)" FE fe-admin DUY NHẤT: filterForAdmin/isAdminHidden (Layout.tsx:182-190) đảo whitelist; 0 BE 0 mig 0 seed Ẩn ≠ xóa: client filter, revert 1 commit; KHÔNG đụng IsVisible (ăn cả fe-user GetMyMenuTreeQuery.cs:88) Sidebar admin chỉ còn khối HỆ THỐNG (Users/Roles/Permissions/Menu) + Quy trình duyệt (Mới) + 8 nhóm KHKK designer-entry; fe-user KHÔNG đổi (diff bundle fe-user = 0) 2
K7 Cầu KHKK→HĐ + đường ống HĐ V2 (tái xuất W5+W6 S160 — đầu wave RE-ĐO đã land chưa) BE: bridge CreateContractFromEvaluationFeatures.cs đọc ApprovedAmount per-Line + pin ApprovalWorkflowId V2 + ghi Line.ContractId + gộp theo OG-1; GetEligiblePhases/inbox V2 (ContractFeatures.cs:334-351/:421-430 nếu còn legacy-only); FE: nút tạo HĐ từ KHKK DaDuyet ×2 app Khuôn ResolveV2InboxIdsAsync (PE + KHKK ContractSigningPlanFeatures.cs:229 đã có bản mirror); acceptance W6/W7 S160 §7 tái dùng nguyên Từ phiếu KHKK DaDuyet tạo được HĐ đúng số lượng theo OG-1, giaTri==ApprovedAmount; HĐ trình → approver V2 THẤY + inbox; hết kẹt ConflictException (ContractWorkflowService.cs:115-116); test bridge PASS 3
K8 DRY-RUN E2E từ PE/2026/A/049 (gói "14 Mat PVC, vải địa") — toàn trình 4 GĐ trên prod 0 code (chỉ chạy + vá lặt vặt); convention ZZTEST cho record mới; bằng chứng screenshot + phiếu thật + cây đúng sub-folder Checklist S160 §4: HĐ test DaPhatHanh KHÔNG xóa được (guard numeric ContractFeatures.cs:632-633) → admin-override hoặc chấp nhận; notification bắn user thật — dặn team; PE test mới per vòng (pe.ContractId idempotency) Phiếu KHKK từ PE/2026/A/049 đi hết trạm nhóm → HĐ → (GĐ3/4 tới đâu nghiệm thu tới đó); cây gói thầu hiện đủ 4 GĐ có leaf; login prod parse accessToken 3

Cụm cho B2: cụm-1 = K1+K2+K3 (nền data + phiếu + duyệt) · cụm-2 = K4+K5+K6 (UI fan-out — thuần hiển thị, chờ cụm-1 cho field approvalGroup) · cụm-3 = K7+K8 (nối GĐ3 + chạy thật). K6 độc lập hoàn toàn — có thể kéo lên sớm nếu owner muốn thấy admin gọn trước.

E. [OWNER-GATE] list

(ĐANG LÀM)

F. Rủi ro + guard

(ĐANG LÀM)