From bc17dba547155577c561239700fae4cec5b7df0f Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Mon, 27 Jul 2026 11:55:41 +0700 Subject: [PATCH] wal: flush 20260727T1155 --- .claude/WAL.md | 3 +- .../sub-implementer-backend-4.md | 130 ++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 .claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-backend-4.md diff --git a/.claude/WAL.md b/.claude/WAL.md index b08463f..3b08bdf 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -37,7 +37,8 @@ chain: [!] 🔴 A-DEPLOY **HOÃN — GATE ĐÓNG**. Anh lệnh deploy, em KHÔNG đẩy vì test ĐỎ. Đo thật: `dotnet test` = **6 FAIL / 505** (Domain 45 PASS · Infra 499P/6F; baseline cũ 532 ⇒ +18 test mới, 6 đỏ). Fail: **F12** (bài kiểm chứng TRUNG TÂM §F.0!) · F14 · F14_F5a · F1c · F16 · ZZ_DIAG_AddLevel (scaffold chẩn-đoán sub bỏ quên, KHÔNG phải test thật — phải gỡ) [!] 🔴 A-wave2 reviewer **KHÔNG SINH GÌ** — `reviewer-diff-dot1.md` không tồn tại, return rỗng ⇒ **diff đợt 1 CHƯA HỀ ĐƯỢC SOI**, kể cả 2 mục lead tự viết. Phải chạy lại [!] wf: A-wave3 hmw VÁ 6 ĐỎ — run-id engine `wf_4ea6451f-648` · 1 task implementer-backend. 2 LỚP lỗi lead đã phân loại: **A** `association severed` (F14, F14_F5a) — `ExecuteDelete` xoá DB nhưng KHÔNG đụng change-tracker ⇒ opinion còn trong RAM ⇒ `Remove(level)` ném · **B** `affected 0 rows` (F12, F1c, F16) — nghi `AsNoTracking` + entity MỚI bị đánh dấu `Modified` thay `Added` (chứng: ZZ_DIAG in `ApprovalWorkflowLevel/Modified`). +gỡ ZZ_DIAG scaffold -[ ] A-wave4: chạy LẠI reviewer (wave-2 sinh 0 file) — BẮT BUỘC trước deploy +[x] A-wave3 XONG — **test XANH 0 FAIL / 549** (45 Domain + 504 Infra; 505−1 do gỡ ZZ_DIAG scaffold). Lead tự đọc lại F14: assertion **KHÔNG bị nới** (ConflictException + message "chữ ký" + DB nguyên vẹn + khối CHỨNG-NHÂN tự Remove để chứng FK Restrict là thật) ⇒ KHÔNG xanh-giả. Root cause đúng 2 lớp lead đoán: **B** = thêm `db.ApprovalWorkflowLevels.Add(newLevel)` TƯỜNG MINH (EF đoán nhầm Added→Modified) · **A** = helper detach chữ ký khỏi ChangeTracker trước `Remove(level)`, quét theo TÊN CỘT ⇒ phủ 7 bảng chữ ký/6 module +[!] wf: A-wave4 reviewer — spawn TRỰC TIẾP qua Agent-tool (KHÔNG qua hmw: wave-2 hmw-reviewer sinh 0 file). Ưu tiên: 2 mục lead tự viết · vùng wave-3 vừa vá · 🔴 nghi ngờ riêng của lead: `ExecuteDelete` commit NGAY, nếu SaveChanges sau đó fail thì chữ ký đã xoá CỨNG mà phần còn lại rollback ⇒ mất dữ liệu không hoàn tác [~] A-vá-lỗi wave-3 gốc: F14 lộ bug THẬT — guard F-5 không chặn trước `db.ApprovalWorkflowLevels.Remove(gone)` ⇒ EF ném "association ... severed" thay vì ConflictException lịch sự. Tức đúng cái F-14 sinh ra để bắt [ ] A-DEPLOY (sau khi wave-3 xanh + reviewer PASS) [ ] ⚠️ Deploy này mang **migration mới** `20260727033522_AddPeAllowApproverDelete` lên PROD — AddColumn bit default false, additive an-toàn, nhưng LÀ đổi schema prod thật diff --git a/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-backend-4.md b/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-backend-4.md new file mode 100644 index 0000000..3d3cc20 --- /dev/null +++ b/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-backend-4.md @@ -0,0 +1,130 @@ +# sub-implementer-backend-4 — WAVE 3: vá 6 test đỏ (hạng mục F, S155) + +Run: `2026-07-27-S155-pe-delete-approver` · vai: implementer-backend · ngày 2026-07-27 + +--- + +## BƯỚC 1 — ĐO TRƯỚC KHI SỬA (không sửa mù) + +Lệnh: `dotnet test SolutionErp.slnx --nologo -v minimal` + +``` +Failed! - Failed: 6, Passed: 499, Skipped: 0, Total: 505 - SolutionErp.Infrastructure.Tests.dll (net10.0) +``` +(Domain: 45 PASS — không đỏ.) + +6 đỏ ĐÚNG như bảng lead giao. Thông điệp lỗi THẬT (trích từ output): + +| # | Test | Exception THẬT | Ném tại | +|---|---|---|---| +| 1 | `F1c_SafeUpdate_LogsChangelogWithReason_OnlyToUnfinishedPes` | `DbUpdateConcurrencyException: expected to affect 1 row(s), but actually affected 0 row(s)` | `ApprovalWorkflowV2AdminFeatures.cs:806` (`SaveChangesAsync`) | +| 2 | `F12_SafeUpdate_AddsPersonToExistingLevel_RunningPeAcceptsNewApproverImmediately` | y hệt #1 | `:806` | +| 3 | `F16_ProposalWorkflow_SafeUpdate_Succeeds_AndWritesNoPeChangelog` | y hệt #1 | `:806` | +| 4 | `F14_RemoveApproverWithLiveOpinion_BlockedPolitely_NotFkViolation` | `InvalidOperationException: association ... 'ApprovalWorkflowLevel' and 'PurchaseEvaluationLevelOpinion' ... has been severed, but the relationship is ... required` | **UpdateAwDefinitionTests.cs:580** (dòng CHỨNG-NHÂN của CHÍNH TEST, không phải handler) | +| 5 | `F14_F5a_OpinionBelongsToDeletedPe_RemovalAllowed_AndOrphanPurged` | y hệt #4 | handler `:755` `db.ApprovalWorkflowLevels.Remove(gone)` | +| 6 | `ZZ_DIAG_AddLevel` | giàn giáo chẩn-đoán, tự ném `Exception` để in tracker | — | + +Dump quyết định (từ ZZ_DIAG): +``` +ENTRIES=[ApprovalWorkflowLevel/Modified/Id=60537ebc-...] +TRACKER=[ApprovalWorkflow/Modified || ApprovalWorkflowLevel/Modified || User/Unchanged ×3 + || Role/Unchanged || IdentityUserRole/Unchanged || ApprovalWorkflowStep/Unchanged + || ApprovalWorkflowLevel/Unchanged] +``` + +--- + +## BƯỚC 2 — ROOT CAUSE ĐO ĐƯỢC (không chép giả thuyết) + +### LỚP B (3 test) — nguyên nhân KHÔNG phải `AsNoTracking` + +Giả thuyết lead: "vùng ghi changelog dùng `db.PurchaseEvaluations.AsNoTracking()` rồi đem cập nhật ⇒ affected 0". +**BÁC BỎ bằng đọc mã:** `:778-783` chỉ `Select(p => new { p.Id, p.Phase })` — projection sang anonymous type, KHÔNG hề gán/ghi lại PE nào. Không có UPDATE nào phát cho `PurchaseEvaluations`. Và F16 (module Đề xuất) thậm chí KHÔNG vào nhánh changelog PE (`:774-776` lọc `DuyetNcc/DuyetNccPhuongAn`) mà vẫn đỏ y hệt ⇒ thủ phạm nằm NGOÀI vùng changelog. + +**Root cause THẬT (khớp đúng dump `Added` → `Modified`):** + +`src/Backend/SolutionErp.Domain/Common/BaseEntity.cs:5` +```csharp +public Guid Id { get; set; } = Guid.NewGuid(); // ⇐ khoá LUÔN có giá trị ngay khi `new` +``` + +Handler thêm dòng Cấp mới bằng **navigation-add** trên entity cha ĐANG ĐƯỢC THEO DÕI +(`:730-747`): `step.Levels.Add(new ApprovalWorkflowLevel { ... })`. + +Khi `DetectChanges` phát hiện một entity LẠ trong collection của cha đã-tracked, EF quyết +`Added` hay `Modified` bằng heuristic **"khoá đã set ⇒ coi là đã tồn tại"**. Vì `BaseEntity` +gán sẵn `Id = Guid.NewGuid()`, khoá KHÔNG BAO GIỜ là default ⇒ EF chọn **Modified** ⇒ phát +`UPDATE ApprovalWorkflowLevels SET ... WHERE Id = @p` cho một dòng CHƯA TỒN TẠI ⇒ 0 row +⇒ `DbUpdateConcurrencyException`. + +Bằng chứng đối chứng: `POST` tạo mới (`CreateAwDefinitionCommandHandler`) KHÔNG dính vì nó +`db.ApprovalWorkflows.Add(wf)` — add từ GỐC ⇒ cả đồ thị bị ép `Added`, heuristic không chạy. + +⇒ **Fix:** đăng ký dòng mới TƯỜNG MINH qua `DbSet.Add` (ép state `Added`, bỏ qua heuristic). + +### LỚP A (2 test) — `ExecuteDelete` không đụng change-tracker (đúng hướng lead) + 1 ca KHÁC + +`PurgeOrphanOpinionsAsync` (`:870-902`) dùng `ExecuteDeleteAsync` — đi thẳng SQL, **không** +cập nhật change-tracker. Bản sao `PurchaseEvaluationLevelOpinion` mà test đã `Add`+`Save` +vẫn nằm trong tracker ở state `Unchanged`. Tới `db.ApprovalWorkflowLevels.Remove(gone)` +(`:755`), EF chạy cascade-check TỨC THÌ (`CascadeDeleteTiming.Immediate` = mặc định), thấy +dependent còn tracked + FK `Restrict` + non-nullable ⇒ ném `InvalidOperationException` +NGAY TẠI `Remove()` (stack: `InternalDbSet.Remove` → `StateManager.CascadeDelete` → +`HandleConceptualNulls`) — chưa kịp tới SaveChanges. + +🔴 **PHÂN BIỆT 2 CA (không được gộp):** +- **F14** (phiếu CÒN SỐNG): handler PHẢI chặn 409. Đo được: guard `:658-665` CHẠY ĐÚNG, + mọi assert phía handler đều xanh. Chỗ nổ là **dòng 580 của chính test** (khối CHỨNG-NHÂN + tự tay `Remove` để chứng minh FK là thật) — xem BƯỚC 5. +- **F14_F5a** (phiếu ĐÃ xoá mềm): handler PHẢI cho qua + purge. Đây mới là lỗi production thật. + +--- + +## BƯỚC 3 — CÁC THAY ĐỔI + +### (1) `ApprovalWorkflowV2AdminFeatures.cs` — vá LỚP B (dòng Cấp mới bị đoán nhầm là "đã tồn tại") + +Vùng `toAdd` (`:730-759` sau sửa): tách `new ApprovalWorkflowLevel {...}` ra biến rồi +**đăng ký tường minh**: +```csharp +db.ApprovalWorkflowLevels.Add(newLevel); // ép state Added, không qua heuristic khoá-đã-set +step.Levels.Add(newLevel); // giữ nav in-memory nhất quán cho phần đọc phía sau +``` +Đúng khuôn đã có sẵn trong repo: `PeWorkItemBudgetFeatures.cs:44` cũng `db.X.Add(rec)` tường minh. + +### (2) `ApprovalWorkflowV2AdminFeatures.cs` — vá LỚP A (`ExecuteDelete` bỏ quên change-tracker) + +Thêm helper `DetachTrackedOpinionsOn(IApplicationDbContext, HashSet)` (đặt cạnh +`PurgeOrphanOpinionsAsync`) + gọi NGAY TRƯỚC vòng `db.ApprovalWorkflowLevels.Remove(gone)`. +Quét `((DbContext)db).ChangeTracker.Entries()` theo **tên cột** `ApprovalWorkflowLevelId` +⇒ phủ đủ **cả 7 bảng chữ ký** (PE + HĐ + Proposal/Leave/Ot/Travel/Vehicle — đã kiểm: +`grep -rl ApprovalWorkflowLevelId src/.../Domain/` ra ĐÚNG 7 file, không dư entity nào khác) +mà không phải liệt kê tay từng DbSet. +Cast `((DbContext)db)` là khuôn đã có trong repo (`PeWorkItemBudgetFeatures.cs:52`, +`PurchaseEvaluationFeatures.cs:125`) ⇒ **KHÔNG phải đổi `IApplicationDbContext`**. + +An toàn: tới điểm gọi, guard F-5 (`:658-673`) đã đọc DB bằng `AsNoTracking` và xác nhận các +dòng đó có **0 chữ ký còn hiệu lực** ⇒ bản sao nào còn tracked cũng là rác ⇒ detach = làm +tracker khớp lại DB (không nuốt dữ liệu thật). + +### (3) `UpdateAwDefinitionTests.cs` — GỠ HẲN `ZZ_DIAG_AddLevel` +Xoá trọn `[Fact] ZZ_DIAG_AddLevel` (26 dòng giàn giáo, ném `Exception` để in tracker). +KHÔNG đụng bất kỳ `[Fact]` nào khác. + +--- + +## BƯỚC 4 — ĐO SAU KHI VÁ (chạy riêng lớp test) + +`dotnet build SolutionErp.slnx` → **0 Warning · 0 Error**. + +`dotnet test --filter FullyQualifiedName~UpdateAwDefinitionTests`: +``` +Failed! - Failed: 1, Passed: 14, Skipped: 0, Total: 15 +``` +- LỚP B: **F12 · F1c · F16 → XANH** (F12 xanh với assert NGUYÊN VẸN — kể cả nửa contrast + `ForbiddenException` trước sửa, `CurrentApprovalLevelOrder == 2` sau sửa, và chữ ký gắn + đúng dòng Cấp mới của bob). +- LỚP A: **F14_F5a → XANH**. +- `ZZ_DIAG` → biến mất (15 test còn lại, trước là 16). +- **F14 → VẪN ĐỎ**, xem BƯỚC 5. +