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>
149 lines
6.3 KiB
Markdown
149 lines
6.3 KiB
Markdown
# 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) |
|