[CLAUDE] Phase3: Workflow MVP — 9-phase state machine + code gen + FE Inbox/Detail

Backend Contracts domain (5 entities):
- Contract aggregate: Phase (9 enum), SlaDeadline, MaHopDong, BypassProcurementAndCCM, DraftData, SlaWarningSent
- ContractApproval: FromPhase → ToPhase, ApproverUserId (null = system auto-approve), Decision, Comment
- ContractComment: thread theo Phase current
- ContractAttachment: FileName + StoragePath + Purpose (DraftExport/ScannedSigned/SealedCopy)
- ContractCodeSequence: Prefix PK + LastSeq — atomic gen

EF configs:
- Unique MaHopDong filtered [MaHopDong] IS NOT NULL
- Indexes: Phase+IsDeleted, SupplierId, ProjectId, SlaDeadline, ContractId+ApprovedAt, ContractId+CreatedAt
- Cascade delete Approvals/Comments/Attachments khi Contract xoa
- Query filter IsDeleted
- Migration AddContractsWorkflow (DB 19 tables)

Workflow service:
- IContractWorkflowService.TransitionAsync:
  - Adjacency check qua Transitions Dict<(from,to), roles[]> (12 transitions)
  - Role guard: user phai co role ∈ allowed
  - Admin bypass (role Admin pass moi check)
  - System bypass (userId=null + Decision=AutoApprove → cho SLA job sau nay)
  - Bypass CCM: BypassProcurementAndCCM=true cho phep DangInKy → DangTrinhKy skip phase 6
  - Gen ma HD khi chuyen DangDongDau (idempotent — khong gen lai neu da co)
  - Reset SlaDeadline = UtcNow + PhaseSla
  - Insert ContractApproval row

Code generator (RG-001):
- 7 format theo ContractType: HDTP / HDGK / NCC / HDDV / MB + 2 framework (year prefix)
- BeginTransactionAsync(Serializable) + ContractCodeSequences UPSERT → atomic
- Idempotent: neu MaHopDong da co thi skip

CQRS (8 feature, ContractFeatures.cs):
- CreateContractCommand + Validator + Handler (set SlaDeadline = +7d)
- UpdateContractDraftCommand (chi khi Phase=DangSoanThao)
- TransitionContractCommand (delegate → WorkflowService)
- AddCommentCommand (phase = hien tai)
- ListContractsQuery (PagedResult + filter phase/supplier/project/search)
- GetMyInboxQuery (map Phase → actor roles, filter theo role user)
- GetContractQuery (detail + approvals + comments + attachments + resolve user names)
- DeleteContractCommand (soft, block > DangInKy)

Controller:
- ContractsController 8 endpoint: GET list/inbox/detail, POST create/transition/comment, PUT update, DELETE

Frontend fe-admin (2 page moi):
- types/contracts.ts: ContractPhase const + Label + Color maps + types
- components/PhaseBadge.tsx
- pages/contracts/ContractsListPage.tsx: filter phase + search + click → detail
- pages/contracts/ContractDetailPage.tsx: 2-col layout (info+comments | timeline), action dialog select target phase + comment

