# CLAUDE.md — AI Agent Context

> **Full content:** [`docs/CLAUDE.md`](docs/CLAUDE.md)

---

**SOLUTION_ERP** — Hệ thống Quản lý Hợp đồng Nhà cung cấp / Thầu phụ / Tổ đội + **Phiếu Duyệt NCC tiền-HĐ** cho Công ty TNHH Xây dựng Solutions.

Kiến trúc: **.NET 10 Clean Architecture + 2 React FE (admin + user) + SQL Server + IIS**.

## 🚀 BẮT ĐẦU SESSION — 5 file đọc trước tiên

```
1. docs/STATUS.md                        ← Snapshot HIỆN TẠI (phase nào, việc gì đang làm — PE module CÒN CHỈNH NHIỀU)
2. docs/HANDOFF.md                       ← Brief 5 phút: session trước làm gì + cảnh báo session tiếp
3. docs/PROJECT-MAP.md                   ← Bản đồ toàn cảnh
4. docs/changelog/migration-todos.md     ← Atomic tasks theo phase (active-work đọc TRONG file — KHÔNG neo nhãn phase ở đây, H24-DEEP F-10)
5. docs/workflow-contract.md             ← ⭐ State machine 9 phase HĐ — base pattern cho PE workflow
```

> ⛓️ **Mạch việc dở (H22):** nằm ở `.claude/WAL.md`; nối lại DUY-NHẤT bằng `/tiep` — kể cả khi anh gõ "làm tiếp" bằng text thường. Không tự recovery cách khác. (Chốt dừng chủ-động = `/pause`; cơ-chế → `docs/governance/harness-11-engine.md` PHẦN N.)

> 🎛️ **Chế-độ đang hiệu-lực (operating-posture) — TRA Ở ĐÂU** *(adap đợt-11, `3-khoan:31-41` discoverability-home ⟂ implement-home)*:
> file này **CỐ Ý KHÔNG chép giá-trị** ON/OFF — chép là sai ngay lượt sau khi anh toggle. Chỉ trỏ **nơi tra**:
> - **HMW-mode** (fan-out Workflow) → marker `.claude/hmw-mode.on`; `/session-start` BƯỚC 0.5 in trạng-thái mỗi phiên. Bật/tắt: `/ultra-on` · `/ultra-off`.
> - **Engine-đắt** (`/fable-real` · `/fable-clone`) → **per-invocation, KHÔNG persist** (H21) — không có mode để tra, chỉ sống trong lượt anh gõ.
> - **Lead model** → `~/.claude/settings.json` (**global, NGOÀI git** — grep trong repo sẽ ra 0 hit); probe runtime mỗi phiên ở BƯỚC 0.6.
> - **Nhịp H24 / ngân-sách memory** → `.claude/agent-memory/memory-budget.json` (single-source; doc **trỏ key, không chép số**).

## ⚡ Quick Rules

### Backend — `.NET 10` Clean Architecture

- Solution: `SolutionErp.slnx` ở root, projects ở `src/Backend/`
- **4 layer:** `Api → Application ← Domain` + `Infrastructure → Application`
- Pattern: **CQRS + MediatR**, **FluentValidation**, **AutoMapper**
- Repository qua `IApplicationDbContext` interface (Application layer)
- Auth: **JWT Bearer + ASP.NET Identity**
- DB: **SQL Server** (LocalDB dev / SQL Server prod), **EF Core 10 Code-First migrations**
- Error handling: `GlobalExceptionMiddleware` map exception → HTTP status
- Commit scope tech stack: `Api` · `App` · `Domain` · `Infra`

### Frontend — 2 app React 19 + Vite + TS + shadcn/ui + TanStack Query

