wal: flush 20260808T0122
This commit is contained in:
@ -45,8 +45,58 @@
|
|||||||
- 🔴 **MỚI — IsActive server-side cho KHKK-create = KHÔNG CÓ:** bridge KHKK→HĐ đã vá đòi `IsActive ∧ IsUserSelectable` (vá-3, `CreateContractFromSigningPlanFeatures.cs:141-147` — chính comment nêu "khuôn ContractFeatures thiếu 2 vế ⇒ forge POST pin được quy trình retired"); Create/UpdateDraft phiếu KHKK thì CHỈ check type ⇒ pin bản archived = 200. fe-user KhkkCreatePage `:104` filter `isUserSelectable` KHÔNG lọc `isActive`, `:114` `find(code)` ăn theo ordering Version-DESC. Hệ quả SAU W1a: (a) tạo v02 xong, v01 vẫn selectable=true (POST không đụng cờ này) — auto-pin trúng v02 chỉ NHỜ ordering; (b) "Bỏ ghim v02" ⇒ rơi về v01 archived im lặng, KHÔNG chặn tạo như spec mô tả răng nút Ghim.
|
- 🔴 **MỚI — IsActive server-side cho KHKK-create = KHÔNG CÓ:** bridge KHKK→HĐ đã vá đòi `IsActive ∧ IsUserSelectable` (vá-3, `CreateContractFromSigningPlanFeatures.cs:141-147` — chính comment nêu "khuôn ContractFeatures thiếu 2 vế ⇒ forge POST pin được quy trình retired"); Create/UpdateDraft phiếu KHKK thì CHỈ check type ⇒ pin bản archived = 200. fe-user KhkkCreatePage `:104` filter `isUserSelectable` KHÔNG lọc `isActive`, `:114` `find(code)` ăn theo ordering Version-DESC. Hệ quả SAU W1a: (a) tạo v02 xong, v01 vẫn selectable=true (POST không đụng cờ này) — auto-pin trúng v02 chỉ NHỜ ordering; (b) "Bỏ ghim v02" ⇒ rơi về v01 archived im lặng, KHÔNG chặn tạo như spec mô tả răng nút Ghim.
|
||||||
- **Grep IsActive consumer (W1a regression):** Office features 0 lọc IsActive trên ApprovalWorkflows (mọi hit IsActive = Users/MeetingRooms/OtPolicies/Catalog) · V1 `WorkflowDefinitions.IsActive` không liên quan · consumer THẬT của IsActive-V2: Overview `Active` (display) + bridge type-3 vá-3 + FE dropdowns (PE/Contract create lọc client-side). KHÔNG AI resolve "active-per-type-10" server-side ⇒ P1 đổi scope type-10 an toàn, P2 không cần.
|
- **Grep IsActive consumer (W1a regression):** Office features 0 lọc IsActive trên ApprovalWorkflows (mọi hit IsActive = Users/MeetingRooms/OtPolicies/Catalog) · V1 `WorkflowDefinitions.IsActive` không liên quan · consumer THẬT của IsActive-V2: Overview `Active` (display) + bridge type-3 vá-3 + FE dropdowns (PE/Contract create lọc client-side). KHÔNG AI resolve "active-per-type-10" server-side ⇒ P1 đổi scope type-10 an toàn, P2 không cần.
|
||||||
|
|
||||||
|
## ĐO TRỰC TIẾP (đợt 3 — service/seeder/status/FE-designer)
|
||||||
|
- **HELD:** service KHKK comment `:36-65` tự khai (c) 4 return-mode CỐ Ý chưa đọc + "mở thêm mode là đổi hành vi duyệt, thuộc quyền lead" · finalize `:276-289` = #8 sống thật · Submit là entry-point controller-gọi-thẳng (`:60-62` — xác nhận KHÔNG có MediatR handler cho Submit).
|
||||||
|
- **HELD:** seeder `DbInitializer.cs:539-625` đúng như invest (idempotent per-Code `:610`, roster email prod, `IsActive=true + IsUserSelectable=true` `:622-623`, thiếu user → bỏ slot + warn).
|
||||||
|
- **HELD (control):** grep `AwV2_` trong `SolutionErp.Api` = **0 hit** — khớp claim "32 policy 0-consumer BE".
|
||||||
|
- **Đo FE Designer:** "Tạo quy trình mới" (`TypePanel:413`) set `cloneFrom = type.active` ⇒ ở panel nhóm (synthetic slice) code state tự prefill `KHKK-N{g}` từ card active — lockedCode là RÀO chống sửa tay + chống case active=null (rơi về `DEFAULT_CODE_BY_TYPE[10]='QT-KHKK-V2-001'` `:650`). Archived card cũng đủ 4 nút (`:426-443`) — slice pass-through by-Id OK.
|
||||||
|
- **Đo STATUS.md:** canonical dòng 8 = **"644 test PASS (45D+599I — CI #444 KHỚP local)"** @S168; mốc sống S179 không ghi đổi test. Spec W4 ghi "baseline 645" — lệch 1, không rõ nguồn.
|
||||||
|
|
||||||
## FINDINGS
|
## FINDINGS
|
||||||
- (đang đổ)
|
|
||||||
|
> Ký hiệu: `[C]` phải sửa spec trước khi execute · `[M]` lỗ/claim-sai phải vá trong spec · `[m]` khai/polish. Mỗi finding: chỗ sai (spec/invest) → bằng chứng file:line → cách vá.
|
||||||
|
|
||||||
|
**F1 [C] — W1b không biên dịch được như viết (helper internal ⟂ Submit nằm Infrastructure).**
|
||||||
|
Spec W1b: *"extract rào (v) → `EnsureWorkflowGroupMatchAsync(db, awId, group)`; gọi ở Create + UpdateDraft + Submit"*. Đo: `ContractSigningPlanScope` = `internal static` (`ContractSigningPlanFeatures.cs:263`); `InternalsVisibleTo` duy nhất = `SolutionErp.Infrastructure.Tests` (`SolutionErp.Application.csproj:25`); Submit KHÔNG có Application-handler — controller gọi thẳng service Infrastructure (`ContractSigningPlanFeatures.cs:19` + `ContractSigningPlanWorkflowService.cs:60-62`) ⇒ helper để trong Scope thì site thứ 3 KHÔNG gọi được, executor sẽ tự chế đường khác ngoài review.
|
||||||
|
**Vá:** tạo `public static class ContractSigningPlanWorkflowGuards` (Application/ContractSigningPlans, file cạnh Scope) chứa helper; Create/UpdateDraft (Application) + SubmitAsync (Infrastructure — đã reference Application) cùng gọi. KHÔNG dùng `InternalsVisibleTo(Infrastructure)` (mở toàn bộ internal surface 2 assembly) · KHÔNG inline-duplicate (drift 3 bản regex).
|
||||||
|
|
||||||
|
**F2 [C] — W1d claim "hết cảnh 547 → 500" là claim-mạnh-hơn-fix: thiếu `IgnoreQueryFilters()`.**
|
||||||
|
PE + Contract + KHKK đều có `HasQueryFilter(!IsDeleted)` (`PurchaseEvaluationConfiguration.cs:84` · `ContractConfiguration.cs:44` · `ContractSigningPlanConfiguration.cs:55`). Đếm-pin naive sẽ KHÔNG THẤY phiếu xoá-mềm, nhưng FK vật lý vẫn chặn (row còn) ⇒ đúng ca PHỔ BIẾN NHẤT (drafter xoá phiếu nháp/TraLai — allow-list KHKK S177 — rồi admin xoá version "không ai dùng") vẫn 547→500. Test trong spec ("DELETE version có pin ⇒ 409") PASS mà bug ship — acceptance đo hình-dạng không đo hành-vi (@S179 class).
|
||||||
|
**Vá:** mọi count pin/opinion trong guard dùng `IgnoreQueryFilters()` (3 module có filter; 5 office không filter — đếm thẳng); opinions đếm CẢ row phiếu-đã-xoá (FK không quan tâm IsDeleted). Test bổ sung: phiếu KHKK đã xoá-mềm pin version → DELETE 409.
|
||||||
|
|
||||||
|
**F3 [M] — DB-invest M3 "data KHÔNG mất (DB chặn được)" SAI cho 5 module Office — pin LOOSE 0-FK; W1d là guard DUY NHẤT.**
|
||||||
|
Đo snapshot: `HasForeignKey("ApprovalWorkflowId")` = **4 hit** (Steps-Cascade `:6231` + KHKK `:6247` + Contract `:6329` + PE `:6757`). Domain có **8 cột pin** (`Proposal/LeaveRequest/OtRequest/VehicleBooking/TravelRequest` thêm vào 3 module trên; ItTicket KHÔNG có — "8/9" của BE-invest ĐÚNG). ⇒ DELETE hôm nay với phiếu Office pin (0 opinions) **thành công vật lý** → dangling pin → phiếu treo chết lúc load workflow. Spec W1d đếm đủ 8 module (đúng), nhưng rationale kế thừa claim sai của DB-invest — executor đọc M3 có thể thu hẹp còn 3 module FK "vì DB chặn rồi".
|
||||||
|
**Vá spec:** sửa rationale W1d thành 2 vế: (a) 3 module FK = nâng UX 547→409; (b) 5 module Office = **chống mất-toàn-vẹn dữ liệu** (không có FK đỡ). Test BẮT BUỘC ca office: Leave/Proposal phiếu pin → DELETE 409 (hôm nay = 200-xoá-êm).
|
||||||
|
|
||||||
|
**F4 [M] — W1a: 2 công thức scope KHÁC HÀNH VI giữa 2 invest, spec phải KHOÁ công thức + thiếu 1 test + thiếu belt.**
|
||||||
|
BE-invest P1 = `typeEnum == ContractSigningPlan ⇒ scope Code`; FE-invest D1 = *"per-Code khi code khớp `^KHKK-N[1-8]$`"*. Khác nhau ở code type-10 NGOÀI-họ (`QT-KHKK-V2-001` — chính là `DEFAULT_CODE_BY_TYPE[10]` `:179` mà panel ContractSigningPlan đề xuất): công thức BE → per-Code (an toàn); công thức FE-regex → rơi nhánh per-type → **NUKE cả 8 nhóm**. Spec chọn đúng (typeEnum) nhưng khai "3 lens hội tụ" — che lệch; executor đọc FE-invest có thể implement regex.
|
||||||
|
**Vá:** (a) KHOÁ công thức trong spec: `w.Code == request.Code && w.ApplicableType == typeEnum && w.IsActive` (vế `ApplicableType` = belt: UNIQUE(Code,Version) là GLOBAL nên 1 Code có thể span 2 type qua version khác nhau — hiếm nhưng rẻ để chặn); (b) thêm test thứ 3: POST type-10 code-lạ `QT-KHKK-V2-001` ⇒ IsActive 8 nhóm bất biến; (c) ghi chú P1 nhất quán với chính hàm — `nextVersion` `:334-337` đã per-Code sẵn.
|
||||||
|
|
||||||
|
**F5 [M] — KHKK-create KHÔNG check `IsActive` server-side ⇒ sau wave này, nút Ghim mất răng như spec mô tả + pin-bản-archived = 200.**
|
||||||
|
Đo: Create/UpdateDraft phiếu KHKK chỉ `EnsureWorkflowTypeAsync` (type) + rào (v) — KHÔNG đòi IsActive/IsUserSelectable; đối chứng bridge KHKK→HĐ ĐÃ vá đúng lỗ này cho type-3 (vá-3, `CreateContractFromSigningPlanFeatures.cs:141-147`, comment tự khai "forge POST pin được quy trình retired"). FE `KhkkCreatePage` fe-user `:104` filter `isUserSelectable` KHÔNG lọc `isActive`, `:114-116` `find(code)` ăn may theo ordering Version-DESC của BE. Hệ quả CHỈ LỘ SAU W2 (mở đường tạo v02): (a) v01 sau deactivate vẫn `IsUserSelectable=true` (POST `:343` không đụng cờ này) ⇒ "Bỏ ghim v02" làm auto-pin **rơi về v01 archived im lặng** thay vì CHẶN tạo — câu regression của spec (*"Bỏ ghim ⇒ CHẶN TẠO PHIẾU nhóm đó"*) chỉ đúng khi nhóm có đúng 1 version; (b) forge POST pin bản archived.
|
||||||
|
**Vá (khuyến nghị làm trong W1):** Create + UpdateDraft-khi-đổi-pin thêm check `IsActive ∧ IsUserSelectable` 2 message tách (mirror vá-3); UpdateDraft giữ-nguyên-pin không đụng (guard chỉ chạy `awId != plan.ApprovalWorkflowId` `:603` — phiếu cũ pin bản archived vẫn sửa GhiChu bình thường). FE ×2 app: `find(w.isActive && code-match)` (fe-user `:114` + fe-admin mirror `:113-116`). Nếu anh muốn giữ nguyên semantics cũ thì tối thiểu PHẢI sửa câu răng-nút-Ghim trong spec thành "chặn khi bỏ ghim TẤT CẢ version của nhóm".
|
||||||
|
|
||||||
|
**F6 [M] — D3=C như spec viết sẽ NÓI DỐI CHIỀU NGƯỢC ở ô #4 Trả-về-Drafter; danh sách ô mờ phải CHÍNH XÁC.**
|
||||||
|
Bảng BE §3 dòng #4: KHKK **BỎ QUA cờ nhưng hành-vi = LUÔN-CHO** (return về Drafter cho mọi NV đúng lượt kể cả cờ tắt — `ReturnOrRejectAsync` không đọc cờ). Mờ ô #4 kiểu "chưa áp dụng cho KHKK" = bảo tính năng KHÔNG CÓ trong khi nó LUÔN BẬT.
|
||||||
|
**Vá spec-C (danh sách chốt, gate `applicableType === 10` — áp cả panel ContractSigningPlan cho nhất quán):**
|
||||||
|
- Mờ-**false**-disabled + tooltip "chưa áp dụng cho KHKK": #1 `allowReturnOneLevel` · #2 `allowReturnOneStep` · #3 `allowReturnToAssignee` (`:1235-1266` vùng render) · #5 `allowApproverEditDetails` `:1268-1276` · #6 `allowApproverEditBudget` `:1277-1285` · #9 `allowApproverDelete` `:1320-1328` — 6 ô.
|
||||||
|
- **Forced-true**-disabled + tooltip "KHKK luôn trả về người soạn khi Trả lại — cờ không tắt được": #4 `allowReturnToDrafter` `:1256-1267`.
|
||||||
|
- Giữ nguyên: #7 đã disabled sẵn (`:1292-1302`) · #8 sống (`:1307-1315`).
|
||||||
|
- Payload: ô mờ gửi giá-trị-đang-lưu (echo, không ép) để không vấp guard PUT.
|
||||||
|
|
||||||
|
**F7 [M] — Ô "Ngưỡng giá trị gói CEO" phải TÁCH KHỎI D3 — disable cho type-10 VÔ ĐIỀU KIỆN.**
|
||||||
|
`:944-961` chỉ `disabled={isEdit}` ⇒ create-mode NHẬP ĐƯỢC cho type-10, trong khi `CeoApprovalThreshold` là quyết-đã-chốt-vĩnh-viễn KHÔNG port (OG-3 NĐUQ human-gate — `ContractSigningPlanWorkflowService.cs:46-47`). Lý lẽ của phương án A ("cờ nằm chờ port") KHÔNG áp dụng cho ô này — nó là #78 vĩnh viễn, không phải tạm thời. Spec hiện bó nó vào C ⇒ anh chọn A hoặc B là lỗ này sống tiếp.
|
||||||
|
**Vá:** W2 thêm mục riêng (mọi phương án D3): type-10 ⇒ ô CEO disabled + tooltip "KHKK không dùng ngưỡng máy — NĐUQ quyết" + POST gửi `null`; tiền lệ cùng-file `:1292`.
|
||||||
|
|
||||||
|
**F8 [m] — Mâu-thuẫn 3 lens spec chưa khai (mục 7 của đề):** (a) công-thức W1a lệch BE⟂FE (đã gộp F4); (b) D3: FE-invest §3 *"mặc định nghiêng GIỮ (A)"* ⟂ BE-invest §4.3 *"KHÔNG port thì FE PHẢI ẩn/disable"* (= C) — spec trình trung lập mà không khai 2 lens ngược nhau; (c) DB-invest W-DB-2 (thêm chiều `code=` phía BE) bị DROP im lặng — spec chọn client-side slice mà không ghi disposition. **Vá:** 3 dòng khai trong MỤC 3.
|
||||||
|
|
||||||
|
**F9 [m] — Baseline test hardcode "645" ⟂ STATUS canonical "644" (@S168 CI #444).** Một trong hai stale. **Vá:** W0/W4 đo tươi + ghi vào run.md, spec không chép số (B1).
|
||||||
|
|
||||||
|
**F10 [m] — Acceptance #3 chưa chỉ định môi trường; prod-create v02 là mutate config THẬT và KHÔNG có đường re-activate** (controller 5 action — không có activate; PUT cấm đổi IsActive; seeder idempotent per-Code không re-set). **Vá:** chứng chính = integration test W1a; acceptance UI #3 chạy LOCAL sau W0 (chấp nhận workflow rỗng người — đo cấu-trúc slice/badge); nếu muốn prod thì v02 = clone-y-hệt và khai với anh "v02 thành bản sống, v01 archived" TRƯỚC khi bấm.
|
||||||
|
|
||||||
|
**F11 [m] — Thiếu data-audit phiếu ĐÃ pin lệch trước vá.** Guard W1b chỉ chặn Create/UpdateDraft/Submit TỪ NAY; phiếu ChoDuyet pin lệch từ trước chạy tiếp đội hình lệch im lặng. **Vá W5:** thêm query `SELECT p.MaKeHoach, p.ApprovalGroup, w.Code FROM ContractSigningPlans p JOIN ApprovalWorkflows w ON w.Id = p.ApprovalWorkflowId WHERE w.Code LIKE 'KHKK-N%' AND w.Code <> CONCAT('KHKK-N', p.ApprovalGroup)` — >0 row thì báo anh xử tay. Cùng W5: nếu query 8-nhóm cho thấy nhóm nào 0-active (bom đã nổ trước vá) → recovery = "Tạo phiên bản mới" từ panel nhóm (lockedCode tự điền mã đúng).
|
||||||
|
|
||||||
|
**F12 [m] — Sót nhỏ (mục 8):** (a) hint ô Mã `:929-932` khi `lockedCode` nên đổi chữ ("Mã khoá theo nhóm — version auto-tăng"); (b) sửa-workflow-tại-chỗ KHÔNG notify approver phiếu treo — parity PE (PE cũng không notify), KHÔNG làm wave này, khai vào Nợ; (c) invalidate keys ĐÃ ĐO ĐỦ: key `['approval-workflow-v2-overview', selectedTypeInt]` + mutations invalidate prefix ⇒ 8 panel nhóm và panel type-10 dùng chung cache tự tươi, 0 việc; fe-user cache là app khác — không cross-invalidate được, chấp nhận (hành vi sẵn có); (d) comment BE `:339` "only ONE active per type" phải sửa CÙNG COMMIT với W1a kẻo comment nói dối.
|
||||||
|
|
||||||
## TỔNG HỢP WAVE FINAL ĐỀ XUẤT
|
## TỔNG HỢP WAVE FINAL ĐỀ XUẤT
|
||||||
- (điền cuối)
|
- (điền cuối)
|
||||||
|
|||||||
@ -3,9 +3,23 @@
|
|||||||
> Run: 2026-08-08-S182-khkk-awf-mirror-ncc · Reviewer #2/2 (`/fable-real reviewer`, YC-013)
|
> Run: 2026-08-08-S182-khkk-awf-mirror-ncc · Reviewer #2/2 (`/fable-real reviewer`, YC-013)
|
||||||
> Trạng thái: ĐANG ĐỔ RUỘT — skeleton viết lúc spawn, Edit liên tục.
|
> Trạng thái: ĐANG ĐỔ RUỘT — skeleton viết lúc spawn, Edit liên tục.
|
||||||
|
|
||||||
## VERDICT (điền cuối)
|
## VERDICT
|
||||||
|
|
||||||
_(pending)_
|
**`REVIEW-2: PASS_WITH_FIXES — 1C/5M` (+7 minor).** Spec DRAFT nền TỐT — 3 tiền đề lớn (0-mig · W1a per-Code P1 · synthetic slice + lockedCode) đều **re-verify ĐỨNG** trên cây; không có finding nào đảo hướng thiết kế. Toàn bộ fix đã được vá thẳng vào **§8 WAVE-PLAN FINAL** — executor cầm §8 chạy, không cần đọc lại spec DRAFT.
|
||||||
|
|
||||||
|
| # | Mức | Finding | Vá ở |
|
||||||
|
|---|---|---|---|
|
||||||
|
| C-1 | 🔴 C | Spec không cấm ship W2 (FE) trước W1a (BE) — 8 nút "Tạo phiên bản mới" nối thẳng bom deactivate-per-type (`:339-343`); bom đã armed sẵn qua panel `/ContractSigningPlan` hôm nay | §8 R-1 |
|
||||||
|
| M-1 | M | DELETE hết version 1 nhóm → seeder TÁI SINH v01 roster-snapshot lúc restart (verified `:610` AnyAsync-per-Code) — spec không khai; giữa 2 restart nhóm bị CHẶN tạo phiếu | §3 · §8 W1d-(iv) D5 |
|
||||||
|
| M-2 | M | D3=C có bẫy thi công: pattern SkipToFinal ép `checked=false` — áp nguyên cho 6 ô còn lại thì ô #4 (seed default TRUE, runtime LUÔN trả về Drafter) hiển thị nói dối chiều ngược | §6 · §8 W2d |
|
||||||
|
| M-3 | M | Spec chép số baseline "645" ⟂ STATUS canonical "644" — vi phạm B1 + measured-label gate | §5 · §8 R-3/W0.3 |
|
||||||
|
| M-4 | M | Acceptance #3 (tạo v02) không nói chạy đâu; trên PROD = mutation MỘT CHIỀU (verified: controller 0 endpoint re-activate) | §4 · §8 R-2 |
|
||||||
|
| M-5 | M | Test W1 thiếu 3 ca: Submit chốt-cuối pin-lệch pre-existing · DELETE nhánh opinions-only · 2 ca negative (changelog phiếu terminal = 0 row; code ngoài họ vẫn PASS) | §5 · §8 T1.5/T1.8/T1.10/T1.6 |
|
||||||
|
| m-1..m-7 | m | W0 "local rỗng người" không chắc · comment `:641-644` drift · cicd thiếu marker byte-level · thiếu pre-deploy prod-state check · acceptance thiếu phép đo UI-ghi · DELETE id-không-tồn-tại = 500 (KeyNotFoundException không map) · `DEFAULT_CODE_BY_TYPE[10]` panel cũ vẫn gợi mã ngoài-họ | §2-§7 · §8 |
|
||||||
|
|
||||||
|
**Còn CHẶN fan-out:** anh chốt **D3** (khuyến nghị **C** — tiền lệ sống ngay trang này, A = mìn-ngủ khi port sau, B = scope ×3-5) + **D5** (rào delete-last-version nhóm, khuyến nghị **CÓ** — 1 nhánh if rẻ hơn viết doc giải thích tái sinh). D4 chốt rồi.
|
||||||
|
|
||||||
|
**Verify độc lập đã chạy (không tin invest chay):** seeder `:549-687` · deactivate `:339-343` + ép-false `:387` · DELETE `:1193-1205` + middleware `:32-39` · controller 5-action/0-reactivate · KhkkCreatePage `:97-116` 2 app · TypePanel/Designer/payload `:255-267/:286-459/:620-978/:1225-1354` · rào (v) `:479-486` + UpdateDraft `:603-607` + Submit `:160-210` · enum Phase (`DaDuyet=3` ⇒ 3 phiếu prod là terminal, KHÔNG phải treo) + enum `ChangelogEntityType.Workflow=4` sẵn · khuôn changelog PE `:863-893` · deploy.yml paths-ignore + build-2-FE · test-homes 2 file tồn tại · STATUS row test 644.
|
||||||
|
|
||||||
## 1. Thứ tự wave + phụ thuộc
|
## 1. Thứ tự wave + phụ thuộc
|
||||||
|
|
||||||
@ -33,16 +47,16 @@ _(pending)_
|
|||||||
|
|
||||||
Mở `DbInitializer.cs:549-687` đọc tận mắt (không tin invest):
|
Mở `DbInitializer.cs:549-687` đọc tận mắt (không tin invest):
|
||||||
- **Idempotency = `AnyAsync(w => w.Code == code)` (:610)** — match BẤT KỲ row nào cùng Code, không phân biệt Version/IsActive. ⇒ admin tạo v02 cho N1 (v01 archived): restart → `AnyAsync("KHKK-N1")` = true → **SKIP. KHÔNG re-seed, KHÔNG re-activate.** An toàn lớp #75/#76. ✅
|
- **Idempotency = `AnyAsync(w => w.Code == code)` (:610)** — match BẤT KỲ row nào cùng Code, không phân biệt Version/IsActive. ⇒ admin tạo v02 cho N1 (v01 archived): restart → `AnyAsync("KHKK-N1")` = true → **SKIP. KHÔNG re-seed, KHÔNG re-activate.** An toàn lớp #75/#76. ✅
|
||||||
- **Nhưng chiều ngược:** admin XOÁ **hết** version của 1 Code (v01 lẫn v02, khi chưa pin) → boot kế `AnyAsync` = false → seeder **TÁI SINH v01** `IsActive=true, IsUserSelectable=true` với **roster snapshot email cứng** (:554-585). Đây là hành vi lớp #75/#76 CÓ THẬT nhưng chiều "resurrection-as-safety-net" — nhóm 1..8 là cấu trúc (ApprovalGroup int trên phiếu + 8 menu leaf) nên tái sinh có lợi, SONG spec **không khai** ⇒ admin xoá nhóm rồi thấy nó sống lại sau restart = ticket "xoá không được". → finding F-3.
|
- **Nhưng chiều ngược:** admin XOÁ **hết** version của 1 Code (v01 lẫn v02, khi chưa pin) → boot kế `AnyAsync` = false → seeder **TÁI SINH v01** `IsActive=true, IsUserSelectable=true` với **roster snapshot email cứng** (:554-585). Đây là hành vi lớp #75/#76 CÓ THẬT nhưng chiều "resurrection-as-safety-net" — nhóm 1..8 là cấu trúc (ApprovalGroup int trên phiếu + 8 menu leaf) nên tái sinh có lợi, SONG spec **không khai** ⇒ admin xoá nhóm rồi thấy nó sống lại sau restart = ticket "xoá không được". → finding **M-1** (kèm đề xuất rào delete-last-version ở §5/§8 — có rào thì đường UI không bao giờ chạm được ca tái sinh, mental-model sạch hơn viết doc giải thích).
|
||||||
- Version tái sinh = `Version=1` — nếu v01 cũ đã xoá thì UNIQUE (Code,1) trống → insert sạch, không 2601. ✅
|
- Version tái sinh = `Version=1` — nếu v01 cũ đã xoá thì UNIQUE (Code,1) trống → insert sạch, không 2601. ✅
|
||||||
- Roster local: seeder resolve email qua `userManager.FindByEmailAsync` (:593) — local CÓ 14 user Solutions thật (CLAUDE.md "30 demo user"), nên **local KHÔNG chắc rỗng người** như W0 spec khẳng định; rỗng hay đủ đều không phải bug → finding F-9 (chỉnh chữ W0).
|
- Roster local: seeder resolve email qua `userManager.FindByEmailAsync` (:593) — local CÓ 14 user Solutions thật (CLAUDE.md "30 demo user"), nên **local KHÔNG chắc rỗng người** như W0 spec khẳng định; rỗng hay đủ đều không phải bug → finding **m-1** (chỉnh chữ W0).
|
||||||
|
|
||||||
## 4. Acceptance prod — đủ đo chưa
|
## 4. Acceptance prod — đủ đo chưa
|
||||||
|
|
||||||
- **[M-4] Acceptance #3 ("Tạo phiên bản mới nhóm N1 ⇒ v02") chạy Ở ĐÂU — spec không nói, và chạy trên PROD là mutation MỘT CHIỀU.** Đã verify controller (`ApprovalWorkflowsV2Controller.cs`): đúng 5 action, **KHÔNG có endpoint re-activate**. Tạo v02 ⇒ v01 archived vĩnh viễn; xoá v02 KHÔNG tự re-activate v01 ⇒ nhóm rơi vào trạng thái 0-active (panel hiện empty-state "Chưa có quy trình..." `:405-409`; KhkkCreatePage vẫn pin được v01 vì lọc `isUserSelectable` không lọc `isActive` — `fe-admin/src/pages/khkk/KhkkCreatePage.tsx:104`, đã verify — nhưng badge/slice sai). **Vá:** acceptance #3 chạy **LOCAL**; trên prod chỉ đo ĐỌC (UI render 8 panel + 3 query SQL). Nếu anh muốn demo #3 trên prod thật: chấp nhận v02 (bản sao y v01) tồn tại vĩnh viễn — khai rõ trước khi bấm.
|
- **[M-4] Acceptance #3 ("Tạo phiên bản mới nhóm N1 ⇒ v02") chạy Ở ĐÂU — spec không nói, và chạy trên PROD là mutation MỘT CHIỀU.** Đã verify controller (`ApprovalWorkflowsV2Controller.cs`): đúng 5 action, **KHÔNG có endpoint re-activate**. Tạo v02 ⇒ v01 archived vĩnh viễn; xoá v02 KHÔNG tự re-activate v01 ⇒ nhóm rơi vào trạng thái 0-active (panel hiện empty-state "Chưa có quy trình..." `:405-409`; KhkkCreatePage vẫn pin được v01 vì lọc `isUserSelectable` không lọc `isActive` — `fe-admin/src/pages/khkk/KhkkCreatePage.tsx:104`, đã verify — nhưng badge/slice sai). **Vá:** acceptance #3 chạy **LOCAL**; trên prod chỉ đo ĐỌC (UI render 8 panel + 3 query SQL). Nếu anh muốn demo #3 trên prod thật: chấp nhận v02 (bản sao y v01) tồn tại vĩnh viễn — khai rõ trước khi bấm.
|
||||||
- **Account đo:** spec không nêu. Chốt: `admin@solutions.com.vn` (role Admin: bypass mọi policy + menu auto-grant `SeedAdminPermissionsAsync`). 8 leaf sidebar hiện cho role nào khác = tầng menu-grant per-role (display) — ĐỘC LẬP tầng API (`Workflows.Create` cho verb ghi, authenticated-only cho GET); acceptance thêm 1 dòng: *"role thường không có grant menu ⇒ không thấy leaf — đúng thiết kế, không phải bug"*.
|
- **Account đo:** spec không nêu. Chốt: `admin@solutions.com.vn` (role Admin: bypass mọi policy + menu auto-grant `SeedAdminPermissionsAsync`). 8 leaf sidebar hiện cho role nào khác = tầng menu-grant per-role (display) — ĐỘC LẬP tầng API (`Workflows.Create` cho verb ghi, authenticated-only cho GET); acceptance thêm 1 dòng: *"role thường không có grant menu ⇒ không thấy leaf — đúng thiết kế, không phải bug"*.
|
||||||
- **3 query SQL — chạy bằng đường nào:** spec DB §4 giao query nhưng không giao ĐƯỜNG. Khuôn đã chứng S177: `ssh vietreport-vps` + `sqlcmd -E` (integrated auth, KHÔNG cần credential; env password TRỐNG là bình thường). Ghi vào wave-plan để executor không đi tìm password.
|
- **3 query SQL — chạy bằng đường nào:** spec DB §4 giao query nhưng không giao ĐƯỜNG. Khuôn đã chứng S177: `ssh vietreport-vps` + `sqlcmd -E` (integrated auth, KHÔNG cần credential; env password TRỐNG là bình thường). Ghi vào wave-plan để executor không đi tìm password.
|
||||||
- **Thiếu 1 phép đo UI-ghi trên local:** acceptance #2 chỉ MỞ dialog. Thêm: sửa-tại-chỗ 1 nhóm (đổi tên 1 NV slot / tick 1 cờ Return) → 204 → panel refresh giữ `v01` + không đổi Version — chứng PUT đường nhóm sống thật (máy PUT là generic by-Id nên gần chắc chắn PASS, nhưng "gần chắc" ≠ đo).
|
- **[m-5] Thiếu 1 phép đo UI-ghi trên local:** acceptance #2 chỉ MỞ dialog. Thêm: sửa-tại-chỗ 1 nhóm (đổi tên 1 NV slot / tick 1 cờ Return) → 204 → panel refresh giữ `v01` + không đổi Version — chứng PUT đường nhóm sống thật (máy PUT là generic by-Id nên gần chắc chắn PASS, nhưng "gần chắc" ≠ đo).
|
||||||
- Acceptance #1/#2/#4 (8 slice + dialog khuôn + regression 4 route cũ): ĐỦ và đo được. Bổ sung điều kiện #4: "645 test" → **số đo tươi tại W0** (xem M-3 §5).
|
- Acceptance #1/#2/#4 (8 slice + dialog khuôn + regression 4 route cũ): ĐỦ và đo được. Bổ sung điều kiện #4: "645 test" → **số đo tươi tại W0** (xem M-3 §5).
|
||||||
|
|
||||||
## 5. Test coverage W1
|
## 5. Test coverage W1
|
||||||
@ -86,6 +100,60 @@ Spec W1 liệt 4 test — **đúng nhưng THIẾU 3 ca** (tính chốt-cuối +
|
|||||||
4. **Chân data (post-deploy):** chạy lại query 1 — kỳ vọng 8 row `IsActive=1` y trước deploy (deploy KHÔNG tự đổi data; lệch = có người bấm gì đó giữa chừng).
|
4. **Chân data (post-deploy):** chạy lại query 1 — kỳ vọng 8 row `IsActive=1` y trước deploy (deploy KHÔNG tự đổi data; lệch = có người bấm gì đó giữa chừng).
|
||||||
- **Post-deploy smoke KHÔNG-GHI trên prod:** login admin → 3/8 panel bất kỳ render đúng slice + mở dialog Sửa-tại-chỗ rồi Hủy. KHÔNG bấm "Tạo phiên bản mới" trên prod trong smoke (M-4).
|
- **Post-deploy smoke KHÔNG-GHI trên prod:** login admin → 3/8 panel bất kỳ render đúng slice + mở dialog Sửa-tại-chỗ rồi Hủy. KHÔNG bấm "Tạo phiên bản mới" trên prod trong smoke (M-4).
|
||||||
|
|
||||||
## 8. WAVE-PLAN FINAL
|
## 8. WAVE-PLAN FINAL — bản executor cầm chạy (hmw Opus MAX)
|
||||||
|
|
||||||
_(pending)_
|
> Đã vá đủ C-1 + M-1..M-5 + m-1..m-7 của review này vào thân plan. **2 điểm quyết còn CHẶN trước khi fan-out:** **D3** (khuyến nghị **C**) + **D5** (rào delete-last-version, khuyến nghị **CÓ**). D4 đã chốt (title menu-label + phụ đề nghiệp vụ). Mọi `file:line` dưới đây đã re-verify trên cây S182 (HEAD nền `936d1aed`).
|
||||||
|
|
||||||
|
### Ràng buộc toàn cục (đọc trước khi chạy bất kỳ wave nào)
|
||||||
|
- **R-1 (từ C-1):** W1+W2 = **MỘT commit code duy nhất** (mặc định). Nếu lead tách: thứ tự BẮT BUỘC W1 → CI xanh → W2. **CẤM mọi commit chứa W2 mà thiếu W1a** — nút "Tạo phiên bản mới" trên panel nhóm khi BE còn deactivate-per-type = tắt IsActive cả 8 nhóm prod.
|
||||||
|
- **R-2 (từ M-4):** trên PROD chỉ đo ĐỌC. Mọi acceptance có GHI (tạo version, sửa-tại-chỗ, xoá) chạy **LOCAL**. Không có endpoint re-activate — POST version mới là ratchet một chiều.
|
||||||
|
- **R-3 (từ M-3):** baseline test = **số đo tươi tại W0** ghi vào `run.md`. Không chép 644/645 từ bất kỳ doc nào.
|
||||||
|
- **R-4:** 0 migration. Nếu wave nào phát hiện cần mig ⇒ DỪNG, báo lead (premise 3-lens vỡ).
|
||||||
|
- **R-5:** commit scope `[CLAUDE] Workflow: ...`; artifact run-folder không trigger CI (paths-ignore đã verify).
|
||||||
|
|
||||||
|
### W0 — prep + đo nền (blocking mọi wave sau)
|
||||||
|
| # | Task | Lệnh/site | Đạt khi |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0.1 | Áp mig local ×2 DB (Dev+Design kẹt Mig 71, đĩa Mig 72) | `dotnet ef database update --project src/Backend/SolutionErp.Infrastructure --startup-project src/Backend/SolutionErp.Api` (×2 connection) | `sqlcmd` top MigrationId = `20260806070242` cả 2 DB |
|
||||||
|
| 0.2 | Boot API 1 lần → seeder đẻ 8 row local | run SolutionErp.Api | SQL: 8 row `KHKK-N1..8` type-10 `IsActive=1`. Roster local **tùy user-seed — rỗng/đủ đều KHÔNG phải bug** (m-1) |
|
||||||
|
| 0.3 | Đo baseline test TƯƠI | `dotnet test SolutionErp.slnx` | Số PASS ghi vào `run.md` — mọi so sánh sau so với số này (R-3) |
|
||||||
|
| 0.4 | Prod-state check ĐỌC-only (m-4) | `ssh vietreport-vps` + `sqlcmd -E` chạy 3 query (spec DB §4) | Kỳ vọng: 8 row IsActive=1 · phiếu KHKK đều Phase=3 (DaDuyet) · opinions đếm được. **LỆCH kỳ vọng (nhóm Archived-oan / phiếu ChoDuyet-TraLai đang treo) ⇒ báo lead TRƯỚC khi W1** — phiếu treo pin lệch nhóm sẽ kẹt submit sau W1b (by design), phải biết trước con số |
|
||||||
|
|
||||||
|
### W1 — BE: 4 fix + 12 test, 2 file + 1 helper, 0-mig
|
||||||
|
**File:** `src/Backend/SolutionErp.Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs` + `src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs` (+`ContractSigningPlanScope` helper) + `src/Backend/SolutionErp.Infrastructure/Services/ContractSigningPlanWorkflowService.cs` (1 call-site).
|
||||||
|
|
||||||
|
| # | Fix | Site (verified) | Cách | Test (RED→GREEN cho a/b; after cho c/d) |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| a | 🔴 Deactivate-per-Code cho type-10 | `ApprovalWorkflowV2AdminFeatures.cs:339-343` | `typeEnum == ContractSigningPlan` ⇒ `Where(w.Code == request.Code && w.IsActive)`; type khác GIỮ per-type (P1) | **T1.1** create v2 `KHKK-N1` ⇒ N2..N8 `IsActive` bất biến + N1 v01→false v02→true · **T1.2** regression type-1: Code MỚI cùng type vẫn tắt Code cũ · **T1.3** type-10 code ngoài họ (vd `QT-KHKK-V2-001`) chỉ tắt chính Code nó |
|
||||||
|
| b | Group-match guard 3-site | extract rào (v) `ContractSigningPlanFeatures.cs:479-486` → `ContractSigningPlanScope.EnsureWorkflowGroupMatchAsync(db, awId, approvalGroup, ct)`; gọi: Create (chỗ cũ) + UpdateDraft `:603-607` (sau `EnsureWorkflowTypeAsync`) + `SubmitAsync` (`ContractSigningPlanWorkflowService.cs` sau rào hạng-mục `:169-187`, trước `LoadStepsAsync :189`) | Code khớp `^KHKK-N([1-8])$` mà n ≠ `ApprovalGroup` ⇒ 409 message chỉ việc ("đổi quy trình trong nháp về đúng nhóm N{x} / hoặc đổi nhóm phiếu"); **code NGOÀI họ ⇒ PASS như cũ** (:477-478 chủ-đích, M-5c.ii) | **T1.4** UpdateDraft re-pin lệch ⇒ 409 · **T1.5** phiếu pin-lệch PRE-EXISTING (seed thẳng DbContext bypass guard) Submit ⇒ 409 · **T1.6** code ngoài họ: Create/UpdateDraft/Submit OK |
|
||||||
|
| c | Changelog PUT type-10 | `ApprovalWorkflowV2AdminFeatures.cs:863-893` thêm nhánh `else if type-10` mirror block PE | Query `ContractSigningPlans` (`ApprovalWorkflowId == def.Id && Phase != DaDuyet && Phase != TuChoi`) → add `ContractSigningPlanChangelog { EntityType = Workflow /*=4, enum SẴN — verified*/, Action = Update, EntityId = def.Id, PhaseAtChange, UserId, UserName, Summary = "Sửa quy trình duyệt tại chỗ: {Code} v{NN} (giữ nguyên phiên bản)", ContextNote = note (kèm Reason) }`. Dialog PUT đang HỨA nguyên văn "ghi vào lịch sử phiếu đang chạy" (`ApprovalWorkflowsV2Page.tsx:963-977`) — fix này làm lời hứa thành thật cho KHKK | **T1.7** PUT workflow nhóm có phiếu ChoDuyet ⇒ 1 row đúng shape · **T1.8** phiếu DaDuyet/TuChoi ⇒ 0 row |
|
||||||
|
| d | DELETE usage-check → 409 + 404 đúng | `ApprovalWorkflowV2AdminFeatures.cs:1193-1205` | (i) pre-count pin: grep `ApprovalWorkflowId` trong Domain để LIỆT ĐỦ module có cột pin (đếm từ grep, không chép số spec) + (ii) count LevelOpinions 3 bảng theo def.Id qua Levels (khuôn `CountLiveOpinionsAsync :961-965` đổi trục) ⇒ >0 = 409 đích danh "N phiếu đang pin / M chữ ký đã ký" · (iii) đổi `KeyNotFoundException` → `NotFoundException` (m-6: hiện id-không-tồn-tại = 500 thô vì middleware không map BCL exception — verified `GlobalExceptionMiddleware.cs:32-39`) · (iv) **D5 (chờ chốt, khuyến nghị CÓ):** code họ `KHKK-N[1-8]` && version CUỐI của Code ⇒ 409 "Nhóm N{n} phải còn ≥1 version" (chặn ca seeder-tái-sinh M-1 khỏi đường UI) | **T1.9** delete có pin ⇒ 409 · **T1.10** hết pin nhưng còn opinions ⇒ 409 · **T1.11** id không tồn tại ⇒ 404 · **T1.12** (nếu D5) xoá version cuối nhóm ⇒ 409 |
|
||||||
|
|
||||||
|
**Test homes (verified tồn tại):** `tests/SolutionErp.Infrastructure.Tests/Application/UpdateAwDefinitionTests.cs` (T1.1-T1.3, T1.7-T1.12) · `ContractSigningPlanApprovalTests.cs` (T1.4-T1.6). **Đạt khi:** build 0-err + FULL suite = baseline W0 + 11-12 test mới, 0 fail.
|
||||||
|
|
||||||
|
### W2 — FE: fe-admin `src/pages/system/ApprovalWorkflowsV2Page.tsx` (file 1355 dòng)
|
||||||
|
| # | Task | Site (verified) | Cách |
|
||||||
|
|---|---|---|---|
|
||||||
|
| a | Parse kind | `:164-172` | `typeCode?.match(/^KhkkN([1-8])$/)` → `khkkGroup`; `selectedTypeInt = khkkGroup ? TYPE_INT_CONTRACT_SIGNING_PLAN : TYPE_CODE_TO_INT[typeCode]`; query overview giữ `applicableType=10` |
|
||||||
|
| b | Synthetic slice per-Code | `:299-301` | từ `types.find(t=>t.applicableType===10)`: `versions = history.filter(d => d.code === khkkGroupWorkflowCode(g))` · `active = versions.find(v=>v.isActive) ?? null` (🔴 KHÔNG đọc ô `Active` per-type — nó là 1-trong-8) · slice truyền vào `TypePanel` giữ shape `TypeSummaryDto` (`history` = versions ĐỦ cả active — panel tự filter `!isActive` `:419/:426`, verified) · label = "Duyệt KHKK — Nhóm {g}" |
|
||||||
|
| c | `lockedCode` xuyên suốt | `TypePanel:357` → `Designer:620` / defaultCode `:650-651` / POST `:798` / ô Mã `:920-928` | prop `lockedCode?: string` = `khkkGroupWorkflowCode(g)`; ô Mã `disabled={isEdit || !!lockedCode}` + helper-text; POST gửi `lockedCode ?? code`. Thiếu = version tự gõ mã RƠI khỏi slice + không được rào (v) nhận |
|
||||||
|
| d | **D3 thi hành (mặc định C — luật M-2 BẮT BUỘC)** | 6 ô `:1232-1285` (4 Return + EditDetails + EditBudget) + CEO `:944-961` | 6 ô: CHỈ thêm `disabled={applicableType === TYPE_INT_CONTRACT_SIGNING_PLAN}` + `title` tooltip — **GIỮ `checked={entry.<flag>}` state THẬT, CẤM ép false** (payload `levelFlagsPayload :255-267` không đổi — echo state, verified). Ô #4 Trả-về-Drafter tooltip RIÊNG: "KHKK hiện luôn trả về Drafter khi Trả lại — cờ chưa phân nhánh" (hiển thị tick-thật, thường TRUE). Ô Ngưỡng CEO: POST-mode `disabled` khi type-10 + helper-text "KHKK không áp ngưỡng CEO (NĐUQ human-gate)" (PUT-mode đã khoá sẵn). SkipToFinal `:1292-1302` GIỮ NGUYÊN |
|
||||||
|
| e | (optional — kéo eoffice rotate) mirror menuKeys | `fe-{admin,user}/src/lib/menuKeys.ts:31-32` | +8 const `AwV2_KhkkN1..8` — 0 hệ quả runtime |
|
||||||
|
| f | (m-2) vá comment lệch | `:641-644` | comment "2 ô duyệt sớm nay sống cho MỌI loại" ⟂ thực tế SkipToFinal vẫn disable type-10 `:1286-1302` — sửa 1 dòng cho khớp |
|
||||||
|
| g | (D4) phụ đề nghiệp vụ | PageHeader `:305-318` + TypePanel | khuôn hiện CHỈ render `applicableTypeLabel` — thêm ~1 prop `subtitle = khkkGroupMenuLabel(g)` (`types/khkk.ts:159-168`, 2-app identical sẵn) |
|
||||||
|
|
||||||
|
**Acceptance LOCAL (R-2):** **A1** 8 leaf → 8 slice khác nhau (URL/header/card `KHKK-N{n} v01`+badge+3 Bước+4 nút+Lịch sử) hết landing · **A2** dialog Sửa-tại-chỗ đúng khuôn ảnh theo D3 (title `KHKK-N{n} v01 (giữ nguyên phiên bản)`, Drafter tick sẵn, rule-box) · **A2b** sửa-tại-chỗ THẬT 1 nhóm (đổi tên NV / tick 1 cờ Return) ⇒ 204, panel refresh giữ v01 · **A3** tạo v02 N1 (local) ⇒ nằm ĐÚNG panel N1 + badge 7 nhóm khác KHÔNG mất (răng W1a) · **A4** regression 4 route cũ render như cũ + fe-user tạo phiếu auto-pin không đổi API. **Đạt khi:** `npm run build` fe-admin (+fe-user nếu e) + tsc 0-err.
|
||||||
|
|
||||||
|
### W3 — CHỈ KHI anh chốt D3=B: port semantics per-cờ vào `ContractSigningPlanWorkflowService` (giữ nguyên nội dung spec DRAFT W3; mỗi cờ test RED→GREEN riêng; `CeoApprovalThreshold` KHÔNG port — OG-3). Mặc định **C ⇒ W3 = N/A**.
|
||||||
|
|
||||||
|
### W4 — test-specialist đóng suite
|
||||||
|
Full `dotnet test SolutionErp.slnx` = baseline-W0 + N-test-mới, 0 fail · chạy SAU khi D3/D5 chốt · nếu số canonical đổi ⇒ update DUY NHẤT `docs/STATUS.md` row test (B1).
|
||||||
|
|
||||||
|
### W5 — ship + verify (sau reviewer pre-commit)
|
||||||
|
1. **Commit MỘT phát** BE+FE+tests (R-1) `[CLAUDE] Workflow: KHKK 8 panel nhóm mirror DuyetNcc + 4 BE guard (0-mig)`.
|
||||||
|
2. **cicd-monitor 4-chân (m-3):** (i) CI Run success + test-gate = số W4 · (ii) bundle **admin** rotate + grep js mới marker `KhkkN1` (control-âm: bundle cũ 0-hit; **eoffice: hash GIỮ nếu bỏ W2e / đổi nếu làm** — đo, không suy) · (iii) BE DLL: ssh grep `SolutionErp.Application.dll` token `EnsureWorkflowGroupMatch` 2-encoding UTF-8/UTF-16 (khuôn S177) + control-dương token cũ · (iv) query-1 lại: 8 row `IsActive=1` bất biến qua deploy.
|
||||||
|
3. **Acceptance PROD đọc-only (R-2):** login `admin@solutions.com.vn` → ≥3/8 panel render đúng slice + mở dialog rồi Hủy · 3 query SQL khớp · **KHÔNG bấm "Tạo phiên bản mới" trên prod** · note: role thường chưa grant menu-row ⇒ không thấy leaf = đúng thiết kế (2-layers).
|
||||||
|
4. Đóng sổ: STATUS (test + mô tả) / HANDOFF / WAL / tick `YC-013` + khai **accepted-risk**: race 2-admin-cùng-Code → 500 thô (UNIQUE đỡ data, không silent-dup) + nợ-ngoài-scope giữ nguyên khối spec (GET 0-policy · roster snapshot · `Khkk_WfView` aggregate · `DEFAULT_CODE_BY_TYPE[10]='QT-KHKK-V2-001'` panel cũ vẫn gợi mã ngoài-họ (m-7) · comment "7 bảng" `:1120`).
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
0-mig ⇒ revert commit sạch phần máy. Data ở lại (hợp lệ, khai rõ): changelog rows W1c = audit-history · v02+ đã tạo = ratchet (vô hại runtime, pin by-Id). 🔴 Revert W1a = **bom per-type TÁI VŨ TRANG** — sau revert, CẤM "Tạo phiên bản mới" mọi panel type-10 tới khi re-apply.
|
||||||
|
|||||||
Reference in New Issue
Block a user