wal: flush 20260727T1330

This commit is contained in:
pqhuy1987
2026-07-27 13:30:58 +07:00
parent a21482ecd7
commit d21b554f29
4 changed files with 106 additions and 1 deletions

View File

@ -63,3 +63,41 @@ Brief ghi gate dùng `actorInV2Level`. Đo thì nó **rộng hơn BE**:
⇒ Dùng `actorIsCurrentApprover` = `!!currentUser?.id && v2Approvers.some(a => a.userId === currentUser.id)`
(tập con CHẶT của `actorInV2Level`, khớp 1-1 rào BE). Khai ở đây để reviewer không tưởng tôi đọc lệch brief.
---
## 2. ✅ BƯỚC 1-2 XONG — nút + dialog + mirror
### 2.1 `PeWorkflowPanel.tsx` (viết ở fe-user → `cp` sang fe-admin)
| Chỗ sửa | Nội dung |
|---|---|
| import | +`Trash2` (lucide) · +`MenuKeys` (`@/lib/menuKeys`) · +`usePermission` (`@/hooks/usePermission`) |
| prop | +`onDeleted?: () => void` — prop **THỨ 4**. KHÔNG tái dùng `onApproved` (khác nghĩa: duyệt ≠ xóa) |
| state | `deleteOpen` · `deleteReason` |
| gate | `canDeleteWhileApproving` = `!readOnly` ∧ `phase===ChoDuyet` ∧ `actorIsCurrentApprover` ∧ `levelOptions?.allowApproverDelete === true` ∧ `can(MenuKeys.PurchaseEvaluations,'Delete')` |
| mutation | `api.delete('/purchase-evaluations/${id}/by-approver', { data: { reason } })` → invalidate `pe-list` + `pe-detail` + **`pe-budget`** rồi `onDeleted?.()` |
| render nút | **CẠNH** `next.map`, là **anh em** của output map trong cùng `flex` — KHÔNG trong map |
| dialog | `<Dialog size="sm">` riêng, `variant="danger"`, 4 gạch đầu dòng hệ quả + `<Textarea maxLength={2000}>` lý do |
**Vì sao đụng 1 token ở điều-kiện khối ngoài** `next.length > 0` → `(next.length > 0 || canDeleteWhileApproving)`:
khối "Hành động:" chỉ render khi có phase kế. Nếu `nextPhases` rỗng mà 4 rào xóa vẫn đúng thì nút sẽ bị
khối cha nuốt IM LẶNG. Nới bằng chính cờ đã qua đủ 4 rào ⇒ không mở rộng bề mặt.
**`pe-budget` có thật** (đo: 6 queryKey `pe-*` sống trong fe-user, `pe-budget` là 1) — không phải key bịa.
### 2.2 Mirror ×2 — kiểm alias TRƯỚC khi copy (không copy mù)
| Kiểm | fe-user | fe-admin |
|---|---|---|
| alias `@/*` (tsconfig.app + vite) | `./src/*` | `./src/*` — **KHỚP** |
| `hooks/usePermission.ts` | có | có (**md5 IDENTICAL**) |
| `lib/menuKeys.ts` → `PurchaseEvaluations` | `:23` | `:23` |
| `Dialog` props | `open/onClose/title/children/footer/size` | **y hệt** (file DIVERGE nhưng chỉ ở style) |
| `Button` variant `danger` | có | có (class khác, key giống) |
⇒ copy an toàn. `md5` sau copy: **`eb55781aae32c8982d9f8d1e4b9b0e26` ×2** + `git diff --no-index` exit 0.
### 2.3 Đính chính snapshot lead gửi giữa chừng (gotcha #68)
Lead báo `Trash2/MenuKeys/onDeleted/can/deleteOpen/canDeleteWhileApproving` **declared-but-never-read**.
Đó là **diagnostic CŨ chụp giữa lúc agent đang sửa**. Đo lại từ đĩa: cả 6 đều có site render, và
`tsconfig.app.json` bật **`noUnusedLocals: true` + `noUnusedParameters: true`** ⇒ `tsc --noEmit -p tsconfig.app.json`
**EXIT 0** đã là bằng chứng KHÔNG còn symbol thừa. (Chỉ tin build chạy SAU agent.)

View File

