Session 5 (29/04) — 6 commit feature + 1 chốt MD này. ==== Stats sau session 5 ==== - 52 DB tables (+1 PEDeptOpinions) - 15 migrations (+`AddPurchaseEvaluationDepartmentOpinions`) - ~128 API endpoints (+4) - ~31 FE pages (+5 Budget + 1 PeWorkflowsPage) - 71 unit test pass (54 Domain + 17 Infra) — CI gate live, fail → no deploy - ~13050 BE LOC (+1300) - 30 demo user, 38 gotchas, 6 skill (no change) ==== MD touched ==== - STATUS.md: header Phase 8 + 6 row Recently Done session 5 + cumulative cột S5 + In Progress S6 (Hard blockers + Optional polish + Tests Phase 3-5 + Ops) - HANDOFF.md: TL;DR 6 milestone S5 + Cảnh báo S6 (CI test gate workflow mới) + Priority 0 S6 (UAT + Ops focus) + Phase status table cập nhật - migration-todos.md: Phase 8 done với A/B/C/D/E (FE Budget / PE-HD integration / PE WF Designer / Ý kiến 4 PB / Tests Phase 1-2) + Phase 9 active (UAT + Ops + carry over) - architecture.md: §11 Testing strategy mới (test pyramid bottom-heavy + stack + CI gate + phased priority + quy tắc bổ sung mỗi feature) - database/schema-diagram.md: Migration 15 row + total 52 tables + §13 PE Department Opinion (1 bảng UNIQUE PEId+Kind + Upsert behavior + SQL DDL) - ef-core-migration SKILL: migration 15 entry + 52 tables total + Phase 8 update note - CLAUDE.md (root): modules table + Tests row + scope `Tests` + Tests section mới + count update 15/52 - docs/CLAUDE.md: 7 module bullet + ERD 52 bảng + Roadmap Phase 8 done + Phase 9 active S6 - memory project_solution_erp.md: Phase 8 summary + Session 6 priority + workflow user mới (dotnet test → commit → push) - session log 2026-04-29-chot-session-5-budget-fe-pe-tests.md (NEW — 10+ section detail) ==== Verify ==== - dotnet test SolutionErp.slnx → 71 pass / 2s - git status clean sau commit này Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16 KiB
Session log — 2026-04-29 (Phase 8 — Budget FE complete + PE feature gap đóng + Tests Phase 1-2)
Topic: FE Budget pages 3-panel + PE/HD-Budget integration + PE Detail UI restructure + PE Workflow Designer + Ý kiến 4 phòng ban + Tests Phase 1-2 (Domain + Infra) + CI gate.
Commits (6 total session 5):
df12fb1— FE Budget pages 3-panel cả 2 app61e5d4d— PE/Contract → Budget integration + cột "So với ngân sách" PE matrix7e36241— PE Detail UI restructure theo spec PHIẾU TRÌNH KÝ (4 section đánh số)5d94bb4— PE Workflow Designer admin UI + Ý kiến 4 phòng ban (Migration 15)d3f9346— Tests Phase 1: Domain unit tests + CI gatedf5988b— Tests Phase 2: Code generator format + sequence (SQLite in-memory)- (Session log này — commit chốt MD session 5)
Migration: 15 AddPurchaseEvaluationDepartmentOpinions — 1 bảng UNIQUE PEId+Kind cho 4 phòng ban sign-off.
A. FE Budget pages 3-panel (commit df12fb1)
Mirror pattern PE 3-panel cho cả fe-admin + fe-user:
| File | Mô tả |
|---|---|
types/budget.ts |
BudgetPhase 5-state enum + DTO types (BudgetListItem/DetailRow/Approval/Bundle/DetailBody) |
pages/budgets/BudgetsListPage.tsx |
3-panel [340px_1fr_360px] + filter Phase + Năm + ?phase=Pending alias filter ChoCCM/ChoCEO + readOnly mode (menu Duyệt) + BudgetDetailPage fullpage mobile |
pages/budgets/BudgetCreatePage.tsx |
Form Header (Tên/Năm/Dự án/Phòng ban/Mô tả). Edit mode khóa Project+Department |
components/budgets/BudgetDetailTabs.tsx |
Section Thông tin Header + Section Hạng mục table CRUD inline (Add/Edit/Delete dialog auto-compute ThanhTien=KL×ĐG). Export BudgetApprovalsSection + BudgetHistorySection cho Panel 3 reuse |
components/budgets/BudgetWorkflowPanel.tsx |
Panel 3 timeline activePhases visual + nextPhases buttons (Approve xanh / Reject đỏ / Trả về vàng) + Dialog xác nhận có comment + Approvals + Changelog |
App.tsx 3 route mới /budgets, /budgets/new, /budgets/:id cả 2 app. Layout resolver Bg_List/Bg_Create/Bg_Pending + root Budgets icon Wallet.
B. PE/Contract → Budget integration (commit 61e5d4d)
BE (6 file)
Budgets/Dtos/BudgetDtos.cs: +BudgetSummaryDto(compact header snapshot reuse cho PE & Contract)PE Dtos + Features: +BudgetId?+Budget?vào DetailBundle. Create/Update PE commands + validate cùng Project + Phase=DaDuyetContract Dtos + Features: same patternCreateContractFromEvaluation: carry forwardpe.BudgetId → contract.BudgetId
FE (10 file)
- PE & Contract Create form: Select "Ngân sách" filter Phase=DaDuyet & Project match (BE-side filter qua
/budgets?projectId=&phase=4) - PE InfoTab + Contract Edit: hiển thị Budget link clickable
→ /budgets?id= - PE ItemsTab matrix: cột "NS link · Δ" chỉ hiện khi
ev.budgetId, match per-row qua keygroupCode|itemCode(fetch/budgets/{id}riêng), footer aggregate (xanh dưới / đỏ vượt / xám khớp)
C. PE Detail UI restructure (commit 7e36241)
Match form chính thức PHIẾU TRÌNH KÝ CHỌN TP/NCC với 4 section đánh số:
1. Thông tin gói thầu (a Tên + b Dự án + Địa điểm/Mô tả compact)
2. Chọn NCC / TP (a NCC chọn / b Ngân sách / c Giá chào thầu auto-compute / d Bản so sánh)
3. NCC / TP tham gia ({n}) (table 5 cột UX web)
4. Hạng mục + Báo giá ({n}) (matrix + cột "NS link · Δ")
5. Ý kiến 4 phòng ban (sign-off) (2x2 grid OpinionBox — Section thêm trong commit kế)
Section 2.c "Giá chào thầu" auto-compute: lookup winnerSupplierRowId qua selectedSupplierId, sum quotes có purchaseEvaluationSupplierId === winnerRowId. Hiện "—" khi chưa chọn NCC hoặc chưa nhập báo giá.
Section 2.d "Bản so sánh" dùng filter purchaseEvaluationSupplierId === null (file tổng, không gắn NCC cụ thể).
FormRow helper mới (label 176px + value flex) thay cho dl grid 2-col cũ. Drop helper Field cũ.
D. PE Workflow Designer admin UI + Ý kiến 4 phòng ban (commit 5d94bb4)
PE Workflow Designer
- BE
Application/PurchaseEvaluations/PeWorkflowAdminFeatures.cs~250 LOC mirror Contract WorkflowAdminFeatures (GetOverview + CreateNewVersion deactivate cũ atomic) - BE
Api/Controllers/PeWorkflowsController.cs2 endpoint reuse policyWorkflows.Read+Workflows.Create - FE
pages/system/PeWorkflowsPage.tsx~500 LOC: Landing 2-card grid → TypePanel khi pick code → DefinitionCard active+history + PeWorkflowDesigner dialog (clone từ existing + edit Code/Name/Description + add/remove steps + +Role / +User approvers) - App.tsx route
/system/pe-workflows+/system/pe-workflows/:typeCode. ResolverPeWf_<Code>từ session 3 đã trỏ.
Ý kiến 4 phòng ban PE
Migration 15 AddPurchaseEvaluationDepartmentOpinions:
CREATE TABLE PurchaseEvaluationDepartmentOpinions (
Id, PurchaseEvaluationId FK Cascade,
Kind INT NOT NULL, -- 1=PheDuyet, 2=Ccm, 3=MuaHang, 4=SmPm
Opinion NVARCHAR(2000) NULL,
SignedAt DATETIME2 NULL,
UserId UNIQUEIDENTIFIER NULL,
UserName NVARCHAR(200) NULL,
-- AuditableEntity columns
);
CREATE UNIQUE INDEX ON (PurchaseEvaluationId, Kind); -- max 1 row mỗi loại phòng ban per phiếu
- Domain entity
PurchaseEvaluationDepartmentOpinion+ enumPeDepartmentKind - Application
PeDepartmentOpinionFeatures.cs: Upsert pattern (sign=true → set SignedAt+UserId+UserName denorm, sign=false → giữ chữ ký cũ chỉ update text), Delete admin override - API
POST /api/purchase-evaluations/{id}/opinions+DELETE .../opinions/{kind} - FE Section "5. Ý kiến 4 phòng ban (sign-off)" 2x2 grid
OpinionBox:- Read mode (menu Duyệt readOnly): hiển thị text + chữ ký
- Edit mode: textarea + 2 button "Lưu text" / "Lưu & Ký"
- Badge "Đã ký" emerald + tên người ký + ngày
E. Tests Phase 1 — Domain unit tests + CI gate (commit d3f9346)
tests/SolutionErp.Domain.Tests/
├── Contracts/WorkflowPolicyTests.cs (~17 test)
├── PurchaseEvaluations/PurchaseEvaluationPolicyTests.cs (~17 test)
├── Budgets/BudgetPolicyTests.cs (~13 test)
└── SolutionErp.Domain.Tests.csproj (xUnit 2.9.3 + FluentAssertions 7.2)
Coverage:
- Standard 9-phase + SkipCcm 7-phase (HĐ): role transitions, CCM check, BOD signing, terminal phase, reject paths
- Registry: DefaultPolicyName per ContractType (7 type) + bypass flag override Standard→SkipCcm
- FromDefinition versioned: build từ ordered steps + reject path back to draft + TuChoi auto-add + UserKindApprover populate UserTransitions
- NccOnly (PE-A) 3-step + NccWithPlan (PE-B) 5-step: skip ChoDuAn/ChoCEODuyetPA chỉ ở B
- BudgetPolicy Default 3-step + DaDuyet/TuChoi terminal + SLA spec verify (5d/3d/2d)
CI gate .gitea/workflows/deploy.yml:
- name: Run unit tests (Domain)
run: dotnet test tests/SolutionErp.Domain.Tests/...
# Fail → exit $LASTEXITCODE → no deploy
54 test pass / 6.4s. Verify local: dotnet test SolutionErp.slnx.
F. Tests Phase 2 — Code generator format + sequence (commit df5988b)
tests/SolutionErp.Infrastructure.Tests/
├── Common/SqliteDbFixture.cs (Test fixture + TestApplicationDbContext)
├── Services/ContractCodeGeneratorTests.cs (~10 test)
├── Services/PurchaseEvaluationCodeGeneratorTests.cs (~7 test)
└── SolutionErp.Infrastructure.Tests.csproj (+ EF Sqlite 10)
Setup SQLite in-memory
TestApplicationDbContextsubclass overrideOnModelCreating— iterate model, replace bất kỳ column type chứa "max" → "TEXT" (SQLite không supportnvarchar(max))FixedDateTimestubIDateTime(control year boundary deterministic)- Shared
SqliteConnectionopen trong fixture, EnsureCreated() từ DbContext model
Coverage
- Format RG-001 cho 5 ContractType (HĐTP/HĐGK/NCC/HĐDV/MB) —
{Project}/{TypeCode}/SOL&{Supplier}/{Seq} - Framework HĐ (NguyenTacNCC + NguyenTacDV) đặc biệt —
{Year}/{TypeCode}/SOL&{Supplier}/{Seq}(year scope thay vì project) - Sequence increment per prefix (
/01→/02→/03) - Different prefixes (project / supplier khác) có sequence độc lập
- Year boundary cho framework — sang 2027 prefix mới reset seq
- PE format
PE/{YYYY}/{A|B}/{Seq:D3}— 3-digit padding test với 12 phiếu - PE Type A và B sequence độc lập trong cùng năm
- PersistsSequenceRow LastSeq + UpdatedAt verification
CI gate thêm step thứ 2 — fail-fast tương tự Domain.
17 test pass / 2s. Total session 5 test count = 71 (54 Domain + 17 Infra).
G. Stats sau session 5
| Trước S5 | Sau S5 | |
|---|---|---|
| BE LOC | ~11750 | ~13050 (+1300 PE WF Designer + Opinion + Budget integration) |
| DB tables | 51 | 52 (+1 PEDeptOpinions) |
| API endpoints | ~124 | ~128 (+2 PE WF + 2 Opinion) |
| Migrations | 14 | 15 |
| FE pages | ~26 | ~31 (+5 Budget + 1 PeWorkflowsPage) |
| Tests | 0 | 71 (54 Domain + 17 Infra) |
| CI gate | 1 (build) | 2 step test gate + build |
| Commits | ~75 | ~82 (+6 session 5 + chốt MD) |
H. Cảnh báo session 6
- CI test gate active — mỗi commit push trigger 2 step
dotnet test. Test fail → NO deploy. Workflow mới: code →dotnet test SolutionErp.slnxlocal → commit → push. - Chưa xóa binding cũ
.huypham.vn— fallback active. Sau verify stable:.\migrate-domains.ps1 -RemoveOld -SkipCert. - win-acme scheduled task "unhealthy" — fix trước cert expire 2026-06-18.
- Login email default vẫn
admin@solutionerp.local(chỉ rebrand demo user). - Atomic sequence Budget chưa chốt — Random.Shared race risk thấp nhưng không zero.
- Versioned workflow Budget chưa có — hardcoded
BudgetPolicy.Default3-step. - Export phiếu PDF/Excel PE pending vô thời hạn (user nói không quan trọng lắm).
I. Lessons learned session 5
-
Test pyramid bottom-heavy hợp solo dev — 71 test (54 Domain pure + 17 Infra SQLite) chạy < 3s. Bỏ E2E, tránh maintenance overhead.
-
SQLite in-memory cho code generator test đủ test format + sequence + year boundary, KHÔNG đủ test race condition thực (cần SQL Server thật cho
IsolationLevel.Serializablestrict). Workaround tốt cho CI runner không có LocalDB. -
TestApplicationDbContext subclass override — pattern sạch nhất để adapt SQL Server-specific model (
nvarchar(max)) cho SQLite mà không touch production DbContext. -
CI gate ROI cao cho solo dev — bug Domain logic recurring (workflow phase, role transition) sẽ block deploy ngay. ~3.5 ngày upfront cho 5 phase, có thể stop ở Phase 2 (1.5 ngày) nếu Phase 3-5 ROI chưa rõ.
-
PE Detail UI restructure theo spec form giấy — match 4 section đánh số PHIẾU TRÌNH KÝ giúp UX consistent giữa fill web và print PDF (tương lai).
-
Sign-off 4 phòng ban dùng table riêng thay vì 12 column header — UNIQUE(PEId, Kind) bảo vệ max 1 row mỗi phòng, dễ query "phòng nào chưa ký", dễ extend nếu thêm kind.
-
Workflow user mới end-of-task =
dotnet test SolutionErp.slnx→ commit → push. CI tự run test gate trước build/deploy.
J. Files touched session 5
src/Backend/SolutionErp.Domain/PurchaseEvaluations/
├── PurchaseEvaluation.cs (mod: + DepartmentOpinions navigation)
└── PurchaseEvaluationDepartmentOpinion.cs (NEW — entity + enum PeDepartmentKind)
src/Backend/SolutionErp.Application/Contracts/
├── ContractFeatures.cs (mod: + BudgetId in commands + GetQuery load Budget)
└── Dtos/ContractDtos.cs (mod: + BudgetId + Budget? trong ContractDetailDto)
src/Backend/SolutionErp.Application/PurchaseEvaluations/
├── PurchaseEvaluationFeatures.cs (mod: + BudgetId + DepartmentOpinions trong bundle)
├── PeWorkflowAdminFeatures.cs (NEW — ~250 LOC mirror Contract pattern)
├── PeDepartmentOpinionFeatures.cs (NEW — Upsert + Delete)
├── CreateContractFromEvaluationFeatures.cs (mod: carry forward BudgetId)
└── Dtos/PurchaseEvaluationDtos.cs (mod: + BudgetId + DepartmentOpinions list)
src/Backend/SolutionErp.Application/Budgets/
└── Dtos/BudgetDtos.cs (mod: + BudgetSummaryDto)
src/Backend/SolutionErp.Application/Common/Interfaces/
└── IApplicationDbContext.cs (mod: + DbSet PEDepartmentOpinions)
src/Backend/SolutionErp.Infrastructure/Persistence/
├── ApplicationDbContext.cs (mod: + DbSet)
├── Configurations/PurchaseEvaluationConfiguration.cs (mod: + DepartmentOpinions cascade + UNIQUE)
└── Migrations/20260429041117_AddPurchaseEvaluationDepartmentOpinions.{cs,Designer.cs} (NEW)
src/Backend/SolutionErp.Api/Controllers/
├── PurchaseEvaluationsController.cs (mod: + 2 opinion endpoints)
└── PeWorkflowsController.cs (NEW)
fe-admin/src/ (FE mod files mirror lên fe-user)
├── types/budget.ts (NEW)
├── types/contracts.ts (mod: + BudgetSummary)
├── types/purchaseEvaluation.ts (mod: + Budget + DepartmentOpinion + Kind enum)
├── pages/budgets/BudgetsListPage.tsx (NEW)
├── pages/budgets/BudgetCreatePage.tsx (NEW)
├── pages/contracts/ContractCreatePage.tsx (mod: + Budget select)
├── pages/pe/PurchaseEvaluationCreatePage.tsx (mod: + Budget select)
├── pages/system/PeWorkflowsPage.tsx (NEW)
├── components/budgets/BudgetDetailTabs.tsx (NEW)
├── components/budgets/BudgetWorkflowPanel.tsx (NEW)
├── components/pe/PeDetailTabs.tsx (mod: 4 section restructure + Budget col + Opinion section)
├── App.tsx (mod: + 5 route Budget + 2 PE WF)
└── components/Layout.tsx (mod: + Bg_* resolver)
tests/SolutionErp.Domain.Tests/ (NEW Phase 1)
├── SolutionErp.Domain.Tests.csproj
├── Contracts/WorkflowPolicyTests.cs (~17 test)
├── PurchaseEvaluations/PurchaseEvaluationPolicyTests.cs (~17 test)
└── Budgets/BudgetPolicyTests.cs (~13 test)
tests/SolutionErp.Infrastructure.Tests/ (NEW Phase 2)
├── SolutionErp.Infrastructure.Tests.csproj (+ EF Sqlite 10)
├── Common/SqliteDbFixture.cs (TestApplicationDbContext + FixedDateTime)
├── Services/ContractCodeGeneratorTests.cs (~10 test)
└── Services/PurchaseEvaluationCodeGeneratorTests.cs (~7 test)
SolutionErp.slnx (mod: + folder /tests/ với 2 project)
.gitea/workflows/deploy.yml (mod: + 2 step dotnet test gate)
docs/STATUS.md (mod: header + 6 row Recently Done + cumulative cột S5)
docs/HANDOFF.md (mod: TL;DR + Cảnh báo S6 + Priority 0 S6 + Phase status)
docs/changelog/migration-todos.md (mod: Phase 8 done + Phase 9 active S6)
docs/architecture.md (mod: + §11 Testing strategy + renumber §12 Liên quan)
docs/database/schema-diagram.md (mod: Migration 15 row + 52 tables + §13 PEDeptOpinions)
docs/CLAUDE.md (root + docs/) (mod: modules table + Tests row + Roadmap S5/S6 + Tests section)
.claude/skills/ef-core-migration/SKILL.md (mod: 15 migrations + 52 tables + Phase 8 update)
docs/changelog/sessions/2026-04-29-chot-session-5-budget-fe-pe-tests.md (NEW — file này)
~/.claude/projects/.../memory/project_solution_erp.md (mod: Phase 8 summary + Session 6 priority)