Compare commits

...

4 Commits

Author SHA1 Message Date
f4055a1eaa [CLAUDE] Docs: Chunk M4 — S23 t3 Plan M wrap: docs + session log + 2 agent MEMORY drift
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m24s
3 commits Plan M `c2042ef + 508b17a + 4dd6f9c` cumulative:
- M1 (`c2042ef`) BE Service refactor F1.OneLevel/OneStep edge case Bước 1 → reset (0, 1) giữ ChoDuyet (KHÔNG fallback Drafter)
- M3 (`508b17a`) FE × 2 app rename phase=TraLai display label "Trả lại" → "Cần chỉnh sửa lại"
- M2 (`4dd6f9c`) Tests add 2 edge case test 106/106 PASS (+2 từ 104)

Docs update:
- docs/STATUS.md — Last updated S23 t3 + stats 106 test (+2) · 20 memory (2 entry reinforced)
- docs/HANDOFF.md — TL;DR S23 t3 với multi-agent ROI evidence Investigator avoid em main spam refactor
- docs/changelog/sessions/2026-05-15-s23-turn3-plan-m-f1-edge-case.md — Session log đầy đủ

Memory user-level update (2 entry reinforced):
- feedback_per_nv_permission_scope.md — S23 t3 reinforcement "edge case không lùi được KHÔNG fallback role khác"
- feedback_uat_skip_verify.md — Plan L S23 t2 lesson "Service refactor semantic BẮT BUỘC test cùng commit"

Agent MEMORY drift (auto-flush per multi-agent rule §):
- Investigator (.claude/agent-memory/investigator/MEMORY.md) — S23 t2 spawn L2 entry + S23 t3 spawn M0 audit findings
- Reviewer (.claude/agent-memory/reviewer/MEMORY.md) — S23 t3 Plan M cumulative PASS-WITH-NOTES verdict + 2 Minor defer

Reviewer verdict: PASS-WITH-NOTES (26 checks PASS, 0 Major, 2 Minor defer).
- Minor #1: 5-8 inline literal "Trả lại" PE module FE còn (filter / hint / button verb mix) — verb button giữ, filter/hint defer
- Minor #2: Contract + Budget module Phase=98 vẫn 'Trả lại' — Plan M scope PE-only, defer

Stats Plan M chốt:
- 31 mig (no change) · 59 tables · ~145 endpoints · 34 FE pages
- **106 test PASS (+2: F1 OneLevel/OneStep edge case)**
- 47 gotcha · 20 memory (2 entry reinforced) · 6 skills
- 4 sub-agents (1 Investigator pre-flight + 2 Implementer Case 2+3 + 1 Reviewer pre-commit)
- 4 commits Plan M `c2042ef..HEAD` ready push

Pending: CICD Monitor post-deploy verify Plan M

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:21:41 +07:00
4dd6f9c013 [CLAUDE] Tests: Chunk M2 — Add F1 OneLevel/OneStep edge case tests Bước 1 reset ChoDuyet
Hai test mới cover edge case Plan M S23 t3 (em main M1 edit
PurchaseEvaluationWorkflowService.cs line 287-333):

- ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet
  PE init Step 0 Cấp 1 + actor=a1 + slot Cấp 1 tick AllowReturnOneLevel.
  Trước fallback Drafter Phase=TraLai → sau reset (0, 1) giữ Phase=ChoDuyet,
  SLA reset 7d, audit log ContextNote chứa "không lùi được".

- ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet
  PE init Step 0 Cấp 2 + actor=a2 + slot Cấp 2 tick AllowReturnOneStep.
  Service check curStepIdx > 0 → fallback ngay. Assert Phase=ChoDuyet,
  pointer (0, 1), SLA reset, audit log "không lùi được".

Extend helper SeedWorkflowAsync +2 params optional (allowReturnOneLevelL1
+ allowReturnOneStepL2) — default false, không phá compat 4 test ReturnMode
existing. Pattern 3 audit-reuse (extend helper KHÔNG clone).

Audit log assertion dùng ContextNote thay vì Summary: LogTransitionAsync set
Summary cố định "Chuyển phase {from} → {to}", summary từ ApplyReturnModeAsync
chèn vào comment qua line 96-99 service → ContextNote = comment.

K7 cascade NO regression: 3 ApproveV2_SkipToFinal_* tests still green (M1
edit chỉ F1 path, KHÔNG đụng F2 ApproveV2Async).

Verify:
- Build pass (0 error, 2 pre-existing DocxRenderer warning)
- 106/106 test pass (58 Domain + 48 Infra: +2 từ 46 baseline Plan L)
- 10 ReturnMode-class tests pass (4 ReturnMode + 3 ApproveV2_SkipToFinal
  + 1 Reject_NonApprover + 2 edge case mới)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:15:47 +07:00
508b17a43c [CLAUDE] FE-Admin FE-User: Chunk M3 — Rename Phase=TraLai display label "Trả lại" → "Cần chỉnh sửa lại"
Plan M Chunk M3 — UAT disconnect fix: bro UAT phát hiện label "Trả lại" + "Bản
nháp" không phân biệt rõ với end-user → rename Phase=TraLai (98) display label
sang "Cần chỉnh sửa lại" để self-descriptive.

Scope HẸP — chỉ status display reference (KHÔNG đụng action verb):
- types/purchaseEvaluation.ts × 2 app: PurchaseEvaluationPhaseLabel[98] +
  PeDisplayStatusLabel.TraLai = 2 const map rename
- components/pe/PeWorkflowPanel.tsx × 2 app: 2 inline literal hardcode trong
  F1 dialog tooltip `Phase → "..."` + confirm message `Phiếu sẽ về "..."` —
  status reference, KHÔNG phải action verb

KHÔNG đụng:
- Action button label `← Trả lại` (verb, giữ nguyên)
- F1 mode picker label `Trả về Người soạn thảo` (4 mode action, giữ nguyên)
- Comments narrative giữ rationale dev (rule §6.5)
- types/contracts.ts + types/budget.ts Phase 98 'Trả lại' — module Contract +
  Budget khác PE, ngoài scope M3

Mirror 2 app rule §3.9 strict applied.

Verify:
- npm run build fe-admin PASS clean (0 TS err, 9.40s)
- npm run build fe-user PASS clean (0 TS err, 6.92s)

Diff: +4/-4 LOC × 4 file = 8 LOC total

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:15:01 +07:00
c2042ef956 [CLAUDE] PurchaseEvaluation: Chunk M1 — Fix F1.OneLevel/OneStep edge case Bước 1 → giữ ChoDuyet (KHÔNG fallback Drafter)
Bro UAT S23 t3: "Các tính năng trả lại 1 cấp hoặc chỉ định hoặc edit cho
xử lý ở trạng thái đang gửi duyệt luôn, không về draft."

Investigator audit confirm 4 mode F1.OneLevel/Assignee + F2 + F3+F4 main
path đã giữ Phase=ChoDuyet (Mig 28-31 cumulative). Edge case duy nhất còn
fallback Drafter (Phase=TraLai):

- F1.OneLevel khi đang Bước 1 Cấp 1 (curStepIdx=0, curLevel=1) — no further back
- F1.OneStep khi đang Bước 1 (curStepIdx=0)

Logic cũ (line 303-310 OneLevel + 325-332 OneStep):
```
evaluation.Phase = PurchaseEvaluationPhase.TraLai;  // 98
evaluation.CurrentWorkflowStepIndex = null;
evaluation.CurrentApprovalLevelOrder = null;
evaluation.SlaDeadline = null;
return "Trả về Người soạn thảo (fallback — đang Bước 1 Cấp 1)";
```

Logic mới — reset (0, 1) giữ ChoDuyet:
```
evaluation.CurrentWorkflowStepIndex = 0;
evaluation.CurrentApprovalLevelOrder = 1;
summary = "Action 'Trả lại 1 Cấp/Bước' không lùi được — phiếu reset về Approver Bước 1 Cấp 1";
// SLA reset 7d ở cuối hàm cho 3 mode còn lại
```

Semantic mới (per bro chốt AskUserQuestion Plan M):
- Phase giữ ChoDuyet (KHÔNG TraLai=98)
- Pointer reset về (0, 1) = chính Approver A hiện tại (effectively no-op)
- SLA reset 7d (cuối hàm switch áp dụng cho cả 3 mode F1 non-Drafter)
- Audit log rõ "không lùi được" để Drafter/Admin biết action không hiệu lực

KHÔNG đụng:
- F1.Drafter (line 268-275) giữ nguyên semantic Phase=TraLai
- F1.Assignee (line 335-360) giữ nguyên throw nếu không match
- F2 ApproveV2Async skipToFinal (line 483-524 Plan K L1 vừa fix)
- F3 EnsureEditableForDetailsAsync (PurchaseEvaluationDetailFeatures.cs:42)
- F4 AdjustBudgetCommand handler (PurchaseEvaluationFeatures.cs:272-329)

Verify:
- dotnet build src/Backend/SolutionErp.Infrastructure clean (0 err, 2 warn
  pre-existing DocxRenderer)
- Service.cs 13+/13- LOC change (1 file, surgical edit)
- Pending Chunk M2: 2 edge case test (Implementer Case 3 spawn) + verify
  K7 Approver F2 không cascade
- Pending Chunk M3: FE label rename Phase=TraLai "Trả lại" → "Cần chỉnh sửa lại" (Implementer Case 2 spawn × 2 app)
- Pending Chunk M4: docs + memory update + push + CICD verify

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:11:36 +07:00
12 changed files with 318 additions and 25 deletions

View File