Frontend fe-user (4 page moi + 14 file shared):
- cp 14 file shared tu fe-admin (menuKeys, types/*, DataTable, PhaseBadge, Dialog, Textarea, Select, apiError, usePermission, PermissionGuard)
- AuthContext update: load menu tu /menus/me + cache
- Layout: menu fixed 3 muc + user info + roles display
- InboxPage: list HD cho role user xu ly (sort theo SLA)
- ContractCreatePage: form chon loai + template + NCC + du an + gia tri + bypass CDT
- ContractDetailPage: duplicate fe-admin pattern (convention)
- MyContractsPage: list HD cua toi
- App.tsx: 4 route moi

E2E verified:
- Setup Supplier + Project
- POST /contracts → 201 + phase=2
- POST /contracts/{id}/transitions x7 → di het 9 phase
- Final: MaHopDong = "FLOCK 01/HĐGK/SOL&PVL2026/01" dung format RG-001
- Approvals: 7 rows audit day du

Docs:
- .claude/skills/contract-workflow/SKILL.md: placeholder → full spec voi state machine, SLA table, role matrix, 7 code format, code pointers, API, E2E workflow, pitfalls
- docs/changelog/sessions/2026-04-21-1330-phase3-workflow.md: session log
- docs/STATUS.md: Phase 3 MVP done, next Phase 4
- docs/HANDOFF.md: update phase status + file tree + commit log + testing points
- docs/changelog/migration-todos.md: tick Phase 3 MVP items + add iteration 2 list

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-04-21 12:26:09 +07:00
parent 5113e4c771
commit 7e957a7654
49 changed files with 4490 additions and 156 deletions

View File

@ -1,6 +1,6 @@
# HANDOFF — Brief 5 phút cho session tiếp theo
**Last updated:** 2026-04-21 12:00 (cuối Phase 2 MVP)
**Last updated:** 2026-04-21 13:30 (cuối Phase 3 MVP)
## Ở đâu rồi?
@ -9,10 +9,11 @@
| 0 Draft | ✅ Done |
| 1 Alpha Core foundation | ✅ Done |
| 1 Alpha Core đợt 2 (CRUD + Permission) | ✅ Done |
| **2 Form Engine MVP** | ✅ Done |
| 2 Form Engine MVP | ✅ Done |
| 2 Form Engine iteration 2 | 📝 Optional |
| 3 Workflow (9 phase state machine) | 📋 Next |
| 4 Report + Polish | 📋 Queue |
| **3 Workflow MVP (9 phase + code gen)** | ✅ Done |
| 3 Workflow iteration 2 (SLA job + notify + attachment) | 📝 Optional |
| 4 Report + Polish | 📋 Next |
| 5 Production (CI/CD IIS) | 📋 Queue |
## Run nhanh
@ -30,43 +31,48 @@ cd fe-user && npm run dev # → http://localhost:8080
Login: `admin@solutionerp.local` / `Admin@123456`
Điểm cần test ngay:
- `/forms` → render FO-002.05 → download .docx (Phase 2 MVP)
- `/system/permissions` chọn role → tick matrix
- `/master/suppliers|projects|departments` → CRUD
Điểm cần test ngay (Phase 3 MVP):
- **fe-user `/contracts/new`** → tạo HĐ draft (Phase 2 DangSoanThao, SLA +7d)
- **fe-user `/inbox`** → xem HĐ ch role mình xử lý
- **`/contracts/{id}`** (cả 2 FE) → click "Duyệt → tiếp" chạy state machine. Đến phase 8 DangDongDau → xem `MaHopDong` tự gen theo RG-001
- **`/forms`** (admin) → render template .docx với JSON data
- **`/system/permissions`** → ma trận Role × MenuKey
- **`/master/suppliers|projects|departments`** → CRUD
## Cần làm kế tiếp (ưu tiên)
### A. Phase 3Workflow (item lớn, ~3 tuần work)
### A. Phase 4Report + Polish (tuần 10-11)
**Đọc trước:**
1. [`workflow-contract.md`](workflow-contract.md) — spec 9 phase + role matrix
2. [`flows/contract-approval-flow.md`](flows/contract-approval-flow.md) — sequence diagram
3. [`flows/sla-expiry-flow.md`](flows/sla-expiry-flow.md) — BackgroundService auto-approve
4. [`forms-spec.md#RG-001`](forms-spec.md) — format mã HĐ
- Dashboard admin: HĐ theo phase, top NCC, top dự án, tổng giá trị tháng
- Excel export list HĐ (dùng ClosedXML đã có)
- Report quá hạn SLA theo phase/role
- UX polish: skeleton loader, empty state có action, error boundary recovery
- Accessibility: keyboard nav, aria labels
- Index DB hot query (SupplierId, ProjectId, Phase combo)
- User guide docs
**Deliverable chính:**
- Entity: `Contract` (Phase, SlaDeadline, BypassProcurementAndCCM, DraftData) + `ContractApproval` + `ContractComment` + `ContractAttachment`
- `IContractWorkflowService.TransitionAsync()` — state guard (9 phase adjacency) + role guard
- `IContractCodeGenerator` (implement RG-001) với transaction SERIALIZABLE + `ContractCodeSequences` table
- `SlaExpiryJob` BackgroundService — auto-approve HĐ quá hạn mỗi 15 phút
- `INotificationService` — email (MailKit) + in-app
- API `POST /api/contracts/{id}/transitions`
- FE `/inbox` (list HĐ chờ tôi xử lý theo role × phase)
- FE `/contracts/{id}` detail — timeline 9 phase, approval panel, comment thread, attachment upload
### B. Phase 3 iteration 2 (polish workflow)
### B. Phase 2 iteration 2 (nếu user muốn polish Form Engine)
- [ ] `SlaExpiryJob` BackgroundService auto-approve (xem `flows/sla-expiry-flow.md`)
- [ ] Email notification (MailKit) — pick up phase + SMTP config
- [ ] In-app notification (SignalR + badge counter)
- [ ] Upload attachment endpoint + FE multipart (store vào `wwwroot/uploads/contracts/{id}/`)
- [ ] RowVersion optimistic concurrency (2 user race → 409)
- [ ] Render HĐ docx lúc tạo (merge TemplateId + DraftData + ContractClause appendix)
- Convert 3 file `.doc` (retry Word COM với timeout OR LibreOffice)
- Field spec JSON → dynamic form builder
- `{{#loop}}...{{/loop}}` support
- PDF convert
- FE upload template UI
### C. Phase 2 iteration 2 (form engine polish)
### C. Quick wins (không block phase)
- Convert 3 file `.doc` qua Word COM `DisplayAlerts=0` + timeout, hoặc LibreOffice
- Field spec JSON per template + dynamic form builder FE
- `{{#loop}}...{{/loop}}` block support cho table lặp
- PDF convert via LibreOffice headless
- Admin upload template UI (multipart)
- FE Users management + Roles CRUD (test permission với non-admin role)
- fe-user sync menu động (đang hardcode)
### D. Quick wins (không block phase)
- FE Users management + Roles CRUD (test permission với non-admin user thật)
- Filter Inbox theo phase FE
- Refresh token auto (FE axios interceptor retry 401)
## Lưu ý kỹ thuật quan trọng
@ -85,43 +91,50 @@ SOLUTION_ERP/
├── src/Backend/ (Clean Arch, 4 project, .NET 10)
│ ├── SolutionErp.Domain/
│ │ ├── Common/ BaseEntity, AuditableEntity
│ │ ├── Contracts/ ContractType, ContractPhase, ApprovalDecision
│ │ ├── Contracts/ ContractType, ContractPhase, ApprovalDecision + **Contract, ContractApproval, ContractComment, ContractAttachment, ContractCodeSequence** ← Phase 3
│ │ ├── Forms/ ContractTemplate, ContractClause ← Phase 2
│ │ ├── Identity/ User, Role, MenuItem, Permission, AppRoles, MenuKeys
│ │ └── Master/ Supplier, Project, Department, SupplierType
│ ├── SolutionErp.Application/
│ │ ├── Auth/ Login, Refresh, Me
│ │ ├── Common/ Exceptions, Behaviors, Interfaces, Models
│ │ ├── Contracts/ **ContractFeatures (8 CQRS), IContractWorkflowService, IContractCodeGenerator, DTOs** ← Phase 3
│ │ ├── Forms/ FormFeatures (List/Get/Render) ← Phase 2
│ │ ├── Master/ Suppliers, Projects, Departments CQRS
│ │ └── Permissions/ GetMyMenuTree, matrix upsert
│ ├── SolutionErp.Infrastructure/
│ │ ├── Forms/ DocxRenderer, XlsxRenderer, FormRenderer ← Phase 2
│ │ ├── Identity/ JwtSettings, JwtTokenService
│ │ ├── Persistence/ DbContext, DbInitializer, Interceptors, Migrations (4)
│ │ └── Services/ DateTimeService
│ │ ├── Persistence/ DbContext, DbInitializer, Interceptors, Migrations (**5** now)
│ │ └── Services/ DateTimeService, **ContractWorkflowService, ContractCodeGenerator** ← Phase 3
│ └── SolutionErp.Api/
│ ├── Authorization/ MenuPermissionHandler + Requirement
│ ├── Controllers/ Auth, Suppliers, Projects, Departments, Menus, Roles, Permissions, Forms
│ ├── Controllers/ Auth, Suppliers, Projects, Departments, Menus, Roles, Permissions, Forms, **Contracts** ← Phase 3
│ ├── Middleware/ GlobalExceptionMiddleware
│ ├── Services/ CurrentUserService, WebHostEnvironmentLocator
│ └── wwwroot/templates/ 5 file .docx/.xlsx ← Phase 2
├── fe-admin/ (7 page)
├── fe-admin/ (9 page)
│ └── src/pages/
│ ├── LoginPage
│ ├── DashboardPage
│ ├── master/SuppliersPage, ProjectsPage, DepartmentsPage
│ ├── system/PermissionsPage
── forms/FormsPage ← Phase 2
├── fe-user/ (2 page — Login + Inbox placeholder)
├── docs/ (24 file — STATUS, PROJECT-MAP, workflow, forms-spec, database-guide, 6 flow, gotchas, 4 session log, 1 handoff)
└── .claude/skills/ (3 skill — contract-workflow placeholder, form-engine + permission-matrix full spec)
── forms/FormsPage ← Phase 2
│ └── contracts/ContractsListPage, ContractDetailPage ← Phase 3
├── fe-user/ (5 page)
│ └── src/pages/
│ ├── LoginPage
│ ├── InboxPage ← Phase 3
│ └── contracts/ContractCreatePage, ContractDetailPage, MyContractsPage ← Phase 3
├── docs/ (26 file — STATUS, PROJECT-MAP, workflow, forms-spec, database-guide, 6 flow, gotchas, HANDOFF, 5 session log)
└── .claude/skills/ (3 skill — all full spec: contract-workflow, form-engine, permission-matrix)
```
## Git state
```
49a5f57..(sẽ là commit 5) — Phase 2 MVP
(sẽ là commit 6) — Phase 3 Workflow MVP
5113e4c — Phase 2 Form Engine MVP
54d6c9b — Phase 1.2 CRUD + Permission
49a5f57 — Docs database-guide + flows
702411f — Phase 1 foundation
@ -146,10 +159,14 @@ admin@solutionerp.local / Admin@123456
**Tốt:**
- Build pass 100% cả BE + FE
- E2E test: login + CRUD + render template đều pass
- Docs đầy đủ: 24 file, session log mỗi chunk, gotchas library tích lũy
- E2E test full 9-phase workflow end-to-end — mã HĐ gen đúng format RG-001
- Docs đầy đủ: 26 file, session log mỗi chunk, gotchas tích lũy 17 pitfall
- Cả 2 FE đều có Contract detail page + timeline + comment thread + state machine action
**Rủi ro:**
- fe-user còn thô — Phase 3 phải build inbox
**Rủi ro còn:**
- SLA chỉ set deadline — không có job auto-approve (Phase 3.2)
- Không có notification (email + in-app) — user phải F5 inbox
- Form render chỉ MVP — loop table + PDF chưa có
- Permission matrix chưa test thực với non-admin user
- 3 file .doc chưa convert (carryover Phase 2)
- Không có upload attachment endpoint (chỉ có entity + DTO)