Files
solution-erp/docs/HANDOFF.md
pqhuy1987 eea86fdfe7
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m31s
[CLAUDE] Docs: Chunk E — chốt Session 21 turn 4 F1+F2+F3 PE Workflow advanced options (Mig 28)
Update docs theo rule §6.5 KEEP narrative:

- `docs/database/schema-diagram.md §14` title "Mig 22-28, S17-21" + thêm 6
  column Allow* inline trong Core ApprovalWorkflows block (inline comment
  F1/F2/F3 mapping). Bonus: 3 bảng (Steps + Levels) unchanged.

- `docs/STATUS.md` Last updated S21 t4 + count 27→28 mig (Mig 28 advanced
  options 6 column). UAT defer test count unchanged 84 (test-after-uat candidate
  bundle Plan C carry).

- `docs/HANDOFF.md` Insert TL;DR S21 t4 đầy đủ (trước S21 t3):
  - Q&A clarify 2 lượt chốt scope (F1 cả 2 mode admin, F1 Assignee runtime,
    F2 chỉ Cấp cuối, F3 Section 2 only, F2+F3 admin tick, F3 mọi approver
    active, test-after UAT)
  - 5 chunk narrative đầy đủ A schema → B BE → C FE Admin → D FE eOffice → E Docs
  - Pattern reusable: backward-compat option flags, boundary helper extension
  - State table cumulative + pending Plan C test-after catch-up

- NEW session log `docs/changelog/sessions/2026-05-13-1200-s21-turn4-pe-workflow-advanced-options.md`:
  - Trigger + Q&A 2 lượt
  - 5 chunk narrative chi tiết với code snippets
  - Pattern reusable 5 lessons learned
  - References file paths + spec context

Stats cumulative S21 t4:
- 28 mig (+1) · 59 tables · ~143 endpoints (+1) · 34 FE pages · 84 test pass
  (UAT defer test-after §7) · 45 gotcha unchanged · 17 memory · 6 skills
- 5 commits S21 t4 cumulative ready push remote

Pending: bro confirm push `0a3b747..HEAD` 8 commits ahead (S21 t3 + S21 t4).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 19:11:14 +07:00

129 KiB
Raw Blame History

HANDOFF — Brief 5 phút cho session tiếp theo

