Files
solution-erp/docs/rules.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

16 KiB

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 cho full list

3. Frontend — React 19 + Vite 8 + TS 6

3.1 Named export, không default export

// ✅
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:

// ❌
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

<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 Iduniqueidentifier
FK {Entity}Iduniqueidentifier
Index IX_{Table}_{Col}
Unique UX_{Table}_{Col}

Full: xem 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

# 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

7. Testing (Phase 8 active — 77 test pass + CI gate live)

Stack đã apply

  • xUnit 2.9.3 + FluentAssertions 7.2 (pin trước v8 commercial license)
  • Microsoft.EntityFrameworkCore.Sqlite 10 (in-memory testing — supports transactions, không như InMemory provider)
  • Custom TestApplicationDbContext override nvarchar(max) → TEXT cho SQLite compat (xem tests/.../Common/SqliteDbFixture.cs)
  • NO mock framework (tránh mock-heavy, prefer EF in-memory hoặc fake data)

Test pyramid (bottom-heavy, KHÔNG E2E)

✅ Phase 1: Domain policy            54 test  (pure functions, no DB)
✅ Phase 2: Infra code generator     17 test  (SQLite in-mem)
✅ Phase 3 mini: Application versioning  6 test  (SQLite, no UserManager)
⏸️ Phase 3 full: Application handlers w/ Identity (cần UserManager DI helper)
⏸️ Phase 4: API smoke (WebApplicationFactory)
⏸️ Phase 5: FE Vitest cho lib utility
❌ E2E (Playwright) — KHÔNG làm (brittle cho solo dev)

Quy tắc bổ sung mỗi feature mới

  • Domain entity / enum → 0 test (compile check đủ)
  • Domain policy method → 2-3 test (positive + negative + edge case)
  • CQRS handler có guard logic → 1 test mỗi guard branch
  • Migration mới → smoke test "DbInitializer chạy được + seed không lỗi"
  • API endpoint mới → 1 happy path smoke (cho endpoint critical)
  • Bug found in production / staging → 1 regression test BEFORE merge fix

Pattern: "1 bug found → 1 regression test added".

Workflow user end-of-task

# Sau mỗi task code (BẮT BUỘC trước commit code):
dotnet test SolutionErp.slnx        # local verify ~3s
git add -A && git commit -m "..."
git push                             # CI tự run test gate trước build/deploy

Test fail local → fix trước khi push. Test fail trên CI → no deploy.

CI gate behavior

.gitea/workflows/deploy.yml:

# Path filter: skip CI hoàn toàn cho commit MD-only
on:
  push:
    paths-ignore:
      - 'docs/**'
      - '**/*.md'
      - '.claude/skills/**'

# 2 step test gate trước build/deploy (fail → exit → no deploy)
- name: Run unit tests (Domain)
- name: Run integration tests (Infrastructure)

Pending (Phase 3-5 future)

  • Application handler tests cần Identity UserManager DI setup helper
  • API smoke tests via WebApplicationFactory + SQLite
  • FE Vitest cho lib/queryMatches, lib/cn, money formatter

Detail: docs/architecture.md §11 Testing strategy.

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. Skills (.claude/skills/)

Project-level skills encode kiến thức SOLUTION_ERP-specific. Claude tự auto-invoke qua Skill tool dựa trên semantic match với description trong SKILL.md. PHẢI dùng skill khi gặp task tương ứng — không tự suy luận lại.

9.1 Skills hiện có (6)

Domain (3) — logic nghiệp vụ:

Skill Trigger khi
contract-workflow Bug chuyển phase, 403, mã HĐ sai, versioned WF, "HĐ cũ giữ cũ"
form-engine Render template docx/xlsx, FieldSpec, PDF export, upload form mới
permission-matrix Permission denied, gán role, menu không hiện, inherit Contracts/Workflows

Ops (3) — devops + security + schema:

