wal: flush 20260812T2215

This commit is contained in:
pqhuy1987
2026-08-12 22:15:40 +07:00
parent 091073f910
commit 2fed7540ad
3 changed files with 149 additions and 10 deletions

View File

@ -3,14 +3,112 @@
Agent: test-specialist · Run: 2026-08-12-S190-soltuon-dieu-chinh-hd
Ranh: Write CHỈ {ContractApproveFinalizeTests.cs, sub-wq3-test.md}. KHÔNG commit. KHÔNG chạm production code.
File test: `tests/SolutionErp.Infrastructure.Tests/Application/ContractApproveFinalizeTests.cs` (MỚI, 4 `[Fact]`).
## Ca đã viết
(CHƯA VIẾT — đang đọc BƯỚC 1)
Đối tượng đo: `src/Backend/SolutionErp.Infrastructure/Services/ContractWorkflowService.cs:527-535`
(`if (matchingLevel.AllowApproverFinalize && applyLevelFinalize)``EndedByLevelFinalize=true`
`FinalizeToDaPhatHanhAsync:576`). Đây là **test-bù** (mã BE land trước, chưa commit), KHÔNG phải test-before.
**Hình chung của cả 4 ca (điều kiện SỐNG CÒN):** quy trình **2 Bước** — Bước 1 có **2 Cấp**, Bước 2
có 1 Cấp — người bấm Duyệt luôn là **Bước 1 Cấp 1**, tức còn NGUYÊN 2 chặng phía sau.
Bài S164-K3: cắm cờ finalize ở slot CUỐI thì "phát hành + có mã" xảy ra ở CẢ HAI thế giới ⇒ F-1
xanh mà chứng minh 0. Hình này được **chốt bằng assert** `AssertFinalizeSlotIsNotTheLastAsync`
(steps==2 ∧ levels(Bước 1)==2) gọi ngay trước mỗi lượt bấm, để lần "gọn hoá seed" sau này không âm
thầm làm rỗng cả file.
| Ca | Tên `[Fact]` | Biến bật/tắt | Kết quả kỳ vọng |
|---|---|---|---|
| F-1 | `ApproveV2_LevelFinalizeFlag_WithDefaultApply_PublishesContractAndSetsRuntimeFlag` | cờ Cấp `AllowApproverFinalize=true` · **KHÔNG truyền** `applyLevelFinalize` | `Phase=DaPhatHanh` · `EndedByLevelFinalize=true` · `MaHopDong="PFIN1/HĐTP/SOL&FIN1/01"` · con-trỏ Bước/Cấp + SLA `null` |
| F-2 | `ApproveV2_LevelFinalizeFlag_ButApproverOptsOut_AdvancesNormally` | cờ Cấp `true` · `applyLevelFinalize:false` | `Phase=ChoDuyet` · `LevelOrder=2` · `StepIndex=0` · cờ `false` · `MaHopDong=null` |
| F-3 | `ApproveV2_NoLevelFinalizeFlag_EvenWhenApplyRequested_AdvancesNormally` | cờ Cấp `false` · `applyLevelFinalize:true` | như F-2 |
| F-4 | `ApproveV2_AfterLevelFinalize_FurtherApproveThrowsConflict_AndChangesNothing` | finalize xong rồi bấm Duyệt tiếp ×2 người | `ConflictException("*DaPhatHanh*không hỗ trợ*")` ×2 · KHÔNG đổi gì |
### Vì sao ba ca F-1/F-2/F-3 không thay thế nhau (ma trận chứng `&&`)
- F-1 giữ **vế dương** + đo luôn ngữ nghĩa **OPT-OUT** (mặc định `true``IContractWorkflowService.cs:25`)
— cố ý KHÔNG truyền tham số; truyền tay `true` là bỏ mất vế đó.
- F-2 tắt vế **ý người duyệt**; F-3 tắt vế **cấu hình Cấp**. Cài `||` thay `&&` ⇒ ĐỎ cả hai.
Bỏ đọc tham số ⇒ chỉ F-2 đỏ. Bỏ đọc cờ cấu hình ⇒ chỉ F-3 đỏ.
- F-3 còn có assert **tiền-đề** đọc lại `AllowApproverFinalize` từ DB — chốt "biến đang tắt đúng là
biến định tắt", nếu không thì seed sai cờ làm F-3 trùng hệt F-2 mà không ai biết.
### F-4 — assert chọn theo MÃ, không theo mong muốn
Đọc `TransitionAsync` trước khi viết: sau finalize `Phase=DaPhatHanh` ⇒ lượt Duyệt kế tiếp trượt hết
mọi nhánh (reject `:55` / trình `:96` / duyệt `:159` đều đòi phase khác, `isAdmin` false) và rơi
xuống câu cuối `:268` `ConflictException("Transition {from} → {to} không hỗ trợ.")`.
⇒ assert đúng là **409 + không đổi gì**, không phải "im lặng bỏ qua". Hai người bấm, hai lý do đời thực:
(a) chính người vừa chốt bấm lại (double-click), (b) NV Cấp 2 — người bị cắt lượt, màn hình cũ còn nút Duyệt.
Vế "không đổi gì" đo cả `MaHopDong` (không cấp mã lần 2), `ContractApprovals` count==1,
`ContractLevelOpinions` count==1 — một cài đặt "ném lỗi SAU khi đã ghi" vẫn xanh nếu chỉ assert exception.
### 2 quyết định hạ tầng
1. **`IdentityFixture`, KHÔNG dựng fixture riêng có `AuditingInterceptor`** (memory-pack gợi ý mirror
`ContractDeleteGuardTests.cs:69`). Lý do: service đòi `UserManager<User>` THẬT, còn interceptor chỉ
phục vụ ô audit/xoá-mềm — file này không đo ô nào trong số đó. Khuôn đúng là file cùng đối tượng đo:
`Services/ContractWorkflowServiceApproveV2Tests.cs:25` (đã chạy xanh với `ContractCodeGenerator`
+ transaction `Serializable` trên SQLite).
2. Mỗi ca một fixture ⇒ DB SQLite in-memory riêng ⇒ `ContractCodeSequences` không dính nhau, mã HĐ
mong đợi luôn `/01`. `NoOpNotificationService` dùng lại stub `internal`
`Services/PurchaseEvaluationWorkflowServiceGuardTests.cs:188` (không chép bản thứ hai).
## Kết quả chạy
(CHƯA ĐO)
### (1) Lọc — 4/4 PASS
```
dotnet test tests/SolutionErp.Infrastructure.Tests \
--filter FullyQualifiedName~ContractApproveFinalizeTests --nologo -p:BuildInParallel=false -maxcpucount:1
Passed! - Failed: 0, Passed: 4, Skipped: 0, Total: 4, Duration: 3 s
```
### (2) CHỨNG RĂNG — probe VI-PHÂN (sửa TEST, KHÔNG chạm production)
Không được chạm mã BE ⇒ không fault-inject được. Thay bằng: thế-giới-hỏng **tương đương chính xác**
với việc gọi CHÍNH service thật bằng tham số/seed khác ⇒ đổi đúng 1 chữ TRONG TEST, chạy trên mã
production THẬT. Backup file trước, revert sau, `diff` chứng khớp byte.
| Probe (thế giới hỏng mô phỏng) | Đổi | Kết quả |
|---|---|---|
| BE bỏ qua nhánh finalize (luôn advance) | F-1 thêm `applyLevelFinalize:false` | **ĐỎ**`Expected contract.Phase to be DaPhatHanh {9} … but found ChoDuyet {10}` (`:240`) |
| BE bỏ đọc tham số `applyLevelFinalize` | F-2 bỏ `applyLevelFinalize:false` | **ĐỎ**`Expected … ChoDuyet {10} … but found DaPhatHanh {9}` (`:302`) |
| BE dùng `\|\|` thay `&&` (ý người duyệt tự cấp quyền) | F-3 seed `allowFinalizeAtS1L1:true` (+ tạm bỏ assert tiền-đề để nhìn vế KẾT QUẢ đỏ) | **ĐỎ**`Expected … ChoDuyet {10} … but found DaPhatHanh {9}` (`:359`) |
`Failed: 3, Passed: 1` — F-4 giữ xanh đúng như thiết kế (không phụ thuộc 2 biến bị lật).
Revert: `diff backup ↔ file` **RỖNG** (khớp byte) · `grep -c PROBE` = **0**.
🔴 F-4 KHÔNG có probe vi-phân: thế-giới-hỏng của nó ("nhánh duyệt nhận cả HĐ đã phát hành") chỉ dựng
được bằng cách sửa production ⇒ khai thẳng. Răng của F-4 là **nội tại**: `ThrowAsync<ConflictException>`
đỏ nếu không ném, + `WithMessage("*DaPhatHanh*…")` khoá đúng lý do, + count==1 khoá vế "không ghi".
### (3) Full suite — 697/0, khớp đúng dự đoán 693+4
```
dotnet test SolutionErp.slnx --nologo -p:BuildInParallel=false -maxcpucount:1
Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45 - SolutionErp.Domain.Tests.dll (59 ms)
Passed! - Failed: 0, Passed: 652, Skipped: 0, Total: 652 - SolutionErp.Infrastructure.Tests.dll (39 s)
tổng = 45 + 652 = 697 (baseline 693 → Δ+4, 0 hồi quy)
```
### (4) Audit ranh giới (isolation)
```
git status --porcelain | grep -E "tests/|sub-wq3-test"
M .claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-wq3-test.md
?? tests/SolutionErp.Infrastructure.Tests/Application/ContractApproveFinalizeTests.cs
```
Đúng 2 file. `src/**` dirty là của lane WQ3-BE (có từ trước lượt này), tôi KHÔNG chạm.
KHÔNG commit · KHÔNG ghi `agent-memory/**` (spawn-record trả về cho em main harvest).
## Ghi chú
(đang đọc)
- **0 bug production phát hiện** — cả 4 ca xanh trên mã BE vừa land, không ca nào phải giữ ĐỎ,
không ca nào phải nới assert.
- **Spec ⟂ code:** không thấy lệch. Cột `Contract.EndedByLevelFinalize` (`Contract.cs:60`, Mig
`20260812141501_AddContractEndedByLevelFinalize`) có write-site DUY NHẤT là nhánh finalize —
F-2/F-3 khoá luôn chiều "không được gắn nhãn kết thúc sớm cho lượt duyệt thường", tức bảo vệ đúng
lý do cột này tồn tại thay vì suy từ config (bài #78).
- **Vùng KHÔNG phủ ở lane này** (khai để lead không đọc nhầm phạm vi):
- `ContractsController.cs:71` `ApplyLevelFinalize: body.ApplyLevelFinalize ?? true` — lớp
controller/DTO; test này gọi service trực tiếp nên chỉ chứng mặc định **ở tầng service**
(`IContractWorkflowService.cs:25`). Hai chỗ đang khớp nhau, nhưng đó là 2 mặc định riêng biệt.
- Ca `skipToFinal=true` **cùng lúc** `applyLevelFinalize=true` (comment `:515-521` khai skip thắng
`return` trước). Không viết: nằm ngoài 4 ca được giao, và đã có `ApproveV2_SkipToFinal_*`
(`ContractWorkflowServiceApproveV2Tests.cs:348`) neo nhánh skip.
- FE panel (lane WQ3b) — ngoài phạm vi.

View File

@ -1,7 +1,36 @@
# WQ3b-FE — port 3 mon KHKK vao panel duyet HD + tai-vi-tri may duyet ve panel-3 (S190 · YC-030)
> Worker #53 chết giữa (68 tool-use, artifact 0 ruột). Trạng thái lúc chết: panel ×2 + types ×2 +
> content fe-user + MyContractsPage-comment ĐÃ sửa; THIẾU copy content fe-admin + gắn panel 5 host.
> **LEAD hoàn thiện on-behalf phần cơ học còn lại** (copy-byte + 5 khối render cùng khuôn) — khai.
## Việc đã làm
## Ô acceptance
1. (worker) `ContractWorkflowPanel.tsx` ×2 app: port 3 món KHKK — ô tích "Cấp này KẾT THÚC quy
trình" (`allowApproverFinalize` 3 hit) + meta `{workflowCode} v{NN}` + banner
`endedByLevelFinalize` (7 hit cụm) + gửi `applyLevelFinalize` trong body Duyệt. SHA cặp
`2edbe534901d` ×2 KHỚP.
2. (worker) `types/contracts.ts` ×2 app (KHÔNG phải cặp SHA — vốn khác nhau by-design, HEAD
`1d4d649f``3d2da3bd`): nở `workflowCode`/`workflowVersion`/`endedByLevelFinalize` +
body `applyLevelFinalize?`/`skipToFinal?` (fe-admin :194-220).
3. (worker) `ContractDetailContent.tsx` fe-user: GỠ khối isV2 render máy duyệt (comment lật :15,
:144 — 0 hit code-only).
4. (lead) copy-byte content → fe-admin: SHA cặp `7ab04d9d939e` ×2 KHỚP.
5. (lead) Gắn `<ContractWorkflowPanel contract={detail.data}>` (vỏ `section border-brand-200`,
điều kiện `approvalWorkflowId != null` — đúng khối cũ) vào **5 host**: MyContractsPage panel-3
(TRÊN WorkflowHistoryPanel — khuôn KhkkListPage) · ContractDetailPage ×2 app (aside) ·
InboxPage fe-user · ContractsListPage fe-admin. Mỗi host +1 import.
## Ô acceptance (lead đo @S190 sau hoàn thiện)
- tsc ×2 EXIT=0 · build ×2 EXIT=0 (`✓ built`) · eslint fe-user **47** / fe-admin **42** = baseline KHÔNG TĂNG
- SHA cặp: panel `2edbe534901d`×2 ✅ · content `7ab04d9d939e`×2 ✅
- Content 0 render panel (grep code-only = 0; 2 hit còn lại là comment lineage) ✅
- 5/5 host `<ContractWorkflowPanel` = 1 mỗi host ✅
- Hành-vi (ô tích finalize gửi body · banner · meta) = DEFERRED-TO-LOCAL-VERIFY vòng-2 của lead
## DEFERRED/DỪNG
- IDE diagnostics never-read/cannot-find giữa chuỗi edit = snapshot dở (#68) — bác bằng tsc EXIT=0 ×2.
- `skipToFinal` có surface body nhưng FE chưa có đường bấm (khai trong types comment :212) — đúng
scope; ô-tích chỉ cho `applyLevelFinalize`.