[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)

View File

@ -2,78 +2,84 @@
> **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-04-21 12:00
**Last updated:** 2026-04-21 13:30
## 📍 Phase hiện tại: **Phase 2 Form Engine (MVP xong)** — sẵn sàng Phase 3 Workflow
## 📍 Phase hiện tại: **Phase 3 Workflow (MVP xong)** — sẵn sàng Phase 4 Report hoặc polish Phase 3 iteration 2
## 🔥 In Progress
_(không có — tạm nghỉ chờ user approve move on)_
_(không có)_
## ✅ Recently Done (newest on top)
| Ngày | Ai | Task | Commit |
|---|---|---|---|
| 2026-04-21 | Claude | **Phase 2 Form Engine MVP** BE: ContractTemplate/ContractClause entities + OpenXml + ClosedXML renderer (placeholder `{{field}}`) + FormsController + seed 8 template. FE: FormsPage list + render dialog. `/api/forms/templates/{id}/render` trả file .docx/.xlsx 482KB OK | (sắp commit) |
| 2026-04-21 | Claude | **Docs:** `gotchas.md` (17 pitfalls) + update 2 skill (form-engine, permission-matrix) từ placeholder → full spec | (sắp commit) |
| 2026-04-21 | Claude | **Phase 1 đợt 2** BE CRUD Supplier/Project/Department + Permission Matrix + FE 4 page | `54d6c9b` |
| 2026-04-21 | Claude | **Docs addition**`database-guide.md` + `flows/` 6 doc | `49a5f57` |
| 2026-04-21 | Claude | **Phase 1 foundation** BE Clean Arch + Identity + JWT + FE 2 app + login E2E | `702411f` |
| 2026-04-21 | Claude | **Phase 0** — scaffold + parse FORM/QUY_TRINH + docs + git init | `25dad7f` |
| 2026-04-21 | Claude | **Phase 3 Workflow MVP** — Contract+Approval+Comment+Attachment+CodeSequence entities + IContractWorkflowService (9 phase adjacency + role guard + bypass CĐT) + IContractCodeGenerator (RG-001 transactional) + CQRS 8 command/query + ContractsController. FE: fe-admin ContractsList + ContractDetail, fe-user Inbox+Create+Detail+MyContracts. **E2E 9 phase end-to-end pass với mã HĐ `FLOCK 01/HĐGK/SOL&PVL2026/01`** | (sắp commit) |
| 2026-04-21 | Claude | **Phase 2 Form Engine MVP** — OpenXml + ClosedXML renderer + seed 8 template + FE FormsPage | `5113e4c` |
| 2026-04-21 | Claude | **Phase 1.2** — CRUD Master + Permission Matrix + FE 4 page | `54d6c9b` |
| 2026-04-21 | Claude | **Docs + flows** | `49a5f57` |
| 2026-04-21 | Claude | **Phase 1 foundation** — Clean Arch + Identity + JWT + 2 FE + login E2E | `702411f` |
| 2026-04-21 | Claude | **Phase 0** — scaffold + docs | `25dad7f` |
Session logs:
- [`changelog/sessions/2026-04-21-1045-phase0-scaffold.md`](changelog/sessions/2026-04-21-1045-phase0-scaffold.md)
- [`changelog/sessions/2026-04-21-1100-phase1-foundation.md`](changelog/sessions/2026-04-21-1100-phase1-foundation.md)
- [`changelog/sessions/2026-04-21-1130-phase1-cruds-permission.md`](changelog/sessions/2026-04-21-1130-phase1-cruds-permission.md)
- [`changelog/sessions/2026-04-21-1200-phase2-form-engine.md`](changelog/sessions/2026-04-21-1200-phase2-form-engine.md)
Session logs: [Phase 0](changelog/sessions/2026-04-21-1045-phase0-scaffold.md) · [Phase 1f](changelog/sessions/2026-04-21-1100-phase1-foundation.md) · [Phase 1.2](changelog/sessions/2026-04-21-1130-phase1-cruds-permission.md) · [Phase 2](changelog/sessions/2026-04-21-1200-phase2-form-engine.md) · [Phase 3](changelog/sessions/2026-04-21-1330-phase3-workflow.md)
Gotchas library: [`gotchas.md`](gotchas.md) — 17 pitfalls đã gặp + cách xử lý.
Gotchas: [`gotchas.md`](gotchas.md) · Handoff: [`HANDOFF.md`](HANDOFF.md)
## 🎯 Next up
### Phase 2 iteration 2 (optional — enhance)
### Phase 3 iteration 2 (polish — optional)
- [ ] Convert 3 file `.doc` (retry Word COM với timeout, hoặc LibreOffice headless)
- [ ] Field spec JSON mỗi template + dynamic form builder FE
- [ ] Support `{{#loop}}...{{/loop}}` cho table lặp
- [ ] PDF convert via LibreOffice
- [ ] Admin upload template UI (POST multipart)
- [ ] `SlaExpiryJob` BackgroundService auto-approve quá hạn
- [ ] Email notification (MailKit) khi chuyển phase
- [ ] In-app notification (SignalR + badge)
- [ ] Upload attachment endpoint + FE multipart
- [ ] RowVersion optimistic concurrency
- [ ] Render HĐ docx khi tạo (merge ContractClause appendix)
### Phase 3Workflow (sắp tới, item lớn)
### Phase 4Report + Polish (tuần 10-11)
Xem [`docs/flows/contract-approval-flow.md`](flows/contract-approval-flow.md) + [`docs/workflow-contract.md`](workflow-contract.md).
- [ ] Dashboard admin: HĐ theo phase / top NCC / top dự án / tổng giá trị tháng
- [ ] Excel export list HĐ (EPPlus/ClosedXML)
- [ ] Report quá hạn SLA theo phase/role
- [ ] UX polish: skeleton, empty state, error boundary
- [ ] Accessibility pass
- [ ] Index DB hot query
- [ ] User guide docs
- [ ] UAT với data thật
Summary:
- [ ] Entity: Contract + ContractApproval + ContractComment + ContractAttachment
- [ ] `IContractWorkflowService` với state guard + role guard
- [ ] `IContractCodeGenerator` RG-001 + transaction SERIALIZABLE
- [ ] `SlaExpiryJob` BackgroundService
- [ ] Email + in-app notification service
- [ ] API `POST /api/contracts/{id}/transitions`
- [ ] FE Inbox + Contract detail page + timeline UI
### Phase 5 — Production (tuần 12-13)
### Optional (không block Phase 3)
- [ ] CI/CD Gitea Actions → IIS deploy
- [ ] HTTPS cert + appsettings Production secrets
- [ ] Rate limiting + Security audit
- [ ] Backup/restore runbook
- [ ] FE Users management + Roles CRUD (cho test permission với non-admin role)
- [ ] fe-user sync menu động (đang hardcode)
### Quick wins (không block)
- [ ] FE Users management + Roles CRUD
- [ ] Filter Inbox theo phase FE
- [ ] Refresh token auto (FE axios interceptor)
## 📊 Thông số cumulative
| | Phase 0 | +Phase 1f | +Phase 1.2 | +Docs | +Phase 2 MVP |
| | Phase 0 | 1f | 1.2 | 2 | **Phase 3 MVP** |
|---|---:|---:|---:|---:|---:|
| BE LOC | 0 | ~400 | ~1500 | — | ~1900 |
| DB tables | 0 | 7 | 12 | | 14 |
| API endpoints | 0 | 4 | ~20 | — | ~23 |
| Migrations | 0 | 1 | 3 | | 4 |
| FE pages | 0 | 2 | 6 | | 7 |
| Docs files | 10 | 13 | 14 | 21 | 24 |
| Commits | 1 | 2 | 3 | | 5 (sắp) |
| BE LOC | 0 | ~400 | ~1500 | ~1900 | **~2700** |
| DB tables | 0 | 7 | 12 | 14 | **19** |
| API endpoints | 0 | 4 | ~20 | ~23 | **~31** |
| Migrations | 0 | 1 | 3 | 4 | **5** |
| FE pages | 0 | 2 | 6 | 7 | **14** (9 admin + 5 user) |
| Docs | 10 | 13 | 14 | 24 | **26** |
| Commits | 1 | 2 | 3 | 5 | **6** (sắp) |
## 🚨 Blockers / risks
-**Gitea remote URL**user sẽ cấp sau
- ⚠️ **3 file .doc** chưa convert (IsActive=false) — retry Word COM với timeout/`DisplayAlerts=0` hoặc LibreOffice
- ⚠️ **fe-user** chưa đồng bộ menu động (chỉ fe-admin đã chuyển) — quick fix lúc Phase 3
-**Gitea remote URL**vẫn chờ
- ⚠️ **SLA hiện chỉ set deadline** — không có job auto-approve (Phase 3.2)
- ⚠️ **Không có notification** (email/in-app) — user phải F5 inbox manual
- ⚠️ **Không có RowVersion** — 2 user cùng transition race → last-write-wins
- ⚠️ **3 file .doc chưa convert** (Phase 2 carryover)
- ⚠️ **Permission chưa test với non-admin user** — tất cả E2E đều dùng admin
## Credentials + URLs