@ -96,3 +96,63 @@ Số TRƯỚC → SAU (exact, không chỉ "giảm"):
<!-- flush 2 -->
## 5. KẾT QUẢ chạy lọc (10 ca mới): **9 PASS / 1 FAIL**
```
Failed! - Failed: 1, Passed: 9, Skipped: 0, Total: 10 - SolutionErp.Infrastructure.Tests.dll
T26_Delete_WritesChangelogWithDeleteAction_AndCarriesReasonWhenProvided [FAIL]
Expected logs to contain 2 item(s) ..., but found 0: {empty}.
```
## 6. 🔴🔴 BUG PRODUCTION — vết xóa (changelog + LÝ DO) KHÔNG BAO GIỜ được ghi
Giả thuyết ở mục 3 **đúng**. Không suy đoán — đã đo bằng chẩn đoán ChangeTracker riêng (file tạm, đã xóa sau khi đọc):
```
stateAfterAdd=Added | navFixedUp=True | parentColl=1 | cascadeTiming=Immediate
| stateAfterRemove=Detached | rowsInDb=0
```
Diễn giải từng bước, khớp đúng mã:
1. `PeSoftDeleteFeatures.cs:126` `Changelogs.Add(...)` ⇒ state **Added**; EF fixup tự gắn nó vào `pe.Changelogs` (navigation 2 chiều `PurchaseEvaluationConfiguration.cs:78`, `OnDelete(Cascade)`) — `parentColl=1` chứng minh.
2. `PeSoftDeleteFeatures.cs:142` `Remove(pe)``CascadeDeleteTiming.Immediate` (mặc định) cascade **ngay tại dòng này**; dependent đang `Added` bị cascade ⇒ EF chuyển thành **Detached** (xóa entity Added = gỡ theo dõi, không phải Deleted).
3. `:143` `SaveChanges`**0 row**. `AuditingInterceptor` chạy ở `SavingChanges` = **SAU** điểm mất mát ⇒ việc nó đổi Deleted→Modified không cứu được vết.
**Prod dính y hệt.** Khác biệt duy nhất giữa harness của tôi và prod là interceptor, mà interceptor nằm SAU chỗ mất. Bằng chứng gián tiếp trong chính suite này: T25b xanh ⇒ xóa mềm chạy đúng, `DeletedBy`/`DeletedAt` đúng — **chỉ LÝ DO bị nuốt IM LẶNG**. Đúng lớp lỗi mà spec §B4 dựng ra để chống.
**Grep cùng lớp (bài học root-cause):** đây là site DUY NHẤT trong `src/Backend` mang hình "Add con → Remove cha → 1 SaveChanges". Đường xóa nháp cũ `PurchaseEvaluationFeatures.cs:1408` không ghi changelog nên không dính. Các file khác có `Changelogs.Add` đều xóa CON (supplier/detail) chứ không xóa PE ⇒ nguyên tắc (PE) sống ⇒ không cascade.
**KHÔNG tự sửa** (production = ngoài phạm vi). Giữ T26 ĐỎ + khai, không nới assert. Hướng vá để lead quyết (đều nằm trong `PeSoftDeleteFeatures.cs`): (a) `SaveChanges` cho changelog trước rồi Remove (2 lượt ghi, cần transaction bao ngoài); (b) đặt `CascadeDeleteTiming.Never` quanh lời gọi; (c) không dùng `Remove()` mà set thẳng `IsDeleted/DeletedAt/DeletedBy`. Tôi không chọn hộ.
<!-- flush 3 -->
## 7. SỐ THẬT — full suite `dotnet test SolutionErp.slnx --nologo -v minimal`
```
Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45 - SolutionErp.Domain.Tests.dll
Failed! - Failed: 1, Passed: 515, Skipped: 0, Total: 516 - SolutionErp.Infrastructure.Tests.dll
```
| | Domain | Infra | Tổng |
|---|---|---|---|
| Baseline (lead đo) | 45 | 506 | **551** pass |
| Sau đợt này | 45 | 516 | **561** total — 560 pass / **1 fail** |
- **+10 test** (đúng 10 ca được giao), tất cả nằm trong 1 file mới.
- **0 hồi quy:** Infra passing 515 9 ca mới xanh = **506** = đúng con số Infra cũ ⇒ không ca cũ nào bị tôi làm đỏ.
- 1 FAIL duy nhất = **T26**, giữ đỏ có chủ ý (bug production ở mục 6), assert KHÔNG bị nới.
## 8. Sản phẩm
- File MỚI (duy nhất): `tests/SolutionErp.Infrastructure.Tests/Application/PeDeleteByApproverTests.cs`
- File tạm dùng để chẩn đoán ChangeTracker: đã **XÓA** sau khi đọc kết quả (kiểm lại: 0 file `Zz*` còn sót).
- KHÔNG chạm `src/Backend`, KHÔNG chạm `fe-*`, KHÔNG sửa `IdentityFixture`/`SqliteDbFixture` dùng chung.
## 9. Ghi chú bàn giao
- `SoftDeleteFixture` (trong chính file test, ~90 LOC) là fixture ĐẦU TIÊN của repo wire `AuditingInterceptor`. Bất kỳ test tương lai nào đo hành vi `Remove()` = xóa mềm đều nên dùng khuôn này thay vì set `IsDeleted` thủ công.
- T24b là ca duy nhất phân biệt được `matchingLevel.AllowApproverDelete` (đúng) với `g.Any(...)` (sai). Nếu ai đó "gọn hoá" chỗ đó, T24b đỏ ngay.
- T22c dùng 2 đối chứng (drafter + approver) chứ không 1 — để số 0 của người ngoài không thể là hệ quả của dữ liệu rỗng.
<!-- END sub-d2-test-4 -->