From 9c0ac6884de2b55d5dc58fe9a0863b5ac17d4270 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Mon, 27 Jul 2026 10:50:36 +0700 Subject: [PATCH] wal: flush 20260727T1050 --- .claude/WAL.md | 6 +- .../sub-implementer-backend-1.md | 42 ++++++ .../sub-implementer-frontend-2.md | 124 ++++++++++++++++++ 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 .claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-backend-1.md create mode 100644 .claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-frontend-2.md diff --git a/.claude/WAL.md b/.claude/WAL.md index a71f65d..a9da30d 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -30,7 +30,11 @@ chain: [ ] ⚠️ mâu-thuẫn CẦN GIẢI ở (7): nếu "nhiều người 1 Cấp" = nhiều bản ghi Level cùng Order thì "thêm người" CHÍNH LÀ "thêm Level" ⇒ ranh AN-TOÀN/PHÁ-VỠ của anh có thể tự mâu thuẫn ở tầng dữ liệu. W4 của Invest #4 phải trả lời [x] A-S2-bis spec PHẦN II (hạng mục F) + S3 reviewer `PASS-WITH-FLAGS 17 FLAG (4H/8M/5L)`; F.0 KHÔNG bác được sau 6 đường tấn công; lead vá 4H+8M vào spec [x] A-owner lượt 3: (8) TÁCH 2 quyền xóa · (9) F6 PER-NGƯỜI · (10) F5 = PHÁ VỠ · (11) ngưỡng CEO = PHÁ VỠ · (12) Step.DepartmentId = AN TOÀN · (13) CHIA 2 ĐỢT -[!] wf: A-S4-đợt1 hmw — run-id engine `wf_f1bfea65-ed9` · run-folder `.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/` · 2 task: implementer-backend (F6 schema+mig+UpdateCommand+validator T1/T2+authz+F-5a+F-6+F-10) · implementer-frontend (F-11 5 dây, bẫy :605). Chết giữa chừng ⇒ /tiep §4 relaunch ĐÃ-CẮT-GỌT, đọc sub-*.md trên đĩa trước +[x] A-S4-đợt1 wave-1 XONG + lead-vá — build 0W/0E · **test 532 PASS** (45D+487I). Landed: F6 entity+Mig `20260727033522_AddPeAllowApproverDelete` 3-file · F-10 comment · F-1..F-4 UpdateAwDefinitionCommand (T1 SIẾT HƠN spec: khoá Order từng row, bắt cả ca hoán vị #11) · F-5a loại-trừ opinion PE-đã-xoá **phủ PE+Contract+Proposal** · F-11 FE 5/5 dây (kể cả bẫy `:605`) build ×2 PASS +[!] 🔴 A-SỰ-CỐ wave-1: sub implementer-backend **MẤT CẢ 2 KÊNH** — return không gọi StructuredOutput + diary `sub-implementer-backend-1.md` **0-byte rồi BIẾN MẤT**. Đây là `skeleton-ruột-rỗng` ở mức nặng nhất từng gặp: ghi-đĩa-trong-lúc-làm KHÔNG cứu được. Recover bằng lane ĐĨA-TRUTH (git diff + grep + build + test). Datum cho memory @close +[x] A-lead-vá 2 lỗ sub BE bỏ sót (chỉ lộ ra vì em-main tự đối chiếu diff, KHÔNG có lời khai nào): **(1) THIẾU HẲN controller endpoint** ⇒ UpdateAwDefinitionCommand là code CHẾT, không ai gọi được + kéo theo F-1a authz cũng vắng → lead thêm `[HttpPut("{id:guid}")]` + `[Authorize(Policy="Workflows.Create")]` · **(2) F-6 chưa làm** — `:705` vẫn `string.Join` trên GUID dù biến đã tên `names` → lead resolve sang FullName qua userManager, fallback GUID +[ ] A-S4-đợt1 wave-2: test-specialist F-1b (403) + F-12..F-16 · reviewer soi diff +[~] wf: A-S4-đợt1 hmw — run-id engine `wf_f1bfea65-ed9` · run-folder `.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/` · 2 task: implementer-backend (F6 schema+mig+UpdateCommand+validator T1/T2+authz+F-5a+F-6+F-10) · implementer-frontend (F-11 5 dây, bẫy :605). Chết giữa chừng ⇒ /tiep §4 relaunch ĐÃ-CẮT-GỌT, đọc sub-*.md trên đĩa trước [ ] A-S4-đợt1 wave 2 (sau khi wave 1 xanh): test-specialist F-12..F-16 + reviewer soi diff [ ] A-S5-đợt2: mục 3-28 (nút xóa FE · menu restructure · màn "Đã xóa" 8a/8b/8c) — CHỈ sau khi đợt 1 UAT xanh [ ] A-ràng-buộc CỨNG cho spec: `IgnoreQueryFilters()` chỉ mở ĐÚNG 1 endpoint list "Đã xóa" — rộng tay = phiếu xóa lọt lại `PeBudgetAccumulator` = phá đúng ý (3) của anh diff --git a/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-backend-1.md b/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-backend-1.md new file mode 100644 index 0000000..7e9b12e --- /dev/null +++ b/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-backend-1.md @@ -0,0 +1,42 @@ +# sub-implementer-backend-1 — ĐỢT 1 (hạng mục F + schema F6) + +> run `2026-07-27-S155-pe-delete-approver` · vai `implementer-backend` · spec canonical = `spec-pe-delete-approver.md` PHẦN II + checklist mục 1-2 +> Ghi TRONG LÚC LÀM (bài học #53). Trạng thái cập nhật dần từ trên xuống. + +## Phạm vi nhận (theo TASK) + +| # | Mục | Trạng thái | +|---|---|---| +| 1 | `ApprovalWorkflowLevel += AllowApproverDelete` (F6) | ⏳ | +| 2 | Migration `AddPeAllowApproverDelete` (3-file rule) | ⏳ | +| 3 | F-10 vá comment sai `ApprovalWorkflow.cs:81-82` | ⏳ | +| 4 | F-1..F-4 + F-1a/b/c `UpdateAwDefinitionCommand` | ⏳ | +| 5 | F-5/F-5a hạng NỬA (bớt người / đổi ApproverUserId) | ⏳ | +| 6 | F-6 message 403 in GUID → tên người | ⏳ | + +KHÔNG làm: nút xóa FE · menu · màn "Đã xóa" · F-9 usage-check DeleteAwDefinition · F-11 (FE) · F-7/F-8 (FE). + +--- + +## PHẦN 0 — Khảo sát nền (đọc trước khi viết dòng nào) + +Số đo lấy từ đĩa, KHÔNG lấy từ memoryPack: + +| Sự kiện | Bằng chứng | +|---|---| +| 8 cờ Allow* per-Level hiện có (F1×4, F3, F4, F2, F5) | `src/Backend/SolutionErp.Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs:104-142` | +| EF `HasDefaultValue` cho từng cờ | `.../Configurations/ApprovalWorkflowConfiguration.cs:80-103` | +| KHÔNG tồn tại lệnh Update — admin V2 chỉ 4 việc | `ApprovalWorkflowV2AdminFeatures.cs` (Get :101 · Create :238 · SetUserSelectable :384 · Delete :404) | +| POST tự tăng Version + hạ IsActive mọi bản cùng type | `:325-328` · `:331-334` · new `ApprovalWorkflow{}` `:336` | +| 3 verb ghi controller đều `[Authorize(Policy="Workflows.Create")]`; class chỉ `[Authorize]` trần `:18` | `ApprovalWorkflowsV2Controller.cs:29 / :40 / :48` | +| Con trỏ LAI: StepIndex = INDEX list sort · LevelOrder = so GIÁ TRỊ | `PurchaseEvaluationWorkflowService.cs:686` · `:689-695` | +| 403 in GUID trần | `PurchaseEvaluationWorkflowService.cs:701-707` (`string.Join(", ", allowedUserIds)`) | + +**🔴 Phát hiện MỚI (chưa có trong spec/memoryPack) — quyết định cài đặt F-5/F-5a phụ thuộc nó:** + +1. **7 bảng LevelOpinion** FK `ApprovalWorkflowLevelId` chứ không phải 1 (PE + Contract + Proposal + Leave + Ot + Travel + VehicleBooking) — grep `ApprovalWorkflowLevelId` trong `Domain/` ra 7 entity. Chặn-lịch-sự chỉ soi PE ⇒ **vẫn nổ FK 547** khi workflow thuộc module Văn phòng số (khớp §F.3 blast-radius ≥6 module). +2. **CẢ 7 LevelOpinion đều là `AuditableEntity`** (không phải BaseEntity) — vd `PurchaseEvaluationLevelOpinion.cs`, `ProposalLevelOpinion.cs:16`. Hệ quả: `Remove()` một opinion **KHÔNG xoá cứng** — `AuditingInterceptor.cs` đổi `Deleted→Modified` ⇒ row Ở LẠI ⇒ **FK vẫn Restrict**. Muốn dọn rác phải `ExecuteDeleteAsync()` (bypass change-tracker). +3. **Chỉ PE + Contract có `HasQueryFilter(!IsDeleted)`**; Proposal/Leave/Ot/Travel/VehicleBooking **KHÔNG** có (grep `HasQueryFilter` toàn `Configurations/` = 16 hit, 0 hit cho 5 bảng Office). ⇒ F-5a "loại trừ opinion của phiếu đã IsDeleted" **phải viết `!p.IsDeleted` TƯỜNG MINH**, không được dựa global filter (dựa filter ⇒ đúng cho PE, SAI cho 5 module Office). +4. `Microsoft.EntityFrameworkCore.Relational 10.0.6` đã có trong `SolutionErp.Application.csproj:13` ⇒ `ExecuteDeleteAsync` dùng được ở tầng Application. + +--- diff --git a/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-frontend-2.md b/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-frontend-2.md new file mode 100644 index 0000000..01d5591 --- /dev/null +++ b/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-frontend-2.md @@ -0,0 +1,124 @@ +# sub-implementer-frontend-2 — [F-11] nối cờ F6 `allowApproverDelete` vào Designer V2 + +> run `2026-07-27-S155-pe-delete-approver` · ĐỢT 1 · vai `implementer-frontend` +> Spec canonical: `spec-pe-delete-approver.md` §F.4 mục **F-11** (dòng 303) + §F.2 bảng 13 thao tác hạng **2a ✅ AN TOÀN**. +> Ghi TRONG LÚC LÀM (5/5 sub run này mất return vì #53) — file này luôn hợp lệ giữa chừng. + +## 0. Phạm vi tự chốt trước khi viết dòng nào + +| Làm | Không làm (đợt 2) | +|---|---| +| 5 dây Designer fe-admin + ô tick F6 | nút Xóa ở màn duyệt (`PeWorkflowPanel.tsx`) | +| 1 dây hiển thị chỉ-xem fe-user (`WorkflowMatrixViewPage:282`) | menu restructure (`Layout.tsx` regex, `menuKeys`) | +| type/DTO FE tương ứng (2 chỗ) | màn "Đã xóa" | + +**Ý nghĩa cờ (viết đúng, không suy diễn):** **PER-NGƯỜI**, KHÔNG per-Cấp. 1 `Level` row = 1 người +(`ApproverUserId` là field của row); "Cấp" chỉ là tập row cùng `Order`. Owner chốt per-ROW để giữ +sát ý *"người đứng đầu phòng"* = **1 người, admin chỉ đích danh** (spec §B2 dòng 60-66). +Khối UI tick nằm trong ô `Quyền duyệt {tên NV}` (`:928-930`) ⇒ ngữ cảnh per-người đã hiện sẵn trên màn. + +## 1. Đối chứng: F5 `allowApproverFinalize` đi qua ĐÚNG 5 chỗ nào (đọc trước, không tin spec suông) + +Grep `allowApproverFinalize` trên `fe-admin/` → 5 hit trong `ApprovalWorkflowsV2Page.tsx` ngoài ô tick: + +| Dây | Line (trước sửa) | Vai trò | Hỏng gì nếu thiếu | +|---|---|---|---| +| 1 | `:54` | `LevelDto` — mirror BE `AwAdminOverviewDto` (đọc VỀ) | TS2339 khi đọc `l.allowApproverDelete` ở dây 3 | +| 2 | `:110` | `EditLevelEntry` — state form đang soạn | TS2339 ở `updateField` + dây 4/5 | +| 3 | `:159` | `copyFromDefinition()` — **Nhân bản** version cũ | tick cũ mất trắng khi admin bấm "Nhân bản" | +| 4 | `:177` | `makeDefaultLevelEntry()` — "+ Thêm NV" | entry mới thiếu key ⇒ TS2739 (object literal thiếu field) | +| 5 | `:605` | **payload POST** `/approval-workflows-v2` | 🔴 **BẪY**: tick xong KHÔNG gửi lên BE | + +🔴 **Vì sao dây `:605` là bẫy thật (không phải cảnh báo lễ nghi):** payload POST là object literal +**liệt kê từng field bằng tay** (`:594-606`), KHÔNG spread `...e`. TypeScript **không** báo lỗi khi +thiếu field (thừa field mới lỗi, thiếu thì chỉ là object hẹp hơn) ⇒ build vẫn PASS, UI tick vẫn +hiện dấu tick, `onChange` vẫn chạy, state vẫn đổi — **chỉ có BE không bao giờ nhận**. Không có +compiler/test nào bắt; chỉ lộ khi UAT tick → lưu → mở lại thấy trống, hoặc tệ hơn: quyền xóa im +lặng không tới người được chỉ định. + +## 2. Thay đổi đã áp — 3 file, 10 neo + +### 2.1 `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx` (+28 dòng) — 5 dây + ô tick + +| # | Line MỚI (cũ) | Nội dung | +|---|---|---| +| Dây 1 | `:58` (54) | `LevelDto` + `allowApproverDelete: boolean` ngay dưới `allowApproverFinalize` | +| Dây 2 | `:116` (110) | `EditLevelEntry` + `allowApproverDelete: boolean` | +| Dây 3 | `:168` (159) | `copyFromDefinition()` → `allowApproverDelete: l.allowApproverDelete ?? false` | +| Dây 4 | `:188` (177) | `makeDefaultLevelEntry()` → `allowApproverDelete: false` | +| **Dây 5** | **`:620`** (605) | **payload POST** `/approval-workflows-v2` → `allowApproverDelete: e.allowApproverDelete` | +| Ô tick | `:1025-1034` | `