[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:
148
docs/changelog/sessions/2026-04-21-1330-phase3-workflow.md
Normal file
148
docs/changelog/sessions/2026-04-21-1330-phase3-workflow.md
Normal file
@ -0,0 +1,148 @@
|
||||
# Session 2026-04-21 13:30 — Phase 3 Workflow MVP
|
||||
|
||||
**Dev:** Claude (Opus 4.7)
|
||||
**Duration:** ~1h15m
|
||||
**Base commit:** `5113e4c`
|
||||
|
||||
## Làm được
|
||||
|
||||
### Chunk G — Domain + EF + Migration
|
||||
|
||||
- Domain/Contracts: **Contract** (aggregate, 15 fields) + **ContractApproval** (history) + **ContractComment** (thread) + **ContractAttachment** (files với `AttachmentPurpose` enum) + **ContractCodeSequence** (Prefix PK + LastSeq)
|
||||
- EF configs với:
|
||||
- Unique MaHopDong filtered `WHERE [MaHopDong] IS NOT NULL`
|
||||
- Indexes: (Phase, IsDeleted), SupplierId, ProjectId, SlaDeadline
|
||||
- Cascade delete cho Approvals/Comments/Attachments khi Contract xóa
|
||||
- Query filter IsDeleted
|
||||
- DbSets + `IApplicationDbContext` update (5 DbSet mới)
|
||||
- Migration `AddContractsWorkflow`
|
||||
|
||||
### Chunk H — Workflow service + CodeGenerator + CQRS + Controller
|
||||
|
||||
**Services:**
|
||||
- `IContractWorkflowService.TransitionAsync(contract, targetPhase, userId, roles, decision, comment)`:
|
||||
- Adjacency check qua `Transitions` Dictionary<(from,to), roles[]>
|
||||
- Role check (Admin bypass)
|
||||
- System actor bypass (cho SLA job Phase 3.2)
|
||||
- Bypass rule Chủ đầu tư (DangInKy → DangTrinhKy skip CCM)
|
||||
- Gen mã HĐ khi DangDongDau (nếu chưa có)
|
||||
- Reset SlaDeadline theo target phase SLA
|
||||
- Insert ContractApproval row
|
||||
- `IContractCodeGenerator.GenerateAsync()`:
|
||||
- 7 format theo ContractType (HĐTP/HĐGK/NCC/HĐDV/MB + 2 framework)
|
||||
- `BeginTransactionAsync(IsolationLevel.Serializable)` + `ContractCodeSequences` UPSERT → atomic
|
||||
- Register scoped trong Infrastructure DI
|
||||
|
||||
**CQRS (ContractFeatures.cs):**
|
||||
- `CreateContractCommand` + Validator + Handler (tạo draft, SlaDeadline = UtcNow + 7d)
|
||||
- `UpdateContractDraftCommand` (chỉ khi Phase = DangSoanThao)
|
||||
- `TransitionContractCommand` (delegate → WorkflowService)
|
||||
- `AddCommentCommand` (phase = hiện tại)
|
||||
- `ListContractsQuery` (PagedResult với filter phase/supplier/project + search)
|
||||
- `GetMyInboxQuery` (map Phase → roles, return HĐ phù hợp role user)
|
||||
- `GetContractQuery` (detail + approvals + comments + attachments, resolve user names)
|
||||
- `DeleteContractCommand` (soft, block > DangInKy)
|
||||
|
||||
**Controller:**
|
||||
- `ContractsController` với 8 endpoint: GET list/inbox/detail, POST create/transition/comment, PUT update, DELETE
|
||||
- Body DTOs inline: `TransitionContractBody`, `AddCommentBody`
|
||||
|
||||
### Chunk I — Frontend 2 app
|
||||
|
||||
**fe-admin (2 page mới):**
|
||||
- `types/contracts.ts` — ContractPhase/Decision const-object + Label + Color maps + types
|
||||
- `components/PhaseBadge.tsx` — badge màu theo phase
|
||||
- `pages/contracts/ContractsListPage.tsx` — DataTable full filter + click row → detail
|
||||
- `pages/contracts/ContractDetailPage.tsx` — 2-col layout: info + comments + timeline + action dialog (approve/reject với select target phase + comment)
|
||||
|
||||
**fe-user (4 page mới + port shared):**
|
||||
- Copy 14 file shared từ fe-admin (menuKeys, types/*, DataTable, PhaseBadge, Dialog, Textarea, Select, apiError, usePermission, PermissionGuard)
|
||||
- `contexts/AuthContext.tsx` update — load menu from `/menus/me` + localStorage cache
|
||||
- `components/Layout.tsx` — menu fixed 3 mục (Inbox / Tạo mới / HĐ của tôi) + user info + role display
|
||||
- `pages/InboxPage.tsx` — list `/api/contracts/inbox` (HĐ chờ role của tôi xử lý, sort theo SLA)
|
||||
- `pages/contracts/ContractCreatePage.tsx` — form chọn loại HĐ + template + NCC + dự án + giá trị + bypass CĐT
|
||||
- `pages/contracts/ContractDetailPage.tsx` — duplicate fe-admin pattern (convention)
|
||||
- `pages/contracts/MyContractsPage.tsx` — HĐ của tôi
|
||||
- `App.tsx` — 4 route mới
|
||||
|
||||
## E2E verified
|
||||
|
||||
```bash
|
||||
# Setup
|
||||
POST /api/suppliers { code: "PVL2026", type: 1 } → 201
|
||||
POST /api/projects { code: "FLOCK 01" } → 201
|
||||
|
||||
# Tạo HĐ
|
||||
POST /api/contracts { type: 2, supplierId, projectId, giaTri: 150000000 }
|
||||
→ 201 contractId e20083d5
|
||||
→ phase=2 (DangSoanThao), slaDeadline=+7d
|
||||
|
||||
# Inbox
|
||||
GET /api/contracts/inbox → 1 contract
|
||||
|
||||
# Full workflow 9 phase (admin bypass)
|
||||
POST /transitions {targetPhase: 3} → 204
|
||||
POST /transitions {targetPhase: 4} → 204
|
||||
POST /transitions {targetPhase: 5} → 204
|
||||
POST /transitions {targetPhase: 6} → 204
|
||||
POST /transitions {targetPhase: 7} → 204
|
||||
POST /transitions {targetPhase: 8} → 204 ← GEN MÃ HĐ
|
||||
POST /transitions {targetPhase: 9} → 204
|
||||
|
||||
# Final
|
||||
GET /api/contracts/{id}
|
||||
→ phase: 9 (DaPhatHanh)
|
||||
→ maHopDong: "FLOCK 01/HĐGK/SOL&PVL2026/01" ✅ ĐÚNG FORMAT RG-001
|
||||
→ 7 approvals với đủ from/to/decision/comment
|
||||
```
|
||||
|
||||
TS check fe-admin + fe-user pass.
|
||||
|
||||
## Bug gặp + fix
|
||||
|
||||
| Bug | Fix |
|
||||
|---|---|
|
||||
| Edit tool "File not read" sau system-reminder | Re-read file rồi Write full |
|
||||
| `Python f-string backslash` trong curl test script | Bỏ escape trong f-string |
|
||||
|
||||
## Docs updates
|
||||
|
||||
- `.claude/skills/contract-workflow/SKILL.md` — full spec từ placeholder: state machine, role matrix, SLA, RG-001 format 7 loại, code pointers, API, workflow E2E, pitfalls
|
||||
- Session log này (session #4 trong changelog/sessions)
|
||||
|
||||
## Handoff cho session tiếp theo
|
||||
|
||||
### Phase 3 iteration 2 (optional — polish)
|
||||
|
||||
- [ ] `SlaExpiryJob` BackgroundService (hosted service, 15min interval, auto-approve quá hạn với `Decision=AutoApprove`) — spec ở `docs/flows/sla-expiry-flow.md`
|
||||
- [ ] Email notification (MailKit) khi chuyển phase
|
||||
- [ ] In-app notification (SignalR + badge counter)
|
||||
- [ ] Upload attachment endpoint + FE (multipart, store vào `wwwroot/uploads/contracts/{id}/`)
|
||||
- [ ] RowVersion optimistic concurrency (2 user cùng duyệt → 409)
|
||||
- [ ] Render HĐ ra .docx lúc tạo (link với TemplateId + DraftData + merge ContractClause appendix)
|
||||
- [ ] E2E test permission matrix: tạo user role Drafter thật → verify chỉ thấy nút approve khi ở đúng phase
|
||||
|
||||
### Phase 4 — Report + Polish
|
||||
|
||||
Xem `docs/changelog/migration-todos.md` section Phase 4.
|
||||
|
||||
### Quick wins
|
||||
|
||||
- FE Users management (tạo user, gán role) — để test workflow với multi-user
|
||||
- Filter Inbox theo phase (dropdown FE)
|
||||
- Export HĐ list ra Excel
|
||||
|
||||
### Blocker
|
||||
|
||||
- ⏳ **Gitea remote URL** (vẫn chờ)
|
||||
|
||||
## Thông số cumulative
|
||||
|
||||
| | Sau Phase 2 | Sau Phase 3 MVP |
|
||||
|---|---:|---:|
|
||||
| BE LOC | ~1900 | ~2700 |
|
||||
| DB tables | 14 | 19 (+Contracts, ContractApprovals, Comments, Attachments, CodeSequences) |
|
||||
| API endpoints | ~23 | ~31 (+8 contract) |
|
||||
| Migrations | 4 | 5 |
|
||||
| FE pages | 7 (fe-admin) + 2 (fe-user) | 9 + 5 |
|
||||
| Commits | 5 | 6 (sắp) |
|
||||
Reference in New Issue
Block a user