# STATUS — Snapshot hiện tại > **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`. **Last updated:** 2026-05-07 (Session 15 — **Diagnose "Lưu & Gửi Duyệt" silent disabled → tooltip reason + dialog warning rõ phase. User feedback bug suy đoán "trùng ID" — không phải bug FE. 1 commit `835cc7f`. Tiếp đó Plan drastic refactor flat workflow user chốt → attempt edit Domain/Configurations 12 files, scope realistic ~8-10h vượt session, REVERT working tree about session sau. 0 test mới. State: 96 test pass.**) ## 📍 Phase hiện tại: **Phase 9 active — UAT** — **57 DB tables, 20 migrations, ~134 API endpoints, 32 FE pages. 96 unit test pass** (54 Domain + 42 Infra). 41 gotcha. 30 demo user. 6 skill. **5 PE display status** (Bản nháp / Đã gửi duyệt / Trả lại / Đã duyệt / Từ chối). **9 PE phase enum** (+TraLai = 98 — orphan, KHÔNG wire). **N-stage workflow** Phòng × PositionLevel (NV/PP/TP) cấu hình động per WorkflowStep cha — **Mig 18+19 (PE) + Mig 20 (Contract)**. Budget defer (cần versioned WF migration trước). **PE 3-button approval** (Duyệt forward / Trả lại smart-reject / Từ chối khoá phiếu). ### 🌐 Production URLs - https://api.solutions.com.vn — API (Let's Encrypt, auto-renew via win-acme) - https://admin.solutions.com.vn — Admin FE (HTTP→HTTPS auto-redirect) - https://eoffice.solutions.com.vn — User FE (HTTP→HTTPS auto-redirect) - https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp — Gitea repo + Actions - Default admin: `admin@solutionerp.local` / `Admin@123456` ⚠️ **RE-ROTATE sau login đầu** ## 🔥 In Progress — Session 7+ (Phase 9 active) ### A. Hard blockers (chờ user / ops) - [ ] **UAT thật 1 tuần** với 2-3 user (Drafter / CCM / BOD) — hard requirement Phase 5 roadmap - [ ] **SMTP config** → Email outbox (BLOCKED chờ user cấp host/user/pass) - [ ] **Rotate creds** — admin + 30 demo + SA + vrapp + JWT secret + Gitea runner token - [ ] **Schedule SQL backup daily** — `scripts/backup-sql.ps1` đã sẵn, chưa schedule Task Scheduler - [ ] **Remove binding cũ `.huypham.vn`** sau verify stable: `ssh vietreport-vps ; .\migrate-domains.ps1 -RemoveOld -SkipCert` - [ ] **win-acme scheduled task "unhealthy"** — auto-renew fix trước 2026-06-18 ### B. Carry over feature gap - [ ] **Export phiếu PDF/Excel** PE — tái dùng `IDocumentConverter` + template `PE-TrinhDuyet.docx` (user pending — không quan trọng lắm) ### C. Optional polish (khi UAT phát sinh bug) - [ ] Budget MaNganSach atomic sequence (hiện Random.Shared → migration `AddBudgetCodeSequences`) - [ ] Budget versioned workflow (admin config UI thay hardcoded `BudgetPolicy.Default`) - [ ] Payment terms PE tách field (JSON blob → 6 column riêng) - [ ] Auto-map PE Details → Contract per-type Details khi gen HĐ - [ ] Matrix Quotes bulk paste từ Excel - [ ] fe-user Inbox thêm section "Phiếu Duyệt NCC chờ tôi" ### D. Tests Phase 3-5 (làm khi gặp bug recurring để justify ROI — rule §7) - [ ] **Phase 3 full** — Application handler tests cần UserManager DI helper (PE Opinion Upsert, Budget validate, CreateContractFromEvaluation BudgetId carry) ~15 test - [ ] **Phase 4** — API smoke tests qua WebApplicationFactory ~7 test - [ ] **Phase 5** — FE Vitest cho lib utility (queryMatches, fmtMoney) ~10 test ### E. 2-stage dept approval — Chunk E-bis ✅ DONE (Session 9) - [x] **FE Workflow Panel** PE — section "Tiến trình duyệt 2-cấp phòng ban" group by phase × dept, highlight amber khi chờ TPB confirm - [x] **FE UserManager toggle** `CanBypassReview` — column "Bypass" badge fuchsia + button toggle ShieldCheck (UsersPage) - [x] **HĐ 2-stage** mở rộng — `ContractWorkflowService` thêm UserManager DI + 2-stage logic mirror PE + `ContractDepartmentApprovalFeatures.cs` + endpoint `GET /contracts/{id}/department-approvals` + FE WorkflowHistoryPanel section mới - [x] **Budget 2-stage** mở rộng — `TransitionBudgetCommandHandler` thêm INotificationService + IDateTime + 2-stage logic + `BudgetDepartmentApprovalFeatures.cs` + endpoint + FE BudgetWorkflowPanel section - [x] **Tests 2-stage** (6 test) — `IdentityFixture` setup full Identity stack + 6 test PE workflow service: NV review block / TPB confirm allow / NV bypass / Admin skip / Reject set / Resume jump-back. Pattern reusable. ### F. Audit định kỳ (cron tự fire) - [x] **2026-05-01** (audit 2026-05-04 manual trigger sau trễ 4 ngày) — combined audit log `docs/changelog/skill-audit-2026-05.md`. Cron Claude SDK KHÔNG fit monthly cadence (auto-expire 7d) → user setup OS Task Scheduler nếu cần auto-remind. - [ ] **2026-06-01** — combined audit kế. Trigger thủ công khi đến ngày hoặc user nói "audit MD" / "kiểm tra docs" / "định kỳ kiểm tra". ## ✅ Recently Done (newest on top) | Ngày | Ai | Task | Commit | |---|---|---|---| | 2026-05-07 | Claude | **🎯 SESSION 15 — Tooltip diagnose "Lưu & Gửi Duyệt" + Plan drastic refactor flat workflow → DEFER** — User UAT live screenshot phiếu PE Bản nháp + báo "Lưu & Gửi Duyệt" KHÔNG hoạt động + suy đoán "trùng ID với phiếu khác". Chẩn đoán: button silent disabled khi `evaluation.workflow.nextPhases` không có forward phase (chỉ TuChoi/TraLai). FE chưa có visual feedback → user không biết. Improvement (commit `835cc7f`): compute `forwardPhase` once + add `submitDisabledReason` string giải thích reason (canEditPhase=false / readOnly / !forwardPhase với hint admin kiểm tra cấu hình quy trình) + button title attribute show reason hover hoặc forward phase label khi enabled + Dialog confirm show forward phase explicit "Sẽ chuyển sang Chờ Purchasing". Mirror fe-admin + fe-user. Build pass cả 2. **"Trùng ID" KHÔNG phải bug FE** — `PurchaseEvaluationWorkspacePage` URL state đúng (`+ Thêm mới` clear `id`, save set new), mỗi PE row unique GUID + MaPhieu. **Tiếp theo plan drastic refactor**: User chốt "bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking" + workflow flat list (Phòng × Cấp × Users[]) thay InnerStep model. Surface 6 chunk plan + start Chunk A: edit Domain entities (Phase enum +ChoDuyet=10, WorkflowStep +DeptId/PositionLevel, drop InnerStep class+nav, PE/Contract +CurrentWorkflowStepIndex/RejectedAtStepIndex, *DeptApproval drop InnerStepId) + EF Configurations (drop InnerStep config + nav, restore simple unique non-filtered) + DbContext drop DbSets — 12 files trong working tree. Realize scope realistic ~8-10h (PolicyRegistry rewrite + 2 Service rewrite + App CQRS + 12 tests rewrite + Designer FE + Migration 21 + Docs) vượt session boundary + risk session context deep ~30 commits. **REVERT working tree** về `835cc7f` clean. Add memory `feedback_drastic_refactor_scope` decision rule: drastic refactor cần dedicated session, ước tính conservative (2x buffer), tránh mid-session big refactor. **Stats unchanged**: 96 test pass, 20 mig, 57 bảng. | `835cc7f` | | 2026-05-07 | Claude | **🎯 SESSION 14 — PE 3-button workflow Duyệt/Trả lại/Từ chối + Task 2 sample seed in-progress** — User chỉ thị thay 2-button approval bằng 3 hành động rõ ràng cho approver: **Duyệt** (forward), **Trả lại** (về DangSoanThao + Drafter sửa, smart reject Mig 16 + clear N-stage rows + Drafter resume jump-back), **Từ chối** (Phase=TuChoi, phiếu khoá vĩnh viễn 17 handler Mig 16 lock edit, Drafter phải tạo phiếu mới). 1 commit (`0d77698`): Domain `PurchaseEvaluationPolicy.cs` NccOnly + NccWithPlan thêm `(X → TuChoi)` transition cho mọi phase trung gian (ChoPurchasing/ChoCCM/ChoDuAn/ChoCEODuyetPA/ChoCEODuyetNCC) với roles của phase. FromDefinition expand: mỗi step (trừ DangSoanThao) thêm (step.Phase → TuChoi) với roles step. Service `PurchaseEvaluationWorkflowService.TransitionAsync` — Reject branch tách 2 case: target=TuChoi giữ nguyên (KHÔNG override + KHÔNG set RejectedFromPhase + KHÔNG clear N-stage); target khác (DangSoanThao) → smart reject (force DangSoanThao + RejectedFromPhase + clear N-stage). FE PeWorkflowPanel (admin + user mirror): render 3 button rõ ràng "✓ Duyệt → X" brand / "← Trả lại (về Drafter sửa)" red / "✗ Hủy / Từ chối" red. Decision logic: target=TuChoi || isSendBack → Reject (2), else Approve (1). Dialog confirm: title rõ + Cancel case warning red "phiếu sẽ bị khoá hoàn toàn" + SendBack case hint amber "Phiếu về DangSoanThao, Drafter sửa rồi trình lại — workflow tự jump tới phase này". Tests: rename `Reject_Sets_RejectedFromPhase_And_Forces_DangSoanThao` → `Reject_To_DangSoanThao_Sets_RejectedFromPhase_TraLai` (target từ TuChoi → DangSoanThao). NEW `Reject_To_TuChoi_Locks_Permanently_No_RejectedFromPhase`. Update `NStage_Reject_Clears_InnerStep_Rows_At_Phase` target → DangSoanThao. **95 → 96 test pass** (+1 Từ chối). **Task 2 sample seed in-progress**: dotnet ef database update applied Mig 9-20 lên LocalDB SolutionErp_Dev (trước đó chỉ Mig 1-8 vì DesignTimeDbContextFactory hardcoded SolutionErp_Design ≠ runtime SolutionErp_Dev — gotcha tooling distinction). API start để DbInitializer auto-seed 30 demo user nhưng exit 255 sớm khi log buffer full → seed dở dang. Defer Task 2 cho session sau (cần guidance: seed manual SQL hoặc manual API run + sample N-stage workflow def + Update PositionLevel cho 30 users existing). | `0d77698` | | 2026-05-07 | Claude | **🎯 SESSION 13 — Mirror N-stage workflow sang Contract (Mig 20, 5 commit per-chunk + skip Chunk E API)** — User chỉ thị mirror N-stage từ PE sang Contract. Budget defer (cần versioned WF migration trước, hardcoded BudgetPolicy hiện tại chưa có WorkflowDefinition). 5 chunk per-commit (build + ef + test pass mỗi chunk): **Chunk A (`951ffa3`)** Domain entity `WorkflowStepInnerStep` (Domain/Contracts/) + nav WorkflowStep.InnerSteps + ALTER ContractDepartmentApproval.InnerStepId Guid? + EF config FK Cascade Step / Restrict Dept+InnerStep + **Migration 20** `AddContractWorkflowInnerStepsAndAlterDeptApprovalUnique` GỘP 1 (CREATE TABLE WorkflowStepInnerSteps + ALTER InnerStepId + DropIndex old + Recreate filtered legacy `WHERE InnerStepId IS NULL` + new filtered N-stage `WHERE InnerStepId IS NOT NULL` + 3 IX + 3 FK). **Chunk B (`04cf2a0`)** Application CQRS DTO — `WorkflowStepInnerStepDto` + extend `WorkflowStepDto` + GetWorkflowAdminOverview Include InnerSteps + DeptNames map + `CreateWorkflowStepInnerStepInput` + `CreateWorkflowStepInput` extend (default null backward compat) + Validator child rules + Handler atomic batch insert. **Chunk C (`e247b67`)** ContractWorkflowService refactor mirror PE — load definition InnerSteps eager, reject branch clear N-stage rows tại fromPhase, dept approval block split hasInnerSteps→N-stage logic / else→legacy 2-stage. N-stage flow giống PE: yêu cầu actor có DeptId+PositionLevel, match firstPending Order asc + (exact level OR canBypass + level≥), exact upsert 1 row InnerStepId, bypass batch upsert NV+PP+TP cùng dept ≤ actor (audit IsBypassed cho cấp dưới), recheck stillPending → BLOCK + log "duyệt cấp X (còn Y pending)". **Chunk D (`7c0772a`)** Tests 6 N-stage Contract mirror PE pattern + helper SeedWorkflowDefinitionAsync 2 step adjacent (DangGopY + DangDamPhan) + SeedContractAsync với Project + Supplier seed + FakeChangelogService + FakeContractCodeGenerator stubs. Bug fix: legacy fallback test ban đầu fail (Standard policy DangGopY → DangDamPhan chỉ cho [Drafter, DeptManager], không Procurement) → switched phase pair sang DangKiemTraCCM → DangTrinhKy + role CostControl khớp. Total 89 → **95 test pass**. **Chunk E SKIP** — WorkflowsController auto-bind `[FromBody] CreateWorkflowDefinitionCommand` record qua JSON, no code change cần. **Chunk F (current)** FE-Admin types/users.ts đã có PositionLevel const từ Session 12 reuse. WorkflowsPage Designer extend mirror PeWorkflowsPage Chunk F: InnerStepDto + EditInnerStep types + copyFromDefinition include + departmentsList query + sub-section "Cấp duyệt nhỏ trong phòng" drag-list { Phòng × Cấp + required } + button "+ Thêm cấp duyệt" emerald + payload include Order asc. Empty state hint fallback 2-cấp legacy. KHÔNG đụng fe-user (admin-only). Docs/Skill update. **Backward compat 100%**: workflow Contract no InnerSteps → fallback legacy 2-stage Mig 16. Data legacy InnerStepId=null vẫn enforce unique cũ qua filtered index. Defer Budget mirror cho session sau (cần migration `AddBudgetVersionedWorkflow` trước). | `951ffa3` (A) · `04cf2a0` (B) · `e247b67` (C) · `7c0772a` (D) · (current F) | | 2026-05-07 | Claude | **🎯 SESSION 12 — N-stage workflow approval Phòng × PositionLevel cấu hình động (PE-only first, 6 commit per-chunk + Mig 18+19)** — User yêu cầu mở rộng từ 2-stage Mig 16 (NV.Review/TPB.Confirm) sang N-stage cấu hình động: mỗi WorkflowStep cha (= 1 phase) có thể cấu hình chuỗi InnerSteps con theo Department × PositionLevel với Order sequential. Spec defaults chốt 6 câu (PositionLevel int 1=NV/2=PP/3=TP, sequential pure, bypass cùng dept TP skip NV+PP, smart reject reset N-stage rows về DangSoanThao, PE-only first, designer 1 sub-section InnerSteps). 6 chunk per-commit (build + ef + test pass mỗi chunk per `feedback_per_chunk_commit.md`): **Chunk A (`13ab533`)** Domain enum `PositionLevel` (NV/PP/TP) + entity `PurchaseEvaluationWorkflowStepInnerStep` + ALTER User.PositionLevel int? + ALTER PEDeptApprovals.InnerStepId Guid? + EF config + **Migration 18** `AddPeWorkflowInnerStepsAndPositionLevel` (1 CREATE TABLE + 2 ALTER + 3 index + FK Cascade Step / Restrict Dept/InnerStep). 3-file rule. **Chunk B (`0e56bd0`)** Application CQRS DTO — `PeWorkflowStepInnerStepDto` + extend `PeWorkflowStepDto` + `CreatePeWorkflowStepInnerStepInput` (default null backward compat existing PeWorkflowAdminTests) + Validator child rules + Handler atomic batch insert + UserDto +PositionLevel field + `SetUserPositionLevelCommand` mirror SetBypassReview. **Chunk C (`0c62e24`)** Service N-stage logic — **Migration 19** `AlterPeDeptApprovalsUniqueFilteredForInnerSteps` (filtered unique: legacy `WHERE InnerStepId IS NULL` + N-stage `WHERE InnerStepId IS NOT NULL`) cho phép multi-row cùng dept khác inner step. PurchaseEvaluationWorkflowService refactor: load definition InnerSteps eager + reject branch clear N-stage rows + dept block split hasInnerSteps→N-stage logic / else→legacy 2-stage. N-stage flow: yêu cầu actor có DeptId+PositionLevel, match firstPending (Order asc IsRequired) same dept + (exact level OR canBypass + level≥), exact match upsert 1 row InnerStepId, bypass batch upsert NV+PP+TP cùng dept ≤ actor level (audit IsBypassed cho cấp dưới skip), recheck stillPending → BLOCK + log "duyệt cấp X (còn Y pending)" / all done → fall through phase transition. Backward compat: workflow no InnerSteps fallback legacy + InnerStepId=null filter unique cũ vẫn enforce. **Chunk D (`3d76c6b`)** Tests N-stage 6 test mới (NV first blocks / 3-level sequential pass / TP bypass skips / wrong dept throws 403 / reject clears rows / legacy fallback no inner) + IdentityFixture extend `+positionLevel` arg + helper SeedWorkflowDefinitionAsync 2 step adjacent (ChoPurchasing+ChoCCM) cho FromDefinition build transition policy guard pass. Total 83→**89 test pass**. **Chunk E (`83ffabd`)** API `PATCH /users/{id}/position-level` mirror SetBypassReview pattern + body `{positionLevel:int?}` Authorize Users.Update. **Chunk F (current)** FE-Admin types/users.ts +positionLevel field + PositionLevel const + Label/Short maps. PeWorkflowsPage Designer extend: InnerStepDto type + EditInnerStep type + copyFromDefinition include + departmentsList query + sub-section "Cấp duyệt nhỏ trong phòng" per step card với drag-drop list { Phòng × Cấp + required checkbox } + button "+ Thêm cấp duyệt" (xanh emerald) + payload include innerSteps Order asc. UsersPage column "Cấp" badge NV/PP/TP emerald + action button cycle null→1→2→3→null call positionLevelMut PATCH. KHÔNG đụng fe-user (admin-only feature). Docs/Skill update. PE-only first. Backward compat 100%: workflow no InnerSteps + data legacy 2-stage rows không phá. | `13ab533` (A) · `0e56bd0` (B) · `0c62e24` (C) · `3d76c6b` (D) · `83ffabd` (E) · (current F) | | 2026-05-08 00:30 | Claude | **🎯 SESSION PHASE 2 WRAP-UP — B12-B14 PE detail polish iterate (3 commit FE-only sau wrap-up `6e7a6db`)** — User UAT iteration tiếp, áp rule strict verify khi rename/remove (lesson hotfix CI). 3 batch nhỏ: **B12 (`378c993`)** "Lưu" no-close (chỉ toast + invalidate, KHÔNG đóng workspace) + nút "Xóa phiếu" red bottom CHỈ Bản nháp (soft-delete `IsDeleted=true` qua AuditableEntity, không xóa hoàn toàn DB) + bỏ header bar workspace mode "Sửa header"/"Xóa"/"Đóng" (chuyển hết xuống bottom action bar) + Section 4 column header dùng `s.supplierName` thay `displayName` (NCC master) + Section 3 row chặn xóa NCC khi đã có quotes (`hasQuotes` computed) + tooltip "xóa báo giá trước". **B13 (`e320027`)** InfoTab `useEffect` watch `[autoEdit, canEdit, ev.id, ...]` → re-trigger edit mode khi pencil click phiếu khác (fix useState mount-time only) + sync values từ ev mới (tránh stale state) + Pencil "sáng lên" active state khi `editingRowId === p.id` (bg-brand-100 + text-brand-700 + ring-brand-300 + shadow-sm + tooltip cập nhật) + wire `editingRowId={autoEditHeader ? selectedId : null}` từ Workspace → PeListPanel. **B14 (`d2306b8`)** QuoteDialog bỏ checkbox "Chọn NCC này cho hạng mục" (consolidate winner ở Section 2.a NccSelectorRow, isSelected vẫn gửi BE giữ trạng thái cũ) + winner column Section 4 matrix highlight emerald (header `bg-emerald-50` + `✓ ` prefix + cells `bg-emerald-50 font-semibold` cho ENTIRE column, không chỉ cell có quote) + loading overlay full-screen QuoteDialog (`bg-white/70 backdrop-blur-sm` + spinner ring brand-600 + text "Đang lưu báo giá…"/"Đang xóa…") + NccSelectorRow inline spinner "Đang chọn NCC + sync cột giá Section 4…" + disable Hủy/Xóa/Lưu buttons khi `isSaving`. Verify: `npm run build` × 2 app pass mỗi commit · `dotnet test` 83 pass (KHÔNG regression). | `378c993` (B12) · `e320027` (B13) · `d2306b8` (B14) | | 2026-05-07 | Claude | **🎯 SESSION WRAP-UP S10-11+++++++ — PE Workspace UX overhaul đầy đủ (23 commit / ~3500 LOC FE + Mig 17 BE)** — User UAT live mode iterate liên tục, áp rule `feedback_uat_skip_verify` (skip dotnet test sau mỗi chunk, push ngay). 7 batch chính: **B1 (S10) PE Thao tác 2-panel workspace** — leaf `Pe_*_Create` từ page Create header riêng → workspace 2-panel `[320px_1fr]` mirror HĐ Thầu phụ; PeListPanel pure picker + sticky "+ Thêm mới"; PeDetailTabs `mode='workspace'` ẩn Workflow/Approvals/History + Section 5 disabled "nhập khi duyệt" (4 commit). **B2 (S11) Migration 17** `AddManualBudgetFieldsToPeAndContract` — 4 ALTER (PE + HĐ × `BudgetManualName` nvarchar(200) + `BudgetManualAmount` decimal(18,2)) cho fallback "user nhập tay khi không link Budget entity approved". Domain + EF config + App CQRS Create/Update + DTO + Validator + carry-forward `CreateContractFromEvaluation`. FE toggle "Nhập tay" trong PeHeaderForm + ContractCreatePage NewForm/EditForm × 2 app (5 commit). **B3 (S11+) BudgetFieldRow inline editor** — Section 2 "b. Ngân sách" thay FormRow tĩnh → editable component (toggle + Select OR 2 input + Save dirty + Hủy). canEdit cho cả 3 view (Workspace/Danh sách/Duyệt mode), readOnly chỉ display (3 commit). **B4 (S11++) InfoTab inline edit + PeListPanel pencil hover** — Section 1 "✎ Sửa" button flip display↔inputs (Tên/Địa điểm/Mô tả/Payment editable, Dự án locked). PeListPanel thêm pencil icon group-hover absolute right + URL `?editHeader=1` chain → `autoEditHeader` prop trigger mount-time edit (3 commit). **B5 (S11+++) Workspace "new" sectioned create view** — `PeWorkspaceCreateView.tsx` ~230 LOC layout 5 sections giống PeDetailTabs visual. S1 + S2.b editable, S3-5 LockedHint "Lưu phiếu trước". POST trigger create. Replace `PeHeaderForm` trong workspace mode='new' (1 commit). **B6 (S11++++) Danh sách disable toàn bộ interactions** — PurchaseEvaluationsListPage `readOnly={true}` hardcoded cho PeDetailTabs + `readOnly={!pendingMe}` cho PeWorkflowPanel (List view → ẩn Chuyển tiếp + show hint "Vào menu Duyệt"; Pending vẫn approve được) (2 commit). **B7 (S11+++++) Lock Loại quy trình + payment preset** — workspace `` theo URL `?type=N`. `