wal: flush 20260727T1330
This commit is contained in:
@ -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.)
|
||||
|
||||
@ -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 -->
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user