[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:
109
docs/HANDOFF.md
109
docs/HANDOFF.md
@ -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 3 — Workflow (item lớn, ~3 tuần work)
|
||||
### A. Phase 4 — Report + 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, có 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)
|
||||
|
||||
@ -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 3 — Workflow (sắp tới, item lớn)
|
||||
### Phase 4 — Report + 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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
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