Files
solution-erp/docs/architecture.md
pqhuy1987 b874743081
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m21s
[CLAUDE] Docs+Tests: chốt final session 5 — 77 test (Phase 3 mini PE WF) + 3 gotcha CI + 8 doc updates
Final close session 5 — bao gồm:

==== Tests Phase 3 mini (NEW) ====
tests/SolutionErp.Infrastructure.Tests/Application/PeWorkflowAdminTests.cs
- 6 test CreatePeWorkflowDefinitionCommandHandler:
  - First version → IsActive=true, Version=1, ActivatedAt set
  - Second version same Code → auto-increment v2 + deactivate v1 (atomic)
  - Different EvaluationType (A vs B) → independent active state
  - Persists steps ordered by Order field
  - Persists approvers per step
  - Third version → v1 + v2 deactivate, v3 active

Total tests: 71 → 77 pass / ~2s (54 Domain + 23 Infra).
Skip Phase 3 full (UpsertOpinion + Budget link validation) — cần
Identity UserManager DI helper, defer session sau.

==== 3 gotcha CI mới (#39 #40 #41) ====
- #39 act_runner github.com TCP timeout 21s → manual checkout fix (run #108/#109 fail, #110 pass)
- #40 npm junction cache `tsc not found` after Move-Item — rolled back, hypothesis nested junctions
  trong node_modules disrupt .bin/ paths. TODO debug session sau với robocopy hoặc act_runner cache.host
- #41 Gitea Actions paths-ignore behavior — workflow file change vẫn trigger (correct), commit
  MD-only skip 100% (verify 512880c → no run #113)
+ Checklist debug bug mới items 18-20 referencing 3 gotcha trên.

==== Doc updates (8 file) ====
- STATUS.md: header Phase 8 update + 3 row Recently Done CI fixes + cumulative test 71→77
- HANDOFF.md: TL;DR + CI optimize section + Phase status + gotcha count 38→41
- migration-todos.md: Phase 8 §E updated với Phase 3 mini done + CI fixes
- rules.md §7 Testing: rewrite full — stack + test pyramid + quy tắc bổ sung mỗi feature +
  workflow user end-of-task (`dotnet test` local trước push) + CI gate behavior
- architecture.md §11: update test pyramid + phased priority + CI optimization sub-section
  (3 fix manual checkout / path filter / npm cache rollback) + tốc độ deploy table
- gotchas.md: + #39 #40 #41 đầy đủ (triệu chứng + nguyên nhân + fix + reference)
- ef-core-migration SKILL: Phase 8 update note thêm CI fixes + 77 test
- CLAUDE.md root: test count 71→77 + folder structure + CI/CD pipeline 3 fix section
- memory project_solution_erp.md: session 5 summary + workflow user mới
- session log 2026-04-29-2300-chot-final-ci-tests-gotchas.md (NEW — 9 section detail)

==== Skill audit cron ====
`solution-erp-skill-audit-monthly` next fire 2026-05-01 (2 ngày sau).
Cron survives across sessions (setup commit b904a25). Khi fire sẽ:
- Cross-check 6 skill với STATUS/gotchas/migration-todos
- Auto-refresh stale + đề xuất add/archive cho human approve
- Log vào docs/changelog/skill-audit-2026-05.md
- ABORT nếu repo dirty

==== Verify ====
- dotnet test SolutionErp.slnx → 77 pass / ~2s (54 Domain + 23 Infra)
- git status clean sau commit này
- CI: commit này chứa code (test + workflow) → trigger CI test gate

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 23:43:42 +07:00

401 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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. PurchaseEvaluation (Phase 6 — tiền-HĐ)
Module mới song song Contract phiếu trình duyệt so sánh giá N NCC × M hạng mục, duyệt xong kế thừa làm .
```
PurchaseEvaluation (Header) ─< PurchaseEvaluationSupplier (N:M × Supplier)
─< PurchaseEvaluationDetail (hạng mục) ─< PurchaseEvaluationQuote (báo giá N×M)
─< PurchaseEvaluationApproval (workflow history)
─< PurchaseEvaluationChangelog (audit)
─< PurchaseEvaluationAttachment (file)
─> PurchaseEvaluationWorkflowDefinition (PINNED at create)
─> Contract? (nullable FK — set khi gen HĐ từ phiếu DaDuyet)
```
**Workflow** (tách riêng Phase enum khác ContractPhase):
- A `DuyetNcc` 3 step: Drafter Procurement CostControl Director DaDuyet
- B `DuyetNccPhuongAn` 5 step: + ProjectManager sau Procurement, + Director duyệt PA trước duyệt NCC
**Kế thừa HĐ** (`CreateContractFromEvaluationCommand`):
- Guard: phase = DaDuyet, SelectedSupplierId != null, ContractId = null
- User pick ContractType (1-7) gen Contract draft với SupplierId/ProjectId/GiaTri kế thừa
- Pin `Contract.WorkflowDefinitionId` theo ContractType chọn
- Link 2 chiều: `PurchaseEvaluation.ContractId = contract.Id`
Chi tiết: [`database/schema-diagram.md §11`](database/schema-diagram.md).
## 10. Budget (Phase 7 — Module Ngân sách)
Module quản ngân sách dự án: header + chi tiết hạng mục + workflow simple 3-step + audit log. Liên kết nullable cả Contract PurchaseEvaluation để đối chiếu chi phí.
```
Budget (Header) ─< BudgetDetail (flat row hạng mục)
─< BudgetApproval (workflow history)
─< BudgetChangelog (audit log)
─> Project (FK Restrict)
─> Department? (FK Restrict)
─> User Drafter (FK Restrict)
Contract.BudgetId? ──────────► Budget (link đối chiếu chi phí HĐ)
PurchaseEvaluation.BudgetId? ─► Budget (link đối chiếu chi phí tiền-HĐ)
```
**Phase enum** (`BudgetPhase` 5-state):
```
DangSoanThao(1) ──Trình──► ChoCCM(2) ──Duyệt──► ChoCEO(3) ──Duyệt──► DaDuyet(4)
▲ │ │
└──Reject(99)───────────┴─────────────────────┘
```
**Workflow simple hardcoded** (`BudgetPolicy.Default`):
- Drafter / DeptManager: DangSoanThao ChoCCM (Trình) hoặc TuChoi (Hủy)
- CostControl (CCM): ChoCCM ChoCEO (Duyệt) hoặc DangSoanThao (Trả về)
- Director / AuthorizedSigner: ChoCEO DaDuyet (Duyệt) hoặc DangSoanThao (Trả về)
**Mã ngân sách** `NS-{YYYYMM}-{Random:4d}` hiện Random.Shared, sẽ chuyển atomic SERIALIZABLE khi format chốt chính thức (mirror Contract/PE pattern).
**Auto-recompute** `TongNganSach`:
- Sau Add/Update/Delete BudgetDetail handler tự sum `Sum(d.ThanhTien)` lại Header.
- Tránh state drift, đơn giản hơn trigger DB.
**Integration roadmap**:
- PE form select Budget (filter `Phase=DaDuyet && NamNganSach=current && ProjectId match`)
- Contract form tương tự
- Tab Hạng mục PE/HD compute "So với ngân sách" (match GroupCode + ItemCode)
Chi tiết: [`database/schema-diagram.md §12`](database/schema-diagram.md).
## 11. Testing strategy (Phase 8 — Session 5)
Test pyramid bottom-heavy, **không E2E** (brittle, maintenance cao cho solo dev).
```
┌──────────────────────────────────┐ ❌ Skip
│ E2E / UI (Playwright) │ Brittle, slow, tốn time maintain
├──────────────────────────────────┤ ⚠️ TODO Phase 4
│ API smoke (WebApplicationFactory)│ 1-2 happy path roundtrip per critical endpoint
├──────────────────────────────────┤ 🟡 Mini done — 6 test (29/04)
│ Application Handler (CQRS) │ PE Workflow Definition versioning. Phase 3 full
│ │ (Opinion + Budget link) cần UserManager DI helper
├──────────────────────────────────┤ ✅ Done Phase 2 — 17 test
│ Infrastructure (SQLite in-mem) │ Code generator format + sequence + year boundary
├──────────────────────────────────┤ ✅ Done Phase 1 — 54 test
│ Domain Policy (pure functions) │ State machine + role transition + Registry mapping
└──────────────────────────────────┘
Total: 77 test pass / ~3s
```
**Stack:**
| Layer | Framework | Note |
|---|---|---|
| .NET test | xUnit 2.9.3 | Industry standard |
| Assertion | FluentAssertions 7.2 | Pin trước v8 commercial license |
| EF testing | Microsoft.EntityFrameworkCore.Sqlite 10 | Phase 2+ supports transactions |
| Test fixture | Custom `SqliteDbFixture` + `TestApplicationDbContext` | Override `nvarchar(max) → TEXT` cho SQLite compat |
**CI gate:** `.gitea/workflows/deploy.yml` chạy `dotnet test` cho từng test project TRƯỚC build/publish/deploy. Test fail `exit $LASTEXITCODE` no deploy.
```yaml
- name: Run unit tests (Domain)
run: dotnet test tests/SolutionErp.Domain.Tests/...
- name: Run integration tests (Infrastructure)
run: dotnet test tests/SolutionErp.Infrastructure.Tests/...
```
**Phase priority — anti-overkill:**
1. **Phase 1 (Done)** Domain policy: 3 test file (Contract WF / PE WF / Budget) cover transition rules + Registry + FromDefinition. **54 test**, < 1s.
2. **Phase 2 (Done)** Infra code generator: format RG-001/PE + sequence increment + year boundary (framework ). **17 test**, ~2s.
3. **Phase 3 mini (Done)** Application versioning: `CreatePeWorkflowDefinitionCommand` auto-increment + deactivate + EvaluationType independence. **6 test**.
4. **Phase 3 full (Pending)** Application handlers cần Identity (UpsertOpinion, Budget link validation in CreatePE/CreateContract). Cần build UserManager DI helper trong test fixture.
5. **Phase 4 (Pending)** API smoke: WebApplicationFactory + SQLite, 5-7 endpoint critical roundtrip.
6. **Phase 5 (Pending)** FE Vitest cho lib utility (queryMatches, fmtMoney) chỉ pure functions, KHÔNG component snapshot.
**Quy tắc bổ sung mỗi feature mới:**
- Domain policy method 2-3 test (positive + negative + edge)
- CQRS handler guard 1 test mỗi guard branch
- Bug found in production 1 regression test added before merge
Detail run: `dotnet test SolutionErp.slnx` chạy cả **77 test ~3s**.
### CI/CD optimization (29/04)
3 fix lớn cho Gitea Actions sau khi gặp incident #108/#109:
1. **Manual checkout bypass github.com (gotcha #39)**:
- Replace `uses: actions/checkout@v4` bằng manual `git init` + `git fetch` từ Gitea internal
- do: act_runner mỗi run đều fetch action source từ github.com TCP timeout 21s liên tục toàn job fail trước test gate
- Token `${{ github.token }}` Gitea tự cấp per-job, fetch by ref + depth=30
2. **Path filter docs-only skip (gotcha #41)**:
- `paths-ignore: ['docs/**', '**/*.md', '.claude/skills/**']` trong `on:push`
- Commit chỉ touch docs/MD/skill **Gitea NO trigger workflow** (saving 100% time, ~196s/commit)
- Workflow file `.gitea/workflows/**` KHÔNG ignore vẫn trigger khi sửa CI config
3. **npm junction cache (gotcha #40)** thử commit `29eb5d9`, fail `tsc not found`:
- Hypothesis: Move-Item của `node_modules` chứa nested junctions .bin/ relative paths broken
- Rolled back `a21790d`, giữ fresh `npm install` mỗi run (49s + 33s = 82s)
- TODO future session: thử `robocopy /MIR` hoặc act_runner built-in cache server
**Tốc độ deploy hiện tại** (run #110/#112):
| Trigger | CI behavior | Total time |
|---|---|---:|
| Code commit | Build + test gate + deploy | ~3 phút |
| Docs-only commit | **Skip CI** (path filter) | **0s** |
| Test fail | Skip build/deploy | ~30-40s |
## 12. 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