[CLAUDE] FE-Admin+Docs: Contract workflow N-stage Designer mirror PE + Docs (Chunk F)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m3s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m3s
FE Admin WorkflowsPage Designer extend mirror PeWorkflowsPage: - Type InnerStepDto + extend StepDto +innerSteps - Type EditInnerStep + extend EditStep +innerSteps - copyFromDefinition include innerSteps map - Default new step +innerSteps:[] - departmentsList useQuery - Save mutation payload include innerSteps Order asc - UI sub-section "Cấp duyệt nhỏ trong phòng" drag-list per step card với Phòng × Cấp + required checkbox + button "+ Thêm cấp duyệt" emerald - Empty state hint fallback 2-cấp legacy KHÔNG đụng fe-user — WorkflowsPage admin-only. Reuse PositionLevel const + Label maps từ Session 12 types/users.ts. Docs: - STATUS.md Last updated + Phase summary (19→20 mig, 89→95 test, 56→57 bảng) + 1 row Recently Done Session 13 (KEEP narrative cũ) - HANDOFF.md TL;DR Session 13 prepend + 7 cảnh báo Session 14+ - migration-todos.md Phase 9 + Session 13 block 5 chunk - Session log NEW `2026-05-07-2400-n-stage-contract-mirror.md` đầy đủ rationale + per-chunk + bug log Defer cron audit 2026-06-01: schema-diagram §17 Mig 20, skill ef-core-migration row, skill contract-workflow N-stage cross-ref. 🎉 SESSION 13 COMPLETE: Mirror N-stage Contract module (Mig 20). 5 commit per-chunk + skip Chunk E auto-bind. Total 95 test pass. Backward compat 100% với 2-stage Mig 16 legacy. Pending Task 4: Wire BE TraLai PE transition + Task 2: Sample data seed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -157,6 +157,24 @@ Session log: `2026-04-28-chot-session-4-budget.md`.
|
||||
|
||||
## 📝 Phase 9 — UAT + Ops + carry over (Session 6+ active)
|
||||
|
||||
### ✅ Session 13 done (2026-05-07) — Mirror N-stage Contract (Mig 20, 5 commit per-chunk + skip Chunk E auto-bind)
|
||||
|
||||
User chỉ thị mirror N-stage từ PE sang Contract. Budget defer (cần migration `AddBudgetVersionedWorkflow` trước — hardcoded `BudgetPolicy.Default` chưa có WorkflowDefinition entity). Pattern reusable đầy đủ từ PE.
|
||||
|
||||
- [x] **Chunk A (`951ffa3`)** Domain entity `WorkflowStepInnerStep` (Domain/Contracts/) + nav WorkflowStep.InnerSteps + ALTER ContractDeptApproval.InnerStepId + EF config FK Cascade Step / Restrict Dept+InnerStep + **Migration 20** `AddContractWorkflowInnerStepsAndAlterDeptApprovalUnique` GỘP 1 (CREATE TABLE WorkflowStepInnerSteps + ALTER InnerStepId + DropIndex old + Recreate filtered legacy `WHERE InnerStepId IS NULL` + new filtered N-stage `WHERE InnerStepId IS NOT NULL`).
|
||||
- [x] **Chunk B (`04cf2a0`)** Application CQRS DTO mirror PE Chunk B — `WorkflowStepInnerStepDto` + extend `WorkflowStepDto` + `CreateWorkflowStepInnerStepInput` + extend `CreateWorkflowStepInput` (default null backward compat) + Validator child rules + Handler atomic batch insert.
|
||||
- [x] **Chunk C (`e247b67`)** ContractWorkflowService refactor mirror PE — load InnerSteps eager + reject branch clear N-stage rows + dept block split hasInnerSteps→N-stage logic / else→legacy 2-stage. N-stage flow giống PE: match firstPending Order asc + (exact level OR canBypass + level≥), exact upsert / bypass batch upsert, recheck stillPending → BLOCK.
|
||||
- [x] **Chunk D (`7c0772a`)** ContractNStageApprovalTests 6 test mirror PE pattern + helper SeedWorkflowDefinitionAsync 2 step adjacent (DangGopY + DangDamPhan) + SeedContractAsync với Project + Supplier + FakeChangelogService + FakeContractCodeGenerator stubs. Bug fix legacy fallback test: switched phase pair sang DangKiemTraCCM → DangTrinhKy + role CostControl khớp Standard.Transitions. **89→95 test pass**.
|
||||
- [x] **Chunk E SKIP** — WorkflowsController auto-bind `[FromBody] CreateWorkflowDefinitionCommand` record qua JSON, no code change cần.
|
||||
- [x] **Chunk F (current)** FE WorkflowsPage Designer extend mirror PeWorkflowsPage Chunk F: InnerStepDto + EditInnerStep types + copyFromDefinition include + departmentsList query + sub-section "Cấp duyệt nhỏ trong phòng" drag-list + button "+ Thêm cấp duyệt" emerald + payload include Order asc. Empty state hint fallback 2-cấp legacy. KHÔNG đụng fe-user (admin-only).
|
||||
|
||||
**Backward compat 100%**: workflow Contract no InnerSteps configured → service fallback legacy 2-stage Mig 16. Data legacy InnerStepId=null vẫn enforce unique cũ qua filtered index.
|
||||
|
||||
**Defer Session 14+:**
|
||||
- [ ] **Budget N-stage** — cần migration `AddBudgetVersionedWorkflow` trước (4 bảng + ALTER Budget.WorkflowDefinitionId), sau đó migration `AddBudgetWorkflowInnerSteps`. User quyết riêng (feature mở rộng module lớn).
|
||||
- [ ] schema-diagram.md §17 Mig 20 update — defer cron audit 2026-06-01.
|
||||
- [ ] Skill ef-core-migration row Mig 20 — defer cron audit.
|
||||
|
||||
### ✅ Session 12 done (2026-05-07) — N-stage workflow approval (Mig 18+19, 6 commit per-chunk, PE-only)
|
||||
|
||||
User yêu cầu: workflow level cha (= phase) cấu hình được level con (Phòng × Cấp NV/PP/TP) sequential, mỗi cấp = 1 inner step duyệt riêng, có bypass cùng dept. 6 spec defaults chốt + 6 chunk per-commit.
|
||||
|
||||
@ -0,0 +1,201 @@
|
||||
# Session 2026-05-07 (S13) — Mirror N-stage Contract (PE → HĐ)
|
||||
|
||||
**Dev:** Claude
|
||||
**Duration:** ~2h
|
||||
**Base commit:** `5e5042d` (sau Session 12 N-stage PE)
|
||||
**Final commit:** (current Chunk F)
|
||||
**Total commits:** 5 per-chunk (Chunk E skipped)
|
||||
|
||||
## Bối cảnh
|
||||
|
||||
Sau Session 12 đóng N-stage cho PE module (Mig 18+19). User confirm mirror sang Contract với caveat Budget defer (Budget chưa có versioned WorkflowDefinition entity — hardcoded `BudgetPolicy.Default`, cần migration `AddBudgetVersionedWorkflow` trước).
|
||||
|
||||
Pattern Contract N-stage 100% mirror PE Mig 18 logic — reusable hoàn toàn vì Contract đã có versioned WF từ Mig 8.
|
||||
|
||||
## 5 chunk per-commit (Chunk E skipped)
|
||||
|
||||
### Chunk A — Domain + Migration 20 (`951ffa3`)
|
||||
|
||||
**Files mới (1):**
|
||||
- (Cùng file `Domain/Contracts/WorkflowDefinition.cs` — class `WorkflowStepInnerStep` thêm cuối file).
|
||||
|
||||
**Files edit (3):**
|
||||
- `Domain/Contracts/WorkflowDefinition.cs` — `using SolutionErp.Domain.Identity` (PositionLevel reuse Session 12) + class WorkflowStep nav `+List<InnerSteps>` + class mới `WorkflowStepInnerStep` (Order, DepartmentId, PositionLevel, Name, SlaDays, IsRequired).
|
||||
- `Domain/Contracts/ContractDepartmentApproval.cs` — `+public Guid? InnerStepId { get; set; }`.
|
||||
|
||||
**Infra edit (3):**
|
||||
- `Infrastructure/Persistence/Configurations/WorkflowDefinitionConfiguration.cs` — config InnerStep table (Cascade Step, Restrict Department, IX (StepId, Order) + IX DeptId).
|
||||
- `Infrastructure/Persistence/Configurations/DepartmentApprovalsConfiguration.cs` — ContractDeptApproval section: Drop UNIQUE old → 2 filtered (legacy `WHERE InnerStepId IS NULL` + N-stage `WHERE InnerStepId IS NOT NULL`) + IX InnerStepId + FK Restrict.
|
||||
- `Infrastructure/Persistence/ApplicationDbContext.cs` — `DbSet<WorkflowStepInnerStep>`.
|
||||
|
||||
**Migration 20** `AddContractWorkflowInnerStepsAndAlterDeptApprovalUnique` GỘP 1 (thay vì tách 2 như Mig 18+19 cho PE — combined CREATE TABLE + ALTER + filtered unique alter trong cùng migration cho Contract):
|
||||
- DropIndex UX_ContractDeptApprovals_Contract_Phase_Dept_Stage
|
||||
- AddColumn InnerStepId nullable
|
||||
- CreateTable WorkflowStepInnerSteps + 2 FK (Cascade Step / Restrict Dept)
|
||||
- CreateIndex IX_InnerStepId
|
||||
- CreateIndex 2 filtered unique (legacy + N-stage)
|
||||
- 2 IX cho InnerSteps (StepId+Order, DeptId)
|
||||
- AddForeignKey ContractDeptApproval → InnerStep Restrict
|
||||
|
||||
3-file rule: Migration .cs + Designer + Snapshot.
|
||||
|
||||
**Verify:** dotnet build pass, ef database update LocalDB applied, 89 test pass (no regression).
|
||||
|
||||
### Chunk B — Application CQRS DTO (`04cf2a0`)
|
||||
|
||||
**`Application/Contracts/WorkflowAdminFeatures.cs`:**
|
||||
- record `WorkflowStepInnerStepDto` (Id, Order, DepartmentId, DepartmentName, PositionLevel, Name, SlaDays, IsRequired) — mirror PE Mig 18.
|
||||
- record `WorkflowStepDto` extend `+List<WorkflowStepInnerStepDto> InnerSteps`.
|
||||
- `GetWorkflowAdminOverviewQueryHandler` — Include InnerSteps OrderBy Order + resolve DeptNames cho display.
|
||||
- record `CreateWorkflowStepInnerStepInput` (Order, DeptId, PositionLevel, Name, SlaDays, IsRequired).
|
||||
- record `CreateWorkflowStepInput` extend `+List<...InnerSteps>? = null` (default null cho backward compat existing test code positional `new(...)`).
|
||||
- Validator child rules cho InnerSteps (Order ≥1, DeptId not empty, PositionLevel 1-3, SlaDays ≥0).
|
||||
- `CreateWorkflowDefinitionCommandHandler` — convert InnerSteps khi build entity (atomic batch insert qua nav collection).
|
||||
|
||||
**Verify:** dotnet build 0 error, 89 test pass.
|
||||
|
||||
### Chunk C — Service N-stage logic (`e247b67`)
|
||||
|
||||
**`Infrastructure/Services/ContractWorkflowService.cs` refactor TransitionAsync:**
|
||||
1. **Reject branch** thêm: clear N-stage approval rows tại `fromPhase` (resume sẽ approve lại từ inner đầu).
|
||||
2. **Load policy** với `.ThenInclude(s => s.InnerSteps.OrderBy(i => i.Order))` eager + assign `definition` outer scope.
|
||||
3. **Department approval block split:**
|
||||
```
|
||||
if (decision==Approve && targetPhase != Stao && targetPhase != TuChoi
|
||||
&& !isResumingAfterReject && !isAdmin && !isSystem
|
||||
&& actorUserId is Guid actorUid)
|
||||
{
|
||||
var step = def?.Steps.FirstOrDefault(s => s.Phase == fromPhase);
|
||||
var hasInnerSteps = step?.InnerSteps.Count > 0;
|
||||
if (hasInnerSteps) { /* N-stage logic */ }
|
||||
else if (actor.DeptId is Guid deptId) { /* legacy 2-stage Mig 16 */ }
|
||||
}
|
||||
```
|
||||
4. **N-stage logic** mirror PE pattern:
|
||||
- Yêu cầu actor có DeptId + PositionLevel (else throw 403).
|
||||
- Load existing approvals tại fromPhase với InnerStepId IN [innerIds] → compute doneInnerIds.
|
||||
- firstPending = pendingRequired[0].
|
||||
- Match: actorDept == firstPending.Dept AND (actorPos == firstPending.Level OR canBypass + actorPos ≥ firstPending.Level). Mismatch → 403.
|
||||
- Exact match: 1 row upsert (Stage=Confirm, InnerStepId=firstPending, IsBypassed=false).
|
||||
- Bypass: batch upsert tất cả inners cùng dept actor có level từ firstPending.Level đến actorPos (IsBypassed=true cho cấp dưới skip).
|
||||
- Recheck stillPending → BLOCK + log Approval/Changelog "duyệt cấp X (còn Y pending)" + return early.
|
||||
- All done → fall through phase transition.
|
||||
5. **Legacy fallback** (else branch) — giữ nguyên logic 2-stage Mig 16 với InnerStepId=null filter.
|
||||
|
||||
**Verify:** dotnet build 0 error, 89 test pass (legacy backward compat OK).
|
||||
|
||||
### Chunk D — Tests N-stage Contract 6 test (`7c0772a`)
|
||||
|
||||
**`tests/.../Services/ContractNStageApprovalTests.cs` (NEW, 6 test):**
|
||||
|
||||
| # | Test | Cover |
|
||||
|---|---|---|
|
||||
| 1 | NStage_FirstInner_NV_Approve_Blocks_Phase_Transition | NV cấp 1 → 1 row InnerStepId set, phase chưa đổi |
|
||||
| 2 | NStage_All_3_Levels_Sequential_Pass_Allow_Phase_Transition | NV → PP → TP → 3 rows + phase chuyển |
|
||||
| 3 | NStage_TP_Bypass_Skips_Lower_Levels_Same_Dept | TP+canBypass → 3 rows (NV+PP IsBypassed, TP exact) |
|
||||
| 4 | NStage_Wrong_Department_Throws_Forbidden | Actor dept khác → ForbiddenException |
|
||||
| 5 | NStage_Reject_Clears_InnerStep_Rows_At_Phase | Admin reject → DangSoanThao + clear N-stage rows |
|
||||
| 6 | LegacyFallback_NoInnerSteps_Uses_2Stage_Logic | Contract no pinned def → fallback 2-stage Stage=Review |
|
||||
|
||||
**Helper `SeedWorkflowDefinitionAsync`** tạo definition với 2 step adjacent (DangGopY có inner steps + DangDamPhan next, no inner) — đủ cho FromDefinition build transition policy guard pass actor role Procurement.
|
||||
|
||||
**Helper `SeedContractAsync`** tạo Contract với Project + Supplier seed cho FK constraints.
|
||||
|
||||
**Stubs `FakeChangelogService` + `FakeContractCodeGenerator`** — no-op cho tests không cần verify changelog/codegen path.
|
||||
|
||||
**Bug fix:** Legacy fallback test ban đầu fail (Standard policy DangGopY → DangDamPhan chỉ cho [Drafter, DeptManager], không Procurement) → switched phase pair sang DangKiemTraCCM → DangTrinhKy + role CostControl khớp Standard.Transitions. NV.CCM không có DeptManager role → Stage=Review block đúng pattern.
|
||||
|
||||
**Verify:** 89 → **95 test pass** (54 Domain + 41 Infra: 17 codegen + 6 PE WF versioning + 6 PE 2-stage + 6 PE N-stage + 6 Contract N-stage).
|
||||
|
||||
### Chunk E SKIP — API auto-bind
|
||||
|
||||
WorkflowsController.cs Create endpoint dùng `[FromBody] CreateWorkflowDefinitionCommand`. Record positional với InnerSteps (default null) → JSON body bind tự động. KHÔNG cần code change.
|
||||
|
||||
### Chunk F — FE Designer + Docs (current)
|
||||
|
||||
**`fe-admin/src/types/users.ts`:** đã có PositionLevel const + Label/Short maps từ Session 12 → reuse.
|
||||
|
||||
**`fe-admin/src/pages/system/WorkflowsPage.tsx`:**
|
||||
- Type `InnerStepDto` + extend `StepDto` `+innerSteps: InnerStepDto[]` mirror PE.
|
||||
- Type `EditInnerStep` + extend `EditStep` `+innerSteps: EditInnerStep[]`.
|
||||
- `copyFromDefinition` include innerSteps map.
|
||||
- Default new step `+innerSteps: []`.
|
||||
- `departmentsList` useQuery fetch /departments.
|
||||
- Save mutation payload: `s.innerSteps.map((ii, ix) => ({ order: ix+1, departmentId, positionLevel, name, slaDays, isRequired }))`.
|
||||
- UI sub-section "Cấp duyệt nhỏ trong phòng (sequential — Order asc)" trong từng step card sau "Người duyệt" section, drag-list rows { Order badge emerald + Select Phòng + Select Cấp + checkbox required + Trash button } + button "+ Thêm cấp duyệt" emerald (disabled khi departmentsList empty).
|
||||
- Empty state hint fallback 2-cấp legacy.
|
||||
|
||||
**KHÔNG đụng fe-user** — WorkflowsPage admin-only.
|
||||
|
||||
**Verify:** npm run build fe-admin pass (✓ built), 0 TS error.
|
||||
|
||||
## E2E verified
|
||||
|
||||
- ✅ `dotnet test SolutionErp.slnx` 95/95 pass (54 Domain + 41 Infra)
|
||||
- ✅ `dotnet build SolutionErp.slnx` 0 error, 2 pre-existing warning
|
||||
- ✅ `dotnet ef database update` Mig 20 LocalDB applied OK
|
||||
- ✅ `npm run build` fe-admin pass — no TS error
|
||||
- 🔄 Manual UAT — defer cho user thử workflow Designer Contract Create version mới có Inner Steps
|
||||
|
||||
## Bug + Fix log
|
||||
|
||||
| # | Issue | Fix | Commit |
|
||||
|---|---|---|---|
|
||||
| 1 | Test legacy fallback `Standard policy DangGopY → DangDamPhan không cho Procurement` | Switch phase pair sang DangKiemTraCCM → DangTrinhKy + role CostControl khớp | `7c0772a` (Chunk D inline) |
|
||||
|
||||
## Docs updates
|
||||
|
||||
- ✅ STATUS.md — Last updated + Phase summary count + 1 row Recently Done Session 13 (KEEP narrative cũ)
|
||||
- ✅ HANDOFF.md — TL;DR Session 13 prepend + 7 cảnh báo Session 14+ + giữ Session 12 narrative
|
||||
- ✅ migration-todos.md — Phase 9 + Session 13 block 5 chunk + 3 defer task
|
||||
- ✅ Session log (file này)
|
||||
- ⏸️ schema-diagram.md §17 Mig 20 — defer cron audit 2026-06-01
|
||||
- ⏸️ Skill ef-core-migration row Mig 20 — defer cron audit
|
||||
- ⏸️ Skill contract-workflow N-stage cross-ref — defer cron audit
|
||||
|
||||
## Stats cumulative (sau Session 13)
|
||||
|
||||
| | Trước S13 | Sau S13 | Diff |
|
||||
|---|---:|---:|---:|
|
||||
| BE LOC | ~15300 | ~15700 | +400 |
|
||||
| API endpoints | ~134 | ~134 | 0 (auto-bind) |
|
||||
| Migrations | 19 | **20** | +1 (Mig 20 gộp) |
|
||||
| DB tables | 56 | **57** | +1 (WorkflowStepInnerSteps) |
|
||||
| DB columns mới | — | +1 | ContractDeptApproval.InnerStepId |
|
||||
| FE pages | 32 | 32 | 0 (extend existing) |
|
||||
| Tests | 89 | **95** | +6 (Contract N-stage) |
|
||||
| Docs | ~56 | ~57 | +1 (session log này) |
|
||||
| Commits S13 | — | **+5** | A→F per-chunk, E skipped |
|
||||
|
||||
## Plan organization sau session 13
|
||||
|
||||
```
|
||||
Plan cha: Phase 9 active — UAT
|
||||
├── Plan con A: Hard blockers (chờ user/ops) — 6 task (giữ nguyên)
|
||||
├── ...
|
||||
├── Plan con S12 ✅ DONE: N-stage PE (Mig 18+19, 6 chunk)
|
||||
├── Plan con S13 ✅ DONE: N-stage Contract (Mig 20, 5 chunk + skip E)
|
||||
│ ├── ✅ A: Domain + Mig 20
|
||||
│ ├── ✅ B: App CQRS
|
||||
│ ├── ✅ C: Service logic
|
||||
│ ├── ✅ D: 6 test
|
||||
│ ├── ⊘ E: API auto-bind skip
|
||||
│ └── ✅ F: FE Designer + Docs
|
||||
├── Plan con kế (Task 4): Wire BE TraLai PE — pending
|
||||
├── Plan con kế (Task 2): Sample data N-stage seed — pending
|
||||
└── Plan con Defer S13+:
|
||||
├── Budget N-stage (cần versioned WF migration trước)
|
||||
├── schema-diagram §17 Mig 20
|
||||
└── Skill ef-core-migration Mig 20 row
|
||||
```
|
||||
|
||||
## Handoff
|
||||
|
||||
UAT iteration mode. Contract N-stage giờ đã ready. Workflow N-stage Contract backward compat 100% với data legacy 2-stage Mig 16.
|
||||
|
||||
**Cron audit kế:** 2026-06-01 (~25 ngày).
|
||||
|
||||
**Tiếp theo theo user order:**
|
||||
1. ✅ Task 3 (3a Contract done, 3b Budget defer)
|
||||
2. ⏸ Task 4 (Wire BE TraLai PE)
|
||||
3. ⏸ Task 2 (Sample data seed)
|
||||
Reference in New Issue
Block a user