View File

@ -122,19 +122,37 @@
## Phase 3 — Workflow State Machine (T7-9)
- [ ] `Domain/Entities/ContractApproval` + `ContractComment` + `ContractAttachment`
- [ ] `Domain/Entities/Contract` update: thêm `Phase`, `SlaDeadline`, `BypassProcurementAndCCM`
- [ ] `Domain/Services/IContractWorkflowService.TransitionAsync(...)` — state guard + role guard + side effects
- [ ] `Infrastructure/Services/ContractCodeGenerator` (implement RG-001) với locking cho seq
- [ ] `Infrastructure/HostedServices/SlaExpiryJob` — check mỗi 15min, auto-approve quá hạn
- [ ] `Infrastructure/Services/NotificationService` — email (MailKit) + in-app (SignalR optional)
- [ ] MediatR behavior: `AuditBehavior` — log mọi command
- [ ] API: `POST /api/contracts/{id}/transitions` body: `{targetPhase, comment}`
- [ ] FE user Inbox: list "HĐ chờ tôi xử lý" (query by current phase + user role)
- [ ] FE Contract detail page: timeline 9 phase, approval panel, comment thread
- [ ] Upload attachment (scan có chữ ký đối tác)
- [ ] Notification UI: badge count, dropdown, click → detail
- [ ] E2E test: happy path end-to-end 1 HĐ qua 9 phase
### MVP xong (iteration 1)
- [x] `Domain/Contracts/Contract` (Phase, SlaDeadline, BypassProcurementAndCCM, MaHopDong, DraftData, SlaWarningSent)
- [x] `Domain/Contracts/ContractApproval` (FromPhase, ToPhase, ApproverUserId, Decision, Comment)
- [x] `Domain/Contracts/ContractComment` + `ContractAttachment` (+ AttachmentPurpose enum)
- [x] `Domain/Contracts/ContractCodeSequence` (Prefix PK, LastSeq)
- [x] EF config + unique MaHopDong filtered + indexes Phase/Supplier/Project/SlaDeadline + cascade delete
- [x] DbSets (5) + `IApplicationDbContext` update
- [x] Migration `AddContractsWorkflow`
- [x] `Application/Contracts/Services/IContractWorkflowService` + `IContractCodeGenerator`
- [x] `Infrastructure/Services/ContractWorkflowService` — adjacency 9 phase + role guard + Admin bypass + system actor + bypass CCM (Chủ đầu tư)
- [x] `Infrastructure/Services/ContractCodeGenerator` — 7 format RG-001 + transaction SERIALIZABLE
- [x] CQRS: Create/UpdateDraft/Transition/AddComment/List/Inbox/GetDetail/Delete (8 feature)
- [x] `Api/Controllers/ContractsController` — 8 endpoint REST
- [x] FE admin: ContractsListPage + ContractDetailPage (timeline + action dialog)
- [x] FE user: InboxPage + ContractCreatePage + ContractDetailPage + MyContractsPage
- [x] PhaseBadge component + color map
- [x] E2E verified: tạo HĐ → chạy 9 phase → gen mã `FLOCK 01/HĐGK/SOL&PVL2026/01`
### Iteration 2 (polish — optional)
- [ ] `Infrastructure/HostedServices/SlaExpiryJob` — check mỗi 15min, auto-approve quá hạn với Decision=AutoApprove
- [ ] Warning notification khi còn 20% SLA
- [ ] `Infrastructure/Services/NotificationService` — email (MailKit) + in-app
- [ ] SignalR hub cho real-time notification badge
- [ ] MediatR `AuditBehavior` — log mọi command (ngoài ContractApprovals)
- [ ] Upload attachment endpoint (multipart) + FE upload UI (`wwwroot/uploads/contracts/{id}/`)
- [ ] RowVersion optimistic concurrency (2 user race → 409)
- [ ] Render HĐ docx lúc tạo (merge TemplateId + DraftData + ContractClause appendix)
- [ ] E2E test với non-admin user (Drafter/CCM/BOD role)
- [ ] Filter Inbox theo phase ở FE
- [ ] E2E test: reject → quay về DangSoanThao
- [ ] E2E test: SLA expired → auto-approve + log

View 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) |