Last updated: 2026-05-13 1200 (Session 21 turn 4 — 🎯 F1+F2+F3 PE Workflow advanced options (Mig 28) — 5 chunk per-commit 0294693 (A schema) → c56024b (B BE) → a508564 (C FE Admin) → d27caaf (D FE eOffice) → this (E Docs). F1 4 mode Trả lại admin tick stick (1 Cấp / 1 Bước / Người chỉ định / Người soạn thảo) — 3 mode đầu giữ Phase=ChoDuyet lùi pointer (peer review chain), mode Drafter giữ Phase=TraLai clear pointer (S17 backward compat). F2 Drafter skip thẳng Cấp cuối — workflow tick + Workspace checkbox dynamic. F3 Approver edit Section 2 (Hạng mục/NCC/Báo giá) khi workflow tick + actor match CurrentLevel.ApproverUserId + audit ghi PurchaseEvaluationChangelog. Mig 28 thêm 6 bit column lên ApprovalWorkflows (DEFAULT 1 cho AllowReturnToDrafter backward compat, 5 còn lại 0). BE Service extend signature 3 optional param (returnMode/returnTargetUserId/skipToFinal). Helper EnsureEditableForDetailsAsync mới gating Detail/Quote/Supplier CRUD theo Drafter scope OR F3 Approver scope + audit changelog Update/Delete (trước đây silent). FE Admin Designer "Cấu hình nâng cao" section 6 checkbox 3 group. FE eOffice 3 changes mirror 2 app. UAT mode skip dotnet test mỗi chunk, npm build × 2 app pass mỗi chunk. CHƯA push remote — chờ bro confirm.) S21 turn 3: 2026-05-12 2100 (Session 21 turn 3 — 🔴 BUG FIX CRITICAL "Trả về nhưng hệ thống vẫn duyệt" PE workflow (gotcha #45 mới). 3 chunk per-commit: de00887 (BE Chunk A guard + 3 test) + 4b29d00 (FE Chunk B fix 2 app mirror) + this Chunk C Docs. Root: PeWorkflowPanel.tsx isReject payload (L64-66) thiếu nhánh TraLai → button "← Trả lại" gửi decision: 1 (Approve) thay vì 2 (Reject) khi target=TraLai(98) → BE skip Reject branch → enter APPROVE STEP → ApproveV2Async UPSERT opinion "đã duyệt" + advance Cấp tiếp theo. Inconsistency phụ: dialog isSendBack (L247-248) cùng pattern thiếu TraLai → dialog title sai '✓ Duyệt → Trả lại' + KHÔNG amber warning. Severity CRITICAL — data integrity issue khó rollback (BE đã SaveChangesAsync). Test-before §7 BẮT BUỘC: viết test reproduce → confirm FAIL (BE đi sâu vào ApproveV2Async throw "Phiếu chưa pin workflow") → thêm BE guard early throw ConflictException khi target ∈ {TraLai, TuChoi} && decision != Reject → confirm PASS. 3 regression test (Throws TraLai+Approve, Throws TuChoi+Approve consistency, happy path Reject+TraLai). Tổng dotnet test SolutionErp.slnx 84 PASS (58 Domain + 26 Infra = +3 from 81 baseline). npm run build × 2 app pass. Stats: 27 mig (no change) · 59 tables · ~142 endpoints · 34 FE pages · 84 test (+3) · 45 gotcha (+1 #45) · 17 memory · 6 skills · 4 sub-agents seeds-only. Em main solo S21 t3 — bug fix reasoning chain cross BE/FE Implementer REFUSE per multi-agent rule (decision tree: tightly coupled BE+FE+test). CHƯA push remote — chờ bro confirm sau Chunk C wrap.) S21 turn 2: 2026-05-12 1800 (Session 21 turn 2 — 🎯 RAG Hybrid setup planning + Cách A validation deep dive. 2 commit (1f8e9af plan save 1223 LOC + this chốt). KHÔNG implement, plan only — defer chờ bro confirm 5 dự án future. Decision chốt: Cách A defensive (giữ blanket 120K em main + RAG retrieve) over Cách B aggressive (cắt 60-70% blanket). Industry-validated cross 4 Anthropic blog + 5 community tools (Cursor/Continue/Cline/Aider). Stack: Voyage-3-large + Qdrant + FastMCP + Streamlit dashboard. Multi-agent cost reality: 4 agents → ~520K cumulative blanket → heavy session ~560K (Cách A) vs ~700K (lazy). 3-layer pattern Phase 1-3 rollout (embeddings + BM25 + reranking, ~70% → ~92% recall). Stats: +1 memory entry (feedback_rag_hybrid_pattern) +1 plan file (rag-setup-plan.md 1500 LOC). Sub-agents vẫn 4 seeds-only, em main solo session.) S21 turn 1: 2026-05-12 0030 (Session 21 turn 1 — 🎯 Add con thứ 4 cicd-monitor (Path A — post-deploy verifier). 1 commit f1c61c9 pushed 36e21c8..f1c61c9 main -> main. CI skipped per path filter (3 file .md). Cost reality update: ~750K spawn (3 → 4 agents) · ~1.35M heavy / ~700K optimized. Stats: 4 sub-agents seeds-only · 16 memory · 27 mig · 59 tables · ~142 endpoints · 81 test · 44 gotcha · 6 skills unchanged. KHÔNG flush 3 agent MEMORY.md (chưa spawn work — em main solo). Trial Week 1 kick-off S21 turn 2+ Plan B Contract V2 wire mirror PE pattern.)

TL;DR Session 21 turn 4 — F1+F2+F3 PE Workflow advanced options (Mig 28)

User request 3 tính năng mới trong PE V2 Workflow:

  • F1 4 mode Trả lại admin stick: 1 Cấp / 1 Bước / Người chỉ định / Người soạn thảo
  • F2 Drafter gửi thẳng Cấp cuối (skip mọi Bước/Cấp trung gian)
  • F3 Approver chỉnh sửa Section 2 (Hạng mục + NCC + Báo giá) khi đang duyệt

Q&A clarify chốt scope (2 lượt AskUserQuestion)

  • F1 "1 bậc" = cả 2 mode (admin chọn 1 Cấp HOẶC 1 Bước HOẶC cả 2 stick)
  • F1 "Người chỉ định" = Approver pick runtime từ list NV đã ký (PE.LevelOpinions)
  • F1 behavior = 3 mode đầu giữ Phase=ChoDuyet lùi pointer (peer review chain). Mode Drafter giữ Phase=TraLai S17 fallback.
  • F2 skip = chỉ skip tới Level cuối (CEO) — Dropdown 2 option "Gửi tuần tự" vs "Gửi thẳng Cấp cuối"
  • F2+F3 admin enable = cả 2 cần admin tick per workflow (audit nghiêm)
  • F3 approver perm = mọi approver Cấp đang active (currentLevel match)
  • F3 scope = Section 2 only (Hạng mục + NCC + Báo giá), KHÔNG đụng PE Header, KHÔNG reset workflow
  • Test = test-after UAT default Phase 9 (skip dotnet test mỗi chunk, npm build × 2 app pass)

Chunk A — Mig 28 + Domain (0294693)

ApprovalWorkflow.cs thêm 6 bool field:

  • AllowReturnOneLevel / AllowReturnOneStep / AllowReturnToAssignee (default false)
  • AllowReturnToDrafter (default TRUE — backward compat S17)
  • AllowDrafterSkipToFinal / AllowApproverEditDetails (default false)

EF config ApprovalWorkflowConfiguration thêm 6 HasDefaultValue match Mig 28 DEFAULT.

Mig 28 AddAdvancedOptionsToApprovalWorkflows:

  • 6 AddColumn bit NOT NULL DEFAULT 0/1
  • 3-file rule complete (mig.cs + Designer.cs + Snapshot.cs)
  • Apply LocalDB Dev + Design

Chunk B — BE Service + handlers + DTOs (c56024b)

Service interface + impl TransitionAsync thêm 3 optional param (backward compat):

  • WorkflowReturnMode? returnMode (enum {OneLevel=1, OneStep=2, Assignee=3, Drafter=4})
  • Guid? returnTargetUserId (required khi mode=Assignee)
  • bool skipToFinal

REJECT branch extend với helper ApplyReturnModeAsync switch 4 mode:

  • OneLevel: lùi 1 Cấp cùng Step. Bước 1 Cấp 1 → fallback Drafter.
  • OneStep: lùi sang Bước trước Cấp cuối. Bước 1 → fallback Drafter.
  • Assignee: tìm Step+Level match ApproverUserId == returnTargetUserId.
  • Drafter: Phase=TraLai clear pointer (S17 behavior).
  • 3 mode đầu giữ ChoDuyet + reset SLA 7d.
  • Admin bypass workflow.Allow* flag check.
  • Non-admin → throw ConflictException nếu flag disabled.

DRAFTER trình branch extend với F2 skipToFinal:

  • Workflow.AllowDrafterSkipToFinal required (non-admin)
  • Set CurrentWorkflowStepIndex = Steps.Count-1 + CurrentApprovalLevelOrder = max Level
  • Audit comment append "[Drafter gửi thẳng Cấp cuối]"

Helper edit guard EnsureEditableForDetailsAsync mới (PurchaseEvaluationDraftGuard class):

  • Drafter scope: DangSoanThao OR TraLai
  • F3 Approver scope: ChoDuyet + workflow.AllowApproverEditDetails + actor match CurrentLevel.ApproverUserId
  • Admin bypass workflow flag check

8 handler switch sang helper mới + inject ICurrentUser khi cần:

  • Detail Add/Update/Delete + Quote Upsert/Delete (5 handler — replace EnsureDraftAsync)
  • Supplier Add/Update/Remove (3 handler — bonus security fix, trước đây hoàn toàn KHÔNG có phase guard!)
  • Update/Delete handler trước đây silent → thêm changelog PhaseAtChange + UserId + Summary (append [Approver edit khi đang duyệt] khi phase=ChoDuyet)

Command DTO + DTOs:

  • TransitionPurchaseEvaluationCommand +3 optional field
  • ApprovalWorkflowOptionsDto NEW sub-record (6 Allow* flag)
  • PurchaseEvaluationDetailBundleDto +WorkflowOptions field
  • AwDefinitionDto +6 Allow* (admin Designer GET)
  • CreateAwDefinitionCommand +6 Allow* param (admin Designer POST)

Chunk C — FE Admin Designer (a508564)

ApprovalWorkflowsV2Page.tsx Designer modal thêm section "Cấu hình nâng cao" 3 sub-group:

  1. Mode Trả lại 4 checkbox:

    • Trả về 1 Cấp trước (peer review chain trong cùng Bước)
    • Trả về 1 Bước trước (Cấp cuối Bước trước nhận lại)
    • Trả về Người chỉ định (pick runtime từ NV đã ký)
    • Trả về Người soạn thảo (default checked = backward compat S17)
  2. Drafter skip: 1 checkbox "Cho phép Drafter gửi thẳng Cấp cuối"

  3. Approver edit: 1 checkbox "Cho phép Approver chỉnh sửa Section 2"

Styling: container amber-50/30 border distinct với Steps section. Helper text [10px] dưới label. Headers uppercase tracking.

DTO types + state defaults từ cloneFrom (giữ config version trước) hoặc S17 fallback (chỉ AllowReturnToDrafter=true). POST body propagate 6 flag → BE Create handler set entity.

fe-user KHÔNG mirror (Designer admin-only).

Chunk D — FE eOffice (d27caaf) mirror 2 app

Types purchaseEvaluation.ts:

  • ApprovalWorkflowOptions type
  • WorkflowReturnMode const-object
  • PeDetailBundle +workflowOptions field

PeWorkflowPanel.tsx F1 Trả lại radio picker:

  • State returnMode (default Drafter) + returnTargetUserId
  • Dialog Trả lại render 1-4 radio mode enabled theo wfOptions.Allow*
  • Assignee mode → submodal Select pick từ levelOpinions (NV đã ký), dedupe by userId
  • Banner amber rounded dưới mô tả hành vi mode chọn
  • Mutation payload +returnMode +returnTargetUserId khi isTraLaiAction

PeDetailTabs.tsx F2 Drafter skip:

  • State skipToFinal + allowSkipToFinal từ workflowOptions
  • submitForApproval mutationFn accept opts.skipToFinal
  • Workspace action bar: checkbox violet "Gửi thẳng Cấp cuối (skip trung gian)" conditional
  • Confirm dialog message + button label dynamic theo skipToFinal

PeDetailTabs.tsx F3 Approver edit Section 2:

  • useAuth import + compute approverEditMode (phase=ChoDuyet + workflowOptions.allowApproverEditDetails + actor match)
  • itemsReadOnly = readOnly && !approverEditMode → ItemsTab nhận
  • Banner violet "ⓘ Bạn được phép chỉnh sửa..." khi approverEditMode + readOnly (Duyệt menu)
  • InfoTab / NccSelectorRow / BudgetFieldRow GIỮ strict isEditablePhase (Header + Section 3, KHÔNG trong F3 scope)

Chunk E — Docs (this commit)

  • docs/database/schema-diagram.md §14 cập nhật title "Mig 22-28, S17-21" + thêm 6 column Allow* trong Core block với inline comment F1/F2/F3
  • docs/STATUS.md Last updated S21 t4 + count 27→28 mig + UAT defer test count unchanged 84
  • docs/HANDOFF.md TL;DR S21 t4 đầy đủ (file này)
  • docs/changelog/sessions/2026-05-13-1200-s21-turn4-pe-workflow-advanced-options.md session log

State chốt S21 turn 4

Metric Trước (S21 t3) Sau (S21 t4) Δ
DB tables 59 59 0
Migrations 27 28 +1 (Mig 28 6 column Allow*)
Endpoints ~142 ~143 +1 (extend transitions body)
FE pages 34 34 0 (Designer extend section)
Unit tests 84 84 0 (UAT defer test-after §7)
Gotchas 45 45 0
Memory entries 17 17 0
Skills 6 6 0
Sub-agents 4 seeds-only 4 seeds-only 0
Commits S21 t4 5 (0294693c56024ba508564d27caaf → this)

Pending — Test-after (Plan C carry)

Per feedback_uat_skip_verify Phase 9 default: viết test sau UAT 2-3 lần ổn. Test scope candidate (test-after-uat commit riêng):

  • Service ApplyReturnModeAsync 4 mode happy path (OneLevel/OneStep/Assignee/Drafter)
  • Service skipToFinal happy path + AllowDrafterSkipToFinal=false → ConflictException
  • EnsureEditableForDetailsAsync 3 scenario: Drafter scope / Approver match / Approver mismatch → Forbidden

Bundle với Plan C existing (test #44 silent 403 + test V2 ApproveV2Async + Mig 25/27 PATCH).


TL;DR Session 21 turn 3 — Bug fix CRITICAL "Trả về nhưng hệ thống vẫn duyệt" (gotcha #45)

User UAT 2026-05-12 21:00 screenshot button labeled ← Trả lại trong PE Workflow Panel (menu "Duyệt"), mô tả hành vi: nhấn vào nhưng phiếu KHÔNG về phase TraLai — ngược lại tiến qua Cấp tiếp theo. User mô tả: "Trả về nhưng hệ thống vẫn duyệt".

Diagnose (em main solo, no agent spawn)

3 chỗ inconsistency cùng pattern trong PeWorkflowPanel.tsx (× 2 app fe-admin + fe-user):

# Location Logic Bug?
1 L205-207 button isSendBack include TraLai → label ← Trả lại ĐÚNG no bug
2 L64-66 payload isReject thiếu nhánh TraLai → gửi decision: 1 (Approve) 🔴 BUG ROOT
3 L247-248 dialog isSendBack thiếu nhánh TraLai → dialog title fallback '✓ Duyệt → Trả lại' + no amber warning 🔴 BUG phụ

BE PurchaseEvaluationWorkflowService.TransitionAsync:

  • L51 if (decision == Reject) branch → đúng cho decision=Reject.
  • L97 APPROVE STEP branch khi decision=Approve + fromPhase=ChoDuyet → ApproveV2Async UPSERT opinion + advance Cấp.
  • → FE gửi decision=1 (do bug isReject) → BE đi vào nhánh APPROVE thay vì REJECT → phiếu approve mặc dù user định trả lại.

Chunk A — BE defense-in-depth + 3 regression test (de00887)

Test-before §7 BẮT BUỘC: Viết test reproduce bug TRƯỚC fix.

// Sau line 48 (set isAdmin/isSystem), trước REJECT branch (L51)
if ((targetPhase == PurchaseEvaluationPhase.TraLai
        || targetPhase == PurchaseEvaluationPhase.TuChoi)
    && decision != ApprovalDecision.Reject)
{
    throw new ConflictException(
        $"Transition tới {targetPhase} BẮT BUỘC decision=Reject (nhận {decision}). " +
        "Báo lỗi caller — payload mismatch giữa target phase và decision.");
}

Boundary protection cho mọi caller tương lai (API client / mobile / cron retry). Guard KHÔNG xoá khi FE fix — defense-in-depth.

3 test file tests/SolutionErp.Infrastructure.Tests/Services/PurchaseEvaluationWorkflowServiceGuardTests.cs:

  • TransitionAsync_TargetTraLai_WithApproveDecision_Throws_AndDoesNotMutateState — reproduce bug, expect ConflictException "TraLaiReject*"
  • TransitionAsync_TargetTuChoi_WithApproveDecision_Throws_AndDoesNotMutateState — consistency cover TuChoi
  • TransitionAsync_TargetTraLai_WithRejectDecision_SetsPhaseTraLai — happy path control (Reject branch vẫn đúng)
  • NoOpNotificationService stub reusable cho future PE service tests (avoid INotificationService real DI complexity).

Run test → 2 FAIL (reproduce bug, BE đi sâu vào ApproveV2Async throw "Phiếu chưa pin workflow") + 1 PASS (happy path). Thêm BE guard → 3 PASS. Tổng dotnet test SolutionErp.slnx 84 PASS (+3 from 81 baseline).

Chunk B — FE fix mirror 2 app (4b29d00)

3 chỗ × 2 app = 6 edits:

// Chỗ 1: isReject payload (L64-66)
const isReject = target === PurchaseEvaluationPhase.TuChoi
  || (target === PurchaseEvaluationPhase.DangSoanThao
      && evaluation.phase !== PurchaseEvaluationPhase.DangSoanThao)
  || (target === PurchaseEvaluationPhase.TraLai             // ← THÊM
      && evaluation.phase !== PurchaseEvaluationPhase.TraLai)

// Chỗ 3: dialog isSendBack (L247-248)
const isSendBack = (target === PurchaseEvaluationPhase.DangSoanThao
                    || target === PurchaseEvaluationPhase.TraLai)   // ← THÊM
  && evaluation.phase !== PurchaseEvaluationPhase.DangSoanThao
  && evaluation.phase !== PurchaseEvaluationPhase.TraLai             // ← THÊM

Chỗ 2 (button label isSendBack L205-207) đã đúng từ S17, KHÔNG đụng.

Verify: npm run build × 2 app pass (fe-user 17.91s + fe-admin 6.71s, 0 TS6 err).

Chunk C — Docs (this commit)

  • docs/gotchas.md +#45 PE button label vs decision payload mismatch (~120 dòng narrative + 2 commit cross-ref + pattern reusable + phòng tránh tương lai)
  • docs/gotchas.md checklist debug +entry 22 quick lookup
  • docs/STATUS.md Last updated S21 t3 + count 81→84 test + 44→45 gotcha
  • docs/HANDOFF.md TL;DR S21 t3 narrative đầy đủ (file này)
  • docs/changelog/sessions/2026-05-12-2100-s21-turn3-fix-tra-lai-bug45.md session log mới

Pending (carry from S21 turn 2)

Plans A-I unchanged. Plan C1 (test regression gotcha #44 silent 403 S18) vẫn còn nợ — không bundle với S21 t3 fix (scope khác, ưu tiên unblock UAT bug critical trước).

Audit cadence

  • Lần gần nhất: 2026-05-04 manual trễ 4 ngày
  • Lần kế: 2026-06-01 combined audit
  • Drift sau S21 t3: 44→45 gotcha (+1) + 81→84 test (+3) + 17→17 memory (no new) + 6 skills unchanged

State chốt S21 turn 3

Metric Trước (S21 t2) Sau (S21 t3) Δ
DB tables 59 59 0
Migrations 27 27 0
Endpoints ~142 ~142 0
FE pages 34 34 0
Unit tests 81 84 +3 (PE guard)
Gotchas 44 45 +1 (#45)
Memory entries 17 17 0
Skills 6 6 0
Sub-agents 4 seeds-only 4 seeds-only 0
Commits S21 t3 3 (de00887 + 4b29d00 + this)

TL;DR Session 21 turn 2 — RAG Hybrid setup planning (Cách A chốt + 3-layer pattern)

User clarify 5 dự án future > 1M MD tokens → cuộc thảo luận deep ~15 turn về RAG infrastructure. Em main solo (no SOLUTION_ERP sub-agent spawn), delegate 2 lần claude-code-guide agent research Anthropic + community practice.

Q&A deep dive 10 topics

  1. RAG fundamentals + Vector DB role (Qdrant)
  2. Embedding "AI nhúng" + Voyage AI cost mechanics ($0.18/M tokens)
  3. Multi-project shared architecture (5 projects → single Qdrant + per-collection)
  4. Audit procedure 3-tier (weekly auto + monthly deep + quarterly major)
  5. UI/UX Streamlit dashboard 7 pages design
  6. Cách A defensive (giữ blanket 120K) vs Cách B aggressive (cắt 60-70%)
  7. Reasoning depth comparison: lazy 60% → A 90% → B 75-80%
  8. Industry validation: Anthropic + Cursor + Continue + Cline + Aider all hybrid
  9. Multi-agent cost reality: 8-10× multiplier, ~520K cumulative blanket 5 entities
  10. 3-layer hybrid pattern (Anthropic Contextual Retrieval Sept 2024)

Quyết định chốt — Cách A vs Cách B

Chọn Cách A (defensive hybrid):

  • Blanket: GIỮ NGUYÊN 120K em main + RAG retrieve supplement
  • Sub-agent spawn baseline: ~80-100K each (4 agents = ~400K cumulative)
  • Heavy session billed: ~560K (saving -20% vs lazy 700K)
  • Quality recall: ~85% (vs Cách B 75-80% do fragmentation)

Why Cách A (bro priority chốt):

  • Em main control flow strong (state ownership direct, response fast)
  • Decision quality 90% (multi-source cohesive reasoning)
  • Wall-clock per task -20% (12 phút vs Cách B 16 phút)
  • Risk-averse (graceful fallback blanket nếu RAG fail)
  • Multi-agent leverage cache 70-90% hit common queries
  • Industry-validated (Anthropic + Cursor + Continue + Cline + Aider)

3-layer hybrid Phase rollout (Anthropic Contextual Retrieval)

Phase Layers Recall Cost/mo
Phase 1 (Week 1-4) Vector embedding only (Voyage-3-large) ~70% ~$1.50
Phase 2 (Month 2) + BM25 hybrid (bm25s free local) ~78% ~$1.50
Phase 3 (Month 3) + Voyage rerank-2 + Contextual prefix ~92% ~$4-5

Stack validated cross-industry

  • Voyage AI embedding (Anthropic partner, multilingual 26 lang)
  • Qdrant local (Rust binary, "leading agent memory backend 2026")
  • FastMCP Python (official Anthropic SDK)
  • SQLite event log + Streamlit dashboard 7 pages
  • Pre-commit hook re-index delta

Multi-agent cost reality (Anthropic warn 8-10× multiplier)

Per entity blanket Cách A:
  Em main: ~120K
  4 sub-agents × ~100K spawn = 400K cumulative
  Total: ~520K cumulative billed (not single context window)

Heavy session 4-agent spawn:
  Lazy: ~700K effective billed
  Cách A: ~560K (-20% from multi-agent shared cache)

Plan I NEW — RAG Setup Implementation (defer)

Trigger: Bro confirm 5 dự án path + stack + pilot choice + Voyage API key + disk cleanup 5-8GB.

Schedule: Dedicated session 10-14h weekend (per feedback_drastic_refactor_scope).

Phase rollout:

  • Phase 1 single project pilot 4-week trial
  • Phase 2-3 upgrade incremental conditional on Phase 1 success
  • Cost realistic: ~$2-5/month total cho 5 projects

Deliverables

  • docs/rag-setup-plan.md (commit 1f8e9af 1223 LOC + extend S21 t2 ~300 LOC = ~1500 LOC final)
  • Memory feedback_rag_hybrid_pattern.md (NEW cross-project reusable)
  • MEMORY.md index +1 entry
  • Session log this chốt
  • ⏭ Implementation defer chờ trigger

Em main solo S21 turn 2 (no SOLUTION_ERP sub-agent spawn)

3 spawn này session — KHÔNG phải 4 SOLUTION_ERP sub-agents:

  • claude-code-guide × 2 (generic agent for Anthropic + industry research)
  • 4 SOLUTION_ERP sub-agents (Inv/Imp/Rev/CICD) vẫn seeds-only

State chốt S21 turn 2

Metric Trước Sau Δ
DB tables 59 59 0
Migrations 27 27 0
Endpoints ~142 ~142 0
FE pages 34 34 0
Unit tests 81 81 0
Gotchas 44 44 0
Memory entries 16 17 +1 (RAG hybrid pattern)
Skills 6 6 0
Sub-agents 4 seeds-only 4 seeds-only 0
Commits S21 cumulative 2 4 +2
Plan files 0 1 (rag-setup-plan.md) +1

TL;DR Session 21 turn 1 — Add cicd-monitor (4th sub-agent, Path A chốt)

User chốt Path A sau pre-flight Plan G Trial Week 1 (S20 wrap đã setup 3 agents Inv/Imp/Rev): thêm sub-agent thứ 4 chuyên post-deploy verify (Gitea Actions poll + bundle hash × 2 app + sqlcmd mig prod + endpoint smoke). Trade-off chốt: +~150K spawn extra mỗi run, đổi lại catch deploy ship fail tự động — recurring blind spot pattern em main solo S20 quên verify ~30% push.

Em main solo S21 turn 1 (no agent spawn)

1 turn từ S20 wrap chiều/đêm 2026-05-11 22:00 sang sáng/đêm 2026-05-12. Em main solo qua context paste + Write file. 3 agents (Inv/Imp/Rev) KHÔNG spawn, vẫn seeds-only state. cicd-monitor mới setup file (Write), chưa spawn work.

Deliverables (1 commit f1c61c9, 456 +/-23 LOC)

File Loại LOC
.claude/agents/cicd-monitor.md NEW system prompt (~7KB) ~200
.claude/agent-memory/cicd-monitor/MEMORY.md NEW seed (~5KB) ~150
.claude/agents/README.md UPDATE 4-agent architecture +~80 / -23
feedback_multi_agent_setup.md (user-level memory) UPDATE 3 → 4 agents +12 / -6

Q&A chốt scope (Path A vs B)

  • Path A (chọn): cicd-monitor READ tier (~150K/spawn) — catch fail tự động
  • Path B (bỏ): Em main thêm checklist verify manual (~0 cost) — phụ thuộc memory + recurring miss
  • Decision rationale: Recurring blind spot S20 ~30% push quên verify → tự động hóa worth +150K trade-off

CI skipped (gotcha #41 path filter)

3 file .md → match paths-ignore: '**/*.md' → CI không trigger → prod IIS không thay đổi. Đây là expected behavior — agent infrastructure là local Claude Code only, KHÔNG cần deploy lên prod IIS. Khi spawn agent đọc file .md từ local filesystem.

State chốt S21 turn 1

Metric Trước S21 Sau S21 t1 Δ
DB tables 59 59 0
Migrations 27 27 0
Endpoints ~142 ~142 0
FE pages 34 34 0
Unit tests 81 81 0 (no test added)
Gotchas 44 44 0
Memory entries 16 16 0 (update existing entry only)
Skills 6 6 0
Sub-agents 3 seeds-only 4 seeds-only +1 cicd-monitor green
Commits S21 1 (f1c61c9)

Plan G (Multi-agent Trial 4-week) update post-S21 t1

  • Setup phase complete: 3 agents S20 t12 + 1 agent S21 t1 → 4 sub-agents seeds-only ready
  • Week 1 pending: Plan B Contract V2 wire Mig 28+29 — kick-off Session 21 turn 2+
  • Pass criteria updated: Rev catch ≥ 2 wire bugs + CI/CD Monitor catch ≥ 1 deploy ship fail (bundle hash unchanged / mig drift) + saving ≥ 25% Case 1+2 + Max 20× quota comfortable

Next session priority unchanged (carry from S20 wrap)

  • Plan B (HIGH) Contract V2 wire — Trial Week 1 kick-off với 4 agents
  • Plan C (HIGH) Test gap fill (B4 silent 403 + Mig 25/26/27 PATCH) — bundle Chunk E Plan B
  • Plan D Hard blockers ops (UAT/SMTP/creds/backup)
  • Plan E Phân quyền strict V2 + drop legacy V1
  • Plan F Audit định kỳ 2026-06-01 (chưa đến — KHÔNG tự chạy)

Cost reality update

Metric Trước (3 agents) Sau (4 agents)
Spawn setup total ~564K ~750K (+150K cicd-monitor)
Heavy session ~1.2M (~6× solo) ~1.35M (~6.5× solo)
Optimized cached ~600K (~3× solo) ~700K (~3.5× solo)

Max 20× plan absorbs ~3.5× solo cost comfortable.


TL;DR Session 20 WRAP (turns 1-12 chốt 2026-05-11)

User UAT live iteration liên tục — 12 turns trong 1 ngày (sáng-trưa-chiều-tối-đêm). 14 commit cumulative.

3 chủ đề lớn

  1. PE Detail UI restructure (turns 1-5 + 6 + 8-10): User yêu cầu 3 polish UX core + 4 polish nhỏ.
    • Turn 1-5 wrap commit 9dee00d→f2f01f4→f8e5675: Section reorder (Hạng mục lên #2 + auto-seed 1 row từ gói thầu) → Nested grid HangMucCard NCC expand (drop SuppliersTab dead code) → Section Ý kiến gộp đồng cấp 1 box / Step
    • Turn 6 f568945: Manual budget "Nhập tay" drop tên field, chỉ giữ số tiền + VND format
    • Turn 8-10: NCC palette 5-màu cycle + Winner icon ✓ đậm + hover transition + AddSupplier auto-fill master data 4 field
  2. Admin menu eOffice management (turn 7): Mig 27 IsVisible + DisplayLabel cột MenuItem + PATCH /api/menus/{key} + NEW MenuVisibilityPage ~210 LOC + fe-user Layout filter !isVisible + render displayLabel || label. Admin sidebar luôn dùng Label gốc (Q2=b).
  3. Infrastructure (turns 11-12):
    • Turn 11 responsive 4-tầng pattern cho laptop nhỏ → memory feedback_responsive_laptop_breakpoint.md
    • Turn 12 SETUP 3 sub-agents (Investigator + Implementer + Reviewer) + em main coordinator → memory feedback_multi_agent_setup.md

Stats cumulative

Metric Trước S20 Sau S20 Δ
DB tables 59 59 0
Migrations 26 27 +1 (Mig 27 menu visibility)
Endpoints ~141 ~142 +1 (PATCH /menus/{key})
FE pages 33 34 +1 (MenuVisibilityPage)
Menu keys ~60 ~61 +1 (MenuVisibility)
Unit tests 81 81 0 (Phase 9 UAT defer §7)
Gotchas 44 44 0
Memory entries 14 16 +2 (responsive t11 + multi-agent t12)
Skills 6 6 0
Sub-agents 0 3 +3 (Inv + Imp + Rev seeds)
Commits S20 14 (9dee00dae1814c)

Multi-agent state chốt session

3 sub-agents vừa setup turn 12 → seeds-only state, chưa spawn work. KHÔNG có findings để flush cross-agent learnings ở session này.

Trial Week 1 sẽ kick off Session 21:

  • Investigator pre-flight: audit PE V2 schema patterns (Mig 22-27) + Permission flow → spec Contract V2
  • Implementer Chunk A-E (Mig 28 ALTER Contract + Mig 29 ContractLevelOpinions + Service ApproveV2Async + Controller + FE mirror)
  • Reviewer pre-commit verify gotcha #42 (V1/V2 dual schema branch)
  • Em main: architecture decisions + scope refusals + final synthesize

Memory entries mới capture S20

  1. feedback_responsive_laptop_breakpoint.md (t11) — 4-tầng pattern: sidebar w-60 xl:w-72 + workspace 2-panel lg:260 xl:320 + Section padding xs/sm responsive + Card flex-wrap. Phân biệt lg vs xl breakpoint quan trọng cho laptop nhỏ.
  2. feedback_multi_agent_setup.md (t12) — Decision gate 6-criteria. Anthropic + Cognition hybrid. Implementer ACCEPT/REFUSE strict rules. Windows MAX_PATH pitfall (drop isolation worktree). NAMGROUP s41-s43 ROI curve.

Pending Session 21+ (cumulative carry over)

Plan cha B (HIGH priority) — Contract V2 wire Mig 28+29: mirror PE pattern S17-S19 + S20 turn 7. Audit-reuse memory feedback_audit_reuse_before_clone áp dụng — discriminator ApplicableType.Contract=3 đã chung 80% với PE schema V2. 6-task plan:

  • Task 1: Mig 28 ALTER Contract.ApprovalWorkflowId? + CurrentApprovalLevelOrder?
  • Task 2: Mig 29 CREATE ContractLevelOpinions (mirror PE Mig 26 UNIQUE + FK Cascade/Restrict)
  • Task 3: ContractWorkflowService.ApproveV2Async branch + UPSERT opinion
  • Task 4: ContractCreatePage Workspace Select V2 (validate ApplicableType=3)
  • Task 5: Pin V2 mặc định cho ContractType qua Designer (admin)
  • Task 6: ContractDetailContent Section "Ý kiến cấp duyệt" V2 dynamic mirror S20 Chunk C

Plan cha C (HIGH priority) — Test coverage gap fill (§7):

  • Test regression B4 silent 403 S18 (HIGH — vi phạm rule §7 test-before bug fix)
  • Test V2 Service wire ApproveV2Async UPSERT opinion (Mig 26) + Section gộp render (S20 t1-5 Chunk C)
  • Test Mig 25 PATCH /user-selectable endpoint
  • Test PATCH /api/menus/{key} Mig 27 (mới)

Plan cha D — Hard blockers ops (chờ user/ops): UAT thật 1 tuần / SMTP / Rotate creds / SQL backup schedule / win-acme fix / remove .huypham.vn binding

Plan cha E — Phân quyền strict V2 + drop legacy V1:

  • List/Inbox/Detail filter actor scope (V2 đã đúng — ResolveV2InboxIdsAsync)
  • Drop tables V1 sau UAT confirm: WorkflowDefinitions/Steps/Approvers + column RejectedAtStepIndex/RejectedFromPhase
  • Mig 30 drop Mig 15 PurchaseEvaluationDepartmentOpinions cleanup

Plan cha F — Audit định kỳ 2026-06-01: skill stale (ef-core-migration "21" → 27 / dependency-audit-erp 41 → 44) + schema-diagram §16-21 + memory consolidate xem có duplicate

Plan cha G NEW — Multi-agent trial 4 tuần (Week 1-4): evaluate ROI keep/tune/archive 3 sub-agents

Audit cadence

  • Lần gần nhất: 2026-05-04 manual trễ 4 ngày
  • Lần kế: 2026-06-01 combined audit
  • Drift sau S20: Mig 27 + 1 menu key + +2 memory entries + 3 sub-agents NEW + (no gotcha new). ef-core-migration skill "21 migration" stale → thực 27 sau S20 t12

TL;DR Session 20 turn 7 — Admin Ẩn/Hiện + Đổi tên menu eOffice (Mig 27)

User UAT live yêu cầu thêm tính năng admin quản lý menu eOffice (fe-user) — Ẩn/Hiện + Đổi tên hiển thị. Hỏi xác nhận "chưa có?" → đúng, chưa có. User clarify Q2=b "edit hiển thị bên ngoài, chỉ của eOffice thôi" → admin sidebar luôn dùng Label gốc, DisplayLabel CHỈ áp render fe-user.

Q&A chốt scope

  • Q1=a Global (không per-role — permission matrix đã handle)
  • Q2=b DisplayLabel chỉ fe-user, admin sidebar giữ Label gốc
  • Q3=a Giữ USER_HIDDEN_KEYS hardcode + tầng IsVisible dynamic combine
  • Q4=ok UAT iteration skip test, vẫn npm build mỗi chunk

Chunk A (2ea2d27) — Schema + Migration 27

Domain MenuItem +IsVisible bool=true +DisplayLabel string?(200). EF config HasDefaultValue(true) + HasMaxLength(200). Migration 27 AddVisibilityAndDisplayLabelToMenuItems (2 AddColumn) — 3-file rule. Apply LocalDB Dev + Design qua --connection override.

Chunk B (ef394f8) — BE API

DTO MenuNodeDto + MenuItemDto +isVisible +displayLabel. GetMyMenuTreeQueryHandler pass through (KHÔNG filter server-side — 2 FE app tự quyết render). NEW UpdateMenuItemCommand + Validator (Key required + DisplayLabel max 200) + Handler (whitespace → null). MenusController +PATCH /api/menus/{key} [Authorize Policy="Permissions.Update"].

Chunk C (059bfcb) — FE Admin MenuVisibilityPage

Domain MenuKeys +MenuVisibility. DbInitializer +leaf System/MenuVisibility (Eye, Order=94). FE Admin: types/menu.ts mirror, lib/menuKeys.ts +const, Layout resolver +/system/menu-visibility, App.tsx +Route. NEW pages/system/MenuVisibilityPage.tsx ~210 LOC:

  • PageHeader + description nhắc admin sidebar dùng Tên gốc
  • 4 StatCard (Tổng / Hiển thị / Đã ẩn / Đã đổi tên)
  • Search box (key | label | displayLabel)
  • Table 5 cột: Key mono + parentKey ↳ / Tên gốc / Input "Tên hiển thị" inline (placeholder "Mặc định: {label}") / Toggle button emerald-Eye / amber-EyeOff / Lưu (khi dirty) + Khôi phục (khi custom)
  • onSuccess invalidate ['menus','all'] + ['my-menu'] → live update

Chunk D (1ed6530) — fe-user Layout filter + render

types/menu.ts mirror. Layout.tsx:

function filterForUser(nodes: MenuNode[]) {
  // 2 tầng: hardcode USER_HIDDEN_KEYS + dynamic !isVisible
  return nodes.filter(n => !USER_HIDDEN_KEYS.has(n.key) && n.isVisible !== false)
              .map(n => ({ ...n, children: filterForUser(n.children) }))
}
function effectiveLabel(n) { return (n.displayLabel?.trim()) || n.label }

Replace 3 callsite {node.label}{effectiveLabel(node)}. fe-admin Layout KHÔNG đụng — render Label gốc + show hết menu (Q2=b).

Pending S21+ (cumulative carry over)

  1. Test V2 Service wire + Section gộp + B4 silent 403 + Mig 25 PATCH user-selectable
  2. Contract V2 wire (Mig 28+29 mirror PE) — biggest pending
  3. Phân quyền strict V2 + drop legacy V1 + Mig 15 cleanup
  4. Test PATCH /api/menus/{key} validate (Mig 27)
  5. Skill permission-matrix thêm section "menu visibility" — defer cron audit 2026-06-01
  6. UX verify trong UAT: admin ẩn menu cha → children có ẩn theo không (FE filter chỉ check !n.isVisible per-node)

Hard blockers ops (carry)

UAT thật / SMTP / Rotate creds / SQL backup schedule / win-acme fix / remove .huypham.vn

TL;DR Session 20 — PE Detail UI restructure 3 yêu cầu UX user (previous)

Note: TL;DR Session 20 PE Detail UI giữ nguyên dưới (rule §6.5 KEEP narrative). Session 20 turn 7 này thêm trên cùng (admin menu visibility — chủ đề khác).

(4 chunk 9dee00d2bba851f2f01f4→Docs). FE-only restructure (1 hook BE nhẹ auto-seed Detail). Q1=a giữ Section "Chọn NCC TP" riêng / Q2=a NCC shared + 1 hạng mục demo / Q3=a chỉ hiện NV đã ký / Q4 public luôn (skip dotnet test, vẫn npm build × 2 app mỗi chunk vì rename/remove function). Chunk A: BE CreatePE handler thêm 1 PurchaseEvaluationDetail mặc định (NoiDung=TenGoiThau, ThanhTienNganSach=Budget.TongNganSach||BudgetManualAmount||0) + FE reorder section Hạng mục lên #2. Chunk B: ItemsTab restructure list HangMucCard 1 card / hạng mục với expand panel chứa NCC inline table 8 cột (NCC/Liên hệ/Điều khoản TT/File báo giá/ĐG chưa VAT/ĐG có VAT/Thành tiền/Action). Click cell quote → QuoteDialog reuse. Drop SuppliersTab function ~134 LOC dead code, giữ 2 dialog + SupplierAttachmentsCell. Section 4 NCC tham gia gộp vào Section 2 → 4 section final (Thông tin/Hạng mục nested/Chọn NCC TP thắng thầu/Ý kiến). Chunk C: LevelOpinionsSectionV2 forEach step → 1 StepOpinionsBox (replace grid-cols-2 N approvers). Header "Bước N — Tên" + dept badge + "X/Y đã duyệt" counter. Body filter signed opinions sort levelOrder asc + signedAt asc → StepOpinionEntry per signed (tên + Cấp badge + admin override badge + timestamp + comment). NV chưa duyệt KHÔNG hiển thị. KHÔNG đụng Mig 26 schema. Drop LevelOpinionBox function. 81 test pass unchanged (UAT defer).**)

TL;DR Session 20 — PE Detail UI restructure 3 yêu cầu user UX

User UAT live feedback: "Logic khá OK rồi, điều chỉnh giao diện chỗ Duyệt NCC 1 tý". 3 yêu cầu cụ thể:

  1. Hạng mục đưa lên phía trên + auto-tạo 1 hạng mục từ gói thầu (tên = TenGoiThau, giá trị = ngân sách)
  2. NCC expand dưới hạng mục (tầng 1 = hạng mục, tầng 2 = NCC, thông tin nhập trên grid)
  3. Section Ý kiến: gộp comment đồng cấp cùng Phòng → 1 ô / bước (chỉ hiện NV đã duyệt)

Q&A clarify trước code (chốt scope)

  • Q1=a: Giữ Section "Chọn NCC TP thắng thầu" riêng (rõ UX, không gộp dropdown winner vào nested grid)
  • Q2=a: NCC shared cross-hạng mục (như schema PE.Suppliers hiện tại) — "nhưng hiện chỉ cần 1 hạng mục trước tiên" → đơn giản scope Chunk B
  • Q3=a: CHỈ hiển thị NV đã ký (KHÔNG show placeholder "— chưa duyệt")
  • Q4 public luôn demo: Phase 9 UAT iteration skip dotnet test mỗi chunk, vẫn chạy npm run build × 2 app mỗi chunk (rule UAT skip verify exception cho rename/remove function — đã catch TS6133 SuppliersTab + SupplierAttachmentsCell)

Chunk A (9dee00d) — BE auto-seed Hạng mục + FE reorder section

BE — PurchaseEvaluationFeatures.cs CreatePurchaseEvaluationCommandHandler:

var defaultBudgetValue = linkedBudgetTotal ?? request.BudgetManualAmount ?? 0m;
var defaultDetail = new PurchaseEvaluationDetail
{
    PurchaseEvaluationId = entity.Id,
    GroupCode = "01",
    GroupName = "Hạng mục chính",
    NoiDung = request.TenGoiThau,
    DonViTinh = "gói",
    KhoiLuongNganSach = 1m,
    KhoiLuongThiCong = 1m,
    DonGiaNganSach = defaultBudgetValue,
    ThanhTienNganSach = defaultBudgetValue,
    Order = 1,
};
db.PurchaseEvaluationDetails.Add(defaultDetail);
// + Changelog Insert audit

linkedBudgetTotal mới: nếu PE link Budget, fetch Budget.TongNganSach (computed sum BudgetDetails). Nếu không link, fall back BudgetManualAmount. Nếu cả 2 null → 0.

FE — Reorder section (mirror fe-admin + fe-user, Chunk A intermediate state): 1.Thông tin / 2.Hạng mục (lên #2) / 3.Chọn NCC / 4.NCC tham gia / 5.Ý kiến.

Verify: dotnet build SolutionErp.slnx 0 warn / 0 err.

Chunk B (2bba851) — Nested grid Hạng mục → NCC expand

Restructure ItemsTab thành list HangMucCard (1 card / 1 hạng mục, expanded=true mặc định cho 1 hạng mục demo).

HangMucCard structure:

┌──────────────────────────────────────────┐
│ ▼ 01 · Tên hạng mục          KL ĐG TT NS │ ← Header row
│ ──────────────────────────────────────── │
│ NCC tham gia (3)  [+ Thêm NCC]           │ ← Sub-header
│ ┌──────────────────────────────────────┐ │
│ │ NCC │ Liên hệ │ ĐK │ File │ giá ... │ │ ← Inline NCC table
│ ├──────────────────────────────────────┤ │
│ │ NCC X │ ... │ ... │ ... │ ... │     │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────┘

Header card: GroupCode + NoiDung + 3 stat (KL/ĐG ngân sách/TT ngân sách) + NS link Δ (nếu có Budget link) + Pencil/Trash actions + ▼/▶ toggle expand. Expand body: NCC inline table 8 cột (NCC / Liên hệ / Điều khoản TT / File báo giá / ĐG chưa VAT / ĐG có VAT / Thành tiền / Action).

Tương tác:

  • Click cell quote (chưa VAT / có VAT / Thành tiền) → mở QuoteDialog cũ (reuse)
  • + Thêm NCC button trong expand panel → AddSupplierDialog cũ (reuse)
  • icon mỗi NCC row → EditSupplierDialog
  • icon → setWinner mutation, row + cell ăn theo màu emerald
  • 🗑 icon disabled khi NCC là winner hoặc đã có quote (giữ logic cũ)
  • SupplierAttachmentsCell nhúng vào cell "File báo giá" — full CRUD upload/download/delete file

Drop dead code:

  • Function SuppliersTab xóa hoàn toàn (~134 LOC) — replace bằng HangMucCard expand panel
  • Bỏ Section 4 "NCC tham gia" cũ trong main render PeDetailTabs (gộp vào Section 2)

Section layout cuối (4 section):

  1. Thông tin gói thầu
  2. Hạng mục + Báo giá NCC (nested expand)
  3. Chọn NCC / TP thắng thầu
  4. Ý kiến cấp duyệt

Verify: npm run build × 2 app pass (sau khi catch TS6133 SuppliersTab unused → drop + SupplierAttachmentsCell unused → restore vào cột "File báo giá").

Chunk C (f2f01f4) — Section Ý kiến gộp đồng cấp cùng Phòng

FE-only mirror 2 app. KHÔNG đụng Mig 26 schema (vẫn UPSERT 1 row / Level trong PurchaseEvaluationLevelOpinions qua ApproveV2Async Service). Chỉ thay đổi render layer.

Trước (S19 LevelOpinionsSectionV2):

forEach step:
  div.grid-cols-2:
    forEach level:
      forEach approver:
        <LevelOpinionBox /> (1 box / NV)
          - Cấp N — Tên NV
          - "Đã duyệt" emerald badge or "— chưa duyệt" italic gray
          - comment text
          - admin override badge nếu signedBy !== approver

Sau (Chunk C):

forEach step:
  <StepOpinionsBox stepOrder stepName departmentName totalApprovers opinions={stepOpinions} />
    - Header: "Bước N — Tên" + dept badge emerald + "X/Y đã duyệt" counter
    - Body:
      - empty → "— Chưa có ý kiến duyệt." italic gray
      - else → list <StepOpinionEntry opinion /> per signed opinion
              (sort levelOrder asc, signedAt asc)

StepOpinionEntry:

  • Header trái: ApproverFullName + "Cấp N" badge slate + admin override badge amber nếu có
  • Header phải: emerald rounded-full timestamp "✓ DD/MM/YYYY HH:mm"
  • Body: comment text whitespace-pre-wrap

NV chưa duyệt KHÔNG hiển thị (Q3=a) — chỉ 1 box / Step thay vì N box / NV như cũ.

Drop dead code:

  • Function LevelOpinionBox xóa (~50 LOC) — replace bằng StepOpinionsBox + StepOpinionEntry

Verify: npm run build × 2 app pass.

Pending Session 21+ (carry over từ HANDOFF S19 + còn nguyên)

  1. Test V2 Service wire (Chunk B Service hook S19 + Section gộp Chunk C S20) — defer chờ UAT user confirm + có sample data Production. Domain test ApproveV2 + UPSERT opinion match logic + Admin override match firstLevel + comment empty placeholder + render gộp Step.
  2. Test regression B4 silent 403 S18 (HIGH §7 priority — vi phạm rule "test-before bug fix") — per-action [Authorize(Policy=...)] ApprovalWorkflowsV2Controller.
  3. Test Mig 25 PATCH /user-selectable endpoint (admin scope hẹp, MED).
  4. 🎯 Contract V2 wire (Mig 27/28 mirror PE pattern) — biggest pending Plan. Audit-reuse memory áp dụng:
    • Mig 27: Contract.ApprovalWorkflowId Guid? + CurrentApprovalLevelOrder int?
    • Mig 28: ContractLevelOpinions mirror PE Mig 26 (UNIQUE composite, FK Cascade/Restrict)
    • ContractWorkflowService.ApproveV2Async mirror PE branch
    • ContractCreatePage Workspace Select V2 (validate ApplicableType=Contract=3)
    • Pin V2 mặc định cho ContractType (admin Designer)
    • ContractDetailContent Section "Ý kiến cấp duyệt" V2 dynamic (mirror S20 Chunk C — 1 box / Step)
  5. Phân quyền strict V2 — vẫn loose UAT (mọi authenticated user thấy mọi phiếu V2). Sau confirm flow:
    • List = Drafter + approver any-Step + Admin
    • Inbox = chỉ approver Cấp hiện tại (V2 đã đúng — ResolveV2InboxIdsAsync)
    • Detail = same as List
    • Cũng giải quyết được bug "/inbox loose trả phiếu Nháp" → sau khi strict, B1 FE filter S18 có thể relax
  6. Drop legacy V1 cleanup sau khi không còn phiếu pin WorkflowDefinitionId:
    • Drop tables WorkflowDefinitions + WorkflowSteps + WorkflowStepApprovers + PE versions
    • Mig 29+ cleanup drop column RejectedAtStepIndex + RejectedFromPhase deprecated S17
    • Drop ApproveV1LegacyAsync branch trong Service
  7. Drop Mig 15 cho V2 phiếu sau UAT confirm — Mig 30 cleanup drop bảng PurchaseEvaluationDepartmentOpinions + entity. Hoặc giữ cả 2 backward compat (Q3 user chốt phiếu MỚI dùng V2, V1 cũ giữ legacy không migrate).
  8. schema-diagram §16 PE Level Opinions V2 + §17-21 Mig 18-21 — defer cron audit 2026-06-01.
  9. Skill ef-core-migration frontmatter "21 migration" stale (thực 26) — defer cron audit 2026-06-01.

Hard blockers ops (carry over từ Session 19)

  • UAT thật 1 tuần với 2-3 user (Drafter/CCM/BOD)
  • 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 chưa schedule Task Scheduler
  • Remove binding cũ .huypham.vn sau verify stable
  • win-acme scheduled task "unhealthy" — auto-renew fix trước 2026-06-18

Audit định kỳ

  • Lần gần nhất: 2026-05-04 (manual trễ 4 ngày) — log docs/changelog/skill-audit-2026-05.md
  • Lần kế: 2026-06-01 combined audit (skill + doc drift). Drift hiện tại unchanged từ S19 (S20 không thêm migration / gotcha mới):
    • ef-core-migration "21 migration" stale (thực 26)
    • dependency-audit-erp count gotcha 41 stale (thực 44)
    • schema-diagram §16 PE Level Opinions V2 + §17-21 Mig 18-21 pending
  • Cron Claude SDK KHÔNG fit monthly (auto-expire 7d, memory feedback_cron_monthly_limitation) — manual trigger khi đến ngày hoặc user nói "audit MD" / "kiểm tra docs"

TL;DR Session 19 — PE Section 5 V2 dynamic theo Workflow + Mig 26

User feedback Section 5 hiện CỨNG 4 box (Mig 15 PheDuyet/CCM/MuaHàng/SmPm từ Phase 8) → cần ĐỘNG theo Workflow V2 đã pin với phiếu. Spec rõ: forEach Step (Phòng) → forEach Level (Cấp) → forEach NV → 1 OpinionBox với ý kiến + tên người. "Bước 1 phòng A có 2 NV → 2 box ngang hàng".

User chốt 5 câu Q&A trước code (capture trong session log):

  • Q1=1B: Comment khi NV nhấn Duyệt trong Workflow Panel auto sync sang OpinionBox của NV đó (Section 5 read-only summary). KHÔNG có form input rời.
  • Q2=2A+Admin: Chỉ NV chính chủ duyệt được. Admin override → lưu SignedByUserId=Admin.Id, FE banner "Admin duyệt thay" khi SignedByUserId !== Level.ApproverUserId.
  • Q3=V2 hết: Phiếu V1 legacy → fallback render Mig 15 4 box CỨNG readOnly cho data history (KHÔNG drop Mig 15 — giữ data cũ).
  • Q4=4C + bonus: Phase=DaDuyet/TuChoi → khoá hoàn toàn. Admin có quyền duyệt thay. Comment empty/whitespace → ghi "(duyệt — không ý kiến)" placeholder.
  • Q5=5A: Layout group theo Step (header "Bước N — Phòng X" badge emerald + hint số người duyệt) + grid-cols-2 cho N approvers (wrap nếu N>2).

Polish 3 button Hành động (873e7a1) — đầu session

Session 18 turn cuối user đã review screenshot "Hành động: Trả lại / Hủy/Từ chối / Duyệt → Chờ CCM" — yêu cầu rút gọn label + 3 màu khác nhau + bold. PeWorkflowPanel.tsx (mirror 2 app):

  • Label: "← Trả lại (về Drafter sửa)" → "← Trả lại" | "✗ Hủy / Từ chối" → "✗ Từ chối" | "✓ Duyệt → Chờ CCM" → "✓ Duyệt"
  • Phase đích vẫn hiện qua tooltip title khi hover
  • 3 màu: Duyệt = emerald (positive) · Trả lại = amber (request changes) · Từ chối = red (terminal)
  • font-medium → font-bold

Chunk A (77a3058) — Domain entity + EF + Mig 26

PurchaseEvaluationLevelOpinion : AuditableEntity:

  • (PEId, ApprovalWorkflowLevelId) UNIQUE composite
  • Comment nvarchar(2000)
  • SignedAt datetime2 (luôn có khi UPSERT)
  • SignedByUserId Guid (NV chính chủ HOẶC Admin override)
  • SignedByFullName nvarchar(200) — denorm tránh user xóa/đổi tên

EF: FK Cascade Pe + Restrict Level. SignedByUserId KHÔNG nav (denorm only). Migration 26 AddPeLevelOpinionsForV2: 1 CREATE TABLE + 2 FK + 2 index. 3-file rule commit đủ. Apply LocalDB SolutionErp_Dev OK (Mig 25 + 26 catchup).

Verify: dotnet build pass + dotnet test 81 pass.

Chunk B (90baa8e) — Service V2 hook + DTO + GET include

Service ApproveV2Async sau khi log approval → UPSERT row LevelOpinion cho Cấp hiện tại:

var matchingLevel = pendingLevelGroup
    .FirstOrDefault(l => actorUserId.HasValue && l.ApproverUserId == actorUserId.Value)
    ?? pendingLevelGroup.First();  // Admin override fallback first
var actorFullName = await ResolveActorFullNameAsync(actorUserId, isSystem, ct);
var existingOpinion = await db.PurchaseEvaluationLevelOpinions
    .FirstOrDefaultAsync(o => o.PurchaseEvaluationId == evaluation.Id
        && o.ApprovalWorkflowLevelId == matchingLevel.Id, ct);
var normalizedComment = string.IsNullOrWhiteSpace(comment)
    ? "(duyệt — không ý kiến)"
    : comment.Trim();
// UPSERT: if existing → update; else → Add new

Reject (Trả lại / Từ chối) KHÔNG sync. Multi-NV cùng Cấp OR-of-N: match level theo ApproverUserId (NV chính chủ). Admin = fallback first; FE banner "Admin duyệt thay".

Helper ResolveActorFullNameAsync lookup denorm SignedByFullName từ Users (fallback "(System)" / "(unknown)").

DTO PurchaseEvaluationLevelOpinionDto 15 fields:

  • LevelId, StepOrder, StepName, StepDepartmentId, StepDepartmentName
  • LevelOrder, LevelName, ApproverUserId, ApproverFullName
  • Comment, SignedAt, SignedByUserId, SignedByFullName

GET handler Include LevelOpinions + BuildLevelOpinionsAsync JOIN ApprovalWorkflows.Steps.Levels + Departments + Users → denorm DTO. Empty list cho phiếu V1 / V2 chưa có cấp duyệt → FE fallback message.

Verify: dotnet build pass + dotnet test 81 pass.

Chunk C (6e913b3) — FE Section 5 V2 dynamic mirror 2 app

Type PeLevelOpinion (15 field) + PeDetailBundle.levelOpinions[].

Section 5 PeDetailTabs conditional:

{evaluation.approvalWorkflowId
  ? <LevelOpinionsSectionV2 ev={evaluation} />
  : <DepartmentOpinionsSection ev={evaluation} readOnly={opinionsReadOnly} />}

LevelOpinionsSectionV2:

  • Empty state khi flow null / 0 steps → message "Workflow chưa cấu hình hoặc chưa có cấp duyệt nào"
  • forEach step → header "Bước N — <step.name>" + dept badge emerald + hint "(N người duyệt)" nếu totalApprovers > 1
  • Body grid-cols-2 cho step.levels.flatMap(level => level.approvers.map(approver => <LevelOpinionBox/>))
  • Lookup opinion theo (stepOrder, levelOrder, approverUserId) match levelOpinions[]

LevelOpinionBox read-only:

  • Title "Cấp N — "
  • Badge amber "⚠ Admin duyệt thay" khi signedByUserId !== approverUserId
  • Badge emerald "✓ Đã duyệt" khi opinion tồn tại
  • Empty: "— chưa duyệt" italic gray
  • Footer: timestamp signedAt format vi-VN

Workspace mode hint amber giữ "Ý kiến + chữ ký auto đồng bộ khi NV duyệt phiếu — vào menu Duyệt để ký."

Mirror fe-admin + fe-user (rule §3.9).

Verify: npm run build × 2 pass · 0 TS error.

Stats Δ Session 19

Trước S19 Sau S19
Migrations 25 26 (+1 Mig 26)
DB tables 58 59 (+1 PeLevelOpinions)
API endpoints ~141 ~141 (no new — UPSERT auto qua Service hook)
FE pages 33 33 (modify existing only)
Test pass 81 81 (no change — UAT defer test §7)
Gotchas 44 44
Memory entries 14 14
Skills 6 6
Commits (after S18) +4 (873e7a1 + 77a3058 + 90baa8e + 6e913b3 + Chunk D Docs)

⚠️ Điều quan trọng cho Session 20+

  1. Test V2 Service wire mới (Chunk B Service hook) — defer khi UAT user UAT confirm + có sample data Production. Domain test ApproveV2 + UPSERT opinion match logic + Admin override match firstLevel + comment empty placeholder.

  2. Drop Mig 15 cho V2 phiếu (cleanup sau UAT confirm) — sau khi không còn phiếu V2 dùng PurchaseEvaluationDepartmentOpinions (tất cả phiếu V2 chỉ dùng Mig 26 LevelOpinions). Mig 27 cleanup drop bảng + entity. Phiếu V1 legacy giữ Mig 15. Hoặc giữ cả 2 để backward compat.

  3. Migrate phiếu V1 cũ sang V2 (data migration) — admin tool chuyển ApprovalWorkflowId từ null → V2 workflow phù hợp + clear WorkflowDefinitionId. Hiện chưa làm (Q3 user nói chuyển V2 hết = phiếu MỚI dùng V2, phiếu V1 cũ giữ legacy không migrate — đơn giản hơn).

  4. Contract V2 wire (Mig 27 hoặc 28) + Section 5 dynamic Contract — mirror PE Mig 26 pattern: thêm Contract.ApprovalWorkflowId + CurrentApprovalLevelOrder (Mig 27) + ContractLevelOpinions (Mig 28) + Service ApproveV2Async mirror PE + ContractDetailContent Section 5 V2. Audit-reuse pattern memory feedback_audit_reuse_before_clone áp dụng.

  5. Phân quyền strict V2 — vẫn loose UAT. Sau confirm V2 flow (S19 Section 5 + S18 polish OK):

    • List = Drafter + approver any-Step + Admin
    • Inbox = chỉ approver Cấp hiện tại (V2 đã đúng)
    • Detail = same as List
  6. schema-diagram §16 PE Level Opinions V2 — thêm khi Chunk D update. Mig 22-25 V2 schema vẫn defer cron audit 2026-06-01.

  7. Skill ef-core-migration frontmatter "21 migration" stale (thực 26). Defer cron audit 2026-06-01.


TL;DR Session 18 — PE V2 polish + Clone B + 4 bug fix UAT

User UAT live tiếp Session 17, 7 batch nhỏ + 1 feature lớn (Mig 25). Áp memory feedback_uat_skip_verify (skip dotnet test mỗi chunk, push ngay) + lesson 0ae3fe2: rename/remove → BẮT BUỘC npm run build.

B1 (aaa1c6c) — Pe Duyệt filter cứng "Đã gửi duyệt"

User: "Duyệt bỏ cái trạng thái đi, chỉ load những trạng thái 'Đã gửi duyệt' là đc."

  • Bỏ dropdown "Tất cả trạng thái" khỏi UI khi pendingMe=true, thay bằng hint amber "Lọc cố định: Đã gửi duyệt (phiếu đang chờ duyệt)"
  • Filter cứng client-side: getPeDisplayStatus(p.phase) === DaGuiDuyet — loại Nháp/Trả lại/Đã duyệt/Từ chối
  • Header count dùng rows.length khi pendingMe (inbox không paged)
  • Workaround BE /inbox loose UAT có thể trả phiếu Nháp (phân quyền strict V2 pending Session 19+)
  • Mirror fe-admin + fe-user PurchaseEvaluationsListPage.tsx

B2 (917446d) — HistoryTab filter Trả lại / Gửi duyệt lại

User: "Lịch sử thay đổi: chỉ bắt các dòng thay đổi khi trả lại và gửi duyệt lại thôi nhé, không cần bắt trạng thái duyệt và các thay đổi trước khi trả lại."

  • FE filter trong PeDetailTabs.HistoryTab, BE giữ audit data đầy đủ (reversible nếu user đổi ý / cần audit trail compliance)
  • Logic giữ:
    • Workflow transition về TraLai (phaseAtChange === 98)
    • Workflow transition từ TraLai (summary chứa "TraLai →")
    • Mọi thay đổi nội dung (Header/Detail/Supplier/Quote/Attachment) khi phaseAtChange === 98
  • Bỏ: workflow Approve cùng cấp (Cấp 1→2→DaDuyet), sửa khi phase=Nháp/ChoDuyet ban đầu
  • Empty state: "Chưa có lịch sử trả lại / gửi duyệt lại"

B3 (937eb24) — Clone V2 cho B (DuyetNccPhuongAn)

User: "Quy trình chọn thầu phụ - NCC → Duyệt NCC đúng. Plan kế hoạch clone toàn bộ updates sang Duyệt NCC và Giải pháp."

Audit reuse trước thay vì duplicate. Phát hiện 80% đã chung:

  • Schema V2 (Mig 22-24) qua ApplicableType enum
  • BE Service ApproveV2Async không hardcode type
  • App CQRS / API /approval-workflows-v2?applicableType=N dynamic
  • FE Designer ApprovalWorkflowsV2PageTYPE_CODE_TO_INT cả 3 type
  • Layout regex ^AwV2_(.+)$ match dynamic typeCode
  • App.tsx route /system/approval-workflows-v2/:typeCode dynamic

Chỉ thiếu cho B: menu key + sample seed (3 file ~60 LOC).

  • MenuKeys.cs +const ApprovalWorkflowDuyetNccPhuongAnV2 = "AwV2_DuyetNccPhuongAn" + add vào All[]
  • DbInitializer.SeedMenusAsync +leaf "Duyệt NCC và Giải pháp (Mới)" dưới root ApprovalWorkflowsV2 (Order=2 cạnh leaf A Order=1)
  • DbInitializer +SeedSampleApprovalWorkflowsV2Async (idempotent — skip nếu admin đã tạo workflow B nào, hoặc thiếu test user nv.test/Phòng CCM): seed QT-DN-PA-V2-001 v01 1 Bước Phòng CCM × 1 Cấp NV test
  • fe-admin/lib/menuKeys.ts +AwV2_DuyetNccPhuongAn

KHÔNG migration / Service / Designer page mới. Memory feedback_audit_reuse_before_clone.md capture pattern.

User feedback "OK khá tốt, 1 phát chạy luôn :))" sau verify → confirm approach.

B4 (f77ea38) — Fix silent 403 ApprovalWorkflowsV2Controller

Triệu chứng: Drafter nv.test Workspace tạo phiếu B → dropdown "Quy trình duyệt" empty mặc dù Admin Designer thấy 2 version (v01 sample + v02 admin clone).

Root cause: Class-level [Authorize(Policy = "Workflows.Read")] → non-admin role 403 Forbidden khi GET /api/approval-workflows-v2. TanStack Query catch error không hiện UI → dropdown rỗng silent.

Fix:

  • Class-level đổi [Authorize] only (any authenticated user)
  • GET endpoint inherit class policy — Drafter list workflow để pick read-only, không nhạy cảm
  • POST + DELETE giữ [Authorize(Policy = "Workflows.Create")] admin-only Designer

Pattern reusable cho Contract V2 Mig 26 sau.

B5 (a9c0857) — Fix sidebar highlight queryMatches transient keys

Triệu chứng: Ở leaf "Danh sách" /purchase-evaluations?type=1, click chọn 1 phiếu → URL thành ?type=1&id=abc → leaf bị mất highlight box (gotcha #34 cũ tái phát theo cách khác).

Root cause: queryMatches exact-set equality — target {type} (1 key) vs current {type, id} (2 keys) length mismatch → no match → leaf unhighlight.

Fix: TRANSIENT_QUERY_KEYS = {id, q, editHeader, page, phase, awId} — strip trước khi compare. Mọi key navigation identity (type, pendingMe, mode) check exact-set như cũ.

Edge cases verified:

URL hiện tại Target leaf Match
?type=1&id=abc Danh sách ?type=1 ✓ giữ highlight
?type=1&pendingMe=1 Danh sách ?type=1 ✗ distinct (không cross-highlight Pending)
?type=1&phase=10 Danh sách ?type=1 ✓ giữ highlight (filter dropdown)
?type=1&pendingMe=1&awId=xyz Duyệt ?type=1&pendingMe=1 ✓ giữ highlight

Mirror fe-admin + fe-user Layout.tsx.

B6 (2a53107) — Mig 25 IsUserSelectable + Designer pin toggle + bỏ "(clone)"

User feedback xem Admin Designer: "Bỏ chữ Clone đi nhé, ghi v02, v03... là đủ rồi. Thêm cho tao nút stick để chọn các quy trình nào mà User đc select bên ngoài khi tạo phiếu."

Bỏ "(clone)": Designer auto-fill name = cloneFrom.name (bỏ (clone) suffix). Version số đã đủ phân biệt.

Pin toggle "Cho user chọn":

  • Migration 25 AddIsUserSelectableToApprovalWorkflows:
    ALTER TABLE ApprovalWorkflows ADD IsUserSelectable bit NOT NULL DEFAULT 0;
    -- Backfill (giữ behavior cũ — active workflows vẫn pickable):
    UPDATE ApprovalWorkflows SET IsUserSelectable = 1 WHERE IsActive = 1;
    
  • Domain ApprovalWorkflow.IsUserSelectable — independent với IsActive, multiple versions có thể cùng selectable (admin có thể "ghim" nhiều version cho user pick).
  • App CQRS:
    • AwDefinitionDto +field IsUserSelectable
    • CreateAwDefinitionCommand Handler set default true cho version mới (mirror IsActive default)
    • New SetAwUserSelectableCommand(Guid Id, bool IsUserSelectable) + Handler — toggle
  • API PATCH /api/approval-workflows-v2/{id}/user-selectable policy Workflows.Create (admin only)
  • DbInitializer SeedSampleApprovalWorkflowsV2Async +IsUserSelectable = true
  • FE Designer (fe-admin/ApprovalWorkflowsV2Page.tsx):
    • DefinitionDto +isUserSelectable
    • Badge amber "📌 Cho user chọn" cạnh badge IsActive/Archived khi isUserSelectable === true
    • Button "📌 Ghim cho user / 🚫 Bỏ ghim" trong action group + mutation toggleSelectable (call PATCH endpoint, invalidate query)
  • FE Workspace (cả fe-admin + fe-user PeWorkspaceCreateView.tsx):
    • approvalWorkflows query .filter(w => w.isUserSelectable) — chỉ workflows admin đã ghim hiện trong dropdown user

B7 (32a8d4d) — Cleanup orphan zip files

.claude.zip + docs.zip từ harness session start lỡ tay vào git add -A ở B6 commit. Untrack + add *.zip rule .gitignore.

Stats Δ Session 18

Trước S18 Sau S18
Migrations 24 25 (+1)
DB tables 58 58 (Mig 25 chỉ ALTER cột)
API endpoints ~140 ~141 (+1 PATCH user-selectable)
FE pages 33 33 (modify existing only)
Test pass 81 81 (no change — UAT feature defer test §7)
Gotchas 43 44 (+1 silent 403)
Memory entries 13 14 (+1 audit reuse pattern)
Skills 6 6 (no add)
Commits (after S17) +7

⚠️ Điều quan trọng cho Session 19+

  1. Contract V2 wire (Mig 26) — pending dedicated session. Pattern audit-reuse áp dụng: phần lớn đã chung. Mirror PE pattern:

    • Thêm Contract.ApprovalWorkflowId + CurrentApprovalLevelOrder (Mig 26)
    • ContractWorkflowService.ApproveV2Async mirror PE pattern
    • ContractCreatePage Workspace Select V2
    • Pin V2 mặc định cho ContractType
    • Permission GET endpoint đã permissive (Session 18 fix), không cần đụng
  2. Phân quyền strict V2 V2 — hiện loose UAT (mọi authenticated thấy mọi phiếu V2). Sau confirm flow:

    • List = Drafter + approver any-Step + Admin
    • Inbox = chỉ approver Cấp hiện tại (V2 đã đúng — ResolveV2InboxIdsAsync)
    • Detail = same as List
    • Cũng giải quyết được bug "/inbox loose trả phiếu Nháp" — sau khi strict, B1 FE filter có thể relax nếu BE đã filter đúng
  3. Drop legacy V1 cleanup sau khi không còn phiếu pin WorkflowDefinitionId (V1):

    • Drop tables WorkflowDefinitions + WorkflowSteps + WorkflowStepApprovers + PE versions
    • Mig 27 cleanup drop column RejectedAtStepIndex + RejectedFromPhase deprecated S17
    • Drop ApproveV1LegacyAsync branch trong Service
  4. Test V2 wire (defer khi UAT confirm + có sample data) — Domain test ApproveV2Async match logic + transient TraLai entry → Cấp 1 reset.

  5. feedback_audit_reuse_before_clone memory — áp dụng cho mọi "clone X sang Y" / "thêm type Z mới" sau này. List "đã chung" vs "còn thiếu" trước khi propose plan.

  6. Sample seed B sample chạy với check hasAnyB — sau UAT có thể remove sample seed (admin đã tạo workflow thật). Hoặc giữ làm fallback. Idempotent skip nếu admin có workflow B → không clobber.


TL;DR Session 17 — PE V2 schema end-to-end

TL;DR Session 17 — PE V2 schema end-to-end

User chốt sau Session 16 (drastic refactor flat Mig 21 vẫn sai intent): viết lại schema riêng + thêm Menu "Duyệt NCC (Mới)" với cấu trúc explicit:

Mã Quy trình - Tên Quy trình
  * Bước 1 - Phòng A
    * Cấp 1 - NV X        ← 1 user CỤ THỂ qua ApproverUserId
    * Cấp 2 - NV Y
  * Bước 2 - Phòng B
    * Cấp 1 - NV Z

Khác Mig 21: mỗi Cấp = 1 NV chính xác, KHÔNG OR-of-many group Dept+PositionLevel/Role/User.

4 commit (3 chunk per-commit + docs):

Chunk A (c847dc0) — Domain + EF + Mig 22 + Menu

  • Domain ApprovalWorkflowsV2/ApprovalWorkflow.cs — 3 entity (ApprovalWorkflow + Step + Level) + enum ApprovalWorkflowApplicableType (DuyetNcc=1 / DuyetNccPhuongAn=2 / Contract=3)
  • EF ApprovalWorkflowConfiguration.cs — UNIQUE (Code, Version), FK Cascade Step→Workflow + Level→Step, FK Restrict Department + ApproverUserId
  • ApplicationDbContext +3 DbSet
  • Migration 22 AddApprovalWorkflowsV2 — 3 CREATE TABLE + 1 UNIQUE + 4 INDEX. Applied cả _Design + _Dev LocalDB
  • DbInitializer SeedMenusAsync: +menu ApprovalWorkflowsV2 root dưới System (icon Workflow) + leaf AwV2_DuyetNcc (icon FileCheck, label "Duyệt NCC (Mới)")
  • MenuKeys.cs +2 const trong All array

Chunk B (f6047d5) — Application CQRS + API

  • Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs:
    • GetAwAdminOverviewQuery(ApplicableType?) — load 3-level Include + dept/user names map
    • CreateAwDefinitionCommand + Validator — auto-increment Version theo Code, deactivate active version cùng ApplicableType
    • DeleteAwDefinitionCommand — UAT helper unconditional (chưa pin)
    • DTO AwDefinition/AwStep/AwLevel + AwTypeSummary
  • IApplicationDbContext +3 DbSet
  • Api/Controllers/ApprovalWorkflowsV2Controller — route /api/approval-workflows-v2, GET ?applicableType=N | POST | DELETE/{id}, reuse policy Workflows.Read + Workflows.Create

Chunk C (2781c7e) — FE Designer

  • fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx (~480 LOC)
    • Overview cards Active+History per ApplicableType
    • DefinitionCard read-only: Bước (badge phòng emerald) → Cấp (badge violet C1/C2 + tên NV + email)
    • Designer dialog: Mã/Tên/Mô tả + Add/Remove Step + reorder (chevron up/down) + Add/Remove Level + Select Phòng + Select NV duyệt
    • Validate: mỗi Step ≥1 Level, mỗi Level phải có ApproverUserId
    • Auto-assign code mặc định theo type: QT-DN-V2-001 / QT-DN-PA-V2-001 / QT-HD-V2-001
  • Layout.tsx resolver +ApprovalWorkflowsV2 root → /system/approval-workflows-v2, +AwV2_ leaf → /system/approval-workflows-v2/<code>
  • App.tsx +2 route
  • menuKeys.ts +2 const sync với BE

Chunk D — Docs

STATUS + HANDOFF + project_solution_erp.md memory.

Chunk E (UAT iteration, 9 commit) — Designer fix + State machine + Service wire + UX

User UAT iter Designer V2 phát hiện multiple issues + chốt spec dần qua state diagram. Per memory feedback_uat_skip_verify.md UAT mode iterate nhanh:

Commit Tóm tắt
9712778 Designer iter 1 lock 3 cấp/bước (sai intent)
f3bea3c Designer iter 2 đúng intent: max 3 cấp × N NV/cấp + sequential gating C2/C3 disabled khi prev empty + filter NV theo Phòng + no-dup same level. Validator BE strict
ff21120 State machine 5 trạng thái Nháp/ĐãGửiDuyệt/TrảLại/TừChối/ĐãDuyệt. TraLai = Phase RIÊNG (không revert DangSoanThao + không jump-back). PE/Contract/Budget Phase enum + Policy + Service Reject branch → TraLai. 4 test mới TraLai entry point
0a40c65 Mig 23 AddApprovalWorkflowIdToPurchaseEvaluation — pin V2 vào PE entity. Workspace Select bắt buộc workflow lúc create. Validate ApplicableType match PE.Type
b41484b Mig 24 AddCurrentApprovalLevelOrderToPe + Service V2 wire — ApproveV2Async iterate Steps/Levels group by Order = Cấp (OR-of-N approvers) match actor.Id ∈ ApproverUserId. Synthetic Policy ForV2Schema() cho FE nextPhases
d814429 DTO CurrentApproval + banner "Đến lượt bạn" / "Không phải lượt bạn" + button Duyệt forward disabled khi V2 + actor không trong cấp + tooltip "chỉ {NV X / Y} duyệt được". Trả lại + Từ chối vẫn enabled
9e63e2d d250ae4 74745a7 List/Inbox V2-aware (ResolveV2InboxIdsAsync precompute IDs). 2 dropdown filter Quy trình + Trạng thái (chỉ ở Duyệt). Inbox endpoint nhận approvalWorkflowId
ac41d5e SQL clean-transactional-uat.sql — clean prod (9 PE + 11 HĐ + 19 Notif xóa) giữ master. Run qua SSH VPS .\SQLEXPRESS
de0f38d Panel 3 thay 4 phase cards bằng flow workflow thực tế: Bước (icon ✓/●/○) → Cấp (label "đang chờ"/"đã duyệt" + tên NV). DTO ApprovalFlow full snapshot với Status Done/Current/Pending

Stats final Session 17: 24 migration (+3), 58 DB tables (+3), ~140 endpoints (+5), 81 test pass (+4).

Test user UAT (tạo qua API admin):

  • Email: nv.test@solutions.com.vn / Pass: TestUser@123456 / Role: Drafter / Phòng: CCM

⚠️ Điều quan trọng cho Session 18+

  1. Contract V2 wire CHƯA làm — chỉ PE wire xong (Mig 23-24). Session sau mirror pattern PE → Contract:

    • Thêm Contract.ApprovalWorkflowId + CurrentApprovalLevelOrder (Mig 25)
    • Update ContractWorkflowService thêm ApproveV2Async branch
    • Update Workspace Select V2 trong ContractCreatePage
    • Pin V2 thành mặc định cho Contract types
  2. Phân quyền strict V2 — hiện loose UAT (mọi authenticated user thấy phiếu V2). Sau confirm flow OK:

    • List: Drafter + bất kỳ approver any-Step + Admin
    • Inbox: chỉ approver Cấp hiện tại (V2 đã đúng — ResolveV2InboxIdsAsync)
    • Detail: same as List
  3. Drop legacy V1 sau UAT — khi không còn phiếu nào pin WorkflowDefinitionId:

    • Drop tables WorkflowDefinitions + WorkflowSteps + WorkflowStepApprovers + PE versions
    • Cleanup migration: drop column RejectedAtStepIndex + RejectedFromPhase (deprecated từ Session 17)
    • Drop ApproveV1LegacyAsync branch trong Service
  4. Admin role bypass — hiện code if (!isAdmin && !isSystem) skip approver check. By design cho UAT + emergency override. Nếu prod cần audit override → option C trong Session 17 thảo luận: thêm flag IsAdminOverride=true trong approval row + banner đỏ trên detail.

  5. 81 test pass — Domain WorkflowPolicyTests + PurchaseEvaluationPolicyTests + BudgetPolicyTests đã update cho TraLai entry point. KHÔNG có test cho V2 Service wire (defer khi UAT confirm + có sample data).


TL;DR Session 16 (08/05 — Drastic refactor flat workflow EXECUTE)

Resume từ Session 15 defer plan. Per memory feedback_drastic_refactor_scope.md: dedicated session, fresh context, conservative buffer.

Spec: Workflow flat list (Phòng × Cấp × Approvers). Mỗi step = 1 (Phòng × Cấp). Service iterate steps OrderBy Order, advance pointer. Phase enum simplify ChoDuyet=10. Pin WorkflowDefinitionId.

2 chunk per-commit (5-6 chunk plan rút gọn vì BE tightly coupled):

Chunk A (dbb0089) — All BE: Domain + Mig 21 + Service + Tests

Domain entities:

  • Phase enum (PE + Contract): + ChoDuyet=10 generic intermediate. Legacy 2-6 + 98 deprecated (giữ enum cho data cũ).
  • WorkflowStep + DepartmentId Guid? FK Restrict + PositionLevel int?
  • PurchaseEvaluation/Contract + CurrentWorkflowStepIndex int? + RejectedAtStepIndex int?
  • DROP class WorkflowStepInnerStep + nav (PE + Contract)
  • DROP *DepartmentApproval.InnerStepId column

EF Configurations:

  • DROP InnerStep config (PE + Contract) → table dropped
  • WorkflowStep config + DeptId/PositionLevel + FK Restrict
  • DepartmentApprovals: restore simple unique non-filtered (Mig 19/20 filtered split reverse)

ApplicationDbContext: DROP DbSet<*WorkflowStepInnerStep> × 2

Migration 21 RefactorWorkflowToFlatModel GỘP:

  • 4 ALTER (PE/Contract +CurrentStepIndex +RejectedAtStepIndex)
  • 2 ALTER (WorkflowStep +DepartmentId +PositionLevel) PE + Contract
  • DROP TABLE × 2 (PEWorkflowStepInnerSteps + WorkflowStepInnerSteps Mig 18+20)
  • DROP COLUMN × 2 (*DeptApproval.InnerStepId)
  • DROP filtered indexes Mig 19/20
  • RESTORE simple UNIQUE (TargetId, Phase, Dept, Stage) non-filtered × 2

Service rewrite (PE + Contract WorkflowService.TransitionAsync):

  • DangSoanThao → ChoDuyet (Drafter trình, init idx=0)
  • ChoDuyet → ChoDuyet (advance idx per approve)
  • ChoDuyet → DaDuyet/DaPhatHanh (idx ≥ steps.Count → terminal, gen mã HĐ Contract)
  • ChoDuyet → DangSoanThao (Trả lại — save RejectedAtStepIndex)
  • ChoDuyet → TuChoi (Từ chối — khoá vĩnh viễn)
  • Resume Drafter (DangSoanThao + RejectedAtStepIndex≠null) → ChoDuyet jump-back
  • Match approver: actor.Dept == step.Dept AND actor.PositionLevel >= step.PositionLevel (OR-of-many cùng cấp/dept) OR Approvers.Kind=User|Role match
  • Admin role bypass policy

App CQRS: WorkflowStepDto + WorkflowStepInput drop InnerStep, add DepartmentId/DepartmentName/PositionLevel (PE + Contract mirror).

Tests:

  • DROP PeNStageApprovalTests.cs (6) + ContractNStageApprovalTests.cs (6) + PeTwoStageApprovalTests.cs (7) — legacy
  • UPDATE PeWorkflowAdminTests signature for new flat input
  • 96 → 77 test pass (-19 legacy)

3-file rule Mig 21 commit đủ (.cs + Designer + Snapshot).

Chunk B (88a5be1) — FE Designer + types

PeWorkflowsPage + WorkflowsPage rewrite (~210 LOC each):

  • Drop InnerStepDto + EditInnerStep types
  • Drop PHASE_OPTIONS (auto-assign ChoDuyet=10 behind scenes)
  • StepDto + EditStep + departmentId, departmentName, positionLevel
  • Designer step UI rewrite: Tên + Phòng Select + Cấp Select + SLA + Approvers (Role/User optional fallback). Drop InnerSteps sub-section.
  • DefinitionCard view: badge Phòng emerald + Cấp NV/PP/TP violet
  • Save payload: phase=10 (ChoDuyet)
  • Hint amber: "User cùng Phòng + Cấp ≥ step → duyệt được (OR-of-many)"

types/purchaseEvaluation.ts (fe-admin + fe-user mirror): + ChoDuyet=10 enum + label "Đang duyệt" + color amber. Legacy 2-6 + 98 keep.

Chunk C (FE PeWorkflowPanel) SKIPPED — existing UI compatible (workflow.nextPhases BE-driven, 3-button Trả lại/Từ chối Session 14 reuse với target=DangSoanThao/TuChoi pattern).

Verify

  • dotnet build SolutionErp.slnx 0 error
  • dotnet ef database update Mig 21 LocalDB applied OK
  • dotnet test 77 pass (54 Domain + 23 Infra)
  • npm build fe-admin + fe-user pass

Cumulative sau Session 16

Trước S16 Sau S16
BE LOC ~15800 ~15500 (-300 service simplified)
Migrations 20 21
DB tables 57 55 (-2 InnerStep tables)
Tests 96 77 (-19 legacy N-stage/2-stage)
FE pages 32 32 (rewrite existing 2 designer)

⚠️ CẢNH BÁO Session 17+

  1. UAT live test — workflow flat ready. Tạo new workflow definition qua /system/pe-workflows/:typeCode với 3 phòng × N cấp setup. Verify Drafter trình → cấp 1 phòng A → cấp 2 phòng A → cấp 1 phòng B → ... → DaDuyet flow.

  2. Old PE/HĐ pinned legacy workflow definitions (phase=ChoPurchasing/ChoCCM/etc) — service rewrite chỉ handle ChoDuyet=10 + DangSoanThao/DaDuyet. Old data ở phase 2-6 sẽ stuck (admin manual transition required). Recommend: data migration script convert old workflow → new flat model (defer).

  3. Approver explicit (Role/User Approvers list) — fallback nếu user không match Dept+PositionLevel của step. Cho phép user external (không thuộc dept) duyệt qua Role match (vd Admin) hoặc User explicit.

  4. Bypass cấp dưới cùng dept — User TP với CanBypassReview=true cùng dept và PositionLevel cao hơn step.PositionLevel → duyệt qua. KHÔNG batch upsert NV+PP rows như Mig 18 N-stage trước (đơn giản hóa: 1 step approve = 1 row).

  5. N-stage tests dropped — 19 test legacy (Mig 18, 20 N-stage + Mig 16 2-stage). Có thể viết test mới cho flat workflow flow nếu UAT phát sinh bug. Defer.

  6. Sample data N-stage seed task vẫn pending (Session 14). Block trên DesignTime vs Runtime DB gotcha + DbInitializer seed flow.

  7. Budget N-stage vẫn defer (cần versioned WF migration trước).

  8. schema-diagram §17-21 update defer cron audit 2026-06-01.

TL;DR Session 15 (07/05 — Tooltip diagnose + drastic refactor DEFER)

User UAT live screenshot báo button "Lưu & Gửi Duyệt" KHÔNG hoạt động + suy đoán "trùng ID" giữa các phiếu.

Diagnose (commit 835cc7f):

  • Root cause: button silent disabled khi evaluation.workflow.nextPhases không có forward phase (chỉ TuChoi/TraLai). Cause khả năng: workflow definition pinned thiếu adjacent step → policy.NextPhasesFrom(DangSoanThao) return empty.
  • Improvement: tooltip + dialog hiển thị reason rõ ràng:
    • submitDisabledReason text: "Phiếu đã ở phase X — chỉ Bản nháp/Trả lại mới sửa+gửi" / "Workflow không có phase tiếp theo từ X. Liên hệ admin kiểm tra cấu hình"
    • Button title attribute → hover show reason hoặc forward phase label
    • Dialog confirm show forward phase explicit ("Sẽ chuyển sang Chờ Purchasing")
  • Mirror fe-admin + fe-user. Build pass cả 2. KHÔNG đụng BE — chỉ FE diagnostic UX.
  • "Trùng ID" KHÔNG phải bug FE — PurchaseEvaluationWorkspacePage URL state đúng, mỗi PE row unique GUID + MaPhieu. Suy đoán user do button silent.

Plan drastic refactor → DEFER:

User confirm "bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking" — refactor workflow từ phase-based + InnerStep nested model sang flat WorkflowStep model (mỗi step = Phòng × Cấp + Approvers users).

Edit working tree 12 files (Domain entities + EF Configurations + DbContext):

  • Phase enum +ChoDuyet=10, legacy values 2-6 deprecated
  • WorkflowStep +DepartmentId, +PositionLevel
  • Drop class WorkflowStepInnerStep + nav (PE + Contract)
  • PE/Contract +CurrentWorkflowStepIndex int?, +RejectedAtStepIndex int?
  • *DepartmentApproval drop InnerStepId column
  • EF Configurations: drop InnerStep config + nav, restore simple unique non-filtered
  • DbContext: drop DbSet × 2

Reality check scope realistic ~8-10h:

  1. Domain + EF + DbContext (~50min) ✓ done in working tree
  2. PolicyRegistry rewrite PE+Contract (~45min)
  3. App CQRS DTOs rewrite (~45min)
  4. Service rewrite PE+Contract (~2-3h)
  5. Tests rewrite — drop 12 N-stage tests + update remaining (~1.5h)
  6. Migration 21 + LocalDB apply + verify (~30min)
  7. FE Designer rewrite (~1.5h)
  8. FE PeWorkflowPanel + workflow timeline (~1h)
  9. Docs/Skill update (~45min)

Vượt session boundary + risk session deep ~30 commits → REVERT working tree về 835cc7f clean state. Test 96 pass intact.

Decision memorized: add memory feedback_drastic_refactor_scope.md — drastic refactor cần dedicated session, scope estimation conservative (2x buffer), tránh mid-session big refactor.

⚠️ CẢNH BÁO Session 16+

  1. Drastic refactor flat workflow chưa làm — DEFER với plan chi tiết. Khi resume:
    • Plan kỹ 6 chunk per-commit
    • Buffer 2x estimate (~16h thực tế)
    • Tests rewrite biggest risk
    • Hoặc fall back Approach Y (FE Designer flat UI giới hạn 5 phòng) ROI 1-2h nếu user OK trade-off
  2. Task 2 sample data seed N-stage vẫn pending (block trên DesignTime vs Runtime DB gotcha + DbInitializer seed flow)
  3. schema-diagram §17-19 Mig 18-20 vẫn defer cron audit 2026-06-01
  4. Hard blockers Ops giữ nguyên 6 task

TL;DR Session 14 (07/05 — PE 3-button approval workflow)

User chỉ thị thay 2-button approval (Duyệt + Reject mơ hồ) bằng 3 hành động rõ ràng cho approver:

  • Duyệt = forward phase tiếp theo (decision=Approve)
  • Trả lại = về DangSoanThao + Drafter sửa (decision=Reject + target=DangSoanThao). Smart reject pattern Mig 16 + clear N-stage rows + Drafter resume jump-back tới phase đã reject.
  • Từ chối = phase=TuChoi (decision=Reject + target=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/ChoDuAn/ChoCCM/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:
    if (decision == Reject) {
      if (target != TuChoi) {  // Trả lại
        RejectedFromPhase = fromPhase
        target = DangSoanThao  // force
        clear N-stage rows tại fromPhase
      }
      // else target=TuChoi: giữ nguyên, KHÔNG set RejectedFromPhase, KHÔNG clear
    }
    
  • FE PeWorkflowPanel (admin + user mirror): render 3 button rõ:
    • "✓ Duyệt → " brand
    • "← Trả lại (về Drafter sửa)" red khi target=DangSoanThao && fromPhase != DangSoanThao
    • "✗ Hủy / Từ chối" red khi target=TuChoi
    • Decision logic FE: isReject = target=TuChoi || isSendBack
  • Dialog confirm: title rõ theo loại, Cancel case warning đỏ "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 update + add 1:
    • rename Reject_Sets_RejectedFromPhase_And_Forces_DangSoanThaoReject_To_DangSoanThao_Sets_RejectedFromPhase_TraLai (change target TuChoi→DangSoanThao)
    • NEW Reject_To_TuChoi_Locks_Permanently_No_RejectedFromPhase (Phase=TuChoi + RejectedFromPhase null)
    • update NStage_Reject_Clears_InnerStep_Rows_At_Phase target → DangSoanThao
  • 95 → 96 test pass (+1 Từ chối).

Task 2 sample seed in-progress (defer Session 15+):

  • Phát hiện gotcha: DesignTimeDbContextFactory hardcoded SolutionErp_Design connection — dotnet ef database update từ session 12-13 thực ra apply lên _Design DB, KHÔNG phải _Dev runtime DB. Khi user run API → DbInitializer auto-MigrateAsync apply lên _Dev.
  • Đã apply Mig 9-20 lên SolutionErp_Dev qua dotnet ef database update --connection.
  • Start API để DbInitializer auto-seed 30 demo user. API exit 255 sớm khi log buffer full (~100 lines) — seeding dở dang (chỉ seed Roles, chưa Users).
  • Defer Task 2 cho session sau. Cần: user manual dotnet run API 1 lần hoàn thành seed, hoặc Claude resume với output redirect to file.

⚠️ CẢNH BÁO Session 15+

  1. Phase TraLai = 98 (orphan) — Domain enum đã có từ S11+++++++ nhưng KHÔNG dùng (user chốt Session 14 không cần phase trung gian, chỉ DangSoanThao + TuChoi). Để giữ enum không phá nhưng ko wire vào policy → orphan. Có thể remove migration sau (không gây hại).

  2. Task 2 sample seed pending: 2 việc:

    • Update Users.PositionLevel (1=NV, 2=PP, 3=TP) cho 30 demo user đã seed (mapping email pattern: tpb./ccm./pro./fin./act./equ./hra. → 3 (TP), pp.* → 2 (PP), nv./qs./ else → 1 (NV), bod./pm./admin./huy.duong/chau.le → null)
    • INSERT N-stage WorkflowDefinition v2 cho DuyetNcc với InnerSteps (PRO + CCM × 3 cấp NV/PP/TP)
  3. DesignTime vs Runtime DB distinction: dev có 2 DB:

    • SolutionErp_Designdotnet ef migrations add/update (DesignTimeDbContextFactory hardcode)
    • SolutionErp_Dev — runtime API (appsettings.Development.json) Khi muốn apply migrations lên Dev: dotnet ef database update --connection "...SolutionErp_Dev..." hoặc dotnet run API (DbInitializer auto-MigrateAsync).
  4. Budget N-stage defer — chưa wire vì Budget chưa có versioned WorkflowDefinition. Cần migration AddBudgetVersionedWorkflow + AddBudgetWorkflowInnerSteps. User quyết riêng (feature mở rộng module lớn).

  5. schema-diagram §17-19 Mig 18-20 chưa update — defer cron audit 2026-06-01.

TL;DR Session 13 (07/05 — Mirror N-stage workflow sang Contract)

User chỉ thị mirror N-stage từ PE (Mig 18+19) sang Contract. Budget defer (cần migration AddBudgetVersionedWorkflow trước — hardcoded BudgetPolicy.Default chưa có WorkflowDefinition entity).

5 chunk per-commit (Chunk E skipped — WorkflowsController auto-bind record qua [FromBody]):

  • Chunk A (951ffa3) Domain WorkflowStepInnerStep + nav WorkflowStep.InnerSteps + ALTER ContractDeptApproval.InnerStepId + EF config + Migration 20 GỘP 1 (CREATE TABLE + ALTER + DropIndex old + Recreate filtered legacy/N-stage)
  • Chunk B (04cf2a0) Application CQRS DTO/Input/Validator/Handler mirror PE Chunk B (default null backward compat)
  • Chunk C (e247b67) ContractWorkflowService refactor — load InnerSteps eager + reject clear N-stage rows + dept block split hasInnerSteps→N-stage / else→legacy 2-stage
  • Chunk D (7c0772a) ContractNStageApprovalTests 6 test mirror PE + helper SeedWorkflowDefinitionAsync 2 step adjacent + FakeChangelogService + FakeContractCodeGenerator stubs. 89→95 test pass
  • Chunk E SKIP — auto-bind no code change
  • Chunk F (current) FE WorkflowsPage Designer extend InnerSteps sub-section mirror PeWorkflowsPage + Docs

Verify: dotnet build pass + dotnet ef database update Mig 20 LocalDB applied + dotnet test 95 pass + npm build fe-admin pass.

Cumulative sau Session 13:

Trước S13 Sau S13
BE LOC ~15300 ~15700 (+400)
Migrations 19 20 (+Mig 20)
DB tables 56 57 (+1 WorkflowStepInnerSteps)
DB columns mới +1 (ContractDeptApproval.InnerStepId)
API endpoints ~134 ~134 (no change — auto-bind)
FE pages 32 32 (extend existing WorkflowsPage)
Tests 89 95 (+6 Contract N-stage)
Commits (after S12) +5 (A→F per-chunk, E skipped)

⚠️ CẢNH BÁO Session 14+

  1. Budget N-stage defer — Budget chưa có versioned WorkflowDefinition entity (hardcoded BudgetPolicy.Default). Để mirror N-stage Budget cần migration AddBudgetVersionedWorkflow trước (4 bảng BudgetWorkflowDefinitions/Steps/StepApprovers/InnerSteps + Budget.WorkflowDefinitionId?). Defer cho user quyết riêng — feature mở rộng module Budget lớn.

  2. PE-only deploy first — Contract N-stage giờ đã có nhưng PE đã được test trước. UAT cả 2 module song song để verify pattern reusable.

  3. Sample data N-stage — pending (Task 2). Khi user yêu cầu sẽ seed PositionLevel cho 30 demo user + 1 N-stage workflow definition active cho DuyetNcc + 1 cho ContractType (ví dụ HopDongThauPhu).

  4. TraLai BE wire — pending (Task 4). Phase TraLai = 98 enum đã có ở Session S11+++++++ FE side, nhưng BE workflow service chưa wire button "Trả lại" cho approver. Khi user yêu cầu sẽ thêm decision SendBack (or reuse Reject với target=TraLai) + Service logic + FE button trong Workflow Panel.

  5. schema-diagram §17 Mig 20 chưa update — defer cron audit 2026-06-01.

  6. Skill ef-core-migration Mig 20 row chưa add — defer cron audit.

  7. Skill contract-workflow N-stage cross-ref chưa add — defer cron audit.

TL;DR Session 12 (07/05 — N-stage workflow approval per phase × dept × cấp)

User yêu cầu mở rộng từ 2-stage Mig 16 sang N-stage cấu hình động: 1 phase (WorkflowStep cha) có thể cấu hình chuỗi InnerSteps con theo Department × PositionLevel sequential. Mỗi phòng có NV → PP → TP duyệt thứ tự, có thể bypass cùng dept khi role cao + CanBypassReview.

6 câu spec defaults chốt:

  • Q1 enum PositionLevel { NhanVien=1, PhoPhong=2, TruongPhong=3 } + User.PositionLevel int?
  • Q2 Sequential pure (Order asc, mỗi inner step = 1 cấp duyệt)
  • Q3 TP có CanBypassReview → skip NV+PP cùng dept (audit IsBypassed=true cho cấp dưới)
  • Q4 Smart reject về DangSoanThao + RejectedFromPhase + clear N-stage rows tại phase reject
  • Q5 PE first (Contract+Budget mirror sau khi PE stable UAT)
  • Q6(a) Designer UI 1 sub-section "Cấp duyệt nhỏ trong phòng" drag-list per step

6 chunk per-commit:

  • Chunk A (13ab533) Domain + Migration 18 — enum PositionLevel + entity InnerStep + ALTER User.PositionLevel + ALTER PEDeptApproval.InnerStepId + EF config Cascade/Restrict + 3-file rule
  • Chunk B (0e56bd0) Application CQRS DTO — extend PeWorkflowStepDto + InnerStepDto + Validator + Handler atomic batch + UserDto +PositionLevel + SetUserPositionLevelCommand (default null backward compat existing PeWorkflowAdminTests)
  • Chunk C (0c62e24) Service N-stage logic + Migration 19 filtered unique (drop UNIQUE Mig 16 → recreate WHERE InnerStepId IS NULL legacy + new WHERE InnerStepId IS NOT NULL N-stage). Refactor TransitionAsync: load InnerSteps eager + reject clear rows + hasInnerSteps→N-stage / else→legacy 2-stage. Match firstPending Order asc, exact match upsert 1 row, bypass batch upsert NV+PP+TP audit IsBypassed
  • Chunk D (3d76c6b) 6 test PE N-stage (FirstInner blocks / 3-level sequential pass / TP bypass skips / wrong dept 403 / reject clears rows / legacy fallback no inner). IdentityFixture extend +positionLevel arg. Helper SeedWorkflowDefinitionAsync 2 step adjacent. 83→89 test pass
  • Chunk E (83ffabd) API PATCH /users/{id}/position-level mirror SetBypassReview
  • Chunk F (current) FE-Admin types/users.ts +positionLevel + PositionLevel const + Label/Short. PeWorkflowsPage Designer + sub-section InnerSteps drag-list { Phòng × Cấp + required } + button "+ Thêm cấp duyệt" emerald + departmentsList query + payload include. UsersPage column "Cấp" badge + cycle button. KHÔNG đụng fe-user (admin-only). Docs/Skill update.

Verify: dotnet build pass + dotnet ef database update Mig 18+19 LocalDB applied + dotnet test 89 pass + npm build fe-admin pass.

Cumulative sau Session 12:

Trước S12 Sau S12
BE LOC ~14850 ~15300 (+450 — Domain enum + entity + EF config + Service N-stage + App CQRS)
Migrations 17 19 (+Mig 18 + Mig 19)
DB tables 55 56 (+1 PEWorkflowStepInnerSteps)
DB columns mới +2 (User.PositionLevel + PEDeptApproval.InnerStepId)
API endpoints ~133 ~134 (+1 PATCH /users/{id}/position-level)
FE pages 32 32 (no change — extend existing 2 page)
Tests 83 89 (+6 N-stage PE)
Commits (after S11+++) +6 (A→F per-chunk)

⚠️ CẢNH BÁO Session 13+

  1. N-stage chỉ áp PE first — Contract + Budget vẫn 2-stage Mig 16 cũ. Mirror sau khi UAT PE 2-3 tuần ổn (giữ pattern, lặp lại Domain entity + Service logic + Tests).

  2. User.PositionLevel chưa seed cho 30 demo user hiện có — admin phải set tay qua UsersPage cycle button. Hoặc seed migration sau (mapping Position text → PositionLevel int).

  3. Designer InnerSteps optional — workflow definition KHÔNG có InnerSteps fallback logic 2-stage Mig 16 cũ. Backward compat 100%. Khi muốn enable N-stage cho 1 phase: clone version + add inner steps + Save.

  4. Bypass kế thừa cùng dept only — TP với CanBypassReview chỉ skip NV+PP CÙNG dept của TP. KHÔNG cross-dept (NV.A bypass không skip NV.B).

  5. Reset N-stage rows khi reject — clear chỉ rows tại phase reject. Phase trung gian khác vẫn giữ approvals nếu có. Resume sẽ jump tới RejectedFromPhase + cần re-approve N-stage tại phase đó.

  6. Wrong-level error message — đã localize "phòng X cấp Y không khớp". UAT user cần feedback nếu confused.

  7. schema-diagram §15 Mig 18 + §16 Mig 19 chưa update — defer cho audit cron 2026-06-01 (small drift, không major).

  8. Skill ef-core-migration Mig 18+19 row chưa add — defer audit 2026-06-01.

TL;DR Session phase 2 (08/05 — B12-B14 polish iterate sau wrap-up 6e7a6db)

User UAT live tiếp tục, áp rule strict verify khi rename/remove (lesson hotfix CI 0ae3fe2).

  • B12 (378c993) PE detail polish 5 changes:
    • "Lưu" no-close (chỉ toast + invalidate sync, KHÔNG đóng workspace)
    • "Xóa phiếu" red bottom button CHỈ Bản nháp (soft-delete IsDeleted=true)
    • Bỏ header bar workspace mode "Sửa header"/"Xóa"/"Đóng" (chuyển hết xuống bottom action bar)
    • Section 4 column header: s.supplierName (master NCC name) thay displayName
    • Section 3 row chặn xóa NCC khi đã có quotes + tooltip "xóa báo giá trước"
  • B13 (e320027) InfoTab auto re-edit + pencil active visual:
    • useEffect watch [autoEdit, canEdit, ev.id, ...] → re-trigger edit khi pencil click phiếu khác
    • Sync values từ ev mới (tránh stale state)
    • Pencil "sáng lên" active state (bg-brand-100 + ring) khi editingRowId === p.id
  • B14 (d2306b8) QuoteDialog + winner column highlight + loading feedback:
    • Bỏ checkbox "Chọn NCC này cho hạng mục" (consolidate winner ở Section 2.a)
    • Winner column Section 4 matrix LUÔN xanh emerald (header prefix + cells full column)
    • QuoteDialog full overlay loading + spinner khi save có delay
    • NccSelectorRow inline spinner "Đang chọn NCC + sync cột giá Section 4…"

Verify: npm run build × 2 app pass · dotnet test 83 pass (KHÔNG regression). Push gitea OK.

⚠️ CẢNH BÁO Session 12+ (cập nhật)

Bổ sung từ wrap-up trước (xem TL;DR S10-11+++++++ phía dưới):

  1. isSelected per-quote BE field — vẫn còn trong API payload nhưng UI ẩn. Nếu sau này muốn cho user chọn winner per-item (khác với winner per-phiếu) → re-enable checkbox QuoteDialog. Hiện tại chỉ gửi existing?.isSelected ?? false để giữ legacy data nếu có.

  2. Section 4 cell winner highlight dùng ev.selectedSupplierId === s.supplierId (per-supplier check). Nếu BE workflow có multi-winner per-item (ít khả năng), cần điều chỉnh logic.

  3. Loading overlay QuoteDialog chỉ áp QuoteDialog. Các dialog khác (AddSupplierDialog/EditSupplierDialog/DetailDialog) chỉ có button text feedback. Nếu UAT thấy delay → mở rộng pattern overlay sang các dialog khác.

  4. InfoTab re-edit useEffect deps include 4 ev fields (tenGoiThau/diaDiem/moTa/paymentTerms). Có thể trigger re-set values nếu BE cập nhật ev (vd auto-save từ chỗ khác). Trade-off: chấp nhận để sync state. Nếu UAT thấy "values mất" → debug deps.

TL;DR Session S10-11+++++++ (07/05 — PE Workspace UX overhaul đầy đủ)

User UAT live mode iterate liên tục. Áp rule feedback_uat_skip_verify skip dotnet test sau mỗi chunk, push ngay. Lesson hotfix CI 0ae3fe2: rename/remove → BẮT BUỘC npm run build trước commit.

11 batch deliverable (23 commit total — xem STATUS Recently Done row đầu tiên cho narrative đầy đủ):

  • B1 PE Thao tác 2-panel workspace (S10)
  • B2 Migration 17 manual budget fields PE + HĐ (S11)
  • B3 BudgetFieldRow inline editor Section 2.b
  • B4 InfoTab inline edit Section 1 + PeListPanel pencil hover
  • B5 Workspace "new" sectioned create view 5 sections
  • B6 Danh sách view disable toàn bộ tương tác
  • B7 Workspace "new" lock Loại quy trình + payment preset Select
  • B8 PE display status meta (Bản nháp / Đã gửi duyệt / Trả lại / Đã duyệt / Từ chối)
  • B9 Phase TraLai = 98 + pencil always visible + edit gating
  • B10 Hotfix CI TS errors (forcedPhase rename + unused import)
  • B11 PE detail polish — NCC selector dropdown + Section 3 winner protect + Bottom action bar Lưu/Gửi Duyệt

Stats sau wrap-up

Trước S10 Sau S11+++++++
BE LOC ~14400 ~14850 (+450 — Mig 17 + Domain TraLai + App CQRS)
Migrations 16 17 (+1 Mig 17)
DB columns mới +4 (PE+HĐ × Name+Amount manual budget)
FE pages 31 32 (+1 Workspace 2-panel)
FE components mới +5 (PeListPanel, PeHeaderForm, PeWorkspaceCreateView, BudgetFieldRow, NccSelectorRow inline)
PE phase enum 8 9 (+TraLai = 98)
PE display status 5 (gom phase chi tiết)
Tests 83 83 (no test added — UAT iteration, áp rule §7 test-after defer)
Commits S10-S11+++++++ +23

⚠️ CẢNH BÁO Session 12+

  1. Workflow transition vào TraLai — Domain enum đã có (98), FE label/color/badge sẵn. NHƯNG chưa wire button "← Trả lại" trong PeWorkflowPanel approver view + workflow service BE chưa transition logic vào TraLai. Khi UAT cần → thêm: button "Trả lại" trong Chuyển tiếp (hoặc Reject Dialog), workflow rule Decision=3 (TraLai) set phase=TraLai + ghi RejectedFromPhase. FE đã ready accept hiển thị TraLai phase.

  2. "Đã gửi duyệt" multi-phase filter — display status "Đã gửi duyệt" gom 5 phase trung gian (ChoPurchasing/ChoDuAn/ChoCCM/ChoCEODuyetPA/ChoCEODuyetNCC). BE chưa hỗ trợ multi-phase param trong list endpoint → FE filter dropdown ẩn option này (chỉ exact-match Bản nháp/Đã duyệt/Từ chối). TODO: BE thêm ?phases=2,3,4,5,6 query param hoặc derive displayStatus field.

  3. feedback_uat_skip_verify exception — khi commit có rename/remove, BẮT BUỘC npm run build 1 lần trước push (lesson hotfix 0ae3fe2). Chỉ skip verify khi pure ADD (props, JSX, new file).

  4. Workspace + Danh sách + Duyệt matrix:

    • Workspace (Pe_*_Create): 2-panel, Section 5 disabled, edit only Bản nháp+Trả lại, pencil bright/disabled, bottom bar 2 nút Lưu/Gửi Duyệt
    • Danh sách (Pe_*_List): 3-panel, readOnly=true toàn bộ (no edit/transition)
    • Duyệt (Pe_*_Pending): 3-panel, PeDetailTabs readOnly + PeWorkflowPanel có Chuyển tiếp + Section 5 nhập opinion (chỗ này hiện vẫn disabled do code path cũ — verify nếu UAT user cần sign opinions ở đây)
  5. Mig 17 columns nullable — backward compat OK. Cả 2 (BudgetId + manual fields) cùng null acceptable. Validation BE chưa XOR.

  6. CreateContractFromEvaluation carry-forward manual fields PE→HĐ — đã wire. Verify UAT.

  7. CI deploy status — sau hotfix 0ae3fe2 các run từ commit 4c0625c trở đi sẽ xanh + deploy. User UAT live trên prod ngay sau push.

TL;DR Session 11++ housekeeping (07/05 — InfoTab inline edit + pencil hover)

User feedback after BudgetFieldRow: muốn thêm nút edit kế bên row trong Panel 1 list, click sáng nội dung Section 1 lên cho sửa header inline.

  • InfoTab inline edit — display mode + button "✎ Sửa" góc phải Section 1. Editing mode: card border brand-200 + 4 input (Tên / Dự án locked / Địa điểm / Mô tả / Payment) + Save (PUT /pe/:id) / Hủy.
  • PeListPanel pencil hover — icon absolute right-2 top-2 mỗi row, group-hover opacity-100. Click → callback onEditClick(id).
  • URL flag ?editHeader=1 — set bởi pencil click → autoEditHeader prop chain xuống PeDetailTabs → InfoTab tự open editing mode mount-time.
  • fe-admin Chunk 1 (5a89dd2) · fe-user mirror Chunk 2 (this).

Defer (chưa làm): Refactor workspace "new" mode wrap PeHeaderForm trong sectioned layout giống detail view (5 sections visible). PeHeaderForm hiện tại single-card đủ dùng — chỉ làm thêm khi user feedback rõ.

TL;DR Session 11+ housekeeping (07/05 — inline budget editor)

User feedback after Session 11: muốn toggle + 2 fields hiển thị trực tiếp trong Section 2 "b. Ngân sách" của PeDetailTabs, không chỉ ở "Sửa header" page. Empty values cứ empty.

  • BudgetFieldRow component (~125 LOC) thay FormRow tĩnh cũ ở b. Ngân sách. canEdit=!readOnly && isDraft. Save dùng existing PUT endpoint (KHÔNG cần BE thay đổi).
  • fe-admin Chunk 1 (commit 19712d8) · fe-user mirror Chunk 2 (commit d5c6f12).
  • Verify: 2 build pass + 0 TS error.

UX:

  • Workspace + Danh sách + isDraft → editable inline: toggle + Select OR 2 input + nút Lưu khi dirty
  • Duyệt + !isDraft → display only (link card / manual values / "—" empty)
  • Empty state: hiển thị "—" thay vì "(chưa link)" verbose (per user)

TL;DR Session 11 (07/05 — Migration 17 manual budget fields)

Output session 11 — UX improvement cho user nhập ngân sách không cần Budget entity approved:

  • Migration 17 AddManualBudgetFieldsToPeAndContract — 4 ALTER (PE + HĐ × BudgetManualName nvarchar(200) + BudgetManualAmount decimal(18,2)). Applied LocalDB OK. Mirror logic PE ↔ HĐ (Q3 user chốt).
  • BE Domain 2 entity (PE + Contract) +2 property + EF config HasMaxLength/HasPrecision. App CQRS Create/Update commands + DTO + Validator + diff log + carry-forward CreateContractFromEvaluation pe→contract.
  • FE Toggle checkbox "Nhập tay (không link)" cạnh Label Ngân sách trong 4 chỗ: PeHeaderForm (workspace + /new page wrap), ContractCreatePage NewForm + EditForm. Khi ON: hide Select, show 2 input field grid 2-col (Tên + Số tiền formatted VND). Khi OFF (default): Select Budget approved cũ. Auto-detect manual mode khi load existing có manual data + !budgetId. Display fallback ở PeDetailTabs Section 1 "b. Ngân sách" + Contract EditForm read-only branch.
  • 5 chunk per-commit (build + 83 test pass mỗi chunk): C1 Domain+Infra Migration 17 (ecd5f7e) · C2 App CQRS (0f7901c) · C3 FE-Admin (bab5031) · C4 FE-User mirror (14f8d9d) · C5 Docs (current).

Validation Q2 chốt: cả BudgetId + manual fields có thể cùng null (PE/HĐ chưa có ngân sách gì). KHÔNG XOR enforce — BE prefer link nếu có (Phase=DaDuyet guarantee), manual fallback only.

KHÔNG đụng: Budget entity / Phase=DaDuyet validation (giữ invariant "PE/HĐ link Budget approved only"). Manual fields chỉ là note/display, KHÔNG join với Budget.Details cho per-row comparison ở PE matrix Section 4 (cột "So với ngân sách" vẫn require ev.budgetId — không có detail rows để compare).

⚠️ CẢNH BÁO session tiếp (Session 12+)

  1. UAT manual budget flow — toggle ON/OFF + save + reload + verify auto-detect mode. Đặc biệt edit existing PE/HĐ có manual data → toggle phải auto-checked. Edit existing có Budget link → toggle auto-unchecked.
  2. Section 4 PE matrix "So với ngân sách" vẫn require Budget link — manual amount KHÔNG render comparison column. Document giới hạn này nếu UAT thắc mắc.
  3. docs/database/schema-diagram.md chưa cập nhật count 16→17 migration + 4 column mới. Defer cho audit định kỳ 2026-06-01 (per §6.4 cron) — nhỏ enough không phải selective rewrite.
  4. CreateContractFromEvaluation đã carry forward manual fields PE→Contract. Verify khi UAT: PE có manual budget → tạo HĐ từ phiếu → HĐ inherit luôn.
  5. No new test added (rule §7 — feature mới = test-after, soak UAT 2-3 tuần ổn → viết happy path).
  6. Schema fields nullable — không phá HĐ/PE cũ (legacy null OK). Backward compatible.

TL;DR Session 10 (07/05 — PE workspace 2-panel)

Output session 10 — restructure leaf "Thao tác" PE từ page Create header riêng sang workspace 2-panel mirror HĐ Thầu phụ pattern:

  • Spec chốt 5 câu trước code — Q1 Panel 2 chỉ data entry (KHÔNG Workflow/Approvals/History); Panel 1 pure picker (no inline edit/delete); Q2 mirror HĐ Thầu phụ (sticky "+ Thêm mới" + new→edit Panel 2); Q3 leaf "Danh sách" + "Duyệt" giữ 3-panel hiện tại; Q4 route mới /workspace; Q5 Section 5 Ý kiến 4PB disable trong workspace (nhập khi duyệt).
  • Chunk 1 fe-admin (commit ee0d360) — 3 file mới: PeListPanel.tsx (~180 LOC pure picker reuse-able + sticky "+ Thêm mới"), PeHeaderForm.tsx (~210 LOC extract header form từ CreatePage), PurchaseEvaluationWorkspacePage.tsx (~120 LOC 2-panel [320px_1fr]). 3 file sửa: PeDetailTabs.tsx thêm prop mode?: 'detail' \| 'workspace' + Section 5 hint amber khi workspace + force opinionsReadOnly. Layout.tsx resolver Pe_*_Create/workspace?type=N. App.tsx route mới.
  • Chunk 2 fe-user mirror (commit ecf3c59) — 6 file y hệt content (rule §3.9 duplicate có chủ đích).
  • Verify: 2 build pass + dotnet test 83 vẫn pass mỗi chunk.

KHÔNG đụng BE / migration / schema / endpoint. Route /new cũ giữ tồn tại cho deep-link "Sửa header" button.

⚠️ CẢNH BÁO session tiếp (Session 11+)

  1. UAT live workspace với anh Kiệt + 2-3 user — feature mới UX khác (page Create cũ thành Dialog implicit qua sticky button + transition new→edit auto).
  2. Section 5 Ý kiến 4PB hint banner amber ở workspace mode chỉ là text gợi ý — KHÔNG block. User vẫn thấy existing opinions read-only. Nếu UAT thấy confused → có thể đổi thành collapsible section.
  3. Mobile: workspace KHÔNG có fallback — màn hình <lg (< 1024px) Panel 2 ẩn (lg:block). Cần test mobile redirect → /purchase-evaluations/:id nếu UAT phát hiện. Hiện chỉ admin desktop dùng workspace.
  4. Pe_*_Pending vẫn /purchase-evaluations?pendingMe=1 — leaf "Duyệt" 3-panel (giữ Panel 3 Workflow + readOnly Section 5 cho phép sign). UAT verify Drafter trình → TPB duyệt thấy Section 5 enable đúng.
  5. PE WorkflowPanel duplicate ở 2 chỗ — leaf "Danh sách" + "Duyệt" Panel 3 (3-panel). KHÔNG có ở "Thao tác" workspace. Verify route resolver active state highlight đúng (queryMatches helper từ session 3).
  6. Sửa header trong workspace — vẫn navigate sang /new?id= (button "Sửa header" trong PeDetailTabs). Chưa wire inline edit qua Dialog. Nếu UAT yêu cầu → thêm prop onEditHeader cho PeDetailTabs trigger Dialog reuse PeHeaderForm.

Housekeeping today (sau Session 9)

  • Audit định kỳ 2026-05 — combined skill + doc drift (commit 7dc0233). 5 drift patch (count 77→83 + 52→55 bảng), 1 skill content patch (contract-workflow Phase 9 cross-ref). KHÔNG tạo skill mới. Log docs/changelog/skill-audit-2026-05.md.
  • Optional polish — fe-user Inbox PE section (commit 332a90f). useQuery /purchase-evaluations/inbox + Panel 1 chia 2 section sticky header (HĐ + PE). PE click → navigate page riêng.
  • User Manual 7 file rewrite compact (commit 16c2c9c). User feedback "ko cần quá đầy đủ chi tiết, cho end-user họ làm". Bỏ field validation table + error troubleshoot table + FAQ chi tiết. Giữ tổng quan ngắn + numbered steps đơn giản. ~86 KB / 7 file (cũ ~123 KB ↓30%). Setup package.json + npm install docx@9.5.0 + npm run gen:all. Lần sau sửa: edit _gen-*.js → run gen:all.

Cron audit định kỳ: Claude SDK CronCreate auto-expire 7 days → KHÔNG fit monthly cadence. User cần setup OS Task Scheduler .bat script ping API hoặc manual trigger mỗi đầu tháng. Lần kế: 2026-06-01.

User Manual style rule (mới chốt session này): end-user docs BỎ field/error tables, FAQ chi tiết, phím tắt — GIỮ tổng quan ngắn + numbered steps + note/warn/tip critical. Phân biệt với rule §6.5 (KHÔNG cắt narrative) — đây là rule cho audience end-user, không phải agent dev đọc rationale.

TL;DR Session 9 (04/05 — Chunk E-bis sau Session 8)

Output session 9 — đóng tất cả Chunk E-bis defer từ session 8:

  • FE PE WorkflowPanel — Section "Tiến trình duyệt 2-cấp phòng ban" group by phase × dept, highlight amber chờ TPB confirm, badge fuchsia bypass (cả fe-admin + fe-user).
  • FE UsersPage UserManager — Column "Bypass" + button ShieldCheck toggle CanBypassReview, badge fuchsia khi enabled. UserDto thêm field.
  • HĐ 2-stage logicContractWorkflowService thêm UserManager DI + mirror logic từ PE service. ContractDepartmentApprovalFeatures.cs List query. Endpoint GET /contracts/{id}/department-approvals. FE WorkflowHistoryPanel section mới.
  • Budget 2-stage logicTransitionBudgetCommandHandler thêm INotificationService + IDateTime DI + 2-stage. BudgetDepartmentApprovalFeatures.cs + endpoint + FE BudgetWorkflowPanel section.
  • 6 test PE 2-stageIdentityFixture setup full Identity stack (DbContext SQLite + AddIdentityCore + AddRoles) reusable. 6 scenario: NV_Review_Blocks / TPB_Confirm_Allows / NV_Bypass / Admin_Skip / Reject_Sets / Resume_Jumps_Back.
  • Verify: Build pass + 83 test pass mỗi commit (54 Domain + 29 Infra: 17 codegen + 6 PE WF Application + 6 PE 2-stage).
  • 5 commit pushed Gitea (E2 → E6).

⚠️ CẢNH BÁO session tiếp (Session 10+)

  1. UAT live ngay với anh Kiệt + 2-3 user — feature 2-stage đầy đủ cả 3 module + UX.
  2. Tests Contract + Budget 2-stage skipped — logic identical PE (cùng pattern, cùng entity shape). Pattern PeTwoStageApprovalTests reusable nếu UAT phát hiện regression riêng.
  3. Bypass toggle audit — chưa log Changelog khi admin toggle CanBypassReview. Audit qua Identity standard column UpdatedAt only. Có thể cần thêm audit row riêng nếu UAT yêu cầu.
  4. Notify TPB cùng dept dùng UserManager.GetRolesAsync filter DeptManager — verify production có user role DeptManager đúng (data already seeded).
  5. fe-user KHÔNG có UsersPage — admin-only function. Bypass toggle chỉ ở fe-admin.
  6. 3 endpoint mới List dept-approvals PE/HĐ/Budget cùng pattern, reuse policy authz *Read.
  7. Cron audit định kỳ 2026-05-01 vẫn EMPTY (No scheduled jobs). Có thể recreate khi user yêu cầu.

TL;DR Session 8 (04/05 — code lớn, 5 commit per-chunk)

TL;DR Session 8 (04/05 — code lớn, 5 commit per-chunk)

Output session 8 — đóng bug anh Kiệt + thêm 3 ràng buộc workflow:

  • Migration 16 AddTwoStageDeptApprovalAndSmartReject — 4 ALTER (3 RejectedFromPhase int + Users.CanBypassReview bit) + 3 CREATE TABLE (Contract/PE/Budget DepartmentApprovals UNIQUE (TargetId, Phase, Dept, Stage)) + 12 indexes.
  • Lock edit 17 handler thêm guard Phase != DangSoanThao (Contract Detail × 15 qua helper, PE Detail × 5 qua helper mới, Budget Detail × 3 inline).
  • Smart reject + Resume 3 module — Reject = lưu phase nguồn + force về DangSoanThao. Resume = jump straight tới phase đã reject (skip phase trung gian, bypass policy guard).
  • PE 2-stage logic trong PurchaseEvaluationWorkflowService — TPB/CanBypass → Confirm; NV → Review only, BLOCK transition cho đến khi TPB confirm.
  • 3 endpoint mới: GET /pe/{id}/department-approvals (List), PATCH /users/{id}/bypass-review (toggle), Notify TPB cùng dept khi NV review.
  • Verify: Build + 77 test pass mỗi commit. Migration applied LocalDB OK. Schema verified.
  • 6 commit pushed (2 docs S7 + 5 code S8).

⚠️ CẢNH BÁO session tiếp (Session 9+)

  1. Bug fix anh Kiệt chỉ áp PE workflow. HĐ + Budget 2-stage scope DEFER cho khi UAT PE OK.
  2. FE Workflow Panel chưa update — workflow vẫn block đúng (BE), nhưng UX chưa hiển thị 2-stage progress. User test sẽ thấy phase không đổi mà không hiểu tại sao "stuck". Phải UAT với hint cho user trước khi code FE.
  3. FE UserManager toggle CanBypassReview chưa làm — tạm thời SET qua HTTP PATCH:
    PATCH /api/users/{userId}/bypass-review
    Authorization: Bearer <admin>
    { "canBypassReview": true }
    
  4. Test thực tế bug fix flow:
    • Login phuong.nguyen (NV.PRO, role=Procurement, DeptId=PRO) tạo phiếu PE type A
    • Trình DangSoanThao → ChoPurchasing
    • phuong.nguyen click Duyệt phase ChoPurchasing → expect: phase KHÔNG đổi, có row Stage=Review
    • tra.bui (TPB.PRO, role=DeptManager) click Duyệt → expect: phase chuyển ChoCCM
  5. Notify TPB cùng dept dùng UserManager.GetRolesAsync filter DeptManager. Best effort, fail OK.
  6. Cron audit định kỳ 2026-05-01 đã quá hạn 3 ngày, vẫn EMPTY runtime. Cần manual trigger.
  7. Smart reject test: Reject phase ChoCCM → DangSoanThao + RejectedFromPhase=ChoCCM. Drafter sửa Detail + trình lại → jump straight tới ChoCCM (skip ChoPurchasing).
  8. Lock edit test: HĐ ở Phase=DangGopY → cố sửa Detail → expect 409 Conflict "đã trình duyệt, không thể chỉnh sửa".

TL;DR Session 6 (30/04 — không code, chỉ docs)

Output session 6 — pure docs work, không thay đổi code/test:

  • Compact 3 file core: STATUS -27%, HANDOFF -32%, migration-todos -35% (-288 dòng tổng). Archive 51 row Recently Done Phase 0-7 → changelog/recently-done-archive-2026-04.md.
  • Refresh 3 skill stale: form-engine (Phase 2 MVP → Tier 3 feature-complete), permission-matrix (12 menu → ~60 + inheritance roots), ef-core-migration (24 DbSet → 52 bảng).
  • Rule mới §7 Timing test: 1 bảng 5-row compact — feature mới = test-after, bug = test-before, critical algorithm = test-before, spec change = update test cũ, skip 5 loại.
  • Rule mới §6.4 Audit + compact MD định kỳ: cadence + checklist + anti-pattern. Cross-ref §9.4 skill audit. KHÔNG rewrite toàn bộ.
  • 77 test vẫn pass (Domain 54 + Infra 23). 0 thay đổi code.
  • Cron 2026-05-01 fire mai — combined audit (skill + doc drift) theo §6.4 + §9.4.

Session 5 (29/04) đóng gần hết feature gap + bật test gate:

🎯 Headline outcomes

  • Budget feature-complete — FE 3-panel pages cả 2 app, BE đã sẵn từ session 4
  • PE/HD ↔ Budget integration — form select Budget filter Phase=DaDuyet + Project, cột "So với ngân sách" ở PE matrix với delta indicator
  • PE Detail UI restructure match form chính thức PHIẾU TRÌNH KÝ (4 section đánh số)
  • PE Workflow Designer admin UI /system/pe-workflows/:typeCode versioned với clone/edit/+Role/+User
  • Ý kiến 4 phòng ban — migration 15 + section sign-off 2x2 grid (Phê duyệt/CCM/MuaHàng/SM-PM)
  • Tests Phase 1-2 — 71 unit test pass + CI gate fail-fast (Domain policy + Infra code generator)

Session 4 (28/04) đã có:

A. Module Ngân sách (Budget) BE — migration 14, +4 bảng, +11 endpoint

  • 4 entity: Budget (Header) + BudgetDetail (flat row) + BudgetApproval (history) + BudgetChangelog (audit log).
  • Enum BudgetPhase 5 state (DangSoanThao→ChoCCM→ChoCEO→DaDuyet + TuChoi).
  • BudgetPolicy.Default hardcoded simple 3-step (Drafter→CCM→CEO) — chưa versioned (TODO khi user cần admin config UI).
  • NS-YYYYMM-XXXX Random.Shared (chưa atomic — TODO khi format chính thức).
  • Link nullable: Contract.BudgetId? + PE.BudgetId? đã có FK + index, FE chưa wire form.
  • Menu seed Budgets root + 3 leaf (Bg_List/Bg_Create/Bg_Pending) order=27 icon Wallet.
  • Application: 11 CQRS handler ~340 LOC (Create/UpdateDraft/Transition/List/GetDetail/Delete + Detail CRUD auto-recompute TongNganSach + ListChangelogs).
  • Api: BudgetsController 11 endpoint REST.
  • FE chưa làm — Priority 0 session 5.

B. 14 demo user Solutions thật

  • PRO 5 (TPB tra.bui + 4 NV) + CCM 7 (TPB ngocanh.huynh + 6 NV) + ISO 1 (chau.le) + CEO 1 (huy.duong).
  • Pwd User@123456. Reconcile pattern (gotcha #38 4-field rename).
  • Tổng 30 user (16 sample cũ giữ + 14 Solutions thật mới).

Tổng cumulative: 52 DB tables, ~128 endpoints, 15 migrations, 41 gotchas (+3 CI fixes), 77 unit test (+6 PE WF Application), 11+ commit session 5 push lên Gitea.

🆕 CI/CD optimize (29/04 tối)

  • Manual checkout bypass github.com — fix #108/#109 transient TCP timeout 21s. Run #110 pass.
  • Path filter docs-only skippaths-ignore trong on:push, commit MD-only KHÔNG trigger CI (verify 512880c → no run #113).
  • ⏸️ npm junction cache — thử ở #111 fail tsc not found, rollback. Cần debug session sau (gotcha #40 doc rồi).
  • Tests Phase 3 mini (PE Workflow Designer) — 6 test versioning logic. Total 77.

⚠️ CẢNH BÁO session tiếp (Session 7+)

  1. CI test gate active — code → dotnet test SolutionErp.slnx local → commit → push. Test fail = NO deploy. Workflow user §7 rules.md.
  2. Timing test rule live (§7): feature mới = test-after, bug = test-before BẮT BUỘC, critical algorithm = test-before merge, spec change = update test cũ + code chung commit.
  3. Doc audit cadence live (§6.4): cuối phase compact, đầu tháng cron audit, KHÔNG rewrite toàn bộ. Cron fire mai (2026-05-01) — combined skill + doc drift theo checklist.
  4. Login email admin@solutionerp.local / Admin@123456 — domain default chưa đổi sang @solutions.com.vn (chỉ demo user rebrand).
  5. Chưa xóa binding cũ .huypham.vn — vẫn fallback. Sau verify stable → .\migrate-domains.ps1 -RemoveOld -SkipCert trên VPS.
  6. win-acme scheduled task "unhealthy" — fix trước cert expire 2026-06-18.
  7. Export phiếu PDF/Excel PE — pending vô thời hạn (user nói không quan trọng).
  8. 3 feature mới chưa test (PE Opinion Upsert / Budget validate / Contract BudgetId carry) — đợi UAT phát sinh bug → áp rule §7 (regression test before fix). Hoặc soak 2-3 tuần ổn → viết happy path.
  9. G-084: VPS shared VietReport — mọi reverse proxy mới phải 127.0.0.1 + bind loopback IPv4 explicit.

Skills (.claude/skills/) — 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 cron: 0 9 1 * * (9:00 AM ngày 1 mỗi tháng). Workflow: docs/rules.md §9.4. Lần kế: 2026-05-01.

Quy tắc: KHÔNG bulk-clone repo skill 3rd party. Chỉ skill PROJECT-SPECIFIC. Đầy đủ: docs/rules.md §9.

Ở đâu rồi?

Phase Trạng thái
0 Draft Done
1 Alpha Core (foundation + đợt 2 CRUD + Permission) Done
2 Form Engine MVP + iter 2 (upload UI + .doc auto-convert + PDF export) Done
3 Workflow MVP (9 phase + code gen) + iter 2 (SLA job + attachment + notify) Done
4 Report MVP (Dashboard + Excel) + user-specific dashboard Done
5 Prep + 5.1 Security + Users Mgmt Done
5 Deploy prod (3 domain HTTPS live) Done
Tier 3 (Attach + Realtime + Form builder + PDF + Versioned WF + Nested menu + Permission 3-panel) Done
Skill governance (6 skill project-level + audit cron 1/tháng) Done
3-panel List/Inbox/Thao tác + sidebar accordion + UserDashboard Done
4-bảng data model (Header + 7 Details + Workflow + Changelog audit) Done
Mã HĐ gen tại Create + backfill legacy Done
4 master catalogs (Units/Materials/Services/WorkItems) + datalist autocomplete Done
Roles VN labels (ShortName) + Users dept/position + 13 demo users Done
RolesPage CRUD /system/roles + custom role admin Done
7 demo HĐ varied phases + details + comments + approvals workflow Done
User-kind approver runtime guard + Warning 20% SLA Done
Edit detail row inline (7 typed Update commands + EditRowDialog) Done
Master expand 15 NCC + 8 Project + backfill demo HĐ diverse Done
Deps audit script (scripts/deps-audit.ps1) Done
Module Duyệt NCC (tiền-HĐ) E2E — 10 bảng + 2 workflow + Kế thừa HĐ Done
PE polish iter 2 — flat layout + per-NCC attachments + readOnly Duyệt + email rebrand Done
Domain rebrand huypham.vn → solutions.com.vn — 3 subdomain + cert + CORS + FE bundle Done
Module Ngân sách BE + 30 user — 4 bảng + 11 endpoint + workflow simple Done (S4)
Module Ngân sách FE — 3-panel pages + Detail tabs + Workflow Panel cả 2 app Done (S5)
PE/Contract → Budget integration — form Select + cột "So với ngân sách" PE matrix Done (S5)
PE Workflow designer admin UI /system/pe-workflows/:typeCode Done (S5)
Ý kiến 4 phòng ban PE — migration 15 + section sign-off 2x2 grid Done (S5)
Tests Phase 1-2 + CI gate — 71 test (Domain policy + Infra code generator) Done (S5)
CI manual checkout bypass github.com + Path filter docs-only skip Done (S5)
Tests Phase 3 mini — 6 test PE Workflow Designer versioning (total 77) Done (S5)
Export phiếu PDF/Excel PEIDocumentConverter + template 📝 Pending (không quan trọng)
Tests Phase 3 full — Application handlers (UpsertOpinion + Budget link validation, cần UserManager setup) 📝 Pending
Tests Phase 4-5 — API smoke + FE Vitest 📝 Pending (làm khi cần)
npm cache CI optimize (debug junction Move-Item issue #40) 📝 Pending
9+ Post-launch (E-signature, Bravo/SAP, Mobile, AI) 📝 Future

Run nhanh

# Terminal 1 — API (auto seed 12 role + 9 dept + 5 supplier + 3 project + 8 template + 7 workflow definition + 28 ContractType menu + 7 workflow menu)
dotnet run --project src\Backend\SolutionErp.Api

# Terminal 2 — Admin FE
cd fe-admin && npm run dev  # → http://localhost:8082

# Terminal 3 — User FE
cd fe-user && npm run dev   # → http://localhost:8080

Login: admin@solutionerp.local / Admin@123456

Quick sanity-check

Admin (:8082):

  • /dashboard → "Của tôi" row 4 card + KPI cards + charts
  • /contracts → list toàn bộ, filter phase/supplier/project
  • /contracts/new?type=5 → tạo HĐ Mua bán, pre-select type từ URL
  • /contracts/{id} → timeline + action dialog + attachments drag-drop + WorkflowSummaryCard
  • /system/workflows → 7-card landing (Thầu phụ/Giao khoán/NCC/Dịch vụ/Mua bán/NguyenTacNcc/NguyenTacDv)
  • /system/workflows/MuaBan → DefinitionCard active + history + "Tạo phiên bản mới" modal với Steps + Approvers (+Role / +User)
  • /system/permissions → 3-panel layout (Role list | Menu×CRUD matrix | Granted stats)
  • /system/users → Users CRUD + assign roles
  • /forms → upload .docx/.xlsx + render dialog Form↔JSON + Tải PDF

User (:8080):

  • /inbox?type=5 → HĐ Mua bán chờ role mình
  • /my-contracts?type=2 → HĐ Thầu phụ của tôi
  • /contracts/new?type=3 → tạo HĐ NCC
  • Sidebar nested: 📄 Hợp đồng → expand 7 type → expand "HĐ Mua bán" → Danh sách / Thao tác / Duyệt

Realtime check:

  • Login 2 tab (admin + user) → user tạo comment / transition → admin nhận toast + bell +1

Cần làm kế tiếp

🔥 Priority 0 — Session 6 (Ops + UAT focus)

Đa số feature gap đã đóng. Còn lại chủ yếu Ops/UAT + optional polish.

A. Hard blockers (chờ user / ops):

  1. UAT thật 1 tuần với 2-3 user — Drafter (QS/NV.PB) + CCM + BOD ghi bug/friction
  2. SMTP config → bật Email outbox (BLOCKED chờ user cấp host/user/pass)
  3. Rotate credentials — admin + 30 demo + SA + vrapp + JWT secret
  4. Schedule SQL backup dailyscripts/backup-sql.ps1 chưa schedule Task Scheduler

B. PE feature gap còn lại:

  • Export phiếu PDF/Excel PE — pending (user nói không quan trọng lắm). Khi cần: tái dùng IDocumentConverter + template PE-TrinhDuyet.docx.

C. Optional polish (làm khi UAT phát sinh):

  • Budget MaNganSach atomic sequence — chốt format → migration AddBudgetCodeSequences mirror Contract/PE
  • 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):

  • Phase 3 — Application handler tests (CQRS + EF InMemory) ~15 test
  • Phase 4 — API smoke tests (WebApplicationFactory) ~7 test
  • Phase 5 — FE Vitest cho lib utility (queryMatches, fmtMoney) ~10 test

Đã xong session 5 (check STATUS Recently Done):

  • Module Ngân sách FE — 3-panel pages cả 2 app
  • PE/Contract → Budget integration + cột "So với ngân sách"
  • PE Detail UI restructure 4 section đánh số
  • PE Workflow Designer admin UI
  • Ý kiến 4 phòng ban (migration 15)
  • Tests Phase 1-2 + CI gate (71 test)

A. Hard blockers (chờ user / ops)

  1. UAT thật 1 tuần với 2-3 user — hard requirement từ roadmap. Kiến nghị:
    • User A: Drafter (QS/NV.PB) — tạo 3 HĐ mỗi type, đi hết 9 phase
    • User B: CCM — duyệt phase 6
    • User C: BOD — duyệt phase 7
    • Ghi bug / friction / đề xuất → backlog iter 2
  2. SMTP config để bật Email outbox:
    "Email": {
      "Host": "smtp.gmail.com",
      "Port": 587,
      "Username": "...",
      "Password": "...",
      "From": "noreply@solutionerp.local"
    }
    
    Khi có → thêm MailKit, IEmailSender, hook vào NotificationService.CreateAsync ngay trước khi enqueue realtime push.
  3. Rotate credentials — SA SQL password, vrapp password, JWT secret prod, Gitea runner registration token
  4. Schedule SQL backupschtasks /create /tn "SolutionErp Backup" /tr "powershell -File C:\...\scripts\backup-sql.ps1" /sc DAILY /st 03:00

A1. Định kỳ — Skill audit

Cadence: Mỗi đầu tháng (4 tuần). Lần audit kế tiếp: 2026-05-01.

Workflow xem docs/rules.md §9.4. Tóm tắt:

  1. Cross-check 6 skill hiện có với STATUS / gotchas / migration-todos
  2. Check repo nguồn 3rd party (alirezarezvani/claude-skills) có gì mới
  3. Update / archive / add skill nếu cần
  4. Log vào docs/changelog/skill-audit-2026-05.md

Trigger: user nói "audit skill" hoặc tự chạy đầu Phase mới.

B. Polish iterations (optional — khi UAT phát sinh)

  • Roles CRUD — admin tạo custom role ngoài 12 hardcoded (Domain.Identity.AppRoles)
  • User-kind approver runtime — data model WorkflowStepApprover.Kind=User + AssignmentValue=userId đã có, chỉ cần:
    // ContractWorkflowService.TransitionAsync (bổ sung):
    var userApprovers = step.Approvers.Where(a => a.Kind == ApproverKind.User)
                                      .Select(a => Guid.Parse(a.AssignmentValue));
    if (userApprovers.Any() && !userApprovers.Contains(actorUserId))
        throw new ForbiddenException();
    
  • Grant Workflows.Read cho non-admin role trong PermissionsPage → menu Wf_* auto-visible (inheritance đã có)
  • Warning notification 20% SLA — job emit khi SlaDeadline - now < sla * 0.2 && !SlaWarningSent, set flag
  • Reject → DangSoanThao E2E test với 3 role khác nhau
  • Deps scan CIdotnet list package --vulnerable + npm audit --audit-level=high

C. Non-goals / parked

  • E-signature (VNPT CA / FPT CA) — Phase 6
  • Bravo/SAP import NCC — Phase 6
  • Mobile app — Phase 6
  • AI OCR scan HĐ — Phase 6+

Lưu ý kỹ thuật quan trọng

Đọc gotchas.md (41 bẫy) trước khi:

  • Thêm package mới → .NET 10 compat (MediatR 14 fail → dùng 12.4.1)
  • Debug TS enum error → dùng const-object pattern (erasableSyntaxOnly)
  • Expression tree lỗi → tách switch ra ngoài LINQ
  • Deploy Windows Feature (WebSockets, etc.) → unlock section ở applicationHost (gotcha #25)
  • Workflow transition 403 → check Contract.WorkflowDefinitionId pin đúng không
  • Migration lỗi → 3 file đầy đủ (Designer + Migration + Snapshot)

Versioned workflow — quick ref

Contract.WorkflowDefinitionId pin tại Create. ContractWorkflowService.LoadAsync resolve order: pinned def → admin override → hardcoded Registry.For(type). Invariant: HĐ cũ giữ policy cũ qua FK Restrict. Detail: workflow-contract.md §7bis.

File đang active

Cấu trúc thư mục + file map: PROJECT-MAP.md. Git state: git log --oneline -10.

Credentials + URLs

admin@solutionerp.local / Admin@123456    ← Admin (QTV)

Demo users — 30 user (User@123456):

  ── 16 sample @solutionerp.local (giữ cho test legacy) ──
  bod.huynh, bod.le              Tổng GĐ + Phó GĐ NĐUQ
  pm.nguyen                      GĐ Dự án FLOCK 01 (PM)
  ccm.tran, pro.pham, fin.do     TPB CCM/PRO/FIN
  act.vu, equ.bui, hra.dang      Kế toán trưởng / TPB EQU / TPB HRA
  qs.hoang, qs.ngo               QS công trường (NV.PB)
  nv.cao, nv.dinh, nv.truong     NV Cung ứng/Tài chính/CCM (NV.PB)
  bod.tran, pm.le                Bonus NĐUQ + PM thứ 2

  ── 14 Solutions thật @solutions.com.vn (session 4) ──
  PRO 5:  tra.bui (TPB) + phuong.nguyen, binh.lethanh, danh.huynh, dat.tran (NV)
  CCM 7:  ngocanh.huynh (TPB) + ha.dao, cuong.do, long.le, ha.nguyen,
                                dung.nguyen, anh.nguyen (NV)
  ISO 1:  chau.le
  CEO 1:  huy.duong

⚠ Rotate ALL passwords trước UAT thật

Đánh giá nhanh

Tốt:

  • 3 domain HTTPS prod live, CI/CD xanh
  • Tier 3 feature-complete: attachment, realtime, form builder (upload + DynamicForm + PDF), versioned workflow (admin-configurable per ContractType, pin per contract), nested menu per type, 3-panel permissions
  • Clean-arch 3-project split đúng cho 2 cross-cutting service (realtime + document-converter)
  • 26 gotchas tích lũy, 8 session log, 40 docs — agent onboard nhanh
  • Invariant critical: "HĐ cũ giữ quy trình cũ" guaranteed by pinning (reference-based immutability, không snapshot copy)

Rủi ro còn:

  • UAT thật chưa chạy → có thể phát hiện edge case missing
  • SMTP chưa có → notification chỉ in-app (toast + bell), không email
  • User-kind approver chưa enable guard runtime (designer cho pick, nhưng transition dùng Role fall-back)
  • Credentials chưa rotate
  • SQL backup chưa schedule Task Scheduler