[CLAUDE] Phase4: Report MVP + Docs Consolidation (rules, architecture, schema-diagram)
Backend Report: - Application/Reports/Dtos/DashboardStatsDto: 5 KPI + PhaseCount + SupplierCount + ProjectCount + MonthlyValue - Application/Reports/Queries/GetDashboardStats handler: total/active/overdue/published this month/totalValueActive + byPhase + top 5 NCC/du an + 12 thang monthly (fill zero khi thang empty) - Application/Reports/Services/IContractExcelExporter interface - Infrastructure/Reports/ContractExcelExporter: ClosedXML workbook 10 cot, header style bold+blue, number format #,##0, formula SUM, auto-fit, freeze header - Application/Reports/Commands/ExportContractsToExcelCommand: filter phase/supplier/project/date range - Api/Controllers/ReportsController: GET /reports/dashboard, GET /reports/contracts/export - DI register IContractExcelExporter (Scoped) Frontend fe-admin: - types/reports.ts: DashboardStats type - components/BarChart.tsx: generic horizontal bar chart — chi Tailwind, khong thu vien ngoai - pages/DashboardPage.tsx REWRITE: 5 KPI card (FileText/TrendingUp/AlertTriangle/CheckCircle2/Coins) + by-phase bar + monthly 12-month chart + top 5 NCC + top 5 du an + skeleton loader - pages/ReportsPage.tsx MOI: filter phase/fromDate/toDate → export Excel button - Route /reports vao App.tsx E2E verified: - GET /api/reports/dashboard → 200 voi day du KPI + monthly fill 12 thang - GET /api/reports/contracts/export → 200 xlsx 7229 bytes (Microsoft Excel 2007+) Docs consolidation (theo yeu cau user): - docs/rules.md MOI: 9 section coding conventions (ngon ngu UI/code/DB/docs, BE Clean Arch, CQRS+MediatR, Validation FluentValidation, Error handling, Async, Entity rules, DI, Package pinning, FE React/TS erasableSyntaxOnly, path alias, TanStack Query, Permission guard, Toast+error, DB convention, Git commit format, Docs structure, Testing, Security) - docs/architecture.md MOI: layered overview ASCII art, request lifecycle (1 POST/api/contracts qua 10 step), workflow state machine 9 phase, permission model, data flow sequence diagram 4 actor (Drafter/Manager/CCM/BOD/HRA), deployment architecture Phase 5, skill library, non-functional table - docs/database/schema-diagram.md MOI: full ERD 19 table mermaid + data flow diagram + vong doi 1 HD (create → 7 transition → gen ma → publish) + index strategy table + relationship cardinality + soft delete behavior + SQL queries (inbox/dashboard/gen ma) + migration history - docs/gotchas.md UPDATE: 17 → 26 pitfalls, them section "Claude Code harness quirks" (Edit File not read, DI build pass nhung runtime fail) + "Contract workflow" (ma HD gen 2 lan, BE-FE NEXT_PHASES sync, race condition) + "Permission matrix" (cache real-time, MenuKey typo) - docs/STATUS.md: Phase 4 MVP done, docs entry points section liet ke het, next Phase 5 Production - docs/HANDOFF.md: phase table them Phase 4 row, file tree update voi Reports, test points day du, git state commit 7 - docs/changelog/migration-todos.md: tick Phase 4 MVP items + them iteration 2 list - docs/changelog/sessions/2026-04-21-1430-phase4-report.md: session log voi thong so cumulative (BE 3100 LOC, 30 docs) - CLAUDE.md root: update Tai lieu quan trong section them rules.md, architecture.md, schema-diagram.md, .claude/skills (13 links now) Bug fix: - TS unused import ContractPhaseLabel trong DashboardPage - DI thieu register IContractExcelExporter — build pass but runtime would fail (added) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
# HANDOFF — Brief 5 phút cho session tiếp theo
|
||||
|
||||
**Last updated:** 2026-04-21 13:30 (cuối Phase 3 MVP)
|
||||
**Last updated:** 2026-04-21 14:30 (cuối Phase 4 MVP + docs consolidation)
|
||||
|
||||
## Ở đâu rồi?
|
||||
|
||||
@ -11,10 +11,11 @@
|
||||
| 1 Alpha Core đợt 2 (CRUD + Permission) | ✅ Done |
|
||||
| 2 Form Engine MVP | ✅ Done |
|
||||
| 2 Form Engine iteration 2 | 📝 Optional |
|
||||
| **3 Workflow MVP (9 phase + code gen)** | ✅ Done |
|
||||
| 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 |
|
||||
| **4 Report + Polish MVP (Dashboard + Excel)** | ✅ Done |
|
||||
| 4 Report iteration 2 (SLA report, PDF export) | 📝 Optional |
|
||||
| 5 Production (CI/CD IIS) | 📋 Next |
|
||||
|
||||
## Run nhanh
|
||||
|
||||
@ -31,34 +32,43 @@ cd fe-user && npm run dev # → http://localhost:8080
|
||||
|
||||
Login: `admin@solutionerp.local` / `Admin@123456`
|
||||
|
||||
Điểm cần test ngay (Phase 3 MVP):
|
||||
Điểm cần test ngay (Phase 4 MVP):
|
||||
- **Admin `/dashboard`** → 5 KPI card + By Phase bar + Monthly chart + Top NCC/dự án
|
||||
- **Admin `/reports`** → filter phase/date → Export Excel .xlsx 10 cột có formula SUM
|
||||
- **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
|
||||
- **`/contracts/{id}`** → click "Duyệt → tiếp" chạy state machine. Phase 8 gen `MaHopDong` RG-001
|
||||
- **`/forms`** → render template .docx
|
||||
- **`/system/permissions`** → ma trận Role × MenuKey
|
||||
- **`/master/suppliers|projects|departments`** → CRUD
|
||||
|
||||
## Cần làm kế tiếp (ưu tiên)
|
||||
|
||||
### A. Phase 4 — Report + Polish (tuần 10-11)
|
||||
### A. Phase 5 — Production (tuần 12-13, item lớn nhất còn lại)
|
||||
|
||||
- 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
|
||||
**Đọc trước:** `docs/changelog/migration-todos.md` section Phase 5.
|
||||
|
||||
### B. Phase 3 iteration 2 (polish workflow)
|
||||
- [ ] `.gitea/workflows/deploy.yml` CI/CD build + deploy IIS
|
||||
- [ ] `scripts/deploy-iis.ps1` stop app pool → xcopy → start
|
||||
- [ ] Windows Server IIS + URL Rewrite + ARR (reverse proxy FE → .NET)
|
||||
- [ ] HTTPS cert via win-acme hoặc mua
|
||||
- [ ] `appsettings.Production.json` + user secrets + JWT secret rotation
|
||||
- [ ] Rate limiting middleware (auth endpoint 5 req/min/IP)
|
||||
- [ ] Security audit OWASP top 10
|
||||
- [ ] Health check endpoint `/health`
|
||||
- [ ] Serilog → file rolling daily retention 30d
|
||||
- [ ] SQL backup: daily full + 15min log
|
||||
- [ ] Runbook: restart, rollback migration, restore backup
|
||||
- [ ] UAT production 1 tuần với 2-3 user thật
|
||||
- [ ] Go-live checklist
|
||||
|
||||
- [ ] `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)
|
||||
### B. Polish iterations (optional — khi rảnh)
|
||||
|
||||
**Phase 2 iter 2:** convert 3 .doc qua Word COM `DisplayAlerts=0` hoặc LibreOffice, field spec JSON + form builder FE dynamic, `{{#loop}}` block support, PDF convert, FE upload template multipart.
|
||||
|
||||
**Phase 3 iter 2:** `SlaExpiryJob` BackgroundService auto-approve, email (MailKit) + in-app (SignalR) notify, upload attachment endpoint + FE multipart (`wwwroot/uploads/contracts/{id}/`), RowVersion concurrency, render HĐ docx khi tạo (merge TemplateId + DraftData + ContractClause).
|
||||
|
||||
**Phase 4 iter 2:** SLA overdue report by role/phase, PDF HĐ export (LibreOffice), dashboard user-specific (role tôi).
|
||||
|
||||
### C. Phase 2 iteration 2 (form engine polish)
|
||||
|
||||
@ -98,42 +108,53 @@ SOLUTION_ERP/
|
||||
│ ├── SolutionErp.Application/
|
||||
│ │ ├── Auth/ Login, Refresh, Me
|
||||
│ │ ├── Common/ Exceptions, Behaviors, Interfaces, Models
|
||||
│ │ ├── Contracts/ **ContractFeatures (8 CQRS), IContractWorkflowService, IContractCodeGenerator, DTOs** ← Phase 3
|
||||
│ │ ├── 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
|
||||
│ │ ├── Permissions/ GetMyMenuTree, matrix upsert
|
||||
│ │ └── Reports/ **DashboardStats, ExportContractsToExcel, IContractExcelExporter** ← Phase 4
|
||||
│ ├── SolutionErp.Infrastructure/
|
||||
│ │ ├── Forms/ DocxRenderer, XlsxRenderer, FormRenderer ← Phase 2
|
||||
│ │ ├── Identity/ JwtSettings, JwtTokenService
|
||||
│ │ ├── Persistence/ DbContext, DbInitializer, Interceptors, Migrations (**5** now)
|
||||
│ │ └── Services/ DateTimeService, **ContractWorkflowService, ContractCodeGenerator** ← Phase 3
|
||||
│ │ ├── Persistence/ DbContext, DbInitializer, Interceptors, Migrations (**5**)
|
||||
│ │ ├── Reports/ **ContractExcelExporter** ← Phase 4
|
||||
│ │ └── Services/ DateTimeService, ContractWorkflowService, ContractCodeGenerator ← Phase 3
|
||||
│ └── SolutionErp.Api/
|
||||
│ ├── Authorization/ MenuPermissionHandler + Requirement
|
||||
│ ├── Controllers/ Auth, Suppliers, Projects, Departments, Menus, Roles, Permissions, Forms, **Contracts** ← Phase 3
|
||||
│ ├── Controllers/ Auth, Suppliers, Projects, Departments, Menus, Roles, Permissions, Forms, Contracts, **Reports** (10 controller)
|
||||
│ ├── Middleware/ GlobalExceptionMiddleware
|
||||
│ ├── Services/ CurrentUserService, WebHostEnvironmentLocator
|
||||
│ └── wwwroot/templates/ 5 file .docx/.xlsx ← Phase 2
|
||||
├── fe-admin/ (9 page)
|
||||
├── fe-admin/ (11 page)
|
||||
│ └── src/pages/
|
||||
│ ├── LoginPage
|
||||
│ ├── DashboardPage
|
||||
│ ├── DashboardPage ← Phase 4 rewrite (KPI cards + BarChart)
|
||||
│ ├── master/SuppliersPage, ProjectsPage, DepartmentsPage
|
||||
│ ├── system/PermissionsPage
|
||||
│ ├── forms/FormsPage ← Phase 2
|
||||
│ └── contracts/ContractsListPage, ContractDetailPage ← Phase 3
|
||||
│ ├── contracts/ContractsListPage, ContractDetailPage ← Phase 3
|
||||
│ └── ReportsPage ← Phase 4
|
||||
├── 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)
|
||||
├── docs/ (30 file)
|
||||
│ ├── STATUS.md, HANDOFF.md, rules.md, architecture.md
|
||||
│ ├── CLAUDE.md, PROJECT-MAP.md
|
||||
│ ├── workflow-contract.md, forms-spec.md
|
||||
│ ├── database/{database-guide, schema-diagram}.md
|
||||
│ ├── flows/ (7 file — README + 6 flow)
|
||||
│ ├── changelog/migration-todos.md + sessions/ (6 session log)
|
||||
│ └── gotchas.md
|
||||
└── .claude/skills/ (3 skill — all full spec)
|
||||
```
|
||||
|
||||
## Git state
|
||||
|
||||
```
|
||||
(sẽ là commit 6) — Phase 3 Workflow MVP
|
||||
(sẽ là commit 7) — Phase 4 Report MVP + docs consolidation
|
||||
7e957a7 — Phase 3 Workflow MVP
|
||||
5113e4c — Phase 2 Form Engine MVP
|
||||
54d6c9b — Phase 1.2 CRUD + Permission
|
||||
49a5f57 — Docs database-guide + flows
|
||||
@ -141,7 +162,7 @@ SOLUTION_ERP/
|
||||
25dad7f — Phase 0 scaffold
|
||||
|
||||
Branch: main
|
||||
Remote: chưa (Gitea URL chờ user)
|
||||
Remote: chưa (Gitea URL chờ user — cần cho Phase 5)
|
||||
```
|
||||
|
||||
## Credentials + URLs
|
||||
|
||||
105
docs/STATUS.md
105
docs/STATUS.md
@ -2,9 +2,9 @@
|
||||
|
||||
> **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 13:30
|
||||
**Last updated:** 2026-04-21 14:30
|
||||
|
||||
## 📍 Phase hiện tại: **Phase 3 Workflow (MVP xong)** — sẵn sàng Phase 4 Report hoặc polish Phase 3 iteration 2
|
||||
## 📍 Phase hiện tại: **Phase 4 Report MVP (xong)** — sẵn sàng Phase 5 Production hoặc polish iterations
|
||||
|
||||
## 🔥 In Progress
|
||||
|
||||
@ -14,72 +14,73 @@ _(không có)_
|
||||
|
||||
| Ngày | Ai | Task | Commit |
|
||||
|---|---|---|---|
|
||||
| 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 4 Report MVP + Docs Consolidation** — BE Dashboard stats (5 KPI + by phase + top 5 NCC/dự án + 12 tháng) + Excel export qua ClosedXML. FE DashboardPage rewrite với BarChart tự build (không thư viện ngoài) + ReportsPage filter export. Docs: rules.md (coding conventions), architecture.md (layered + sequence), database/schema-diagram.md (ERD + data flow 19 table), gotchas.md update 26 pitfalls | (sắp commit) |
|
||||
| 2026-04-21 | Claude | **Phase 3 Workflow MVP** — 9 phase state machine + code gen RG-001 + Inbox/Detail FE — E2E pass mã `FLOCK 01/HĐGK/SOL&PVL2026/01` | `7e957a7` |
|
||||
| 2026-04-21 | Claude | **Phase 2 Form Engine MVP** | `5113e4c` |
|
||||
| 2026-04-21 | Claude | **Phase 1.2** — CRUD Master + Permission Matrix | `54d6c9b` |
|
||||
| 2026-04-21 | Claude | **Docs addition** — database-guide + flows | `49a5f57` |
|
||||
| 2026-04-21 | Claude | **Phase 1 foundation** — Clean Arch + Identity + JWT + 2 FE | `702411f` |
|
||||
| 2026-04-21 | Claude | **Phase 0** — scaffold + docs | `25dad7f` |
|
||||
|
||||
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)
|
||||
Session logs: [P0](changelog/sessions/2026-04-21-1045-phase0-scaffold.md) · [P1f](changelog/sessions/2026-04-21-1100-phase1-foundation.md) · [P1.2](changelog/sessions/2026-04-21-1130-phase1-cruds-permission.md) · [P2](changelog/sessions/2026-04-21-1200-phase2-form-engine.md) · [P3](changelog/sessions/2026-04-21-1330-phase3-workflow.md) · [P4](changelog/sessions/2026-04-21-1430-phase4-report.md)
|
||||
|
||||
Gotchas: [`gotchas.md`](gotchas.md) · Handoff: [`HANDOFF.md`](HANDOFF.md)
|
||||
**Docs entry points:**
|
||||
- [`rules.md`](rules.md) — coding conventions
|
||||
- [`architecture.md`](architecture.md) — layered + data flow + deployment
|
||||
- [`database/database-guide.md`](database/database-guide.md) + [`database/schema-diagram.md`](database/schema-diagram.md) — DB spec
|
||||
- [`gotchas.md`](gotchas.md) — 26 pitfalls
|
||||
- [`HANDOFF.md`](HANDOFF.md) — brief 5 phút
|
||||
- [`workflow-contract.md`](workflow-contract.md) — state machine
|
||||
- [`forms-spec.md`](forms-spec.md) — 8 form catalog
|
||||
- [`flows/`](flows/) — 6 flow diagram
|
||||
|
||||
## 🎯 Next up
|
||||
|
||||
### Phase 3 iteration 2 (polish — optional)
|
||||
### Phase 5 — Production (T12-13, item lớn nhất còn lại)
|
||||
|
||||
- [ ] `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)
|
||||
- [ ] CI/CD Gitea Actions (`.gitea/workflows/deploy.yml`) deploy IIS
|
||||
- [ ] `scripts/deploy-iis.ps1` stop app pool → xcopy → start
|
||||
- [ ] Windows Server setup: IIS + URL Rewrite + ARR
|
||||
- [ ] HTTPS cert via win-acme
|
||||
- [ ] `appsettings.Production.json` + user secrets
|
||||
- [ ] Rate limiting middleware
|
||||
- [ ] Security audit OWASP top 10
|
||||
- [ ] Health check endpoint `/health`
|
||||
- [ ] Serilog → file rolling daily retention 30d
|
||||
- [ ] Runbook: restart, rollback, backup/restore
|
||||
- [ ] UAT production 1 tuần
|
||||
|
||||
### Phase 4 — Report + Polish (tuần 10-11)
|
||||
### Polish iterations (optional — làm khi rảnh)
|
||||
|
||||
- [ ] 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
|
||||
**Phase 2 iter 2:** convert 3 .doc, field spec JSON + form builder, {{#loop}}, PDF convert, upload template UI
|
||||
**Phase 3 iter 2:** SLA auto-approve job, email/in-app notification, attachment upload, RowVersion, render HĐ khi tạo
|
||||
**Phase 4 iter 2:** SLA overdue report, PDF HĐ export, dashboard user-specific
|
||||
|
||||
### Phase 5 — Production (tuần 12-13)
|
||||
### Quick wins
|
||||
|
||||
- [ ] CI/CD Gitea Actions → IIS deploy
|
||||
- [ ] HTTPS cert + appsettings Production secrets
|
||||
- [ ] Rate limiting + Security audit
|
||||
- [ ] Backup/restore runbook
|
||||
|
||||
### Quick wins (không block)
|
||||
|
||||
- [ ] FE Users management + Roles CRUD
|
||||
- [ ] Filter Inbox theo phase FE
|
||||
- [ ] Refresh token auto (FE axios interceptor)
|
||||
- FE Users management + Roles CRUD (test permission non-admin)
|
||||
- Filter Inbox theo phase FE
|
||||
- FE refresh token auto interceptor
|
||||
|
||||
## 📊 Thông số cumulative
|
||||
|
||||
| | Phase 0 | 1f | 1.2 | 2 | **Phase 3 MVP** |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| 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) |
|
||||
| | P0 | P1f | P1.2 | P2 | P3 | **P4** |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| BE LOC | 0 | ~400 | ~1500 | ~1900 | ~2700 | **~3100** |
|
||||
| DB tables | 0 | 7 | 12 | 14 | 19 | **19** |
|
||||
| API endpoints | 0 | 4 | ~20 | ~23 | ~31 | **~33** |
|
||||
| FE pages | 0 | 2 | 6 | 7 | 14 | **16** |
|
||||
| Docs | 10 | 13 | 14 | 24 | 26 | **30** |
|
||||
| Commits | 1 | 2 | 3 | 5 | 6 | **7** (sắp) |
|
||||
|
||||
## 🚨 Blockers / risks
|
||||
|
||||
- ⏳ **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
|
||||
- ⏳ **Gitea remote URL** — user sẽ cấp khi vào Phase 5
|
||||
- ⚠️ **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
|
||||
- ⚠️ **SLA chỉ set deadline** — không auto-approve (Phase 3.2)
|
||||
- ⚠️ **Không notification** email/in-app (Phase 3.2)
|
||||
- ⚠️ **Permission chưa test non-admin user thật** — cần FE Users mgmt
|
||||
- ⚠️ **FE refresh token** — 401 chỉ redirect logout, chưa auto-refresh
|
||||
|
||||
## Credentials + URLs
|
||||
|
||||
@ -88,5 +89,5 @@ admin@solutionerp.local / Admin@123456
|
||||
```
|
||||
|
||||
- API: http://localhost:5443 — Swagger `/swagger`
|
||||
- Admin FE: http://localhost:8082
|
||||
- User FE: http://localhost:8080
|
||||
- Admin FE: http://localhost:8082 — Dashboard → **`/dashboard`** (KPI mới), **`/contracts`** (list), **`/reports`** (export), **`/master/*`** (NCC/DA/PB), **`/forms`**, **`/system/permissions`**
|
||||
- User FE: http://localhost:8080 — Inbox → **`/inbox`**, **`/contracts/new`**, **`/my-contracts`**
|
||||
|
||||
250
docs/architecture.md
Normal file
250
docs/architecture.md
Normal file
@ -0,0 +1,250 @@
|
||||
# Architecture — SOLUTION_ERP
|
||||
|
||||
> Kiến trúc tổng thể + trách nhiệm từng layer + diagram luồng dữ liệu.
|
||||
|
||||
## 1. Layered overview
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ CLIENT TIER │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ fe-admin :8082 │ │ fe-user :8080 │ │
|
||||
│ │ React 19 + Vite │ │ React 19 + Vite │ │
|
||||
│ │ Tailwind 4 │ │ Tailwind 4 │ │
|
||||
│ │ TanStack Query │ │ TanStack Query │ │
|
||||
│ └────────┬─────────┘ └────────┬─────────┘ │
|
||||
└───────────┼──────────────────────────┼───────────────────────┘
|
||||
│ Vite dev proxy /api │
|
||||
│ IIS URL Rewrite prod │
|
||||
▼ ▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ API LAYER (:5443) │
|
||||
│ SolutionErp.Api — ASP.NET Core 10 Web API │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ Controllers: Auth, Menus, Roles, Permissions, │ │
|
||||
│ │ Suppliers, Projects, Departments, │ │
|
||||
│ │ Forms, Contracts, Reports │ │
|
||||
│ │ Middleware: GlobalException, Serilog, CORS, JWT │ │
|
||||
│ │ Authorization: MenuPermissionHandler (policy-based) │ │
|
||||
│ │ Services: CurrentUserService, WebHostEnvLocator │ │
|
||||
│ │ wwwroot/templates/ (5 .docx/.xlsx) │ │
|
||||
│ └────────────────────┬───────────────────────────────────┘ │
|
||||
└───────────────────────┼──────────────────────────────────────┘
|
||||
│ MediatR ISender.Send(cmd)
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ APPLICATION LAYER │
|
||||
│ SolutionErp.Application │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ Auth │ │ Master │ │ Permissions │ │
|
||||
│ │ (Login/Me) │ │ (CRUD 3 │ │ (Menu tree + │ │
|
||||
│ │ │ │ entity) │ │ matrix) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
||||
│ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ Forms │ │ Contracts │ ┌──────────────┐ │
|
||||
│ │ (Render │ │ (Workflow 9 │ │ Reports │ │
|
||||
│ │ engine) │ │ phase, Inbox) │ │ (Dashboard + │ │
|
||||
│ └──────────────┘ └──────────────────┘ │ Excel exp) │ │
|
||||
│ └──────────────┘ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Common: Exceptions, Behaviors (ValidationPipeline), │ │
|
||||
│ │ Interfaces (IDbContext, ICurrentUser, ...), │ │
|
||||
│ │ Models (PagedResult) │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└───────┬──────────────────────────────────────────┬───────────┘
|
||||
│ depends on interface │ runs on
|
||||
▼ ▼
|
||||
┌──────────────────────┐ ┌───────────────────────────┐
|
||||
│ DOMAIN LAYER │ │ INFRASTRUCTURE LAYER │
|
||||
│ SolutionErp.Domain │ │ SolutionErp.Infrastructure│
|
||||
│ │ │ │
|
||||
│ Common/BaseEntity │ │ Persistence/ApplicationD │
|
||||
│ Contracts/ │ │ bContext + Migrations │
|
||||
│ Forms/ │ │ Identity/JwtTokenService │
|
||||
│ Identity/ │ │ Forms/Docx+XlsxRenderer │
|
||||
│ Master/ │ │ Services/ContractWorkflow│
|
||||
│ │ │ + ContractCodeGenerator│
|
||||
│ Enum + value object │ │ Reports/ExcelExporter │
|
||||
└──────────────────────┘ │ Services/DateTimeService │
|
||||
↑ └────────────┬──────────────┘
|
||||
│ references │
|
||||
└───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────┐
|
||||
│ DATA TIER │
|
||||
│ SQL Server 2022 │
|
||||
│ (dbo schema, 19 bảng)│
|
||||
└───────────────────────┘
|
||||
```
|
||||
|
||||
## 2. Request lifecycle (1 POST/api/contracts)
|
||||
|
||||
```
|
||||
1. Browser → POST /api/contracts { type, supplierId, ... }
|
||||
2. Vite → proxy tới :5443
|
||||
3. JwtBearerMiddleware → validate token, set ClaimsPrincipal
|
||||
4. Routing → ContractsController.Create(cmd)
|
||||
5. MediatR.Send(CreateContractCommand)
|
||||
6. ValidationBehavior (pipeline) → FluentValidation run
|
||||
7. CreateContractCommandHandler
|
||||
├─ check Supplier/Project exists (IApplicationDbContext)
|
||||
├─ new Contract entity + set DrafterUserId (ICurrentUser)
|
||||
├─ set SlaDeadline (IDateTime + IContractWorkflowService.GetPhaseSla)
|
||||
├─ db.Contracts.Add(...)
|
||||
└─ SaveChangesAsync
|
||||
├─ AuditingInterceptor sets CreatedAt/CreatedBy
|
||||
└─ EF Core INSERT → SQL Server
|
||||
8. Return Guid id
|
||||
9. Controller → 201 Created + Location header
|
||||
10. Axios interceptor (FE) → TanStack Query invalidate + UI update
|
||||
```
|
||||
|
||||
## 3. Workflow state machine (Phase 3)
|
||||
|
||||
Xem full ở [`workflow-contract.md`](workflow-contract.md).
|
||||
|
||||
```
|
||||
DangChon → DangSoanThao → DangGopY → DangDamPhan → DangInKy →
|
||||
→ DangKiemTraCCM → DangTrinhKy → DangDongDau → DaPhatHanh
|
||||
|
||||
Alternates: → TuChoi (từ DangSoanThao)
|
||||
→ DangSoanThao (revise từ bất kỳ phase duyệt)
|
||||
|
||||
Bypass CĐT (BypassProcurementAndCCM=true):
|
||||
DangInKy → DangTrinhKy (skip CCM)
|
||||
```
|
||||
|
||||
**Code generator trigger:** khi `targetPhase = DangDongDau` + `MaHopDong IS NULL` → gen format RG-001 với transaction SERIALIZABLE.
|
||||
|
||||
## 4. Permission model
|
||||
|
||||
```
|
||||
User ──(AspNetUserRoles)── Role ──(Permissions)── MenuItem
|
||||
│
|
||||
├── CanRead
|
||||
├── CanCreate
|
||||
├── CanUpdate
|
||||
└── CanDelete
|
||||
```
|
||||
|
||||
**Resolution:**
|
||||
- Login → JWT chứa claims (sub, email, roles)
|
||||
- `/api/menus/me` → query Permissions theo roleIds, **union OR** CRUD flags, filter tree theo CanRead
|
||||
- FE cache menu trong `AuthContext` + localStorage
|
||||
- Mỗi API action: `[Authorize(Policy = "{MenuKey}.{Action}")]` → `MenuPermissionHandler` check
|
||||
|
||||
**Admin bypass:** role `Admin` luôn pass mọi policy (seed default full CRUD).
|
||||
|
||||
## 5. Data flow — "tạo HĐ và chạy hết workflow"
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor D as Drafter
|
||||
actor M as Manager (PD/PM)
|
||||
actor C as CCM
|
||||
actor B as BOD
|
||||
actor H as HRA
|
||||
|
||||
participant FE as fe-user
|
||||
participant API
|
||||
participant WF as WorkflowService
|
||||
participant CG as CodeGenerator
|
||||
participant DB
|
||||
|
||||
D->>FE: POST /contracts/new
|
||||
FE->>API: POST /api/contracts
|
||||
API->>DB: INSERT Contracts (Phase=DangSoanThao, SLA=+7d)
|
||||
API-->>FE: 201 {id}
|
||||
|
||||
D->>FE: Click "Submit → góp ý"
|
||||
FE->>API: POST /contracts/{id}/transitions {target:3}
|
||||
API->>WF: Transition(contract, 3, roles=[Drafter])
|
||||
WF->>WF: Check adjacency + role
|
||||
WF->>DB: INSERT ContractApproval + UPDATE Contract Phase=3
|
||||
API-->>FE: 204
|
||||
|
||||
M->>FE: Inbox → click HĐ → góp ý + "Chuyển tiếp"
|
||||
FE->>API: POST /transitions {target:4}
|
||||
API->>WF: Transition → Phase 4
|
||||
Note over WF: Chạy tương tự qua 5,6,7
|
||||
|
||||
B->>FE: Duyệt → target:8 DangDongDau
|
||||
FE->>API: POST /transitions {target:8}
|
||||
API->>WF: Transition
|
||||
WF->>CG: GenerateAsync(contract, project, supplier)
|
||||
CG->>DB: BEGIN TRAN SERIALIZABLE
|
||||
CG->>DB: SELECT/UPDATE ContractCodeSequences
|
||||
CG->>DB: COMMIT
|
||||
CG-->>WF: "FLOCK 01/HĐGK/SOL&PVL/03"
|
||||
WF->>DB: UPDATE Contract SET MaHopDong, Phase=8
|
||||
API-->>FE: 204
|
||||
|
||||
H->>FE: Click đóng dấu → target:9
|
||||
FE->>API: POST /transitions {target:9}
|
||||
API->>WF: Transition (role HrAdmin)
|
||||
WF->>DB: UPDATE Phase=9 (DaPhatHanh)
|
||||
API-->>FE: 204
|
||||
```
|
||||
|
||||
## 6. Deployment architecture (Phase 5 — planned)
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ Internet / Corp LAN │
|
||||
└──────────────┬──────────────┘
|
||||
│
|
||||
┌──────────▼──────────┐
|
||||
│ IIS (Win Server) │
|
||||
│ URL Rewrite / ARR │
|
||||
└──────────┬──────────┘
|
||||
┌─────────────┼─────────────┐
|
||||
│ │ │
|
||||
┌──────▼──────┐ ┌───▼────┐ ┌──────▼──────┐
|
||||
│ fe-admin │ │ Api │ │ fe-user │
|
||||
│ static files│ │ Kestrel│ │ static files│
|
||||
│ (dist/) │ │ :5443 │ │ (dist/) │
|
||||
└─────────────┘ └───┬────┘ └─────────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ SQL Server │
|
||||
│ (same host OR │
|
||||
│ separate VM) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
- IIS app pool riêng cho Api, Integrated Managed Pipeline, .NET CLR disabled (hosting .NET 10 OOP)
|
||||
- Static files 2 FE deploy vào `C:\inetpub\wwwroot\solution-erp-admin\` + `...user\`
|
||||
- HTTPS: Let's Encrypt qua win-acme (hoặc cert mua)
|
||||
- Backup SQL: daily full + 15min log → D:\Backups
|
||||
|
||||
## 7. Skill library (AI agent support)
|
||||
|
||||
`.claude/skills/` có 3 skill chuyên biệt:
|
||||
|
||||
| Skill | Dùng khi |
|
||||
|---|---|
|
||||
| [`contract-workflow`](../.claude/skills/contract-workflow/SKILL.md) | Debug chuyển phase, 403, mã HĐ, bypass CĐT |
|
||||
| [`form-engine`](../.claude/skills/form-engine/SKILL.md) | Render template, upload, placeholder không replace |
|
||||
| [`permission-matrix`](../.claude/skills/permission-matrix/SKILL.md) | Access denied, menu không hiện, gán role |
|
||||
|
||||
Claude auto-invoke theo description matching.
|
||||
|
||||
## 8. Non-functional
|
||||
|
||||
| Aspect | Current | Phase 5 target |
|
||||
|---|---|---|
|
||||
| Availability | dev-only | 99.5% (IIS restart, SQL HA optional) |
|
||||
| Latency | <200ms P95 local | <500ms P95 prod |
|
||||
| Concurrency | unrestricted | rate limit 100 req/min/IP |
|
||||
| Observability | Serilog console | + file rolling daily + Seq/ELK |
|
||||
| Security | JWT + HTTPS dev | + rate limit + audit log + CSP |
|
||||
|
||||
## 9. Liên quan
|
||||
|
||||
- [`rules.md`](rules.md) — coding conventions
|
||||
- [`database/database-guide.md`](database/database-guide.md) — DB schema chi tiết
|
||||
- [`flows/`](flows/) — per-feature sequence diagrams
|
||||
- [`workflow-contract.md`](workflow-contract.md) — state machine spec
|
||||
- [`forms-spec.md`](forms-spec.md) — 8 form catalog
|
||||
@ -158,13 +158,25 @@
|
||||
|
||||
## Phase 4 — Reporting + Polish (T10-11)
|
||||
|
||||
- [ ] Dashboard admin: số HĐ theo phase, top NCC, top dự án, tổng giá trị theo tháng
|
||||
- [ ] Excel export theo bộ lọc (dùng EPPlus)
|
||||
- [ ] Report: HĐ quá hạn SLA bao nhiêu lần theo phase/role
|
||||
- [ ] UX polish: skeleton loader, empty state, error boundary có recovery button
|
||||
### MVP xong (iteration 1)
|
||||
|
||||
- [x] Dashboard admin: 5 KPI (total/active/overdue/published this month/total value) + by phase + top 5 NCC + top 5 dự án + 12 tháng
|
||||
- [x] Excel export HĐ theo filter (phase/supplier/project/date range) qua ClosedXML
|
||||
- [x] BE `GetDashboardStatsQuery` + `ExportContractsToExcelCommand` + ReportsController
|
||||
- [x] FE `DashboardPage` rewrite với `BarChart` tự build (Tailwind only, không thư viện ngoài)
|
||||
- [x] FE `ReportsPage` filter + export
|
||||
- [x] Docs consolidation: `rules.md` + `architecture.md` + `database/schema-diagram.md` + gotchas update
|
||||
|
||||
### Iteration 2 (polish — optional)
|
||||
|
||||
- [ ] SLA overdue report (by role / phase, export Excel)
|
||||
- [ ] Contract audit log export (từng HĐ ra PDF)
|
||||
- [ ] Dashboard user-specific (HĐ của tôi / role của tôi)
|
||||
- [ ] Chart library recharts (nếu cần chart phức tạp)
|
||||
- [ ] UX polish: skeleton loader cho mọi list, empty state có action, error boundary recovery
|
||||
- [ ] Accessibility: keyboard nav, focus trap modal, aria labels
|
||||
- [ ] Dark mode (optional, nếu rảnh)
|
||||
- [ ] Performance: index DB cho query hot (SupplierId, ProjectId, Phase)
|
||||
- [ ] Dark mode
|
||||
- [ ] Performance: explicit index DB cho query hot đã identify
|
||||
- [ ] Tài liệu user guide: quy trình tạo HĐ + duyệt
|
||||
- [ ] UAT với 5-10 HĐ dữ liệu thật từ bộ phận Cung ứng
|
||||
|
||||
|
||||
122
docs/changelog/sessions/2026-04-21-1430-phase4-report.md
Normal file
122
docs/changelog/sessions/2026-04-21-1430-phase4-report.md
Normal file
@ -0,0 +1,122 @@
|
||||
# Session 2026-04-21 14:30 — Phase 4 Report MVP + Docs Consolidation
|
||||
|
||||
**Dev:** Claude (Opus 4.7)
|
||||
**Duration:** ~1h
|
||||
**Base commit:** `7e957a7`
|
||||
|
||||
## Làm được
|
||||
|
||||
### Chunk K — BE Dashboard + Excel export
|
||||
|
||||
- `Application/Reports/Dtos/DashboardStatsDto.cs`: DashboardStats + PhaseCount + SupplierCount + ProjectCount + MonthlyValue
|
||||
- `Application/Reports/Queries/GetDashboardStats` Handler:
|
||||
- Counts: Total / Active (not final) / Overdue (SlaDeadline < now) / PublishedThisMonth
|
||||
- TotalValueActive: SUM GiaTri của active contracts
|
||||
- ByPhase: group theo phase
|
||||
- TopSuppliers, TopProjects: top 5 theo count + sum value
|
||||
- MonthlyValue: fill 12 tháng liên tục (kể cả tháng rỗng)
|
||||
- `Application/Reports/Services/IContractExcelExporter`
|
||||
- `Infrastructure/Reports/ContractExcelExporter`:
|
||||
- ClosedXML workbook 10 cột (STT, Mã HĐ, Tên, Loại, Phase, NCC, Dự án, Giá trị, SLA, Ngày tạo)
|
||||
- Header style (bold + background blue)
|
||||
- Number format `#,##0` cho cột giá trị
|
||||
- Formula SUM tổng cuối bảng
|
||||
- Auto-fit columns + freeze header row
|
||||
- `Application/Reports/Commands/ExportContractsToExcelCommand` (filter phase/supplier/project/date range)
|
||||
- `Api/Controllers/ReportsController`: GET `/api/reports/dashboard`, GET `/api/reports/contracts/export`
|
||||
- DI register IContractExcelExporter (Scoped)
|
||||
|
||||
### Chunk L — FE admin Dashboard + Reports
|
||||
|
||||
- `types/reports.ts`: DashboardStats type
|
||||
- `components/BarChart.tsx`: generic horizontal bar chart — chỉ Tailwind, không thư viện ngoài (tránh bloat bundle)
|
||||
- `pages/DashboardPage.tsx` rewrite:
|
||||
- 5 StatCard KPI: Tổng HĐ, Đang xử lý, Quá hạn SLA, Phát hành tháng, Tổng giá trị
|
||||
- Section By Phase (bar với PhaseBadge)
|
||||
- Section Monthly Value 12 tháng (BarChart)
|
||||
- Section Top 5 NCC + Top 5 dự án
|
||||
- Skeleton loader khi loading
|
||||
- Money formatter (tỷ / tr / số thường)
|
||||
- `pages/ReportsPage.tsx` MỚI: form filter (phase / from / to) → export Excel button
|
||||
- Route `/reports` added vào App.tsx
|
||||
|
||||
### Docs Consolidation (theo yêu cầu user)
|
||||
|
||||
- `docs/rules.md` MỚI: 9 section coding conventions (ngôn ngữ, BE Clean Arch, CQRS, validation, error, async, entity, DI, packages, FE React/TS, database, git, docs)
|
||||
- `docs/architecture.md` MỚI: layered diagram, request lifecycle, workflow state machine, permission model, data flow sequence, deployment, non-functional
|
||||
- `docs/database/schema-diagram.md` MỚI: ERD full 19 tables với mermaid, data flow diagram, lifecycle 1 HĐ, index strategy, relationship cardinality, soft delete behavior, SQL queries điển hình, migration history
|
||||
- `docs/gotchas.md` UPDATE: thêm 7 pitfalls mới (20-26: Claude Code harness, DI register, Contract workflow, Permission)
|
||||
- Session log này
|
||||
|
||||
## E2E verified
|
||||
|
||||
```bash
|
||||
GET /api/reports/dashboard → 200
|
||||
totalContracts: 1, activeContracts: 0, overdueContracts: 0,
|
||||
publishedThisMonth: 1, totalValueActive: 0,
|
||||
byPhase: [{phase:9, count:1}],
|
||||
topSuppliers: [{supplierName:"Cong ty PVL Test", count:1, totalValue:150M}],
|
||||
topProjects: [...],
|
||||
monthlyValue: [12 rows, fill 0 if empty month]
|
||||
|
||||
GET /api/reports/contracts/export → HTTP 200, file xlsx 7229 bytes
|
||||
Microsoft Excel 2007+ OK
|
||||
|
||||
TS check fe-admin: pass
|
||||
```
|
||||
|
||||
## Bug gặp + fix
|
||||
|
||||
| Bug | Fix |
|
||||
|---|---|
|
||||
| Edit tool "File not read" sau system-reminder | Read lại + Write full |
|
||||
| TS unused import ContractPhaseLabel | Remove unused import |
|
||||
| DI thiếu register IContractExcelExporter | Add `services.AddScoped<IContractExcelExporter, ContractExcelExporter>()` |
|
||||
|
||||
## Handoff session sau
|
||||
|
||||
### Phase 4 iteration 2 (polish)
|
||||
|
||||
- [ ] SLA overdue report (by role / phase, export Excel)
|
||||
- [ ] Contract audit log export (từng HĐ ra PDF)
|
||||
- [ ] Dashboard user-specific (HĐ tôi / role tôi)
|
||||
- [ ] Chart library đơn giản (recharts hoặc tiếp tục Tailwind)
|
||||
|
||||
### Phase 5 — Production (T12-13)
|
||||
|
||||
- [ ] CI/CD Gitea Actions `.gitea/workflows/deploy.yml`
|
||||
- [ ] IIS setup script + runbook
|
||||
- [ ] HTTPS cert (win-acme)
|
||||
- [ ] Rate limit middleware
|
||||
- [ ] Security audit OWASP top 10
|
||||
- [ ] Backup SQL runbook
|
||||
|
||||
### Phase 3 iteration 2 (vẫn còn thiếu)
|
||||
|
||||
- [ ] SlaExpiryJob BackgroundService auto-approve
|
||||
- [ ] Email + in-app notification
|
||||
- [ ] Upload attachment endpoint + FE
|
||||
- [ ] RowVersion concurrency
|
||||
- [ ] Render HĐ docx khi tạo
|
||||
|
||||
### Phase 2 iteration 2 (vẫn còn)
|
||||
|
||||
- [ ] Convert 3 file .doc
|
||||
- [ ] Field spec JSON + form builder
|
||||
- [ ] {{#loop}} block
|
||||
- [ ] PDF convert
|
||||
|
||||
### Blocker
|
||||
|
||||
- ⏳ Gitea remote URL (vẫn chờ — sẽ quyết Phase 5)
|
||||
|
||||
## Thông số cumulative
|
||||
|
||||
| | Phase 2 | Phase 3 | **Phase 4 MVP** |
|
||||
|---|---:|---:|---:|
|
||||
| BE LOC | ~1900 | ~2700 | **~3100** |
|
||||
| DB tables | 14 | 19 | 19 (không thêm table mới) |
|
||||
| API endpoints | ~23 | ~31 | **~33** |
|
||||
| FE pages | 9+5 | 14 | **16** (+Reports, +Dashboard rewrite) |
|
||||
| Docs files | 24 | 26 | **30** (+rules, architecture, schema-diagram) |
|
||||
| Commits | 5 | 6 | **7** (sắp) |
|
||||
358
docs/database/schema-diagram.md
Normal file
358
docs/database/schema-diagram.md
Normal file
@ -0,0 +1,358 @@
|
||||
# Schema Diagram — Luồng DB SOLUTION_ERP
|
||||
|
||||
> ERD đầy đủ + mối quan hệ 19 table sau Phase 3. Mermaid render ở VS Code / GitHub / Gitea.
|
||||
|
||||
## 1. Full ERD
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
Users ||--o{ UserRoles : "has"
|
||||
Roles ||--o{ UserRoles : "assigned to"
|
||||
Users ||--o{ UserClaims : "has"
|
||||
Users ||--o{ UserLogins : "has"
|
||||
Users ||--o{ UserTokens : "has"
|
||||
Roles ||--o{ RoleClaims : "has"
|
||||
|
||||
Roles ||--o{ Permissions : "grants"
|
||||
MenuItems ||--o{ Permissions : "controlled by"
|
||||
MenuItems ||--o{ MenuItems : "parent-of"
|
||||
|
||||
Suppliers ||--o{ Contracts : "party-B"
|
||||
Projects ||--o{ Contracts : "belongs-to"
|
||||
Departments ||--o{ Contracts : "drafted-in"
|
||||
Users ||--o{ Contracts : "drafter"
|
||||
ContractTemplates ||--o{ Contracts : "uses"
|
||||
|
||||
Contracts ||--o{ ContractApprovals : "history"
|
||||
Contracts ||--o{ ContractComments : "thread"
|
||||
Contracts ||--o{ ContractAttachments : "files"
|
||||
Users ||--o{ ContractApprovals : "approved-by"
|
||||
Users ||--o{ ContractComments : "author"
|
||||
|
||||
Users {
|
||||
uniqueidentifier Id PK
|
||||
nvarchar FullName "200"
|
||||
nvarchar Email "UK"
|
||||
nvarchar PasswordHash
|
||||
nvarchar RefreshToken "512"
|
||||
datetime2 RefreshTokenExpiresAt
|
||||
bit IsActive
|
||||
datetime2 CreatedAt
|
||||
}
|
||||
|
||||
Roles {
|
||||
uniqueidentifier Id PK
|
||||
nvarchar Name "UK"
|
||||
nvarchar Description "500"
|
||||
datetime2 CreatedAt
|
||||
}
|
||||
|
||||
MenuItems {
|
||||
nvarchar Key PK "50"
|
||||
nvarchar Label "200"
|
||||
nvarchar ParentKey FK "NULL if root"
|
||||
int Order
|
||||
nvarchar Icon "50"
|
||||
}
|
||||
|
||||
Permissions {
|
||||
uniqueidentifier Id PK
|
||||
uniqueidentifier RoleId FK
|
||||
nvarchar MenuKey FK "50"
|
||||
bit CanRead
|
||||
bit CanCreate
|
||||
bit CanUpdate
|
||||
bit CanDelete
|
||||
}
|
||||
|
||||
Suppliers {
|
||||
uniqueidentifier Id PK
|
||||
nvarchar Code "UK 50"
|
||||
nvarchar Name "200"
|
||||
int Type "NCC/NTP/TD/DVDV/CDT"
|
||||
nvarchar TaxCode "20"
|
||||
nvarchar Phone "30"
|
||||
nvarchar Email "100"
|
||||
nvarchar Address "500"
|
||||
bit IsDeleted
|
||||
}
|
||||
|
||||
Projects {
|
||||
uniqueidentifier Id PK
|
||||
nvarchar Code "UK 50"
|
||||
nvarchar Name "200"
|
||||
date StartDate
|
||||
date EndDate
|
||||
uniqueidentifier ManagerUserId FK
|
||||
decimal BudgetTotal "18,2"
|
||||
}
|
||||
|
||||
Departments {
|
||||
uniqueidentifier Id PK
|
||||
nvarchar Code "UK 50"
|
||||
nvarchar Name "200"
|
||||
uniqueidentifier ManagerUserId FK
|
||||
}
|
||||
|
||||
ContractTemplates {
|
||||
uniqueidentifier Id PK
|
||||
nvarchar FormCode "UK 50"
|
||||
nvarchar Name "200"
|
||||
int ContractType "nullable"
|
||||
nvarchar FileName "255"
|
||||
nvarchar StoragePath "500"
|
||||
nvarchar Format "docx/xlsx"
|
||||
nvarchar FieldSpec "max JSON"
|
||||
bit IsActive
|
||||
}
|
||||
|
||||
ContractClauses {
|
||||
uniqueidentifier Id PK
|
||||
nvarchar Code "UK 50"
|
||||
nvarchar Name "200"
|
||||
nvarchar Content "max rich text"
|
||||
int Version
|
||||
bit IsActive
|
||||
}
|
||||
|
||||
Contracts {
|
||||
uniqueidentifier Id PK
|
||||
nvarchar MaHopDong "UK filtered 100"
|
||||
nvarchar TenHopDong "500"
|
||||
int Type "ContractType enum"
|
||||
int Phase "9 state"
|
||||
uniqueidentifier SupplierId FK
|
||||
uniqueidentifier ProjectId FK
|
||||
uniqueidentifier DepartmentId FK
|
||||
uniqueidentifier DrafterUserId FK
|
||||
uniqueidentifier TemplateId FK
|
||||
decimal GiaTri "18,2"
|
||||
bit BypassProcurementAndCCM
|
||||
datetime2 SlaDeadline
|
||||
bit SlaWarningSent
|
||||
nvarchar DraftData "max JSON"
|
||||
bit IsDeleted
|
||||
}
|
||||
|
||||
ContractApprovals {
|
||||
uniqueidentifier Id PK
|
||||
uniqueidentifier ContractId FK
|
||||
int FromPhase
|
||||
int ToPhase
|
||||
uniqueidentifier ApproverUserId FK "NULL=system"
|
||||
int Decision "Pending/Approve/Reject/AutoApprove"
|
||||
nvarchar Comment "1000"
|
||||
datetime2 ApprovedAt
|
||||
}
|
||||
|
||||
ContractComments {
|
||||
uniqueidentifier Id PK
|
||||
uniqueidentifier ContractId FK
|
||||
uniqueidentifier UserId FK
|
||||
int Phase "phase luc comment"
|
||||
nvarchar Content "2000"
|
||||
datetime2 CreatedAt
|
||||
}
|
||||
|
||||
ContractAttachments {
|
||||
uniqueidentifier Id PK
|
||||
uniqueidentifier ContractId FK
|
||||
nvarchar FileName "255"
|
||||
nvarchar StoragePath "500"
|
||||
bigint FileSize
|
||||
nvarchar ContentType "100"
|
||||
int Purpose "DraftExport/ScannedSigned/SealedCopy"
|
||||
nvarchar Note "500"
|
||||
}
|
||||
|
||||
ContractCodeSequences {
|
||||
nvarchar Prefix PK "200"
|
||||
int LastSeq
|
||||
datetime2 UpdatedAt
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Luồng dữ liệu chính (data flow diagram)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph IDENTITY ["🔐 Identity domain (seed lần đầu)"]
|
||||
U[Users] -->|N-M| R[Roles]
|
||||
R --> P[Permissions]
|
||||
P --> MI[MenuItems]
|
||||
end
|
||||
|
||||
subgraph MASTER ["📋 Master data (admin CRUD)"]
|
||||
S[Suppliers]
|
||||
PR[Projects]
|
||||
DE[Departments]
|
||||
end
|
||||
|
||||
subgraph FORMS ["📄 Form templates (seed)"]
|
||||
CT[ContractTemplates]
|
||||
CC[ContractClauses]
|
||||
end
|
||||
|
||||
subgraph CONTRACT ["📝 Contract workflow (Phase 3 core)"]
|
||||
C[Contracts]
|
||||
CA[ContractApprovals]
|
||||
CCM[ContractComments]
|
||||
CAT[ContractAttachments]
|
||||
CCS[ContractCodeSequences]
|
||||
end
|
||||
|
||||
U -.Drafter.-> C
|
||||
S --> C
|
||||
PR --> C
|
||||
DE --> C
|
||||
CT --> C
|
||||
C --> CA
|
||||
C --> CCM
|
||||
C --> CAT
|
||||
C -.gen when DangDongDau.-> CCS
|
||||
```
|
||||
|
||||
## 3. Vòng đời 1 HĐ — data changes
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Create[POST /contracts]
|
||||
Create --> C1["Contracts INSERT<br/>Phase=2, SLA=+7d"]
|
||||
|
||||
Transition1[Transition 2→3]
|
||||
Transition1 --> C2["UPDATE Phase=3<br/>INSERT ContractApprovals"]
|
||||
|
||||
Comment[POST /comments]
|
||||
Comment --> C3[INSERT ContractComments]
|
||||
|
||||
Transition2[Transition 3→4→5→6→7]
|
||||
Transition2 --> C4["UPDATE Phase + SlaDeadline<br/>INSERT ContractApprovals"]
|
||||
|
||||
Transition3[Transition 7→8 BOD ký]
|
||||
Transition3 --> CG["ContractCodeGenerator<br/>SERIALIZABLE tran<br/>UPSERT ContractCodeSequences"]
|
||||
CG --> C5["UPDATE Contract<br/>SET MaHopDong='FLOCK 01/HĐGK/SOL&PVL/01',<br/>Phase=8"]
|
||||
|
||||
Transition4[Transition 8→9 HRA phát hành]
|
||||
Transition4 --> C6["UPDATE Phase=9, SlaDeadline=NULL<br/>INSERT ContractApprovals"]
|
||||
```
|
||||
|
||||
## 4. Index strategy
|
||||
|
||||
| Table | Index | Purpose |
|
||||
|---|---|---|
|
||||
| Contracts | `UX_Contracts_MaHopDong` filtered `WHERE [MaHopDong] IS NOT NULL` | Unique mã HĐ |
|
||||
| Contracts | `IX_Contracts_Phase_IsDeleted` | Inbox query theo phase |
|
||||
| Contracts | `IX_Contracts_SupplierId` | Filter HĐ theo NCC |
|
||||
| Contracts | `IX_Contracts_ProjectId` | Filter HĐ theo dự án |
|
||||
| Contracts | `IX_Contracts_SlaDeadline` | SLA expiry job query |
|
||||
| ContractApprovals | `IX_ContractApprovals_ContractId_ApprovedAt` | Timeline query theo HĐ |
|
||||
| ContractComments | `IX_ContractComments_ContractId_CreatedAt` | Thread load |
|
||||
| ContractAttachments | `IX_ContractAttachments_ContractId` | Attachments load |
|
||||
| Suppliers/Projects/Departments | `UX_{Table}_Code` | Unique business code |
|
||||
| Permissions | `UX_Permissions_RoleId_MenuKey` | 1 row / role / menu |
|
||||
| MenuItems | `IX_MenuItems_ParentKey` | Tree query |
|
||||
|
||||
Chi tiết + cheatsheet SQL: [`database-guide.md`](database-guide.md).
|
||||
|
||||
## 5. Relationship cardinality
|
||||
|
||||
| Parent → Child | Cardinality | OnDelete | Ghi chú |
|
||||
|---|---|---|---|
|
||||
| Contract → ContractApproval | 1 - N | Cascade | Xóa HĐ → xóa lịch sử (nhưng mức delete bị chặn sau DangInKy) |
|
||||
| Contract → ContractComment | 1 - N | Cascade | — |
|
||||
| Contract → ContractAttachment | 1 - N | Cascade | File vật lý vẫn còn trong `wwwroot/uploads/`, cleanup riêng |
|
||||
| Supplier → Contract | 1 - N | Restrict | Không xóa Supplier nếu còn HĐ tham chiếu |
|
||||
| Project → Contract | 1 - N | Restrict | Tương tự |
|
||||
| Role → Permission | 1 - N | Cascade | Xóa role → clear permissions |
|
||||
| MenuItem → Permission | 1 - N | Cascade | — |
|
||||
| MenuItem → MenuItem (self-ref) | 1 - N | Restrict | Parent-child menu |
|
||||
|
||||
## 6. Soft delete behavior
|
||||
|
||||
Mọi entity extend `AuditableEntity`:
|
||||
- Delete qua `db.X.Remove(entity)` → `AuditingInterceptor` chuyển `EntityState.Deleted` → `Modified`, set `IsDeleted=true, DeletedAt, DeletedBy`
|
||||
- Query filter `HasQueryFilter(x => !x.IsDeleted)` tự động filter ra
|
||||
- Để query bao gồm soft-deleted: `.IgnoreQueryFilters()`
|
||||
|
||||
Entity list áp dụng:
|
||||
- Supplier, Project, Department
|
||||
- Contract
|
||||
- ContractTemplate, ContractClause
|
||||
|
||||
KHÔNG soft delete (cascade hoặc keep):
|
||||
- ContractApproval, ContractComment, ContractAttachment — cascade khi Contract xóa
|
||||
- Permission, MenuItem — cascade khi Role xóa
|
||||
- ContractCodeSequence — không bao giờ xóa (giữ history seq)
|
||||
- Identity tables (Users, Roles, ...) — Identity không support soft delete built-in
|
||||
|
||||
## 7. Truy vấn tiêu biểu
|
||||
|
||||
### Inbox HĐ chờ role của tôi
|
||||
|
||||
```sql
|
||||
-- Tương đương GetMyInboxQuery
|
||||
SELECT c.Id, c.MaHopDong, c.TenHopDong, c.Phase, c.SlaDeadline, s.Name AS SupplierName, p.Name AS ProjectName
|
||||
FROM Contracts c
|
||||
INNER JOIN Suppliers s ON c.SupplierId = s.Id
|
||||
INNER JOIN Projects p ON c.ProjectId = p.Id
|
||||
WHERE c.IsDeleted = 0
|
||||
AND c.Phase IN (/* phase eligible cho role hiện tại */)
|
||||
ORDER BY c.SlaDeadline ASC;
|
||||
```
|
||||
|
||||
### Dashboard stats
|
||||
|
||||
```sql
|
||||
-- Tổng + active + overdue
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM Contracts WHERE IsDeleted = 0) AS Total,
|
||||
(SELECT COUNT(*) FROM Contracts WHERE IsDeleted = 0 AND Phase NOT IN (9, 99)) AS Active,
|
||||
(SELECT COUNT(*) FROM Contracts WHERE IsDeleted = 0 AND Phase NOT IN (9, 99) AND SlaDeadline < GETUTCDATE()) AS Overdue;
|
||||
|
||||
-- By phase
|
||||
SELECT Phase, COUNT(*) FROM Contracts WHERE IsDeleted = 0 GROUP BY Phase;
|
||||
|
||||
-- Top 5 NCC
|
||||
SELECT TOP 5 c.SupplierId, s.Name, COUNT(*) AS Cnt, SUM(c.GiaTri) AS TotalValue
|
||||
FROM Contracts c
|
||||
INNER JOIN Suppliers s ON c.SupplierId = s.Id
|
||||
WHERE c.IsDeleted = 0
|
||||
GROUP BY c.SupplierId, s.Name
|
||||
ORDER BY COUNT(*) DESC;
|
||||
```
|
||||
|
||||
### Gen mã HĐ atomic
|
||||
|
||||
```sql
|
||||
BEGIN TRAN;
|
||||
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
|
||||
|
||||
MERGE ContractCodeSequences AS tgt
|
||||
USING (SELECT @Prefix AS Prefix) AS src ON tgt.Prefix = src.Prefix
|
||||
WHEN MATCHED THEN UPDATE SET LastSeq = LastSeq + 1, UpdatedAt = GETUTCDATE()
|
||||
WHEN NOT MATCHED THEN INSERT (Prefix, LastSeq, UpdatedAt) VALUES (@Prefix, 1, GETUTCDATE());
|
||||
|
||||
SELECT LastSeq FROM ContractCodeSequences WHERE Prefix = @Prefix;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
(EF Core impl dùng `UPDATE + IF ROWCOUNT = 0 INSERT` thay MERGE — tương đương nhưng an toàn hơn với SERIALIZABLE).
|
||||
|
||||
## 8. Migration lịch sử
|
||||
|
||||
| # | Migration | Tables added |
|
||||
|---|---|---|
|
||||
| 1 | `Init` | 7 Identity tables |
|
||||
| 2 | `AddMasterData` | Suppliers, Projects, Departments |
|
||||
| 3 | `AddPermissions` | MenuItems, Permissions |
|
||||
| 4 | `AddForms` | ContractTemplates, ContractClauses |
|
||||
| 5 | `AddContractsWorkflow` | Contracts, ContractApprovals, ContractComments, ContractAttachments, ContractCodeSequences |
|
||||
|
||||
Tổng: **19 bảng** (+ `__EFMigrationsHistory` hệ thống).
|
||||
|
||||
## 9. Liên quan
|
||||
|
||||
- [`database-guide.md`](database-guide.md) — conventions + migration workflow + cheatsheet đầy đủ
|
||||
- [`../architecture.md`](../architecture.md) — layered architecture + data flow
|
||||
- [`../workflow-contract.md`](../workflow-contract.md) — state machine spec
|
||||
- [`../flows/`](../flows/) — sequence diagrams
|
||||
198
docs/gotchas.md
198
docs/gotchas.md
@ -1,6 +1,6 @@
|
||||
# Gotchas — SOLUTION_ERP
|
||||
|
||||
> Các bẫy, pitfall đã gặp + cách xử lý. Đọc trước khi debug tương tự để không mất thời gian.
|
||||
> Bẫy/pitfall đã gặp + cách xử lý. Đọc trước khi debug tương tự để không mất thời gian. Cập nhật liên tục khi gặp bug mới.
|
||||
|
||||
## Tech stack constraints (.NET 10 + TS 6 + Vite 8)
|
||||
|
||||
@ -8,180 +8,188 @@
|
||||
|
||||
**Triệu chứng:** `Unable to resolve service for type 'MediatR.IMediator'` — `AddMediatR` vẫn chạy nhưng không register IMediator.
|
||||
|
||||
**Nguyên nhân:** MediatR v14 (late 2025) refactored, extension methods khác.
|
||||
|
||||
**Fix:** Downgrade `<PackageReference Include="MediatR" Version="12.4.1" />`. Khi đó `RequestHandlerDelegate<TResponse>` là delegate không tham số (v14 có thêm CancellationToken param).
|
||||
**Fix:** Pin `MediatR 12.4.1`. Khi đó `RequestHandlerDelegate<TResponse>` là delegate không tham số (v14 có thêm CancellationToken).
|
||||
|
||||
### 2. Swashbuckle 10.x + Microsoft.OpenApi 2.x breaking change
|
||||
|
||||
**Triệu chứng:** Build fail `The type or namespace 'Models' does not exist in 'Microsoft.OpenApi'`. Swagger endpoint 404.
|
||||
|
||||
**Nguyên nhân:** `.NET 10` template auto-cài `Microsoft.AspNetCore.OpenApi 10` → pull `Microsoft.OpenApi 2.0` → namespace `Microsoft.OpenApi.Models` đã bị remove.
|
||||
**Triệu chứng:** Build fail `The type or namespace 'Models' does not exist in 'Microsoft.OpenApi'`. Swagger 404.
|
||||
|
||||
**Fix:**
|
||||
- Remove `Microsoft.AspNetCore.OpenApi` khỏi Api
|
||||
- Downgrade Swashbuckle về `6.9.0` (compatible với OpenApi 1.x)
|
||||
- Downgrade Swashbuckle về `6.9.0`
|
||||
|
||||
### 3. TypeScript 6 `erasableSyntaxOnly` cấm `enum`
|
||||
|
||||
**Triệu chứng:** `TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.` khi dùng `enum`.
|
||||
|
||||
**Nguyên nhân:** Vite 8 scaffold bật `erasableSyntaxOnly: true` — enum sinh runtime code nên bị cấm.
|
||||
|
||||
**Fix:** Dùng `const + as const + typeof[keyof]` pattern:
|
||||
|
||||
```ts
|
||||
// ❌ Không được
|
||||
export enum SupplierType { NhaCungCap = 1 }
|
||||
|
||||
// ✅ OK
|
||||
export const SupplierType = { NhaCungCap: 1, NhaThauPhu: 2 } as const
|
||||
export const SupplierType = { NhaCungCap: 1 } as const
|
||||
export type SupplierType = typeof SupplierType[keyof typeof SupplierType]
|
||||
```
|
||||
|
||||
### 4. TypeScript 6 deprecate `baseUrl`
|
||||
|
||||
**Triệu chứng:** `TS5101: Option 'baseUrl' is deprecated`.
|
||||
**Fix:** Bỏ `baseUrl` trong tsconfig, chỉ giữ `paths`. Paths resolve relative tsconfig location.
|
||||
|
||||
**Fix:** Bỏ `baseUrl` trong `tsconfig.app.json`, chỉ giữ `paths`. Paths resolve relative to tsconfig location.
|
||||
### 5. Node 22 local vs CI pin 20
|
||||
|
||||
### 5. Node 22 local nhưng CI phải pin 20
|
||||
|
||||
**Bài học NamGroup:** CI build fail trên Node latest, phải downgrade. Dev local dùng Node 22 thoải mái.
|
||||
**Bài học NamGroup:** CI build fail trên Node latest.
|
||||
|
||||
**Fix:**
|
||||
- `package.json` engines: `">=20"` (min only, không upper bound)
|
||||
- `.nvmrc` = `20` (CI dùng)
|
||||
- GitHub/Gitea Actions: `actions/setup-node@v4` với `node-version-file: '.nvmrc'` hoặc hardcode `'20.x'`
|
||||
- `package.json` engines: `">=20"` (min, không upper)
|
||||
- `.nvmrc` = `20` cho CI
|
||||
- GitHub/Gitea Actions: `actions/setup-node@v4` với `node-version: '20.x'`
|
||||
|
||||
## EF Core 10
|
||||
|
||||
### 6. Expression tree không support switch expression
|
||||
|
||||
**Triệu chứng:** `CS8514: An expression tree may not contain a switch expression` khi viết `.AnyAsync(p => action switch { ... })`.
|
||||
**Triệu chứng:** `CS8514: An expression tree may not contain a switch expression`.
|
||||
|
||||
**Fix:** Tách switch ra ngoài, mỗi case gọi query riêng:
|
||||
**Fix:** Tách switch ra ngoài LINQ:
|
||||
|
||||
```csharp
|
||||
// ❌
|
||||
var hasPermission = await query.AnyAsync(p => action switch { "Read" => p.CanRead, ... });
|
||||
|
||||
// ✅
|
||||
var hasPermission = action switch
|
||||
{
|
||||
"Read" => await query.AnyAsync(p => p.CanRead),
|
||||
"Create" => await query.AnyAsync(p => p.CanCreate),
|
||||
...
|
||||
_ => false,
|
||||
};
|
||||
```
|
||||
|
||||
### 7. Design-time DbContext resolve fail
|
||||
|
||||
**Triệu chứng:** `dotnet ef migrations add` → `Unable to resolve service for type 'DbContextOptions<ApplicationDbContext>'`.
|
||||
**Triệu chứng:** `dotnet ef migrations add` → `Unable to resolve service for type 'DbContextOptions<T>'`.
|
||||
|
||||
**Fix:** Tạo `IDesignTimeDbContextFactory<ApplicationDbContext>` trong Infrastructure — EF CLI sẽ dùng factory này thay vì chạy full Host.
|
||||
**Fix:** Tạo `IDesignTimeDbContextFactory<ApplicationDbContext>` trong Infrastructure.
|
||||
|
||||
### 8. `AddDefaultTokenProviders()` không tồn tại trong `AddIdentityCore`
|
||||
### 8. `AddDefaultTokenProviders()` không có trong `AddIdentityCore`
|
||||
|
||||
**Triệu chứng:** Build fail `IdentityBuilder does not contain AddDefaultTokenProviders`.
|
||||
**Fix:** Bỏ call nếu chưa cần password reset. Khi cần, chuyển `AddIdentity` hoặc add package `Microsoft.AspNetCore.Identity.UI`.
|
||||
|
||||
**Nguyên nhân:** `AddIdentityCore` là minimal variant, không include token providers (password reset, email confirmation).
|
||||
## OpenXml / ClosedXML
|
||||
|
||||
**Fix:** Bỏ call `AddDefaultTokenProviders()` nếu chưa cần. Khi cần password reset (Phase 4), chuyển sang `AddIdentity` hoặc add package `Microsoft.AspNetCore.Identity.UI`.
|
||||
### 9. `SpaceProcessingModeValues` namespace
|
||||
|
||||
## OpenXml / ClosedXML (Form Engine Phase 2)
|
||||
|
||||
### 9. `SpaceProcessingModeValues.Preserve` namespace không tìm thấy
|
||||
|
||||
**Triệu chứng:** `CS0103: The name 'SpaceProcessingModeValues' does not exist`.
|
||||
|
||||
**Fix:** Dùng full path `DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve` và wrap trong `EnumValue<>`:
|
||||
**Fix:** Full path + wrap `EnumValue<>`:
|
||||
|
||||
```csharp
|
||||
textElement.Space = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.SpaceProcessingModeValues>(
|
||||
textElement.Space = new DocumentFormat.OpenXml.EnumValue<
|
||||
DocumentFormat.OpenXml.SpaceProcessingModeValues>(
|
||||
DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve);
|
||||
```
|
||||
|
||||
### 10. Placeholder `{{field}}` bị split giữa 2 `<w:t>` elements
|
||||
### 10. Placeholder `{{field}}` bị split runs
|
||||
|
||||
**Vấn đề:** Word hay split text thành nhiều run (style, typo check), placeholder `{{giaTri}}` có thể bị chia thành `{{gia` + `Tri}}` nằm trong 2 `<w:t>` khác nhau → regex replace miss.
|
||||
**Vấn đề:** Word hay split text thành nhiều `<w:t>` — placeholder miss khi regex replace.
|
||||
|
||||
**Fix (đã implement trong DocxRenderer):** Iterate theo Paragraph, gom text của mọi `<w:t>` trong cùng paragraph → replace → gán lại vào `<w:t>` đầu + clear rest. Giữ run style của text đầu.
|
||||
**Fix:** Iterate Paragraph, gom text tất cả `<w:t>` → replace → gán lại text đầu + clear rest. Đã implement trong `DocxRenderer`.
|
||||
|
||||
### 11. Word COM SaveAs PowerShell type conversion error
|
||||
### 11. Word COM `SaveAs` PowerShell type conversion
|
||||
|
||||
**Triệu chứng:** `Cannot convert "..." value of type "psobject" to type "Object"` khi gọi `$doc.SaveAs([ref]$outPath, [ref]16)`.
|
||||
|
||||
**Fix:** Dùng `SaveAs2` (không đòi ref parameters):
|
||||
**Fix:** Dùng `SaveAs2`:
|
||||
|
||||
```powershell
|
||||
$doc.SaveAs2($outPath, 16) # 16 = wdFormatDocumentDefault (.docx)
|
||||
$doc.SaveAs2($outPath, 16) # 16 = wdFormatDocumentDefault
|
||||
```
|
||||
|
||||
### 12. Word COM stuck/hang
|
||||
|
||||
**Triệu chứng:** Script chạy không xong, process `WINWORD.EXE` còn nhưng CPU idle hoặc high.
|
||||
|
||||
**Nguyên nhân:** Hidden dialog (activation, recovery, template warning) block COM.
|
||||
### 12. Word COM stuck
|
||||
|
||||
**Fix:**
|
||||
- Set `$word.DisplayAlerts = 0` trước khi mở file
|
||||
- `$word.DisplayAlerts = 0`
|
||||
- Nếu stuck → `Get-Process WINWORD | Stop-Process -Force`
|
||||
- Fallback: dùng LibreOffice headless `soffice --headless --convert-to docx file.doc`
|
||||
- Fallback: LibreOffice headless `soffice --headless --convert-to docx`
|
||||
|
||||
## System.Text.Json (ASP.NET Core 10)
|
||||
## System.Text.Json
|
||||
|
||||
### 13. Record constructor deserialization fail với Unicode
|
||||
### 13. Record deserialization fail với Unicode qua CLI
|
||||
|
||||
**Triệu chứng:** POST JSON chứa ký tự tiếng Việt từ Windows bash/curl CLI → 400 "JSON value could not be converted to ... CreateSupplierCommand. Path: $.name".
|
||||
**Triệu chứng:** POST JSON tiếng Việt từ Windows bash/curl → 400 "JSON value could not be converted".
|
||||
|
||||
**Nguyên nhân:** Encoding CLI không đúng UTF-8 khi pass vào `curl -d '{...}'`.
|
||||
|
||||
**Fix:**
|
||||
- Test qua file: `curl --data-binary @payload.json` (file lưu UTF-8 thật)
|
||||
- Không phải bug backend — API handle UTF-8 đúng qua axios/Swagger
|
||||
**Fix:** Dùng `curl --data-binary @file.json` (file UTF-8). API handle đúng qua axios/Swagger.
|
||||
|
||||
## File operations
|
||||
|
||||
### 14. Dropbox sync có thể "revert" file đang edit
|
||||
### 14. Dropbox sync có thể revert file đang edit
|
||||
|
||||
**Triệu chứng:** Write file thành công, build pass, nhưng file thực tế vẫn là nội dung cũ.
|
||||
**Triệu chứng:** Write thành công, build pass, runtime chạy code cũ.
|
||||
|
||||
**Case cụ thể (Phase 1):** Program.cs Write thành công nhưng runtime chạy với default scaffold code.
|
||||
**Fix:** Sau Write quan trọng → Read lại verify. Nếu revert → Write lại.
|
||||
|
||||
**Fix:** Sau Write file quan trọng → Read lại hoặc `head -5` để xác nhận nội dung. Nếu phát hiện revert → Write lại ngay.
|
||||
### 15. `.gitignore` wwwroot rules
|
||||
|
||||
### 15. `.gitignore` wwwroot/uploads/ vs wwwroot/templates/
|
||||
|
||||
**Quy ước:**
|
||||
- `wwwroot/uploads/` → **ignore** (user-uploaded files, không commit)
|
||||
- `wwwroot/templates/` → **commit** (template files là source of truth, phải version control)
|
||||
- `wwwroot/exports/` → ignore (rendered output, tạm)
|
||||
- `wwwroot/uploads/` → **ignore** (user files)
|
||||
- `wwwroot/templates/` → **commit** (source of truth)
|
||||
- `wwwroot/exports/` → ignore (temp)
|
||||
|
||||
## Dev workflow
|
||||
|
||||
### 16. Port conflict khi restart dev server
|
||||
|
||||
**Triệu chứng:** `npm run dev` fail với `Port 8082 is in use`.
|
||||
**Fix:** `TaskStop` task cũ, hoặc `netstat -ano | findstr :8082` → `taskkill /F /PID <pid>`.
|
||||
|
||||
**Nguyên nhân:** Background task trước chưa kill hẳn.
|
||||
### 17. EF migration 3-file rule
|
||||
|
||||
**Fix:** `TaskStop` task cũ, hoặc kill process listening port: `netstat -ano | findstr :8082` → `taskkill /F /PID <pid>`.
|
||||
Mỗi migration tạo: `{name}.cs` + `{name}.Designer.cs` + `ApplicationDbContextModelSnapshot.cs`. Commit đủ 3.
|
||||
|
||||
### 17. EF migration tạo 3 file, COMMIT ĐỦ
|
||||
## Claude Code harness quirks
|
||||
|
||||
**Quy tắc:** Mỗi migration tạo:
|
||||
- `{timestamp}_{Name}.cs` — up/down
|
||||
- `{timestamp}_{Name}.Designer.cs` — model snapshot lúc đó
|
||||
- `ApplicationDbContextModelSnapshot.cs` — current snapshot (update mỗi lần)
|
||||
### 18. Edit tool "File not read" sau system-reminder
|
||||
|
||||
Commit đủ 3 file. Nếu thiếu, team khác `dotnet ef database update` sẽ fail.
|
||||
**Triệu chứng:** Edit file vừa Read, lỗi "File has not been read yet".
|
||||
|
||||
## Checklist khi gặp bug mới
|
||||
**Nguyên nhân:** System reminder interrupt reset read-cache.
|
||||
|
||||
1. Build có pass không? Nếu fail → check using + package version
|
||||
2. Log API startup có error ẩn không? → `tail` output file
|
||||
3. File đã persist đúng chưa? → `head -5` verify
|
||||
4. Nếu là package compat → thử downgrade về stable (không dùng preview/latest)
|
||||
5. Nếu là TS error exotic → check tsconfig flags (`erasableSyntaxOnly`, `verbatimModuleSyntax`)
|
||||
6. Nếu là EF expression tree error → tách logic ra ngoài query
|
||||
**Fix:** Read lại file rồi Write/Edit. Hoặc dùng Write (ghi đè full) thay Edit.
|
||||
|
||||
### 19. Build pass nhưng DI thiếu registration
|
||||
|
||||
**Triệu chứng:** `dotnet build` → 0 errors nhưng runtime throw `Unable to resolve service`.
|
||||
|
||||
**Nguyên nhân:** C# compiler chỉ check type, không check DI graph.
|
||||
|
||||
**Fix:** Sau thêm interface mới + impl → luôn add `services.AddScoped<IX, X>()` trong `DependencyInjection.cs`. Test API start up là OK check.
|
||||
|
||||
## Contract workflow
|
||||
|
||||
### 20. Mã HĐ gen 2 lần sau reject → approve lại
|
||||
|
||||
**Fix:** Check `if (contract.MaHopDong is null)` trước khi gen. Đã implement trong `ContractWorkflowService.TransitionAsync`.
|
||||
|
||||
### 21. BE adjacency vs FE NEXT_PHASES sync
|
||||
|
||||
**Triệu chứng:** FE hiển thị nút chuyển phase, click → BE 403.
|
||||
|
||||
**Nguyên nhân:** FE `NEXT_PHASES` map phải khớp BE `Transitions` dict.
|
||||
|
||||
**Fix:** Khi đổi adjacency BE → sync FE `src/pages/contracts/ContractDetailPage.tsx` ngay lập tức (cả 2 app).
|
||||
|
||||
### 22. Race condition gen mã HĐ khi 2 user cùng transition tới DangDongDau
|
||||
|
||||
**Fix:** `IsolationLevel.Serializable` transaction trong `ContractCodeGenerator`. Không skip.
|
||||
|
||||
## Permission matrix
|
||||
|
||||
### 23. Permission update không real-time
|
||||
|
||||
**Triệu chứng:** Admin tick permission cho role X → user X vẫn thấy menu cũ.
|
||||
|
||||
**Nguyên nhân:** FE cache menu trong `localStorage`, không auto refetch.
|
||||
|
||||
**Fix:** User phải logout/login. Phase 3 iteration 2 có thể thêm SignalR push "permission-changed" → FE tự refetch `/menus/me`.
|
||||
|
||||
### 24. MenuKey typo — không check type
|
||||
|
||||
**Fix:** Luôn dùng `MenuKeys.Contracts` const (BE) + `MenuKeys.Contracts` (FE `menuKeys.ts`). Không hardcode string.
|
||||
|
||||
## Checklist debug bug mới
|
||||
|
||||
1. Build pass không? → fail → check using + package version compat
|
||||
2. DI register đủ? → runtime error "Unable to resolve" → add `AddScoped/Singleton`
|
||||
3. API log startup có error ẩn? → `tail` output file
|
||||
4. File đã persist đúng chưa? → `head -5` verify sau Write
|
||||
5. Nếu package exotic → thử downgrade về stable trước
|
||||
6. Nếu TS error → check `erasableSyntaxOnly`, `verbatimModuleSyntax`
|
||||
7. Nếu EF expression tree → tách logic ra ngoài query
|
||||
8. Nếu Unicode CLI → dùng file payload
|
||||
9. Nếu workflow 403 → check FE NEXT_PHASES sync BE
|
||||
|
||||
300
docs/rules.md
Normal file
300
docs/rules.md
Normal file
@ -0,0 +1,300 @@
|
||||
# Rules — Coding Conventions SOLUTION_ERP
|
||||
|
||||
> Nguồn duy nhất cho rules. Bất cứ commit nào vi phạm cần justify trong message.
|
||||
|
||||
## 1. Ngôn ngữ
|
||||
|
||||
| Thành phần | Ngôn ngữ |
|
||||
|---|---|
|
||||
| UI FE | **100% tiếng Việt** |
|
||||
| Code (.cs/.ts) | **English** (tên biến/hàm/class) |
|
||||
| Comment code | Tiếng Anh hoặc Việt — miễn rõ |
|
||||
| Tên DB table + column | **English PascalCase** (Contracts, SupplierId) |
|
||||
| Business label | Tiếng Việt (vd `"Đang soạn thảo"` cho phase 2) |
|
||||
| Commit message | English hoặc Việt — preferred English |
|
||||
| Doc MD (docs/) | **Tiếng Việt** (audience là người Việt) |
|
||||
|
||||
## 2. Backend — .NET 10
|
||||
|
||||
### 2.1 Clean Architecture dependency rule
|
||||
|
||||
```
|
||||
Api → Application ← Domain
|
||||
↑
|
||||
Infrastructure ──┘
|
||||
```
|
||||
|
||||
- **Domain:** pure, chỉ dùng `Microsoft.Extensions.Identity.Stores` (cần cho IdentityUser) — không depend ASP.NET Core framework
|
||||
- **Application:** depend Domain; CQRS handlers + interfaces + DTOs + validators
|
||||
- **Infrastructure:** depend Application + Domain; impl interfaces (EF, JWT, rendering, services)
|
||||
- **Api:** depend Application + Infrastructure; Controllers + middleware + composition root
|
||||
|
||||
### 2.2 CQRS + MediatR pattern
|
||||
|
||||
- 1 feature = 1 file `{Verb}{Entity}Command.cs` / `{Verb}{Entity}Query.cs`
|
||||
- File chứa: Command record + Validator + Handler (cùng 1 file cho compact)
|
||||
- Dài thì tách: `{Feature}/Commands/{Create}/...`
|
||||
|
||||
**Command naming:**
|
||||
```
|
||||
Create{Entity}Command // tạo mới
|
||||
Update{Entity}Command // update full
|
||||
Patch{Entity}FieldCommand // update 1 field
|
||||
Delete{Entity}Command // soft delete (default)
|
||||
{Action}{Entity}Command // action domain-specific (vd TransitionContract)
|
||||
```
|
||||
|
||||
**Query naming:**
|
||||
```
|
||||
Get{Entity}Query // single by Id
|
||||
List{Entity}sQuery // list với paging
|
||||
{Entity}{Purpose}Query // custom query (vd GetMyInboxQuery)
|
||||
```
|
||||
|
||||
### 2.3 Validation
|
||||
|
||||
- FluentValidation (KHÔNG dùng DataAnnotation)
|
||||
- Validator = class trong cùng file command: `{Command}Validator : AbstractValidator<{Command}>`
|
||||
- Pipeline: `ValidationBehavior` auto-chạy trước Handler — throw `ValidationException` → 400 ProblemDetails
|
||||
|
||||
### 2.4 Error handling
|
||||
|
||||
- KHÔNG try-catch ở Controller
|
||||
- Throw domain exception: `NotFoundException`, `ForbiddenException`, `UnauthorizedException`, `ConflictException`, `ValidationException`
|
||||
- `GlobalExceptionMiddleware` map exception → HTTP status + ProblemDetails JSON
|
||||
- Logging: Serilog structured, không `Console.WriteLine`
|
||||
|
||||
### 2.5 Async/await
|
||||
|
||||
- Tất cả I/O (DB, HTTP, file) PHẢI async
|
||||
- Truyền `CancellationToken` mọi async call
|
||||
- Tên method async: suffix `Async` (trừ ASP.NET action signature)
|
||||
|
||||
### 2.6 Entity rules
|
||||
|
||||
- Mọi entity mới extend `BaseEntity` (Id Guid + audit) hoặc `AuditableEntity` (+ soft delete)
|
||||
- PK: `Id` type `Guid`, default `Guid.NewGuid()` trong constructor
|
||||
- FK: `{Entity}Id` type `Guid`
|
||||
- Enum: `HasConversion<int>()` trong EF config
|
||||
- String: luôn `HasMaxLength(n)` trong EF config — không để `nvarchar(max)` trừ khi là JSON/rich text
|
||||
- Money: `decimal` + `HasPrecision(18, 2)`
|
||||
- DateTime: luôn UTC, lấy từ `IDateTime.UtcNow` (không `DateTime.Now`)
|
||||
- Query filter soft delete: `HasQueryFilter(x => !x.IsDeleted)` cho AuditableEntity
|
||||
|
||||
### 2.7 DI registration
|
||||
|
||||
- Singleton: stateless services (DateTimeService, FormRenderer)
|
||||
- Scoped: per-request services (JwtTokenService, ContractWorkflowService, DbContext)
|
||||
- Transient: lightweight utility (hiếm dùng)
|
||||
|
||||
### 2.8 Package version pinning
|
||||
|
||||
- **KHÔNG dùng `*` hoặc `latest`** — luôn pin version cụ thể
|
||||
- Check compatibility với .NET 10 trước khi add. Gotchas đã biết:
|
||||
- MediatR 14 → lỗi IMediator DI, **pin 12.4.1**
|
||||
- Swashbuckle 10 → conflict OpenApi 2, **pin 6.9.0**
|
||||
- Xem [`gotchas.md`](gotchas.md) cho full list
|
||||
|
||||
## 3. Frontend — React 19 + Vite 8 + TS 6
|
||||
|
||||
### 3.1 Named export, không default export
|
||||
|
||||
```tsx
|
||||
// ✅
|
||||
export function SuppliersPage() { }
|
||||
export const MyConstant = 1
|
||||
|
||||
// ❌
|
||||
export default function SuppliersPage() { }
|
||||
|
||||
// Exception: App.tsx dùng default export (Vite convention)
|
||||
```
|
||||
|
||||
### 3.2 erasableSyntaxOnly (Vite 8)
|
||||
|
||||
KHÔNG dùng `enum` — dùng const-object pattern:
|
||||
|
||||
```ts
|
||||
// ❌
|
||||
export enum SupplierType { NhaCungCap = 1 }
|
||||
|
||||
// ✅
|
||||
export const SupplierType = { NhaCungCap: 1, NhaThauPhu: 2 } as const
|
||||
export type SupplierType = typeof SupplierType[keyof typeof SupplierType]
|
||||
```
|
||||
|
||||
### 3.3 Path alias
|
||||
|
||||
- Dùng `@/` cho absolute import: `import { api } from '@/lib/api'`
|
||||
- Config ở `vite.config.ts` + `tsconfig.app.json` (chỉ `paths`, không `baseUrl`)
|
||||
|
||||
### 3.4 Data fetching
|
||||
|
||||
- **TanStack Query** cho mọi API call (KHÔNG useState + useEffect thủ công)
|
||||
- `useQuery` cho GET, `useMutation` cho POST/PUT/DELETE
|
||||
- `invalidateQueries` sau mutation thành công
|
||||
|
||||
### 3.5 API calls
|
||||
|
||||
- Qua `api` instance (`src/lib/api.ts`) — đã wire axios interceptor JWT + 401 redirect
|
||||
- Base URL: `/api` (Vite proxy → `:5443`)
|
||||
|
||||
### 3.6 Permission guard
|
||||
|
||||
```tsx
|
||||
<PermissionGuard menuKey="Contracts" action="Update">
|
||||
<Button>Sửa</Button>
|
||||
</PermissionGuard>
|
||||
|
||||
// Hook
|
||||
const { can } = usePermission()
|
||||
if (!can('Contracts', 'Update')) return null
|
||||
```
|
||||
|
||||
Nhớ: FE guard chỉ là UX. BE policy `[Authorize(Policy = "Contracts.Update")]` là source of truth.
|
||||
|
||||
### 3.7 Component naming
|
||||
|
||||
- PascalCase cho file: `SuppliersPage.tsx`, `DataTable.tsx`
|
||||
- kebab-case cho utility: `api.ts`, `cn.ts`
|
||||
- React component = PascalCase
|
||||
- Hook: prefix `use`, ví dụ `usePermission`
|
||||
|
||||
### 3.8 Toast + error
|
||||
|
||||
- Dùng `sonner` (đã wire `<Toaster>` ở App)
|
||||
- `toast.success('...')`, `toast.error(getErrorMessage(err))`
|
||||
- Error helper: `src/lib/apiError.ts` extract từ ProblemDetails
|
||||
|
||||
### 3.9 Duplicate giữa 2 FE
|
||||
|
||||
**CÓ CHỦ ĐÍCH.** Mỗi app (`fe-admin` + `fe-user`) là standalone — copy shared code giữa 2 app thay vì extract ra package chung. Lý do: 2 app có UX rất khác (admin mission-critical, user workflow-heavy), evolution độc lập.
|
||||
|
||||
Khi share code: `cp` file từ app này sang app kia. Đồng bộ tay khi có breaking change.
|
||||
|
||||
## 4. Database
|
||||
|
||||
### 4.1 Convention
|
||||
|
||||
| Item | Rule |
|
||||
|---|---|
|
||||
| Schema | 1 schema duy nhất: `dbo` |
|
||||
| Table | PascalCase tiếng Anh, plural: `Contracts`, `Suppliers` |
|
||||
| Column | PascalCase: `FullName`, `CreatedAt` |
|
||||
| PK | `Id` — `uniqueidentifier` |
|
||||
| FK | `{Entity}Id` — `uniqueidentifier` |
|
||||
| Index | `IX_{Table}_{Col}` |
|
||||
| Unique | `UX_{Table}_{Col}` |
|
||||
|
||||
Full: xem [`database/database-guide.md`](database/database-guide.md).
|
||||
|
||||
### 4.2 Migration
|
||||
|
||||
- Một commit = 1 migration (trừ khi nhiều change nhỏ trong cùng feature)
|
||||
- Naming PascalCase: `AddMasterData`, `AddContractsWorkflow`
|
||||
- Commit đủ 3 file: `{Name}.cs`, `{Name}.Designer.cs`, `ApplicationDbContextModelSnapshot.cs`
|
||||
|
||||
## 5. Git & commits
|
||||
|
||||
### 5.1 Branch
|
||||
|
||||
- `main` — deploy branch
|
||||
- Feature branch (nếu nhiều người): `feature/phase{N}-{feature}`
|
||||
|
||||
### 5.2 Commit format
|
||||
|
||||
```
|
||||
[CLAUDE] <scope>: <imperative message>
|
||||
|
||||
<body optional — chi tiết what + why>
|
||||
|
||||
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
**Scope:** `Contract` · `Form` · `Workflow` · `Supplier` · `Auth` · `Admin` · `Api` · `App` · `Domain` · `Infra` · `FE-Admin` · `FE-User` · `Docs` · `CICD` · `Scripts` · `Report` · `Dashboard`
|
||||
|
||||
### 5.3 Một commit nên
|
||||
|
||||
- Build pass (BE + FE)
|
||||
- Không leave WIP nửa chừng (trừ khi commit message nói rõ)
|
||||
- Test E2E với endpoint mới (curl trong session log)
|
||||
|
||||
### 5.4 Không commit
|
||||
|
||||
- `.env`, `appsettings.*.Local.json`
|
||||
- `node_modules/`, `bin/`, `obj/`
|
||||
- `wwwroot/uploads/` (user files)
|
||||
- `SolutionErp_Design` database (tạm cho EF CLI)
|
||||
|
||||
## 6. Docs
|
||||
|
||||
### 6.1 Khi nào viết session log
|
||||
|
||||
- Session có commit thay đổi code → PHẢI tạo `docs/changelog/sessions/YYYY-MM-DD-HHMM-{topic}.md`
|
||||
- Session chỉ Q&A, không đụng file → không cần
|
||||
|
||||
### 6.2 Session log format
|
||||
|
||||
```markdown
|
||||
# Session YYYY-MM-DD HH:MM — <topic>
|
||||
|
||||
**Dev:** Claude / Copilot / <name>
|
||||
**Duration:** ~Nh
|
||||
**Base commit:** <sha>
|
||||
|
||||
## Làm được
|
||||
|
||||
### Chunk X — <name>
|
||||
- bullet list deliverable
|
||||
|
||||
## E2E verified
|
||||
|
||||
<curl output hoặc screenshot>
|
||||
|
||||
## Bug gặp + fix
|
||||
|
||||
| Bug | Fix |
|
||||
|
||||
## Docs updates
|
||||
|
||||
## Handoff
|
||||
|
||||
## Thông số cumulative
|
||||
```
|
||||
|
||||
### 6.3 Update khi thêm feature
|
||||
|
||||
- Thêm entity → update [`database/database-guide.md`](database/database-guide.md) schema section
|
||||
- Thêm endpoint → update [`flows/`](flows/) relevant flow
|
||||
- Thêm bug fix lặp lại → update [`gotchas.md`](gotchas.md)
|
||||
- Thêm pattern → update skill tương ứng ở `.claude/skills/`
|
||||
- Phase đổi → update [`STATUS.md`](STATUS.md) + [`HANDOFF.md`](HANDOFF.md) + [`changelog/migration-todos.md`](changelog/migration-todos.md)
|
||||
|
||||
## 7. Testing (hiện chưa có test tự động)
|
||||
|
||||
**Phase 1-4 — manual test:**
|
||||
- E2E qua curl/Postman trong mỗi session log
|
||||
- Build + TS check mỗi commit
|
||||
|
||||
**Phase 5 — tự động (chưa làm):**
|
||||
- Unit test: xUnit cho BE, Vitest cho FE
|
||||
- Integration test: TestContainer SQL Server cho BE
|
||||
- E2E test: Playwright cho FE
|
||||
|
||||
## 8. Security baseline
|
||||
|
||||
- HTTPS everywhere prod
|
||||
- JWT Secret trong user-secrets / env var (không commit appsettings)
|
||||
- Password hash: PBKDF2 (Identity default)
|
||||
- CORS whitelist chỉ 2 FE origin
|
||||
- SQL injection: EF Core parameterized (không raw SQL trừ khi phải)
|
||||
- Audit log: mọi ghi phải log qua `ContractApproval` hoặc `AuditLog` (future)
|
||||
- Rate limit: `/api/auth/login` 5 req/min/IP (chưa implement — Phase 5)
|
||||
|
||||
## 9. Liên quan
|
||||
|
||||
- [`CLAUDE.md`](../CLAUDE.md) — entry point cho AI agent
|
||||
- [`architecture.md`](architecture.md) — kiến trúc chi tiết (layered, CQRS, state machine)
|
||||
- [`gotchas.md`](gotchas.md) — pitfalls đã gặp
|
||||
- [`database/database-guide.md`](database/database-guide.md) — DB conventions
|
||||
- [`flows/`](flows/) — sequence diagram per feature
|
||||
Reference in New Issue
Block a user