wal: flush 20260808T0101

This commit is contained in:
pqhuy1987
2026-08-08 01:01:12 +07:00
parent d6975e2dfd
commit b7140ea9c7
5 changed files with 138 additions and 16 deletions

View File

@ -6,7 +6,7 @@ goal: YC-013 — 8 workflow "Duyệt KHKK — Nhóm 1..8" mirror ĐỦ chức n
chain:
[x] bookend @open 5 vòng + ctx-audit (7/7 garble, recover 7/7) — runs/2026-08-07-S182-bookend-open/
[x] YC-013 ghi sổ nguyên văn (so-yeu-cau:46) + run-folder khkk-awf-mirror-ncc + run.md
[!] wf: B1 3× fable-real invest (BE·DB·FE) — spawn @00:52, artifact-first chống-#53
[!] wf: B1 3× fable-real invest — DB ✅ XONG-TRỌN (18KB, VERDICT 0-MIG + 3 mìn M1/M2/M3) · BE đang resume điền §4/§5 (§1-§3 đã đầy 11.7KB) · FE đang chạy
[ ] B2 lead tổng hợp wave-plan (spec DRAFT)
[ ] B3 2× fable-real reviewer
[ ] B4 spec final

View File

@ -8,6 +8,7 @@ Read-only INTERNAL audit SOLUTION_ERP. Tools: Read/Grep/Glob/Bash + 5 RAG MCP. O
- NOT: external docs/CVE/lib investigator-api · write implementer · test test-specialist · architecture decision em main
## 📌 Recent invest (1-line digest; verbatim → archive + runs)
- **S182 (08-08) ×Fable KHKK 8-nhóm AWF mirror DuyetNcc [`runs/2026-08-08-S182-khkk-awf-mirror-ncc/sub-invest-be-fable.md`]:** BE generic ~85% 8 workflow seed `KHKK-N{n}` CÙNG ApplicableType=10, phân biệt bằng CODE (`DbInitializer.cs:549-687`, idempotent per-Code CỐ Ý khuôn per-type) bất biến only-1-active-per-type VỠ TỪ SEED + POST version mới deactivate CẢ 8 (`ApprovalWorkflowV2AdminFeatures.cs:339-343`); 9-checkbox = 9 cột per-Level dùng chung mọi type nhưng KHKK runtime CHỈ đọc F5 (`ContractSigningPlanWorkflowService.cs:276`) 6/9 tick cho type-10 = #78-class cờ-nằm-im; bypass rào-(v) 2-bước (UpdateDraft re-pin `ContractSigningPlanFeatures.cs:603-607` chỉ check type, không re-check group-match); DELETE version 0 usage-check + `DbUpdateException` KHÔNG được middleware map FK Restrict nổ 500 không phải 409 (`GlobalExceptionMiddleware.cs:39`); 32 policy `AwV2_KhkkN*` 0 consumer BE (0-hit Api + control-dương `KeHoachKyKet` 29-hit cùng path) mirror y 2 key `AwV2_DuyetNcc*` ; FE 8 route KhkkN đổ landing chung (`TYPE_CODE_TO_INT` thiếu key). #53 cắt return giữa phiên artifact đổ-ruột-liên-tục cứu trọn, 0 mất. Tag `[s182, seed-code-family-cung-type, khkk-runtime-chi-F5, policy-menu-only, dbupdate-500]`
- **S180-W1a (08-07) kiểm-kê MÁY-ĐỌC-TÊN [`runs/2026-08-07-S180-adap-upgrade-pack-phased/sub-w1a-mayd-octen.md` 44.9KB]:** 14 đoạn máy (11 vỡ-CÂM · 3 ồn · **0 tự-thích-nghi**) · BÁC 2 (`detectors:1716`+`:1679` = CHÚ THÍCH không phải ; `applied-eval-nokey.ps1` = 0 file-IO, control-dương mfe-eval=14). **Luật rút ra: nhóm tên sạch CHÍNH XÁC tới độ sâu máy KIỂM, không sâu hơn 1 nấc** STOP-HARD KHÔNG cần (`detectors:1639` chỉ INFORM `session-<N>` sạch 12/12); cần **máy TỪ CHỐI**, *đọc-rồi-liệt* (`$unmapped:1109`) + *chấp-2-tên* (`-or :1660`) + *[skip] not found* đều để tên trôi. 🔴 `memory-selfimprove-audit.ps1:298` còn dùng `-Filter 'sub-*.md'` = ĐÚNG bug C11(b) đã @S159 `detectors:1102` **vá tệp A quên tệp B**. Bất biến ngầm 0-máy-kiểm: `agents/<role>.md`.BaseName `agent-memory/<role>/`. Miễn-trừ 5 vùng đóng băng nuốt **73,2% kho .md (1131/1545) + 90,7% lệch đã biết (225/248)** máy soi quét .md trả 0-hit nghĩa; đường thoát = quét **231 tên vật-bộ-máy, 0 miễn trừ**. `CLAUDE.md:151` khớp 64/159=40,3% đĩa, 0 máy chặn, sống nhiều tháng. Tag `[s180, sach-toi-do-sau-may-kiem, va-tep-A-quen-tep-B, mau-so-sau-mien-tru]`
- **S180 (08-07) ×Fable fit-map 4-thư upgrade-pack 7GĐ [`runs/2026-08-07-S180-adap-upgrade-pack-phased/sub-invest-fitmap-S180.md`]:** PHỦ-ĐỦ 7GĐ/12TC/9LỖI. Nghề: hash mesh CHỈ tin máy canonical `scripts/stamp_verify.py` (tự chế MISMATCH cả 4 = suýt phán tamper sai) · TÔI = vai 7/7 dính #53 + bị bắt LỚP-7 tự-giẫm (202K đọc/7KB đĩa) "đổ ruột TRƯỚC đo SAU" cứu; tỉ-lệ byte-đĩa/token-đọc = tín hiệu giám sát · xung đột duy nhất gói: SE exit-0-advisory (`governance-detectors.ps1:1963`) hub cổng-chặn-mã-lỗi VƯỢT-KHUNG · GĐ-5 gần xong nhất / GĐ-4 hổng nhất (0 sổ-yêu-cầu-anh, 0 chấm-điểm) · bác-một-phần lead: `pull_warn_days` trống = CỐ Ý owner khai (`check-email.md:48`) ghost-wire. Tag `[s180, stamp-verify-canonical-only, lop7-tu-giam, exit0-vs-gate]`
- **S175 (08-05) ×Fable cột DỰ ÁN ma-trận NS [seed on-behalf; `runs/2026-08-05-S175-duan-budget-column/`]:** BÁC 2 tiền-đề lead cột = CHỖ-CHỪA-SẴN (`e33481e` cùng-commit comment `PeWorkItemBudget.cs:19`, KHÔNG phải di-chứng Mig50) · `—` = 11 ô/app không phải 3; phả-hệ đo bằng commit-sinh-ra + comment cùng commit, đừng suy từ khoảng-thời-gian-gần. `Project.BudgetTotal` sống từ Mig-2 nhưng Dev 61/69 NULL = "cơ-chế đúng dữ-liệu không có" lần 7. Spawn TASK-RỖNG cứu bằng run.md trên đĩa. Tag `[s175, pha-he-commit, artifact-dia-cuu]`

