wal: flush 20260727T1050
This commit is contained in:
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
---
|
||||
@ -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` | `<label class="col-span-2 … text-rose-700">` ngay DƯỚI ô F5 (kết ở `:1024`) |
|
||||
|
||||
Nhãn ô tick (tiếng Việt, per-người): **"Cho phép NV này XÓA phiếu khi đến lượt duyệt của mình"**.
|
||||
Màu `text-rose-700` phân biệt với F5 `text-emerald-700` — hành vi phá huỷ, cố ý cho khác màu.
|
||||
`updateField` là `(field: keyof EditLevelEntry, value: boolean)` (`:940`) ⇒ chuỗi `'allowApproverDelete'`
|
||||
được TS kiểm, không phải magic string.
|
||||
|
||||
### 2.2 `fe-user/src/types/approvalWorkflowV2.ts` (+7/−1)
|
||||
`AwLevelDto` + `allowApproverDelete: boolean` (`:27`); header đổi **"8 Allow* flag"** → **"9 Allow* flag"** (`:5-6`).
|
||||
|
||||
### 2.3 `fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx` (+2) — dây chỉ-xem
|
||||
`:284` (F5 ở `:282`) — `<FlagRow active={r.level.allowApproverDelete} … colSpan2 />` cùng nhãn với Designer.
|
||||
|
||||
### 2.4 🔴 Dây `:605`/`:620` — KHAI RIÊNG THEO YÊU CẦU: **ĐÃ NỐI**, và đây là bằng chứng nó thật sự là bẫy
|
||||
|
||||
Kiểm cuối cùng, sau khi đã build xong. Grep `allowApproverDelete` toàn repo `*.ts,*.tsx` = **10 hit / 3 file**,
|
||||
trong đó hit `ApprovalWorkflowsV2Page.tsx:620` nằm **bên trong** `s.levelEntries.map(e => ({…}))` của
|
||||
`await api.post('/approval-workflows-v2', …)`.
|
||||
|
||||
**Ba tầng cùng im lặng nếu thiếu dòng này** (đo, không đoán):
|
||||
1. **TS không bắt** — payload là object literal liệt kê tay từng field (`:609-621`), **không** `...e`.
|
||||
Thiếu key = object hẹp hơn, hợp lệ với `any` param của `api.post` ⇒ build vẫn `✓ built`.
|
||||
2. **BE không bắt** — `AwLevelInput` là positional record với **default `= false`**
|
||||
(`ApprovalWorkflowV2AdminFeatures.cs:216-230`) ⇒ JSON thiếu key bind thẳng `false`, **không 400**,
|
||||
không validator nào kêu.
|
||||
3. **UI không bắt** — ô tick là controlled input trên state FE, tick vẫn hiện dấu, vẫn đổi màu.
|
||||
|
||||
⇒ Chuỗi "tick → lưu → toast xanh → DB ghi `false`" **hoàn toàn không có tín hiệu lỗi ở bất kỳ tầng nào**.
|
||||
Chỉ lộ khi UAT mở lại thấy ô trống, hoặc tệ hơn: người được chỉ định bấm xóa thì **403** mà không ai hiểu vì sao.
|
||||
|
||||
## 3. Verify
|
||||
|
||||
| Phép đo | Kết quả |
|
||||
|---|---|
|
||||
| `npm run build` **fe-admin** | ✅ **PASS** — `✓ 1950 modules transformed` · `✓ built in 21.05s` · bundle `index-_gosUXpu.js` 1,630.28 kB · **0 TS error** |
|
||||
| `npm run build` **fe-user** | ✅ **PASS** — `✓ 1939 modules transformed` · `✓ built in 8.32s` · bundle `index-B-nir85X.js` 1,536.32 kB · **0 TS error** |
|
||||
| Warning | 3 cái, **pre-existing 100%**: `@import` phải đứng trước rule · chunk >500 kB · `realtime.ts` INEFFECTIVE_DYNAMIC_IMPORT |
|
||||
| Dây đã nối | **5/5** + ô tick + 2 neo fe-user (grep 10 hit / 3 file) |
|
||||
| `git status` | đúng **3 file code** + sub-MD này. (`.session-counter.json` = harness, không phải tôi; `sub-implementer-backend-1.md` = lane BE song song) |
|
||||
|
||||
**Mirror / SHA:** slice này **KHÔNG có cặp mirror nào** — Designer chỉ tồn tại ở `fe-admin`
|
||||
(fe-user không có `ApprovalWorkflowsV2Page.tsx`), matrix chỉ-xem chỉ tồn tại ở `fe-user`
|
||||
(fe-admin không có `WorkflowMatrixViewPage.tsx`, cũng không có `types/approvalWorkflowV2.ts`).
|
||||
⇒ 3 file, 0 cặp SHA. Không phải bỏ sót mirror.
|
||||
**Chứng minh đợt-2 chưa bị đụng:** `PeWorkflowPanel.tsx` md5 **`02671be6334110028c55fe82f5e70dee`** ×2 app
|
||||
— khớp đúng con số spec §E3 ⇒ file đặt nút Xóa còn nguyên vẹn và vẫn byte-identical.
|
||||
|
||||
## 4. 🔴 Phụ thuộc chéo lane — cần lane BE xác nhận (chưa xong lúc tôi return)
|
||||
|
||||
Đo **2 lần**, lane BE land giữa chừng:
|
||||
- **Lúc bắt đầu:** `AllowApproverDelete` trong `src/Backend` = **0 hit**.
|
||||
- **Lúc return (sau build):** đã có **entity** `ApprovalWorkflow.cs:169 public bool AllowApproverDelete`
|
||||
+ migration `20260727033522_AddPeAllowApproverDelete` (3-file). ✅ **Tên khớp chính xác** cái FE chờ.
|
||||
NHƯNG `SolutionErp.Application/` vẫn **0 hit** ⇒ **4 neo DTO chưa nối**.
|
||||
|
||||
⇒ Đúng lúc này, tick trên Designer **vẫn chưa tới được DB**: cột đã có, ô tick đã có, nhưng
|
||||
không có đường DTO nối 2 đầu. Cần lane BE đóng nốt 4 neo dưới đây (đây là số dòng của khuôn F5,
|
||||
đo trực tiếp trên file):
|
||||
|
||||
- **Đọc về:** `AwLevelDto` (`…AdminFeatures.cs:41`) phải có `bool AllowApproverDelete` + projection `:188`
|
||||
⇒ camelCase mặc định của System.Text.Json cho ra đúng `allowApproverDelete` mà `LevelDto:58` chờ.
|
||||
- **Ghi lên:** `AwLevelInput` (`:230`) phải có `bool AllowApproverDelete = false` + map entity `:368`.
|
||||
🔴 Nếu BE đặt **tên khác** (vd `AllowDeleteWhilePending`) thì FE gửi key lạ → **bị bỏ qua im lặng**,
|
||||
đúng **cùng một class lỗi** với bẫy `:620`. Đây là chỗ duy nhất 2 lane có thể lệch mà cả 2 build đều xanh.
|
||||
|
||||
## 5. Đã CỐ Ý không làm (ranh giới đợt 1/đợt 2)
|
||||
|
||||
| Bỏ qua | Lý do |
|
||||
|---|---|
|
||||
| Nút Xóa ở màn duyệt (`PeWorkflowPanel.tsx` ×2) | đợt 2, spec mục 14/14a — cần prop + mutation mới |
|
||||
| `types/purchaseEvaluation.ts` (`fe-admin:274/427` · `fe-user:276/429`) | đó là bundle **PE detail** (`levelOptions` + `approvalFlow`), BE đợt 1 **không** trả cờ này ⇒ khai `boolean` non-optional bây giờ = **type nói dối**. Thuộc gate E4 đợt 2 |
|
||||
| `Layout.tsx` regex, `menuKeys.ts`, màn "Đã xóa" | checklist mục 3-28, đợt 2 |
|
||||
| Luật 4-place mirror | không kích: 0 route mới, 0 menu key mới, 0 page mới (spec §C xác nhận thêm nút/mục không kích 4-place) |
|
||||
|
||||
<!-- END sub-implementer-frontend-2 -->
|
||||
Reference in New Issue
Block a user