Skill Trigger khi
dependency-audit-erp npm audit, dotnet vulnerable, scan CVE, nâng cấp package
ef-core-migration migrations add, schema change, snapshot lỗi, revert migration, DesignTimeDbContextFactory
iis-deploy-runbook Prod 500/502, cert hết hạn, restart app pool, gitea-runner fail, SignalR 401

9.2 Nguyên tắc tạo skill mới

Project-specific, KHÔNG clone generic. User-level skills global đã có sẵn (code-reviewer, sql-database-assistant, focused-fix, senior-frontend, mcp-builder, webapp-testing, review, security-review, ...). Project-level skills chỉ thêm khi:

  • Encode kiến thức SOLUTION_ERP-only (path, version pin, gotcha number, commit convention)
  • Generic skill không thể có (vd: 8 migration history cụ thể của project)
  • Có YAML when-to-use triggers rõ ràng (cả tiếng Việt + Anh)
  • Workflow Vietnamese-first (audience là dev Việt)

9.3 Format bắt buộc SKILL.md

---
name: kebab-case-name
description: 1-3 câu mô tả + stack specificity (cho semantic match đúng)
when-to-use:
  - "trigger english"
  - "trigger tiếng Việt"
---

# Skill Name

## Context
## Workflow / Commands (copy-pastable, không pseudocode)
## Pitfalls
## Code pointers (path đầy đủ src/Backend/...)
## Related (dẫn chiếu gotcha #N, docs/...)

9.4 Audit định kỳ — mỗi 4 tuần

Cadence: Mỗi đầu tháng (hoặc thứ Hai tuần đầu tháng), chạy audit skill list:

1. Đọc .claude/skills/README.md — list 6 skill hiện có
2. Cross-check với:
   - docs/STATUS.md "Recently Done" — feature mới nào chưa có skill cover?
   - docs/gotchas.md — gotcha mới (>3 dòng) nào nên gom thành skill mới?
   - migration-todos.md "In Progress" / "Next up" — task lớn sắp tới có cần skill?
3. Check repo nguồn skill 3rd party có gì mới:
   - https://github.com/alirezarezvani/claude-skills (default-branch)
   - Anthropic skills built-in (xem system reminder skill list)
4. Đánh giá staleness:
   - Mỗi skill: SKILL.md có còn match code thực tế không?
   - Trigger phrases có còn hiệu quả?
   - Code pointers có còn đúng path?
5. Hành động:
   - Skill stale → update SKILL.md + commit `[CLAUDE] Skill: refresh <name>`
   - Skill cần thêm → tạo theo §9.2 §9.3 + commit `[CLAUDE] Skill: add <name>`
   - Skill không còn cần → archive vào .claude/skills/_archived/ + commit
6. Update bảng §9.1 trong rules.md + .claude/skills/README.md + docs/CLAUDE.md count
7. Log vào docs/changelog/skill-audit-{YYYY-MM}.md (1 page max)

Trigger: Khi user nói "audit skill", "check skill list", "skill staleness", "định kỳ kiểm tra skill" → chạy workflow này.

Lịch chính thức:

  • Tháng 1, 2, 3, ... — đầu tháng audit (1 buổi sáng)
  • Hoặc khi bắt đầu Phase mới (vd Phase 6) → audit ngay trước phase
  • Hoặc khi docs/gotchas.md vượt mốc (cứ +5 gotcha thì xem có gom thành skill được không)

9.5 Anti-patterns

KHÔNG bulk-clone repo skill 3rd party — phần lớn doc-dump generic, không có when-to-use, dễ trigger sai.

KHÔNG viết skill chỉ để "có thêm" — skill = trigger semantic match, nếu description quá chung sẽ trigger nhầm trong session khác.

KHÔNG copy nội dung skill từ docs/ sang SKILL.md — skill phải ngắn gọn, action-oriented; docs/ là reference dài. Skill dẫn chiếu docs.

KHÔNG bỏ qua skill khi gặp task khớp — tự suy luận lại = lãng phí context + dễ sai.

10. Liên quan