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