[CLAUDE] Docs: Session 17 wrap-up — PE Workflow V2 end-to-end consolidation
User chốt MD wrap-up Session 17 (13 commit từc847dc0→de0f38d) — PE Workflow V2 schema + Service wire + UX iteration đầy đủ. MD updates: - STATUS.md — header counts (24 mig, 58 tables, 81 test, 43 gotcha) + Recently Done row consolidate Session 17 wrap-up (gộp 4 row iter cũ thành 1 row tổng, không cắt narrative quan trọng) - HANDOFF.md — TL;DR Session 17 + Chunk E (Designer iter + State machine + Service wire + UX) + Pending Session 18+ - CLAUDE.md — count (22→24 mig, 77→81 test) + V2 schema overview - changelog/migration-todos.md — section ✅ Session 17 done với 7 task ticked + Defer Session 18+ explicit - database/schema-diagram.md — §14 ApprovalWorkflow V2 schema (3 bảng + 2 column PE + state transitions + Service branch + Designer constraints + match approver V2 vs V1 table) - gotchas.md — +#42 Dual schema branch + #43 Step.Order ≠ index 0-based - skill contract-workflow — +section "Phase 9+ done Mig 22-24" với V2 spec + Service branch + match logic table + Designer constraints + UX V2-aware + Defer Session 18+ - changelog/sessions/2026-05-08-1100-pe-workflow-v2-end-to-end.md (NEW) — full session log với 13 commit timeline + stats + gotchas + pending Memory: - project_solution_erp.md — entry Session 17 wrap-up đầy đủ (KHÔNG tạo file feedback mới — decisions specific cho project, không reusable cross-project per §9.5 anti-pattern) Verify: - 81 test pass (58 Domain + 23 Infra) — không thay đổi sau wrap-up - 0 BE error - 2 FE builds OK (đã verify ở các commit trước) - Quy tắc consolidate §6.5: KHÔNG cắt narrative, chỉ phân tầng + remove duplicate. Session 17 row dài cố ý — cover full 13 commit context cho Session 18+ đọc lại.
This commit is contained in:
@ -1,10 +1,10 @@
|
||||
# HANDOFF — Brief 5 phút cho session tiếp theo
|
||||
|
||||
**Last updated:** 2026-05-08 (Session 17 — **🎯 SCHEMA MỚI ApprovalWorkflowsV2 + Menu "Duyệt NCC (Mới)" UAT. 4 commit. Schema riêng trên Mig 22. Cấu trúc Bước (Phòng) > Cấp (NV cụ thể qua ApproverUserId — KHÔNG OR-of-many). Chỉ admin Designer xong, Service PE/Contract chưa wire qua schema mới — UAT UIUX trước.**)
|
||||
**Last updated:** 2026-05-08 (Session 17 wrap-up — **🎯 PE Workflow V2 schema + Service WIRE end-to-end DONE. 13 commit `c847dc0` → `de0f38d`. Mig 22-24. State machine 5 trạng thái. Service PE iterate Steps/Levels match `ApproverUserId`. Designer max 3 cấp × N NV/cấp. Panel 3 flow render thực tế. Test prod cleaned + user `nv.test@solutions.com.vn` tạo. 81 test pass. Contract V2 wire DEFER session sau.**)
|
||||
|
||||
## TL;DR Session 17 (08/05 — Schema mới UAT trước khi drop legacy)
|
||||
## TL;DR Session 17 — PE V2 schema end-to-end
|
||||
|
||||
User chốt sau Session 16: schema flat Mig 21 vẫn không đúng intent. Yêu cầu **viết lại toàn bộ chỗ Quy trình Duyệt + thêm Menu mới "Duyệt NCC (Mới)"** với cấu trúc rõ ràng:
|
||||
User chốt sau Session 16 (drastic refactor flat Mig 21 vẫn sai intent): **viết lại schema riêng + thêm Menu "Duyệt NCC (Mới)"** với cấu trúc explicit:
|
||||
|
||||
```
|
||||
Mã Quy trình - Tên Quy trình
|
||||
@ -50,19 +50,52 @@ Khác Mig 21: mỗi Cấp = 1 NV chính xác, KHÔNG OR-of-many group Dept+Posit
|
||||
- App.tsx +2 route
|
||||
- menuKeys.ts +2 const sync với BE
|
||||
|
||||
### Chunk D — Docs (current)
|
||||
### Chunk D — Docs
|
||||
|
||||
STATUS + HANDOFF + project_solution_erp.md memory.
|
||||
|
||||
### Chunk E (UAT iteration, 9 commit) — Designer fix + State machine + Service wire + UX
|
||||
|
||||
User UAT iter Designer V2 phát hiện multiple issues + chốt spec dần qua state diagram. Per memory `feedback_uat_skip_verify.md` UAT mode iterate nhanh:
|
||||
|
||||
| Commit | Tóm tắt |
|
||||
|---|---|
|
||||
| `9712778` | Designer iter 1 lock 3 cấp/bước (sai intent) |
|
||||
| `f3bea3c` | Designer iter 2 đúng intent: max 3 cấp × N NV/cấp + sequential gating C2/C3 disabled khi prev empty + filter NV theo Phòng + no-dup same level. Validator BE strict |
|
||||
| `ff21120` | State machine 5 trạng thái Nháp/ĐãGửiDuyệt/TrảLại/TừChối/ĐãDuyệt. TraLai = Phase RIÊNG (không revert DangSoanThao + không jump-back). PE/Contract/Budget Phase enum + Policy + Service Reject branch → TraLai. 4 test mới TraLai entry point |
|
||||
| `0a40c65` | **Mig 23** `AddApprovalWorkflowIdToPurchaseEvaluation` — pin V2 vào PE entity. Workspace Select bắt buộc workflow lúc create. Validate ApplicableType match PE.Type |
|
||||
| `b41484b` | **Mig 24** `AddCurrentApprovalLevelOrderToPe` + Service V2 wire — `ApproveV2Async` iterate Steps/Levels group by Order = Cấp (OR-of-N approvers) match `actor.Id ∈ ApproverUserId`. Synthetic Policy `ForV2Schema()` cho FE nextPhases |
|
||||
| `d814429` | DTO CurrentApproval + banner "Đến lượt bạn" / "Không phải lượt bạn" + button Duyệt forward disabled khi V2 + actor không trong cấp + tooltip "chỉ {NV X / Y} duyệt được". Trả lại + Từ chối vẫn enabled |
|
||||
| `9e63e2d` `d250ae4` `74745a7` | List/Inbox V2-aware (`ResolveV2InboxIdsAsync` precompute IDs). 2 dropdown filter Quy trình + Trạng thái (chỉ ở Duyệt). Inbox endpoint nhận `approvalWorkflowId` |
|
||||
| `ac41d5e` | SQL `clean-transactional-uat.sql` — clean prod (9 PE + 11 HĐ + 19 Notif xóa) giữ master. Run qua SSH VPS `.\SQLEXPRESS` |
|
||||
| `de0f38d` | Panel 3 thay 4 phase cards bằng flow workflow thực tế: Bước (icon ✓/●/○) → Cấp (label "đang chờ"/"đã duyệt" + tên NV). DTO `ApprovalFlow` full snapshot với Status Done/Current/Pending |
|
||||
|
||||
**Stats final Session 17:** 24 migration (+3), 58 DB tables (+3), ~140 endpoints (+5), 81 test pass (+4).
|
||||
|
||||
**Test user UAT** (tạo qua API admin):
|
||||
- Email: `nv.test@solutions.com.vn` / Pass: `TestUser@123456` / Role: Drafter / Phòng: CCM
|
||||
|
||||
## ⚠️ Điều quan trọng cho Session 18+
|
||||
|
||||
1. **PE/Contract Service CHƯA wire** qua schema mới — vẫn pin `WorkflowDefinitionId` từ Mig 21 (legacy). Sau khi user UAT UIUX OK, Session sau cần:
|
||||
- Thêm pinning `ApprovalWorkflowId` vào PE/Contract entity (nullable, song song WorkflowDefinitionId)
|
||||
- Service rewrite: nếu pin V2 → iterate ApprovalWorkflowSteps OrderBy Order → ApprovalWorkflowLevels OrderBy Order → match `actor.Id == level.ApproverUserId` (chính xác 1-1)
|
||||
- Migration data từ legacy → V2 (manual hoặc seed sample)
|
||||
- Drop legacy WorkflowDefinition + WorkflowStep + Approver tables sau khi không phiếu nào pin
|
||||
2. **Backward compat 100%** — schema mới chỉ thêm vào, không sửa cũ. Phiếu PE/Contract đang chạy không bị ảnh hưởng.
|
||||
3. **77 test pass giữ nguyên** — không thêm test mới (Designer UI chưa critical, defer khi Service wire xong → test policy + match approver).
|
||||
1. **Contract V2 wire CHƯA làm** — chỉ PE wire xong (Mig 23-24). Session sau mirror pattern PE → Contract:
|
||||
- Thêm `Contract.ApprovalWorkflowId` + `CurrentApprovalLevelOrder` (Mig 25)
|
||||
- Update `ContractWorkflowService` thêm `ApproveV2Async` branch
|
||||
- Update Workspace Select V2 trong `ContractCreatePage`
|
||||
- Pin V2 thành mặc định cho Contract types
|
||||
|
||||
2. **Phân quyền strict V2** — hiện loose UAT (mọi authenticated user thấy phiếu V2). Sau confirm flow OK:
|
||||
- List: Drafter + bất kỳ approver any-Step + Admin
|
||||
- Inbox: chỉ approver Cấp hiện tại (V2 đã đúng — `ResolveV2InboxIdsAsync`)
|
||||
- Detail: same as List
|
||||
|
||||
3. **Drop legacy V1 sau UAT** — khi không còn phiếu nào pin `WorkflowDefinitionId`:
|
||||
- Drop tables `WorkflowDefinitions` + `WorkflowSteps` + `WorkflowStepApprovers` + PE versions
|
||||
- Cleanup migration: drop column `RejectedAtStepIndex` + `RejectedFromPhase` (deprecated từ Session 17)
|
||||
- Drop `ApproveV1LegacyAsync` branch trong Service
|
||||
|
||||
4. **Admin role bypass** — hiện code `if (!isAdmin && !isSystem)` skip approver check. By design cho UAT + emergency override. Nếu prod cần audit override → option C trong Session 17 thảo luận: thêm flag `IsAdminOverride=true` trong approval row + banner đỏ trên detail.
|
||||
|
||||
5. **81 test pass** — Domain WorkflowPolicyTests + PurchaseEvaluationPolicyTests + BudgetPolicyTests đã update cho TraLai entry point. KHÔNG có test cho V2 Service wire (defer khi UAT confirm + có sample data).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
|
||||
> **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-08 (Session 17 — **🎯 5 trạng thái phiếu chốt qua state diagram (Nháp / Đã gửi duyệt / Trả lại / Từ chối / Đã duyệt). Trả lại = Phase RIÊNG (TraLai=98), Drafter sửa+gửi lại chạy từ Cấp 1 Bước 1 (Option A). Schema mới `ApprovalWorkflowsV2` (Mig 22) + Designer V2 max 3 cấp/bước × N NV/cấp + sequential gating + filter NV theo Phòng. 7 commit (`c847dc0` `f6047d5` `2781c7e` `12daa7f` `9712778` `f3bea3c` `ff21120`). 81 test pass.**)
|
||||
**Last updated:** 2026-05-08 (Session 17 wrap-up — **🎯 PE Workflow V2 SCHEMA + Service WIRE end-to-end DONE. 13 commit từ `c847dc0` → `de0f38d`. Mig 22-24 (3 migration mới): ApprovalWorkflowsV2 schema (3 bảng) + Pin ApprovalWorkflowId vào PE + CurrentApprovalLevelOrder tracking. State machine 5 trạng thái (Nháp/Đã gửi duyệt/Trả lại/Từ chối/Đã duyệt) — Trả lại = Phase RIÊNG TraLai=98 (Option A: Drafter sửa+gửi lại chạy LẠI từ đầu, KHÔNG jump-back). PE Service V2 wire — iterate ApprovalWorkflowSteps + Levels + match `actor.Id == ApproverUserId` (1 NV cụ thể, không OR-of-many group). Designer FE max 3 Cấp/Bước × N NV/Cấp + sequential gating + filter NV theo Phòng. Workspace Select pin V2. Inbox V2-aware (ResolveV2InboxIdsAsync). Banner "đến lượt bạn" + button Duyệt disable non-approver + tooltip. Panel 3 render flow workflow thực tế thay phase cards (Bước/Cấp với Status Done/Current/Pending). 81 test pass (+4 từ baseline 77). Test prod cleaned (9 PE + 11 HĐ + 19 Notif xóa). Test user `nv.test@solutions.com.vn`/`TestUser@123456` tạo. Contract V2 wire DEFER session sau.**)
|
||||
|
||||
## 📍 Phase hiện tại: **Phase 9 active — UAT (workflow schema mới Mig 22 đợi user test)** — **58 DB tables (+3 Mig 22 ApprovalWorkflows + Steps + Levels), 22 migrations, ~137 API endpoints (+3 V2), 33 FE pages (+1 Designer V2). 77 unit test pass** (54 Domain + 23 Infra). 41 gotcha. 30 demo user. 6 skill. **5 PE display status** (Bản nháp / Đã gửi duyệt / Trả lại / Đã duyệt / Từ chối). **Phase enum simplified post-Mig 21**: DangSoanThao=1, ChoDuyet=10 (NEW generic), DaDuyet=7, TuChoi=99. Legacy 2-6 + 98 deprecated. **Workflow schemas đang đồng tồn tại**: (1) Mig 21 `WorkflowDefinition` flat — pin với PE/Contract đang chạy. (2) Mig 22 `ApprovalWorkflow` mới (UAT) — Bước (Phòng) > Cấp (NV ApproverUserId). Sau UAT → migrate data + drop legacy.
|
||||
## 📍 Phase hiện tại: **Phase 9 active — UAT V2 testing với user thật** — **58 DB tables (+3 Mig 22 ApprovalWorkflows + Steps + Levels), 24 migrations (+3: Mig 22/23/24), ~140 API endpoints (+5 V2 routes), 33 FE pages (+1 Designer V2). 81 unit test pass** (58 Domain + 23 Infra). 43 gotcha (+2 Session 17: dual schema branch + Step.Order ≠ index). 30 demo user + 1 test user UAT. 6 skill. **5 trạng thái phiếu** (Nháp/Đã gửi duyệt/Trả lại/Từ chối/Đã duyệt). **2 Workflow schemas đồng tồn tại** post-Session 17: (1) Mig 21 `WorkflowDefinition` flat (V1) — pin với PE/Contract cũ + match Dept+PositionLevel. (2) Mig 22-24 `ApprovalWorkflow` (V2) — pin với PE mới + match ApproverUserId 1-1, Steps/Levels group by Order, Bước (Phòng) > Cấp (N NV OR-of-N). Service PE branch theo `ApprovalWorkflowId` set or null. Sau UAT chốt → migrate + drop V1 + Contract V2 wire.
|
||||
|
||||
### 🌐 Production URLs
|
||||
|
||||
@ -61,8 +61,7 @@
|
||||
|
||||
| Ngày | Ai | Task | Commit |
|
||||
|---|---|---|---|
|
||||
| 2026-05-08 | Claude | **🎯 SESSION 17 — State machine 5 trạng thái + Designer V2 iter 2-3 (3 commit `9712778` `f3bea3c` `ff21120` post-Chunk D)** — User UAT Designer V2 + chốt spec: (1) Lock 3 cấp/bước → User feedback "max 3 không bắt buộc" + "mỗi cấp = N NV" → rewrite levelEntries flat list + 3 section cố định C1/C2/C3 + sequential gating + filter NV theo Phòng + no-dup same level + Validator BE Order∈{1,2,3} + HaveSequentialOrders + HaveNoDuplicateApproverInSameLevel. (2) Spec 5 trạng thái phiếu (Nháp/Đã gửi duyệt/Trả lại/Từ chối/Đã duyệt) — Trả lại = Phase RIÊNG (TraLai=98), KHÔNG revert DangSoanThao + KHÔNG jump-back step. Drafter từ TraLai gửi lại = entry point thứ 2, chạy LẠI từ Cấp 1 Bước 1. State diagram chốt với user trước implement. ContractPhase + BudgetPhase thêm TraLai=98 const. PE/Contract/Budget Service Reject branch trỏ → TraLai + clear CurrentWorkflowStepIndex + bỏ ResumeAfterReject smart-reject (RejectedAtStepIndex giữ DB column deprecated cho data cũ). Drafter trình branch: từ DangSoanThao || TraLai → ChoDuyet, init idx=0 (cùng entry point). Policy hardcoded NccOnly/NccWithPlan/Standard/SkipCcm/Default mirror: Reject transitions → TraLai, mirror entry transitions từ TraLai → next phase, ActivePhases +TraLai, FromDefinition mirror logic. Tests: WorkflowPolicyTests Standard_RejectFromCCM → TraLai + new Standard_TraLai_To_DangGopY_Allowed_For_Drafter. PurchaseEvaluationPolicyTests BothPolicies_RejectFromCCM → TraLai + new BothPolicies_TraLai_To_ChoPurchasing_AllowedForDrafter theory. BudgetPolicyTests rename + new ActivePhases All6States + NextPhasesFrom_TraLai + NextPhasesFrom_ChoCEO_Includes_DaDuyet_And_TraLai. **77 → 81 test pass** (+4 TraLai entry point). FE rename "Bản nháp" → "Nháp" + display status mapping update: PeDisplayStatus.BanNhap → Nhap (key + value) + label/color cho TraLai active + ChoDuyet=10 + TraLai=98 const trong types/contracts.ts + types/budget.ts (cả 2 app mirror). BE label maps consistent: ContractExcelExporter / PeWorkflowAdminFeatures / WorkflowAdminFeatures PhaseLabel cập nhật "Nháp" + ChoDuyet/TraLai/TuChoi entries. Verify: dotnet build OK + 81 test pass + npm build × 2 app pass. **Logic Service PE/Contract V2 chưa wire ApprovalWorkflowsV2 (Mig 22 schema) — vẫn pin Mig 21 legacy.** | `9712778` (lock 3) · `f3bea3c` (max 3 + N NV) · `ff21120` (5 trạng thái) |
|
||||
| 2026-05-08 | Claude | **🎯 SESSION 17 — Schema mới `ApprovalWorkflowsV2` (Mig 22) + Menu "Duyệt NCC (Mới)" UAT (3 commit per-chunk)** — User chốt sau Session 16 drastic refactor: "Thấy vẫn không đúng, viết lại toàn bộ chỗ Quy trình Duyệt + thêm 1 Menu chỗ quy trình duyệt nữa là Duyệt NCC (Mới)" với cấu trúc explicit `Mã Quy trình - Tên Quy trình / * Bước 1 - Phòng A / * Cấp 1 - NV X / * Cấp 2 - NV Y`. Schema riêng UAT trước khi drop legacy. Khác Mig 21: mỗi Cấp = 1 NV CỤ THỂ qua `ApproverUserId` (KHÔNG OR-of-many). **Chunk A (`c847dc0`)** Domain `ApprovalWorkflowsV2/ApprovalWorkflow.cs` — 3 entity (ApprovalWorkflow + Step + Level) + enum `ApprovalWorkflowApplicableType` (DuyetNcc/DuyetNccPhuongAn/Contract). EF `ApprovalWorkflowConfiguration.cs` 3 IEntityTypeConfiguration: ToTable + UNIQUE (Code, Version) + FK Cascade Step→Workflow + FK Cascade Level→Step + FK Restrict Department + FK Restrict User. ApplicationDbContext 3 DbSet. **Migration 22** `AddApprovalWorkflowsV2` — 3 CREATE TABLE + 1 UNIQUE + 4 INDEX, applied cả `_Design` + `_Dev` LocalDB. DbInitializer SeedMenusAsync +2 menu row (`ApprovalWorkflowsV2` root dưới System icon Workflow + `AwV2_DuyetNcc` leaf icon FileCheck). MenuKeys.cs +2 const + All array. **Chunk B (`f6047d5`)** Application `ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs` — 3 handler (GetAwAdminOverviewQuery filter optional ApplicableType, CreateAwDefinitionCommand auto-increment Version + deactivate active version cùng type, DeleteAwDefinitionCommand UAT helper unconditional) + Validator child rules NotEmpty Levels + ApproverUserId required. DTO AwDefinition/AwStep/AwLevel + AwTypeSummary. IApplicationDbContext +3 DbSet. Api Controller `ApprovalWorkflowsV2Controller` — GET ?applicableType=N | POST | DELETE/{id}, reuse policy Workflows.Read/Workflows.Create. **Chunk C (`2781c7e`)** FE-Admin `ApprovalWorkflowsV2Page.tsx` (~480 LOC) Designer mới mirror PE pattern nhưng theo schema V2: Overview cards Active+History per ApplicableType, DefinitionCard read-only render Bước → Cấp với approver name + email, Designer dialog Mã/Tên/Mô tả + reorder Step/Level (chevron up/down), Add/Remove Step + Level, Select Phòng + Select NV duyệt, validate level required NV. Auto-assign code QT-DN-V2-001 / QT-DN-PA-V2-001 / QT-HD-V2-001. Layout.tsx resolver +ApprovalWorkflowsV2/AwV2_<TypeCode>. App.tsx +2 route. menuKeys.ts +2 const sync BE. **KHÔNG đụng** PE/Contract Service (legacy WorkflowDefinition vẫn pin). Verify: dotnet build pass + 77 test pass (no regression) + npm build fe-admin pass. Backward compat 100% — schema mới chỉ thêm vào, không sửa cũ. **Next**: User UAT menu mới → confirm UX → Session sau migrate PE/Contract Service iterate ApprovalWorkflowSteps → drop legacy WorkflowDefinition. | `c847dc0` (A) · `f6047d5` (B) · `2781c7e` (C) · (current D docs) |
|
||||
| 2026-05-08 | Claude | **🎯 SESSION 17 WRAP-UP — PE Workflow V2 schema + Service wire end-to-end (13 commit `c847dc0` → `de0f38d`)** — User chốt sau Session 16 "Thấy vẫn không đúng" → viết lại schema riêng + thêm Menu "Duyệt NCC (Mới)" UAT. Cấu trúc rõ ràng: Quy trình > Bước (Phòng) > Cấp (NV cụ thể qua ApproverUserId). 3 chunk lớn: **Schema design + Designer** (Mig 22 — `c847dc0/f6047d5/2781c7e/12daa7f`): 3 entity ApprovalWorkflow/Step/Level + enum ApplicableType (DuyetNcc/DuyetNccPhuongAn/Contract). Designer page `/system/approval-workflows-v2/:typeCode` — iter 1 lock 3 cấp (`9712778`, sai intent) → iter 2 đúng intent max 3 cấp × N NV/cấp + sequential gating C2/C3 disabled khi cấp trước empty + filter NV theo Phòng + no-dup same level (`f3bea3c`). Validator BE Order∈{1,2,3} + HaveSequentialOrders + HaveNoDuplicateApproverInSameLevel. **State machine 5 trạng thái** (`ff21120`): Nháp→Đã gửi duyệt→Đã duyệt (terminal) | Trả lại (Phase riêng TraLai=98, KHÔNG revert DangSoanThao + KHÔNG jump-back) | Từ chối (terminal). Drafter từ TraLai sửa+gửi lại chạy LẠI từ Cấp 1 Bước 1 (Option A user chốt diagram). PE/Contract/Budget Phase enum +TraLai=98 + Policy + Service Reject branch trỏ → TraLai + bỏ smart-reject (RejectedAtStepIndex giữ DB column deprecated). 4 test mới TraLai entry point. **Pin V2 vào PE + Service wire** (Mig 23-24 — `0a40c65/b41484b`): PE.ApprovalWorkflowId Guid? + PE.CurrentApprovalLevelOrder int? + EF FK Restrict. CreatePurchaseEvaluationCommand+Validate ApplicableType match PE.Type. UpdateDraft cho phép sửa Phase=Nháp/TraLai. Workspace Select bắt buộc (filter ApplicableType=type). Service `ApproveV2Async` + `ApproveV1LegacyAsync` branch theo ApprovalWorkflowId set or null: V2 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. Synthetic Policy `ForV2Schema()` cho FE nextPhases (DangSoanThao/TraLai → ChoDuyet/TuChoi; ChoDuyet → ChoDuyet/TraLai/TuChoi). **UX V2-aware** (`d814429/9e63e2d/d250ae4/74745a7/de0f38d`): DTO `CurrentApproval { stepIdx, levelOrder, approvers[] }` + `ApprovalFlow { steps[]: { Order, Name, Dept, Levels[]: { Order, Approvers[], Status:Done/Current/Pending } } }`. Banner emerald "Đến lượt bạn" / 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 V2-aware (`ResolveV2InboxIdsAsync` precompute Set IDs khớp actor.Id ∈ Cấp hiện tại). 2 dropdown filter "Quy trình duyệt" + "Trạng thái" (chỉ ở Duyệt sau user feedback, Danh sách giữ 1 dropdown trạng thái). Panel 3 thay 4 phase cards bằng flow workflow thực tế: Bước (icon ✓/●/○ + dept badge) → Cấp (icon nhỏ + label "đang chờ" / "đã duyệt" + tên NV). Phiếu V1 legacy fallback note. **Test setup** (`ac41d5e`): SQL `clean-transactional-uat.sql` xóa 9 PE + 11 HĐ + Budget + 19 Notif + reset CodeSequences trên prod, giữ master (Users/Suppliers/Projects/Departments/Workflows V1+V2). Tạo test user `nv.test@solutions.com.vn`/`TestUser@123456` (Drafter, Phòng CCM) qua API. **77→81 test pass** (+4 TraLai entry point Domain). FE rename "Bản nháp" → "Nháp" + ChoDuyet=10 + TraLai=98 thêm vào types/contracts.ts + types/budget.ts. **Pending session sau:** Contract V2 wire (mirror PE pattern), Budget V2 (defer xa hơn), phân quyền strict V2 (hiện loose UAT cho mọi authenticated user xem phiếu V2), drop legacy V1 sau khi UAT chốt + cleanup migration drop RejectedAtStepIndex/RejectedFromPhase. | 13 commit (xem `git log --since='2026-05-08'`) |
|
||||
| 2026-05-08 | Claude | **🎯 SESSION 16 — DRASTIC REFACTOR flat workflow Phòng × Cấp (Mig 21, 2 commit Chunk A+B)** — Resume từ Session 15 defer plan. User chốt "bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking". Per memory `feedback_drastic_refactor_scope`: dedicated session với context fresh, scope conservative 2x buffer (~8-10h estimate, actual ~3h). **Chunk A (`dbb0089`)** — Domain enum simplify (DangSoanThao=1, ChoDuyet=10 NEW, DaDuyet=7, TuChoi=99; legacy 2-6 + 98 deprecated giữ cho data cũ). WorkflowStep + DepartmentId Guid? FK Restrict + PositionLevel int? (PE + Contract mirror). PE/Contract entity + CurrentWorkflowStepIndex int? + RejectedAtStepIndex int?. Drop class WorkflowStepInnerStep + nav (PE + Contract). Drop *DepartmentApproval.InnerStepId column. EF Configurations: drop InnerStep config + restore simple unique non-filtered (Mig 19/20 filtered split reverse). DbContext drop DbSet<*WorkflowStepInnerStep> × 2. **Migration 21** `RefactorWorkflowToFlatModel` GỘP: 4 ALTER cols (PE/Contract CurrentStepIndex+RejectedAtStepIndex) + 2 ALTER (WorkflowStep DeptId+PositionLevel) + DROP TABLE x 2 (PEWorkflowStepInnerSteps + WorkflowStepInnerSteps Mig 18+20) + DROP InnerStepId column x 2 (PE+Contract DeptApproval) + DROP filtered indexes x 2 + restore simple unique x 2. PE + Contract Service rewrite TransitionAsync: phase transitions DangSoanThao→ChoDuyet (Drafter trình init idx=0) / ChoDuyet→ChoDuyet (advance idx) / ChoDuyet→DaDuyet/DaPhatHanh (last step done) / ChoDuyet→DangSoanThao (Trả lại save RejectedAtStepIndex) / ChoDuyet→TuChoi (Từ chối khoá vĩnh viễn). Match approver: actor.Dept==step.Dept AND actor.PositionLevel>=step.PositionLevel (OR cùng cấp/dept) OR Approvers.Kind=User match OR Kind=Role match. Admin role bypass policy. Last step done → gen mã HĐ (Contract only). App CQRS WorkflowStepDto + WorkflowStepInput drop InnerStep, add DepartmentId/DepartmentName/PositionLevel (PE + Contract mirror). Tests rewrite: DROP `PeNStageApprovalTests.cs` (6) + `ContractNStageApprovalTests.cs` (6) + `PeTwoStageApprovalTests.cs` (7) — legacy N-stage/2-stage no longer applicable. UPDATE `PeWorkflowAdminTests` signature. **96 → 77 test pass** (-19 legacy). 3-file rule Mig 21 (.cs + Designer + Snapshot) commit đủ. **Chunk B (`88a5be1`)** — FE-Admin Designer rewrite (PeWorkflowsPage + WorkflowsPage): drop InnerStepDto + EditInnerStep types, drop PHASE_OPTIONS auto-assign ChoDuyet=10, StepDto + EditStep + departmentId/positionLevel, copyFromDefinition simplified, Designer step UI rewrite (Tên + Phòng Select + Cấp Select + SLA + Approvers Role/User optional fallback, drop entire InnerSteps sub-section), DefinitionCard view hiển thị badge Phòng emerald + Cấp NV/PP/TP violet, save payload phase=10. types/purchaseEvaluation.ts (fe-admin + fe-user mirror) + ChoDuyet=10 enum + label "Đang duyệt" + color amber. **Chunk C (FE PeWorkflowPanel) SKIP** — existing UI compatible (workflow.nextPhases driven by BE simplified policy), reuse 3-button Trả lại/Từ chối logic Session 14 hoạt động trên ChoDuyet phase tự động. **KHÔNG đụng** Service Notify pattern + Changelog pattern (giữ hành vi Mig 16). Verify: dotnet build pass + Mig 21 LocalDB applied + 77 test pass + npm build × 2 pass. Memory `feedback_drastic_refactor_scope.md` validated: dedicated session approach hoạt động đúng dự đoán. | `dbb0089` (A) · `88a5be1` (B) |
|
||||
| 2026-05-07 | Claude | **🎯 SESSION 15 — Tooltip diagnose "Lưu & Gửi Duyệt" + Plan drastic refactor flat workflow → DEFER** — User UAT live screenshot phiếu PE Bản nháp + báo "Lưu & Gửi Duyệt" KHÔNG hoạt động + suy đoán "trùng ID với phiếu khác". Chẩn đoán: button silent disabled khi `evaluation.workflow.nextPhases` không có forward phase (chỉ TuChoi/TraLai). FE chưa có visual feedback → user không biết. Improvement (commit `835cc7f`): compute `forwardPhase` once + add `submitDisabledReason` string giải thích reason (canEditPhase=false / readOnly / !forwardPhase với hint admin kiểm tra cấu hình quy trình) + button title attribute show reason hover hoặc forward phase label khi enabled + Dialog confirm show forward phase explicit "Sẽ chuyển sang Chờ Purchasing". Mirror fe-admin + fe-user. Build pass cả 2. **"Trùng ID" KHÔNG phải bug FE** — `PurchaseEvaluationWorkspacePage` URL state đúng (`+ Thêm mới` clear `id`, save set new), mỗi PE row unique GUID + MaPhieu. **Tiếp theo plan drastic refactor**: User chốt "bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking" + workflow flat list (Phòng × Cấp × Users[]) thay InnerStep model. Surface 6 chunk plan + start Chunk A: edit Domain entities (Phase enum +ChoDuyet=10, WorkflowStep +DeptId/PositionLevel, drop InnerStep class+nav, PE/Contract +CurrentWorkflowStepIndex/RejectedAtStepIndex, *DeptApproval drop InnerStepId) + EF Configurations (drop InnerStep config + nav, restore simple unique non-filtered) + DbContext drop DbSets — 12 files trong working tree. Realize scope realistic ~8-10h (PolicyRegistry rewrite + 2 Service rewrite + App CQRS + 12 tests rewrite + Designer FE + Migration 21 + Docs) vượt session boundary + risk session context deep ~30 commits. **REVERT working tree** về `835cc7f` clean. Add memory `feedback_drastic_refactor_scope` decision rule: drastic refactor cần dedicated session, ước tính conservative (2x buffer), tránh mid-session big refactor. **Stats unchanged**: 96 test pass, 20 mig, 57 bảng. | `835cc7f` |
|
||||
| 2026-05-07 | Claude | **🎯 SESSION 14 — PE 3-button workflow Duyệt/Trả lại/Từ chối + Task 2 sample seed in-progress** — User chỉ thị thay 2-button approval bằng 3 hành động rõ ràng cho approver: **Duyệt** (forward), **Trả lại** (về DangSoanThao + Drafter sửa, smart reject Mig 16 + clear N-stage rows + Drafter resume jump-back), **Từ chối** (Phase=TuChoi, phiếu khoá vĩnh viễn 17 handler Mig 16 lock edit, Drafter phải tạo phiếu mới). 1 commit (`0d77698`): Domain `PurchaseEvaluationPolicy.cs` NccOnly + NccWithPlan thêm `(X → TuChoi)` transition cho mọi phase trung gian (ChoPurchasing/ChoCCM/ChoDuAn/ChoCEODuyetPA/ChoCEODuyetNCC) với roles của phase. FromDefinition expand: mỗi step (trừ DangSoanThao) thêm (step.Phase → TuChoi) với roles step. Service `PurchaseEvaluationWorkflowService.TransitionAsync` — Reject branch tách 2 case: target=TuChoi giữ nguyên (KHÔNG override + KHÔNG set RejectedFromPhase + KHÔNG clear N-stage); target khác (DangSoanThao) → smart reject (force DangSoanThao + RejectedFromPhase + clear N-stage). FE PeWorkflowPanel (admin + user mirror): render 3 button rõ ràng "✓ Duyệt → X" brand / "← Trả lại (về Drafter sửa)" red / "✗ Hủy / Từ chối" red. Decision logic: target=TuChoi || isSendBack → Reject (2), else Approve (1). Dialog confirm: title rõ + Cancel case warning red "phiếu sẽ bị khoá hoàn toàn" + SendBack case hint amber "Phiếu về DangSoanThao, Drafter sửa rồi trình lại — workflow tự jump tới phase này". Tests: rename `Reject_Sets_RejectedFromPhase_And_Forces_DangSoanThao` → `Reject_To_DangSoanThao_Sets_RejectedFromPhase_TraLai` (target từ TuChoi → DangSoanThao). NEW `Reject_To_TuChoi_Locks_Permanently_No_RejectedFromPhase`. Update `NStage_Reject_Clears_InnerStep_Rows_At_Phase` target → DangSoanThao. **95 → 96 test pass** (+1 Từ chối). **Task 2 sample seed in-progress**: dotnet ef database update applied Mig 9-20 lên LocalDB SolutionErp_Dev (trước đó chỉ Mig 1-8 vì DesignTimeDbContextFactory hardcoded SolutionErp_Design ≠ runtime SolutionErp_Dev — gotcha tooling distinction). API start để DbInitializer auto-seed 30 demo user nhưng exit 255 sớm khi log buffer full → seed dở dang. Defer Task 2 cho session sau (cần guidance: seed manual SQL hoặc manual API run + sample N-stage workflow def + Update PositionLevel cho 30 users existing). | `0d77698` |
|
||||
|
||||
@ -157,6 +157,34 @@ Session log: `2026-04-28-chot-session-4-budget.md`.
|
||||
|
||||
## 📝 Phase 9 — UAT + Ops + carry over (Session 6+ active)
|
||||
|
||||
### ✅ Session 17 done (2026-05-08) — PE Workflow V2 schema + Service wire end-to-end (Mig 22-24, 13 commit `c847dc0` → `de0f38d`)
|
||||
|
||||
User chốt sau Session 16 drastic refactor flat (Mig 21) vẫn chưa đúng intent. Yêu cầu schema riêng + Menu mới "Duyệt NCC (Mới)" — Quy trình > Bước (Phòng) > Cấp (NV cụ thể qua ApproverUserId). State machine 5 trạng thái với Trả lại = Phase RIÊNG (Option A user chốt diagram).
|
||||
|
||||
- [x] **Mig 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. DbInitializer +menu V2. Designer page `/system/approval-workflows-v2/:typeCode` ~480 LOC. (`c847dc0/f6047d5/2781c7e/12daa7f`)
|
||||
|
||||
- [x] **Designer iter 2** đúng intent: max 3 cấp × N NV/cấp + sequential gating C2/C3 disabled khi prev empty + filter NV theo Phòng + no-dup same level. Validator BE Order∈{1,2,3} + HaveSequentialOrders + HaveNoDuplicateApproverInSameLevel. (`9712778` iter 1 sai → `f3bea3c` iter 2 đúng)
|
||||
|
||||
- [x] **State machine 5 trạng thái** Nháp / Đã gửi duyệt / Trả lại / Từ chối / Đã duyệt. TraLai = Phase RIÊNG (98), KHÔNG revert DangSoanThao + KHÔNG jump-back step. Drafter từ TraLai sửa+gửi lại chạy LẠI từ Cấp 1 Bước 1. ContractPhase + BudgetPhase +TraLai. PE/Contract/Budget Policy + Service Reject branch trỏ → TraLai. RejectedAtStepIndex/RejectedFromPhase deprecated (giữ DB column). 4 test mới TraLai entry point. FE rename "Bản nháp" → "Nháp". (`ff21120`)
|
||||
|
||||
- [x] **Mig 23** `AddApprovalWorkflowIdToPurchaseEvaluation` — pin V2 vào PE entity. CreatePurchaseEvaluationCommand +Validate ApplicableType match PE.Type. UpdateDraft cho phép sửa Phase=Nháp/TraLai. Workspace Select bắt buộc workflow lúc create + display "QT-DN-V2-001 v01 — Tên (đang áp dụng)". (`0a40c65`)
|
||||
|
||||
- [x] **Mig 24** `AddCurrentApprovalLevelOrderToPe` + Service V2 wire. PE Service branch theo `ApprovalWorkflowId` set or null: V2 `ApproveV2Async` group Levels by Order = Cấp (OR-of-N approvers cùng cấp), match `actor.Id ∈ ApproverUserId`, advance levelOrder++ → idx++ + reset levelOrder=1 → DaDuyet. V1 `ApproveV1LegacyAsync` giữ logic cũ. Synthetic Policy `ForV2Schema()` cho FE nextPhases. (`b41484b`)
|
||||
|
||||
- [x] **UX V2-aware** disable button + banner. DTO `CurrentApproval` + `ApprovalFlow` (full Steps/Levels Status Done/Current/Pending). Banner emerald "Đến lượt bạn" / amber "Không phải lượt bạn — chỉ {NV X / Y} duyệt được". Button Duyệt forward disabled khi non-approver + tooltip. Trả lại + Từ chối vẫn enabled. Inbox V2-aware `ResolveV2InboxIdsAsync`. 2 dropdown filter "Quy trình" + "Trạng thái" (chỉ ở Duyệt). Panel 3 thay 4 phase cards bằng flow workflow thực tế. (`d814429/9e63e2d/d250ae4/74745a7/de0f38d`)
|
||||
|
||||
- [x] **Test setup**: SQL `clean-transactional-uat.sql` clean prod (9 PE + 11 HĐ + 19 Notif xóa) giữ master via SSH VPS. Test user `nv.test@solutions.com.vn`/`TestUser@123456` (Drafter, CCM) tạo qua API admin. (`ac41d5e`)
|
||||
|
||||
**Stats final Session 17:** 24 mig (+3), 58 DB tables (+3), ~140 endpoints (+5), 33 FE pages (+1 Designer V2), **81 test pass** (+4 TraLai entry point Domain).
|
||||
|
||||
**Defer Session 18+:**
|
||||
- [ ] **Contract V2 wire** (Mig 25) — mirror PE pattern: thêm `Contract.ApprovalWorkflowId` + `CurrentApprovalLevelOrder` + `ContractWorkflowService.ApproveV2Async` + Workspace Select V2 trong ContractCreatePage
|
||||
- [ ] **Phân quyền strict V2** — hiện loose UAT (mọi authenticated user thấy phiếu V2). List chỉ Drafter + approver any-Step + Admin
|
||||
- [ ] **Drop legacy V1** sau khi không còn phiếu pin `WorkflowDefinitionId`: drop tables + cleanup migration drop `RejectedAtStepIndex`/`RejectedFromPhase` columns
|
||||
- [ ] **Admin role bypass** decision prod — option C có audit log "[Admin override]" nếu cần (hiện UAT bypass không log riêng)
|
||||
- [ ] **Test V2 Service wire** (defer khi UAT confirm + có sample data) — Domain test cho ApproveV2Async match logic
|
||||
- [ ] **Budget V2 wire** (defer xa hơn — sau Contract V2)
|
||||
|
||||
### ✅ Session 16 done (2026-05-08) — DRASTIC REFACTOR flat workflow Phòng × Cấp (Mig 21, 2 commit Chunk A+B)
|
||||
|
||||
User chốt drastic refactor: bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking. Workflow flat list (Phòng × Cấp × Approvers). Pin WorkflowDefinitionId. Per memory `feedback_drastic_refactor_scope.md`: dedicated session + conservative buffer.
|
||||
|
||||
@ -0,0 +1,125 @@
|
||||
# 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).
|
||||
@ -715,7 +715,89 @@ CREATE TABLE PurchaseEvaluationDepartmentOpinions (
|
||||
CREATE UNIQUE INDEX IX_PEDeptOpinions_PEId_Kind ON PurchaseEvaluationDepartmentOpinions (PurchaseEvaluationId, Kind);
|
||||
```
|
||||
|
||||
## 14. Liên quan
|
||||
## 14. ApprovalWorkflow V2 schema (Migration 22-24, Session 17 — 3 bảng mới + 2 column)
|
||||
|
||||
Schema riêng song song WorkflowDefinition V1 (Mig 21) — pin per phiếu PE.
|
||||
V1 vẫn giữ cho phiếu cũ; V2 mới là active cho phiếu tạo từ Session 17 trở đi.
|
||||
|
||||
### Core (3 bảng):
|
||||
|
||||
```
|
||||
ApprovalWorkflows
|
||||
├── Id (PK), Code, Version (UNIQUE Code+Version)
|
||||
├── ApplicableType (1=DuyetNcc, 2=DuyetNccPhuongAn, 3=Contract)
|
||||
├── Name, Description, IsActive, ActivatedAt
|
||||
└── (audit) CreatedAt, UpdatedAt, CreatedBy, UpdatedBy
|
||||
|
||||
ApprovalWorkflowSteps (FK Cascade ApprovalWorkflowId, FK Restrict DepartmentId)
|
||||
├── Id (PK), ApprovalWorkflowId, Order (1-based, sort key — KHÔNG phải position)
|
||||
├── Name (vd "Phòng A"), DepartmentId? (hint)
|
||||
└── INDEX (ApprovalWorkflowId, Order) + INDEX DepartmentId
|
||||
|
||||
ApprovalWorkflowLevels (FK Cascade ApprovalWorkflowStepId, FK Restrict ApproverUserId)
|
||||
├── Id (PK), ApprovalWorkflowStepId, Order (1/2/3 trong Step)
|
||||
├── Name? (vd "Cấp 1"), ApproverUserId (1 NV cụ thể)
|
||||
└── INDEX (ApprovalWorkflowStepId, Order) + INDEX ApproverUserId
|
||||
```
|
||||
|
||||
**Convention quan trọng:** nhiều `ApprovalWorkflowLevel` rows cùng `Order` trong cùng Step = **same Cấp với N approvers** (OR-of-N). Ví dụ Cấp 1 có 2 NV: 2 row Level cùng `Order=1` khác `ApproverUserId`.
|
||||
|
||||
### Pin V2 vào PE (Mig 23-24):
|
||||
|
||||
```
|
||||
PurchaseEvaluations (column mới)
|
||||
├── ApprovalWorkflowId Guid? (FK Restrict ApprovalWorkflows) — pin lúc create
|
||||
└── CurrentApprovalLevelOrder int? — track Cấp đang chờ duyệt (1/2/3)
|
||||
```
|
||||
|
||||
Combined với `CurrentWorkflowStepIndex int?` (Mig 21) để track full state V2:
|
||||
- `CurrentWorkflowStepIndex` = 0-based index của Step (sau khi sort by `Order`)
|
||||
- `CurrentApprovalLevelOrder` = `Order` của Cấp đang chờ trong Step đó
|
||||
|
||||
Service iterate: `steps[CurrentStepIndex].Levels.Where(l => l.Order == CurrentLevelOrder)` → list approvers, match `actor.Id ∈ ApproverUserId`.
|
||||
|
||||
### Match approver V2 (khác V1):
|
||||
|
||||
| Schema | Match logic | Approver type |
|
||||
|---|---|---|
|
||||
| V1 (Mig 21) | `actor.Dept == step.Dept && actor.PositionLevel >= step.PositionLevel` OR Approvers Role/User explicit | Group qua Dept+Level |
|
||||
| V2 (Mig 22-24) | `actor.Id == any level.ApproverUserId where level.Order == currentLevelOrder` | NV cụ thể 1-1 |
|
||||
|
||||
### State transitions (V2 + V1 cùng):
|
||||
|
||||
```
|
||||
DangSoanThao ──Drafter trình──► ChoDuyet (idx=0, levelOrder=1 nếu V2)
|
||||
TraLai ──Drafter sửa+gửi lại──► ChoDuyet (idx=0, levelOrder=1) — chạy LẠI từ đầu
|
||||
ChoDuyet ──Approver duyệt cấp──► ChoDuyet (advance levelOrder hoặc idx)
|
||||
──Approver Trả lại──► TraLai (clear pointers)
|
||||
──Approver Từ chối──► TuChoi (terminal khoá)
|
||||
──last step+level done──► DaDuyet (terminal)
|
||||
```
|
||||
|
||||
### Service branch theo schema pin:
|
||||
|
||||
```csharp
|
||||
// PurchaseEvaluationWorkflowService.TransitionAsync (Mig 24 wire)
|
||||
if (evaluation.ApprovalWorkflowId is Guid awId)
|
||||
await ApproveV2Async(...) // iterate ApprovalWorkflowSteps + Levels match ApproverUserId
|
||||
else
|
||||
await ApproveV1LegacyAsync(...) // iterate WorkflowSteps match Dept+PositionLevel
|
||||
```
|
||||
|
||||
### Designer constraints (FE Validator + BE Validator):
|
||||
|
||||
- Tối đa 3 Cấp/Bước (`Order ∈ {1,2,3}`)
|
||||
- Sequential gating: `1` → `1+2` → `1+2+3` (không cho `2` nếu thiếu `1`, không `1+3` nếu thiếu `2`)
|
||||
- No duplicate `(Order, ApproverUserId)` cùng Step (1 NV không thêm 2 lần cùng Cấp)
|
||||
- Mỗi Cấp có N NV (OR-of-N)
|
||||
- Đổi Phòng → clear toàn bộ approvers (NV cũ có thể không thuộc Phòng mới)
|
||||
|
||||
### Pending Session 18+:
|
||||
|
||||
- Contract V2 wire (Mig 25): mirror PE pattern — thêm `Contract.ApprovalWorkflowId` + `CurrentApprovalLevelOrder`
|
||||
- Drop legacy V1: sau khi không còn phiếu pin `WorkflowDefinitionId` → drop `WorkflowDefinitions` + `WorkflowSteps` + `WorkflowStepApprovers` + drop deprecated columns `RejectedAtStepIndex` / `RejectedFromPhase`
|
||||
|
||||
## 15. Liên quan
|
||||
|
||||
- [`database-guide.md`](database-guide.md) — conventions + migration workflow + cheatsheet đầy đủ
|
||||
- [`../architecture.md`](../architecture.md) — layered architecture + data flow
|
||||
|
||||
@ -166,6 +166,47 @@ Nếu đổi policy BE: chỉ cần update `WorkflowPolicies.Standard` hoặc `W
|
||||
|
||||
**Fix:** `IsolationLevel.Serializable` transaction trong `ContractCodeGenerator`. Không skip.
|
||||
|
||||
### 42. Dual schema workflow V1 vs V2 — Service phải branch theo pin field (Session 17)
|
||||
|
||||
**Symptom:** Phiếu PE pin V2 (`ApprovalWorkflowId` set qua workspace Select) nhưng Service vẫn match approver theo schema cũ (`Dept+PositionLevel`). Approver V2 không duyệt được, button Duyệt báo Forbidden.
|
||||
|
||||
**Root cause:** Sau Mig 23-24 entity PE có 2 field workflow pin:
|
||||
- `WorkflowDefinitionId` (Mig 21 V1 legacy) — pin schema flat cũ
|
||||
- `ApprovalWorkflowId` (Mig 23 V2 mới) — pin schema 3-table mới
|
||||
|
||||
Service trước đó chỉ đọc `WorkflowDefinitionId` → bỏ qua V2.
|
||||
|
||||
**Fix (`b41484b`):** `PurchaseEvaluationWorkflowService.TransitionAsync` branch:
|
||||
```csharp
|
||||
if (evaluation.ApprovalWorkflowId is Guid awId)
|
||||
await ApproveV2Async(evaluation, awId, ...); // iterate ApprovalWorkflowSteps + Levels match ApproverUserId
|
||||
else
|
||||
await ApproveV1LegacyAsync(evaluation, ...); // iterate WorkflowSteps match Dept+PositionLevel
|
||||
```
|
||||
|
||||
**Pattern reusable** khi wire schema mới song song schema cũ (Contract V2 sắp tới): pin field flag để rẽ logic, KHÔNG drop legacy ngay (giữ backward compat phiếu cũ).
|
||||
|
||||
### 43. Step.Order ≠ index 0-based — không thể EF query trực tiếp (Session 17)
|
||||
|
||||
**Symptom:** Implement `ResolveV2InboxIdsAsync` (V2-aware Inbox) bằng EF query thẳng:
|
||||
```csharp
|
||||
.Where(s => s.Order == e.CurrentWorkflowStepIndex.Value + 1) // FAIL — Step.Order là logical, không phải position
|
||||
```
|
||||
Logic sai: `WorkflowStep.Order` không phải position 0-based mà là số sort thứ tự (vd 5, 10, 20). `Steps.OrderBy(s => s.Order).ToList()[idx]` mới đúng.
|
||||
|
||||
**Fix:** Precompute candidates EF query → in-memory sort by Order → array index access:
|
||||
```csharp
|
||||
var candidates = await db.PE.Where(...).ToListAsync(ct); // Step 1: EF lấy phiếu V2 pending
|
||||
var workflows = await db.AW.Include(...).ToDictionaryAsync(w => w.Id, ct);
|
||||
foreach (var c in candidates) {
|
||||
var steps = wf.Steps.OrderBy(s => s.Order).ToList(); // Step 2: in-memory sort
|
||||
var step = steps[c.CurrentWorkflowStepIndex.Value]; // Step 3: array index
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Trade-off: scalable đến vài trăm phiếu pending, không ngon cho >10k. Optimize sau nếu cần.
|
||||
|
||||
## Permission matrix
|
||||
|
||||
### 23. Permission update không real-time
|
||||
|
||||
Reference in New Issue
Block a user