Sau S21 turn 1 chốt cicd-monitor, bro clarify 5 dự án future > 1M MD tokens → discussion deep ~15 turn về RAG infrastructure. Em main solo (no SOLUTION_ERP sub-agent spawn), delegate claude-code-guide × 2 research Anthropic + community practice. Quyết định chốt: - Cách A defensive (giữ blanket 120K em main + RAG retrieve supplement) - Bỏ Cách B aggressive (cắt 60-70% blanket) — vi phạm priority em main control flow strong - Industry-validated cross 4 Anthropic blog + 5 community tools (Cursor/Continue/Cline/Aider all hybrid) - 3-layer pattern Phase 1-3 incremental rollout (vector → +BM25 → +reranking, recall ~70% → ~92%) - Stack: Voyage-3-large + Qdrant local + FastMCP Python + Streamlit dashboard Multi-agent cost reality clarify (post-S21 t2): - Em main blanket: ~120K - 4 sub-agents spawn cumulative: ~400K - Total billed heavy session: ~560K Cách A vs ~700K lazy - Saving -20% từ multi-agent shared cache 70-90% - Anthropic acknowledge 8-10× multiplier multi-agent Files updated: - docs/STATUS.md (Last updated S21 turn 2 + Recently Done row top) - docs/HANDOFF.md (TL;DR Session 21 turn 2 section + Last updated) - docs/rag-setup-plan.md (+Section 13 multi-agent cost reality + Section 14 3-layer hybrid Phase 1-3, +355 LOC) - docs/changelog/sessions/2026-05-12-1800-s21-turn2-rag-planning.md (new session log) Memory user-level update (outside repo, separate update): - feedback_rag_hybrid_pattern.md (NEW cross-project pattern reusable) - MEMORY.md index (+1 entry pointer) Plan I NEW deferred — trigger bro confirm 5 dự án path + stack + pilot + Voyage API + disk cleanup → dedicated session 10-14h weekend (per feedback_drastic_refactor_scope rule). Stats: - 17 memory entries (+1 RAG hybrid) - 1 plan file rag-setup-plan.md (1500 LOC final) - 4 sub-agents seeds-only unchanged - 81 test unchanged - 4 commits S21 cumulative (f1c61c9+3a34831+1f8e9af+ this) CI skip per path filter (all .md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1707 lines
113 KiB
Markdown
1707 lines
113 KiB
Markdown
# HANDOFF — Brief 5 phút cho session tiếp theo
|
||
|
||
**Last updated:** 2026-05-12 (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 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** | (`9dee00d` → `ae1814c`) |
|
||
|
||
### 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:
|
||
```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 `9dee00d`→`2bba851`→`f2f01f4`→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`:**
|
||
|
||
```csharp
|
||
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` cũ
|
||
- `✓` 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 <name> 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:
|
||
```csharp
|
||
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:
|
||
```tsx
|
||
{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 — <ApproverFullName>"
|
||
- Badge amber "⚠ Admin <SignedByFullName> 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 `ApprovalWorkflowsV2Page` có `TYPE_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`:
|
||
```sql
|
||
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_<TypeCode> 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<WorkflowStepInnerStep> × 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 → <label phase>" 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_DangSoanThao` → `Reject_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_Design` — `dotnet 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):
|
||
|
||
8. **`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ó.
|
||
|
||
9. **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.
|
||
|
||
10. **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.
|
||
|
||
11. **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`](../../../../Users/pqhuy/.claude/projects/D--Dropbox-CONG-VIEC-SOLUTION/memory/feedback_uat_skip_verify.md) 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 logic** — `ContractWorkflowService` 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 logic** — `TransitionBudgetCommandHandler` thêm INotificationService + IDateTime DI + 2-stage. `BudgetDepartmentApprovalFeatures.cs` + endpoint + FE `BudgetWorkflowPanel` section.
|
||
- ✅ **6 test PE 2-stage** — `IdentityFixture` setup full Identity stack (DbContext SQLite + AddIdentityCore + AddRoles<Role>) 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).
|
||
- Mã `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 skip** — `paths-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 PE** — `IDocumentConverter` + 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
|
||
|
||
```powershell
|
||
# 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 daily** — `scripts/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:
|
||
```json
|
||
"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 backup** — `schtasks /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:
|
||
```csharp
|
||
// 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 CI** — `dotnet 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`](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`](workflow-contract.md).
|
||
|
||
## File đang active
|
||
|
||
Cấu trúc thư mục + file map: [`PROJECT-MAP.md`](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
|
||
```
|
||
|
||
- API prod: https://api.solutions.com.vn — `/health/live`, `/health/ready`
|
||
- Admin FE prod: https://admin.solutions.com.vn
|
||
- User FE prod: https://eoffice.solutions.com.vn
|
||
- API dev: http://localhost:5443 — `/swagger` (Dev only)
|
||
- Admin FE dev: http://localhost:8082
|
||
- User FE dev: http://localhost:8080
|
||
- SQL dev: `(localdb)\MSSQLLocalDB` / `SolutionErp_Dev`
|
||
- SQL prod: `.\SQLEXPRESS` / `SolutionErp` / `vrapp` (⚠️ rotate)
|
||
|
||
## Đá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
|