wal: flush 20260808T0057

This commit is contained in:
pqhuy1987
2026-08-08 00:57:41 +07:00
parent ba95a68e3d
commit d6975e2dfd
3 changed files with 171 additions and 12 deletions

View File

@ -3,13 +3,80 @@
> Trạng thái: ĐANG ĐO — đổ ruột liên tục. Mỗi claim kèm file:line.
## §1 Endpoint map trang approval-workflows-v2 (admin)
(CHƯA ĐO)
**Controller duy nhất:** `src/Backend/SolutionErp.Api/Controllers/ApprovalWorkflowsV2Controller.cs` — route `api/approval-workflows-v2` (:17), class-level `[Authorize]` trần (:18).
| Action | Verb+Route | Authz | file:line |
|---|---|---|---|
| Overview (list all types + versions + history) | `GET ?applicableType=&isUserSelectable=` | **authenticated-only, 0 policy** (CỐ Ý — Drafter cần list để pick lúc create, comment :10-15) | :21-26 |
| Tạo version mới | `POST` | `Workflows.Create` | :28-34 |
| Sửa-tại-chỗ (giữ Id/Version/IsActive) | `PUT {id}` | `Workflows.Create` | :45-51 |
| Ghim/Bỏ ghim (`IsUserSelectable`) | `PATCH {id}/user-selectable` | `Workflows.Create` | :56-62 |
| Xoá version | `DELETE {id}` | `Workflows.Create` | :64-70 |
**Tham số phân loại = `ApplicableType` (int enum), KHÔNG code-prefix.** Enum `ApprovalWorkflowApplicableType` @ `Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs:53-71`: DuyetNcc=1 · DuyetNccPhuongAn=2 · Contract=3 · Proposal/Leave/Ot/Vehicle/ItTicket/Travel=4-9 · **ContractSigningPlan=10** (:70). Trang DuyetNcc = `?applicableType=1`; "NCC và Giải pháp" = 2; KHKK = 10.
**Handler Overview:** `Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs` — filter generic `d.ApplicableType == typeEnum` (:123-127) + `IsUserSelectable` (:129-132); khi `applicableType=null` group theo `Enum.GetValues<>()` toàn bộ (:199-213) ⇒ **GENERIC, 0 hardcode chặn type-10**. Label map `AwLabels.Type` :90-98 CÓ entry type-10 "Kế hoạch ký kết HĐ" (:97) — nhưng label này là 1-per-type, KHÔNG phân biệt 8 nhóm N1-N8 (8 nhóm cùng type=10 sẽ gộp chung 1 `AwTypeSummaryDto` với 1 `Active` duy nhất — xem §2 hệ quả).
**9 checkbox = 9 cột `bool` per-Level row** trên `ApprovalWorkflowLevel` (`ApprovalWorkflow.cs:99-176`) — schema DÙNG CHUNG mọi ApplicableType:
| # | Checkbox UI | Flag | line |
|---|---|---|---|
| 1 | Trả về 1 Cấp trước | `AllowReturnOneLevel` (F1m1) | :120 |
| 2 | Trả về 1 Bước trước | `AllowReturnOneStep` (F1m2) | :123 |
| 3 | Trả về Người chỉ định | `AllowReturnToAssignee` (F1m3) | :126 |
| 4 | Trả về Drafter (default) | `AllowReturnToDrafter` (F1m4, default TRUE) | :129 |
| 5 | Sửa Section 2 lúc duyệt | `AllowApproverEditDetails` (F3) | :133 |
| 6 | Sửa Section ngân sách | `AllowApproverEditBudget` (F4) | :139 |
| 7 | Duyệt thẳng Cấp cuối | `AllowApproverSkipToFinal` (F2) | :147 |
| 8 | Duyệt là KẾT THÚC (không CEO) | `AllowApproverFinalize` (F5) | :158 |
| 9 | NV XÓA phiếu khi đến lượt | `AllowApproverDelete` (F6, Mig 68) | :173 |
**🔴 Create handler ĐÃ CÓ nhánh type-10 đích danh** (`ApprovalWorkflowV2AdminFeatures.cs:387`): `AllowApproverSkipToFinal = typeEnum != ContractSigningPlan && l.AllowApproverSkipToFinal`**ép-false F2 CHO RIÊNG type-10** (K3-test O-1: KHKK service 0 lần đọc cờ này, echo true = config-lie #78). `AllowApproverFinalize` từng ép-false @S161 nhưng ĐÃ GỠ @S165 (service KHKK port level-finalize) — comment :376-386.
**PUT sửa-tại-chỗ (S155 F-1):** T1 khung-không-đổi (tập Step.Order + Level.Order bất biến, :552-559 + :572-574 + :593-595 + :636-648) + T2 luật-chơi-không-đổi (Code/ApplicableType/CeoApprovalThreshold/F5/F2 = ECHO-only, lệch ⇒ 409, :539-550 + :598-603). Guard chữ-ký 2 tầng (:665-724): `CountLiveOpinionsAsync` + `ListRetainedOpinionsAsync` — cần kiểm 2 helper này có đọc `ContractSigningPlanLevelOpinions` không (xem §2/§4). Transaction rào @ :744-745.
**Deactivate-per-type khi POST:** `:339-343` — deactivate MỌI workflow `ApplicableType == typeEnum && IsActive`**only-ONE-active-per-TYPE. Với 8 nhóm KHKK cùng type=10: tạo version mới cho N2 sẽ TẮT IsActive của N1..N8 còn lại** ⇒ đây là site chặn #1 (xem §2).
### §1-bis Phần sau của AdminFeatures (line 800-1205)
- **Guard chữ-ký 3-site ĐÃ phủ KHKK 3/3:** `CountLiveOpinionsAsync` :961-965 + `ListRetainedOpinionsAsync` :1033-1038 + `PurgeOrphanOpinionsAsync` :1099-1102 đều có nhánh `ContractSigningPlanLevelOpinions` (site liệt-kê-thủ-công, comment :957-960 tự khai luật "module mới PHẢI vào CẢ 3"). `DetachTrackedOpinionsOn` :1122-1136 quét theo TÊN CỘT metadata ⇒ tự phủ (comment :1120 nói "7 bảng" — stale nhẹ, thực tế 8).
- **🔴 GAP changelog PUT sửa-tại-chỗ:** `:863-893` chỉ ghi vết vào `PurchaseEvaluationChangelogs` khi `ApplicableType ∈ {DuyetNcc, DuyetNccPhuongAn}`**KHKK type-10 bị SKIP** (comment :857-862 tự khai: `ContractSigningPlanChangelogs` là changelog PHIẾU, "wire nếu cần ở W2/W3" — CHƯA wire). Sửa workflow nhóm KHKK đang có phiếu treo ⇒ phiếu 0 vết, chỉ có `def.UpdatedAt` (:851).
- **🔴 GAP DELETE version 0 usage-check:** `DeleteAwDefinitionCommandHandler` :1193-1205 = load + `Remove` + Save, **KHÔNG kiểm phiếu đang pin** (comment :1187-1189 tự khai "Sau UAT ... cần check usage trước khi delete" — nợ từ Mig 22 era). Hard-delete; hành vi thực tế phụ thuộc FK của cột pin từng module (cần đo FK `ContractSigningPlans.ApprovalWorkflowId` — xem §2).
- **SetUserSelectable** :1173-1185 generic by-Id ⇒ type-10 dùng được 0-đổi.
## §2 Seed 8 workflow nhóm KHKK + phân loại
(CHƯA ĐO)
**Seed = `DbInitializer.SeedKhkkGroupWorkflowsAsync`** (`Infrastructure/Persistence/DbInitializer.cs:549-687`, K3 nhịp-1 S164/S165) — KHÔNG phải Mig 71 (Mig 71 chỉ AddColumn `ApprovalGroup` lên phiếu + backfill=1; workflow ROWS do DbInitializer đẻ lúc boot, INFRASTRUCTURE seed not-gated DemoSeed, comment :539).
- **Phân loại 8 nhóm = `Code` string `KHKK-N{n}`** (:609), n=1..8 (:607). TẤT CẢ 8 cùng `ApplicableType=ContractSigningPlan(10)` (:616). Name `"Duyệt KHKK — Nhóm {n}"` (:617). Version=1, `IsActive=true` + `IsUserSelectable=true` cả 8 (:615,:622-623).
- **Idempotent PER-CODE** `AnyAsync(w.Code == code)` (:610) — comment :540-542 tự khai vì sao KHÔNG dùng khuôn per-type của 6 seeder mẫu: 8 workflow dùng chung 1 type, check type sẽ chặn 7 nhóm còn lại.
- **Đội hình 3 trạm PRO→CCM→BGĐ** sao từ QT-DN-V2-001 prod (roster :554-585): PRO C1 4-người OR-of-N + C2 tra.bui · CCM C1 4-người + C2 chuong.phan **`AllowApproverFinalize=true`** (:576-579) · BGĐ C1 truong.nguyen. Resolve theo EMAIL, thiếu user ⇒ bỏ slot + warn không throw (:593-600), cấp rỗng ⇒ nén Order (:645), workflow 0-bước vẫn tạo giữ mã (:673-676).
- **8 nhóm hiện SỐNG CHUNG IsActive=true cùng type=10** ⇒ bất biến "only-ONE-active-per-type" của POST create (`ApprovalWorkflowV2AdminFeatures.cs:339-343`) ĐÃ VỠ từ lúc seed (8 active cùng type). Hệ quả: **POST tạo version mới cho BẤT KỲ nhóm nào sẽ deactivate CẢ 8** — site chặn đích danh #1.
- Sidebar 8 menu-row `AwV2_KhkkN1..N8` seed @ `DbInitializer.cs:1988-1995`, parent `ApprovalWorkflowsV2`; MenuKeys VÀO `All` (`MenuKeys.cs:246-249`) ⇒ 32 policy `AwV2_KhkkN{n}.{R,C,U,D}` tồn tại.
## §3 Runtime duyệt KHKK — bảng 9-flag × {PE-runtime, KHKK-runtime}
(CHƯA ĐO)
KHKK runtime = `Infrastructure/Services/ContractSigningPlanWorkflowService.cs` (W3 S161 + K3 S165). 4 action cố định `submit/approve/return/reject` (:74-75), KHÔNG có `targetPhase` param. OR-of-N `ResolveActingLevel` (:455-469), UPSERT `ContractSigningPlanLevelOpinions` (:477-513), con-trỏ ĐÔI reset-Cấp-1-khi-sang-Bước (:309-310), submit reset về Bước1/Cấp1 cả 2 lối vào (:198-202).
| # | Flag | PE-runtime | KHKK-runtime | Evidence KHKK |
|---|---|---|---|---|
| 1 | `AllowReturnOneLevel` | TÔN TRỌNG — `PurchaseEvaluationWorkflowService.cs:421` (switch `WorkflowReturnMode`) | **BỎ QUA** — return luôn = `TraLai` toàn phần | `ContractSigningPlanWorkflowService.cs:48-50` comment (c) "4 return-mode per-level CỐ Ý chưa đọc"; `ReturnOrRejectAsync:354-400` 0 đọc flag |
| 2 | `AllowReturnOneStep` | TÔN TRỌNG — `:422` | **BỎ QUA** — nt | nt |
| 3 | `AllowReturnToAssignee` | TÔN TRỌNG — `:423` | **BỎ QUA** — nt | nt |
| 4 | `AllowReturnToDrafter` | TÔN TRỌNG — `:424` (mode Drafter phải bật cờ) | **BỎ QUA (hành vi = luôn-cho)** — return về Drafter cho MỌI NV đúng lượt, kể cả cờ tắt | `ReturnOrRejectAsync` chỉ guard đúng-lượt (:379-380), không đọc cờ |
| 5 | `AllowApproverEditDetails` (F3) | TÔN TRỌNG — `PurchaseEvaluationDetailFeatures.cs:84` + `PurchaseEvaluationSupplierFeatures.cs:45` | **BỎ QUA / CHƯA WIRE** — KHKK Lines/Dossier edit = drafter-only Nháp/TraLai | `EnsureDraftEditable` `ContractSigningPlanFeatures.cs:295-303` (mọi đường ghi W2); 0 hit `AllowApproverEditDetails` trong ContractSigningPlan* |
| 6 | `AllowApproverEditBudget` (F4) | TÔN TRỌNG — `PurchaseEvaluationFeatures.cs:411` | **BỎ QUA / PE-semantics** (KHKK không có section NS; giá dòng = `ApprovedAmount ??= ProposedAmount` chốt tại finalize `:345-349`) | 0 hit trong ContractSigningPlan* |
| 7 | `AllowApproverSkipToFinal` (F2) | TÔN TRỌNG — `PurchaseEvaluationWorkflowService.cs:810` (+ Contract `:335`) | **CHƯA WIRE + BỊ ÉP-FALSE tại POST create cho type-10** | service 0 đọc (comment `:47` "giữ nguyên không port"); ép-false `ApprovalWorkflowV2AdminFeatures.cs:387` |
| 8 | `AllowApproverFinalize` (F5) | TÔN TRỌNG — `:870` (opt-out `applyLevelFinalize`) | **TÔN TRỌNG** — port @K3 S165, opt-out param mirror PE S96, set `EndedByLevelFinalize` runtime-flag (Mig 71) | `ContractSigningPlanWorkflowService.cs:276-289`; seed bật cho TP.CCM (`DbInitializer.cs:576-579,:655`) |
| 9 | `AllowApproverDelete` (F6) | TÔN TRỌNG — `PeSoftDeleteFeatures.cs:110-114` (3 rào: policy ∧ đúng-lượt ∧ F6) | **BỎ QUA / CHƯA WIRE** — delete KHKK không có nhánh approver-đúng-lượt (xem §3-bis delete handler) | 0 hit `AllowApproverDelete` trong ContractSigningPlan* |
- `CeoApprovalThreshold` (workflow-level, NGOÀI 9 checkbox): PE đọc; KHKK **CỐ Ý KHÔNG port** (NĐUQ human-gate vĩnh viễn, OG-3 — comment `:46-47`).
- DTO detail KHKK ĐÃ expose `AllowApproverFinalize` per-level (`ContractSigningPlanFeatures.cs:140,:1013,:1030`) — các flag khác không expose (PE expose đủ 9 @ `PurchaseEvaluationDtos.cs:124-138`).
### §3-bis Surprises quanh pin/đổi workflow (evidence create/update path)
- **Rào (v) group-match CHỈ chạy lúc CREATE:** `ContractSigningPlanFeatures.cs:479-486` regex `^KHKK-N([1-8])$` so `approvalGroup`; workflow code NGOÀI họ (type-10 code khác) → CHO QUA (comment :477-478).
- **🔴 UpdateDraft re-pin (:603-607) chỉ gọi `EnsureWorkflowTypeAsync` (type=10, :307-318), KHÔNG re-check group-match** ⇒ phiếu nhóm 3 tạo đúng KHKK-N3 rồi PUT draft re-pin KHKK-N5 = 200 OK; SubmitAsync cũng không re-check workflow-code↔group (`ContractSigningPlanWorkflowService.cs:169-187` chỉ check Lines↔group) ⇒ **bypass rào (v) 2-bước** — phiếu nhóm 3 chạy đội hình nhóm 5.
- FK `ContractSigningPlans.ApprovalWorkflowId` → ApprovalWorkflows = **Restrict vật lý** (`ContractSigningPlanConfiguration.cs:48-53`).
## §4 Gap-analysis BE → wave đề xuất
(CHƯA ĐO)

View File

@ -4,21 +4,89 @@
> Câu hỏi trung tâm: **schema hiện tại ĐỦ chưa hay cần mig mới?** Kỳ vọng "máy rộng UI hẹp" ⇒ 0 mig — PHẢI CHỨNG.
> Trạng thái file: đổ ruột liên tục (chống-#53). Khu chưa đo ghi `CHƯA ĐO`.
## §1 Schema V2 — cột thật 3 bảng (từ EF Configuration + ModelSnapshot)
## §1 Schema V2 — cột thật 3 bảng (từ ModelSnapshot + entity)
CHƯA ĐO
Nguồn: `ApplicationDbContextModelSnapshot.cs:128-313` + `src/Backend/SolutionErp.Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs` (1 file chứa cả 3 entity).
### §1b Bảng ánh xạ 9 checkbox ↔ cột
### `ApprovalWorkflows` (snapshot :128-187)
| Cột | Kiểu | Ghi chú |
|---|---|---|
| Id | uniqueidentifier PK | BaseEntity |
| Code | nvarchar(100) NOT NULL | "QT-DN-V2-001" |
| Version | int | monotonic per Code |
| ApplicableType | int | enum 1..10 — **ContractSigningPlan=10** (`ApprovalWorkflow.cs:70`) |
| Name | nvarchar(200) NOT NULL | |
| Description | nvarchar(1000) NULL | |
| IsActive | bit | |
| ActivatedAt | datetime2 NULL | |
| IsUserSelectable | bit | Mig 25 — Ghim/Bỏ ghim |
| CeoApprovalThreshold | decimal(18,2) NULL | Mig 53 — ngưỡng gói CEO (PE semantic) |
| CreatedAt/CreatedBy/UpdatedAt/UpdatedBy | | BaseEntity audit |
CHƯA ĐO
Index: `(ApplicableType, IsActive)` + **UNIQUE `(Code, Version)`** (:181-184). 🔴 **KHÔNG có IsDeleted**`ApprovalWorkflow : BaseEntity` (entity :21), KHÔNG AuditableEntity ⇒ 0 soft-delete, 0 query-filter trên cả 3 bảng V2.
## §2 Versioning — Code/Version/Archived/unique/sửa-tại-chỗ/tạo-version/xoá
### `ApprovalWorkflowSteps` (snapshot :274-313)
Id · ApprovalWorkflowId (FK) · Order int · Name nvarchar(200) NOT NULL · DepartmentId uniqueidentifier NULL (hint) · audit 4 cột. Index: `DepartmentId` + `(ApprovalWorkflowId, Order)`.
CHƯA ĐO
### `ApprovalWorkflowLevels` (snapshot :189-272)
Id · ApprovalWorkflowStepId (FK) · Order int (Cấp, OR-of-N rows cùng Order) · Name nvarchar(200) NULL · ApproverUserId NOT NULL (1 row = 1 NV slot) · audit 4 cột · **9 cờ bit Allow\***. Index: `ApproverUserId` + `(ApprovalWorkflowStepId, Order)`.
## §3 KHKK wiring — Mig 71 catalog + ContractSigningPlans pin/pointer/finalize-flag
### §1b Bảng ánh xạ 9 checkbox ảnh ↔ cột — **9/9 CÓ ĐỦ, 0 cột thiếu**
CHƯA ĐO
Per-NV = per-row `ApprovalWorkflowLevels` (mỗi row = 1 ApproverUserId ⇒ cờ trên row chính là cờ per-NV).
| # | Checkbox (ảnh YC-013) | Cột thật | Default (snapshot) | Mig gốc |
|---|---|---|---|---|
| 1 | Trả về 1 Cấp trước | `AllowReturnOneLevel` | false (:220-223) | 29 |
| 2 | Trả về 1 Bước trước | `AllowReturnOneStep` | false (:225-228) | 29 |
| 3 | Trả về Người chỉ định | `AllowReturnToAssignee` | false (:230-233) | 29 |
| 4 | Trả về Drafter (mặc định) | `AllowReturnToDrafter` | **true** (:235-238) | 29 |
| 5 | Sửa Section 2 | `AllowApproverEditDetails` | false (:205-208) | 29 |
| 6 | Sửa Section ngân sách | `AllowApproverEditBudget` | false (:200-203) | 30 |
| 7 | Duyệt thẳng Cấp cuối | `AllowApproverSkipToFinal` | false (:215-218) | 31 |
| 8 | Duyệt là KẾT THÚC | `AllowApproverFinalize` | false (:210-213) | 58 |
| 9 | XÓA phiếu | `AllowApproverDelete` | false (:195-198) | 68 |
Cột nằm trên bảng V2 DÙNG CHUNG mọi ApplicableType ⇒ workflow KHKK (ApplicableType=10) **lưu được cả 9 cờ ngay hôm nay, 0 mig**. ⚠️ Ranh giới lens: cột TỒN TẠI ≠ service KHKK ĐỌC cờ (semantic #3 đọc `PeLevelOpinions`, #5/#6 đọc section PE — entity comment `ApprovalWorkflow.cs:119-173`; KHKK service có honor không = lens BE).
## §2 Versioning — đọc từ `ApprovalWorkflowV2AdminFeatures.cs` (KHÔNG đoán từ UI)
- **Lưu version:** cùng `Code` nhiều row, `Version int` monotonic; **UNIQUE (Code, Version)** (snapshot :183-184). "Archived" KHÔNG có cột riêng — chỉ là `IsActive=false` (nhãn UI suy từ bit).
- **Tạo phiên bản mới** = `CreateAwDefinitionCommandHandler` (:326-): INSERT row mới `Version = max(Code)+1` (:334-337), `IsActive=true, IsUserSelectable=true` (:352-353). 🔴 **LANDMINE cho KHKK:** deactivate cũ chạy theo **`ApplicableType`** chứ KHÔNG theo Code — `Where(w.ApplicableType == typeEnum && w.IsActive)` set `IsActive=false` TẤT CẢ (:339-343). Với DuyetNcc (1 code/type) đúng; với KHKK **8 code chung type=10** ⇒ tạo version mới cho `KHKK-N1` sẽ **tắt IsActive của cả KHKK-N2..N8**. Chính seeder đã né bẫy same-class ở check idempotent (`DbInitializer.cs:540-542` "check theo type sẽ chặn 7 nhóm còn lại") nhưng Create handler CHƯA vá — vá-tệp-A-quên-tệp-B. **Fix = code BE (đổi scope deactivate theo Code), 0 mig.**
- **Sửa tại chỗ** = `UpdateAwDefinitionCommandHandler` (:515-900): UPDATE in-place ĐÚNG row + **diff Levels theo `Level.Id` — GIỮ NGUYÊN Id** (:561-634, "F-3 CẤM delete-then-insert") ⇒ không đẻ Id mới, không mồ-côi con trỏ (khác nỗi lo F17/S155 — máy Update nay ĐÃ TỒN TẠI, memory cũ "WF-V2 0 lệnh Update" STALE). Cấm in-place: đổi Code/ApplicableType/CeoThreshold (:539-550), thêm/bớt/đảo Bước (:553-574), đổi Order dòng (:593), toggle 2 cờ đổi-đường-đi F5/F2 (:598-603), bỏ người giữ F5/F2 (:627-632), làm rỗng Cấp (:639-647) — tất cả 409 + gợi ý tạo version mới. 7 cờ còn lại + đổi người + thêm/bỏ người trong Cấp = hợp lệ in-place (:789-797).
- **Guard chữ ký khi bỏ/đổi người:** 2 tầng quét **CẢ 8 bảng LevelOpinions** gồm `ContractSigningPlanLevelOpinions` — count-sống (:961-965) + retained (:1033-1038); transaction rào (:744-745, :895-899). ⇒ máy sửa-tại-chỗ **ĐÃ KHKK-aware ở tầng guard**.
- ⚠️ Vết changelog khi sửa-tại-chỗ chỉ ghi cho **PE** (`ApplicableType ∈ {DuyetNcc, DuyetNccPhuongAn}` :863-892); KHKK khai-giới-hạn defer F-6b (:857-862) — bảng `ContractSigningPlanChangelogs` ĐÃ TỒN TẠI (Mig 69) ⇒ wire = code BE thuần, **0 mig**.
- **Ghim/Bỏ ghim** = `SetAwUserSelectableCommandHandler` (:1173-1185): flip 1 bit, generic mọi type — KHKK dùng ngay.
- **Xoá version** = `DeleteAwDefinitionCommandHandler` (:1193-1205): **HARD delete** (`Remove` + BaseEntity không soft-delete), **0 guard usage** — comment tự khai ":1187-1189 Sau UAT … cần check usage trước khi delete". Phiếu pin (PE/HĐ/KHKK Restrict) hoặc LevelOpinion tồn tại ⇒ SqlException 547 → 500. Version không ai đụng ⇒ xoá sạch (cascade Steps→Levels).
## §3 KHKK wiring — Mig 70/71 + ContractSigningPlans pin/pointer/finalize-flag
### FK map V2 (snapshot :6210-6323) — quyết định risk xoá
| Quan hệ | Behavior |
|---|---|
| Steps → ApprovalWorkflows | **Cascade** (:6229-6233) |
| Levels → Steps | **Cascade** (:6212-6216) |
| Levels.ApproverUserId → Users | Restrict (:6218-6222) |
| Steps.DepartmentId → Departments | Restrict (:6235-6238) |
| `ContractSigningPlans.ApprovalWorkflowId` → ApprovalWorkflows | **Restrict** (:6245-6248) |
| `Contracts.ApprovalWorkflowId` → | Restrict (:6327-6330) |
| `PurchaseEvaluations.ApprovalWorkflowId` → | Restrict (:6755-6758) |
| `ContractSigningPlanLevelOpinions.ApprovalWorkflowLevelId` → Levels | **Restrict** (:6297-6301) |
| `ContractSigningPlanApprovals.ApprovalWorkflowLevelId` | ⚠️ **LOOSE Guid — 0 FK** (cột + index :416/:455 nhưng relationship section :6251-6260 chỉ có 1 HasOne về Plan Cascade) |
⇒ Hard-delete 1 workflow version bị DB CHẶN (SqlException 547) nếu: (a) bất kỳ phiếu PE/HĐ/KHKK pin nó, HOẶC (b) bất kỳ LevelOpinion (PE/HĐ/KHKK) trỏ Level của nó — cascade Steps→Levels đụng Restrict của LevelOpinions.
### Mig 71 `AddKhkkApprovalGroupCatalogAndFinalizeRuntime` (`20260731085624_*.cs`)
- **KHÔNG có bảng catalog-nhóm-duyệt riêng** — "catalog" trong tên mig = gắn `ContractSigningPlanLines.CatalogEntryId` (loose-Guid, KHÔNG FK, :70-74) + `TenHangMuc` denorm (:76-81) trỏ về `ContractCatalogEntries` (Mig 70, bảng danh mục 86 dòng).
- `ContractSigningPlans.ApprovalGroup int NOT NULL default 0` + backfill vô-điều-kiện `SET = 1` (:47-61) — nhóm duyệt 1..8 lưu TRÊN PHIẾU dạng int trần (0 bảng, 0 FK, 0 CHECK constraint 1..8).
- `EndedByLevelFinalize bit` runtime-flag (:63-68) — ghost-window khai trước, K3 tiêu thụ.
- Index-swap: UNIQUE `(PlanId, SupplierId)` → UNIQUE `(PlanId, SupplierId, CatalogEntryId)` filtered `[IsDeleted]=0` (:43-45, :93-98). Down có bẫy F-8 (:132-141).
### `ContractSigningPlans` pin + pointer (snapshot :315-408) — mirror ĐỦ khuôn PE
`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ì?)
## §4 Dữ liệu thật LocalDB Dev (sqlcmd -E)

View File

@ -3,7 +3,31 @@
> Fable deep-pass đơn lẻ, /fable-real investigator-codebase. Đề: 8 mục sidebar "Duyệt KHKK — Nhóm 1..8" phải ra ĐÚNG trang `system/approval-workflows-v2/DuyetNcc` khuôn ảnh anh gửi. 0 sửa code.
## §1 Trang DuyetNcc render từ đâu (route + page + KIND_CONFIG + dialog)
CHƯA ĐO
**Route:** `fe-admin/src/App.tsx:79-80``/system/approval-workflows-v2` + `/system/approval-workflows-v2/:typeCode` → cùng 1 component `ApprovalWorkflowsV2Page` (named export, `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx` 1355 dòng).
**KIND map (KHÔNG phải KIND_CONFIG object đầy đủ kiểu S29/S35 — chỉ 2 Record thưa):**
- `TYPE_CODE_TO_INT` `:164-172` = `{ DuyetNcc:1, DuyetNccPhuongAn:2, Contract:3, ContractSigningPlan:10 }`**0 entry `KhkkN*`**.
- `DEFAULT_CODE_BY_TYPE` `:173-180` = `{1:'QT-DN-V2-001', 2:'QT-DN-PA-V2-001', 3:'QT-HD-V2-001', 10:'QT-KHKK-V2-001'}` (chỉ gợi-ý ô Mã).
- Nhãn hiển thị KHÔNG có bảng FE — lấy từ BE `applicableTypeLabel` (`AwLabels.Type`, `ApprovalWorkflowV2AdminFeatures.cs:97` có entry type-10 "Kế hoạch ký kết HĐ").
**Cơ chế render `:286-353`:** `selectedTypeInt = TYPE_CODE_TO_INT[typeCode]`; unknown typeCode ⇒ `undefined``currentType=null` ⇒ render **LANDING GRID** mọi type (`:323-343`) thay vì panel. Panel thật = `TypePanel` `:357-459` (active card + "Lịch sử versions" + nút "Tạo quy trình mới") — mỗi panel khoá vào ĐÚNG 1 `applicableType`, active = `type.active` (BE tính), history render **filter `!d.isActive`** (`:419,:426`).
**Cột nút phải (khuôn ảnh):** `DefinitionCard:577-612` — Sửa tại chỗ `:587` / Tạo phiên bản mới `:596` / Ghim-Bỏ ghim `:606` / Xoá version `:610`. Badge: Đang áp dụng `:490` · Cho user chọn `:502` · Ngưỡng CEO `:506-513`. Badge NS PRO/CCM per NV `:560-565` (display-only suy từ role).
**Dialog Sửa tại chỗ / Tạo mới:** cùng component `Designer:620-1354`, `isEdit = editTarget !== null` `:638`; title "Sửa tại chỗ — {code} v{NN} (giữ nguyên phiên bản)" `:868`. Khối cam "QUYỀN DUYỆT {TÊN}" `:1228-1329` = **9 checkbox** map field TS (`EditLevelEntry:100-123` / API `LevelDto:37-62`):
| # | Label UI | Field |
|---|---|---|
|1| Trả về 1 Cấp trước | `allowReturnOneLevel` |
|2| Trả về 1 Bước trước | `allowReturnOneStep` |
|3| Trả về Người chỉ định | `allowReturnToAssignee` |
|4| Trả về Drafter (mặc định) — tick sẵn | `allowReturnToDrafter` (default true `:231`) |
|5| Chỉnh sửa **Section 2 (Hạng mục/NCC/Báo giá)** | `allowApproverEditDetails` `:1275` |
|6| Chỉnh sửa Section ngân sách | `allowApproverEditBudget` `:1284` |
|7| Duyệt thẳng Cấp cuối | `allowApproverSkipToFinal` — 🔴 **DISABLED khi type=10** `:1292-1302` (K3: service KHKK chưa port SkipToFinal, BE ép-false) |
|8| Duyệt là KẾT THÚC quy trình (xanh emerald) | `allowApproverFinalize` `:1307-1315` |
|9| Cho phép NV này XÓA phiếu (đỏ rose) | `allowApproverDelete` `:1320-1328` |
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