18 KiB
18 KiB
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
- 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. - Bẫy Proposal-lite (S157 §B.1):
ProposalFeatures.cs:427-439phá OR-of-N (flatten global-level, match 1 user) ⇒ service duyệt PHẢI copyContractWorkflowService.ApproveV2Async:217-394, Proposal chỉ làm khuôn CRUD/menu/FE. - 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).
- 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. - 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ÀIMenuKeys.All⇒ 0 policy riêng, dùng root-policyContracts.{Action}. - 3 lỗ đường ống Contract (S156/S160 §1):
GetEligiblePhases(ContractFeatures.cs:334-351) + inboxPhaseActorRoles100% 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. - Mặt tấn công thật: Reject-trước-guard
ContractWorkflowService.cs:49-66(Approve có guard, Reject không) +DeleteContractguardPhase >= DangInKyso 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). - "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).
- Đã 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ếuKHKK/{YYYY}/{Seq:D3}bỏ token loại · menuKhkk_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/CEOApplicableType=10(W3) · cây 4-folderusePipelineStages×2 app (S162) · gotcha #85 gate FE đúng key policyKeHoachKyKet. - Còn nợ từ S161: W5 cầu KHKK→HĐ chưa làm ⇒
pe.contractIdlà đườ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. - 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)
ContractSigningPlanheader (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.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.- ApplicableType hiện dùng 1-10, slot 11+ trống (
Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs:53-71); comment:60chốt trục "mỗi module 1 type".ContractSigningPlan = 10(:70). - 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ọcisUserSelectable=truetrong bucket type-10 (KhkkCreatePage.tsx:57-64) — IsUserSelectable độc lập IsActive, PATCH per-workflow (ApprovalWorkflowV2AdminFeatures.cs:1164-1177). - KHKK service CỐ Ý KHÔNG port
CeoApprovalThreshold/AllowApproverFinalize(commentContractSigningPlanWorkflowService.cs:44) ⇒ set ngưỡng trên workflow type-10 hôm nay = config-lie #78 (0 consumer). Cột sẵnApprovalWorkflow.cs:43. - 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 roleSeedKeHoachKyKetAccessAsync(:2110,:2415-2424, chạy SAU revoke). Inherit switch vẫn đúng 4 root, KHKK ngoài (GetMyMenuTreeQuery.cs:70-74);IsVisible+DisplayLabelpass-through DTO (:88). - "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 (grepContractSigningPlanDbInitializer = 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-adminLayout.tsx:166-172resolvePath AwV2_ chỉ biết 3 code cũ. - Cây 4-folder:
usePipelineStages.ts(MIRROR SHA256 ×2 app,:3) — GĐ2 nốikhkkByPeId(:110-121), GĐ3/4 nối quape.contractIdĐƠN (:214-217, liên-danh chỉ HĐ[0]), GĐ4 =c.hasSealedCopy(:231),c.typeCÓ SẴN trên leaf (:240navigate/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êmgroups?vào type hoặc truyềncontentReactNode (khuôn stage1Content:66-69). GĐ2 group-by cần field nhóm trênKhkkListItemDto(chưa có); GĐ3/4 group-byc.type= thuần FE. - Sidebar admin = menu-tree ĐỘNG từ
AuthContext.menu+ filter clientfilterForAdmin(fe-admin/Layout.tsx:299,:315,:186-190); hiệnisAdminHiddenchỉ ẩnCt_*(: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). - 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-67theo 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)