- `fe-admin/` (port **8082**) · `fe-user/` (port **8080**)
- Vite proxy `/api → http://localhost:5443` (SolutionErp.Api)
- **Named export**, không default export (trừ `App`)
- shadcn/ui copy-paste, **duplicate giữa 2 app là CÓ CHỦ ĐÍCH** (mỗi app UX riêng)
- Auth context: `solution-erp-admin-token` / `solution-erp-user-token` ở `localStorage`
- TanStack Query cho data fetching
- **Node pin `>=20`** trong `engines`; CI pin `20.x` qua `.nvmrc` (bài học NamGroup — KHÔNG dùng Node latest trên CI)
- UI **100% tiếng Việt**

### Database conventions

- Schema: `dbo` (single schema)
- Table: **PascalCase tiếng Anh** (Contracts, Suppliers, Projects, ContractApprovals, PurchaseEvaluations, ...)
- PK: `Id` (Guid), FK: `{Entity}Id`
- Audit fields: `CreatedAt`, `UpdatedAt`, `CreatedBy`, `UpdatedBy` (`BaseEntity`)
- Soft delete: `IsDeleted`, `DeletedAt`, `DeletedBy` (`AuditableEntity`)
- Migrations: `dotnet ef migrations add <Name> --project src/Backend/SolutionErp.Infrastructure --startup-project src/Backend/SolutionErp.Api`
- **Số migration · bảng hiện tại → [`docs/STATUS.md`](docs/STATUS.md) (canonical — KHÔNG copy số ở đây để tránh drift; Harness-11 B1).** Lịch sử mig gần đây: **+S164/S165 Mig 70 `AddContractCatalogEntries` (K1 SP-002 — danh mục hạng-mục HĐ 86 dòng, +1 bảng 96→97) + Mig 71 `AddKhkkApprovalGroupCatalogAndFinalizeRuntime` (K2/K3 — phiếu KHKK +nhóm duyệt N1-N8 backfill=1 + Line +hạng-mục + unique 3-cột + cờ `EndedByLevelFinalize`, 0 bảng mới)** · **+S161 Mig 69 `AddContractSigningPlans`** (module MỚI **Kế hoạch Ký kết HĐ** — 7 `CreateTable`, bảng 89→96; menu-key `KeHoachKyKet` sinh **policy** KHÔNG sinh menu-row; gotcha **#85** menu-key OR-gate FE ≠ policy per-action BE) · **+S157 Mig 68 `AddPeAllowApproverDelete`** (PE nút XÓA phiếu ở màn duyệt — AddColumn, no new table) · **+S133 Mig 67 `AddPeApprovedBudgetSnapshot`** (PE freeze ngân sách phiếu DaDuyet — snapshot 11 cột AddColumn chốt tại finalize choke-point + site-5 admin-override + backfill `Sql()` OUTER APPLY 18/18 prod, no new table; display-gate `budgetFrozen` phiếu DaDuyet đọc snapshot/phiếu mới đọc live; gotcha #81-EXT) · **+S97 Mig 60 `AddPeEndedByLevelFinalize` + Mig 61 `BackfillEndedByLevelFinalizeFromChangelog`** (PE cờ RUNTIME finalize-sớm — display đọc runtime thay suy-config; backfill Mig 60 soi nhầm bảng Approvals → Mig 61 đúng Changelogs; gotcha #78/#79) · +S74 Mig 55 `AddCcmNoteToPeWorkItemBudget` (PE CcmNote) · +S73 Mig 54 `AddPeSuggestedAndApprovedPrice` (PE giá đề xuất PRO/CCM). +S69: Mig 53 `AddPeUrgentAndCeoApprovalThreshold` — PE +IsUrgentByPro/Ccm cờ gấp PRO/CCM + ApprovalWorkflow +CeoApprovalThreshold ngưỡng CCM duyệt-final, 3 AddColumn no new table. +S65: Mig 51 `AddDepartmentParentId` Department.ParentId loose-Guid no-FK org-tree phân cấp + Mig 52 `AddHoSoLinkToPurchaseEvaluation` PE HoSoLink nvarchar(1000) hyperlink NAS — cả 2 AddColumn no new table, tables giữ 88. Phase 10 COMPLETE + Phase 11 P11-A→F done — Mig 34-42 HRM/Office/WorkflowApps/Attendance + Contract V2 (32-33) + WireWorkflowApps V2 (41) + LeaveBalance (42) + Holiday filtered-unique (43, S45) + Vehicle/Driver catalog (44, S51) + HRM-catalog filtered-unique 3× (45, S51) + ItTicket SLA (46, S52) + Master filtered-unique 3× (47, S53 gotcha #57 EXT) + Project master fields Year/Investor/Location/Package (48, S55 — AddColumn no new table, kèm nạp 62 dự án + 71 hạng mục + 3 NCC real data từ Excel qua `SeedRealMasterDataAsync` ungated idempotent) + PE gắn Hạng mục công việc WorkItemId loose-Guid KHÔNG FK vật lý (49, S57bis — AddColumn+CreateIndex, no new table) + **Mig 50 `ReplaceBudgetModuleWithPeWorkItemBudgets` (S61, 2026-06-13) — XÓA module Budget cũ, thay bằng `PeWorkItemBudgets` ngân sách per-gói-thầu (1 record/cặp Dự án × Hạng mục, nhập theo role PRO/CCM, vượt ngân sách = cảnh báo mềm cho lưu S62); backfill `BudgetManualAmount→BudgetPeriodAmount` TRƯỚC DropColumn (phiếu UAT giữ số); net bảng 93→88; gotcha #63/#64**. V2 schema history S29-era bên dưới giữ nguyên — Mig 32+33 Plan B Contract V2 cookie-cutter mirror PE Mig 22-26 (S29). Mig 26 `AddPeLevelOpinionsForV2`: bảng mới `PurchaseEvaluationLevelOpinions` UNIQUE composite (PEId, LevelId), FK Cascade Pe + Restrict Level. Section 5 "Ý kiến cấp duyệt" V2 dynamic theo workflow đã pin: forEach Step (Phòng) → forEach Level (Cấp) → forEach NV → 1 OpinionBox. Service `ApproveV2Async` UPSERT auto khi NV duyệt — Q1=1B (sync gắn với Duyệt, KHÔNG form input rời). SignedByUserId track signer thật, FE banner "Admin duyệt thay" khi !== ApproverUserId. Comment empty → "(duyệt — không ý kiến)" placeholder. Phiếu V1 legacy fallback Mig 15 4 box readOnly (data history). Mig 25 `AddIsUserSelectableToApprovalWorkflows`: ALTER `ApprovalWorkflows` +`IsUserSelectable bit` (admin pin/unpin workflow nào cho user pick lúc create phiếu, multi-select độc lập IsActive). Backfill `WHERE IsActive=1 SET 1` giữ behavior cũ. Designer +badge "Cho user chọn" + button Ghim/Bỏ ghim. Workspace filter dropdown chỉ workflows `IsUserSelectable=true`. Mig 22-24 V2 schema (Session 17): `ApprovalWorkflows`/Steps/Levels — Quy trình > Bước (Phòng) > Cấp (N NV cụ thể qua ApproverUserId, OR-of-N cùng cấp). PE.ApprovalWorkflowId pin V2. PE.CurrentApprovalLevelOrder track. State machine 5 trạng thái: Nháp / Đã gửi duyệt / Trả lại (Phase riêng TraLai=98) / Từ chối / Đã duyệt. PE Service V2 wire match `actor.Id == ApproverUserId`. Contract V2 ĐÃ WIRE (Mig 32+33 Plan B S29 — cookie-cutter mirror PE V2: `ApproveV2Async` + `ContractLevelOpinions` UPSERT + Workspace V2 Select dropdown). Mig 21 V1 flat workflow vẫn live cho phiếu cũ.)

### Modules

| Module | Namespace | Migration | Trạng thái |
|---|---|---|---|
| Contract (HĐ) | `Domain/Contracts/` | 1-11 | Feature-complete (7 ContractType × 9 phase) |
| PurchaseEvaluation (Duyệt NCC tiền-HĐ) | `Domain/PurchaseEvaluations/` | 12,13,15,49,50,52-61,65-68 | Feature-complete — +Hạng mục (Mig 49) +ngân sách per-gói-thầu role PRO/CCM (Mig 50) +Link hồ sơ NAS (Mig 52) +cờ gấp PRO/CCM + CCM duyệt-final theo ngưỡng (Mig 53) +giá đề xuất PRO/CCM + CCM-note (Mig 54/55) +PRO-split + ghi-chú-giá (Mig 56/57) +multi-winner liên-danh + level-finalize + NS-kỳ-CCM (Mig 58/59) +**`EndedByLevelFinalize` runtime-flag + backfill-fix (Mig 60/61, S97 — gotcha #78/#79)** +multi-NCC per-hạng-mục (Mig 65, S114 — gotcha #81) +duyệt-khoảng giá Min–Max (Mig 66, S117) +**freeze NS phiếu DaDuyet snapshot 11-cột + display-gate `budgetFrozen` (Mig 67, S133 — gotcha #81-EXT indirect-assignment)** +**lũy kế TẠM TÍNH vs CHÍNH XÁC (S134, 0-mig — `ComputePendingAsync` complementary [ChoDuyet-winner vào tạm-tính, nháp list-only] + sub-dòng amber matrix + banner màn duyệt + D4 audit-note changelog)** +**nút XÓA phiếu ở màn duyệt (Mig 68, S157/S162 — `AllowApproverDelete`, AddColumn no new table)**. Export PDF pending |
| **ContractSigningPlans (Kế hoạch Ký kết HĐ — KHKK)** | `Domain/ContractSigningPlans/` | **69, 71** | Feature-complete — **Mig 69 `AddContractSigningPlans` (S161, 7 bảng, 89→96)**: CRUD + Dossier 15 policy (W2) · duyệt V2 3-trạm test-before RED→GREEN (W3) · đường-ống HĐ V2 (W6) · bản-cứng **1-MỐC** theo owner *(form điền đủ → duyệt nếu có → upload bản cứng = xong)* (W7) · cây 4-folder GĐ ×2 app (S162) · **chuỗi K SP-002 @S164-S167 (Mig 71 nhóm-duyệt N1-N8 + Line hạng-mục + 8 workflow nhóm + bridge KHKK→HĐ hội-2-khóa K7 `aaed699`)**. 🔴 Gate quyền dùng **đúng key policy của endpoint** (`KeHoachKyKet`), KHÔNG OR key con — gotcha **#85** |
| ~~Budget (Ngân sách dự án)~~ | — | 14 → **Mig 50 DROP** | ⚠️ **REMOVED S61** — module Budget cũ XÓA, thay bằng PE-budget-per-gói-thầu (`PeWorkItemBudgets`). FE pages/types/menu `Bg_*` gỡ hết |
| Master (Supplier/Project/Department) | `Domain/Master/` | 2, 10, 48, 51, 62-64 | Feature-complete — +Project fields Year/Investor/Location/Package (Mig 48) +Department.ParentId org-tree (Mig 51) +Supplier expand 30-field/import/publish-state (Mig 62-64) |
| Identity (User/Role/Permission/MenuItem) | `Domain/Identity/` | 1, 3, 11 | Feature-complete (30 demo user — 16 sample + 14 Solutions thật) |
| Forms (Template + Clause) | `Domain/Forms/` | 4 | Feature-complete |
| Notifications | `Domain/Notifications/` | 6 | In-app + SignalR OK, email SMTP TODO |
| **Tests** | `tests/SolutionErp.{Domain,Infrastructure}.Tests/` | — | **Test pass — số hiện tại → [`docs/STATUS.md`](docs/STATUS.md)** — CI gate + path filter docs-only skip |

### Commit convention

```
[CLAUDE] <scope>: <imperative message>
```

**Scope:** `Contract` · `PurchaseEvaluation` · `Budget` · `Form` · `Workflow` · `Supplier` · `Auth` · `Admin` · `Api` · `App` · `Domain` · `Infra` · `FE-Admin` · `FE-User` · `Tests` · `Docs` · `CICD` · `Scripts` · `Skill`

## 🧪 Tests (Phase 9 — Session 9 +6)

```
tests/
├── SolutionErp.Domain.Tests/         (Domain policy: Workflow / PE)
└── SolutionErp.Infrastructure.Tests/ (codegen + CQRS handler + authz regression)
    ├── Common/                       (SqliteDbFixture + TestApplicationDbContext + IdentityFixture S9)
    ├── Services/                     (17 codegen + 6 PE 2-stage approval S9)
    └── Application/                  (6 test - PeWorkflowDefinition versioning)
```

**Unit test pass — số hiện tại → [`docs/STATUS.md`](docs/STATUS.md) (canonical).** CI gate + path filter live. (Lịch sử: S74 +5 CcmNote; S69 +20 Office-golive/PE-threshold; S67 +23 HRM test-after; S61 +22 `PeWorkItemBudgetTests` −14 `BudgetPolicyTests`, Domain drop BudgetPolicyTests cùng module **Budget đã REMOVE S61**.)

```bash
dotnet test SolutionErp.slnx     # chạy cả 2 test project
```

**Quy tắc timing test:** feature mới = test-after (UAT ổn → viết). Bug fix = test-before BẮT BUỘC (reproduce → fix). Critical algorithm (codegen/guard/financial/security) = test-before merge. Spec change = update test cũ + code chung commit. Skip: DTO mapping, CRUD master, FE snapshot. Detail `docs/rules.md §7`.

### CI/CD pipeline (3 fix lớn 29/04)

- ✅ Manual checkout bypass github.com (fix gotcha #39 TCP timeout)
- ✅ Path filter docs-only skip — `paths-ignore` (gotcha #41)
- ⏸️ npm cache (gotcha #40 — failed, rolled back)

**Tốc độ:** code commit ~3 phút / docs-only commit **0s** (skip).

## 🛠️ Skills (.claude/skills/) — 6 skill PHẢI dùng khi task khớp

| Domain (3) | Ops (3) |
|---|---|
| `contract-workflow` — state machine + versioned WF | `dependency-audit-erp` — npm/dotnet CVE scan |
| `form-engine` — render docx/xlsx + PDF | `ef-core-migration` — EF migration + 3-file rule |
| `permission-matrix` — role × menu × CRUD | `iis-deploy-runbook` — 3 site IIS + win-acme + runner |

**Audit định kỳ:** đầu mỗi tháng — combined skill + doc drift audit theo `docs/rules.md §6.4 + §9.4`, **em-main chạy thủ-công session đầu-tháng** (🧊 ghost-wire cron gỡ S100 H18 — CronList runtime = 0 job). **ĐÃ CHẠY 2026-08-07 (S179)** — trễ 6 ngày so hạn 08-01; verdict `tooling-auditor` = `PASS_WITH_FLAGS — 8`. **Lần kế: 2026-09-01.** 🔴 Ô ngày này là **giá-trị SỐNG duy nhất** trong repo (rules.md:546 trỏ về STATUS nhưng STATUS KHÔNG có row canonical) ⇒ **đóng audit thì phải lật ô này trong CÙNG lượt**, không thì đúng lượt đóng lại đẻ neo thối mới. 🧊 lineage: hạn 08-01 (S96 chạy 07-01).

Quy tắc:
- KHÔNG bulk-clone repo skill 3rd party. Chỉ thêm skill PROJECT-SPECIFIC. Xem `docs/rules.md §9` đầy đủ.
- KHÔNG rewrite toàn bộ MD định kỳ. Chỉ compact + patch drift. Xem `docs/rules.md §6.4` đầy đủ.

## 📖 Tài liệu quan trọng

| File | Nội dung |
|---|---|
| [`docs/STATUS.md`](docs/STATUS.md) | **🔥 Current state** — đọc đầu tiên |
| [`docs/HANDOFF.md`](docs/HANDOFF.md) | Brief 5 phút: session trước làm gì + next tasks |
| [`docs/rules.md`](docs/rules.md) | ⭐ Coding conventions (BE Clean Arch, FE React, DB, Git, Docs) |
| [`docs/architecture.md`](docs/architecture.md) | ⭐ Layered architecture + request lifecycle + deployment |
| [`docs/PROJECT-MAP.md`](docs/PROJECT-MAP.md) | Bản đồ tổng quan |
| [`docs/changelog/migration-todos.md`](docs/changelog/migration-todos.md) | Roadmap 5 phase + atomic tasks |
| [`docs/CLAUDE.md`](docs/CLAUDE.md) | Full context — tech stack chi tiết |
| [`docs/workflow-contract.md`](docs/workflow-contract.md) | State machine 9 phase HĐ + role matrix |
| [`docs/forms-spec.md`](docs/forms-spec.md) | Catalog 8 form + quy định mã HĐ RG-001 |
| [`docs/database/database-guide.md`](docs/database/database-guide.md) | DB conventions + migration workflow + cheatsheet |
| [`docs/database/schema-diagram.md`](docs/database/schema-diagram.md) | ⭐ ERD + luồng DB + data flow (số bảng → [`docs/STATUS.md`](docs/STATUS.md)) (+ §11 PE + §12 ~~Budget~~ DROP + §13 PEDeptOpinions + §14 Contract V2 LevelOpinions; §16+ Mig 32-62 pending) |
| [`docs/flows/README.md`](docs/flows/README.md) | Index 6 flow (auth, permission, contract, form, SLA) |
| [`docs/gotchas.md`](docs/gotchas.md) | ⭐ Bẫy đã gặp (số hiện tại → [`docs/STATUS.md`](docs/STATUS.md)) — đọc trước khi debug tương tự |
| [`.claude/skills/`](.claude/skills/README.md) | 6 skill: contract-workflow, form-engine, permission-matrix, dependency-audit-erp, ef-core-migration, iis-deploy-runbook |
| [`docs/guides/vps-setup.md`](docs/guides/vps-setup.md) | ⭐ Master runbook deploy VPS shared với VIETREPORT |

## 📒 Sổ yêu-cầu chủ dự án — GHI NGAY · TRA TRƯỚC KHI HỎI

Sổ: [`docs/governance/so-yeu-cau-chu-du-an.md`](docs/governance/so-yeu-cau-chu-du-an.md) (3 khối · mã `YC-`/`ST-`/`PN-` **vĩnh viễn, cấm tái dụng**).

- **GHI NGAY:** anh nói ra một yêu-cầu / sở-thích → ghi **nguyên văn** vào sổ **trong chính lượt đó**, không đợi cuối phiên. Diễn giải để **cột thứ 7** (*đội hiểu thành gì*), **tách hẳn** khỏi cột nguyên văn.
- **TRA TRƯỚC KHI HỎI:** trước khi hỏi lại anh bất-kỳ điều gì → **tra sổ trước**. 🔴 **Định nghĩa A5:** hỏi lại mà **không viện dẫn thông tin mới** = **vi phạm**; hỏi lại **kèm nguồn mới** = kỷ luật đúng.
- **Đóng = lật trạng thái, KHÔNG xoá dòng.** Số lần nhắc chỉ tăng.
- 🔴 **Sổ = REQUEST ⟂ WAL = TASK.** Sổ **CẤM** có cột trạng-thái-thi-công; muốn biết việc tới đâu thì **TRỎ** về WAL. Luật + lineage canonical → [`.claude/commands/tiep.md` §3-bis](.claude/commands/tiep.md).

## ⚠️ Kết thúc session

1. Update `docs/STATUS.md` (`In Progress` → `Recently Done`)
2. Tick checklist tương ứng trong `docs/changelog/migration-todos.md`
3. Tạo session log `docs/changelog/sessions/YYYY-MM-DD-HHMM-{topic}.md` nếu đáng ghi
4. Commit `[CLAUDE] <scope>: <message>`
5. ⚠️ **Update `SolutionErp.slnx`** nếu có `.cs/.csproj` mới

> Bỏ qua nếu chỉ trả lời câu hỏi, không sửa file nào.
