# 2026-05-08 (Session 17 wrap-up) — PE Workflow V2 schema + Service wire end-to-end

**13 commit** từ `c847dc0` → `de0f38d`. Resume từ Session 16 (drastic refactor flat Mig 21) — user phát hiện schema flat vẫn không đúng intent → yêu cầu schema riêng + Menu mới.

## Spec từ user

```
Mã Quy trình - Tên Quy trình
  * Bước 1 - Phòng A
    * Cấp 1 - NV X (1 NV cụ thể qua ApproverUserId)
    * Cấp 2 - NV Y
  * Bước 2 - Phòng B
    * Cấp 1 - NV Z
```

Khác Mig 21: mỗi Cấp = NV CỤ THỂ qua `ApproverUserId`, KHÔNG group Dept+PositionLevel/Role/User.

## State machine 5 trạng thái (Option A user chốt diagram)

```
Nháp ──Drafter trình──► Đã gửi duyệt ──approve cấp cuối──► Đã duyệt (terminal)
                                     ├─ Trả lại ──────────► Trả lại
                                     └─ Từ chối ──────────► Từ chối (terminal)
Trả lại ──Drafter sửa+gửi lại──► Đã gửi duyệt (chạy LẠI từ Cấp 1 Bước 1)
```

Trả lại = Phase RIÊNG (`TraLai=98`), KHÔNG revert DangSoanThao + KHÔNG jump-back. Drafter từ TraLai gửi lại = entry point thứ 2 mirror DangSoanThao → workflow chạy lại từ đầu.

## Migration 22-24 (3 mig mới)

| Mig | Tên | Nội dung |
|---|---|---|
| **22** | `AddApprovalWorkflowsV2` | 3 entity: ApprovalWorkflow + Step + Level + ApplicableType enum (DuyetNcc/DuyetNccPhuongAn/Contract). 3 CREATE TABLE + UNIQUE (Code, Version) + FK Cascade Step→Workflow + Level→Step + FK Restrict Department + ApproverUserId. Menu V2 seed |
| **23** | `AddApprovalWorkflowIdToPurchaseEvaluation` | PE.ApprovalWorkflowId Guid? FK Restrict — pin V2 lúc create |
| **24** | `AddCurrentApprovalLevelOrderToPe` | PE.CurrentApprovalLevelOrder int? — track Cấp đang chờ |

## 13 commit timeline

| # | Commit | Tóm tắt |
|---|---|---|
| 1 | `c847dc0` | Chunk A — Domain V2 + EF Config + Mig 22 + Menu seed |
| 2 | `f6047d5` | Chunk B — Application CQRS + API ApprovalWorkflowsV2Controller |
| 3 | `2781c7e` | Chunk C — FE Designer ~480 LOC `ApprovalWorkflowsV2Page.tsx` |
| 4 | `12daa7f` | Chunk D — Docs Schema mới |
| 5 | `9712778` | Designer iter 1 — lock 3 cấp/bước (sai intent) |
| 6 | `f3bea3c` | Designer iter 2 — đúng intent: max 3 cấp × N NV/cấp + sequential gating + filter Phòng + Validator BE strict |
| 7 | `ff21120` | State machine 5 trạng thái — TraLai Phase riêng. Policy + Service Reject branch → TraLai. 4 test mới TraLai entry. FE rename "Bản nháp" → "Nháp" |
| 8 | `d642fd3` | Docs STATUS Session 17 5 trạng thái |
| 9 | `0a40c65` | Mig 23 pin V2 vào PE + Workspace Select V2 |
| 10 | `b41484b` | Mig 24 + Service V2 wire `ApproveV2Async` + Synthetic Policy ForV2Schema |
| 11 | `d814429` | UX disable button non-approver + banner "Đến lượt bạn" / "Không phải lượt" + DTO CurrentApproval |
| 12 | `9e63e2d` `d250ae4` `74745a7` | List/Inbox V2-aware + 2 dropdown filter (chỉ ở Duyệt) |
| 13 | `ac41d5e` | SQL clean prod + test user `nv.test@solutions.com.vn` |
| — | `de0f38d` | Panel 3 flow render — bỏ phase cards, hiện Bước/Cấp với Status Done/Current/Pending |

## Service V2 wire — branch theo schema pin

