[CLAUDE] Docs+Skill: Session 19 consolidate — schema-diagram §15 + skill refresh
User chốt Session 19 toàn bộ: update các MD chưa làm ở Chunk D Docs trước (theo §6.5 không cố sửa khi không cần — nhưng schema mới fundamental + skill drift quá lệch → đáng update ngay). - docs/database/schema-diagram.md — thêm §15 PE Level Opinions V2 (Mig 26): bối cảnh decision (5 câu Q&A user chốt), schema bảng + constraint design rationale (UNIQUE composite, FK Cascade Pe + Restrict Level, denorm SignedByFullName), Service hook pattern (ApproveV2Async UPSERT), so sánh anti-pattern Mig 15 cũ (endpoint POST/opinions rời) vs Mig 26 (Service hook auto sync khi Duyệt). Pattern reusable cho derived state khác. §16 cũ "Liên quan" đổi thành §16 (renumber). - .claude/skills/ef-core-migration/SKILL.md — frontmatter "21 migration" → "26 migration" + table history thêm Mig 22 ApprovalWorkflowsV2 / Mig 23 PE.ApprovalWorkflowId / Mig 24 CurrentApprovalLevelOrder / Mig 25 IsUserSelectable / Mig 26 PeLevelOpinionsForV2. Total bảng 55→59. Code pointers + Related cross-ref §15 mới. - .claude/skills/README.md — count "16 migration" → "26 migration" + "41 bẫy" → "44 bẫy" (drift cumulative S16-S19 patch). Path filter `.claude/skills/**` + `docs/**` → CI skip deploy (gotcha #41). Verify: dotnet test 81 pass (no regression). Memory home dir KHÔNG commit (ở C:\Users\pqhuy\.claude\): add entry mới `feedback_service_hook_vs_endpoint.md` + MEMORY.md index +1 row (15 entries total).
This commit is contained in:
@ -17,7 +17,7 @@ Skill này là tài liệu chuyên biệt để Claude (và developer khác) dù
|
|||||||
| Skill | Mục đích | Trigger ví dụ | Trạng thái |
|
| Skill | Mục đích | Trigger ví dụ | Trạng thái |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `dependency-audit-erp` | Scan CVE NuGet + npm 2 FE, respect pin constraint (MediatR 12.4.1, Swashbuckle 6.9.0) | "npm audit", "dotnet vulnerable", "deps scan", "nâng cấp package" | ✅ New Tier 3 |
|
| `dependency-audit-erp` | Scan CVE NuGet + npm 2 FE, respect pin constraint (MediatR 12.4.1, Swashbuckle 6.9.0) | "npm audit", "dotnet vulnerable", "deps scan", "nâng cấp package" | ✅ New Tier 3 |
|
||||||
| `ef-core-migration` | Tạo/revert EF Core 10 migration, 3-file rule, DesignTimeDbContextFactory, **16 migration history** (Init → AddTwoStageDeptApprovalAndSmartReject) | "thêm migration", "EF migration", "schema update", "snapshot lỗi" | ✅ Updated Phase 9 (Mig 16) |
|
| `ef-core-migration` | Tạo/revert EF Core 10 migration, 3-file rule, DesignTimeDbContextFactory, **26 migration history** (Init → AddPeLevelOpinionsForV2) | "thêm migration", "EF migration", "schema update", "snapshot lỗi" | ✅ Updated Session 19 (Mig 26 PE Level Opinions V2) |
|
||||||
| `iis-deploy-runbook` | 3 IIS site + win-acme cert + gitea-runner + LibreOffice + debug 500/502/SignalR prod + **G-084 IPv4/IPv6 hardening** | "prod 500", "IIS fail", "cert hết hạn", "restart app pool", "deploy IIS", "port hijack" | ✅ Updated (G-084) |
|
| `iis-deploy-runbook` | 3 IIS site + win-acme cert + gitea-runner + LibreOffice + debug 500/502/SignalR prod + **G-084 IPv4/IPv6 hardening** | "prod 500", "IIS fail", "cert hết hạn", "restart app pool", "deploy IIS", "port hijack" | ✅ Updated (G-084) |
|
||||||
|
|
||||||
## Format chuẩn 1 skill
|
## Format chuẩn 1 skill
|
||||||
@ -87,5 +87,5 @@ when-to-use:
|
|||||||
## Related
|
## Related
|
||||||
|
|
||||||
- `docs/CLAUDE.md` — quick rules + full stack context
|
- `docs/CLAUDE.md` — quick rules + full stack context
|
||||||
- `docs/gotchas.md` — 41 bẫy đã gặp
|
- `docs/gotchas.md` — 44 bẫy đã gặp
|
||||||
- `docs/changelog/migration-todos.md` — roadmap 5 phase + Tier 3
|
- `docs/changelog/migration-todos.md` — roadmap 5 phase + Tier 3
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: ef-core-migration
|
name: ef-core-migration
|
||||||
description: Tạo/sửa/revert EF Core 10 migration cho SOLUTION_ERP. Dùng khi thêm entity mới, thay đổi schema, rollback migration, debug DesignTimeDbContextFactory fail. Đã có 21 migration sẵn (Init → RefactorWorkflowToFlatModel). Snapshot + Designer + Migration 3-file rule bắt buộc commit đủ.
|
description: Tạo/sửa/revert EF Core 10 migration cho SOLUTION_ERP. Dùng khi thêm entity mới, thay đổi schema, rollback migration, debug DesignTimeDbContextFactory fail. Đã có 26 migration sẵn (Init → AddPeLevelOpinionsForV2). Snapshot + Designer + Migration 3-file rule bắt buộc commit đủ.
|
||||||
when-to-use:
|
when-to-use:
|
||||||
- "thêm migration"
|
- "thêm migration"
|
||||||
- "EF Core migration"
|
- "EF Core migration"
|
||||||
@ -16,7 +16,7 @@ when-to-use:
|
|||||||
|
|
||||||
> **Context:** .NET 10 + EF Core 10 + SQL Server. DbContext: `ApplicationDbContext` ở `Infrastructure/Persistence/`. Startup: `SolutionErp.Api`.
|
> **Context:** .NET 10 + EF Core 10 + SQL Server. DbContext: `ApplicationDbContext` ở `Infrastructure/Persistence/`. Startup: `SolutionErp.Api`.
|
||||||
|
|
||||||
## Migration history (21 migration hiện có)
|
## Migration history (26 migration hiện có)
|
||||||
|
|
||||||
| # | Name | Tables added / changed |
|
| # | Name | Tables added / changed |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@ -41,8 +41,13 @@ when-to-use:
|
|||||||
| **19** | **`AlterPeDeptApprovalsUniqueFilteredForInnerSteps`** | **Filtered unique split: drop UNIQUE cũ Mig 16 → 2 filtered: legacy `WHERE InnerStepId IS NULL` (Stage Review/Confirm) + N-stage `WHERE InnerStepId IS NOT NULL` (per inner step). Tránh conflict 2 inner step cùng dept Stage=Confirm. Session 12.** |
|
| **19** | **`AlterPeDeptApprovalsUniqueFilteredForInnerSteps`** | **Filtered unique split: drop UNIQUE cũ Mig 16 → 2 filtered: legacy `WHERE InnerStepId IS NULL` (Stage Review/Confirm) + N-stage `WHERE InnerStepId IS NOT NULL` (per inner step). Tránh conflict 2 inner step cùng dept Stage=Confirm. Session 12.** |
|
||||||
| **20** | **`AddContractWorkflowInnerStepsAndAlterDeptApprovalUnique`** | **N-stage workflow Contract mirror PE Mig 18+19 — GỘP 1 migration: CREATE TABLE `WorkflowStepInnerSteps` + ALTER `ContractDeptApproval.InnerStepId` + DropIndex old + Recreate filtered legacy/N-stage + 3 IX + FK. Session 13 (2026-05-07).** |
|
| **20** | **`AddContractWorkflowInnerStepsAndAlterDeptApprovalUnique`** | **N-stage workflow Contract mirror PE Mig 18+19 — GỘP 1 migration: CREATE TABLE `WorkflowStepInnerSteps` + ALTER `ContractDeptApproval.InnerStepId` + DropIndex old + Recreate filtered legacy/N-stage + 3 IX + FK. Session 13 (2026-05-07).** |
|
||||||
| **21** | **`RefactorWorkflowToFlatModel`** | **🎯 DRASTIC REFACTOR (Session 16, 2026-05-08): bỏ phase enum legacy, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking. Workflow flat (Phòng × Cấp + Approvers). 4 ALTER (PE/Contract +CurrentWorkflowStepIndex/RejectedAtStepIndex) + 2 ALTER (WorkflowStep +DepartmentId/PositionLevel PE+Contract) + DROP TABLE × 2 (PE+Contract WorkflowStepInnerSteps Mig 18+20) + DROP COLUMN InnerStepId × 2 + DROP filtered indexes Mig 19/20 + RESTORE simple unique non-filtered × 2.** |
|
| **21** | **`RefactorWorkflowToFlatModel`** | **🎯 DRASTIC REFACTOR (Session 16, 2026-05-08): bỏ phase enum legacy, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking. Workflow flat (Phòng × Cấp + Approvers). 4 ALTER (PE/Contract +CurrentWorkflowStepIndex/RejectedAtStepIndex) + 2 ALTER (WorkflowStep +DepartmentId/PositionLevel PE+Contract) + DROP TABLE × 2 (PE+Contract WorkflowStepInnerSteps Mig 18+20) + DROP COLUMN InnerStepId × 2 + DROP filtered indexes Mig 19/20 + RESTORE simple unique non-filtered × 2.** |
|
||||||
|
| **22** | **`AddApprovalWorkflowsV2`** | **🎯 V2 schema mới (Session 17, 2026-05-08) — riêng cho UAT trước khi drop legacy V1. 3 entity ApprovalWorkflow + Step + Level + enum ApplicableType (DuyetNcc=1 / DuyetNccPhuongAn=2 / Contract=3). 3 CREATE TABLE + UNIQUE (Code, Version) + FK Cascade Step→Workflow + Level→Step + FK Restrict Department + ApproverUserId. Cấu trúc: Quy trình > Bước (Phòng) > Cấp (1 NV cụ thể qua ApproverUserId, OR-of-N rows cùng Order = same Cấp). DbInitializer +menu V2 + leaf `AwV2_DuyetNcc/DuyetNccPhuongAn`.** |
|
||||||
|
| **23** | **`AddApprovalWorkflowIdToPurchaseEvaluation`** | **Pin V2 vào PE — `PE.ApprovalWorkflowId Guid?` + EF FK Restrict. Workspace Select bắt buộc workflow lúc create + Validate `ApplicableType` match `PE.Type`. Session 17.** |
|
||||||
|
| **24** | **`AddCurrentApprovalLevelOrderToPe`** | **Service V2 wire — `PE.CurrentApprovalLevelOrder int?` track Cấp đang chờ trong Step hiện tại khi pin ApprovalWorkflowId. Null khi V1 legacy hoặc terminal. Service `ApproveV2Async` group Levels by Order = Cấp (OR-of-N), match `actor.Id ∈ ApproverUserId`, advance levelOrder++ trong Step → idx++ + reset levelOrder=1 → DaDuyet. Synthetic Policy `ForV2Schema()` cho FE nextPhases. Session 17.** |
|
||||||
|
| **25** | **`AddIsUserSelectableToApprovalWorkflows`** | **ALTER `ApprovalWorkflows` +`IsUserSelectable bit NOT NULL DEFAULT 0` — admin pin/unpin workflow cho user pick lúc create phiếu (multi-select, độc lập IsActive — multiple version cùng selectable). Backfill `WHERE IsActive=1 SET 1` giữ behavior cũ (active workflows vẫn pickable). Designer +badge "Cho user chọn" + button Ghim/Bỏ ghim + mutation toggle. Workspace dropdown filter `isUserSelectable=true` only. API `PATCH /api/approval-workflows-v2/{id}/user-selectable` admin-only. Session 18 (2026-05-08).** |
|
||||||
|
| **26** | **`AddPeLevelOpinionsForV2`** | **🎯 Section 5 dynamic theo Workflow V2 (Session 19, 2026-05-09) — bảng mới `PurchaseEvaluationLevelOpinions` UNIQUE composite (PEId, ApprovalWorkflowLevelId), FK Cascade Pe + Restrict Level. Comment nvarchar(2000) + SignedAt + SignedByUserId + SignedByFullName denorm. Service `ApproveV2Async` UPSERT auto khi NV duyệt (Q1=1B sync gắn với Duyệt, KHÔNG endpoint CRUD rời). Match level theo ApproverUserId; Admin override fallback first level (FE banner "Admin duyệt thay" khi SignedByUserId !== ApproverUserId). Comment empty → "(duyệt — không ý kiến)" placeholder Q4 bonus. Phiếu V1 legacy fallback Mig 15 4 box readOnly. Mig 15 deprecated cho V2 phiếu (drop sau UAT confirm Mig 27+).** |
|
||||||
|
|
||||||
Total: **55 bảng** dbo + `__EFMigrationsHistory` (Mig 21 drop 2 InnerStep tables, restore Mig 16 simple unique). Xem `docs/database/schema-diagram.md` ERD đầy đủ.
|
Total: **59 bảng** dbo + `__EFMigrationsHistory` (Mig 22 add 3 V2 + Mig 26 add 1 PE Level Opinions). Xem `docs/database/schema-diagram.md` ERD đầy đủ + §14 ApprovalWorkflow V2 + §15 PE Level Opinions V2.
|
||||||
|
|
||||||
## N-stage workflow pattern (Mig 18-20 — Session 12-13)
|
## N-stage workflow pattern (Mig 18-20 — Session 12-13)
|
||||||
|
|
||||||
@ -250,7 +255,7 @@ sqlcmd -S .\SQLEXPRESS -d SolutionErp -U vrapp -P <pw> -i migrate.sql
|
|||||||
|
|
||||||
## Code pointers
|
## Code pointers
|
||||||
|
|
||||||
- `src/Backend/SolutionErp.Infrastructure/Persistence/ApplicationDbContext.cs` — DbSet cho 55 bảng (16 migration)
|
- `src/Backend/SolutionErp.Infrastructure/Persistence/ApplicationDbContext.cs` — DbSet cho 59 bảng (26 migration)
|
||||||
- `src/Backend/SolutionErp.Infrastructure/Persistence/DesignTimeDbContextFactory.cs` — EF tooling factory
|
- `src/Backend/SolutionErp.Infrastructure/Persistence/DesignTimeDbContextFactory.cs` — EF tooling factory
|
||||||
- `src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs` — seed + warn + migrate runtime + backfill (idempotent reconcile pattern)
|
- `src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs` — seed + warn + migrate runtime + backfill (idempotent reconcile pattern)
|
||||||
- `src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/` — IEntityTypeConfiguration<T> per entity
|
- `src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/` — IEntityTypeConfiguration<T> per entity
|
||||||
@ -259,5 +264,5 @@ sqlcmd -S .\SQLEXPRESS -d SolutionErp -U vrapp -P <pw> -i migrate.sql
|
|||||||
## Related
|
## Related
|
||||||
|
|
||||||
- `docs/database/database-guide.md` — conventions + migration workflow chi tiết
|
- `docs/database/database-guide.md` — conventions + migration workflow chi tiết
|
||||||
- `docs/database/schema-diagram.md` — **ERD 55 bảng** + §11 PE module + §12 Budget module + §13 PEDeptOpinions + §14 DepartmentApprovals (Mig 16)
|
- `docs/database/schema-diagram.md` — **ERD 59 bảng** + §11 PE module + §12 Budget module + §13 PEDeptOpinions (Mig 15) + §14 ApprovalWorkflow V2 (Mig 22-25) + §15 PE Level Opinions V2 (Mig 26)
|
||||||
- `docs/gotchas.md` #7, #17, #38 — migration pitfalls + Identity 4-field rename
|
- `docs/gotchas.md` #7, #17, #38 — migration pitfalls + Identity 4-field rename
|
||||||
|
|||||||
@ -805,12 +805,84 @@ Default behavior (sau Mig 25 backfill): active workflows tự động `IsUserSel
|
|||||||
|
|
||||||
Workspace `PeWorkspaceCreateView` query `.filter(w => w.isUserSelectable)` — chỉ hiện workflows admin đã ghim.
|
Workspace `PeWorkspaceCreateView` query `.filter(w => w.isUserSelectable)` — chỉ hiện workflows admin đã ghim.
|
||||||
|
|
||||||
### Pending Session 19+:
|
### Pending Session 20+:
|
||||||
|
|
||||||
- Contract V2 wire (Mig 26): mirror PE pattern — thêm `Contract.ApprovalWorkflowId` + `CurrentApprovalLevelOrder` + `ContractWorkflowService.ApproveV2Async`
|
- Contract V2 wire (Mig 27/28): mirror PE pattern — thêm `Contract.ApprovalWorkflowId` + `CurrentApprovalLevelOrder` + `ContractWorkflowService.ApproveV2Async` + `ContractLevelOpinions` (mirror Mig 26)
|
||||||
- Drop legacy V1: sau khi không còn phiếu pin `WorkflowDefinitionId` → drop `WorkflowDefinitions` + `WorkflowSteps` + `WorkflowStepApprovers` + drop deprecated columns `RejectedAtStepIndex` / `RejectedFromPhase` (Mig 27 cleanup)
|
- Drop legacy V1: sau khi không còn phiếu pin `WorkflowDefinitionId` → drop `WorkflowDefinitions` + `WorkflowSteps` + `WorkflowStepApprovers` + drop deprecated columns `RejectedAtStepIndex` / `RejectedFromPhase` (Mig cleanup)
|
||||||
|
- Drop Mig 15 `PurchaseEvaluationDepartmentOpinions` cho V2 phiếu (sau khi tất cả phiếu V2 chỉ dùng Mig 26 LevelOpinions). Phiếu V1 legacy giữ Mig 15 backward compat.
|
||||||
|
|
||||||
## 15. Liên quan
|
## 15. PE Level Opinions V2 (Migration 26 — 1 bảng mới, Session 19)
|
||||||
|
|
||||||
|
**Mục đích:** Thay thế Section 5 "Ý kiến 4 phòng ban" CỨNG (Mig 15 — PheDuyet/CCM/MuaHàng/SmPm) → động theo `ApprovalWorkflowsV2` (Mig 22-25). Mỗi (PE × Level) = 1 row sign-off với ý kiến + chữ ký NV.
|
||||||
|
|
||||||
|
**Bối cảnh decision (đáng giữ nguyên văn cho session sau):** User UAT Section 5 cũ "có 4 box CỨNG" — không scale theo workflow V2 cấu hình động (N Bước × N Cấp × N NV). Q&A 5 câu chốt spec trước code (memory `feedback_drastic_refactor_scope` áp dụng):
|
||||||
|
|
||||||
|
- **Q1=1B (gắn — KHÔNG rời):** Comment khi NV nhấn Duyệt qua Workflow Panel → auto sync sang OpinionBox của NV đó. Section 5 read-only summary, KHÔNG có form input rời (anti-pattern Mig 15 cũ — user phải double action: duyệt + ký).
|
||||||
|
- **Q2=2A+Admin override:** NV chính chủ duyệt (match `ApproverUserId == actor.Id`). Admin override → fallback first level group, lưu `SignedByUserId = admin.Id`. FE banner amber "⚠ Admin <name> duyệt thay" khi `SignedByUserId !== ApproverUserId` → minh bạch ai thực sự duyệt.
|
||||||
|
- **Q3=V2 hết:** Phiếu V2 (pin `ApprovalWorkflowId`) → render dynamic. Phiếu V1 legacy (chỉ `WorkflowDefinitionId`) → fallback Mig 15 readOnly (giữ data history, KHÔNG drop). Mig 15 deprecated cho V2 phiếu, drop sau UAT confirm.
|
||||||
|
- **Q4=4C+placeholder:** Phase=DaDuyet/TuChoi → khoá hoàn toàn. Comment empty/whitespace → ghi `"(duyệt — không ý kiến)"` placeholder (vẫn tính là sign-off).
|
||||||
|
- **Q5=5A grid layout:** Group theo Step (header "Bước N — Phòng X" badge emerald + hint "(N người duyệt)") → grid-cols-2 cho N approvers. "Bước 1 phòng A có 2 NV → 2 box ngang hàng" — wrap row khi N>2.
|
||||||
|
|
||||||
|
**Bảng `PurchaseEvaluationLevelOpinions`:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Id uniqueidentifier PK
|
||||||
|
PurchaseEvaluationId FK Cascade → PurchaseEvaluations
|
||||||
|
ApprovalWorkflowLevelId FK Restrict → ApprovalWorkflowLevels (Mig 22)
|
||||||
|
Comment nvarchar(2000) -- ý kiến hoặc placeholder
|
||||||
|
SignedAt datetime2 NOT NULL -- luôn có khi UPSERT
|
||||||
|
SignedByUserId uniqueidentifier NOT NULL -- NV chính chủ HOẶC Admin override
|
||||||
|
SignedByFullName nvarchar(200) NOT NULL -- denorm: tránh user xóa/đổi tên
|
||||||
|
+ AuditableEntity audit fields (CreatedAt/UpdatedAt/IsDeleted/...)
|
||||||
|
|
||||||
|
UNIQUE (PurchaseEvaluationId, ApprovalWorkflowLevelId)
|
||||||
|
INDEX (ApprovalWorkflowLevelId)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Constraint design rationale (KHÔNG paraphrase):**
|
||||||
|
|
||||||
|
- **UNIQUE composite (PEId, LevelId):** 1 row per (phiếu × Cấp). Latest write wins khi Service ApproveV2Async UPSERT — phiếu loop qua TraLai → resubmit → cùng NV duyệt lại = OVERWRITE row cũ (không tạo duplicate). Multi-NV cùng Cấp (OR-of-N) = nhiều LevelId distinct → mỗi NV có row riêng nếu duyệt.
|
||||||
|
- **FK Cascade Pe:** Xoá phiếu (soft-delete actually) → opinions cascade.
|
||||||
|
- **FK Restrict Level:** Admin xoá Level (qua Designer V2) bị chặn nếu opinion tồn tại — protect history sign-off.
|
||||||
|
- **`SignedByUserId` KHÔNG nav (denorm `SignedByFullName`):** User có thể bị soft-delete / đổi tên → opinion vẫn render lịch sử đúng tên tại thời điểm ký. Tránh cascade phức tạp khi xoá user.
|
||||||
|
|
||||||
|
**Service hook pattern (ApproveV2Async):**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Sau khi log approval row → UPSERT opinion cho Cấp hiện tại
|
||||||
|
var matchingLevel = pendingLevelGroup
|
||||||
|
.FirstOrDefault(l => l.ApproverUserId == actorUserId) // NV chính chủ
|
||||||
|
?? pendingLevelGroup.First(); // Admin fallback first
|
||||||
|
|
||||||
|
var existing = await db.PurchaseEvaluationLevelOpinions
|
||||||
|
.FirstOrDefaultAsync(o => o.PEId == pe.Id && o.LevelId == matchingLevel.Id, ct);
|
||||||
|
|
||||||
|
var normalizedComment = string.IsNullOrWhiteSpace(comment)
|
||||||
|
? "(duyệt — không ý kiến)" // Q4 bonus placeholder
|
||||||
|
: comment.Trim();
|
||||||
|
|
||||||
|
// Upsert: existing → update; null → Add new
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reject KHÔNG sync** (Trả lại / Từ chối) — giữ snapshot opinion cũ nếu có.
|
||||||
|
|
||||||
|
**Lưu ý anti-pattern (so với Mig 15 cũ):**
|
||||||
|
|
||||||
|
- ❌ Mig 15 dùng endpoint POST `/pe/{id}/opinions` cho user nhập rời + 4 box CỨNG → user phải nhấn 2 action (Duyệt workflow + Ký opinion box) → UX kém + risk inconsistency
|
||||||
|
- ✅ Mig 26 dùng Service hook UPSERT khi nhấn Duyệt → 1 action user, sync auto
|
||||||
|
|
||||||
|
→ Pattern **"derived state qua Service hook, KHÔNG endpoint CRUD riêng"** áp dụng được cho:
|
||||||
|
- Audit trail tự động khi save entity
|
||||||
|
- Notification interceptor SaveChangesInterceptor
|
||||||
|
- Code generation (gen mã HĐ khi DangDongDau) — đã có
|
||||||
|
|
||||||
|
### Pending Session 20+:
|
||||||
|
|
||||||
|
- Contract Section 5 V2 dynamic — mirror PE Mig 26 pattern. Tạo `ContractLevelOpinions` (Mig sau Contract V2 wire). Audit-reuse pattern memory `feedback_audit_reuse_before_clone` áp dụng.
|
||||||
|
- Drop Mig 15 cho V2 phiếu — cleanup sau UAT confirm.
|
||||||
|
- Test V2 Service hook — Domain test ApproveV2 + UPSERT opinion match logic + Admin override match firstLevel + comment empty placeholder.
|
||||||
|
|
||||||
|
## 16. Liên quan
|
||||||
|
|
||||||
- [`database-guide.md`](database-guide.md) — conventions + migration workflow + cheatsheet đầy đủ
|
- [`database-guide.md`](database-guide.md) — conventions + migration workflow + cheatsheet đầy đủ
|
||||||
- [`../architecture.md`](../architecture.md) — layered architecture + data flow
|
- [`../architecture.md`](../architecture.md) — layered architecture + data flow
|
||||||
|
|||||||
Reference in New Issue
Block a user