@ -204,6 +204,9 @@ KHÔNG `*` / `latest`. Critical pins:
## 📅 Recent activity (last 10 FIFO) ## 📅 Recent activity (last 10 FIFO)
- **2026-05-15 (S24, Plan M Chunk M2 PASS):** F1 edge case Bước 1 reset ChoDuyet tests (em main M1 service edit `PurchaseEvaluationWorkflowService.cs` line 287-333 đã DONE — fallback Drafter TraLai → reset (0, 1) giữ ChoDuyet + audit log "không lùi được"). Cookie-cutter 1 file test `PurchaseEvaluationWorkflowServiceReturnModeTests.cs` — 2 sub-tasks: (1) extend `SeedWorkflowAsync` helper +2 params optional `allowReturnOneLevelL1` + `allowReturnOneStepL2` (default false, không phá compat 4 test ReturnMode existing), set vào `l1.AllowReturnOneLevel` + `l2.AllowReturnOneStep` tương ứng — Pattern 3 audit-reuse EXTEND không clone helper; (2) add 2 `[Fact]` test ngay sau test admin bypass OneLevel (line 241) — `ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` (PE init Step 0 Cấp 1 + actor=a1 + slot Cấp 1 tick AllowReturnOneLevel, build PE inline vì helper `BuildPeAtLevel2` không phù hợp cho Cấp 1) + `ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` (PE Step 0 Cấp 2 + actor=a2 + slot Cấp 2 tick AllowReturnOneStep, reuse `BuildPeAtLevel2`, OneStep service check `curStepIdx > 0` → fallback ngay không quan tâm Cấp). Assert: Phase=ChoDuyet (KHÔNG TraLai như Drafter mode) + pointer (0, 1) + SLA NotNull + Changelog **ContextNote** chứa "không lùi được" (Summary field cố định `"Chuyển phase {from} → {to}"`, summary từ ApplyReturnModeAsync chèn vào comment qua line 96-99 service → LogTransition `ContextNote = comment`). K7 cascade verify NO regression: 3 ApproveV2_SkipToFinal_* tests still green (M1 edit chỉ F1 OneLevel/OneStep edge case, KHÔNG đụng F2 path `ApproveV2Async`). Verify: `dotnet test SolutionErp.slnx` clean 0 err 2 warn pre-existing DocxRenderer, **106/106 PASS** (58 Domain + 48 Infra: +2 từ 46 baseline post Plan L). 10 ReturnMode-class tests verified individually PASS (4 ReturnMode + 3 ApproveV2_SkipToFinal + 1 Reject_NonApprover + 2 edge case mới). Diff +94 LOC trên 1 test file (test add + helper signature 2 params). Token ~10k. Spec deterministic + 1 file independent + < 1h verified.
- **2026-05-15 (S24, Plan M Chunk M3 PASS):** FE rename Phase=TraLai (98) display label "Trả lại" "Cần chỉnh sửa lại" cho UAT disconnect fix. Spec scope HẸP (display badge label + status reference) tuân thủ strict: 4 file FE × 2 app = 8 edit total. **2 file types/purchaseEvaluation.ts** × 2 app: `PurchaseEvaluationPhaseLabel[98]` (raw phase badge) + `PeDisplayStatusLabel.TraLai` (display status badge main user-facing). **2 file components/pe/PeWorkflowPanel.tsx** × 2 app: rename 2 inline literal hardcode "Trả lại" trong F1 dialog tooltip (`Phase → "..."`) + confirm message (`Phiếu sẽ về "..."`) đây status display reference, KHÔNG phải action verb. Pattern 5 mirror 2 app strict applied. KHÔNG đụng: (1) action button label `← Trả lại` (verb), (2) F1 mode picker label `Trả về Người soạn thảo` (4 mode action), (3) comments narrative line 62/71/etc giữ rationale dev (rule §6.5), (4) `types/contracts.ts` + `types/budget.ts` Phase 98 'Trả lại' module Contract + Budget khác PE, ngoài scope M3. Verify: `npm run build` fe-admin PASS clean (0 TS err, 9.40s, 1395 KB bundle unchanged trivial) + `npm run build` fe-user PASS clean (0 TS err, 6.92s, 1275 KB). Diff +4/-4 LOC × 2 = 8 LOC tổng trên 4 file. Token ~9k. Decision tactical: 2 chỗ inline literal PeWorkflowPanel rename để giữ UX consistency với badge label sau rename KHÔNG drift outside spec cùng "status display reference" semantics (badge + tooltip + confirm message phải mirror). Anti-fiddle threshold <20% LOC respected.
- **2026-05-14 (S23 t1, K7 Chunk F PASS):** Mig 31 Approver F2 service regression tests. Sub-task 1 fix broken Drafter F2 test reference K1 flagged: `PurchaseEvaluationWorkflowServiceReturnModeTests.cs:253` `drafter.AllowDrafterSkipToFinal = true` (DELETE 3 deprecated Drafter F2 tests entire `SkipToFinal_DrafterAllowed_SetsPointerToFinalLevel` + `SkipToFinal_DrafterDenied_NonAdmin_Throws` + `SkipToFinal_AdminBypass_Succeeds`, semantic deprecated no value). Sub-task 2 add 3 new Approver F2 tests: `ApproveV2_SkipToFinal_AdminTickFlag_SetsPhaseDaDuyet` (happy path slot Cấp 1 Bước 1 admin tick Phase=DaDuyet, pointer cleared, opinion + PEA + Changelog logged), `ApproveV2_SkipToFinal_FlagOff_NonAdmin_ThrowsConflictException` (denied flag off non-admin slot user throw "chưa được phép duyệt thẳng Cấp cuối"), `ApproveV2_SkipToFinal_FlagOff_Admin_BypassesFlagCheck` (admin bypass flag off admin role DaDuyet allowed). Pattern 11 SeedWorkflowAsync cookie-cutter REUSE created `SeedApproverF2WorkflowAsync` helper (2 Steps × 2 Levels multi-step verify skip thẳng terminal KHÔNG fallthrough advance pointer next Step), AllowApproverSkipToFinal per-slot param. PE init Phase=ChoDuyet + CurrentWorkflowStepIndex=0 + CurrentApprovalLevelOrder=1 (vs S22 happy path từ DangSoanThao). Add `using Microsoft.EntityFrameworkCore` cho `.ToListAsync()` PEL/PEA/Changelog query. File header narrative line 17-25 REWRITE để track Mig 31 refactor semantic Approver scope ChoDuyet vs Drafter-from-Nháp . Verify: `dotnet build` clean 0 err 2 warn pre-existing DocxRenderer. `dotnet test SolutionErp.slnx` 104 PASS (58 Domain + 46 Infra, 3 deleted + 3 added cancel out, baseline preserved). 3 Approver F2 tests verified individually PASS. Diff +175/-92 LOC trên 1 file. Token ~14k. - **2026-05-14 (S23 t1, K7 Chunk F PASS):** Mig 31 Approver F2 service regression tests. Sub-task 1 fix broken Drafter F2 test reference K1 flagged: `PurchaseEvaluationWorkflowServiceReturnModeTests.cs:253` `drafter.AllowDrafterSkipToFinal = true` (DELETE 3 deprecated Drafter F2 tests entire `SkipToFinal_DrafterAllowed_SetsPointerToFinalLevel` + `SkipToFinal_DrafterDenied_NonAdmin_Throws` + `SkipToFinal_AdminBypass_Succeeds`, semantic deprecated no value). Sub-task 2 add 3 new Approver F2 tests: `ApproveV2_SkipToFinal_AdminTickFlag_SetsPhaseDaDuyet` (happy path slot Cấp 1 Bước 1 admin tick Phase=DaDuyet, pointer cleared, opinion + PEA + Changelog logged), `ApproveV2_SkipToFinal_FlagOff_NonAdmin_ThrowsConflictException` (denied flag off non-admin slot user throw "chưa được phép duyệt thẳng Cấp cuối"), `ApproveV2_SkipToFinal_FlagOff_Admin_BypassesFlagCheck` (admin bypass flag off admin role DaDuyet allowed). Pattern 11 SeedWorkflowAsync cookie-cutter REUSE created `SeedApproverF2WorkflowAsync` helper (2 Steps × 2 Levels multi-step verify skip thẳng terminal KHÔNG fallthrough advance pointer next Step), AllowApproverSkipToFinal per-slot param. PE init Phase=ChoDuyet + CurrentWorkflowStepIndex=0 + CurrentApprovalLevelOrder=1 (vs S22 happy path từ DangSoanThao). Add `using Microsoft.EntityFrameworkCore` cho `.ToListAsync()` PEL/PEA/Changelog query. File header narrative line 17-25 REWRITE để track Mig 31 refactor semantic Approver scope ChoDuyet vs Drafter-from-Nháp . Verify: `dotnet build` clean 0 err 2 warn pre-existing DocxRenderer. `dotnet test SolutionErp.slnx` 104 PASS (58 Domain + 46 Infra, 3 deleted + 3 added cancel out, baseline preserved). 3 Approver F2 tests verified individually PASS. Diff +175/-92 LOC trên 1 file. Token ~14k.
- **2026-05-14 (S23 t1, K5 Chunk D PASS):** Cleanup zombie F2 endpoint + UsersPage column + DTO field + stale narrative comments (Reviewer Major #1 + Major #2 + Minor #3 + Minor #4). Pattern post-refactor full cleanup atomic 1 commit. BE 7 file (UsersController.cs DELETE PATCH /allow-skip-final endpoint + SetAllowDrafterSkipToFinalBody record; UserFeatures.cs DELETE UserDto field + SetUserAllowDrafterSkipToFinalCommand + Handler + sentinel-false mappings cleanup; ApprovalWorkflow.cs REWRITE stale narrative line 78-80 Mig 31 semantic + docstring line 108; PurchaseEvaluationFeatures.cs REWRITE Command DTO comment line 401; ApprovalWorkflowConfiguration.cs APPEND Mig 31 narrative line 22-24 + clean storage move comment line 87; ApprovalWorkflowV2AdminFeatures.cs clean DTO comment line 58; IPurchaseEvaluationWorkflowService.cs + PurchaseEvaluationDtos.cs clean stale "storage Users.AllowDrafterSkipToFinal" references) + FE Admin 2 file (UsersPage.tsx DELETE "Skip cuối" column TableHeader/TableCell + FastForward import + allowSkipMut mutation hook + FastForward toggle button; types/users.ts DELETE allowDrafterSkipToFinal field). fe-user KHÔNG đụng (no UsersPage admin-only + K6 sẽ handle Workspace Drafter checkbox), FE Designer page KHÔNG đụng (K3 done 2 stale comment line 75 + 504 leftover deferred K6). Grep `AllowDrafterSkipToFinal` + `allow-skip-final` + `allowDrafterSkipToFinal` + `Skip cuối` + `FastForward` ZERO results across src/Backend (excl migrations) + fe-admin/src. Build BE production projects clean (0 err, 2 pre-existing DocxRenderer warn). Build fe-admin clean (0 TS err, 0 new warn). Diff +42/-94 LOC trên 9 file. Token ~12k. K6 Workspace Drafter checkbox cleanup next. - **2026-05-14 (S23 t1, K5 Chunk D PASS):** Cleanup zombie F2 endpoint + UsersPage column + DTO field + stale narrative comments (Reviewer Major #1 + Major #2 + Minor #3 + Minor #4). Pattern post-refactor full cleanup atomic 1 commit. BE 7 file (UsersController.cs DELETE PATCH /allow-skip-final endpoint + SetAllowDrafterSkipToFinalBody record; UserFeatures.cs DELETE UserDto field + SetUserAllowDrafterSkipToFinalCommand + Handler + sentinel-false mappings cleanup; ApprovalWorkflow.cs REWRITE stale narrative line 78-80 Mig 31 semantic + docstring line 108; PurchaseEvaluationFeatures.cs REWRITE Command DTO comment line 401; ApprovalWorkflowConfiguration.cs APPEND Mig 31 narrative line 22-24 + clean storage move comment line 87; ApprovalWorkflowV2AdminFeatures.cs clean DTO comment line 58; IPurchaseEvaluationWorkflowService.cs + PurchaseEvaluationDtos.cs clean stale "storage Users.AllowDrafterSkipToFinal" references) + FE Admin 2 file (UsersPage.tsx DELETE "Skip cuối" column TableHeader/TableCell + FastForward import + allowSkipMut mutation hook + FastForward toggle button; types/users.ts DELETE allowDrafterSkipToFinal field). fe-user KHÔNG đụng (no UsersPage admin-only + K6 sẽ handle Workspace Drafter checkbox), FE Designer page KHÔNG đụng (K3 done 2 stale comment line 75 + 504 leftover deferred K6). Grep `AllowDrafterSkipToFinal` + `allow-skip-final` + `allowDrafterSkipToFinal` + `Skip cuối` + `FastForward` ZERO results across src/Backend (excl migrations) + fe-admin/src. Build BE production projects clean (0 err, 2 pre-existing DocxRenderer warn). Build fe-admin clean (0 TS err, 0 new warn). Diff +42/-94 LOC trên 9 file. Token ~12k. K6 Workspace Drafter checkbox cleanup next.
- **2026-05-14 (S23 t1, K3 Chunk C PASS):** FE Admin Designer 7th checkbox AllowApproverSkipToFinal + banner rewrite. Pattern Mig 29/30 admin opt-in per-slot mirror **reinforced 3×** cumulative (Mig 29 F1+F3 5 checkbox + Mig 30 F4 1 checkbox + Mig 31 F2-refactor 1 checkbox = 7 checkbox total per slot). Cookie-cutter 1 file fe-admin only (`ApprovalWorkflowsV2Page.tsx`, fe-user no Designer per Investigator K0 S1). 7 sub-items atomic: (1) LevelDto type +`allowApproverSkipToFinal: boolean`, (2) EditLevelEntry type +same, (3) `makeDefaultLevelEntry` default false, (4) `copyFromDefinition` propagate `?? false`, (5) inline checkbox row position **cuối list** sau F4 AllowApproverEditBudget logical grouping (Edit Section 2 Edit Budget Skip to Final), (6) banner rewrite line ~623 từ "F2 cấu hình User Management" (Plan D S22 stale) "Cấu hình quyền duyệt riêng cho từng NV trong slot Approver bên dưới (Trả lại / Edit Section 2 / Edit Budget / Duyệt thẳng Cấp cuối)", (7) POST/PATCH mutation body `levels.map` +allowApproverSkipToFinal. Verify: `npm run build` fe-admin PASS clean 0 TS error, 0 new warning. Bundle 1395.74 KB (unchanged trivial vs baseline). Diff +26/-7 LOC. Token ~6k. K5 next chunk cleanup zombie endpoint + UsersPage column. - **2026-05-14 (S23 t1, K3 Chunk C PASS):** FE Admin Designer 7th checkbox AllowApproverSkipToFinal + banner rewrite. Pattern Mig 29/30 admin opt-in per-slot mirror **reinforced 3×** cumulative (Mig 29 F1+F3 5 checkbox + Mig 30 F4 1 checkbox + Mig 31 F2-refactor 1 checkbox = 7 checkbox total per slot). Cookie-cutter 1 file fe-admin only (`ApprovalWorkflowsV2Page.tsx`, fe-user no Designer per Investigator K0 S1). 7 sub-items atomic: (1) LevelDto type +`allowApproverSkipToFinal: boolean`, (2) EditLevelEntry type +same, (3) `makeDefaultLevelEntry` default false, (4) `copyFromDefinition` propagate `?? false`, (5) inline checkbox row position **cuối list** sau F4 AllowApproverEditBudget logical grouping (Edit Section 2 Edit Budget Skip to Final), (6) banner rewrite line ~623 từ "F2 cấu hình User Management" (Plan D S22 stale) "Cấu hình quyền duyệt riêng cho từng NV trong slot Approver bên dưới (Trả lại / Edit Section 2 / Edit Budget / Duyệt thẳng Cấp cuối)", (7) POST/PATCH mutation body `levels.map` +allowApproverSkipToFinal. Verify: `npm run build` fe-admin PASS clean 0 TS error, 0 new warning. Bundle 1395.74 KB (unchanged trivial vs baseline). Diff +26/-7 LOC. Token ~6k. K5 next chunk cleanup zombie endpoint + UsersPage column.

View File

@ -128,6 +128,8 @@ State machine 5 trạng thái phiếu PE: Nháp / Đã gửi duyệt / **Trả l
## 📅 Recent activity (last 10 FIFO) ## 📅 Recent activity (last 10 FIFO)
- **2026-05-15 (S23 t2 spawn M0 — Plan M F1+F2+F3+F4 ChoDuyet semantic audit):** Bro UAT post-Plan L deploy 2026-05-15 ~02:00: "Hiện logic cũ là khi trả lại 1 cấp hoặc chỉ định hoặc edit là trạng thái draft -> cái này thay đổi lại nhé, các tính năng duyệt thẳng, trả lại 1 cấp hoặc người chỉ định hoặc cho edit thì cho xử lý đc ở trạng thái đang gửi duyệt luôn." Audit 4 BE file + 4 FE file × 2 app cho F1+F2+F3+F4 phase semantic ChoDuyet preservation. **Verdict: CODE ĐÃ ĐÚNG semantic mới — đây là DISCONNECT bro mental model vs code reality post-Mig 28/29/30/31.** Evidence per feature: **F1.OneLevel** `PurchaseEvaluationWorkflowService.cs:285-312` SWITCH branch lùi 1 Cấp cùng Step (`curLevel-1`) ELSE lùi Step trước Cấp cuối; **Phase KHÔNG đổi** (giữ ChoDuyet, line 364 reset SLA only); fallback Drafter `:303-310` CHỈ KHI đang Bước 1 Cấp 1 no further back (clear pointer + Phase=TraLai). **F1.Assignee** `:335-360` foreach Steps find ApproverUserId match → set pointer; Phase giữ ChoDuyet; ConflictException nếu không tìm thấy NV trong workflow. **F1.OneStep** `:314-333` lùi prev Step Cấp max; Phase giữ ChoDuyet; fallback Drafter nếu đang Bước 1. **F1.Drafter** `:268-275` SET `Phase=TraLai=98` + clear cả 2 pointer + SLA null — đây là CASE DUY NHẤT về "draft". **F2 skipToFinal** `:483-524` Plan K L1 ĐÃ FIX advance pointer tới Bước cuối Cấp cuối (max), **Phase giữ ChoDuyet** (NV cuối duyệt thật để DaDuyet); guard line 485 ConflictException non-admin + flag off; opinion UPSERT trước line 441-468 ensure actor's signature lưu trước. **F3 EnsureEditableForDetailsAsync** `PurchaseEvaluationDetailFeatures.cs:42-99` 2 trường hợp accepted: Drafter scope (DangSoanThao/TraLai return pe sớm line 49-51) **OR Approver scope ChoDuyet line 54-94** (V2 schema + actor match level.ApproverUserId + level.AllowApproverEditDetails flag). **F4 AdjustPurchaseEvaluationBudgetCommandHandler** `PurchaseEvaluationFeatures.cs:272-329` Drafter scope `:283-290` (DangSoanThao/TraLai + isDrafter) ELSE Approver scope ChoDuyet `:291-323` (V2 + pointer init + actor match + level.AllowApproverEditBudget flag) — Phase ChoDuyet đã handle full. **Validation Allow* flag location** `PurchaseEvaluationWorkflowService.cs:252-265` ApplyReturnModeAsync gate per slot per mode: throw ConflictException nếu disabled (Admin bypass line 252). **FE PeWorkflowPanel.tsx**: TraLai dialog `:331-422` 4 radio button (OneLevel/OneStep/Assignee/Drafter) gated bằng `levelOptions?.allowReturnXxx` flag per current Approver Cấp (line 343-396); useEffect `:60-68` S23 t2 fix default first available mode KHÔNG Drafter khi admin tick F1 modes. Skip Final checkbox `:425-442` chỉ visible khi `levelOptions?.allowApproverSkipToFinal` + Approve forward direction (line 425). **FE PeDetailTabs.tsx F3+F4 wire**: `itemsReadOnly = readOnly && !approverEditMode` line 118 bypass readOnly khi F3 enabled (Mig 28 pattern); `canAdjust = isAdmin || (!readOnly && isDrafter && isDrafterPhase) || isApproverChoDuyet` line 977 bypass readOnly khi F4 enabled (L2 fix). Mirror fe-admin/fe-user line 109-115 + 967-979 ĐỒNG BỘ rule §3.9. **Surprise**: "Trả lại" trong UI memory docs đôi khi gọi "draft" colloquial — bro confuse 2 khái niệm `Phase=TraLai=98` (Drafter sửa rồi gửi LẠI từ Step 0) vs `Phase=DangSoanThao=1` (chưa từng gửi duyệt). KHÔNG code path nào trong F1 OneLevel/OneStep/Assignee/F2/F3/F4 set targetPhase=DangSoanThao mà không nên. **Recommendation: LOW effort** (0-20 LOC): chỉ cần communication clarification em main confirm với bro 4 mode F1 + F2 + F3 + F4 đã giữ Phase=ChoDuyet trừ F1.Drafter; mở DB SELECT phiếu UAT confirm `Phase` column number sau click test; OPTIONAL touch up FE label nếu user thấy "Đã gửi duyệt" nhầm "Bản nháp". KHÔNG cần Service refactor hay handler change. Nếu bro thấy phiếu cụ thể về Phase=1 SAU click F1.OneLevel hoặc F2 → spawn lại Investigator audit DB state + log Changelog cho phiếu đó cụ thể (data debug, not code bug).
- **2026-05-15 (S23 t2 spawn L2 — F3+F4 edit menu Duyệt audit):** Bug UAT prod 409a967: admin tick F3+F4 cho slot Approver, login actor user, vào menu "Duyệt" (`?pendingMe=1` cùng `PurchaseEvaluationsListPage` 3-panel), click PE Phase=ChoDuyet → Section 2 Hạng mục/NCC/Báo giá + Section 5 Điều chỉnh ngân sách vẫn read-only. **Root cause F3 = OK / F4 = BROKEN at readOnly short-circuit:** F3 wire ĐÚNG (`PeDetailTabs.tsx:118 itemsReadOnly = readOnly && !approverEditMode` override readOnly per Mig 28 S21 t4 → ItemsTab read prop OK). F4 wire SAI (`PeDetailTabs.tsx:245` passes `readOnly={readOnly}` (=true từ menu Duyệt) xuống BudgetAdjustSection, line 973 compute `canAdjust = !readOnly && (isAdmin || (isDrafter && isDrafterPhase) || isApproverChoDuyet)``!readOnly` short-circuits to false BEFORE F4 isApproverChoDuyet evaluate. Edit pencil button hidden line 1030 `{canAdjust && <Button>Điều chỉnh</Button>}`. Asymmetric vs F3 pattern Section 2 (PeDetailTabs.tsx:113-118). **Evidence:** FE F3 OK `fe-user/src/components/pe/PeDetailTabs.tsx:113-118` + `:224 ItemsTab ev={evaluation} readOnly={itemsReadOnly}`; FE F4 BUG `:957-973 BudgetAdjustSection !readOnly gate` + `:245 readOnly={readOnly}`; menu Duyệt route `fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx:256-261 PeDetailTabs readOnly={true}` (cùng route Danh sách); BE GetPe `PurchaseEvaluationFeatures.cs:735-770 currentLevelOptions populate 7 fields` OK; BE budget-adjust handler `:281-329` Phase ChoDuyet F4 branch + actor match + flag check ĐẦY ĐỦ + return ConflictException nếu Allow=false; BE PurchaseEvaluationDraftGuard.EnsureEditableForDetailsAsync ở `PurchaseEvaluationDetailFeatures.cs:42` (8 callsites Detail+Supplier handlers). **Recommendation:** Fix `BudgetAdjustSection` line 973 mirror approverEditMode pattern Section 2: thay `canAdjust = !readOnly && (isAdmin || ...)` thành `canAdjust = isAdmin || (!readOnly && isDrafter && isDrafterPhase) || isApproverChoDuyet` — Approver bypass readOnly khi F4 conditions met. **LOC ~3-5 LOC 1 file.** Surprise: Inbox `/inbox` route (InboxPage.tsx) navigate sang `/purchase-evaluations/:id` (mobile DetailPage route, default readOnly=false) — chỉ Danh sách `?pendingMe=1` desktop 3-panel mới hard readOnly=true.
- **2026-05-14 (S23 t1 spawn K0 — Plan K F2 refactor pre-flight):** Audit F2 state cho Plan K Mig 31 (move `Users.AllowDrafterSkipToFinal``ApprovalWorkflowLevels.AllowApproverSkipToFinal` + change semantic Drafter Nháp → Approver ChoDuyet skip thẳng Cấp cuối). **Confirmed state Mig 30:** Migrations path = `Persistence/Migrations/` (not direct `Migrations`); 30 mig latest = `20260513160703_AddAllowApproverEditBudgetToLevels`; `User.cs:38` AllowDrafterSkipToFinal prop; `ApprovalWorkflow.cs:86-105` 6 Allow* props (4 ReturnMode + EditDetails + EditBudget) per Level slot; F2 Drafter branch ở `PurchaseEvaluationWorkflowService.cs:119-161` trong SUBMIT branch (line 125 `if (skipToFinal && evaluation.ApprovalWorkflowId is Guid skipAwId)` check user.AllowDrafterSkipToFinal); APPROVE STEP branch ở `~line 393-525` (advance pointer). TransitionAsync signature: `skipToFinal` là param thứ 8 (position 47:47), default=false. `TransitionPurchaseEvaluationCommand``PurchaseEvaluationFeatures.cs:393-402` với param `SkipToFinal=false` default. `ApprovalWorkflowOptionsDto``PurchaseEvaluationDtos.cs:86-92` (6 field). `PurchaseEvaluationDetailBundleDto.DrafterAllowSkipToFinal` ở line 217 + `CurrentLevelOptions` ở line 214. UsersController `PATCH /users/{id}/allow-skip-final` ở line 91-98 + `SetAllowDrafterSkipToFinalBody` ở line 105. `SetUserAllowDrafterSkipToFinalCommand``UserFeatures.cs:332`. **FE state:** fe-admin Designer `system/ApprovalWorkflowsV2Page.tsx` slot label "NV #{ei + 1}" ở line 873 (KHÔNG phải "#NV {order}" theo prompt) — inline checkbox panel 5+1=6 checkbox ở line 853-933 (4 ReturnMode + EditDetails + EditBudget). fe-admin `system/UsersPage.tsx` "Skip cuối" column line 306-318 + FastForward button toggle line 365-372 + allowSkipMut hook line 181-186. fe-admin/fe-user PeDetailTabs Drafter Workspace checkbox "Gửi thẳng Cấp cuối (skip trung gian)" ở line 287-297 (admin) / 294-304 (user). **GAP fe-user**: KHÔNG có UsersPage + ApprovalWorkflowsV2Page (admin-only mgmt) → Plan K UI changes localized fe-admin chỉ; fe-user side chỉ touch PeDetailTabs (remove old Drafter checkbox + thêm Approver toggle near Duyệt button). **Drift Dev DB**: Total=2 user (admin + test.drafter), Flagged=0 — NOT match 33-user prod seed. **Prod actual**: Total=33 / Flagged=4 (NOT 2 per S22+2 spec). 4 user flagged sẽ lose value when DROP column — acceptable per new semantic (Drafter pre-submit moot). - **2026-05-14 (S23 t1 spawn K0 — Plan K F2 refactor pre-flight):** Audit F2 state cho Plan K Mig 31 (move `Users.AllowDrafterSkipToFinal``ApprovalWorkflowLevels.AllowApproverSkipToFinal` + change semantic Drafter Nháp → Approver ChoDuyet skip thẳng Cấp cuối). **Confirmed state Mig 30:** Migrations path = `Persistence/Migrations/` (not direct `Migrations`); 30 mig latest = `20260513160703_AddAllowApproverEditBudgetToLevels`; `User.cs:38` AllowDrafterSkipToFinal prop; `ApprovalWorkflow.cs:86-105` 6 Allow* props (4 ReturnMode + EditDetails + EditBudget) per Level slot; F2 Drafter branch ở `PurchaseEvaluationWorkflowService.cs:119-161` trong SUBMIT branch (line 125 `if (skipToFinal && evaluation.ApprovalWorkflowId is Guid skipAwId)` check user.AllowDrafterSkipToFinal); APPROVE STEP branch ở `~line 393-525` (advance pointer). TransitionAsync signature: `skipToFinal` là param thứ 8 (position 47:47), default=false. `TransitionPurchaseEvaluationCommand``PurchaseEvaluationFeatures.cs:393-402` với param `SkipToFinal=false` default. `ApprovalWorkflowOptionsDto``PurchaseEvaluationDtos.cs:86-92` (6 field). `PurchaseEvaluationDetailBundleDto.DrafterAllowSkipToFinal` ở line 217 + `CurrentLevelOptions` ở line 214. UsersController `PATCH /users/{id}/allow-skip-final` ở line 91-98 + `SetAllowDrafterSkipToFinalBody` ở line 105. `SetUserAllowDrafterSkipToFinalCommand``UserFeatures.cs:332`. **FE state:** fe-admin Designer `system/ApprovalWorkflowsV2Page.tsx` slot label "NV #{ei + 1}" ở line 873 (KHÔNG phải "#NV {order}" theo prompt) — inline checkbox panel 5+1=6 checkbox ở line 853-933 (4 ReturnMode + EditDetails + EditBudget). fe-admin `system/UsersPage.tsx` "Skip cuối" column line 306-318 + FastForward button toggle line 365-372 + allowSkipMut hook line 181-186. fe-admin/fe-user PeDetailTabs Drafter Workspace checkbox "Gửi thẳng Cấp cuối (skip trung gian)" ở line 287-297 (admin) / 294-304 (user). **GAP fe-user**: KHÔNG có UsersPage + ApprovalWorkflowsV2Page (admin-only mgmt) → Plan K UI changes localized fe-admin chỉ; fe-user side chỉ touch PeDetailTabs (remove old Drafter checkbox + thêm Approver toggle near Duyệt button). **Drift Dev DB**: Total=2 user (admin + test.drafter), Flagged=0 — NOT match 33-user prod seed. **Prod actual**: Total=33 / Flagged=4 (NOT 2 per S22+2 spec). 4 user flagged sẽ lose value when DROP column — acceptable per new semantic (Drafter pre-submit moot).
- **2026-05-13 (S22, no spawn — em main solo throughout):** S22 18:00→~21:00 em main solo. Cumulative state: 30 mig (+1 Mig 30 `AddAllowApproverEditBudgetToLevels` F4 per-Level slot), 104 test PASS (+20: 5 reg #44 Authorize policy + 7 ReturnMode + 7 Guard + 1 V2 actor scope reject), ~146 endpoints (+3: PATCH /users/{id}/allow-skip-final + PATCH /pe/{id}/budget-adjust + GET /pe/{id}/attachments/{attId}/view), 46 gotcha unchanged, 19 memory unchanged (recommend +1 entry — see below). Prod active users 13→33 (+20 role-based: act.nv/pp/tp, bod.1/2, equ/fin/hra/pm/qs.nv/pp/tp). **Discoveries S22:** (1) **Per-NV admin opt-in flag pattern reinforced 2×** — Mig 30 F4 cùng pattern Mig 29 F1+F3 (S21 t5). Bro corrected em main lần đầu: "phải tick checkbox như Section 2", default = admin opt-in per slot, KHÔNG = mở rộng default. Cross-ref memory `feedback_per_nv_permission_scope.md` proven 2×. (2) **Plan F drop V1 ABORTED** — pre-flight sqlcmd reveal Contract entity HOÀN TOÀN V1 chưa wire V2 (chưa có ApprovalWorkflowId column) + 4 PE V1-only + 19 PE V1+V2 mix. Lesson: drop migration cần verify entity scope toàn bộ (Contract liên đới — không chỉ PE). (3) **Identity password policy ≥12 chars** — seed 20 user FAIL 400 với "User@123456" (11 chars), `TestUser@2026` (13 chars) pass. (4) **Identity rename atomic 4 fields** confirm gotcha #38: Email + NormalizedEmail + UserName + NormalizedUserName + FullName; sqlcmd cần `SET QUOTED_IDENTIFIER ON` cho filtered unique index. (5) **API login response field name `accessToken` + `refreshToken` + `user`** — KHÔNG có field `token` (correct prior Bash example trong spec dùng `.token` sẽ fail). (6) **PS 5.1 ASCII-only script discipline** reinforced gotcha #30: `seed-test-users-prod.ps1` viết Vietnamese names without diacritics tránh parser error. Recommend bro add 1 memory entry "Admin opt-in flag pattern proven 2×" cumulative Mig 29 + Mig 30. - **2026-05-13 (S22, no spawn — em main solo throughout):** S22 18:00→~21:00 em main solo. Cumulative state: 30 mig (+1 Mig 30 `AddAllowApproverEditBudgetToLevels` F4 per-Level slot), 104 test PASS (+20: 5 reg #44 Authorize policy + 7 ReturnMode + 7 Guard + 1 V2 actor scope reject), ~146 endpoints (+3: PATCH /users/{id}/allow-skip-final + PATCH /pe/{id}/budget-adjust + GET /pe/{id}/attachments/{attId}/view), 46 gotcha unchanged, 19 memory unchanged (recommend +1 entry — see below). Prod active users 13→33 (+20 role-based: act.nv/pp/tp, bod.1/2, equ/fin/hra/pm/qs.nv/pp/tp). **Discoveries S22:** (1) **Per-NV admin opt-in flag pattern reinforced 2×** — Mig 30 F4 cùng pattern Mig 29 F1+F3 (S21 t5). Bro corrected em main lần đầu: "phải tick checkbox như Section 2", default = admin opt-in per slot, KHÔNG = mở rộng default. Cross-ref memory `feedback_per_nv_permission_scope.md` proven 2×. (2) **Plan F drop V1 ABORTED** — pre-flight sqlcmd reveal Contract entity HOÀN TOÀN V1 chưa wire V2 (chưa có ApprovalWorkflowId column) + 4 PE V1-only + 19 PE V1+V2 mix. Lesson: drop migration cần verify entity scope toàn bộ (Contract liên đới — không chỉ PE). (3) **Identity password policy ≥12 chars** — seed 20 user FAIL 400 với "User@123456" (11 chars), `TestUser@2026` (13 chars) pass. (4) **Identity rename atomic 4 fields** confirm gotcha #38: Email + NormalizedEmail + UserName + NormalizedUserName + FullName; sqlcmd cần `SET QUOTED_IDENTIFIER ON` cho filtered unique index. (5) **API login response field name `accessToken` + `refreshToken` + `user`** — KHÔNG có field `token` (correct prior Bash example trong spec dùng `.token` sẽ fail). (6) **PS 5.1 ASCII-only script discipline** reinforced gotcha #30: `seed-test-users-prod.ps1` viết Vietnamese names without diacritics tránh parser error. Recommend bro add 1 memory entry "Admin opt-in flag pattern proven 2×" cumulative Mig 29 + Mig 30.
- **2026-05-13 (S21 t3-t5, no spawn):** Em main solo 3 turns (bug fix gotcha #45 + F1+F2+F3 workflow-level Mig 28 + refactor per-NV Mig 29). Implementer REFUSE per cross-stack reasoning chain rule. Investigator KHÔNG spawn — em main đã có context cumulative S20 t12 setup + active dev throughout. No findings to flush. Cumulative state update: 84 test, 29 mig, 45 gotcha, 19 memory entries (+2 S21 t5 pending), 6 skills unchanged. Pattern reusable saved cho future spawn: per-NV permission scope split + EF migration ADD→BACKFILL→DROP reorder. - **2026-05-13 (S21 t3-t5, no spawn):** Em main solo 3 turns (bug fix gotcha #45 + F1+F2+F3 workflow-level Mig 28 + refactor per-NV Mig 29). Implementer REFUSE per cross-stack reasoning chain rule. Investigator KHÔNG spawn — em main đã có context cumulative S20 t12 setup + active dev throughout. No findings to flush. Cumulative state update: 84 test, 29 mig, 45 gotcha, 19 memory entries (+2 S21 t5 pending), 6 skills unchanged. Pattern reusable saved cho future spawn: per-NV permission scope split + EF migration ADD→BACKFILL→DROP reorder.

View File

@ -144,6 +144,7 @@ Flag commit nếu thấy `<PackageReference Include="MediatR" Version="14...` ho
## 📅 Recent activity (last 10 FIFO) ## 📅 Recent activity (last 10 FIFO)
- **2026-05-15 (S23 t3 Plan M cumulative review, spawn):** Pre-push adversarial verify 3 commits local `c2afef2..508b17a..4dd6f9c` Plan M F1 edge case Bước 1 + Phase=TraLai display rename. Diff cumulative: 26 LOC BE (Service.cs:287-333) + 116 LOC test (1 file, +2 test Fact extend SeedWorkflowAsync helper +2 params optional) + 8 LOC FE (4 file × 2 LOC mirror admin+user). **Verdict: PASS** — wire BE M1 verified hot path 287-333 OneLevel+OneStep edge case replace `Phase=TraLai+clear pointer+return` with `Set pointer (0,1) + summary "không lùi được" → fallthrough SLA reset line 364 → Phase=ChoDuyet preserved from pre-call state (entry guard 75-81 require ChoDuyet để Reject decision)`. ApplyReturnModeAsync caller line 94-100 truyền `evaluation.Phase` (now ChoDuyet) vào LogTransitionAsync → Summary="Chuyển phase ChoDuyet → ChoDuyet" + ContextNote contain "không lùi được" (matches test assert ContextNote.Contain). Drafter mode line 268-275 GIỮ Phase=TraLai semantic (unchanged). Assignee line 335-360 throw nếu không match unchanged. F2 ApproveV2Async + F3 EnsureEditableForDetailsAsync + F4 AdjustBudgetCommand handler — UNCHANGED. Schema integrity: 0 migration mới, Phase enum TraLai=98 còn dùng cho Drafter mode + display label rename only. Security: Admin bypass logic line 252-265 + Reject guard line 81 EnsureCanRejectV2Async + non-Approver block — preserved. Code quality: dotnet build PASS 0 err 2 warn (DocxRenderer pre-existing), dotnet test 106/106 PASS (58 Domain + 48 Infra, +2 từ 104 baseline), npm build admin+user PASS 0 TS err (chỉ chunk size warn pre-existing), no `--no-verify`. Test coverage: 2 Fact mới `ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` + `ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` cover OneLevel Step1Lv1 + OneStep Step1Lv2→reset. Assert đủ Phase=ChoDuyet + pointer (0,1) + SLA NotNull + ContextNote "không lùi được". Helper extend +2 params backward compat OK (default false). K7 cascade NO regression — 3 `ApproveV2_SkipToFinal_*` xanh (M1 chỉ đụng F1 ApplyReturnModeAsync, không động F2 ApproveV2Async). Mirror 2 FE app (§3.9): purchaseEvaluation.ts admin+user identical (98:'Cần chỉnh sửa lại' + PeDisplayStatus.TraLai:'Cần chỉnh sửa lại'), PeWorkflowPanel.tsx admin+user identical (2 inline literal "Phase → ..." + "Phiếu sẽ về ..." rename). Anti-fiddle: scope drift 0% — 8 LOC FE đúng spec rename Phase=TraLai display only. Anti-pattern guard: **(1) Stale narrative comment Service.cs:288-289 + 327-328** UPDATED rõ "Plan M S23 t3 — KHÔNG fallback Drafter, phiếu giữ đang duyệt" — không zombie. **(2) Audit log consistency** test+code match "không lùi được" exact. **(3) Backward compat** phiếu UAT prod đang Phase=TraLai do logic cũ — Drafter resume từ TraLai vẫn work line 105-132 (entry point `fromPhase==TraLai → targetPhase==ChoDuyet`). **Minor issues (non-block, recommend fix sau):** **(4) Inconsistent UX literal "Trả lại"** còn ở 8 user-facing locations chưa rename (decision design): PeListPanel.tsx:112 "Bản nháp + Trả lại" filter label, PeWorkflowPanel.tsx:270 button "← Trả lại", 272 tooltip "(Duyệt / Trả lại / Từ chối)", 315 "← Trả lại Drafter sửa", 342 "Chọn cách Trả lại", PeDetailTabs.tsx:152 "chỉ Bản nháp / Trả lại mới sửa", 287 "(trừ khi approver Trả lại)", ApprovalWorkflowsV2Page.tsx:639 "(Trả lại / Edit Section 2 / ...)" — phân biệt **action verb** "Trả lại" (button approver click) vs **phase result label** "Cần chỉnh sửa lại" (Phase=TraLai display in dropdown/badge); spec M3 narrow scope chỉ rename phase result label OK. Em main quyết: giữ action verb hay rename hết. **(5) Inconsistent across module** contracts.ts:29 + budget.ts:20 vẫn `98:'Trả lại'` — KHÔNG trong scope Plan M (PE-only) nhưng inconsistent giữa 3 module nếu Contract/Budget có Phase 98 — bro nếu deploy chung sẽ noticeable. Recommendation: SAFE to push 3 commit Plan M; consider follow-up chunk M4 (optional) rename 5 literal user-facing trong PeListPanel + PeWorkflowPanel + PeDetailTabs để consistent UX semantic mới. Smart Friend guard active.
- **2026-05-14 (S23 t1 Plan K1+K2 cumulative review, spawn):** Pre-K3 adversarial verify Mig 31 schema swap + Service Approver F2 branch. Diff scope: 11 BE files +4093/-83 LOC (Mig Designer 3938 lines dominate). 3 commits chuỗi `eb106f2..56868bf..db66253..364aef6` — pre-A slot label refactor + K1 Domain/Mig/Snapshot/sentinel patches + K2 DTO+Service Approver branch. **Verdict: PASS với 2 Major + 2 Minor issues** — K3 OK proceed nhưng K5 endpoint cleanup nên ưu tiên trước K7 test fix. Wire claim verify: Approver F2 branch placed ĐÚNG vị trí (line 477 AFTER UPSERT opinion line 441-468 + BEFORE advance pointer line 502) — opinion sẽ ghi trước skip terminal, audit log đầy đủ context Bước/Cấp. ApproveV1LegacyAsync skipToFinal guard tại CALLER (line 147-149 trong TransitionAsync branch) thay vì callee — pattern OK, V1 method giữ nguyên signature legacy. Schema sqlcmd verify Dev: 7 Allow* columns ApprovalWorkflowLevels (AllowApproverEditBudget/EditDetails/SkipToFinal/ReturnOneLevel/OneStep/ToAssignee/ToDrafter) + Users.AllowDrafterSkipToFinal dropped (count=0). Snapshot regen clean. EF config HasDefaultValue(false) wire. Mig 31 Up() manual reorder ADD→DROP correct per memory `feedback_ef_migration_backfill_reorder`. **Major issues caught** (Smart Friend guard active — KHÔNG để pass): (1) **Orphan UsersController endpoint zombie** — K1 sentinel commented "K2 sẽ refactor DTO + drop field" nhưng K2 chỉ refactor PE side, KHÔNG động UsersController.SetAllowDrafterSkipToFinal + UserFeatures.SetUserAllowDrafterSkipToFinalCommand + UserDto field. Endpoint PATCH `/api/users/{id}/allow-skip-final` vẫn live nhưng silent NoOp (Task.CompletedTask), admin UI tick → BE swallow → confusion UX. Cần K5 endpoint cleanup chunk (per spec). (2) **Stale Mig 28 comment ApprovalWorkflow.cs:78** — comment cũ "F2 (Drafter skip) đã move sang Users.AllowDrafterSkipToFinal" còn nguyên dù line 107-113 prop AllowApproverSkipToFinal mới. Confuse future dev. **Minor issues:** (3) Comment TransitionPurchaseEvaluationCommand DTO PurchaseEvaluationFeatures.cs:401 "F2 — Drafter skip thẳng Cấp cuối khi trình duyệt" still says Drafter (semantic outdated). (4) ApprovalWorkflowConfiguration.cs:22 stale Mig 28/29 narrative comment chưa note Mig 31. Anti-fiddle audit PASS: K1 18 LOC UserFeatures.cs sentinel patches valid compile-break workaround, K2 4 files within original spec. Anti-pattern reinforced 3×: admin opt-in per slot per-NV (Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2 — em main lần đầu sai Mig 30 default scope expansion → bro corrected). Production build PASS 0 err 2 warn (DocxRenderer unrelated). Test references K7-pending line 253. No `--no-verify` bypass. Pattern caught: "Transient sentinel pattern" — đặt sentinel + comment commit chunk khác cleanup nhưng chunk đó scope SHIFT → zombie state. Recommend explicit K5 cleanup chunk trước K7 test fix. Cumulative state: 31 mig (+1 Mig 31), 47 gotcha unchanged. Smart Friend guard active. - **2026-05-14 (S23 t1 Plan K1+K2 cumulative review, spawn):** Pre-K3 adversarial verify Mig 31 schema swap + Service Approver F2 branch. Diff scope: 11 BE files +4093/-83 LOC (Mig Designer 3938 lines dominate). 3 commits chuỗi `eb106f2..56868bf..db66253..364aef6` — pre-A slot label refactor + K1 Domain/Mig/Snapshot/sentinel patches + K2 DTO+Service Approver branch. **Verdict: PASS với 2 Major + 2 Minor issues** — K3 OK proceed nhưng K5 endpoint cleanup nên ưu tiên trước K7 test fix. Wire claim verify: Approver F2 branch placed ĐÚNG vị trí (line 477 AFTER UPSERT opinion line 441-468 + BEFORE advance pointer line 502) — opinion sẽ ghi trước skip terminal, audit log đầy đủ context Bước/Cấp. ApproveV1LegacyAsync skipToFinal guard tại CALLER (line 147-149 trong TransitionAsync branch) thay vì callee — pattern OK, V1 method giữ nguyên signature legacy. Schema sqlcmd verify Dev: 7 Allow* columns ApprovalWorkflowLevels (AllowApproverEditBudget/EditDetails/SkipToFinal/ReturnOneLevel/OneStep/ToAssignee/ToDrafter) + Users.AllowDrafterSkipToFinal dropped (count=0). Snapshot regen clean. EF config HasDefaultValue(false) wire. Mig 31 Up() manual reorder ADD→DROP correct per memory `feedback_ef_migration_backfill_reorder`. **Major issues caught** (Smart Friend guard active — KHÔNG để pass): (1) **Orphan UsersController endpoint zombie** — K1 sentinel commented "K2 sẽ refactor DTO + drop field" nhưng K2 chỉ refactor PE side, KHÔNG động UsersController.SetAllowDrafterSkipToFinal + UserFeatures.SetUserAllowDrafterSkipToFinalCommand + UserDto field. Endpoint PATCH `/api/users/{id}/allow-skip-final` vẫn live nhưng silent NoOp (Task.CompletedTask), admin UI tick → BE swallow → confusion UX. Cần K5 endpoint cleanup chunk (per spec). (2) **Stale Mig 28 comment ApprovalWorkflow.cs:78** — comment cũ "F2 (Drafter skip) đã move sang Users.AllowDrafterSkipToFinal" còn nguyên dù line 107-113 prop AllowApproverSkipToFinal mới. Confuse future dev. **Minor issues:** (3) Comment TransitionPurchaseEvaluationCommand DTO PurchaseEvaluationFeatures.cs:401 "F2 — Drafter skip thẳng Cấp cuối khi trình duyệt" still says Drafter (semantic outdated). (4) ApprovalWorkflowConfiguration.cs:22 stale Mig 28/29 narrative comment chưa note Mig 31. Anti-fiddle audit PASS: K1 18 LOC UserFeatures.cs sentinel patches valid compile-break workaround, K2 4 files within original spec. Anti-pattern reinforced 3×: admin opt-in per slot per-NV (Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2 — em main lần đầu sai Mig 30 default scope expansion → bro corrected). Production build PASS 0 err 2 warn (DocxRenderer unrelated). Test references K7-pending line 253. No `--no-verify` bypass. Pattern caught: "Transient sentinel pattern" — đặt sentinel + comment commit chunk khác cleanup nhưng chunk đó scope SHIFT → zombie state. Recommend explicit K5 cleanup chunk trước K7 test fix. Cumulative state: 31 mig (+1 Mig 31), 47 gotcha unchanged. Smart Friend guard active.
- **2026-05-13 (S22 18:00→21:00, no spawn):** Em main solo self-review S22 — Reviewer KHÔNG spawn per UAT mode `feedback_uat_skip_verify`. Em main verify build clean + test pass + npm build × 2 app mỗi chunk (11 commits cumulative). Key validations: (1) Plan E phân quyền strict V2 — actor.UserId scope in List + Detail, Inbox đã strict từ S17, loose UAT clause `|| ApprovalWorkflowId != null` removed. (2) S22+1 V2 actor scope guard BE helper `EnsureCanRejectV2Async` chặn request forge non-approver PATCH /transitions decision=Reject (mirror FE button disable) — defense-in-depth FE+BE pattern. (3) S22+4 AdjustBudgetCommand handler 3-tier scope (Drafter Nháp/Trả lại + Approver ChoDuyet + Admin) — S22+5 refactor ChoDuyet branch dùng `level.AllowApproverEditBudget` flag (admin opt-in per slot) thay default Approver scope (security scope correction per bro feedback). (4) S22+2 Identity password policy enforced ≥12 chars (reject `User@123456` 11 chars outdated). Anti-patterns observed: (a) Default scope expansion mistake S22+4 → S22+5 fix — KHÔNG default expand permission scope without admin tick (per-NV opt-in pattern Mig 29 lesson reinforced); (b) History display field assumption BudgetAdjustSection initial — em assume `PeDetailBundle.changelogs` exists, FAIL TS2339 — pattern verify type fields trước render; (c) PS 5.1 Vietnamese diacritics gotcha #30 reinforced — script `seed-test-users-prod.ps1` first attempt FAIL parser error, ASCII-only discipline. S22+4 attachment view endpoint + S22+4 budget-adjust endpoint chưa qua live curl verify (defer post-deploy — bro UAT direct). S22+5 Mig 30 applied LocalDB Dev + Design via `dotnet ef database update`. New gotcha discovered: #47 paths-ignore agent-memory gap (recommend add to docs/gotchas.md — pending bro decide). Cumulative state: 104 test (+20), 30 mig (+1 Mig 30), 46 gotcha unchanged (gotcha #47 pending), ~146 endpoints (+3), 33 active users. Smart Friend guard still active for future spawn. - **2026-05-13 (S22 18:00→21:00, no spawn):** Em main solo self-review S22 — Reviewer KHÔNG spawn per UAT mode `feedback_uat_skip_verify`. Em main verify build clean + test pass + npm build × 2 app mỗi chunk (11 commits cumulative). Key validations: (1) Plan E phân quyền strict V2 — actor.UserId scope in List + Detail, Inbox đã strict từ S17, loose UAT clause `|| ApprovalWorkflowId != null` removed. (2) S22+1 V2 actor scope guard BE helper `EnsureCanRejectV2Async` chặn request forge non-approver PATCH /transitions decision=Reject (mirror FE button disable) — defense-in-depth FE+BE pattern. (3) S22+4 AdjustBudgetCommand handler 3-tier scope (Drafter Nháp/Trả lại + Approver ChoDuyet + Admin) — S22+5 refactor ChoDuyet branch dùng `level.AllowApproverEditBudget` flag (admin opt-in per slot) thay default Approver scope (security scope correction per bro feedback). (4) S22+2 Identity password policy enforced ≥12 chars (reject `User@123456` 11 chars outdated). Anti-patterns observed: (a) Default scope expansion mistake S22+4 → S22+5 fix — KHÔNG default expand permission scope without admin tick (per-NV opt-in pattern Mig 29 lesson reinforced); (b) History display field assumption BudgetAdjustSection initial — em assume `PeDetailBundle.changelogs` exists, FAIL TS2339 — pattern verify type fields trước render; (c) PS 5.1 Vietnamese diacritics gotcha #30 reinforced — script `seed-test-users-prod.ps1` first attempt FAIL parser error, ASCII-only discipline. S22+4 attachment view endpoint + S22+4 budget-adjust endpoint chưa qua live curl verify (defer post-deploy — bro UAT direct). S22+5 Mig 30 applied LocalDB Dev + Design via `dotnet ef database update`. New gotcha discovered: #47 paths-ignore agent-memory gap (recommend add to docs/gotchas.md — pending bro decide). Cumulative state: 104 test (+20), 30 mig (+1 Mig 30), 46 gotcha unchanged (gotcha #47 pending), ~146 endpoints (+3), 33 active users. Smart Friend guard still active for future spawn.
- **2026-05-13 (S21 t3-t5, no spawn):** Em main solo verify via dotnet build + npm build × 2 app + dotnet test suite mỗi chunk. Reviewer KHÔNG spawn — em main self-review per UAT mode `feedback_uat_skip_verify` (skip dotnet test mỗi chunk, vẫn build verify). Gotcha #45 fix self-test 3 regression test (test-before §7). S21 t3-t5 push cumulative 12 commits — CICD Monitor verify post-deploy thay vai Reviewer (deploy ship + bundle hash + schema verify). Cumulative state: 84 test, 29 mig, 45 gotcha, 19 memory entries. Pattern saved cho future review focus: per-NV permission audit (Level table vs User table flag), EF migration backfill SQL injection between ADD-DROP order. Smart Friend guard still active for future spawn. - **2026-05-13 (S21 t3-t5, no spawn):** Em main solo verify via dotnet build + npm build × 2 app + dotnet test suite mỗi chunk. Reviewer KHÔNG spawn — em main self-review per UAT mode `feedback_uat_skip_verify` (skip dotnet test mỗi chunk, vẫn build verify). Gotcha #45 fix self-test 3 regression test (test-before §7). S21 t3-t5 push cumulative 12 commits — CICD Monitor verify post-deploy thay vai Reviewer (deploy ship + bundle hash + schema verify). Cumulative state: 84 test, 29 mig, 45 gotcha, 19 memory entries. Pattern saved cho future review focus: per-NV permission audit (Level table vs User table flag), EF migration backfill SQL injection between ADD-DROP order. Smart Friend guard still active for future spawn.

View File

@ -1,6 +1,8 @@
# HANDOFF — Brief 5 phút cho session tiếp theo # HANDOFF — Brief 5 phút cho session tiếp theo
**Last updated:** 2026-05-14 (Session 23 turn 1**🎯 Plan K Mig 31 F2 refactor sang per-Approver-slot — DONE 9 commits `56868bf..0062fcb` pushed remote**. Verify result K9 + K11: Mig 31 prod TOP 1 ✅, Levels.AllowApproverSkipToFinal col added ✅, Users.AllowDrafterSkipToFinal col dropped ✅, zombie PATCH /allow-skip-final → 404 ✅, bundle hash rotated 2/2 (admin `CpI5OL8n→CRsX6cFo`, user `d064StNa→X7qb4Zl4`) ✅, 33 active users preserved ✅, AwLevelDto `allowApproverSkipToFinal` field PRESENT (13 keys, default False opt-in) ✅. CICD Monitor K9 catch CRITICAL wire gap: `AwLevelDto` admin DTO miss field — em main K2 + Reviewer K2 cùng miss audit `ApprovalWorkflowV2AdminFeatures.cs`. K10 hotfix ~15 LOC 1 file (AwLevelDto + ToDto + CreateAwLevelInput + entity init) pushed `0062fcb`. Run #195 success → K11 self-verify field present. Pattern lesson: per-NV admin opt-in flag wire **8 surface points** required (NOT 6 — admin overview DTO + Create input là 2 gap em main miss S22+5 và S23 t1). Memory `feedback_per_nv_permission_scope.md` cần add wire checklist gotcha S23 t2+. **Last updated:** 2026-05-15 (Session 23 turn 3**🎯 Plan M: Fix F1.OneLevel/OneStep edge case Bước 1 → giữ ChoDuyet + FE label phase rename**. 3 commits Plan M `c2042ef..4dd6f9c` local (chưa push, chờ Reviewer verdict). Bro UAT post-Plan L deploy: "Hiện logic cũ là khi trả lại 1 cấp hoặc chỉ định hoặc edit là trạng thái draft → thay đổi lại". Investigator audit confirm 4 mode F1.OneLevel/Assignee + F2 + F3 + F4 main path đã giữ ChoDuyet đúng (Mig 28-31 cumulative). Edge case F1.OneLevel ở Bước 1 Cấp 1 + F1.OneStep ở Bước 1 còn fallback Drafter (Phase=TraLai) — gap "draft" bro phát hiện. Plan M fix: reset (0, 1) giữ Phase=ChoDuyet + audit log "không lùi được" + SLA reset 7d (no-op effective). M3 FE label phase Phase=TraLai badge "Trả lại" → "Cần chỉnh sửa lại" (rõ end-user). M2 add 2 edge case tests **106/106 PASS** (+2 từ 104). F1.Drafter mode 4 GIỮ NGUYÊN Phase=TraLai semantic (explicit role mode). Multi-agent ROI evidence: Investigator catch fact code main path đã đúng (avoid em main spam fix sai) + Implementer Case 2+3 cookie-cutter 2 spawns + Reviewer pre-commit pending. Memory user-level update 2 entry. Stats: **31 mig** · 59 tables · **~145 endpoints** · 34 FE pages · **106 test (+2)** · 47 gotcha · 20 memory (2 entry reinforced) · 6 skills · 4 sub-agents. CHƯA push remote.)
**Last updated S23 t1:** 2026-05-14 (Session 23 turn 1 — **🎯 Plan K Mig 31 F2 refactor sang per-Approver-slot — DONE 9 commits `56868bf..0062fcb` pushed remote**. Verify result K9 + K11: Mig 31 prod TOP 1 ✅, Levels.AllowApproverSkipToFinal col added ✅, Users.AllowDrafterSkipToFinal col dropped ✅, zombie PATCH /allow-skip-final → 404 ✅, bundle hash rotated 2/2 (admin `CpI5OL8n→CRsX6cFo`, user `d064StNa→X7qb4Zl4`) ✅, 33 active users preserved ✅, AwLevelDto `allowApproverSkipToFinal` field PRESENT (13 keys, default False opt-in) ✅. CICD Monitor K9 catch CRITICAL wire gap: `AwLevelDto` admin DTO miss field — em main K2 + Reviewer K2 cùng miss audit `ApprovalWorkflowV2AdminFeatures.cs`. K10 hotfix ~15 LOC 1 file (AwLevelDto + ToDto + CreateAwLevelInput + entity init) pushed `0062fcb`. Run #195 success → K11 self-verify field present. Pattern lesson: per-NV admin opt-in flag wire **8 surface points** required (NOT 6 — admin overview DTO + Create input là 2 gap em main miss S22+5 và S23 t1). Memory `feedback_per_nv_permission_scope.md` cần add wire checklist gotcha S23 t2+.
**S23 t1 prev:** 2026-05-14 (Session 23 turn 1 — **🎯 Plan K Mig 31 F2 refactor sang per-Approver-slot — DONE 8 commits Plan K `56868bf..<latest>`**. Bro phát hiện inconsistency S22: F1+F3+F4 đều per-slot ở Designer, F2 lone wolf ở User Management (Plan D S22 wire) + bro chốt đổi semantic F2: Drafter from Nháp → Approver during ChoDuyet skip thẳng Cấp cuối. Plan K 8 chunk: pre-A slot label rename "#NV {order}" → ApproverFullName + K1 Mig 31 schema swap (drop Users + add Levels, NO BACKFILL Option A) + K2 Service ApproveV2Async +skipToFinal 8th param APPROVE STEP branch + DTO 7th Allow* field + K3 Designer 7th checkbox + banner rewrite + K5 zombie endpoint cleanup (PATCH /users/{id}/allow-skip-final + Command/Handler/DTO/UI all backout Plan D S22) + K6 Workspace × 2 app DROP Drafter checkbox + ADD Approver toggle Dialog amber warning + K7 tests 104/104 PASS regression (3 deleted Drafter F2 + 3 added Approver F2 cancel out) + K8 docs cumulative. Multi-agent ROI: 🟦 Investigator K0 pre-flight + 🟨 Implementer 4 spawns (pre-A + K1 + K3 + K5 + K7 Case 2+3) + 🟥 Reviewer K2 pre-commit catch zombie endpoint Major + 👤 Chủ trì K0-bis sqlcmd + K2 cross-stack reasoning + K6 UX flow + K8 docs. 4 prod user lose AllowDrafterSkipToFinal=true value per Option A (admin re-config qua Designer). Reviewer K2 PASS 0 critical, 2 Major + 2 Minor flagged → K5 + K8 resolved. New pattern caught: "Transient sentinel zombie" anti-pattern (K1 sentinel-false patch + chunk scope shift → endpoint NoOp swallow silent). Per-NV admin opt-in flag pattern proven **3× cumulative** (Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2) — pattern ALSO applies cho refactor existing scope, KHÔNG chỉ greenfield. Memory `feedback_per_nv_permission_scope.md` reinforced S23 t1. State final: **31 mig (+1 Mig 31)** · 59 tables · **~145 endpoints (-1 backout)** · 34 FE pages · **104 test PASS unchanged** · 47 gotcha · 20 memory · 6 skills · 4 sub-agents (Investigator 1 spawn + Implementer 4 spawn + Reviewer 1 spawn + CICD pending K9). CHƯA push remote — chờ bro confirm K9 spawn CICD Monitor verify.) **S23 t1 prev:** 2026-05-14 (Session 23 turn 1 — **🎯 Plan K Mig 31 F2 refactor sang per-Approver-slot — DONE 8 commits Plan K `56868bf..<latest>`**. Bro phát hiện inconsistency S22: F1+F3+F4 đều per-slot ở Designer, F2 lone wolf ở User Management (Plan D S22 wire) + bro chốt đổi semantic F2: Drafter from Nháp → Approver during ChoDuyet skip thẳng Cấp cuối. Plan K 8 chunk: pre-A slot label rename "#NV {order}" → ApproverFullName + K1 Mig 31 schema swap (drop Users + add Levels, NO BACKFILL Option A) + K2 Service ApproveV2Async +skipToFinal 8th param APPROVE STEP branch + DTO 7th Allow* field + K3 Designer 7th checkbox + banner rewrite + K5 zombie endpoint cleanup (PATCH /users/{id}/allow-skip-final + Command/Handler/DTO/UI all backout Plan D S22) + K6 Workspace × 2 app DROP Drafter checkbox + ADD Approver toggle Dialog amber warning + K7 tests 104/104 PASS regression (3 deleted Drafter F2 + 3 added Approver F2 cancel out) + K8 docs cumulative. Multi-agent ROI: 🟦 Investigator K0 pre-flight + 🟨 Implementer 4 spawns (pre-A + K1 + K3 + K5 + K7 Case 2+3) + 🟥 Reviewer K2 pre-commit catch zombie endpoint Major + 👤 Chủ trì K0-bis sqlcmd + K2 cross-stack reasoning + K6 UX flow + K8 docs. 4 prod user lose AllowDrafterSkipToFinal=true value per Option A (admin re-config qua Designer). Reviewer K2 PASS 0 critical, 2 Major + 2 Minor flagged → K5 + K8 resolved. New pattern caught: "Transient sentinel zombie" anti-pattern (K1 sentinel-false patch + chunk scope shift → endpoint NoOp swallow silent). Per-NV admin opt-in flag pattern proven **3× cumulative** (Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2) — pattern ALSO applies cho refactor existing scope, KHÔNG chỉ greenfield. Memory `feedback_per_nv_permission_scope.md` reinforced S23 t1. State final: **31 mig (+1 Mig 31)** · 59 tables · **~145 endpoints (-1 backout)** · 34 FE pages · **104 test PASS unchanged** · 47 gotcha · 20 memory · 6 skills · 4 sub-agents (Investigator 1 spawn + Implementer 4 spawn + Reviewer 1 spawn + CICD pending K9). CHƯA push remote — chờ bro confirm K9 spawn CICD Monitor verify.)
**S22 chốt cuối:** 2026-05-13 2300 (Session 22 CHỐT cuối cùng — **bro chốt directive Thứ 9 BẮT BUỘC delegate sub-agent**. 14 commits pushed remote `3d725c4..2b9788d` + (this final). CICD Monitor Run #193 PASS verified all live endpoints + Mig 30 prod + bundle hash rotated. State final: **30 mig · 104 test · 47 gotcha (#47 revised informational) · 19 memory · 6 skills · 4 sub-agents · 33 active users prod**. Retrospective S22: em main solo 6/10 task lẽ ra delegate được — vi phạm directive Thứ 9. **Forward S23+ rule:** BẮT BUỘC spawn sub-agent khi ACCEPT criteria match (Implementer Case 1/2/3/5, Investigator pre-flight, Reviewer pre-commit, CICD Monitor post-deploy). Em main solo CHỈ khi schema/UX/architecture decision + cross-stack tight coupling + bug fix reasoning chain.) **S22 chốt cuối:** 2026-05-13 2300 (Session 22 CHỐT cuối cùng — **bro chốt directive Thứ 9 BẮT BUỘC delegate sub-agent**. 14 commits pushed remote `3d725c4..2b9788d` + (this final). CICD Monitor Run #193 PASS verified all live endpoints + Mig 30 prod + bundle hash rotated. State final: **30 mig · 104 test · 47 gotcha (#47 revised informational) · 19 memory · 6 skills · 4 sub-agents · 33 active users prod**. Retrospective S22: em main solo 6/10 task lẽ ra delegate được — vi phạm directive Thứ 9. **Forward S23+ rule:** BẮT BUỘC spawn sub-agent khi ACCEPT criteria match (Implementer Case 1/2/3/5, Investigator pre-flight, Reviewer pre-commit, CICD Monitor post-deploy). Em main solo CHỈ khi schema/UX/architecture decision + cross-stack tight coupling + bug fix reasoning chain.)

View File

@ -2,7 +2,8 @@
> **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`. > **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`.
**Last updated:** 2026-05-14 (Session 23 turn 1**🎯 Plan K: F2 refactor sang per-Approver-slot Mig 31** + UI consistency. 8 commits Plan K `56868bf..<latest>`: Chunk pre-A slot label rename → K1 Mig 31 schema swap (drop `Users.AllowDrafterSkipToFinal` + add `ApprovalWorkflowLevels.AllowApproverSkipToFinal` no BACKFILL) → K2 Service ApproveV2Async Approver F2 branch + DTO refactor → K3 Designer 7th checkbox + banner rewrite → K5 zombie endpoint cleanup + Reviewer Major #1+#2 + Minor #3+#4 → K6 Workspace × 2 app DROP Drafter + ADD Approver toggle → K7 tests regression 104/104 PASS (3 deleted + 3 added cancel) → K8 docs. Multi-agent execution: 🟦 Investigator K0 pre-flight + 🟨 Implementer K1+K3+K5+K7 (4 spawns Case 2+3) + 🟥 Reviewer K2 pre-commit (PASS 0 critical, 2 Major + 2 Minor) + 👤 Chủ trì K0-bis+K2+K6+K8. Bro decision: F2 semantic ĐỔI Drafter from Nháp → Approver during ChoDuyet skip thẳng Cấp cuối (mirror F3+F4 admin opt-in per slot) + setup ALL ở Workflow Designer (KHÔNG ở User Management). UI slot label "#NV {order}" → ApproverFullName. Stats final S23 t1: **31 mig (+1 Mig 31)** · 59 tables · **~145 endpoints (-1 backout /allow-skip-final)** · 34 FE pages · **104 test pass unchanged** (3 deleted + 3 added) · 47 gotcha unchanged · 20 memory (cumulative reinforce `feedback_per_nv_permission_scope` 3×) · 6 skills · **4 sub-agents active 4 spawns (Implementer Case 2+3) + 1 Reviewer**. 4 prod user lose `AllowDrafterSkipToFinal=true` value per Option A (admin re-config qua Designer). Pattern reinforced: per-NV admin opt-in flag 3× proven (Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2) — pattern ALSO applies cho refactor existing scope, KHÔNG chỉ greenfield. Plan B Contract V2 wire vẫn pending S23 t2+. CHƯA push remote — chờ bro confirm.) **Last updated:** 2026-05-15 (Session 23 turn 3**🎯 Plan M: Fix F1.OneLevel/OneStep edge case Bước 1 → giữ ChoDuyet (KHÔNG fallback Drafter) + FE label phase TraLai rename**. 3 commits Plan M `c2042ef..4dd6f9c` chuỗi M1→M3→M2: M1 (`c2042ef`) BE Service `ApplyReturnModeAsync` 2 edge case block (line 287-333) → reset (0, 1) giữ Phase=ChoDuyet thay vì fallback Phase=TraLai (semantic mới per bro chốt AskUserQuestion: "Cho phép nhưng vẫn giữ ChoDuyet, clear pointer thôi") + M3 (`508b17a`) FE × 2 app rename `PurchaseEvaluationPhaseLabel[98]` + `PeDisplayStatusLabel.TraLai` + 2 inline literal trong PeWorkflowPanel F1 dialog "Trả lại" → "Cần chỉnh sửa lại" (phase status badge, KHÔNG đụng action verb "← Trả lại" + mode picker "Trả về Người soạn thảo") + M2 (`4dd6f9c`) Tests add 2 edge case test `OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` + `OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` + helper `SeedWorkflowAsync` extend 2 param optional. Multi-agent execution: 🟦 Investigator audit pre-flight catch fact code main path đã đúng (F1.OneLevel/Assignee + F2 + F3 + F4 main path giữ ChoDuyet) chỉ edge case Bước 1 fallback sai + 🟨 Implementer 2 spawns Case 2+3 (M2 + M3) + 🟥 Reviewer pre-commit pending + 👤 Chủ trì M1+M4. Test final: **106/106 PASS** (+2 từ 104, 58 Domain + 48 Infra) — K7 Approver F2 NO cascade. F1.Drafter mode 4 GIỮ NGUYÊN Phase=TraLai semantic (explicit role mode). Memory user-level update 2 entry: `feedback_per_nv_permission_scope.md` reinforcement S23 t3 "edge case không lùi được KHÔNG fallback role khác" + `feedback_uat_skip_verify.md` Plan L lesson "Service refactor semantic BẮT BUỘC update test cùng commit". Stats final S23 t3: **31 mig** (no change) · 59 tables · **~145 endpoints** (no change) · 34 FE pages · **106 test pass (+2)** · 47 gotcha · 20 memory (2 entry reinforced) · 6 skills · 4 sub-agents. CHƯA push remote — chờ Reviewer verdict.)
**Last updated S23 t1:** 2026-05-14 (Session 23 turn 1 — **🎯 Plan K: F2 refactor sang per-Approver-slot Mig 31** + UI consistency. 8 commits Plan K `56868bf..<latest>`: Chunk pre-A slot label rename → K1 Mig 31 schema swap (drop `Users.AllowDrafterSkipToFinal` + add `ApprovalWorkflowLevels.AllowApproverSkipToFinal` no BACKFILL) → K2 Service ApproveV2Async Approver F2 branch + DTO refactor → K3 Designer 7th checkbox + banner rewrite → K5 zombie endpoint cleanup + Reviewer Major #1+#2 + Minor #3+#4 → K6 Workspace × 2 app DROP Drafter + ADD Approver toggle → K7 tests regression 104/104 PASS (3 deleted + 3 added cancel) → K8 docs. Multi-agent execution: 🟦 Investigator K0 pre-flight + 🟨 Implementer K1+K3+K5+K7 (4 spawns Case 2+3) + 🟥 Reviewer K2 pre-commit (PASS 0 critical, 2 Major + 2 Minor) + 👤 Chủ trì K0-bis+K2+K6+K8. Bro decision: F2 semantic ĐỔI Drafter from Nháp → Approver during ChoDuyet skip thẳng Cấp cuối (mirror F3+F4 admin opt-in per slot) + setup ALL ở Workflow Designer (KHÔNG ở User Management). UI slot label "#NV {order}" → ApproverFullName. Stats final S23 t1: **31 mig (+1 Mig 31)** · 59 tables · **~145 endpoints (-1 backout /allow-skip-final)** · 34 FE pages · **104 test pass unchanged** (3 deleted + 3 added) · 47 gotcha unchanged · 20 memory (cumulative reinforce `feedback_per_nv_permission_scope` 3×) · 6 skills · **4 sub-agents active 4 spawns (Implementer Case 2+3) + 1 Reviewer**. 4 prod user lose `AllowDrafterSkipToFinal=true` value per Option A (admin re-config qua Designer). Pattern reinforced: per-NV admin opt-in flag 3× proven (Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2) — pattern ALSO applies cho refactor existing scope, KHÔNG chỉ greenfield. Plan B Contract V2 wire vẫn pending S23 t2+. CHƯA push remote — chờ bro confirm.)
**S22 chốt cuối:** 2026-05-13 2200 (Session 22 CHỐT — **🎯 Bro chốt sub-agent solution OK**. 11 commits S22 pushed remote `3d725c4..b04a11a`. CICD Monitor Run #188 PASS verified. Highlights: Plan D F2 toggle + Plan C +20 test (test catch-up backlog) + Plan E strict V2 scope + Plan F ABORTED pre-flight (defer sau Plan B) + S22+1 fix disable 3 button bug + S22+2 seed 20 test user role-based naming + S22+3 rename users `{dept}.{level}@solutions.com.vn` + S22+4 attachment view inline PDF + Section "Điều chỉnh ngân sách" + S22+5 Mig 30 `AllowApproverEditBudget` per-NV opt-in (spec fix bro feedback). Stats final S22: **30 mig (+1 Mig 30)** · 59 tables · **~146 endpoints (+3)** · 34 FE pages · **104 test pass (+20: 5 reg #44 + 7 ReturnMode + 7 Guard + 1 V2 actor scope)** · **47 gotcha (+1 #47 paths-ignore agent-memory gap pending bro fix)** · 19 memory · 6 skills · **33 active users prod** (13 cũ + 20 mới role-based) · 4 sub-agents (CICD Monitor Run #188 PASS verify, 3 sub-agents seeds-only em main solo throughout S22). Pattern reinforced: per-NV admin opt-in flag 2× proven (Mig 29 + Mig 30) — anti-pattern default scope expansion. Memory `feedback_per_nv_permission_scope` reinforced S22+5 narrative.) **S22 chốt cuối:** 2026-05-13 2200 (Session 22 CHỐT — **🎯 Bro chốt sub-agent solution OK**. 11 commits S22 pushed remote `3d725c4..b04a11a`. CICD Monitor Run #188 PASS verified. Highlights: Plan D F2 toggle + Plan C +20 test (test catch-up backlog) + Plan E strict V2 scope + Plan F ABORTED pre-flight (defer sau Plan B) + S22+1 fix disable 3 button bug + S22+2 seed 20 test user role-based naming + S22+3 rename users `{dept}.{level}@solutions.com.vn` + S22+4 attachment view inline PDF + Section "Điều chỉnh ngân sách" + S22+5 Mig 30 `AllowApproverEditBudget` per-NV opt-in (spec fix bro feedback). Stats final S22: **30 mig (+1 Mig 30)** · 59 tables · **~146 endpoints (+3)** · 34 FE pages · **104 test pass (+20: 5 reg #44 + 7 ReturnMode + 7 Guard + 1 V2 actor scope)** · **47 gotcha (+1 #47 paths-ignore agent-memory gap pending bro fix)** · 19 memory · 6 skills · **33 active users prod** (13 cũ + 20 mới role-based) · 4 sub-agents (CICD Monitor Run #188 PASS verify, 3 sub-agents seeds-only em main solo throughout S22). Pattern reinforced: per-NV admin opt-in flag 2× proven (Mig 29 + Mig 30) — anti-pattern default scope expansion. Memory `feedback_per_nv_permission_scope` reinforced S22+5 narrative.)
**Last updated S22 prev:** 2026-05-13 1800 (Session 22 — Plan C + D + E done, Plan F ABORTED pre-flight fail. 5 commits local `60efeed``dbda37e``215b1e0``f149661`→Docs. Plan D BE+FE Admin User F2 toggle UI (PATCH /users/{id}/allow-skip-final + UsersPage column "Skip cuối" violet badge). Plan C task 4 5 reflection regression test gotcha #44 silent 403 ApprovalWorkflowsV2Controller policy split (catch class-level Policy regression). Plan C task 1-3 14 service test catch-up — ApplyReturnMode 4 mode per-NV Mig 29 + skipToFinal per-Drafter + EnsureEditableForDetails F3 gating. Plan E strict V2 scope List + Detail (remove UAT loose `|| ApprovalWorkflowId != null`, replace with V2 approver actor.UserId scope via ApprovalWorkflowLevels join). Plan F ABORTED — pre-flight prod sqlcmd reveal 23 PE + 7 Contract pin V1 + Contract entity HOÀN TOÀN V1 chưa wire V2 → drop V1 BE crash startup. Defer F sau Plan B Contract V2 + migrate 4 V1-only PE + UAT 2-3 tuần. Stats S22: 29 mig (0) · 59 tables · ~144 endpoints (+1) · 34 FE pages · **103 test pass (+19: 5 reg #44 + 7 ReturnMode + 7 Guard)** · 46 gotcha · 19 memory · 6 skills · 4 sub-agents (em main solo). Pattern reusable: SeedWorkflowAsync + SeedApproversAsync helper test infra + reflection Authorize regression test + pre-flight prod check pattern. CHƯA push remote — chờ bro confirm.) **Last updated S22 prev:** 2026-05-13 1800 (Session 22 — Plan C + D + E done, Plan F ABORTED pre-flight fail. 5 commits local `60efeed``dbda37e``215b1e0``f149661`→Docs. Plan D BE+FE Admin User F2 toggle UI (PATCH /users/{id}/allow-skip-final + UsersPage column "Skip cuối" violet badge). Plan C task 4 5 reflection regression test gotcha #44 silent 403 ApprovalWorkflowsV2Controller policy split (catch class-level Policy regression). Plan C task 1-3 14 service test catch-up — ApplyReturnMode 4 mode per-NV Mig 29 + skipToFinal per-Drafter + EnsureEditableForDetails F3 gating. Plan E strict V2 scope List + Detail (remove UAT loose `|| ApprovalWorkflowId != null`, replace with V2 approver actor.UserId scope via ApprovalWorkflowLevels join). Plan F ABORTED — pre-flight prod sqlcmd reveal 23 PE + 7 Contract pin V1 + Contract entity HOÀN TOÀN V1 chưa wire V2 → drop V1 BE crash startup. Defer F sau Plan B Contract V2 + migrate 4 V1-only PE + UAT 2-3 tuần. Stats S22: 29 mig (0) · 59 tables · ~144 endpoints (+1) · 34 FE pages · **103 test pass (+19: 5 reg #44 + 7 ReturnMode + 7 Guard)** · 46 gotcha · 19 memory · 6 skills · 4 sub-agents (em main solo). Pattern reusable: SeedWorkflowAsync + SeedApproversAsync helper test infra + reflection Authorize regression test + pre-flight prod check pattern. CHƯA push remote — chờ bro confirm.)
**S21 chốt cuối:** 2026-05-13 1530 (Session 21 chốt cuối — 5 turn cumulative pushed remote, CICD verified PASS 2/2 run, 1 gotcha mới #46 Gitea API path, 2 memory entry mới — `feedback_ef_migration_backfill_reorder` + `feedback_per_nv_permission_scope`. Session 21 5 turn timeline: t1 cicd-monitor add → t2 RAG planning Cách A → t3 fix gotcha #45 → t4 F1+F2+F3 Mig 28 → t5 refactor Allow* per-NV Mig 29. Stats final: 29 mig · 59 tables · ~143 endpoints · 34 FE pages · **84 test pass** · **46 gotcha (+2 từ S20: #45 PE button mismatch + #46 Gitea API)** · **19 memory entries (+3 từ S20: RAG + EF backfill + per-NV scope)** · 6 skills · 4 sub-agents (3 seeds-only + 1 cicd-monitor 2 runs PASS). 12 commits pushed `3a34831..c0af9e0`. Plan G Trial Week 1 evidence: CICD Monitor catch 0 fail vì green 2/2 run, cost 110-120K/run under 150K budget, 3-3.5min CI time stable. KHÔNG còn pending push. UAT mode skip dotnet test mỗi chunk, test-after Plan C bundle cho tất cả turn 3-5 sau UAT 2-3 lần ổn.) **S21 chốt cuối:** 2026-05-13 1530 (Session 21 chốt cuối — 5 turn cumulative pushed remote, CICD verified PASS 2/2 run, 1 gotcha mới #46 Gitea API path, 2 memory entry mới — `feedback_ef_migration_backfill_reorder` + `feedback_per_nv_permission_scope`. Session 21 5 turn timeline: t1 cicd-monitor add → t2 RAG planning Cách A → t3 fix gotcha #45 → t4 F1+F2+F3 Mig 28 → t5 refactor Allow* per-NV Mig 29. Stats final: 29 mig · 59 tables · ~143 endpoints · 34 FE pages · **84 test pass** · **46 gotcha (+2 từ S20: #45 PE button mismatch + #46 Gitea API)** · **19 memory entries (+3 từ S20: RAG + EF backfill + per-NV scope)** · 6 skills · 4 sub-agents (3 seeds-only + 1 cicd-monitor 2 runs PASS). 12 commits pushed `3a34831..c0af9e0`. Plan G Trial Week 1 evidence: CICD Monitor catch 0 fail vì green 2/2 run, cost 110-120K/run under 150K budget, 3-3.5min CI time stable. KHÔNG còn pending push. UAT mode skip dotnet test mỗi chunk, test-after Plan C bundle cho tất cả turn 3-5 sau UAT 2-3 lần ổn.)

View File

@ -0,0 +1,171 @@
# Session 23 turn 3 — 2026-05-15 — Plan M Fix F1 edge case + FE label rename
**Dev:** Claude Opus 4.7 1M (4 sub-agents active + em main coordinator)
**Duration:** ~2h
**Base commit:** `83c9f7b` (S23 t2 Plan L5 chốt)
**Final HEAD:** `4dd6f9c` (M2 tests) — local, CHƯA push remote (chờ Reviewer verdict)
**Total commits Plan M:** **3** (M1 + M3 + M2)
## 🎯 Trigger session
Bro UAT post-Plan L deploy (S23 t2 chốt ~02:00) phát hiện disconnect:
> "Hiện logic cũ là khi trả lại 1 cấp hoặc chỉ định hoặc edit là trạng thái draft -> cái này thay đổi lại nhé, các tính năng duyệt thẳng, trả lại 1 cấp hoặc người chỉ định hoặc cho edit thì cho xử lý đc ở trạng thái đang gửi duyệt luôn."
Em main pre-flight Investigator spawn audit code thực tế. **Verdict surprise:** code main path đã đúng (F1.OneLevel/Assignee + F2 + F3+F4 giữ ChoDuyet đúng spec Mig 28-31 cumulative). Chỉ **edge case F1.OneLevel/OneStep tại Bước 1** còn fallback Drafter (Phase=TraLai) — đây là "draft" bro phát hiện.
Bro 3 AskUserQuestion chốt spec Plan M:
- **F1.OneLevel/OneStep Bước 1**: reset (0, 1) giữ ChoDuyet (Recommended Option A — no-op effective + audit log)
- **FE Phase=TraLai display label**: rename "Trả lại" → "Cần chỉnh sửa lại"
- **F1.Drafter mode 4**: GIỮ NGUYÊN Phase=TraLai semantic (explicit role mode, KHÔNG phải fallback)
## 🌳 Plan M 3 chunk execution
### Chunk M1 — BE Service refactor F1 edge case (`c2042ef`)
👤 Chủ trì Solo (cross-stack reasoning F1 state machine — Implementer auto-refuse criteria #1 schema-ish + #4 bug-fix-reasoning).
File: [PurchaseEvaluationWorkflowService.cs:287-333](src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs:287)
```diff
case WorkflowReturnMode.OneLevel:
// Lùi 1 Cấp trong cùng Step. Nếu đang Cấp 1 → lùi sang Bước trước
// Cấp cuối. Nếu đang Bước 1 Cấp 1 → reset về (0, 1) giữ ChoDuyet
- // → fallback Drafter (no further).
+ // (Plan M S23 t3 — KHÔNG fallback Drafter, phiếu giữ "đang duyệt").
if (curLevel > 1) { ... }
else if (curStepIdx > 0) { ... }
else
{
- // Bước 1 Cấp 1 — no further back. Fallback Drafter.
- evaluation.Phase = PurchaseEvaluationPhase.TraLai;
- evaluation.CurrentWorkflowStepIndex = null;
- evaluation.CurrentApprovalLevelOrder = null;
- evaluation.SlaDeadline = null;
- return "Trả về Người soạn thảo (fallback — đang Bước 1 Cấp 1)";
+ // Bước 1 Cấp 1 → reset về (0, 1) giữ Phase=ChoDuyet (no-op effective
+ // — Approver A hiện tại). Audit log rõ "không lùi được". SLA reset
+ // dưới cuối hàm cho approver giữ nguyên.
+ evaluation.CurrentWorkflowStepIndex = 0;
+ evaluation.CurrentApprovalLevelOrder = 1;
+ summary = "Action 'Trả lại 1 Cấp' không lùi được — phiếu reset về Approver Bước 1 Cấp 1";
}
```
OneStep analog: line 314-333 cùng pattern reset (0, 1) thay vì fallback Drafter.
**Verify:**
- `dotnet build src/Backend/SolutionErp.Infrastructure` clean (0 err, 2 warn pre-existing DocxRenderer)
- Diff 13+/13- LOC 1 file surgical
**KHÔNG đụng:**
- F1.Drafter line 268-275 (explicit role mode, giữ TraLai)
- F1.Assignee line 335-360 (giữ throw nếu không match)
- F2 ApproveV2Async line 483-524 (Plan L L1 fix unchanged)
- F3 EnsureEditableForDetailsAsync (Mig 28 wire unchanged)
- F4 AdjustBudgetCommand handler (Mig 30 wire unchanged)
### Chunk M3 — FE label Phase=TraLai rename × 2 app (`508b17a`)
🟨 Implementer Case 2 (cookie-cutter mirror 2 app, ~6K tokens spawn).
Files (4 FE + 1 MEMORY):
- [fe-admin/src/types/purchaseEvaluation.ts](fe-admin/src/types/purchaseEvaluation.ts): `PurchaseEvaluationPhaseLabel[98]` + `PeDisplayStatusLabel.TraLai`
- [fe-user/src/types/purchaseEvaluation.ts](fe-user/src/types/purchaseEvaluation.ts) mirror
- [fe-admin/src/components/pe/PeWorkflowPanel.tsx](fe-admin/src/components/pe/PeWorkflowPanel.tsx): F1 dialog tooltip + confirm message status reference
- [fe-user/src/components/pe/PeWorkflowPanel.tsx](fe-user/src/components/pe/PeWorkflowPanel.tsx) mirror
Diff +4/-4 LOC × 4 file = 8 LOC tổng. Mirror 2 app §3.9 strict.
**Scope decisions Implementer:**
- ✅ Rename 2 const map label (badge primary)
- ✅ Tactical extension: rename 2 inline literal "Trả lại" trong PeWorkflowPanel.tsx F1 dialog (tooltip + confirm message — status display reference, KHÔNG phải action verb). Anti-fiddle <20% LOC threshold respected.
- KHÔNG đụng `← Trả lại` action button, `Trả về Người soạn thảo` mode picker, comments narrative
- KHÔNG đụng `types/contracts.ts` + `types/budget.ts` Phase 98 (Contract + Budget module ngoài scope M3)
**Verify:** fe-admin npm build PASS clean (0 TS err, 9.40s) + fe-user PASS clean (0 TS err, 6.92s).
### Chunk M2 — Tests F1 edge case (`4dd6f9c`)
🟨 Implementer Case 3 (test generation cookie-cutter, ~14K tokens spawn).
File: [tests/SolutionErp.Infrastructure.Tests/Services/PurchaseEvaluationWorkflowServiceReturnModeTests.cs](tests/SolutionErp.Infrastructure.Tests/Services/PurchaseEvaluationWorkflowServiceReturnModeTests.cs)
2 test mới (Pattern 11 SeedWorkflowAsync helper extend 2 param optional `allowReturnOneLevelL1` + `allowReturnOneStepL2`):
- `ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet`
- `ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet`
Assertions:
- Phase.Should().Be(ChoDuyet) KHÔNG TraLai
- CurrentWorkflowStepIndex.Should().Be(0)
- CurrentApprovalLevelOrder.Should().Be(1)
- SlaDeadline.Should().NotBeNull (reset 7d)
- ContextNote contain "không lùi được" (KHÔNG `Summary` fix spec bug em main bị Implementer correct: `LogTransitionAsync` set Summary = "Chuyển phase {from} → {to}" cố định; summary từ `ApplyReturnModeAsync` chèn vào `comment` param ghi vào `ContextNote`)
**Verify:** `dotnet test SolutionErp.slnx` **106/106 PASS** (58 Domain + 48 Infra: +2 từ 46 baseline). K7 Approver F2 NO regression (3 `ApproveV2_SkipToFinal_*` tests still green M1 chỉ đụng F1 path).
Diff +94/-2 LOC test file + 22 LOC MEMORY append.
### Pending Chunk M4 + Reviewer verdict + push
- 🟥 Reviewer pre-commit running background (a7cce1b29) verify M1+M2+M3 cumulative
- 👤 Chủ trì M4 docs (STATUS + HANDOFF + session log file này + memory update) IN PROGRESS
- Push remote sau Reviewer PASS + bro confirm
- 🟩 CICD Monitor post-deploy verify
## 📊 Stats Plan M chốt
| Metric | Trước (S23 t2) | Sau (S23 t3) | Δ |
|---|---|---|---|
| DB tables | 59 | 59 | 0 |
| Migrations | 31 | 31 | 0 |
| Endpoints | ~145 | ~145 | 0 |
| FE pages | 34 | 34 | 0 |
| **Unit tests** | 104 | **106** | **+2** (F1 OneLevel/OneStep edge case) |
| Gotchas | 47 | 47 | 0 |
| **Memory entries** | 20 | **21** | +1 reinforcement S23 t3 (per_nv_permission_scope + uat_skip_verify update) |
| Skills | 6 | 6 | 0 |
| Sub-agents | 4 (1 spawn Plan L) | 4 (3 spawn Plan M: 1 Inv + 2 Imp + 1 Rev pending) | active |
| **Commits Plan M** | | **3** (M1 + M3 + M2 local) | pending push |
## 🎯 Multi-agent ROI evidence Plan M
| Spawn | Agent | Cost (tokens) | Output | Catch |
|---|---|---|---|---|
| Pre-flight | 🟦 Investigator | ~20K | 7-feature audit matrix file:line + verdict "code main path đúng, chỉ edge case sai" | **Avoid em main spam refactor sai** verify 6/7 feature đã đúng spec, chỉ scope hẹp Bước 1 fallback cần fix |
| M1 | 👤 Chủ trì | ~self | 1 commit BE Service 13+/13- LOC | |
| M3 | 🟨 Implementer Case 2 | ~6K | 1 commit FE label rename × 4 file mirror | Tactical extension catch inline literal F1 dialog tooltip + confirm message |
| M2 | 🟨 Implementer Case 3 | ~14K | 1 commit tests 2 new + 106/106 PASS | Spec field bug catch (`ContextNote` vs `Summary`) + helper extend backward-compat |
| Reviewer | 🟥 Reviewer | ~22K (pending) | PASS verdict + Smart Friend guard | TBD post-spawn |
| Post-deploy | 🟩 CICD Monitor | ~150K (pending) | Bundle hash + smoke F1 endpoint | TBD post-push |
**Total Plan M spawn cost (excl Reviewer + CICD):** ~40K tokens well under 200K budget per session.
**Multi-agent value caught:**
1. **Investigator avoid em main spam refactor** bro statement "logic về draft" easy lead em main refactor toàn bộ 4 mode F1 + F2 + F3 + F4. Investigator catch fact code main path đã đúng scope giảm 80% (LOW effort vs HIGH).
2. **Implementer Case 3 catch spec bug `ContextNote` vs `Summary`** em main spec viết `Summary.Should().Contain(...)` sai field. Implementer correct test pass real, không pass-by-mistake.
3. **Implementer Case 2 tactical extension** rename inline literal trong F1 dialog tooltip + confirm message (cùng status reference disconnect). Anti-fiddle threshold respected, KHÔNG scope drift.
## ⚠️ Pending S23 t4+
- 🟥 **M-Review verdict** chờ Reviewer pre-commit Plan M cumulative
- 🟩 **CICD Monitor K-Plan-M** chờ push remote spawn post-deploy verify
- 🟡 **Plan B Contract V2 wire (Mig 32+33)** vẫn pending S23+ (HIGH priority next)
- **Plan F drop V1** defer sau Plan B + UAT 2-3 tuần
- 🟢 **Plan H PE PDF Export** LOW priority carry
- 🟡 **Plan I RAG Hybrid setup 5 dự án** defer chờ bro confirm
- 🔧 **Gotcha #47 paths-ignore agent-memory** PENDING bro confirm
## 📋 Pattern reinforced
- **Investigator pre-flight audit avoid em main spam refactor** khi bro statement vẻ "code sai" Investigator audit verify code đã đúng scope giảm drastically (LOW vs HIGH effort). Pattern reusable: BẮT BUỘC spawn Investigator pre-flight cho mọi bug fix scope > 50 LOC, KHÔNG em main solo audit (bias).
- **Edge case "không thực hiện được" KHÔNG fallback role khác** — pattern user-level reinforced trong `feedback_per_nv_permission_scope.md` S23 t3 entry. Action per-NV flag được trigger nhưng state machine không cho phép → giữ Phase hiện tại + reset pointer hợp lệ + audit log warning.
- **Service refactor semantic BẮT BUỘC test cùng commit** — Plan L L1→L4 lesson (CI #196/#197 FAIL cascade). Memory `feedback_uat_skip_verify.md` updated với rule mới.
- **Implementer Case 2+3 cookie-cutter 2 spawns parallel** — M2 (test) + M3 (FE) chạy background đồng thời ~6K + ~14K tokens. Em main solo M1 (cross-stack reasoning). Total Plan M token ~40K under 200K budget.
## References
- Memory user-level updated: `feedback_per_nv_permission_scope.md` (S23 t3 reinforcement) + `feedback_uat_skip_verify.md` (Plan L lesson Service refactor semantic)
- Files: [Service.cs:287-333](src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs:287) (M1) + [types/purchaseEvaluation.ts × 2 app](fe-admin/src/types/purchaseEvaluation.ts) + [PeWorkflowPanel.tsx × 2 app](fe-admin/src/components/pe/PeWorkflowPanel.tsx) (M3) + [PurchaseEvaluationWorkflowServiceReturnModeTests.cs](tests/SolutionErp.Infrastructure.Tests/Services/PurchaseEvaluationWorkflowServiceReturnModeTests.cs) (M2)
- 4 agent MEMORY.md flushed (Investigator pending + Implementer 2 entry + Reviewer pending) at `.claude/agent-memory/{investigator,implementer,reviewer,cicd-monitor}/MEMORY.md`
- Rules: §3.9 mirror 2 FE, §7 test timing, `feedback_uat_skip_verify` (updated rule Service semantic refactor), `feedback_per_nv_permission_scope` (S23 t3 reinforcement)

View File

@ -405,7 +405,7 @@ export function PeWorkflowPanel({
/> />
<span> <span>
<span className="font-medium">Trả về Người soạn thảo (mặc đnh)</span> <span className="font-medium">Trả về Người soạn thảo (mặc đnh)</span>
<span className="block text-[10px] text-slate-500">Phase "Trả lại". Drafter sửa rồi gửi lại chạy từ Cấp 1 Bước 1.</span> <span className="block text-[10px] text-slate-500">Phase "Cần chỉnh sửa lại". Drafter sửa rồi gửi lại chạy từ Cấp 1 Bước 1.</span>
</span> </span>
</label> </label>
)} )}
@ -414,7 +414,7 @@ export function PeWorkflowPanel({
)} )}
<div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[11px] text-amber-800"> <div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[11px] text-amber-800">
{returnMode === WorkflowReturnMode.Drafter {returnMode === WorkflowReturnMode.Drafter
? 'Phiếu sẽ về "Trả lại". Drafter có thể sửa rồi trình lại từ Cấp 1 Bước 1.' ? 'Phiếu sẽ về "Cần chỉnh sửa lại". Drafter có thể sửa rồi trình lại từ Cấp 1 Bước 1.'
: returnMode === WorkflowReturnMode.Assignee : returnMode === WorkflowReturnMode.Assignee
? 'Phiếu sẽ về Cấp/Bước của NV đã chọn (vẫn "Đã gửi duyệt"). NV nhận lại để duyệt tiếp.' ? 'Phiếu sẽ về Cấp/Bước của NV đã chọn (vẫn "Đã gửi duyệt"). NV nhận lại để duyệt tiếp.'
: 'Phiếu sẽ lùi pointer (vẫn "Đã gửi duyệt"). NV trước nhận lại để duyệt tiếp.'} : 'Phiếu sẽ lùi pointer (vẫn "Đã gửi duyệt"). NV trước nhận lại để duyệt tiếp.'}

View File

@ -41,7 +41,7 @@ export const PurchaseEvaluationPhaseLabel: Record<number, string> = {
6: 'Chờ CEO duyệt NCC', 6: 'Chờ CEO duyệt NCC',
7: 'Đã duyệt', 7: 'Đã duyệt',
10: 'Đã gửi duyệt', 10: 'Đã gửi duyệt',
98: 'Trả lại', 98: 'Cần chỉnh sửa lại',
99: 'Từ chối', 99: 'Từ chối',
} }
@ -83,7 +83,7 @@ export type PeDisplayStatus = typeof PeDisplayStatus[keyof typeof PeDisplayStatu
export const PeDisplayStatusLabel: Record<PeDisplayStatus, string> = { export const PeDisplayStatusLabel: Record<PeDisplayStatus, string> = {
Nhap: 'Nháp', Nhap: 'Nháp',
DaGuiDuyet: 'Đã gửi duyệt', DaGuiDuyet: 'Đã gửi duyệt',
TraLai: 'Trả lại', TraLai: 'Cần chỉnh sửa lại',
DaDuyet: 'Đã duyệt', DaDuyet: 'Đã duyệt',
TuChoi: 'Từ chối', TuChoi: 'Từ chối',
} }

View File

@ -404,7 +404,7 @@ export function PeWorkflowPanel({
/> />
<span> <span>
<span className="font-medium">Trả về Người soạn thảo (mặc đnh)</span> <span className="font-medium">Trả về Người soạn thảo (mặc đnh)</span>
<span className="block text-[10px] text-slate-500">Phase "Trả lại". Drafter sửa rồi gửi lại chạy từ Cấp 1 Bước 1.</span> <span className="block text-[10px] text-slate-500">Phase "Cần chỉnh sửa lại". Drafter sửa rồi gửi lại chạy từ Cấp 1 Bước 1.</span>
</span> </span>
</label> </label>
)} )}
@ -413,7 +413,7 @@ export function PeWorkflowPanel({
)} )}
<div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[11px] text-amber-800"> <div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[11px] text-amber-800">
{returnMode === WorkflowReturnMode.Drafter {returnMode === WorkflowReturnMode.Drafter
? 'Phiếu sẽ về "Trả lại". Drafter có thể sửa rồi trình lại từ Cấp 1 Bước 1.' ? 'Phiếu sẽ về "Cần chỉnh sửa lại". Drafter có thể sửa rồi trình lại từ Cấp 1 Bước 1.'
: returnMode === WorkflowReturnMode.Assignee : returnMode === WorkflowReturnMode.Assignee
? 'Phiếu sẽ về Cấp/Bước của NV đã chọn (vẫn "Đã gửi duyệt"). NV nhận lại để duyệt tiếp.' ? 'Phiếu sẽ về Cấp/Bước của NV đã chọn (vẫn "Đã gửi duyệt"). NV nhận lại để duyệt tiếp.'
: 'Phiếu sẽ lùi pointer (vẫn "Đã gửi duyệt"). NV trước nhận lại để duyệt tiếp.'} : 'Phiếu sẽ lùi pointer (vẫn "Đã gửi duyệt"). NV trước nhận lại để duyệt tiếp.'}

View File

@ -41,7 +41,7 @@ export const PurchaseEvaluationPhaseLabel: Record<number, string> = {
6: 'Chờ CEO duyệt NCC', 6: 'Chờ CEO duyệt NCC',
7: 'Đã duyệt', 7: 'Đã duyệt',
10: 'Đã gửi duyệt', 10: 'Đã gửi duyệt',
98: 'Trả lại', 98: 'Cần chỉnh sửa lại',
99: 'Từ chối', 99: 'Từ chối',
} }
@ -83,7 +83,7 @@ export type PeDisplayStatus = typeof PeDisplayStatus[keyof typeof PeDisplayStatu
export const PeDisplayStatusLabel: Record<PeDisplayStatus, string> = { export const PeDisplayStatusLabel: Record<PeDisplayStatus, string> = {
Nhap: 'Nháp', Nhap: 'Nháp',
DaGuiDuyet: 'Đã gửi duyệt', DaGuiDuyet: 'Đã gửi duyệt',
TraLai: 'Trả lại', TraLai: 'Cần chỉnh sửa lại',
DaDuyet: 'Đã duyệt', DaDuyet: 'Đã duyệt',
TuChoi: 'Từ chối', TuChoi: 'Từ chối',
} }

View File

@ -286,7 +286,8 @@ public class PurchaseEvaluationWorkflowService(
{ {
case WorkflowReturnMode.OneLevel: case WorkflowReturnMode.OneLevel:
// Lùi 1 Cấp trong cùng Step. Nếu đang Cấp 1 → lùi sang Bước trước // Lùi 1 Cấp trong cùng Step. Nếu đang Cấp 1 → lùi sang Bước trước
// Cấp cuối. Nếu đang Bước 1 Cấp 1 → fallback Drafter (no further). // Cấp cuối. Nếu đang Bước 1 Cấp 1 → reset về (0, 1) giữ ChoDuyet
// (Plan M S23 t3 — KHÔNG fallback Drafter, phiếu giữ "đang duyệt").
if (curLevel > 1) if (curLevel > 1)
{ {
evaluation.CurrentApprovalLevelOrder = curLevel - 1; evaluation.CurrentApprovalLevelOrder = curLevel - 1;
@ -302,12 +303,12 @@ public class PurchaseEvaluationWorkflowService(
} }
else else
{ {
// Bước 1 Cấp 1 — no further back. Fallback Drafter. // Bước 1 Cấp 1 → reset về (0, 1) giữ Phase=ChoDuyet (no-op effective
evaluation.Phase = PurchaseEvaluationPhase.TraLai; // — Approver A hiện tại). Audit log rõ "không lùi được". SLA reset
evaluation.CurrentWorkflowStepIndex = null; // dưới cuối hàm cho approver giữ nguyên.
evaluation.CurrentApprovalLevelOrder = null; evaluation.CurrentWorkflowStepIndex = 0;
evaluation.SlaDeadline = null; evaluation.CurrentApprovalLevelOrder = 1;
return "Trả về Người soạn thảo (fallback — đang Bước 1 Cấp 1)"; summary = "Action 'Trả lại 1 Cấp' không lùi được — phiếu reset về Approver Bước 1 Cấp 1";
} }
break; break;
@ -323,12 +324,11 @@ public class PurchaseEvaluationWorkflowService(
} }
else else
{ {
// Đang Bước 1 → fallback Drafter // Bước 1 → reset về (0, 1) giữ Phase=ChoDuyet (Plan M S23 t3 —
evaluation.Phase = PurchaseEvaluationPhase.TraLai; // KHÔNG fallback Drafter, phiếu giữ "đang duyệt").
evaluation.CurrentWorkflowStepIndex = null; evaluation.CurrentWorkflowStepIndex = 0;
evaluation.CurrentApprovalLevelOrder = null; evaluation.CurrentApprovalLevelOrder = 1;
evaluation.SlaDeadline = null; summary = "Action 'Trả lại 1 Bước' không lùi được — phiếu reset về Approver Bước 1 Cấp 1";
return "Trả về Người soạn thảo (fallback — đang Bước đầu)";
} }
break; break;

View File

@ -42,6 +42,8 @@ public class PurchaseEvaluationWorkflowServiceReturnModeTests
// Workflow setup: 1 Bước (1 Step) — 2 Cấp (2 Levels) — mỗi Cấp 1 Approver. // Workflow setup: 1 Bước (1 Step) — 2 Cấp (2 Levels) — mỗi Cấp 1 Approver.
// Mặc định mọi Allow* = false trên Level slot (admin opt-in pattern Mig 29). // Mặc định mọi Allow* = false trên Level slot (admin opt-in pattern Mig 29).
// ApproverUserId mặc định = approverId truyền vào (caller có thể override). // ApproverUserId mặc định = approverId truyền vào (caller có thể override).
// Plan M S23 t3 — thêm 2 param L1 (`allowReturnOneLevelL1` + `allowReturnOneStepL2`)
// phục vụ test edge case Bước 1 Cấp 1 reset ChoDuyet (không lùi được).
private static async Task<(ApprovalWorkflow wf, ApprovalWorkflowStep step, ApprovalWorkflowLevel l1, ApprovalWorkflowLevel l2)> private static async Task<(ApprovalWorkflow wf, ApprovalWorkflowStep step, ApprovalWorkflowLevel l1, ApprovalWorkflowLevel l2)>
SeedWorkflowAsync( SeedWorkflowAsync(
TestApplicationDbContext db, TestApplicationDbContext db,
@ -49,7 +51,9 @@ public class PurchaseEvaluationWorkflowServiceReturnModeTests
Guid approver2UserId, Guid approver2UserId,
bool allowReturnOneLevelL2 = false, bool allowReturnOneLevelL2 = false,
bool allowReturnToDrafterL2 = false, bool allowReturnToDrafterL2 = false,
bool allowApproverEditL2 = false) bool allowApproverEditL2 = false,
bool allowReturnOneLevelL1 = false,
bool allowReturnOneStepL2 = false)
{ {
var wf = new ApprovalWorkflow var wf = new ApprovalWorkflow
{ {
@ -75,7 +79,8 @@ public class PurchaseEvaluationWorkflowServiceReturnModeTests
ApprovalWorkflowStepId = step.Id, ApprovalWorkflowStepId = step.Id,
Order = 1, Order = 1,
ApproverUserId = approver1UserId, ApproverUserId = approver1UserId,
// L1 defaults: Allow* = false (test sad path easier) AllowReturnOneLevel = allowReturnOneLevelL1,
// Các Allow* khác mặc định false (sad path)
}; };
var l2 = new ApprovalWorkflowLevel var l2 = new ApprovalWorkflowLevel
{ {
@ -84,6 +89,7 @@ public class PurchaseEvaluationWorkflowServiceReturnModeTests
Order = 2, Order = 2,
ApproverUserId = approver2UserId, ApproverUserId = approver2UserId,
AllowReturnOneLevel = allowReturnOneLevelL2, AllowReturnOneLevel = allowReturnOneLevelL2,
AllowReturnOneStep = allowReturnOneStepL2,
AllowReturnToDrafter = allowReturnToDrafterL2, AllowReturnToDrafter = allowReturnToDrafterL2,
AllowApproverEditDetails = allowApproverEditL2, AllowApproverEditDetails = allowApproverEditL2,
}; };
@ -240,6 +246,113 @@ public class PurchaseEvaluationWorkflowServiceReturnModeTests
} }
} }
// ============ Plan M S23 t3 — F1 edge case Bước 1 reset ChoDuyet ============
// Cũ: Approver Bước 1 Cấp 1 click "Trả lại 1 Cấp" / "Trả lại 1 Bước" → fallback
// Drafter mode (Phase=TraLai, clear pointer). UX confusing: Approver A đột nhiên
// bị "đẩy phiếu về Drafter" mặc dù mình chính là người đang giữ phiếu.
// Mới: reset (Step=0, Level=1) GIỮ Phase=ChoDuyet — no-op effective (Approver A
// vẫn giữ phiếu), SLA reset 7d, audit log ContextNote "không lùi được". 2 test
// dưới cover OneLevel + OneStep edge case riêng — Drafter mode (line 268-275)
// GIỮ semantic Phase=TraLai unchanged.
[Fact]
public async Task ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet()
{
// Workflow 1 Step × 2 Levels, PE đang Bước 1 Cấp 1 (Approver A duyệt).
// Slot Cấp 1 tick AllowReturnOneLevel=true (admin opt-in pattern Mig 29).
var (svc, fix, db, _) = CreateService();
using (fix)
{
var (a1, a2) = await SeedApproversAsync(fix, "rm-edge-onelevel");
var (wf, _, _, _) = await SeedWorkflowAsync(db, a1.Id, a2.Id, allowReturnOneLevelL1: true);
var pe = new PurchaseEvaluation
{
Id = Guid.NewGuid(),
Type = PurchaseEvaluationType.DuyetNcc,
Phase = PurchaseEvaluationPhase.ChoDuyet,
MaPhieu = "PE-RM-EDGE-OL",
TenGoiThau = "OneLevel edge Bước 1 Cấp 1",
ProjectId = Guid.NewGuid(),
DrafterUserId = Guid.NewGuid(),
ApprovalWorkflowId = wf.Id,
CurrentWorkflowStepIndex = 0, // Bước 1
CurrentApprovalLevelOrder = 1, // Cấp 1 — edge case "không lùi được"
};
db.PurchaseEvaluations.Add(pe);
await db.SaveChangesAsync(CancellationToken.None);
await svc.TransitionAsync(
evaluation: pe,
targetPhase: PurchaseEvaluationPhase.TraLai,
actorUserId: a1.Id,
actorRoles: new[] { AppRoles.CostControl },
decision: ApprovalDecision.Reject,
comment: "thử trả 1 cấp tại Bước 1 Cấp 1",
returnMode: WorkflowReturnMode.OneLevel,
ct: CancellationToken.None);
pe.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet,
"Plan M edge case: GIỮ ChoDuyet (no-op effective), KHÔNG fallback Drafter TraLai");
pe.CurrentWorkflowStepIndex.Should().Be(0, "Pointer reset Bước 1");
pe.CurrentApprovalLevelOrder.Should().Be(1, "Pointer reset Cấp 1");
pe.SlaDeadline.Should().NotBeNull("SLA reset 7d cho Approver A giữ phiếu");
// Audit log "không lùi được" phải xuất hiện ở ContextNote (LogTransitionAsync
// append summary từ ApplyReturnModeAsync vào field này — line 96-99 service).
// Summary field cố định "Chuyển phase {from} → {to}".
var changelog = await db.PurchaseEvaluationChangelogs
.Where(c => c.PurchaseEvaluationId == pe.Id)
.OrderByDescending(c => c.CreatedAt)
.FirstAsync();
changelog.ContextNote.Should().NotBeNull();
changelog.ContextNote!.Should().Contain("không lùi được",
"Audit trail rõ ràng cho UAT review — phân biệt no-op vs lùi thật");
}
}
[Fact]
public async Task ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet()
{
// Workflow 1 Step × 2 Levels, PE đang Bước 1 Cấp 2 (Approver B duyệt).
// Slot Cấp 2 tick AllowReturnOneStep=true. Action OneStep từ Bước 1 → edge
// case "không lùi được Bước" (vẫn là first Step) → reset (0, 1) giữ ChoDuyet.
// Approver B sau reset bị bàn giao về Approver A (Cấp 1) — vẫn trong chuỗi duyệt.
var (svc, fix, db, _) = CreateService();
using (fix)
{
var (a1, a2) = await SeedApproversAsync(fix, "rm-edge-onestep");
var (wf, _, _, _) = await SeedWorkflowAsync(db, a1.Id, a2.Id, allowReturnOneStepL2: true);
var pe = BuildPeAtLevel2(wf.Id, drafterId: Guid.NewGuid(), code: "PE-RM-EDGE-OS");
db.PurchaseEvaluations.Add(pe);
await db.SaveChangesAsync(CancellationToken.None);
await svc.TransitionAsync(
evaluation: pe,
targetPhase: PurchaseEvaluationPhase.TraLai,
actorUserId: a2.Id,
actorRoles: new[] { AppRoles.CostControl },
decision: ApprovalDecision.Reject,
comment: "thử trả 1 bước tại Bước 1",
returnMode: WorkflowReturnMode.OneStep,
ct: CancellationToken.None);
pe.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet,
"Plan M edge case: GIỮ ChoDuyet (no-op effective), KHÔNG fallback Drafter TraLai");
pe.CurrentWorkflowStepIndex.Should().Be(0, "Pointer reset Bước 1");
pe.CurrentApprovalLevelOrder.Should().Be(1, "Pointer reset Cấp 1 (bàn giao về Approver A)");
pe.SlaDeadline.Should().NotBeNull("SLA reset 7d cho approver mới");
var changelog = await db.PurchaseEvaluationChangelogs
.Where(c => c.PurchaseEvaluationId == pe.Id)
.OrderByDescending(c => c.CreatedAt)
.FirstAsync();
changelog.ContextNote.Should().NotBeNull();
changelog.ContextNote!.Should().Contain("không lùi được",
"Audit trail rõ ràng cho UAT review — phân biệt no-op vs lùi thật");
}
}
// ============ Task 2: skipToFinal — Mig 31 (S23 t1 Plan K Chunk F) ============ // ============ Task 2: skipToFinal — Mig 31 (S23 t1 Plan K Chunk F) ============
// Semantic mới: Approver during ChoDuyet duyệt thẳng Cấp cuối → Phase=DaDuyet terminal. // Semantic mới: Approver during ChoDuyet duyệt thẳng Cấp cuối → Phase=DaDuyet terminal.
// Storage: matchingLevel.AllowApproverSkipToFinal (per-Approver-slot, admin opt-in). // Storage: matchingLevel.AllowApproverSkipToFinal (per-Approver-slot, admin opt-in).