```csharp
// PurchaseEvaluationWorkflowService.TransitionAsync (Mig 24)
if (evaluation.ApprovalWorkflowId is Guid awId)
    await ApproveV2Async(...)
        // Load AW.Steps + Levels Include 3-level
        // Group Levels by Order = Cấp (OR-of-N approvers cùng cấp)
        // Match actor.Id ∈ ApproverUserId
        // Advance: levelOrder++ trong Step → idx++ + reset levelOrder=1 → DaDuyet
else
    await ApproveV1LegacyAsync(...)  // giữ logic Mig 21
```

## UX V2-aware

- Banner emerald "✓ Đến lượt bạn duyệt" / amber "⚠ Không phải lượt bạn — chỉ {NV X / Y} duyệt được"
- Button Duyệt forward `disabled` khi V2 + actor không trong Cấp + tooltip
- Trả lại + Từ chối vẫn enabled (BE không gating reject theo cấp)
- Inbox `ResolveV2InboxIdsAsync` precompute IDs khớp actor.Id
- 2 dropdown filter "Quy trình duyệt" + "Trạng thái" (chỉ ở Duyệt — Danh sách giữ 1 dropdown)
- Panel 3 thay 4 phase cards bằng flow workflow thực tế:
  ```
  ✓ Bước 1 — Phòng CCM
     ✓ Cấp 1 (đã duyệt) NV Test
     ● Cấp 2 (đang chờ) Phan Văn Chương   ← highlight brand
     ○ Cấp 3 (chưa)     Nguyễn Văn Trường
  ```

## Test setup prod

- SQL `clean-transactional-uat.sql` xóa 9 PE + 11 HĐ + 19 Notif + reset CodeSequences. Giữ master (Users=8, Suppliers=19, Projects=9, Departments=9, V1+V2 Workflows). Run via SSH VPS `.\SQLEXPRESS`.
- Test user `nv.test@solutions.com.vn` / `TestUser@123456` (Drafter, CCM, ID `881269c7-dbb5-4aad-a92c-08deace07898`) tạo qua API admin `POST /api/users`.

## Stats final Session 17

| | Trước | Sau |
|---|---|---|
| Migrations | 21 | **24** (+3) |
| DB tables | 55 | **58** (+3) |
| API endpoints | ~134 | **~140** (+5) |
| FE pages | 32 | **33** (+1) |
| Test pass | 77 | **81** (+4 TraLai entry point) |
| Gotchas | 41 | **43** (+2 dual schema + Step.Order ≠ index) |

## Gotchas mới (2)

- **#42 Dual schema V1 vs V2 — Service phải branch theo pin field**: phiếu pin V2 mà Service đọc `WorkflowDefinitionId` → match approver schema cũ → Forbidden. Fix: branch `if (ApprovalWorkflowId is Guid)` → `ApproveV2Async`.
- **#43 Step.Order ≠ index 0-based**: EF không thể query `Step.Order == idx + 1` thẳng. Cần precompute candidates EF + in-memory sort by Order + array index.

## Pending Session 18+

| Task | Estimate | Priority |
|---|---|---|
| Contract V2 wire (Mig 25) | 4-6h dedicated session | High (sau khi PE UAT confirm) |
| Phân quyền strict V2 | 2h | Medium |
| Drop legacy V1 + cleanup deprecated columns | Mig 26 cleanup, 2h | Low (chờ migrate hết phiếu cũ) |
| Test Domain ApproveV2Async + match logic | 3h | Medium |
| Budget V2 wire (Mig 27) | 4h dedicated | Low |
| Admin role bypass option C audit log | 2h | Low |

## Memory entry — không tạo mới

Session 17 decisions ghi đầy đủ vào `memory/project_solution_erp.md`. Pattern dual schema reusable nhưng quá specific cho project — không tạo `feedback_dual_schema_pattern.md` riêng (anti-pattern §9.5 "viết skill chỉ để có thêm" áp dụng cho memory).

## Skill update

`contract-workflow` skill +section "Phase 9+ done (Mig 22-24 — Session 17) — V2 schema riêng + state machine 5 trạng thái" — mention V2 cross-ref + Service branch + match logic V2 vs V1 table + Designer constraints.

KHÔNG tạo skill `pe-workflow-v2` mới — overlap với `contract-workflow` existing skill (cùng pattern state machine + versioned WF, khác schema).