View File

@ -79,7 +79,41 @@ KHKK runtime = `Infrastructure/Services/ContractSigningPlanWorkflowService.cs` (
- FK `ContractSigningPlans.ApprovalWorkflowId` → ApprovalWorkflows = **Restrict vật lý** (`ContractSigningPlanConfiguration.cs:48-53`).
## §4 Gap-analysis BE → wave đề xuất
(CHƯA ĐO)
### §4.0 Authz đối chiếu 32 policy `AwV2_KhkkN{n}.{R,C,U,D}` (gotcha #85)
**Đo:** grep `AwV2_` toàn `SolutionErp.Api` = **0-hit** (control-dương cùng thước: `KeHoachKyKet` cùng path = 29 hit / 1 file ⇒ vắng thật). ⇒ **32 policy KHÔNG controller nào tiêu thụ** — chúng chỉ sống ở tầng menu-display (sidebar row + Permission matrix). Đây KHÔNG phải bug mới của KHKK: 2 key cũ `AwV2_DuyetNcc`/`AwV2_DuyetNccPhuongAn` (MenuKeys.cs:116-117) cũng 0 consumer BE — trang DuyetNcc hiện hành chạy đúng mô hình này rồi.
- **Key gate THẬT của trang admin (mọi type):** GET = authenticated-only · POST/PUT/PATCH/DELETE = `Workflows.Create` (§1). "Y như trang DuyetNcc" ⇒ **0-đổi-authz**: admin nào có `Workflows.Create` sửa được CẢ 8 nhóm (và cả PE/Contract workflows).
- **Trade-off nếu muốn per-group write-authz** (nhóm N1 chỉ ai được cấp N1 sửa): controller phải resolve workflow→Code→group rồi check `AwV2_KhkkN{n}.Update` — đổi hình authz của controller DÙNG CHUNG 9 type ⇒ blast radius mọi module; và theo #85 endpoint phải gate bằng key CỦA CHÍNH NÓ, không OR key con. **Quyết định thuộc owner/lead — không tự quyết**; mặc định rẻ + nhất quán = giữ `Workflows.Create`.
- 🔸 Menu-row 8 leaf cần `CanRead` grant per-role (KHKK root CỐ Ý không inherit — `GetMyMenuTreeQuery.cs` 4-case; seeder grant explicit) — tầng display độc lập tầng API (memory `permission_grant_two_layers`).
### §4.1 Disposition 4+1 site chặn
| # | Site | Hiện trạng | Đề xuất wave | Rủi ro |
|---|---|---|---|---|
| (a) | **only-ONE-active-per-TYPE** `ApprovalWorkflowV2AdminFeatures.cs:339-343` | POST version mới nhóm N2 ⇒ deactivate CẢ 8 (bất biến đã vỡ từ seed: 8 active cùng type-10). Blast thực tế: pick-list KhkkCreatePage lọc `isUserSelectable` KHÔNG lọc IsActive (`fe-admin/src/pages/khkk/KhkkCreatePage.tsx:97`) + runtime load by-Id ⇒ thiệt hại = Overview `Active` badge/summary + mọi chỗ đọc IsActive về sau | **P1 (khuyên):** đổi scope deactivate = `w.Code == request.Code` khi `typeEnum == ContractSigningPlan` (đúng semantic "1 active per chuỗi-version", 8 chuỗi độc lập). **P2:** đổi per-Code cho MỌI type — sạch hơn nhưng đổi hành vi PE/Contract (hiện tạo Code MỚI cùng type tự tắt Code cũ — có thể có chỗ dựa vào) | P1: if-đặc-thù type-10 (thêm 1 nhánh rẽ); P2: regression PE/Contract khó lường — cần grep chỗ đọc IsActive trước |
| (b) | **Changelog PUT skip type-10** `:863-893` | Sửa workflow nhóm có phiếu KHKK treo ⇒ phiếu 0 vết (comment :857-862 tự khai defer W2/W3) | Wire block mirror PE: query `ContractSigningPlans` pin `def.Id` + Phase ∉ {DaDuyet,TuChoi} → add `ContractSigningPlanChangelogs` (EntityType Workflow, khuôn `ContractSigningPlanWorkflowService.cs:546-557`) | Thấp — bảng phiếu-changelog có sẵn FK Cascade; chỉ thêm 1 nhánh else-if |
| (c) | **DELETE version 0 usage-check** `:1193-1205` | FK `ContractSigningPlans.ApprovalWorkflowId` **Restrict** (`ContractSigningPlanConfiguration.cs:50-53`) ⇒ data KHÔNG mất, nhưng `DbUpdateException` KHÔNG được map (`GlobalExceptionMiddleware.cs:32-39` chỉ map 5 app-exception, `_`**500 "Lỗi hệ thống"**). Phiếu xoá-MỀM vẫn còn row ⇒ vẫn chặn. Workflow từng dùng rồi unpin: opinions FK Restrict vào Levels cũng chặn cascade ⇒ 500 | Guard usage-check trong `DeleteAwDefinitionCommandHandler`: đếm pin per-module (PE `ApprovalWorkflowId`, Contract, Proposal, 4 WorkflowApps, KHKK — 8/9 module có cột pin) + đếm LevelOpinions (tái dùng khuôn `CountLiveOpinionsAsync` NHƯNG theo workflow-id) → 409 đích danh "N phiếu đang pin" | Trung bình — liệt-kê-thủ-công 8 module (đúng class lỗi comment :957-960 đã cảnh báo); nợ này của Mig 22 era, sửa 1 lần lợi mọi type |
| (d) | **Bypass rào (v) 2-bước** `ContractSigningPlanFeatures.cs:603-607` | UpdateDraft re-pin chỉ check type, không check group-match; Submit không re-check ⇒ phiếu N3 chạy đội hình N5 | Extract rào (v) (:479-486) thành `ContractSigningPlanScope.EnsureWorkflowGroupMatchAsync(db, awId, group)` → gọi ở CẢ Create + UpdateDraft; cân nhắc gọi thêm ở `SubmitAsync` làm chốt-cuối (khuôn 2-tầng như hạng-mục :179-187) | Thấp; chú ý phiếu cũ đã lỡ pin lệch trước vá sẽ kẹt submit — message phải chỉ việc cần làm |
| (e) | **Ép-false F2 type-10** `:387` | ĐÚNG theo chống config-lie #78 (service KHKK 0 đọc F2 — `ContractSigningPlanWorkflowService.cs:47`) | **GIỮ** cho tới khi có lệnh owner port F2. Nếu port: copy nhánh PE `PurchaseEvaluationWorkflowService.cs:797-810` vào service KHKK + gỡ ép-false + gỡ khoá FE **CÙNG WAVE** (đúng tiền lệ F5 @K3: port + gỡ ép-false + gỡ FE-lock 1 wave, comment :376-386) | Mở mà không port = đẻ lại #78; port mà quên gỡ ép-false = cờ chết chiều ngược |
### §4.2 Việc 0-đổi-BE (đã generic sẵn — wave FE nối thẳng)
1. GET Overview `?applicableType=10` trả đủ 8 nhóm × versions × 9-flag per-level (§1) — FE lọc client-side theo `code == "KHKK-N{n}"` (payload nhỏ; muốn sạch hơn có thể thêm query param `code` — optional, không blocker).
2. PUT sửa-tại-chỗ (thêm/bớt người, đổi NV, 7 cờ tại-chỗ, đổi tên/phòng) — T1/T2 + guard chữ-ký 3-site ĐÃ phủ KHKK (§1-bis).
3. PATCH user-selectable + DELETE (khi chưa pin) — generic by-Id.
4. Lịch sử versions (History list per code) — có sẵn trong Overview DTO.
5. Runtime approve/return/reject/submit KHKK + F5 finalize + inbox + notify — W3/K3 xong (§3).
6. Authz: giữ `Workflows.Create` = y-hệt trang DuyetNcc (§4.0).
### §4.3 Việc PHẢI đổi BE (gom wave)
- **Wave-BE-1 (nhỏ, an toàn):** (a)-P1 deactivate-per-Code cho type-10 · (d) group-match guard 2-site · (b) changelog type-10 · (c) usage-check DELETE → 409. 4 việc đều trong 2 file `ApprovalWorkflowV2AdminFeatures.cs` + `ContractSigningPlanFeatures.cs` (+1 helper Scope), 0 migration.
- **Wave-BE-2 (chỉ khi owner duyệt semantics):** port return-modes F1×4 / F3-analog (sửa Lines lúc duyệt) / F6 approver-delete / F2 skip-to-final vào KHKK runtime — mỗi cái là ĐỔI HÀNH VI DUYỆT, thuộc quyền owner (comment (c) service :48-50 đã khai chủ-đích chưa đọc). Nếu KHÔNG port: FE phải ẩn/disable các ô chưa-wire cho type-10 (tiền lệ `earlyFinalizeLocked` W1 — đã có khuôn), không thì mỗi ô tick = config-lie #78 mới.
## §5 Surprises ngoài đề bài
(CHƯA ĐO)
1. **8 link sidebar KhkkN{n} hiện đổ về trang landing chung**`Layout.tsx:188` route `/system/approval-workflows-v2/KhkkN{n}` nhưng `ApprovalWorkflowsV2Page.tsx` `TYPE_CODE_TO_INT` (:164-172) KHÔNG có key `KhkkN*``selectedTypeInt=undefined` → rơi về grid landing (:299-301,:323). Menu có, trang chưa phân nhóm — wave FE là phần nặng nhất của đề bài, BE gần xong.
2. **GET Overview 0-policy trả đủ 9-flag + CeoApprovalThreshold + tên/email toàn bộ approver cho MỌI user authenticated** (kể cả fe-user token) — cố ý từ Mig 22 cho Drafter pick (:10-15), nhưng payload nay giàu hơn nhiều so 2026-05; đáng để owner ngó lại (information-disclosure mềm).
3. **Roster 8 workflow = SNAPSHOT lúc seed theo email hardcode** (`DbInitializer.cs:554-585`) — QT-DN-V2-001 prod đổi người thì 8 nhóm KHÔNG tự theo; idempotent per-Code nên restart không re-sync (đúng thiết kế, nhưng dễ bị hiểu nhầm "sao đội hình không khớp DuyetNcc nữa").
4. **Bất biến only-1-active-per-type đã vỡ từ seed** (8 active type-10) mà Overview `Active = versions.FirstOrDefault(IsActive)` (:210) chỉ hiện 1 — 7 nhóm còn lại "tàng hình" khỏi ô Active của type-summary; FE per-group phải đọc theo Code, đừng tin ô `Active`.
5. `DetachTrackedOpinionsOn` comment "7 bảng" stale (thực 8 — quét metadata nên vẫn đúng hành vi) `ApprovalWorkflowV2AdminFeatures.cs:1120`.
6. KHKK delete phiếu allow-list ĐÃ rộng hơn PE: {Nháp, TuChoi, **TraLai**} (S177 owner) vs PE {DangSoanThao, TuChoi} + F6-đúng-lượt (`ContractSigningPlanFeatures.cs:1733-1738` vs `PurchaseEvaluationFeatures.cs:1404-1406` + `PeSoftDeleteFeatures.cs:110-114`) — mirror "nút XÓA ở màn duyệt" cho KHKK là F6-wire (wave-BE-2), không phải nới allow-list thêm.
## VERDICT
BE cho 8 workflow "Duyệt KHKK — Nhóm 1..8" đã **generic ~85%**: controller + 5 action + PUT sửa-tại-chỗ + guard chữ-ký + ghim/xoá + seed 8 nhóm (Code `KHKK-N{n}`, cùng ApplicableType=10) + runtime F5 finalize đều SỐNG. **4 site BE phải sửa** (wave-BE-1, 2 file, 0-mig): deactivate-per-Code type-10 (`:339-343`) · group-match guard thêm 2-site (`:603-607`+Submit) · changelog type-10 (`:863-893`) · usage-check DELETE→409 (`:1193-1205`). **6/9 checkbox hiện chỉ PE tôn trọng** — tick cho type-10 = cờ nằm im (#78-class): hoặc port runtime (wave-BE-2, cần owner duyệt semantics), hoặc FE khoá ô theo tiền lệ `earlyFinalizeLocked`. Authz giữ `Workflows.Create` y trang DuyetNcc; 32 policy `AwV2_KhkkN*` 0 consumer BE (đo + control-dương). Phần nặng còn lại là FE (8 route đổ landing chung).

View File

@ -86,20 +86,65 @@ Cột nằm trên bảng V2 DÙNG CHUNG mọi ApplicableType ⇒ workflow KHKK (
`ApprovalWorkflowId Guid?` (pin, Restrict) · `CurrentWorkflowStepIndex int?` (:336) · `CurrentApprovalLevelOrder int?` (:333) · `EndedByLevelFinalize bit` (:351) · `ApprovalGroup int` (:321) · `Phase int` · AuditableEntity (IsDeleted/DeletedAt/DeletedBy — phiếu SOFT-delete, ≠ workflow) · UNIQUE filtered `MaKeHoach` (:395-397) · index `(ApprovalGroup, Phase)` (:403).
### 8 workflow nhóm = 8 row `ApprovalWorkflows` phân loại thế nào?
CHƯA ĐO — cần: seed DbInitializer + cơ chế resolve nhóm→workflow (Code? riêng gì?)
- Seed `SeedKhkkGroupWorkflowsAsync` (`DbInitializer.cs:549-687`): 8 row `Code="KHKK-N{n}"` n=1..8, `Version=1`, `ApplicableType=10`, `Name="Duyệt KHKK — Nhóm {n}"`, `IsActive=true`, `IsUserSelectable=true` (:609-625). 3 Bước PRO→CCM→BOD, Cấp OR-of-N, riêng Cấp 2 CCM (a Chương) `AllowApproverFinalize=true` (:575-579, :655). Idempotent **PER-CODE** (:610) — cố ý né bẫy check-theo-type (:540-542). Resolve người theo EMAIL, thiếu user → bỏ slot + log, workflow 0 bước vẫn TẠO (:544-548).
- **Liên kết nhóm↔workflow = QUY ƯỚC CODE, không phải cột/FK:** phiếu chốt `ApprovalGroup` lúc tạo (default 1, validator 1..8 `ContractSigningPlanFeatures.cs:415-418, :461`); rào (v) regex `^KHKK-N([1-8])$` trên Code của workflow user pick — lệch nhóm = 409, Code NGOÀI họ (type-10 chung) **cho qua** (:475-486). `ContractCatalogEntry.ApprovalGroup int` 1..8 (validator `ContractCatalogFeatures.cs:120`, KHÔNG CHECK constraint) quyết nhóm của hạng mục; line-gán guard khớp nhóm phiếu (`:691, :802`).
- Con trỏ duyệt KHKK: service `ContractSigningPlanWorkflowService.cs` GHI đủ cặp `CurrentWorkflowStepIndex` (0-based index list sorted) + `CurrentApprovalLevelOrder` (1-based) (:201-202, :295, :309-310) + `EndedByLevelFinalize=true` nhánh finalize (:275-282 — ghost-window Mig 71 ĐÃ ĐÓNG, K3 live) + null-out khi terminal (:323-324). Đọc pointer từ CHÍNH workflow đã pin, KHÔNG đụng `IsActive` (:422-446, có bounds-check) ⇒ **version Archived (IsActive=false) không làm phiếu treo kẹt**.
- Mig 72 `AddMaHangMucToContractSigningPlanLines` (`20260806070242`): +`MaHangMuc nvarchar(20)` denorm + backfill từ catalog Code, idempotent (:13-34) — mig CUỐI trên đĩa.
## §4 Dữ liệu thật LocalDB Dev (sqlcmd -E)
## §4 Dữ liệu thật LocalDB Dev (sqlcmd -E, đo 2026-08-08)
CHƯA ĐO
| Phép đo | Kết quả |
|---|---|
| `ApprovalWorkflows` theo type | chỉ type {3,4,5,6,7,9} × 1 row (6 seeder mẫu). **Type 1/2 (DuyetNcc): 0 row. Type 10 (KHKK): 0 row** |
| 8 workflow `KHKK-N*` | **0/8 trên Dev local** — seeder idempotent sẽ tạo ở lần boot API kế; QT-DN-V2-001 v01-v04 là dữ liệu PROD (admin tạo tay), local không có |
| `ContractSigningPlans` | 0 phiếu · 0 LevelOpinions · 0 Approvals |
| `__EFMigrationsHistory` Dev | top = `20260731085624` (**Mig 71**) — **Mig 72 trên đĩa CHƯA áp** |
| `__EFMigrationsHistory` Design | top = Mig 71 — cùng drift |
## §5 Verdict migration + risk concurrency/integrity
**Câu "8 row đủ chưa, IsUserSelectable bao nhiêu" trên PROD: CHƯA ĐO** (SSH chết khi load SQL-client — S134/S148/S177). Local suy được từ seed-code: lần boot prod sau commit K3 đã tạo 8 row `IsActive=1, IsUserSelectable=1`; ảnh sidebar YC-013 hiện 8 mục = seed đã chạy prod. Query bàn giao implementer chạy trên prod:
```sql
SELECT Code, Version, IsActive, IsUserSelectable FROM ApprovalWorkflows WHERE ApplicableType=10 ORDER BY Code, Version;
SELECT ApprovalGroup, Phase, COUNT(*) n FROM ContractSigningPlans GROUP BY ApprovalGroup, Phase;
SELECT COUNT(*) FROM ContractSigningPlanLevelOpinions;
```
CHƯA ĐO
## §5 Verdict migration + risk
### VERDICT: **0 MIGRATION.** Schema V2 dùng chung đã đủ 100% cho mirror
1. 9/9 checkbox = 9 cột bit có sẵn trên `ApprovalWorkflowLevels` (§1b), bảng dùng chung mọi ApplicableType.
2. Versioning v01→v04 + Archived: UNIQUE `(Code, Version)` + `IsActive` bit — đã phục vụ đúng cơ chế này cho `QT-DN-V2-001` v04; `KHKK-N{n}` là Code như mọi Code.
3. Ghim = `IsUserSelectable` sẵn; Sửa-tại-chỗ = máy Update diff-by-Id ĐÃ KHKK-aware ở guard chữ ký (3/3 site :961/:1033/:1099); Xoá = máy Delete sẵn (thiếu guard là thiếu CODE, không thiếu cột).
4. Pin + con trỏ + finalize-flag phiếu KHKK: đủ từ Mig 69/71, service đã ghi/đọc sống.
5. Hạng-mục/nhóm: `ApprovalGroup` (phiếu + catalog) + regex Code — 0 nhu cầu cột mới.
### Nhưng 3 QUẢ MÌN CODE-LEVEL lộ ra từ scan DB-semantics (fix = BE code, vẫn 0 mig)
| # | Mìn | Bằng chứng | Hệ quả nếu bỏ qua |
|---|---|---|---|
| M1 | **Create-new-version deactivate theo `ApplicableType`** — tạo v02 cho `KHKK-N1` sẽ `IsActive=false` cả active-version của N2..N8 (8 Code chung type=10; giả định 1-Code-1-type vỡ) | `ApprovalWorkflowV2AdminFeatures.cs:339-343`; đối chiếu seeder đã né same-class `DbInitializer.cs:540-542` | Overview 7 nhóm kia mất "Active" (thành Archived-oan); mọi consumer lọc IsActive theo type sai |
| M2 | **Overview API `Active = FirstOrDefault(IsActive)` per-TYPE + KHÔNG có param Code** — type 10 có 8 chuỗi version trộn chung 1 `History`, `Active` lấy 1 trong 8 | `:107-109` (query 2 param), `:203-212` | Trang admin 8 mục sidebar không tách được version-list per nhóm nếu FE không tự group by Code |
| M3 | **Delete = hard-delete 0 guard usage** — phiếu pin (Restrict) / opinion (Restrict) → SqlException 547 → 500 thô; version rác thì xoá êm | `:1187-1204`; FK snapshot :6245-6248, :6297-6301 | UX 500 khó hiểu thay 409; data KHÔNG mất (DB chặn được) |
### Risk integrity/concurrency chi tiết
- **Sửa workflow đang có phiếu KHKK treo:** an toàn cấu trúc — mọi mutation đổi-đường-đi bị 409; đổi người/cờ giữ nguyên `Level.Id` ⇒ pointer (StepIndex, LevelOrder) vẫn giải được; guard chữ ký quét cả `ContractSigningPlanLevelOpinions` (sống + retained); transaction rào :744/:899. **Lỗ nhỏ:** vết sửa KHÔNG ghi vào `ContractSigningPlanChangelogs` (chỉ PE :863-892, defer F-6b) — phiếu KHKK đang chạy bị sửa quy trình mà lịch sử phiếu im lặng.
- **Xoá version Archived có phiếu treo/xong trỏ vào:** bị Restrict chặn (pin `ApprovalWorkflowId`) — kể cả phiếu soft-deleted (row còn, FK còn). Pointer phiếu KHÔNG chết vì không xoá được. Riêng `ContractSigningPlanApprovals.ApprovalWorkflowLevelId` LOOSE (0 FK) — nếu tương lai có đường hard-delete phiếu/level thì history giữ Guid treo (hiện không có đường đó).
- **Race 2 admin cùng "Tạo version mới" cùng Code:** UNIQUE (Code,Version) đỡ — kẻ thua 2601/2627 → 500 (không silent-dup). Chấp nhận được cho màn quản trị.
- **Race tạo 2 phiếu cùng (PE, nhóm):** rào handler check-then-insert, CỐ Ý không filtered-unique (`ContractSigningPlanFeatures.cs:456-460`, gate-K2 F-7) — giữ nguyên quyết định cũ, ngoài scope.
## §6 Đề xuất wave DB
CHƯA ĐO
- **W-DB-0 (prep local, bắt buộc trước test):** `dotnet ef database update` ×2 DB (Dev + Design đang kẹt Mig 71, đĩa 72) + boot API 1 lần để `SeedKhkkGroupWorkflowsAsync` đẻ 8 row local. 0 mig mới.
- **W-DB-1 (BE, đi cùng wave mirror):** vá M1 — scope deactivate trong `CreateAwDefinitionCommandHandler` đổi `ApplicableType``Code` (hoặc Code∧Type); test: tạo v2 `KHKK-N1` ⇒ N2..N8 `IsActive` bất biến + case DuyetNcc regression.
- **W-DB-2 (BE):** overview/API thêm chiều Code (param `code=` hoặc group per-Code trong `AwTypeSummaryDto`) để 8 trang nhóm đọc đúng chuỗi version của mình.
- **W-DB-3 (BE, khuyến nghị):** guard lịch sự cho Delete — pre-count pin (PE/HĐ/KHKK/5 office) + LevelOpinions → 409 kể tên hồ sơ (tái dùng khuôn `CountLiveOpinionsAsync`), thay 547→500.
- **W-DB-4 (BE, optional):** wire changelog KHKK khi sửa-tại-chỗ (bảng `ContractSigningPlanChangelogs` sẵn — mirror khối PE :863-892).
- **Verify prod (bàn giao implementer):** 3 query §4.
## §7 Surprises ngoài đề bài
1. **Local DB drift:** Mig 72 committed trên đĩa (2026-08-06) nhưng CẢ Dev lẫn Design local dừng Mig 71 — pattern committed-but-unapplied-local; test wave nào chạm Lines sẽ lệch schema nếu quên update.
2. **Dev local 0 row type-1/2 (DuyetNcc)** — workflow NCC prod tạo tay qua UI, không seed ⇒ mọi test local về trang DuyetNcc cần tự tạo workflow trước.
3. Memory cũ S155 "WF-V2 KHÔNG có lệnh Update" đã STALE — `UpdateAwDefinitionCommand` (sửa-tại-chỗ diff-by-Id, giữ Id Level) đã land sau đó, guard rất dày.
4. Seeder KHKK resolve người theo EMAIL cứng (11 email prod `DbInitializer.cs:556-584`) — DB thiếu user (local!) ⇒ workflow RỖNG 0 bước vẫn được tạo + log warning; trang designer local sẽ thấy 8 workflow trống người, KHÔNG phải bug.
5. `HaveSequentialOrders` (Create :306-316) — validator jagged-levels per-step chỉ chạy path Create; Update path dùng bộ guard riêng (T1 set-equality) — 2 bộ luật song song cùng file, drift-risk nếu wave sau chỉnh 1 quên 1.
(chưa có)
---
*Đo: ModelSnapshot + entity + 2 handler-file + DbInitializer + Mig 69/70/71/72 + sqlcmd Dev/Design. Prod: CHƯA ĐO (SSH giới hạn). ~62K token.*

View File

@ -30,16 +30,58 @@
9 cờ gửi qua 1 hàm chung `levelFlagsPayload:255-267` (2 call-site POST `:820` + PUT `:788` — chống silent-drop F6/F-7 S155). Rule-box xanh cuối dialog `:1343-1350`. Ô "Ngưỡng CEO" `:944-961` render cho MỌI type (kể cả 10).
## §2 8 mục KHKK sidebar hiện map đi đâu (4-chỗ pattern 16-bis)
CHƯA ĐO
## §3 Delta "ko lệch" — KIND_CONFIG entries cần thêm + hardcode-PE-label sites
CHƯA ĐO
| Chỗ | File:line | Trạng thái |
|---|---|---|
| 1. BE const + `All` | `MenuKeys.cs:127-134` + `:248-249` | ✅ 8 key `AwV2_KhkkN1..8` VÀO `All` (⇒ 32 policy) |
| 2. BE menu-row seed | `DbInitializer.cs:1988-1995` | ✅ label "Duyệt KHKK — Nhóm {n}", parent `ApprovalWorkflowsV2` (root :1981 dưới System), order 3..10, icon FileSignature; Admin auto-grant qua `SeedAdminPermissionsAsync` (comment :1984-1987) |
| 3. FE `lib/menuKeys.ts` mirror | `fe-admin:31-32` / `fe-user:31-32` | ❌ chỉ có `AwV2_DuyetNcc(+PhuongAn)`**0 KhkkN**. VÔ HẠI runtime: sidebar render BE-tree + `resolvePath` string thô; 0 call-site `can('AwV2_*')`; route `/system/approval-workflows-v2` **KHÔNG PermissionGuard** (`App.tsx:79-80` route trần). Optional mirror-completeness |
| 4. FE `resolvePath` | `fe-admin/src/components/Layout.tsx:188` | ✅ `/^KhkkN[1-8]$/``/system/approval-workflows-v2/KhkkN{n}` (K3 S164/S165, CHỈ nới họ `AwV2_*`) |
**Bấm vào hôm nay ra gì:** route khớp `App.tsx:80``ApprovalWorkflowsV2Page`, nhưng `TYPE_CODE_TO_INT['KhkkN3'] = undefined``currentType=null`**LANDING GRID mọi type** (`:323-343`, header "Quy trình duyệt (Mới)" + hint "chọn loại từ menu"). **CẢ 8 mục ra CÙNG landing — KHÔNG ra khuôn DuyetNcc panel. Đây chính là "lệch".** (Không phải ComingSoon, không 404.)
**Sibling:** root AwV2 có leaf 1-2 = DuyetNcc/PhuongAn (`:1982-1983`); KHÔNG có leaf `AwV2_Contract` seed (Layout `:178` chỉ phục vụ deep-link).
## §3 Delta "ko lệch" — cần thêm gì + hardcode-PE-label sites
**Danh tính nhóm = `Code`, KHÔNG phải ApplicableType.** 8 workflow seed `SeedKhkkGroupWorkflowsAsync` (`DbInitializer.cs:549-625`): `Code="KHKK-N{n}"`, **CÙNG `ApplicableType=10`**, Version=1, `IsActive=true` cả 8, `IsUserSelectable=true`, 3 trạm PRO→CCM→BGĐ sao roster Duyệt NCC prod. BE guard pin: regex `^KHKK-N([1-8])$` phải khớp `plan.ApprovalGroup` (`ContractSigningPlanFeatures.cs:479-486`). FE helper sẵn **2 app identical**: `khkkGroupWorkflowCode(n)='KHKK-N{n}'` + `khkkGroupMenuLabel(n)` (`fe-{admin,user}/src/types/khkk.ts:159-168`). Precedent slice-by-code đã chạy ở fe-user `WorkflowMatrixViewPage.tsx:84-99` (K4c: client-side filter `wf.code === khkkGroupWorkflowCode(g)`, không thêm trục group vào queryKey).
**Delta CHÍNH — 1 file `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx`** (page đã generic theo `:typeCode`; KHÔNG thêm route/menu/BE-seed):
1. `:164-172` — thêm chiều parse: `const khkkKindMatch = typeCode?.match(/^KhkkN([1-8])$/)``khkkGroup: 1..8 | null`; `selectedTypeInt = khkkGroup ? 10 : TYPE_CODE_TO_INT[typeCode]` (overview query giữ nguyên `applicableType=10`).
2. `:299-301``currentType` cho kind nhóm = **synthetic slice**: từ `types.find(t=>t.applicableType===10)` lấy `versions = history.filter(d => d.code === khkkGroupWorkflowCode(g))`; `active = versions.find(v=>v.isActive) ?? null`; `applicableTypeLabel = 'Duyệt KHKK — Nhóm {g}'` (menu-label BE `:1988`) — **D4** anh chọn nhãn này hay nhãn nghiệp vụ `khkkGroupMenuLabel(g)` = "{g}. Thiết bị, máy móc…" (đề xuất: title menu-label + phụ đề nghiệp vụ).
3. `TypePanel:357` + `Designer:620` — prop mới `lockedCode?: string`: Designer `defaultCode → lockedCode ?? DEFAULT_CODE_BY_TYPE[...]` (`:650-651`), ô Mã `disabled={isEdit || !!lockedCode}` (`:927`), POST gửi `lockedCode ?? code` (`:798`). **Thiếu khoá này thì "Tạo quy trình mới" từ panel nhóm 3 với mã tự gõ sẽ đẻ version rơi khỏi slice → biến mất khỏi panel** (và không được guard `:482` nhận là workflow nhóm).
4. Không đổi gì khác của khuôn: 4 nút phải, badge, Lịch sử versions, dialog 9-checkbox, rule-box — dùng nguyên `TypePanel`/`DefinitionCard`/`Designer` (đúng "ko lệch"). SkipToFinal đã tự disable cho type-10 (`:1292-1302`) — 8 kind thừa hưởng vì cùng applicableType=10.
**Hardcode-label sites trong khuôn chung (đánh dấu — KHÔNG tự quyết):**
- `:1275` "Cho phép chỉnh sửa **Section 2 (Hạng mục/NCC/Báo giá)** lúc đang duyệt" — từ vựng PE. KHKK service **KHÔNG đọc cờ này** (0-hit `AllowApproverEditDetails` trong `ContractSigningPlan*.cs`).
- `:1284` "Section ngân sách" — tương tự, 0-hit `AllowApproverEditBudget`.
- `:944-961` ô "**Ngưỡng giá trị gói CEO**" render mọi type — KHKK service **CỐ Ý KHÔNG port** `CeoApprovalThreshold` (`ContractSigningPlanWorkflowService.cs:46-47`: NĐUQ = human-gate vĩnh viễn, OG-3) ⇒ nhập cho kind KHKK = config-lie class #78. Khuôn disable-có-tooltip sẵn ở `:1292`.
- Badge "✎ NS PRO/NS CCM" (`:560-565`) — suy từ role PE-budget; hiển thị trên panel KHKK như đang hiển thị cho type-10 hiện nay.
- **Răng thật của 9 checkbox với KHKK hôm nay: CHỈ #8 `AllowApproverFinalize` sống** (`ContractSigningPlanWorkflowService.cs:276`); 4 cờ Trả-về không đọc — Trả lại KHKK = TOÀN PHẦN luôn-được (`:48,:354-362`); Delete/EditDetails/EditBudget/SkipToFinal: 0-hit. **D3:** giữ nguyên khuôn (đúng "ko lệch", như panel ContractSigningPlan đang render) hay disable 7 ô chết theo khuôn SkipToFinal — anh/lead chốt; mặc định nghiêng GIỮ (uniform, cờ nằm chờ port).
## §4 fe-user — IsUserSelectable tại create KHKK + designer admin-only proof
CHƯA ĐO
- **Tạo phiếu KHKK KHÔNG có dropdown chọn quy trình** — workflow GÁN CỨNG theo nhóm (owner S174): `fe-user/src/pages/khkk/KhkkCreatePage.tsx:96-116` query `applicableType=10`**filter `w.isUserSelectable` `:104`**`autoWorkflow = find(code === 'KHKK-N{group}')` `:113-116`; thiếu mã ⇒ CHẶN tạo + báo "Chưa có quy trình KHKK-N{n}" (`:128,:155`). `fe-admin/src/pages/khkk/KhkkCreatePage.tsx` mirror cùng khuôn (`:97-100`). ⇒ **Bỏ ghim (`IsUserSelectable=false`) 1 workflow nhóm = CHẶN TẠO PHIẾU nhóm đó** ở cả 2 app — nút Ghim/Bỏ ghim trên 8 panel mới có hệ quả nghiệp vụ thật.
- Consumer thứ 2 `fe-user/src/pages/khkk/KhkkDetailPage.tsx:214-227` = bridge KHKK→HĐ, query `applicableType=3` lọc `isUserSelectable && isActive` — không liên quan nhóm.
- **Designer admin-only — CHỨNG:** grep `approval-workflows-v2` toàn `fe-user/src` = chỉ API-consumer (Create/Detail/Matrix/Proposal/WorkflowApp) — **0 page designer, 0 route `/system/approval-workflows-v2`**, Layout fe-user **0 nhánh `AwV2_*`** (grep 0-hit ngoài `lib/menuKeys.ts:31-32` const chết). fe-user chỉ có view read-only `pages/pe/WorkflowMatrixViewPage.tsx` (`/khkk/workflow-matrix?type=10&group=n`, K4c slice cùng convention code).
## §5 Wave FE đề xuất + acceptance
CHƯA ĐO
**W-FE1 (fe-admin, 1 file):** `ApprovalWorkflowsV2Page.tsx` — 4 chạm §3 (parse kind `:164-172` · synthetic slice `:299-301` · `lockedCode` xuyên `TypePanel:345-350/:357/:447-455``Designer:620/:650/:798/:927` · title). Import từ `types/khkk.ts` (đã có sẵn trong fe-admin). Ước ~60-90 dòng diff.
**W-FE2 (optional):** `fe-{admin,user}/src/lib/menuKeys.ts` +8 const `AwV2_KhkkN*` (mirror-completeness, 0 hệ quả runtime).
**SHA-mirror 2 app: KHÔNG áp dụng** — trang system Designer là fe-admin-only (chứng §4); `types/khkk.ts` 2 app đã identical sẵn, không chạm.
**BE:** 0 bắt buộc cho khuôn hiển thị; **D1 (khuyến nghị làm cùng wave):** `ApprovalWorkflowV2AdminFeatures.cs:339-343` deactivate-per-TYPE — sửa thành per-Code khi code khớp `^KHKK-N[1-8]$` (hoặc per-Code toàn cục — semantic đổi, anh chốt).
**Acceptance đo được:**
1. Login admin → sidebar Hệ thống Quy trình duyệt (Mới) hiện 10 leaf; bấm "Duyệt KHKK — Nhóm 3" → URL `/system/approval-workflows-v2/KhkkN3` → header "Quy trình duyệt (Mới): Duyệt KHKK — Nhóm 3" + card `KHKK-N3 v01` badge "Đang áp dụng"+"Cho user chọn" + 3 Bước (PRO 4NV+TP·CCM 4NV+TP·BGĐ CEO) + cột 4 nút phải + "Lịch sử versions"/"Tạo quy trình mới". 8/8 leaf → 8 slice khác nhau, hết landing grid.
2. "Sửa tại chỗ" → dialog title "Sửa tại chỗ — KHKK-N3 v01 (giữ nguyên phiên bản)", per-NV khối cam "QUYỀN DUYỆT …" 9 checkbox 2 cột, "Trả về Drafter (mặc định)" tick sẵn, dòng xanh KẾT THÚC + dòng đỏ XÓA phiếu, SkipToFinal mờ + tooltip, rule-box cuối.
3. "Tạo quy trình mới" từ panel nhóm: ô Mã khoá `KHKK-N3` → lưu ra v02 nằm ĐÚNG panel nhóm 3; (nếu D1 làm) badge "Đang áp dụng" 7 nhóm khác KHÔNG mất.
4. Regression: `/DuyetNcc` `/DuyetNccPhuongAn` `/Contract` `/ContractSigningPlan` render như cũ; fe-user tạo phiếu nhóm 3 vẫn auto-pin `KHKK-N3` (không đổi API).
## §6 Surprises ngoài đề
CHƯA ĐO
1. 🔴 **Panel `ContractSigningPlan` (đích của `Khkk_WfView` admin, `Layout.tsx:40`) hôm nay GIẤU 7/8 workflow nhóm:** BE `Active = FirstOrDefault(IsActive)` per-type (`ApprovalWorkflowV2AdminFeatures.cs:210`, tie-break Version DESC không xác định giữa 8 bản v1) + FE history filter `!d.isActive` (`:419,:426`) ⇒ 8 bản đều active thì 1 hiện card, 7 KHÔNG hiện đâu cả. Slice-by-code của wave này vá đúng bệnh; cân nhắc re-point `Khkk_WfView` hoặc để panel aggregate liệt theo code.
2. 🔴 **Bom deactivate-per-TYPE** (`:339-343`): bấm "Tạo quy trình mới"/"Lưu + kích hoạt" cho BẤT KỲ mã nào thuộc type-10 ⇒ `IsActive=false` CẢ 8 nhóm (seeder không re-set — idempotent per-Code `:610` skip). Runtime tạo phiếu KHÔNG hỏng (pin theo `IsUserSelectable`, guard type `EnsureWorkflowTypeAsync:307-318` không đòi IsActive) nhưng badge/active-slice sai toàn bộ. = D1.
3. Seeder KHKK resolve người duyệt THEO EMAIL, thiếu user ⇒ bỏ slot/cấp/bước + log warning (`DbInitializer.cs:544-548,:597-600`) — DB dev thiếu người thật thì 8 panel có thể hiện bước rỗng/thiếu NV; không phải bug FE.
4. `AwV2_Contract` không có menu-leaf seed (chỉ deep-link `Layout.tsx:178`) — sibling lệch nhẹ, không thuộc scope.
5. Route FE `/system/approval-workflows-v2` KHÔNG PermissionGuard (App.tsx:79-80) — 2-tầng gotcha #82: hiển thị dựa menu-grant, authz thật ở BE controller.