Compare commits
2 Commits
edba4ae147
...
ea440da990
| Author | SHA1 | Date | |
|---|---|---|---|
| ea440da990 | |||
| 7b0781b94e |
@ -188,29 +188,11 @@ Bug latency observed when miss points 9-10: 2-3 days prod silent (Mig 28-29 depl
|
|||||||
|
|
||||||
- **Archived Run #231 PARTIAL detail (Plan B Contract V2 kick-off DemoSeed gap discovery) → `archive/2026-05-q2.md` 2026-05-26 S32 curate.** KEY findings preserved in S29 wrap entry above: Run #231 PASS deploy + Mig 32+33 + bundle rotate, gap = ApplicableType=3 ZERO seed (DemoSeed gate), resolved by Run #232 Hotfix CICD → gotcha #51 added.
|
- **Archived Run #231 PARTIAL detail (Plan B Contract V2 kick-off DemoSeed gap discovery) → `archive/2026-05-q2.md` 2026-05-26 S32 curate.** KEY findings preserved in S29 wrap entry above: Run #231 PASS deploy + Mig 32+33 + bundle rotate, gap = ApplicableType=3 ZERO seed (DemoSeed gate), resolved by Run #232 Hotfix CICD → gotcha #51 added.
|
||||||
|
|
||||||
- **2026-05-22 (S28 wrap — Layer A governance apply, NO Run S28):** Zero git push remote cả S28 (all local docs + memory + RAG store chunks) → 0 deploy event, bundle hash 2/2 unchanged Run #227 baseline, Mig 31 prod unchanged, 111 test baseline unchanged. **Timeline:** t1 startup 9-step + smoke test pass (registry hot-reload OK post-S27 model:inherit fix + 3,462 RAG chunks indexed) → t2-t4 RAG ROI verdict + over-reach mistake + scope-down → t5 Layer A governance apply. **Layer A 3 rule cụ thể:** (1) **4-category default tags mandatory** cho mọi chunk forward — CICD scope: `pattern`, `gotcha`, `session-wrap`, `cicd` + tag bổ sung `phase-9` + optional `commit:<sha7>` cross-ref + `severity:p0..p3`. (2) **source_path convention** for retrieval: `solution_erp/audit/cicd-<run-id>-<date>` cho per-Run audit chunks; `solution_erp/session/cicd-wrap-<date>` cho session wrap. (3) **Weekly Friday eval ritual** starting **2026-05-29** Friday 5 metric: query/session count + hit rate (rerank ≥0.7) + store noise % + RAG vs MD ratio + Voyage embed cost/week. 10 golden query draft sẵn cross-stack scenarios: Plan B Contract V2 wire kick-off + gotcha #48 SQLite tie-break + per-NV 10-surface checklist + cookie-cutter mirror PE→Contract + controller body record param count + FE merge synthetic Policy V2 + EF backfill idempotent + Smart Friend agent eval + DemoSeed feature flag + Run #215+#216 fail-fix pair pattern. **ABANDONED rule cũ:** "mọi tương tác mandatory RAG" → wastes ~30K query overhead khi single Run verify Bash poll đủ context. **Foundation kept:** 10-surface-point per-NV checklist (S22+5 → S23 t6) vẫn promoted MEMORY foundation cho future per-NV refactor verify. **Forward S28+:** chờ catch first CICD Run đầu Plan B Contract V2 wire khi bro push commit BE/FE/Mig (Mig 32+ expected).
|
- **Archived to `archive/2026-05-q3.md` 2026-05-27 S34 curate (em main proxy):** S28 wrap Layer A governance apply (NO Run, governance only — tag schema absorbed forward) + S27 wrap-up hot-reload pitfall (model:inherit fix, registry hot-reload lesson preserved in foundation) + 2026-05-22 Curate session note (Run #186-#221 → `archive/2026-05-runs.md`). KEY pattern absorbed forward: SESSION START PROTOCOL MUST spawn test agent trước khi assume registry loaded.
|
||||||
|
|
||||||
- **2026-05-22 (S27 wrap-up em main proxy - hot-reload pitfall):** NO Run triggered S27 (zero git push remote - all changes local docs + scripts + memory curate). 0 deploy event to monitor. **Meta-discovery em main S27:** Sub-agent registry KHÔNG load trong session active vì 4 file `.claude/agents/*.md` dùng `model: claude-opus-4-7` (200K) + non-standard `effort: max` field → CLI silent reject per VIPIX pitfall #2. Em main solo cả S27 KHÔNG có lựa chọn delegate. Fix applied: 4 file → `model: inherit` + remove `effort: max`. **Pending:** Anh restart Claude Code CLI để hot-reload (pitfall #1 - edit file disk KHÔNG hot-reload session đang chạy). Sau restart, S28+ next push sẽ có CICD Monitor spawn trở lại normal cycle. Pattern reinforced: SESSION START PROTOCOL MUST spawn test agent trước khi assume registry loaded.
|
|
||||||
|
|
||||||
- **2026-05-22 (Curate session em main):** Archived Run #186 → Run #221 verbose entries (14 runs S21 t3 → S25 Plan AF) → `archive/2026-05-runs.md`. KEEP: Run #215+#216 (gotcha #48 fail+fix pair lesson critical) + Run #222-#227 S26 summary + setup baseline. Memory size before: ~72KB → after: ~25-28KB target. Cumulative 8 patterns extracted vào archive header + 10-surface-point per-NV checklist promoted to foundation section above.
|
|
||||||
|
|
||||||
- **2026-05-21 (S26 Run #222-#227 cumulative — Plan AG series PE List tree view UI iteration):** Hybrid verify pattern: CICD Monitor spawn 1× cho Phase wire initial Run #222 sha=`0bf6c7e` Plan AG (~12K — bundle hash 2/2 rotate admin `C8TvDy7r→CWHIdoFo` + user `BvcWrq2z→Bg2FNeIz`, smoke 5/5 200, PE List API shape preserved 9 fields, test gate 111 unchanged, Mig 31 unchanged). Run #223-#227 polish chunks Plan AG2-AG6 em main self-verify (bundle visual check + git push success + Gitea auto-trigger 3-4min deploy). Plan AG4 BE+FE cross-stack (DTO +4 fields DrafterUserId/DrafterName/DepartmentId/DepartmentName + 3 projection JOIN Users+Departments): dotnet test 111/111 PASS local pre-push. **Pattern saved:** CICD Monitor spawn 1× đầu Phase wire ROI tốt cho 1 dev solo iteration scenario. Polish chunks (CSS/UX/copy) cùng Plan em main self-verify thay vì re-spawn ~150K × N wasteful. 0 prod regression observed cumulative S26.
|
- **2026-05-21 (S26 Run #222-#227 cumulative — Plan AG series PE List tree view UI iteration):** Hybrid verify pattern: CICD Monitor spawn 1× cho Phase wire initial Run #222 sha=`0bf6c7e` Plan AG (~12K — bundle hash 2/2 rotate admin `C8TvDy7r→CWHIdoFo` + user `BvcWrq2z→Bg2FNeIz`, smoke 5/5 200, PE List API shape preserved 9 fields, test gate 111 unchanged, Mig 31 unchanged). Run #223-#227 polish chunks Plan AG2-AG6 em main self-verify (bundle visual check + git push success + Gitea auto-trigger 3-4min deploy). Plan AG4 BE+FE cross-stack (DTO +4 fields DrafterUserId/DrafterName/DepartmentId/DepartmentName + 3 projection JOIN Users+Departments): dotnet test 111/111 PASS local pre-push. **Pattern saved:** CICD Monitor spawn 1× đầu Phase wire ROI tốt cho 1 dev solo iteration scenario. Polish chunks (CSS/UX/copy) cùng Plan em main self-verify thay vì re-spawn ~150K × N wasteful. 0 prod regression observed cumulative S26.
|
||||||
|
|
||||||
- **2026-05-19 10:13-10:21 — Run #215 FAIL → Run #216 PASS (gotcha #48 SQLite tie-break catch+fix pair, KEEP for crucial lesson):**
|
- **Archived to `archive/2026-05-q3.md` 2026-05-27 S34 curate (em main proxy):** Run #215+#216 (gotcha #48 SQLite tie-break catch+fix pair, S25 t1-t2) — full detail preserved in archive. KEY lesson cumulative trong foundation gotcha #48 line 40-45 (test discriminator filter Summary contains "Chuyển phase"). 2026-05-13 S22 chốt cuối verify (Run #214 baseline + Discovery #3 docs-only skip). 2026-05-12 setup baseline.
|
||||||
|
|
||||||
**Run #215** id=329 sha=`cdfd542` VERDICT=FAIL (S25 t1 Plan AB Chunk A — Changelog visibility fix Bug 1 Budget Adjust + Bug 2 Return Mode). Push range `e23f51c..cdfd542` 1 commit 3 files (1 BE `PurchaseEvaluationWorkflowService.cs` +207/-95 LOC refactor `ApplyReturnModeAsync` + 2 FE `PeDetailTabs.tsx` mirror filter extend). Duration 1m06s (early test gate fail, deploy stage never reached). **CRITICAL — Test gate FAIL at test_infra:** 51/53 passed, 2 FAIL same root cause:
|
|
||||||
- `PurchaseEvaluationWorkflowServiceReturnModeTests.ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet`
|
|
||||||
- `PurchaseEvaluationWorkflowServiceReturnModeTests.ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet`
|
|
||||||
- **Error:** `Expected changelog.ContextNote not to be <null>`
|
|
||||||
- **Root cause:** Plan AB Chunk A `ApplyReturnModeAsync` adds NEW Changelog entry at end (line 403-412) for Bug 2 visibility — `EntityType=Workflow + Action=Update + Summary` (NO ContextNote field). After refactor, BOTH ApplyReturnModeAsync (new entry, no ContextNote) AND LogTransitionAsync (line 100, existing entry with ContextNote=comment) are added in same `SaveChangesAsync` transaction. Test fetches `.OrderByDescending(c => c.CreatedAt).FirstAsync()` — with SQLite + frozen test clock both entries get SAME CreatedAt, OrderByDescending tie-break returns Plan AB's Workflow entry (without ContextNote) instead of Transition entry.
|
|
||||||
- **Deploy NOT shipped:** Bundle hashes unchanged from Run #214 Plan AA baseline. Mig 31 TOP 1 unchanged. Plan AB Bug 1+Bug 2 fix NOT live (bro UAT screenshot pre-deploy stale).
|
|
||||||
- **Side benefit:** CI test gate caught BEFORE prod deploy — bro UAT spared broken Plan M edge case audit trail.
|
|
||||||
|
|
||||||
**Run #216** id=330 sha=`8c05947` VERDICT=PASS (S25 t2 Plan AB Chunk A2 fix). Tip commit Chunk A2: 1 test file +7/-2 LOC — 2 Plan M edge case tests add `.Where(c => c.Summary!.Contains("Chuyển phase"))` filter trước `OrderByDescending(CreatedAt).First()` để pick LogTransition entry (chứa ContextNote) thay vì Plan AB new Changelog entry. Plan AB Chunk A code `cdfd542` KHÔNG bị revert — Bug 1+Bug 2 fix giữ nguyên. Test gate PASS: test_domain 58/58 + test_infra 53/53 (2 Plan M tests now PASS — verified live). Bundle hash 2/2 rotated. Bug 1 Budget Adjust entry LIVE + Bug 2 Return Mode entries LIVE on PE c6e9. 8 min turnaround 10:13 fail → 10:21 fix. **Demonstrates test-after UAT mode CAN tolerate edge case bug if next chunk lands within minutes — but Plan AB > 100 LOC BE refactor should have local `dotnet test` verify pre-push (UAT skip-test rule risky for refactor scope).**
|
|
||||||
|
|
||||||
- **2026-05-13 23:25 — Verify S22 chốt cuối cumulative** (push range `3d725c4..cc8a7d3` 12 commits VERDICT=PASS — S22+1-S22+5 Plan C/D/E + Mig 30 F4 per-NV Approver edit Budget). 33 active users prod confirmed. Bundle hash rotated 2/2. 104/104 test (+1 từ S21 baseline 103). Mig 30 prod confirmed. **Discovery #3 first surfaced:** `cc8a7d3` docs+4 agent MEMORY.md → CI SKIPPED via `**/*.md` glob (all match — `.md` files at any depth match). Spec hypothesis "`.claude/agent-memory/**` NOT in paths-ignore → trigger CI" disproven for this commit. Gotcha #47 still useful as PREVENTIVE for future non-.md state files under `.claude/agent-memory/`.
|
|
||||||
|
|
||||||
- **2026-05-12 (setup):** CI/CD Monitor agent initialized. Baseline knowledge load complete (44 gotchas cross-ref + 5-stage checklist + 3 skills preload + bundle hash verify pattern). No runs monitored yet.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -220,4 +202,4 @@ Bug latency observed when miss points 9-10: 2-3 days prod silent (Mig 28-29 depl
|
|||||||
- Duplicate failure patterns → merge into single entry (vd act_runner timeout x3 → 1 entry)
|
- Duplicate failure patterns → merge into single entry (vd act_runner timeout x3 → 1 entry)
|
||||||
- Stale > 3 months → remove
|
- Stale > 3 months → remove
|
||||||
|
|
||||||
**Last curate: 2026-05-26 S32 em main proxy curate** (post-S31 RAG fix) — archived 1 verbose Run #231 PARTIAL detail → `archive/2026-05-q2.md`. KEEP in MEMORY: S32 startup, S29 wrap summary (Run #229-#232 cumulative), S28 Layer A governance, S27 hot-reload pitfall, S22 curate, Run #215+#216 pair gotcha #48 critical, S26 Run #222-#227 summary, 2026-05-12 setup. Foundation 10-surface-point per-NV checklist + Stage 0-5 checklist + Discovery #6 INFRASTRUCTURE vs DEMO seed (Stage 4.6) preserved. MEMORY size before: 27 KB → after: ~24 KB. **Previous curate: 2026-05-22** — archived 14 verbose Run #186-#221 → `archive/2026-05-runs.md`. Next trigger: > 25KB OR Plan G-H1 kick off.
|
**Last curate: 2026-05-27 S34 em main proxy curate** (post-S33 wrap, sequence 1/4) — archived Run #215+#216 verbose detail (gotcha #48 catch+fix pair, KEY lesson preserved in foundation line 40-45) + 2026-05-13 S22 verify + 2026-05-12 setup → `archive/2026-05-q3.md`. KEEP in MEMORY: Run #237 + Run #350 (S33 prod deploy), S33 startup health, S32 wrap + S32 startup, S29 wrap, S28 wrap, S26 Run #222-#227, S22 curate. Foundation 10-surface-point per-NV checklist + Stage 0-5 checklist + Stage 4.6 sqlcmd seed verify + Discovery #6 INFRASTRUCTURE vs DEMO seed + gotchas #39-#48 detail all preserved untouched. MEMORY size before: 32.9 KB → after: target ~22-24 KB. **Previous curate: 2026-05-26 S32** — Run #231 PARTIAL detail → `archive/2026-05-q2.md`. **Previous curate: 2026-05-22** — Run #186-#221 → `archive/2026-05-runs.md`. Next trigger: > 25KB OR Plan G-O1 Danh bạ kick off.
|
||||||
|
|||||||
30
.claude/agent-memory/cicd-monitor/archive/2026-05-q3.md
Normal file
30
.claude/agent-memory/cicd-monitor/archive/2026-05-q3.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# CI/CD Monitor — Archive Q3 2026-05 (S34 curate)
|
||||||
|
|
||||||
|
> Verbose Recent runs archived from MEMORY.md S34 init (em main proxy curate 2026-05-27).
|
||||||
|
> Foundation gotchas #39-#52 + 5-stage checklist + 10-surface-point per-NV + Stage 4.6 sqlcmd seed verify preserved untouched in MEMORY.md.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Run #215 FAIL → Run #216 PASS (gotcha #48 SQLite tie-break catch+fix pair) — 2026-05-19 S25 t1-t2
|
||||||
|
|
||||||
|
**Run #215** id=329 sha=`cdfd542` VERDICT=FAIL (S25 t1 Plan AB Chunk A — Changelog visibility fix Bug 1 Budget Adjust + Bug 2 Return Mode). Push range `e23f51c..cdfd542` 1 commit 3 files (1 BE `PurchaseEvaluationWorkflowService.cs` +207/-95 LOC refactor `ApplyReturnModeAsync` + 2 FE `PeDetailTabs.tsx` mirror filter extend). Duration 1m06s (early test gate fail, deploy stage never reached). **CRITICAL — Test gate FAIL at test_infra:** 51/53 passed, 2 FAIL same root cause:
|
||||||
|
- `PurchaseEvaluationWorkflowServiceReturnModeTests.ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet`
|
||||||
|
- `PurchaseEvaluationWorkflowServiceReturnModeTests.ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet`
|
||||||
|
- **Error:** `Expected changelog.ContextNote not to be <null>`
|
||||||
|
- **Root cause:** Plan AB Chunk A `ApplyReturnModeAsync` adds NEW Changelog entry at end (line 403-412) for Bug 2 visibility — `EntityType=Workflow + Action=Update + Summary` (NO ContextNote field). After refactor, BOTH ApplyReturnModeAsync (new entry, no ContextNote) AND LogTransitionAsync (line 100, existing entry with ContextNote=comment) are added in same `SaveChangesAsync` transaction. Test fetches `.OrderByDescending(c => c.CreatedAt).FirstAsync()` — with SQLite + frozen test clock both entries get SAME CreatedAt, OrderByDescending tie-break returns Plan AB's Workflow entry (without ContextNote) instead of Transition entry.
|
||||||
|
- **Deploy NOT shipped:** Bundle hashes unchanged from Run #214 Plan AA baseline. Mig 31 TOP 1 unchanged. Plan AB Bug 1+Bug 2 fix NOT live (bro UAT screenshot pre-deploy stale).
|
||||||
|
- **Side benefit:** CI test gate caught BEFORE prod deploy — bro UAT spared broken Plan M edge case audit trail.
|
||||||
|
|
||||||
|
**Run #216** id=330 sha=`8c05947` VERDICT=PASS (S25 t2 Plan AB Chunk A2 fix). Tip commit Chunk A2: 1 test file +7/-2 LOC — 2 Plan M edge case tests add `.Where(c => c.Summary!.Contains("Chuyển phase"))` filter trước `OrderByDescending(CreatedAt).First()` để pick LogTransition entry (chứa ContextNote) thay vì Plan AB new Changelog entry. Plan AB Chunk A code `cdfd542` KHÔNG bị revert — Bug 1+Bug 2 fix giữ nguyên. Test gate PASS: test_domain 58/58 + test_infra 53/53 (2 Plan M tests now PASS — verified live). Bundle hash 2/2 rotated. Bug 1 Budget Adjust entry LIVE + Bug 2 Return Mode entries LIVE on PE c6e9. 8 min turnaround 10:13 fail → 10:21 fix. **Demonstrates test-after UAT mode CAN tolerate edge case bug if next chunk lands within minutes — but Plan AB > 100 LOC BE refactor should have local `dotnet test` verify pre-push (UAT skip-test rule risky for refactor scope).**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-13 23:25 — Verify S22 chốt cuối cumulative
|
||||||
|
|
||||||
|
Verify S22 chốt cuối cumulative (push range `3d725c4..cc8a7d3` 12 commits VERDICT=PASS — S22+1-S22+5 Plan C/D/E + Mig 30 F4 per-NV Approver edit Budget). 33 active users prod confirmed. Bundle hash rotated 2/2. 104/104 test (+1 từ S21 baseline 103). Mig 30 prod confirmed. **Discovery #3 first surfaced:** `cc8a7d3` docs+4 agent MEMORY.md → CI SKIPPED via `**/*.md` glob (all match — `.md` files at any depth match). Spec hypothesis "`.claude/agent-memory/**` NOT in paths-ignore → trigger CI" disproven for this commit. Gotcha #47 still useful as PREVENTIVE for future non-.md state files under `.claude/agent-memory/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-12 — Setup baseline
|
||||||
|
|
||||||
|
CI/CD Monitor agent initialized. Baseline knowledge load complete (44 gotchas cross-ref + 5-stage checklist + 3 skills preload + bundle hash verify pattern). No runs monitored yet.
|
||||||
@ -162,7 +162,7 @@ Pattern reusable: test PE workflow → 1 Step + 2 Levels + N approvers per Level
|
|||||||
|
|
||||||
Tránh API surface bloat. Reusable cho future guard / helper internal cần test.
|
Tránh API surface bloat. Reusable cho future guard / helper internal cần test.
|
||||||
|
|
||||||
### Pattern 16-bis: 4-place mirror checklist khi cookie-cutter copy page CROSS-APP (S29 Plan CA Hotfix 1 — gotcha #50)
|
### Pattern 16-bis: 4-place mirror checklist khi cookie-cutter copy page CROSS-APP (S29 Plan CA Hotfix 1 — gotcha #50, reinforced 5× cumulative qua S33+S34)
|
||||||
|
|
||||||
Khi spec yêu cầu "move page X từ fe-admin → fe-user" hoặc ngược lại (Implementer Case 2 cookie-cutter mirror page), MUST mirror 4 places (NOT just 3):
|
Khi spec yêu cầu "move page X từ fe-admin → fe-user" hoặc ngược lại (Implementer Case 2 cookie-cutter mirror page), MUST mirror 4 places (NOT just 3):
|
||||||
|
|
||||||
@ -175,6 +175,13 @@ Khi spec yêu cầu "move page X từ fe-admin → fe-user" hoặc ngược lạ
|
|||||||
|
|
||||||
**Verification post-fix:** Reviewer Cat 1 "Wire claim verify" SHOULD add to checklist: "Sidebar menu visible end-to-end test post-build" — curl `/api/menus/me` + grep MenuLeaf render output. Smart Friend prevent silent drop.
|
**Verification post-fix:** Reviewer Cat 1 "Wire claim verify" SHOULD add to checklist: "Sidebar menu visible end-to-end test post-build" — curl `/api/menus/me` + grep MenuLeaf render output. Smart Friend prevent silent drop.
|
||||||
|
|
||||||
|
**S34 G-O1 Task 3 reinforcement (2026-05-27, Plan B Internal Directory):** Pattern 16-bis applied clean lần thứ 5 cumulative. Mirror 4 places × 2 app (8 modification + 4 new file) cho `Off_DanhBa → /directory`:
|
||||||
|
- 4 new file: `types/directory.ts` × 2 (SHA256 `7349d9f64e78`) + `pages/office/InternalDirectoryPage.tsx` × 2 (SHA256 `2aa7e0eed2c8`) — both MATCH identical hash
|
||||||
|
- 6 modified: App.tsx × 2 (+route), menuKeys.ts × 2 (+Off/OffDanhBa const), Layout.tsx × 2 (+staticMap Off_DanhBa)
|
||||||
|
- npm build × 2 app: fe-admin 21.99s clean (bundle 1436.71 kB / gzip 364.54 kB), fe-user 9.37s clean (bundle 1350.28 kB / gzip 349.01 kB) — 0 TS error
|
||||||
|
- Token cost ~20k (under budget 25k). Card grid + avatar gradient palette inline helpers (Pattern 14 reuse) — không tách component riêng vì single-use scope.
|
||||||
|
- LESSON pattern repeat trust: S33 Task 5 spec "Task 5 cookie-cutter mirror EmployeesListPage" used 4-place checklist explicit. S34 G-O1 Task 3 spec follow same template → execute 0 ambiguity. Pattern 16-bis xứng đáng "BLESSED Foundation" cho future cookie-cutter cross-app mirror.
|
||||||
|
|
||||||
### Pattern 12-bis: Cross-module entity cookie-cutter mirror (S29 Plan B Chunk C — Mig 33)
|
### Pattern 12-bis: Cross-module entity cookie-cutter mirror (S29 Plan B Chunk C — Mig 33)
|
||||||
|
|
||||||
Khi spec yêu cầu "mirror entity X từ PE module sang Contract module" (vd LevelOpinions / DepartmentApproval / ManualBudgetFields):
|
Khi spec yêu cầu "mirror entity X từ PE module sang Contract module" (vd LevelOpinions / DepartmentApproval / ManualBudgetFields):
|
||||||
@ -330,30 +337,9 @@ KHÔNG `*` / `latest`. Critical pins:
|
|||||||
|
|
||||||
- **2026-05-26 (S32 startup — context verify + RAG live confirm + size FLAG > 25KB):** Em chủ trì spawn em verify Session 32 context. **Verify done:** (1) MEMORY size 36.2KB (Get-Item Length=36207 bytes) — **OVER 25KB threshold ~45% bigger** → FLAG cho em main schedule dedicated curate session per Pattern curate trigger rule line 364. KHÔNG self-curate vì em chủ trì preference reserve cho em main solo judgment call §6.5 KEEP vs CUT (S27 retrospective C1-C4 task lesson). (2) Patterns saved 1-12 foundation + 12-bis NEW S29 + 13-15 + 16-bis NEW S29 + 17-19 — total **17 numbered patterns** (Pattern 16 baseline implied trong recent activity S27 chưa numbered explicit). Pattern 12-bis (cross-module entity cookie-cutter mirror PE→Contract Mig 33) **SAVED line 178-200** confirmed present. Pattern 16-bis (4-place mirror cross-app S29 Plan CA Hotfix 1) **SAVED line 165-176** confirmed present. (3) MCP RAG tools **PRESENT** — `mcp__rag-unified__search_memory` + `mcp__rag-unified__cross_project_search` both visible trong tools list. Test query "Pattern 12-bis cross-module entity cookie-cutter mirror PE Contract V2" top_k=3 returned 3 results với rerank scores **0.824/0.801/0.793** — all healthy > 0.7 threshold. S31 RAG v1.3 baseline PASS confirmed live post CLI restart. **Pending tasks em main có thể gọi em lại spawn S32+:** (a) Plan B-Wrap BW1-BW7 test bundle codegen Case 2 cookie-cutter mirror PE WorkflowService test pattern (regression ApproveV2Async + UPSERT LevelOpinions test) — 7 file new test class mirror PE test bundle structure; (b) ContractWorkflowMatrixView mirror PE WorkflowMatrixView Plan AA S24 (1 page mirror cross-module — Case 2 fits Pattern 13 read-only admin Designer mirror + Pattern 14 Tailwind JIT palette + Pattern 15 HTML table rowSpan iteration helper). **Decision tree forward:** Em chủ trì gọi em với task code edit → em ACCEPT case (a)/(b) khi spec deterministic, REFUSE nếu first-time pattern. Em chủ trì confirm Layer A governance still active scope SOLUTION_ERP. Token cost spawn này ~5k (3 Read + 1 RAG query + 1 Edit + final report). KHÔNG curate — defer em main full curate session. Tag: `[verify, phase-9, infra]`.
|
- **2026-05-26 (S32 startup — context verify + RAG live confirm + size FLAG > 25KB):** Em chủ trì spawn em verify Session 32 context. **Verify done:** (1) MEMORY size 36.2KB (Get-Item Length=36207 bytes) — **OVER 25KB threshold ~45% bigger** → FLAG cho em main schedule dedicated curate session per Pattern curate trigger rule line 364. KHÔNG self-curate vì em chủ trì preference reserve cho em main solo judgment call §6.5 KEEP vs CUT (S27 retrospective C1-C4 task lesson). (2) Patterns saved 1-12 foundation + 12-bis NEW S29 + 13-15 + 16-bis NEW S29 + 17-19 — total **17 numbered patterns** (Pattern 16 baseline implied trong recent activity S27 chưa numbered explicit). Pattern 12-bis (cross-module entity cookie-cutter mirror PE→Contract Mig 33) **SAVED line 178-200** confirmed present. Pattern 16-bis (4-place mirror cross-app S29 Plan CA Hotfix 1) **SAVED line 165-176** confirmed present. (3) MCP RAG tools **PRESENT** — `mcp__rag-unified__search_memory` + `mcp__rag-unified__cross_project_search` both visible trong tools list. Test query "Pattern 12-bis cross-module entity cookie-cutter mirror PE Contract V2" top_k=3 returned 3 results với rerank scores **0.824/0.801/0.793** — all healthy > 0.7 threshold. S31 RAG v1.3 baseline PASS confirmed live post CLI restart. **Pending tasks em main có thể gọi em lại spawn S32+:** (a) Plan B-Wrap BW1-BW7 test bundle codegen Case 2 cookie-cutter mirror PE WorkflowService test pattern (regression ApproveV2Async + UPSERT LevelOpinions test) — 7 file new test class mirror PE test bundle structure; (b) ContractWorkflowMatrixView mirror PE WorkflowMatrixView Plan AA S24 (1 page mirror cross-module — Case 2 fits Pattern 13 read-only admin Designer mirror + Pattern 14 Tailwind JIT palette + Pattern 15 HTML table rowSpan iteration helper). **Decision tree forward:** Em chủ trì gọi em với task code edit → em ACCEPT case (a)/(b) khi spec deterministic, REFUSE nếu first-time pattern. Em chủ trì confirm Layer A governance still active scope SOLUTION_ERP. Token cost spawn này ~5k (3 Read + 1 RAG query + 1 Edit + final report). KHÔNG curate — defer em main full curate session. Tag: `[verify, phase-9, infra]`.
|
||||||
|
|
||||||
- **2026-05-22 (S29 wrap — Plan CA Chunk B + Plan B 4 chunks Case 2 cookie-cutter + 1 stopped E3 + Pattern 12-bis NEW):** Implementer = busiest agent S29 với 5 spawn total. **Plan CA Chunk B (~10K):** 4 master pages mirror fe-admin→fe-user byte-identical SHA256, touch 6 file (4 page + App.tsx +5 route + menuKeys.ts +5 key Catalogs*), 948 LOC mirror PASS 0 TS err. Saved `pattern_master_page_mirror.md`. **Plan B 4 spawn cumulative:** (A2 Mig 32 ~25K) schema +column FK Restrict IX + Configuration + DbInitializer SeedSampleContractWorkflowV2 — **stash em main WIP ContractWorkflowService.cs** để build verify clean (em main + Implementer parallel touch BE → race condition trick); (C Mig 33 ContractLevelOpinions ~25K) entity + Mig + Config + DbSet + Contract.cs +LevelOpinions nav — **Pattern 12-bis NEW cross-module entity cookie-cutter mirror PE→Contract** scaffold 4-file pattern documented Patterns section; (D FE Workspace V2 ~12K) ContractCreatePage × 2 app +useQuery V2 + Select dropdown wire ApprovalWorkflowId, 88 LOC mirror byte-similar; (E3 stopped mid-task) FE Section 5 V2 STOPPED at "check ContractDetail type" judgment call → em main solo finish. **Lessons:** (1) **Race condition em main + Implementer parallel BE** → stash trick works but adds overhead. Forward SEQUENTIAL chunks A→B→C khi cùng touch BE, NOT parallel. (2) **Complex FE feature mirror với type extend + new component** → em main solo more reliable than Implementer khi spec ambiguity > 20% in Read-required component inspection. (3) Pattern 16-bis 4-place mirror cross-app reinforced 2× (Plan CA Chunk B + Plan B Chunk D) — verify Layout staticMap khi page move/route enhance. **Patterns proven NEW S29:** Pattern 12-bis cross-module entity scaffold + Pattern 16-bis 4-place mirror reinforced. **Anti-pattern observed:** (a) Implementer E3 stopped mid-task FE complex judgment — em main miss provide concrete component template trong prompt. (b) Race condition parallel BE → stash workaround. Tag: `[pattern, phase-9, frontend+infra]`. KHÔNG curate (33.2KB OK borderline > 25KB threshold but close — defer to next session em main full curate khi S30 wrap).
|
- **Archived to `archive/2026-05-q3.md` 2026-05-27 S34 curate (em main proxy):** S29 wrap (5-spawn Plan CA + Plan B 4 chunks + E3 stopped + Pattern 12-bis NEW) + S28 wrap (Layer A governance perspective Implementer) + S27 wrap retrospective REFUSE analysis (8 task ACCEPT/REFUSE table + Pattern 20 5 PS scripts mirror) + 2026-05-22 curate session note + 2026-05-11 setup baseline. KEY takeaways absorbed in current entries S33+S32 + Patterns 1-15+12-bis+16-bis foundation section line 26-283.
|
||||||
|
|
||||||
- **5 verbose entries S25-S29 archived to `archive/2026-05-q2.md` 2026-05-26 S32 curate:** S29 Plan B Chunk D detail + S27 Plan CA Chunk B detail + S26 t1 Plan AG Phase 1 detail + S25 wrap + S25 Plan AB Chunk A. KEY takeaways preserved in S29 wrap entry above. Patterns 16-19 NEW S25-S26 reference foundation section line 165-283.
|
- **5 verbose entries S25-S29 archived to `archive/2026-05-q2.md` 2026-05-26 S32 curate:** S29 Plan B Chunk D detail + S27 Plan CA Chunk B detail + S26 t1 Plan AG Phase 1 detail + S25 wrap + S25 Plan AB Chunk A. KEY takeaways preserved trong S33+S32 wrap entries. Patterns 16-19 NEW S25-S26 reference foundation section line 165-283.
|
||||||
|
|
||||||
- **2026-05-22 (S28 wrap — Layer A governance distributed active, Implementer policy local apply):** S28 em main solo cả buổi (KHÔNG Implementer work code thực sự). Timeline: t1 RAG ROI verdict marginal-short/transform-long → t2 bro feedback "ghi mọi tương tác" → t3 em đề xuất 2-week monitoring 5 metric → t4 bro caught self-authorize cross-project rule mistake → t5 governance broadcast Layer A active 3-Layer distributed scope-down về SOLUTION_ERP self-discipline. **Implementer perspective về Layer A governance:** (1) Pattern proven ≥ 2× qualifies Layer B nominate. **Pattern 7 per-NV admin opt-in flag (Mig 29 AllowDrafterEdit + Mig 30 AllowApproverEditSection1 + Mig 31 AllowEarlyApprove + AllowDelegate) đã proven 4× — strong candidate Layer B promote khi unfreeze.** (2) Tag schema mandatory áp dụng forward: store Pattern chunk với format `[pattern, phase-<N>, <bc>]` — vd Pattern 19 HTML details tree view → `[pattern, phase-9, frontend]`; Pattern 18 FE userMap fallback → `[pattern, phase-9, frontend]`; Pattern 16 preventive batch fix → `[pattern, phase-9, cross-cutting]`. (3) source_path convention: `solution_erp/pattern/<topic>-<date>` — vd `solution_erp/pattern/tailwind-jit-palette-2026-05-19` cho Pattern 14, `solution_erp/pattern/per-nv-flag-2026-05-15` cho Pattern 7. (4) **KHÔNG self-authorize cross-project rule (lesson S28 t4)** — distributed governance respects boundary, Implementer agent KHÔNG override Layer A scope cho cross-project (vd "NamGroup follow SOLUTION_ERP Pattern 19" → REFUSE cross-project assertion, route lên Layer B human review). (5) Quên rule cũ "mọi tương tác mandatory store" — **ABANDONED** (was self-authorize over-reach). Forward S28+ commit: Implementer apply tag schema `[type, phase, bc-or-module]` mandatory cho mỗi Pattern entry mới + scope discipline strict SOLUTION_ERP only + KHÔNG cross-project rule assertion + 4-category default (pattern/architecture/decision/gotcha) + skip list (ephemeral, code, log) + 11 phase enum (phase-9 UAT ACTIVE) + 8 BC + 5 cross-cutting + optional prefix.
|
|
||||||
|
|
||||||
- **2026-05-22 (S27 wrap-up em main proxy - retrospective REFUSE analysis):** Em main S27 SOLO cả buổi vì registry KHÔNG load (per pitfall VIPIX #1+#2). Retrospective analysis 6 task S27 vs ACCEPT/REFUSE criteria:
|
|
||||||
|
|
||||||
| Task S27 | Implementer fit? | Verdict |
|
|
||||||
|---|---|---|
|
|
||||||
| C1 Curate cicd-monitor 72KB | ❌ REFUSE #1 (judgment §6.5 KEEP vs CUT) | Em main solo CORRECT |
|
|
||||||
| C2-C4 Curate 3 agent MEMORY | ❌ REFUSE #1 same | Em main solo CORRECT |
|
|
||||||
| C5 Audit drift §6.4 + §9.4 | ❌ REFUSE #1 same | Em main solo CORRECT |
|
|
||||||
| A3.1 Write 5 PS scripts | ✅ ACCEPT Case 2 (5 file cookie-cutter mirror pattern) | **Implementer would ACCEPT** - em main miss delegate opportunity |
|
|
||||||
| A3.2 Dashboard HTML + generator | ❌ REFUSE #7 (first time pattern, no precedent) + #2 UX design needed | Em main solo CORRECT |
|
|
||||||
| A4 rag-onboarding-guide.md 421 lines | ❌ REFUSE #2 (docs writing judgment) | Em main solo CORRECT |
|
|
||||||
| F1 Qdrant Web UI fix | ❌ REFUSE #4 (bug reasoning chain) | Em main solo CORRECT |
|
|
||||||
| F2 4 agent files fix model:inherit | ✅ ACCEPT Case 1 (4 file mechanical same edit) | **Implementer would ACCEPT** - em main miss delegate opportunity |
|
|
||||||
|
|
||||||
**Verdict: 2/8 task lẽ ra delegate được Implementer (Case 1+2) nhưng registry empty → em main forced solo.** Net loss ~30 phút time + miss cookie-cutter mirror discipline. Pattern 20 NEW saved foundation: "**5 PS scripts mirror pattern** — start/stop/status/dashboard/boot family cùng ASCII discipline + same Write-Host structure + same try-catch pattern" reusable cho future infra automation. **Pending post-restart CLI S28+:** Implementer Case 2 spawn cho any mass file mechanical refactor.
|
|
||||||
|
|
||||||
- **2026-05-22 (Curate session em main):** Archived 12 verbose Recent activity entries S21 t3 → S24 Plan AA → `archive/2026-05-q1.md`. KEEP: S26 Plan AG (Pattern 19 NEW), S25 wrap (Patterns 16-18 NEW), S25 Plan AB, setup baseline. Patterns 1-19 foundation section preserved. Memory size before: 38.8 KB → after: target ~22-24 KB.
|
|
||||||
|
|
||||||
- **2026-05-11 (setup):** Implementer agent initialized. Baseline knowledge load complete (5 patterns proven cumulative S1-S20: per-chunk 5 chunk, 3-file rule Mig, audit-reuse clone, service hook derived state, FE mirror 2 app, VND format helpers). No implementations performed yet. Awaiting first SendMessage from em main. Strict scope auto-refuse criteria active.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -363,4 +349,4 @@ KHÔNG `*` / `latest`. Critical pins:
|
|||||||
- Duplicate entries detected → merge
|
- Duplicate entries detected → merge
|
||||||
- Stale > 3 months → remove
|
- Stale > 3 months → remove
|
||||||
|
|
||||||
**Last curate: 2026-05-26 S32 em main proxy curate** (post-S31 RAG fix) — archived 5 verbose entries (S25 Plan AB → S29 Plan B Chunk D detail + S27 Plan CA Chunk B detail + S26 Plan AG) → `archive/2026-05-q2.md`. KEEP: S32 startup (latest), S29 wrap (5-spawn summary cumulative), S28 Layer A governance, S27 retrospective REFUSE analysis, S22 curate session lesson, S11 setup baseline. Patterns 1-19 + 12-bis + 16-bis foundation section preserved untouched. MEMORY size before: 38.4 KB → after: target ~21 KB. Per `feedback_md_compact_narrative.md` §6.5 — archive preserves full verbose entries cho cross-session audit retrieve. **Previous curate: 2026-05-22** — archived 12 verbose (S21 t3 → S24 Plan AA) → `archive/2026-05-q1.md`. Next trigger: > 25KB OR Plan G-H1 kick off.
|
**Last curate: 2026-05-27 S34 em main proxy curate** (post-S33 wrap, sequence 1/4) — archived 5 verbose entries (S29 wrap + S28 wrap + S27 wrap REFUSE analysis + S22 curate session note + S11 setup) → `archive/2026-05-q3.md`. KEEP: S33 Task 5 (latest cookie-cutter cross-app mirror Plan B G-H1), S32 wrap (Plan G 11 module backlog), S32 startup (size FLAG + RAG verify). Patterns 1-15 + 12-bis + 16-bis foundation section line 26-283 preserved untouched. MEMORY size before: 30.5 KB → after: target ~18-20 KB. Per `feedback_md_compact_narrative.md` §6.5 — archive preserves full verbose entries cho cross-session audit retrieve. **Previous curate: 2026-05-26 S32** — 5 verbose S25-S29 → `archive/2026-05-q2.md`. **Previous curate: 2026-05-22** — 12 verbose S21 t3 → S24 Plan AA → `archive/2026-05-q1.md`. Next trigger: > 25KB OR Plan G-O1 Danh bạ kick off.
|
||||||
|
|||||||
47
.claude/agent-memory/implementer/archive/2026-05-q3.md
Normal file
47
.claude/agent-memory/implementer/archive/2026-05-q3.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Implementer — Archive Q3 2026-05 (S34 curate)
|
||||||
|
|
||||||
|
> Verbose Recent activity entries archived from MEMORY.md S34 init (em main proxy curate 2026-05-27).
|
||||||
|
> Patterns 1-15 + 12-bis + 16-bis foundation section preserved untouched in MEMORY.md.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-22 — S29 wrap Plan CA Chunk B + Plan B 4 chunks Case 2 cookie-cutter + 1 stopped E3 + Pattern 12-bis NEW
|
||||||
|
|
||||||
|
Implementer = busiest agent S29 với 5 spawn total. **Plan CA Chunk B (~10K):** 4 master pages mirror fe-admin→fe-user byte-identical SHA256, touch 6 file (4 page + App.tsx +5 route + menuKeys.ts +5 key Catalogs*), 948 LOC mirror PASS 0 TS err. Saved `pattern_master_page_mirror.md`. **Plan B 4 spawn cumulative:** (A2 Mig 32 ~25K) schema +column FK Restrict IX + Configuration + DbInitializer SeedSampleContractWorkflowV2 — **stash em main WIP ContractWorkflowService.cs** để build verify clean (em main + Implementer parallel touch BE → race condition trick); (C Mig 33 ContractLevelOpinions ~25K) entity + Mig + Config + DbSet + Contract.cs +LevelOpinions nav — **Pattern 12-bis NEW cross-module entity cookie-cutter mirror PE→Contract** scaffold 4-file pattern documented Patterns section; (D FE Workspace V2 ~12K) ContractCreatePage × 2 app +useQuery V2 + Select dropdown wire ApprovalWorkflowId, 88 LOC mirror byte-similar; (E3 stopped mid-task) FE Section 5 V2 STOPPED at "check ContractDetail type" judgment call → em main solo finish. **Lessons:** (1) **Race condition em main + Implementer parallel BE** → stash trick works but adds overhead. Forward SEQUENTIAL chunks A→B→C khi cùng touch BE, NOT parallel. (2) **Complex FE feature mirror với type extend + new component** → em main solo more reliable than Implementer khi spec ambiguity > 20% in Read-required component inspection. (3) Pattern 16-bis 4-place mirror cross-app reinforced 2× (Plan CA Chunk B + Plan B Chunk D) — verify Layout staticMap khi page move/route enhance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-22 — S28 wrap Layer A governance distributed active
|
||||||
|
|
||||||
|
Implementer policy local apply: S28 em main solo cả buổi (KHÔNG Implementer work code thực sự). Timeline: t1 RAG ROI verdict marginal-short/transform-long → t2 bro feedback "ghi mọi tương tác" → t3 em đề xuất 2-week monitoring 5 metric → t4 bro caught self-authorize cross-project rule mistake → t5 governance broadcast Layer A active 3-Layer distributed scope-down về SOLUTION_ERP self-discipline. **Implementer perspective về Layer A governance:** (1) Pattern proven ≥ 2× qualifies Layer B nominate. **Pattern 7 per-NV admin opt-in flag (Mig 29 AllowDrafterEdit + Mig 30 AllowApproverEditSection1 + Mig 31 AllowEarlyApprove + AllowDelegate) đã proven 4× — strong candidate Layer B promote khi unfreeze.** (2) Tag schema mandatory áp dụng forward: store Pattern chunk với format `[pattern, phase-<N>, <bc>]`. (3) source_path convention: `solution_erp/pattern/<topic>-<date>`. (4) **KHÔNG self-authorize cross-project rule (lesson S28 t4)**. (5) Quên rule cũ "mọi tương tác mandatory store" — **ABANDONED**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-22 — S27 wrap-up retrospective REFUSE analysis
|
||||||
|
|
||||||
|
Em main S27 SOLO cả buổi vì registry KHÔNG load (per pitfall VIPIX #1+#2). Retrospective analysis 6 task S27 vs ACCEPT/REFUSE criteria:
|
||||||
|
|
||||||
|
| Task S27 | Implementer fit? | Verdict |
|
||||||
|
|---|---|---|
|
||||||
|
| C1 Curate cicd-monitor 72KB | ❌ REFUSE #1 (judgment §6.5 KEEP vs CUT) | Em main solo CORRECT |
|
||||||
|
| C2-C4 Curate 3 agent MEMORY | ❌ REFUSE #1 same | Em main solo CORRECT |
|
||||||
|
| C5 Audit drift §6.4 + §9.4 | ❌ REFUSE #1 same | Em main solo CORRECT |
|
||||||
|
| A3.1 Write 5 PS scripts | ✅ ACCEPT Case 2 (5 file cookie-cutter mirror pattern) | **Implementer would ACCEPT** - em main miss delegate opportunity |
|
||||||
|
| A3.2 Dashboard HTML + generator | ❌ REFUSE #7 (first time pattern, no precedent) + #2 UX design needed | Em main solo CORRECT |
|
||||||
|
| A4 rag-onboarding-guide.md 421 lines | ❌ REFUSE #2 (docs writing judgment) | Em main solo CORRECT |
|
||||||
|
| F1 Qdrant Web UI fix | ❌ REFUSE #4 (bug reasoning chain) | Em main solo CORRECT |
|
||||||
|
| F2 4 agent files fix model:inherit | ✅ ACCEPT Case 1 (4 file mechanical same edit) | **Implementer would ACCEPT** - em main miss delegate opportunity |
|
||||||
|
|
||||||
|
**Verdict: 2/8 task lẽ ra delegate được Implementer (Case 1+2) nhưng registry empty → em main forced solo.** Net loss ~30 phút time + miss cookie-cutter mirror discipline. Pattern 20 NEW saved foundation: "**5 PS scripts mirror pattern** — start/stop/status/dashboard/boot family cùng ASCII discipline + same Write-Host structure + same try-catch pattern" reusable cho future infra automation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-22 — Curate session (S29 era)
|
||||||
|
|
||||||
|
Archived 12 verbose Recent activity entries S21 t3 → S24 Plan AA → `archive/2026-05-q1.md`. KEEP: S26 Plan AG (Pattern 19 NEW), S25 wrap (Patterns 16-18 NEW), S25 Plan AB, setup baseline. Patterns 1-19 foundation section preserved. Memory size before: 38.8 KB → after: target ~22-24 KB.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-11 — Setup baseline
|
||||||
|
|
||||||
|
Implementer agent initialized. Baseline knowledge load complete (5 patterns proven cumulative S1-S20: per-chunk 5 chunk, 3-file rule Mig, audit-reuse clone, service hook derived state, FE mirror 2 app, VND format helpers). No implementations performed yet. Awaiting first SendMessage from em main. Strict scope auto-refuse criteria active.
|
||||||
@ -141,17 +141,7 @@ State machine 5 trạng thái phiếu PE: Nháp / Đã gửi duyệt / **Trả l
|
|||||||
|
|
||||||
- **Archived 4 verbose entries S25-S26-S29 → `archive/2026-05-q2.md` 2026-05-26 S32 curate:** S25 t1 5Q audit Bug Changelog detail + S26 Plan AG 5Q PE List tree view + S26 Plan AI RAG 4 study cases (Cursor/Cline/Continue/Sourcegraph) + Plan B Contract V2 Q1-Q5 audit detail. KEY recommendations preserved in S29 wrap entry above (Plan B re-chunk 6 chunks pattern + ApplicableType=3 ZERO Prod seed flag + PE Mig 22-26 reference template paths).
|
- **Archived 4 verbose entries S25-S26-S29 → `archive/2026-05-q2.md` 2026-05-26 S32 curate:** S25 t1 5Q audit Bug Changelog detail + S26 Plan AG 5Q PE List tree view + S26 Plan AI RAG 4 study cases (Cursor/Cline/Continue/Sourcegraph) + Plan B Contract V2 Q1-Q5 audit detail. KEY recommendations preserved in S29 wrap entry above (Plan B re-chunk 6 chunks pattern + ApplicableType=3 ZERO Prod seed flag + PE Mig 22-26 reference template paths).
|
||||||
|
|
||||||
- **2026-05-22 (S28 wrap — Layer A governance internalized + tag schema mandatory forward):** S28 5-turn arc t1 RAG ROI verdict → t2 over-reach mistake → t4 bro caught → t5 Layer A active. Investigator agent absorb 3 rules forward: (1) **Tag schema mandatory** every RAG ingest `[<type>, phase-<N>, <bc-or-module>]` — type ∈ {bug, plan, audit, schema, ui-pattern, decision, retrospective, gotcha-doc}; phase enum 11 (`phase-9` UAT current); bc/module 8 domain {contract, pe, budget, master, identity, forms, notifications, audit-log} + 5 cross-cutting {cicd, infra, rag, governance, deploy}. (2) **4-category default + skip list** — skip ephemeral state ("S28 t3 startup ngày 22"), pure file paths không context, single-file < 5-line edit confirmation. Investigator output report tags MUST include all 3. (3) **NO self-authorize cross-project rule** — lesson S28 t4: stay scope-down SOLUTION_ERP self-discipline, don't generalize policy without bro consent. **Pre-flight audit query patterns Layer A §6.2** internalized: Pattern A `lookup_gotcha_<id>` (top_k=3, narrow), Pattern B `verify_precedent_pattern_X` (top_k=3 thay default 10 — broad similarity rerank surface 3 best matches), Pattern C `find_code_symbol_<class.method>` (top_k=5, symbol-anchor). **Weekly Friday eval ritual:** Investigator support Plan B Contract V2 wire pre-flight audit kick-off khi bro launch. Cumulative audit checklist (foundation cicd-monitor MEMORY S22+5→S23 t6 10-surface-point per-NV): (a) Contract entity V1 state inspect `Domain/Contracts/Contract.cs` + `WorkflowDefinitionId`, (b) Mig 22-23/26 PE V2 mirror reference confirm pattern reusable, (c) 4 PE V1-only audit migrate gap (per memory S22+4 Plan F ABORT), (d) 7 V1 contract scope drop boundary, (e) verify 10-surface-point per-NV checklist S22+5 cumulative reusable Contract V2 wire trước Mig 32 schema design. **source_path convention output:** `solution_erp/audit/investigator-<topic>-<date>` standardized cho tag schema BC=`pe` or `contract` switch per task. **Lesson abandoned:** S28 t2 "ghi RAG mọi tương tác" rule ABANDONED — Investigator KHÔNG mass-ingest, only specific findings with full tag schema. Audit retroactive S20-S26 "Investigator spawn" claims (memory log discrepancy registry NOT loaded vs token cost ghi 28-40K) defer S28+ priority #4 — uncertainty preserve cross-session, không retract memory unverified.
|
- **Archived to `archive/2026-05-q3.md` 2026-05-27 S34 curate (em main proxy):** S28 wrap Layer A governance internalized (3 rules + tag schema mandatory + 4-category default + NO self-authorize cross-project — absorbed forward into agent output discipline) + S27 wrap-up retrospective (pre-flight infrastructure audit must spawn Investigator lesson + S20-S26 spawn audit uncertainty flag) + 2026-05-22 curate session note + S25 wrap Bug 1+2 audit + 2026-05-11 setup baseline. KEY pattern absorbed forward: pre-flight infrastructure audit MUST spawn Investigator + scope-down SOLUTION_ERP self-discipline + tag schema `[type, phase, bc-or-module]` mandatory output.
|
||||||
|
|
||||||
- **2026-05-22 (S27 wrap-up em main proxy - retrospective Investigator):** Investigator pre-flight CRITICAL miss: nếu spawn pre-Plan A.3 (RAG manual control build) → would have catch Qdrant Web UI static missing PRE-em main write rag-dashboard.ps1 link `localhost:6333/dashboard`. Pattern reinforced: **pre-flight infrastructure audit MUST spawn Investigator** trước khi em main claim "X work" trong docs. Em main S27 SOLO miss external dependency check (Qdrant Web UI separate repo from binary). Anh pqhuy catch via UAT browser → escalate.
|
|
||||||
|
|
||||||
**Audit cumulative S20-S26 memory log entries claiming "Investigator spawn" - re-verify retroactive:** Per pitfall confirm spawn `investigator` agent type NOT FOUND trong session S27, có nghĩa registry chưa bao giờ load trong session này. Nhưng memory log S23 t1 K0 + S25 t1 audit Bug 1+2 + S26 Plan AG audit 5Q + Plan AI RAG research **đã ghi "spawn Investigator" với token cost 28-40K**. Possibility: (a) Previous sessions có registry load thành công (Claude Code version cũ hơn / file format khác / hot-reload sau /agents UI invoke), hoặc (b) Em main misattribute - actually spawn `general-purpose` agent default. Cần audit history Claude Code logs để xác minh - ngoài scope S27. **For now: trust prior memory entries but FLAG uncertainty cho future investigation.**
|
|
||||||
|
|
||||||
- **2026-05-22 (Curate session em main):** Archived 10 verbose Recent activity entries S21 → S24 Plan AA → `archive/2026-05-q1.md`. KEEP: S25 Bug audit + wrap, S26 Plan AG + Plan AI RAG, 2026-05-11 setup. Patterns proven section + Active workflow schemas section preserved. Memory size before: 34.9 KB → after: target ~20-22 KB.
|
|
||||||
|
|
||||||
- **2026-05-19 (S25 wrap — Plan AB Bug 1+2 audit + 6 follow-up plans em main solo):** Pre-Plan AB audit ~28K confirm root cause: Bug 1 Budget Adjust Handler ĐÃ log Changelog (Header+Update) nhưng FE HistoryTab filter strict TraLai-only loại. Bug 2 ApplyReturnModeAsync 4 mode KHÔNG add Changelog.Add() — chỉ caller LogTransitionAsync log phase transition. Recommended fix path: BE add log return mode + FE filter relax + Decision badge differentiation. **Em main solo từ Plan AC** (cross-stack reasoning Implementer would REFUSE). Patterns reusable Contract V2 audit recovery + Budget changelog UI: synthetic recovery FE merge + userMap fallback + drop misleading badges + preventive batch fix systemic gap. CICD result: 7 commits `e23f51c..506cada` push remote, runs #215 FAIL Plan M tests SQLite tie-break re-emerge → #216-#221 PASS streak. Gotcha #48 SQLite tie-break pending docs.
|
|
||||||
|
|
||||||
- **2026-05-11 (setup):** Investigator agent initialized. Baseline knowledge load complete (44 gotchas + 14 memory entries + 6 skills + 27 mig + 81 test pass cumulative). No investigations performed yet. Awaiting first SendMessage from em main.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -161,5 +151,5 @@ State machine 5 trạng thái phiếu PE: Nháp / Đã gửi duyệt / **Trả l
|
|||||||
- Duplicate entries detected → merge
|
- Duplicate entries detected → merge
|
||||||
- Stale > 3 months → remove
|
- Stale > 3 months → remove
|
||||||
|
|
||||||
**Last curate: 2026-05-26 S32 em main proxy curate** (post-S31 RAG fix) — archived 4 verbose entries (S25 t1 5Q audit detail + S26 Plan AG 5Q + S26 Plan AI RAG research + Plan B Contract V2 Q1-Q5 audit detail) → `archive/2026-05-q2.md`. KEEP: S32 startup (latest), S29 wrap summary, S28 Layer A governance, S27 retrospective, S25 wrap summary, S22 curate session, S11 setup baseline. Patterns + Active workflow schemas foundation preserved untouched. MEMORY size before: 27.7 KB → after: target ~21 KB. **Previous curate: 2026-05-22** — archived 10 verbose (S21 → S24 Plan AA) → `archive/2026-05-q1.md`. Next trigger: > 25KB OR Plan G-H1 kick off.
|
**Last curate: 2026-05-27 S34 em main proxy curate** (post-S33 wrap, sequence 1/4) — archived 4 verbose entries (S27 wrap retrospective + 2026-05-22 curate session + S25 wrap + S11 setup) → `archive/2026-05-q3.md`. KEEP: S33 t1 Plan G-H1 pre-flight NamGroup audit (latest 10 bảng inventory + 4 decision schema), S33 startup audit (4 MEMORY size + RAG hit verify), S32 wrap (Plan G 11 module backlog), S32 startup (S31 RAG awareness), S29 wrap (2 spawn Plan CA + Plan B pre-flight + 3 patterns NEW), S28 wrap (Layer A governance). Patterns + Active workflow schemas foundation preserved untouched. MEMORY size before: 26 KB → after: target ~20-22 KB. **Previous curate: 2026-05-26 S32** — 4 verbose entries → `archive/2026-05-q2.md`. **Previous curate: 2026-05-22** — 10 verbose S21→S24 → `archive/2026-05-q1.md`. Next trigger: > 25KB OR Plan G-O1 Danh bạ kick off.
|
||||||
|
|
||||||
|
|||||||
30
.claude/agent-memory/investigator/archive/2026-05-q3.md
Normal file
30
.claude/agent-memory/investigator/archive/2026-05-q3.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Investigator — Archive Q3 2026-05 (S34 curate)
|
||||||
|
|
||||||
|
> Verbose Recent activity entries archived from MEMORY.md S34 init (em main proxy curate 2026-05-27).
|
||||||
|
> Patterns proven + Active workflow schemas foundation preserved untouched in MEMORY.md.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-22 — S27 wrap-up retrospective em main proxy
|
||||||
|
|
||||||
|
Investigator pre-flight CRITICAL miss: nếu spawn pre-Plan A.3 (RAG manual control build) → would have catch Qdrant Web UI static missing PRE-em main write rag-dashboard.ps1 link `localhost:6333/dashboard`. Pattern reinforced: **pre-flight infrastructure audit MUST spawn Investigator** trước khi em main claim "X work" trong docs. Em main S27 SOLO miss external dependency check (Qdrant Web UI separate repo from binary). Anh pqhuy catch via UAT browser → escalate.
|
||||||
|
|
||||||
|
**Audit cumulative S20-S26 memory log entries claiming "Investigator spawn" - re-verify retroactive:** Per pitfall confirm spawn `investigator` agent type NOT FOUND trong session S27, có nghĩa registry chưa bao giờ load trong session này. Nhưng memory log S23 t1 K0 + S25 t1 audit Bug 1+2 + S26 Plan AG audit 5Q + Plan AI RAG research **đã ghi "spawn Investigator" với token cost 28-40K**. Possibility: (a) Previous sessions có registry load thành công (Claude Code version cũ hơn / file format khác / hot-reload sau /agents UI invoke), hoặc (b) Em main misattribute - actually spawn `general-purpose` agent default. Cần audit history Claude Code logs để xác minh - ngoài scope S27. **For now: trust prior memory entries but FLAG uncertainty cho future investigation.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-22 — Curate session em main S29 era
|
||||||
|
|
||||||
|
Archived 10 verbose Recent activity entries S21 → S24 Plan AA → `archive/2026-05-q1.md`. KEEP: S25 Bug audit + wrap, S26 Plan AG + Plan AI RAG, 2026-05-11 setup. Patterns proven section + Active workflow schemas section preserved. Memory size before: 34.9 KB → after: target ~20-22 KB.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-19 — S25 wrap Plan AB Bug 1+2 audit + 6 follow-up plans em main solo
|
||||||
|
|
||||||
|
Pre-Plan AB audit ~28K confirm root cause: Bug 1 Budget Adjust Handler ĐÃ log Changelog (Header+Update) nhưng FE HistoryTab filter strict TraLai-only loại. Bug 2 ApplyReturnModeAsync 4 mode KHÔNG add Changelog.Add() — chỉ caller LogTransitionAsync log phase transition. Recommended fix path: BE add log return mode + FE filter relax + Decision badge differentiation. **Em main solo từ Plan AC** (cross-stack reasoning Implementer would REFUSE). Patterns reusable Contract V2 audit recovery + Budget changelog UI: synthetic recovery FE merge + userMap fallback + drop misleading badges + preventive batch fix systemic gap. CICD result: 7 commits `e23f51c..506cada` push remote, runs #215 FAIL Plan M tests SQLite tie-break re-emerge → #216-#221 PASS streak. Gotcha #48 SQLite tie-break pending docs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-11 — Setup baseline
|
||||||
|
|
||||||
|
Investigator agent initialized. Baseline knowledge load complete (44 gotchas + 14 memory entries + 6 skills + 27 mig + 81 test pass cumulative). No investigations performed yet. Awaiting first SendMessage from em main.
|
||||||
@ -166,12 +166,7 @@ Flag commit nếu thấy `<PackageReference Include="MediatR" Version="14...` ho
|
|||||||
|
|
||||||
- **2026-05-22 (S29 wrap — Smart Friend 4× cumulative):** Plan CA (admin→eoffice 7 commits) + Plan B (Contract V2 11 commits) — 2 MAJOR catches Reviewer spawn. **CA MAJOR**: `DemoUserPassword = "User@123456"` 11 chars vs Identity policy ≥12 chars → new catalog.manager seed CreateAsync FAIL prod. Fix per-user inline conditional override `"CatalogMgr@2026"` 15 chars. **B MAJOR**: see above S29 Plan B entry. Smart Friend cumulative S22 #44 + S25 #48 + S29 CA password + S29 B ApplicableType. **Cat 3 Security checklist reinforced**: ApplicableType type guard V2 + password ≥12 chars + IsActive/IsUserSelectable re-validate. **Recommendation forward**: Reviewer spawn MANDATORY cho cross-module mirror diff (PE→Contract, PE→Budget V2 future, identity policy change). UI polish iteration em main solo OK.
|
- **2026-05-22 (S29 wrap — Smart Friend 4× cumulative):** Plan CA (admin→eoffice 7 commits) + Plan B (Contract V2 11 commits) — 2 MAJOR catches Reviewer spawn. **CA MAJOR**: `DemoUserPassword = "User@123456"` 11 chars vs Identity policy ≥12 chars → new catalog.manager seed CreateAsync FAIL prod. Fix per-user inline conditional override `"CatalogMgr@2026"` 15 chars. **B MAJOR**: see above S29 Plan B entry. Smart Friend cumulative S22 #44 + S25 #48 + S29 CA password + S29 B ApplicableType. **Cat 3 Security checklist reinforced**: ApplicableType type guard V2 + password ≥12 chars + IsActive/IsUserSelectable re-validate. **Recommendation forward**: Reviewer spawn MANDATORY cho cross-module mirror diff (PE→Contract, PE→Budget V2 future, identity policy change). UI polish iteration em main solo OK.
|
||||||
|
|
||||||
- **2026-05-22 (S29 Plan B Contract V2 wire pre-push — FAIL 1 MAJOR):** 9 commits `58898e8..14feb69` ~8.9K LOC. **MAJOR FOUND**: `CreateContractCommandHandler` accepts `ApprovalWorkflowId` from body but DOES NOT validate `aw.ApplicableType == Contract` — Drafter forge POST với PE/Budget V2 workflow ID → FK Restrict allows only Id existence → Contract pins wrong-scope workflow. Mirror PE pattern `PurchaseEvaluationFeatures.cs:62-77`. Hotfix ~10-12 LOC add validation guard, recommended HOLD push until fixed. Test gap deferred: ApproveV2Async ~150 LOC 0 unit test → Plan B-Wrap test bundle (S33 BW1-BW7 cover happy + terminal + skip F2 + outsider + V1 fallback + UNIQUE + UPSERT + Cascade). Detail archive `archive/2026-05-q1.md`.
|
- **Archived to `archive/2026-05-q2.md` 2026-05-27 S34 curate (em main proxy):** S29 Plan B pre-push detail (MAJOR catch ApplicableType validation — KEY lesson absorbed Cross-module security mirror foundation line 41-49) + S26 Plan AG pre-commit + AG2-AG6 em main solo + S25 Plan AB wrap (gotcha #48 lesson — foundation line 40-45) + S28 wrap Layer A governance Cat 6 add (absorbed forward 5-category baseline). Smart Friend guard 4× cumulative S22+S25+S29×2 preserved in current S33 entries.
|
||||||
|
|
||||||
- **2026-05-21 (S26 Plan AG pre-commit + AG2-AG6 em main solo):** Plan AG Chunk A+B+C verify spawn ~25K, 12 adversarial deep check PASS 0 issue. Commit `0bf6c7e` 2 file +346/-116 LOC mirror IDENTICAL `21001E90...`. Wire: useMemo group nested + `<details>/<summary>` 2-level + localStorage Set persist. Schema 0 mig. AG2-AG6 (5 follow-up polish UAT feedback bro Tra Sol) em main solo verify (SHA256 IDENTICAL + npm build × 2 + dotnet test 111/111) — KHÔNG re-spawn Reviewer (ROI thấp UI polish 50-100 LOC). **Pattern reinforced**: Reviewer spawn cho heavy cross-stack (A+B+C ~370 LOC + 4 sub-agent collab), em main solo cho polish iteration. Cumulative S26: 6 commits, 0 prod regression, baseline 111 preserved.
|
|
||||||
|
|
||||||
- **2026-05-19 (S25 Plan AB + wrap):** Archived to `archive/2026-05-q1.md` — keywords: gotcha #48 SQLite frozen clock tie-break (Multi-Changelog.Add same SaveChangesAsync transaction non-deterministic `OrderByDescending(CreatedAt).FirstAsync()`), UAT skip `dotnet test` recurring risk khi BE refactor > 100 LOC, ApplyReturnModeAsync refactor cdfd542 PE Budget Adjust + Trả lại Người chỉ định log. Cat 5 checklist add: test filter discriminator beyond timestamp (EntityType + Summary keyword).
|
|
||||||
- **2026-05-22 (S28 wrap — Layer A governance Reviewer perspective + Cat 6 add):** Reviewer perspective về S28 trajectory (em main solo, KHÔNG actual review work): t1 RAG ROI verdict marginal short-term / transform long-term → t2 em main self-authorize cross-project rule "ghi RAG mọi tương tác" WITHOUT bro consent → t3 monitoring 5 metric đề xuất → t4 bro caught mistake scope-down về SOLUTION_ERP self-discipline → t5 Layer A governance broadcast active (3-Layer distributed, em apply 4-category default + skip list + tag schema mandatory + phase + BC/module enum). **Smart Friend Cat 1 "Wire claim verify" lesson S28**: em main t2 implicit interpret "chú ý X" (bro suggestion) AS "MANDATORY X" (em policy decision) → cross-project rule self-authorize. Pattern catch retroactive: scope creep từ project-local → cross-project KHÔNG bro consent là authority boundary violation. Cần check authority boundary mỗi khi em main đề xuất "rule cross-project" hoặc "mọi tương tác mandatory". **Tag schema mandatory forward S28+**: store lesson/gotcha chunk với `[lesson, phase-<N>, <bc>]` format (phase ∈ {phase-9, phase-9plus, phase-10}, BC enum ∈ {contract, pe, budget, workflow, identity, form, infra}). **Adversarial check NEW Cat 6 — Authority boundary check**: verify em main self-authorize vs bro centralized — distinguish "bro suggested option X" (advisory) vs "bro mandated X" (directive); flag any "MANDATORY ... cross-project" sourced từ em main self-decision. 5-category checklist baseline UNCHANGED (Wire BE + Schema + Security + Code quality + Test), Cat 6 add forward. **Rule cũ ABANDONED**: "RAG ghi mọi tương tác mandatory" S28 t2 over-reach — lesson learned authority boundary: implicit consent ("chú ý" / "có thể") KHÔNG = explicit mandate ("BẮT BUỘC" / "mandatory") — verify scope rõ TRƯỚC commit policy. Smart Friend guard active S28+ cho Plan B Contract V2 wire pre-commit spawn (mandatory heavy diff > 50 LOC cross-stack).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -181,4 +176,4 @@ Flag commit nếu thấy `<PackageReference Include="MediatR" Version="14...` ho
|
|||||||
- Duplicate entries detected → merge
|
- Duplicate entries detected → merge
|
||||||
- Stale > 3 months → remove
|
- Stale > 3 months → remove
|
||||||
|
|
||||||
**Last curate: 2026-05-26 S32 startup drop oldest entry** — dropped 2026-05-22 S27 retrospective entry (Qdrant 404 lesson — preserved redundantly trong S29 wrap "Self-review bias" reference + already in archive 2026-05-q1.md). Reason: S32 entry added → size cross 25KB threshold (25.23KB) → maintain 10-FIFO ceiling. KEEP: S32 startup (new), S29 wrap (2 MAJOR catches), S29 Plan B pre-push detail, S26 Plan AG, S25 wrap, S25 Plan AB pre-push, S28 governance. 5-category checklist (Cat 3 Security ApplicableType + password ≥12 chars reinforced S29) + Smart Friend guard 4× cumulative + Cross-module security validation mirror pattern (NEW S29 foundation) preserved. Next curate trigger: > 25KB OR Plan B Wrap test bundle complete OR Budget V2 wire start OR Phase 9 UAT hard blocker audit spawn.
|
**Last curate: 2026-05-27 S34 em main proxy curate** (post-S33 wrap, sequence 1/4) — archived 3 verbose entries (S26 Plan AG + S25 Plan AB wrap + S28 Layer A governance Cat 6 add) → `archive/2026-05-q2.md`. KEEP: S33 Plan B G-H1 Phase 2 pre-commit (Smart Friend 6× clean), S33 Plan C B-Wrap pre-commit (9/9 [Fact] verified), S33 startup drift audit (CLAUDE.md SEVERE), S32 wrap (Plan B-Wrap pre-commit scope ahead), S32 startup, S29 wrap (Smart Friend 4× cumulative), S29 Plan B pre-push (1 MAJOR catch ApplicableType). 5-category checklist + Cat 6 Authority boundary check + Smart Friend guard + Cross-module security validation mirror pattern (NEW S29 foundation) preserved. MEMORY size before: 28.5 KB → after: target ~20-22 KB. **Previous curate: 2026-05-26 S32 startup** drop S27 retrospective. Next curate trigger: > 25KB OR Plan G-O1 Danh bạ kick off.
|
||||||
|
|||||||
22
.claude/agent-memory/reviewer/archive/2026-05-q2.md
Normal file
22
.claude/agent-memory/reviewer/archive/2026-05-q2.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Reviewer — Archive Q2 2026-05 (S34 curate)
|
||||||
|
|
||||||
|
> Verbose Recent activity entries archived from MEMORY.md S34 init (em main proxy curate 2026-05-27).
|
||||||
|
> Foundation gotchas #17-#48 + Smart Friend guard + 5-category + Cat 6 + Cross-module security validation mirror pattern preserved untouched in MEMORY.md.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-21 — S26 Plan AG pre-commit + AG2-AG6 em main solo
|
||||||
|
|
||||||
|
Plan AG Chunk A+B+C verify spawn ~25K, 12 adversarial deep check PASS 0 issue. Commit `0bf6c7e` 2 file +346/-116 LOC mirror IDENTICAL `21001E90...`. Wire: useMemo group nested + `<details>/<summary>` 2-level + localStorage Set persist. Schema 0 mig. AG2-AG6 (5 follow-up polish UAT feedback bro Tra Sol) em main solo verify (SHA256 IDENTICAL + npm build × 2 + dotnet test 111/111) — KHÔNG re-spawn Reviewer (ROI thấp UI polish 50-100 LOC). **Pattern reinforced**: Reviewer spawn cho heavy cross-stack (A+B+C ~370 LOC + 4 sub-agent collab), em main solo cho polish iteration. Cumulative S26: 6 commits, 0 prod regression, baseline 111 preserved.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-19 — S25 Plan AB + wrap
|
||||||
|
|
||||||
|
Keywords: gotcha #48 SQLite frozen clock tie-break (Multi-Changelog.Add same SaveChangesAsync transaction non-deterministic `OrderByDescending(CreatedAt).FirstAsync()`), UAT skip `dotnet test` recurring risk khi BE refactor > 100 LOC, ApplyReturnModeAsync refactor cdfd542 PE Budget Adjust + Trả lại Người chỉ định log. Cat 5 checklist add: test filter discriminator beyond timestamp (EntityType + Summary keyword).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-22 — S28 wrap Layer A governance Reviewer perspective + Cat 6 add
|
||||||
|
|
||||||
|
Reviewer perspective về S28 trajectory (em main solo, KHÔNG actual review work): t1 RAG ROI verdict marginal short-term / transform long-term → t2 em main self-authorize cross-project rule "ghi RAG mọi tương tác" WITHOUT bro consent → t3 monitoring 5 metric đề xuất → t4 bro caught mistake scope-down về SOLUTION_ERP self-discipline → t5 Layer A governance broadcast active (3-Layer distributed, em apply 4-category default + skip list + tag schema mandatory + phase + BC/module enum). **Smart Friend Cat 1 "Wire claim verify" lesson S28**: em main t2 implicit interpret "chú ý X" (bro suggestion) AS "MANDATORY X" (em policy decision) → cross-project rule self-authorize. Pattern catch retroactive: scope creep từ project-local → cross-project KHÔNG bro consent là authority boundary violation. Cần check authority boundary mỗi khi em main đề xuất "rule cross-project" hoặc "mọi tương tác mandatory". **Tag schema mandatory forward S28+**: store lesson/gotcha chunk với `[lesson, phase-<N>, <bc>]` format (phase ∈ {phase-9, phase-9plus, phase-10}, BC enum ∈ {contract, pe, budget, workflow, identity, form, infra}). **Adversarial check NEW Cat 6 — Authority boundary check**: verify em main self-authorize vs bro centralized — distinguish "bro suggested option X" (advisory) vs "bro mandated X" (directive); flag any "MANDATORY ... cross-project" sourced từ em main self-decision. 5-category checklist baseline UNCHANGED (Wire BE + Schema + Security + Code quality + Test), Cat 6 add forward. **Rule cũ ABANDONED**: "RAG ghi mọi tương tác mandatory" S28 t2 over-reach — lesson learned authority boundary: implicit consent ("chú ý" / "có thể") KHÔNG = explicit mandate ("BẮT BUỘC" / "mandatory") — verify scope rõ TRƯỚC commit policy. Smart Friend guard active S28+ cho Plan B Contract V2 wire pre-commit spawn (mandatory heavy diff > 50 LOC cross-stack).
|
||||||
@ -28,6 +28,7 @@ import { BudgetsListPage, BudgetDetailPage } from '@/pages/budgets/BudgetsListPa
|
|||||||
import { BudgetCreatePage } from '@/pages/budgets/BudgetCreatePage'
|
import { BudgetCreatePage } from '@/pages/budgets/BudgetCreatePage'
|
||||||
import { EmployeesListPage } from '@/pages/hrm/EmployeesListPage'
|
import { EmployeesListPage } from '@/pages/hrm/EmployeesListPage'
|
||||||
import { EmployeeCreatePage } from '@/pages/hrm/EmployeeCreatePage'
|
import { EmployeeCreatePage } from '@/pages/hrm/EmployeeCreatePage'
|
||||||
|
import { InternalDirectoryPage } from '@/pages/office/InternalDirectoryPage'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@ -73,6 +74,8 @@ function App() {
|
|||||||
{/* Hồ sơ Nhân sự (Phase 10.1 G-H1 — Mig 34) */}
|
{/* Hồ sơ Nhân sự (Phase 10.1 G-H1 — Mig 34) */}
|
||||||
<Route path="/employees" element={<EmployeesListPage />} />
|
<Route path="/employees" element={<EmployeesListPage />} />
|
||||||
<Route path="/employees/new" element={<EmployeeCreatePage />} />
|
<Route path="/employees/new" element={<EmployeeCreatePage />} />
|
||||||
|
{/* Văn phòng số — Danh bạ nội bộ (Phase 10.2 G-O1) */}
|
||||||
|
<Route path="/directory" element={<InternalDirectoryPage />} />
|
||||||
<Route path="/reports" element={<ReportsPage />} />
|
<Route path="/reports" element={<ReportsPage />} />
|
||||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@ -55,6 +55,9 @@ function resolvePath(key: string): string | null {
|
|||||||
// Plan CA Hotfix 1 gotcha #50: PHẢI mirror staticMap khi thêm page mới
|
// Plan CA Hotfix 1 gotcha #50: PHẢI mirror staticMap khi thêm page mới
|
||||||
// — nếu thiếu, MenuLeaf line ~198 `if (!path) return null` → sidebar drop silent.
|
// — nếu thiếu, MenuLeaf line ~198 `if (!path) return null` → sidebar drop silent.
|
||||||
Hrm_HoSo: '/employees',
|
Hrm_HoSo: '/employees',
|
||||||
|
// [Phase 10.2 G-O1 S34 2026-05-27] Module Văn phòng số — Danh bạ nội bộ.
|
||||||
|
// 4-place mirror Pattern 16-bis: types/ + pages/ + App.tsx + menuKeys + staticMap.
|
||||||
|
Off_DanhBa: '/directory',
|
||||||
}
|
}
|
||||||
if (staticMap[key]) return staticMap[key]
|
if (staticMap[key]) return staticMap[key]
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,9 @@ export const MenuKeys = {
|
|||||||
// Module Hồ sơ Nhân sự (Mig 34 — Phase 10.1 G-H1 Session 33, 2026-05-26)
|
// Module Hồ sơ Nhân sự (Mig 34 — Phase 10.1 G-H1 Session 33, 2026-05-26)
|
||||||
Hrm: 'Hrm',
|
Hrm: 'Hrm',
|
||||||
HrmHoSo: 'Hrm_HoSo',
|
HrmHoSo: 'Hrm_HoSo',
|
||||||
|
// Module Văn phòng số — Danh bạ nội bộ (Phase 10.2 G-O1 Session 34, 2026-05-27)
|
||||||
|
Off: 'Off',
|
||||||
|
OffDanhBa: 'Off_DanhBa',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type MenuKey = typeof MenuKeys[keyof typeof MenuKeys]
|
export type MenuKey = typeof MenuKeys[keyof typeof MenuKeys]
|
||||||
|
|||||||
236
fe-admin/src/pages/office/InternalDirectoryPage.tsx
Normal file
236
fe-admin/src/pages/office/InternalDirectoryPage.tsx
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
// Danh bạ nội bộ (Internal Directory) — Phase 10.2 G-O1 (S34 2026-05-27).
|
||||||
|
// Card grid responsive, filter search + department, avatar fallback gradient theo
|
||||||
|
// userId hash stable. File này MIRROR SHA256 identical với fe-admin counterpart.
|
||||||
|
// Reuse BE GET /api/directory readonly (DirectoryFeatures.cs).
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
|
import { Mail, Phone, Search, UserCircle2, Users } from 'lucide-react'
|
||||||
|
import { PageHeader } from '@/components/PageHeader'
|
||||||
|
import { EmptyState } from '@/components/EmptyState'
|
||||||
|
import { Input } from '@/components/ui/Input'
|
||||||
|
import { Select } from '@/components/ui/Select'
|
||||||
|
import { api } from '@/lib/api'
|
||||||
|
import { cn } from '@/lib/cn'
|
||||||
|
import type { Paged, Department } from '@/types/master'
|
||||||
|
import type { DirectoryItem } from '@/types/directory'
|
||||||
|
|
||||||
|
// Pattern 14 Tailwind JIT palette 6 màu gradient cho initials avatar.
|
||||||
|
const AVATAR_PALETTE = [
|
||||||
|
'bg-gradient-to-br from-blue-400 to-blue-600',
|
||||||
|
'bg-gradient-to-br from-emerald-400 to-emerald-600',
|
||||||
|
'bg-gradient-to-br from-amber-400 to-amber-600',
|
||||||
|
'bg-gradient-to-br from-violet-400 to-violet-600',
|
||||||
|
'bg-gradient-to-br from-rose-400 to-rose-600',
|
||||||
|
'bg-gradient-to-br from-cyan-400 to-cyan-600',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
// Format SĐT Vietnam 10 chữ số: 0XXX XXX XXX
|
||||||
|
function formatPhone(p: string | null): string {
|
||||||
|
if (!p) return ''
|
||||||
|
const digits = p.replace(/\D/g, '')
|
||||||
|
if (digits.length === 10) return digits.replace(/(\d{4})(\d{3})(\d{3})/, '$1 $2 $3')
|
||||||
|
if (digits.length === 11) return digits.replace(/(\d{4})(\d{3})(\d{4})/, '$1 $2 $3')
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash stable userId → palette index (charCodeAt sum mod 6).
|
||||||
|
function avatarColor(userId: string): string {
|
||||||
|
let sum = 0
|
||||||
|
for (let i = 0; i < userId.length; i++) sum += userId.charCodeAt(i)
|
||||||
|
return AVATAR_PALETTE[sum % AVATAR_PALETTE.length]
|
||||||
|
}
|
||||||
|
|
||||||
|
// First letter của FullName (uppercase, fallback "?").
|
||||||
|
function initials(fullName: string): string {
|
||||||
|
const trimmed = fullName.trim()
|
||||||
|
if (!trimmed) return '?'
|
||||||
|
return trimmed.charAt(0).toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InternalDirectoryPage() {
|
||||||
|
const [sp, setSp] = useSearchParams()
|
||||||
|
const search = sp.get('q') ?? ''
|
||||||
|
const departmentId = sp.get('deptId') ?? ''
|
||||||
|
|
||||||
|
const [localSearch, setLocalSearch] = useState(search)
|
||||||
|
|
||||||
|
const departments = useQuery({
|
||||||
|
queryKey: ['departments-all-directory'],
|
||||||
|
queryFn: async () =>
|
||||||
|
(await api.get<Paged<Department>>('/departments', { params: { page: 1, pageSize: 200 } })).data.items,
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = useQuery({
|
||||||
|
queryKey: ['directory', { search, departmentId }],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await api.get<DirectoryItem[]>('/directory', {
|
||||||
|
params: {
|
||||||
|
search: search || undefined,
|
||||||
|
departmentId: departmentId || undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return res.data
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function setParam(key: string, value: string | null) {
|
||||||
|
const next = new URLSearchParams(sp)
|
||||||
|
if (value == null || value === '') next.delete(key)
|
||||||
|
else next.set(key, value)
|
||||||
|
setSp(next, { replace: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySearch(e: React.FormEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
setParam('q', localSearch.trim() || null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = list.data?.length ?? 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6">
|
||||||
|
<PageHeader
|
||||||
|
title="Danh bạ nội bộ"
|
||||||
|
description={list.isLoading ? 'Đang tải...' : `${total} nhân viên`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Filter bar sticky top */}
|
||||||
|
<div className="sticky top-0 z-10 mb-4 flex flex-col gap-2 rounded-lg border border-slate-200 bg-white/95 p-3 shadow-sm backdrop-blur sm:flex-row sm:items-center">
|
||||||
|
<form onSubmit={applySearch} className="relative flex-1">
|
||||||
|
<Search className="pointer-events-none absolute left-2.5 top-2.5 h-4 w-4 text-slate-400" />
|
||||||
|
<Input
|
||||||
|
value={localSearch}
|
||||||
|
onChange={e => setLocalSearch(e.target.value)}
|
||||||
|
onBlur={() => setParam('q', localSearch.trim() || null)}
|
||||||
|
placeholder="Tìm tên / email / SĐT / mã NV..."
|
||||||
|
className="pl-8"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<Select
|
||||||
|
value={departmentId}
|
||||||
|
onChange={e => setParam('deptId', e.target.value || null)}
|
||||||
|
className="sm:w-64"
|
||||||
|
>
|
||||||
|
<option value="">Tất cả phòng ban</option>
|
||||||
|
{(departments.data ?? []).map(d => (
|
||||||
|
<option key={d.id} value={d.id}>{d.name}</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card grid */}
|
||||||
|
{list.isLoading ? (
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<div key={i} className="h-44 animate-pulse rounded-lg border border-slate-200 bg-slate-100" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : total === 0 ? (
|
||||||
|
<EmptyState
|
||||||
|
icon={Users}
|
||||||
|
title="Không tìm thấy nhân viên nào"
|
||||||
|
description="Thử đổi từ khoá tìm hoặc chọn phòng ban khác."
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
|
{list.data!.map(item => (
|
||||||
|
<DirectoryCard key={item.userId} item={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DirectoryCard({ item }: { item: DirectoryItem }) {
|
||||||
|
return (
|
||||||
|
<div className="rounded-lg border border-slate-200 bg-white p-4 shadow-sm transition hover:shadow-md">
|
||||||
|
{/* Top row: avatar + name + code */}
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
{item.photoUrl ? (
|
||||||
|
<img
|
||||||
|
src={item.photoUrl}
|
||||||
|
alt={item.fullName}
|
||||||
|
className="h-14 w-14 shrink-0 rounded-full object-cover ring-2 ring-white shadow"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex h-14 w-14 shrink-0 items-center justify-center rounded-full text-xl font-bold text-white shadow ring-2 ring-white',
|
||||||
|
avatarColor(item.userId),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{initials(item.fullName)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="flex items-baseline gap-1.5">
|
||||||
|
<h3 className="truncate text-sm font-semibold text-slate-900" title={item.fullName}>
|
||||||
|
{item.fullName}
|
||||||
|
</h3>
|
||||||
|
{item.employeeCode && (
|
||||||
|
<span className="shrink-0 rounded bg-slate-100 px-1.5 py-0.5 font-mono text-[10px] text-slate-600">
|
||||||
|
{item.employeeCode}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{item.position && (
|
||||||
|
<p className="mt-0.5 truncate text-xs text-slate-600" title={item.position}>
|
||||||
|
{item.position}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{item.departmentName && (
|
||||||
|
<span className="mt-1 inline-flex rounded bg-emerald-100 px-2 py-0.5 text-[11px] font-medium text-emerald-900">
|
||||||
|
{item.departmentName}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact rows */}
|
||||||
|
<div className="mt-3 space-y-1.5 border-t border-slate-100 pt-3 text-xs">
|
||||||
|
{item.email ? (
|
||||||
|
<a
|
||||||
|
href={`mailto:${item.email}`}
|
||||||
|
className="flex items-center gap-1.5 text-slate-700 hover:text-brand-700 hover:underline"
|
||||||
|
title={item.email}
|
||||||
|
>
|
||||||
|
<Mail className="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
||||||
|
<span className="truncate">{item.email}</span>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-1.5 text-slate-400">
|
||||||
|
<Mail className="h-3.5 w-3.5 shrink-0" />
|
||||||
|
<span>—</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.phone ? (
|
||||||
|
<a
|
||||||
|
href={`tel:${item.phone}`}
|
||||||
|
className="flex items-center gap-1.5 text-slate-700 hover:text-brand-700 hover:underline"
|
||||||
|
title={item.phone}
|
||||||
|
>
|
||||||
|
<Phone className="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
||||||
|
<span className="truncate">{formatPhone(item.phone)}</span>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-1.5 text-slate-400">
|
||||||
|
<Phone className="h-3.5 w-3.5 shrink-0" />
|
||||||
|
<span>—</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.internalPhone && (
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<UserCircle2 className="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
||||||
|
<span className="inline-flex rounded bg-amber-100 px-1.5 py-0.5 font-mono text-[10px] font-medium text-amber-900">
|
||||||
|
Ext: {item.internalPhone}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
fe-admin/src/types/directory.ts
Normal file
22
fe-admin/src/types/directory.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Danh bạ nội bộ (Internal Directory) — Phase 10.2 G-O1 (S34 2026-05-27).
|
||||||
|
// Mirror BE DirectoryItemDto (Application/Office/DirectoryFeatures.cs).
|
||||||
|
// File này MIRROR SHA256 identical với fe-admin/src/types/directory.ts.
|
||||||
|
export type DirectoryItem = {
|
||||||
|
userId: string
|
||||||
|
fullName: string
|
||||||
|
position: string | null
|
||||||
|
photoUrl: string | null
|
||||||
|
departmentId: string | null
|
||||||
|
departmentName: string | null
|
||||||
|
employeeCode: string | null
|
||||||
|
email: string | null
|
||||||
|
phone: string | null
|
||||||
|
internalPhone: string | null
|
||||||
|
personalEmail: string | null
|
||||||
|
workLocation: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DirectoryQuery = {
|
||||||
|
search?: string
|
||||||
|
departmentId?: string
|
||||||
|
}
|
||||||
@ -21,6 +21,7 @@ import { BudgetsListPage, BudgetDetailPage } from '@/pages/budgets/BudgetsListPa
|
|||||||
import { BudgetCreatePage } from '@/pages/budgets/BudgetCreatePage'
|
import { BudgetCreatePage } from '@/pages/budgets/BudgetCreatePage'
|
||||||
import { EmployeesListPage } from '@/pages/hrm/EmployeesListPage'
|
import { EmployeesListPage } from '@/pages/hrm/EmployeesListPage'
|
||||||
import { EmployeeCreatePage } from '@/pages/hrm/EmployeeCreatePage'
|
import { EmployeeCreatePage } from '@/pages/hrm/EmployeeCreatePage'
|
||||||
|
import { InternalDirectoryPage } from '@/pages/office/InternalDirectoryPage'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@ -56,6 +57,8 @@ function App() {
|
|||||||
{/* Hồ sơ Nhân sự (Phase 10.1 G-H1 — Mig 34) */}
|
{/* Hồ sơ Nhân sự (Phase 10.1 G-H1 — Mig 34) */}
|
||||||
<Route path="/employees" element={<EmployeesListPage />} />
|
<Route path="/employees" element={<EmployeesListPage />} />
|
||||||
<Route path="/employees/new" element={<EmployeeCreatePage />} />
|
<Route path="/employees/new" element={<EmployeeCreatePage />} />
|
||||||
|
{/* Văn phòng số — Danh bạ nội bộ (Phase 10.2 G-O1) */}
|
||||||
|
<Route path="/directory" element={<InternalDirectoryPage />} />
|
||||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
<Route
|
<Route
|
||||||
path="*"
|
path="*"
|
||||||
|
|||||||
@ -77,6 +77,9 @@ function resolvePath(key: string): string | null {
|
|||||||
// Plan CA Hotfix 1 gotcha #50: PHẢI mirror staticMap khi thêm page mới
|
// Plan CA Hotfix 1 gotcha #50: PHẢI mirror staticMap khi thêm page mới
|
||||||
// — nếu thiếu, MenuLeaf line ~250 `if (!path) return null` → sidebar drop silent.
|
// — nếu thiếu, MenuLeaf line ~250 `if (!path) return null` → sidebar drop silent.
|
||||||
Hrm_HoSo: '/employees',
|
Hrm_HoSo: '/employees',
|
||||||
|
// [Phase 10.2 G-O1 S34 2026-05-27] Module Văn phòng số — Danh bạ nội bộ.
|
||||||
|
// 4-place mirror Pattern 16-bis: types/ + pages/ + App.tsx + menuKeys + staticMap.
|
||||||
|
Off_DanhBa: '/directory',
|
||||||
}
|
}
|
||||||
if (staticMap[key]) return staticMap[key]
|
if (staticMap[key]) return staticMap[key]
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,9 @@ export const MenuKeys = {
|
|||||||
// Module Hồ sơ Nhân sự (Mig 34 — Phase 10.1 G-H1 Session 33, 2026-05-26)
|
// Module Hồ sơ Nhân sự (Mig 34 — Phase 10.1 G-H1 Session 33, 2026-05-26)
|
||||||
Hrm: 'Hrm',
|
Hrm: 'Hrm',
|
||||||
HrmHoSo: 'Hrm_HoSo',
|
HrmHoSo: 'Hrm_HoSo',
|
||||||
|
// Module Văn phòng số — Danh bạ nội bộ (Phase 10.2 G-O1 Session 34, 2026-05-27)
|
||||||
|
Off: 'Off',
|
||||||
|
OffDanhBa: 'Off_DanhBa',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type MenuKey = typeof MenuKeys[keyof typeof MenuKeys]
|
export type MenuKey = typeof MenuKeys[keyof typeof MenuKeys]
|
||||||
|
|||||||
236
fe-user/src/pages/office/InternalDirectoryPage.tsx
Normal file
236
fe-user/src/pages/office/InternalDirectoryPage.tsx
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
// Danh bạ nội bộ (Internal Directory) — Phase 10.2 G-O1 (S34 2026-05-27).
|
||||||
|
// Card grid responsive, filter search + department, avatar fallback gradient theo
|
||||||
|
// userId hash stable. File này MIRROR SHA256 identical với fe-admin counterpart.
|
||||||
|
// Reuse BE GET /api/directory readonly (DirectoryFeatures.cs).
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
|
import { Mail, Phone, Search, UserCircle2, Users } from 'lucide-react'
|
||||||
|
import { PageHeader } from '@/components/PageHeader'
|
||||||
|
import { EmptyState } from '@/components/EmptyState'
|
||||||
|
import { Input } from '@/components/ui/Input'
|
||||||
|
import { Select } from '@/components/ui/Select'
|
||||||
|
import { api } from '@/lib/api'
|
||||||
|
import { cn } from '@/lib/cn'
|
||||||
|
import type { Paged, Department } from '@/types/master'
|
||||||
|
import type { DirectoryItem } from '@/types/directory'
|
||||||
|
|
||||||
|
// Pattern 14 Tailwind JIT palette 6 màu gradient cho initials avatar.
|
||||||
|
const AVATAR_PALETTE = [
|
||||||
|
'bg-gradient-to-br from-blue-400 to-blue-600',
|
||||||
|
'bg-gradient-to-br from-emerald-400 to-emerald-600',
|
||||||
|
'bg-gradient-to-br from-amber-400 to-amber-600',
|
||||||
|
'bg-gradient-to-br from-violet-400 to-violet-600',
|
||||||
|
'bg-gradient-to-br from-rose-400 to-rose-600',
|
||||||
|
'bg-gradient-to-br from-cyan-400 to-cyan-600',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
// Format SĐT Vietnam 10 chữ số: 0XXX XXX XXX
|
||||||
|
function formatPhone(p: string | null): string {
|
||||||
|
if (!p) return ''
|
||||||
|
const digits = p.replace(/\D/g, '')
|
||||||
|
if (digits.length === 10) return digits.replace(/(\d{4})(\d{3})(\d{3})/, '$1 $2 $3')
|
||||||
|
if (digits.length === 11) return digits.replace(/(\d{4})(\d{3})(\d{4})/, '$1 $2 $3')
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash stable userId → palette index (charCodeAt sum mod 6).
|
||||||
|
function avatarColor(userId: string): string {
|
||||||
|
let sum = 0
|
||||||
|
for (let i = 0; i < userId.length; i++) sum += userId.charCodeAt(i)
|
||||||
|
return AVATAR_PALETTE[sum % AVATAR_PALETTE.length]
|
||||||
|
}
|
||||||
|
|
||||||
|
// First letter của FullName (uppercase, fallback "?").
|
||||||
|
function initials(fullName: string): string {
|
||||||
|
const trimmed = fullName.trim()
|
||||||
|
if (!trimmed) return '?'
|
||||||
|
return trimmed.charAt(0).toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InternalDirectoryPage() {
|
||||||
|
const [sp, setSp] = useSearchParams()
|
||||||
|
const search = sp.get('q') ?? ''
|
||||||
|
const departmentId = sp.get('deptId') ?? ''
|
||||||
|
|
||||||
|
const [localSearch, setLocalSearch] = useState(search)
|
||||||
|
|
||||||
|
const departments = useQuery({
|
||||||
|
queryKey: ['departments-all-directory'],
|
||||||
|
queryFn: async () =>
|
||||||
|
(await api.get<Paged<Department>>('/departments', { params: { page: 1, pageSize: 200 } })).data.items,
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = useQuery({
|
||||||
|
queryKey: ['directory', { search, departmentId }],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await api.get<DirectoryItem[]>('/directory', {
|
||||||
|
params: {
|
||||||
|
search: search || undefined,
|
||||||
|
departmentId: departmentId || undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return res.data
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function setParam(key: string, value: string | null) {
|
||||||
|
const next = new URLSearchParams(sp)
|
||||||
|
if (value == null || value === '') next.delete(key)
|
||||||
|
else next.set(key, value)
|
||||||
|
setSp(next, { replace: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySearch(e: React.FormEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
setParam('q', localSearch.trim() || null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = list.data?.length ?? 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6">
|
||||||
|
<PageHeader
|
||||||
|
title="Danh bạ nội bộ"
|
||||||
|
description={list.isLoading ? 'Đang tải...' : `${total} nhân viên`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Filter bar sticky top */}
|
||||||
|
<div className="sticky top-0 z-10 mb-4 flex flex-col gap-2 rounded-lg border border-slate-200 bg-white/95 p-3 shadow-sm backdrop-blur sm:flex-row sm:items-center">
|
||||||
|
<form onSubmit={applySearch} className="relative flex-1">
|
||||||
|
<Search className="pointer-events-none absolute left-2.5 top-2.5 h-4 w-4 text-slate-400" />
|
||||||
|
<Input
|
||||||
|
value={localSearch}
|
||||||
|
onChange={e => setLocalSearch(e.target.value)}
|
||||||
|
onBlur={() => setParam('q', localSearch.trim() || null)}
|
||||||
|
placeholder="Tìm tên / email / SĐT / mã NV..."
|
||||||
|
className="pl-8"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<Select
|
||||||
|
value={departmentId}
|
||||||
|
onChange={e => setParam('deptId', e.target.value || null)}
|
||||||
|
className="sm:w-64"
|
||||||
|
>
|
||||||
|
<option value="">Tất cả phòng ban</option>
|
||||||
|
{(departments.data ?? []).map(d => (
|
||||||
|
<option key={d.id} value={d.id}>{d.name}</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card grid */}
|
||||||
|
{list.isLoading ? (
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<div key={i} className="h-44 animate-pulse rounded-lg border border-slate-200 bg-slate-100" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : total === 0 ? (
|
||||||
|
<EmptyState
|
||||||
|
icon={Users}
|
||||||
|
title="Không tìm thấy nhân viên nào"
|
||||||
|
description="Thử đổi từ khoá tìm hoặc chọn phòng ban khác."
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
|
{list.data!.map(item => (
|
||||||
|
<DirectoryCard key={item.userId} item={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DirectoryCard({ item }: { item: DirectoryItem }) {
|
||||||
|
return (
|
||||||
|
<div className="rounded-lg border border-slate-200 bg-white p-4 shadow-sm transition hover:shadow-md">
|
||||||
|
{/* Top row: avatar + name + code */}
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
{item.photoUrl ? (
|
||||||
|
<img
|
||||||
|
src={item.photoUrl}
|
||||||
|
alt={item.fullName}
|
||||||
|
className="h-14 w-14 shrink-0 rounded-full object-cover ring-2 ring-white shadow"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex h-14 w-14 shrink-0 items-center justify-center rounded-full text-xl font-bold text-white shadow ring-2 ring-white',
|
||||||
|
avatarColor(item.userId),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{initials(item.fullName)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="flex items-baseline gap-1.5">
|
||||||
|
<h3 className="truncate text-sm font-semibold text-slate-900" title={item.fullName}>
|
||||||
|
{item.fullName}
|
||||||
|
</h3>
|
||||||
|
{item.employeeCode && (
|
||||||
|
<span className="shrink-0 rounded bg-slate-100 px-1.5 py-0.5 font-mono text-[10px] text-slate-600">
|
||||||
|
{item.employeeCode}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{item.position && (
|
||||||
|
<p className="mt-0.5 truncate text-xs text-slate-600" title={item.position}>
|
||||||
|
{item.position}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{item.departmentName && (
|
||||||
|
<span className="mt-1 inline-flex rounded bg-emerald-100 px-2 py-0.5 text-[11px] font-medium text-emerald-900">
|
||||||
|
{item.departmentName}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact rows */}
|
||||||
|
<div className="mt-3 space-y-1.5 border-t border-slate-100 pt-3 text-xs">
|
||||||
|
{item.email ? (
|
||||||
|
<a
|
||||||
|
href={`mailto:${item.email}`}
|
||||||
|
className="flex items-center gap-1.5 text-slate-700 hover:text-brand-700 hover:underline"
|
||||||
|
title={item.email}
|
||||||
|
>
|
||||||
|
<Mail className="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
||||||
|
<span className="truncate">{item.email}</span>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-1.5 text-slate-400">
|
||||||
|
<Mail className="h-3.5 w-3.5 shrink-0" />
|
||||||
|
<span>—</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.phone ? (
|
||||||
|
<a
|
||||||
|
href={`tel:${item.phone}`}
|
||||||
|
className="flex items-center gap-1.5 text-slate-700 hover:text-brand-700 hover:underline"
|
||||||
|
title={item.phone}
|
||||||
|
>
|
||||||
|
<Phone className="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
||||||
|
<span className="truncate">{formatPhone(item.phone)}</span>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-1.5 text-slate-400">
|
||||||
|
<Phone className="h-3.5 w-3.5 shrink-0" />
|
||||||
|
<span>—</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.internalPhone && (
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<UserCircle2 className="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
||||||
|
<span className="inline-flex rounded bg-amber-100 px-1.5 py-0.5 font-mono text-[10px] font-medium text-amber-900">
|
||||||
|
Ext: {item.internalPhone}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
fe-user/src/types/directory.ts
Normal file
22
fe-user/src/types/directory.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Danh bạ nội bộ (Internal Directory) — Phase 10.2 G-O1 (S34 2026-05-27).
|
||||||
|
// Mirror BE DirectoryItemDto (Application/Office/DirectoryFeatures.cs).
|
||||||
|
// File này MIRROR SHA256 identical với fe-admin/src/types/directory.ts.
|
||||||
|
export type DirectoryItem = {
|
||||||
|
userId: string
|
||||||
|
fullName: string
|
||||||
|
position: string | null
|
||||||
|
photoUrl: string | null
|
||||||
|
departmentId: string | null
|
||||||
|
departmentName: string | null
|
||||||
|
employeeCode: string | null
|
||||||
|
email: string | null
|
||||||
|
phone: string | null
|
||||||
|
internalPhone: string | null
|
||||||
|
personalEmail: string | null
|
||||||
|
workLocation: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DirectoryQuery = {
|
||||||
|
search?: string
|
||||||
|
departmentId?: string
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using SolutionErp.Application.Office;
|
||||||
|
|
||||||
|
namespace SolutionErp.Api.Controllers;
|
||||||
|
|
||||||
|
// Phase 10.2 G-O1 (S34) — Danh bạ nội bộ.
|
||||||
|
// 1 endpoint readonly. Class-level [Authorize] — mọi authenticated user thấy được
|
||||||
|
// (không restrict admin-only vì danh bạ nội bộ default open cho NV tra cứu nhau).
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/directory")]
|
||||||
|
[Authorize]
|
||||||
|
public class DirectoryController(IMediator mediator) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<ActionResult<List<DirectoryItemDto>>> Get(
|
||||||
|
[FromQuery] string? search = null,
|
||||||
|
[FromQuery] Guid? departmentId = null,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
=> Ok(await mediator.Send(new GetDirectoryQuery(search, departmentId), ct));
|
||||||
|
}
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SolutionErp.Application.Common.Interfaces;
|
||||||
|
|
||||||
|
namespace SolutionErp.Application.Office;
|
||||||
|
|
||||||
|
// Phase 10.2 G-O1 (S34) — Danh bạ nội bộ.
|
||||||
|
// 1 endpoint readonly query JOIN Users + EmployeeProfiles + Departments.
|
||||||
|
// FE card grid hiển thị avatar/name/dept/position/email/phone/internal-phone.
|
||||||
|
// Reuse existing data — KHÔNG mở schema mới.
|
||||||
|
|
||||||
|
public sealed record DirectoryItemDto(
|
||||||
|
Guid UserId,
|
||||||
|
string FullName,
|
||||||
|
string? Position,
|
||||||
|
string? PhotoUrl,
|
||||||
|
Guid? DepartmentId,
|
||||||
|
string? DepartmentName,
|
||||||
|
string? EmployeeCode,
|
||||||
|
string? Email,
|
||||||
|
string? Phone,
|
||||||
|
string? InternalPhone,
|
||||||
|
string? PersonalEmail,
|
||||||
|
string? WorkLocation);
|
||||||
|
|
||||||
|
public sealed record GetDirectoryQuery(
|
||||||
|
string? Search = null,
|
||||||
|
Guid? DepartmentId = null) : IRequest<List<DirectoryItemDto>>;
|
||||||
|
|
||||||
|
public sealed class GetDirectoryQueryHandler(IApplicationDbContext db)
|
||||||
|
: IRequestHandler<GetDirectoryQuery, List<DirectoryItemDto>>
|
||||||
|
{
|
||||||
|
public async Task<List<DirectoryItemDto>> Handle(GetDirectoryQuery request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
// Active users only — Department LEFT join (admin/system user có thể null dept).
|
||||||
|
// EmployeeProfile LEFT join (user chưa được seed EmployeeProfile vẫn hiện trong danh bạ
|
||||||
|
// với phone/internalPhone null — sau Phase 10.1 G-H1 thì tất cả 33 prod user đều có).
|
||||||
|
var query =
|
||||||
|
from u in db.Users.AsNoTracking().Where(x => x.IsActive)
|
||||||
|
join d in db.Departments.AsNoTracking() on u.DepartmentId equals d.Id into deptJoin
|
||||||
|
from d in deptJoin.DefaultIfEmpty()
|
||||||
|
join ep in db.EmployeeProfiles.AsNoTracking() on u.Id equals ep.UserId into epJoin
|
||||||
|
from ep in epJoin.DefaultIfEmpty()
|
||||||
|
select new
|
||||||
|
{
|
||||||
|
u.Id,
|
||||||
|
u.FullName,
|
||||||
|
u.Position,
|
||||||
|
u.Email,
|
||||||
|
u.DepartmentId,
|
||||||
|
DepartmentName = d != null ? d.Name : null,
|
||||||
|
EmployeeCode = ep != null ? ep.EmployeeCode : null,
|
||||||
|
Phone = ep != null ? ep.Phone : null,
|
||||||
|
InternalPhone = ep != null ? ep.InternalPhone : null,
|
||||||
|
PersonalEmail = ep != null ? ep.PersonalEmail : null,
|
||||||
|
PhotoUrl = ep != null ? ep.PhotoUrl : null,
|
||||||
|
WorkLocation = ep != null ? ep.WorkLocation : null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (request.DepartmentId is Guid deptId)
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.DepartmentId == deptId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.Search))
|
||||||
|
{
|
||||||
|
var term = request.Search.Trim();
|
||||||
|
query = query.Where(x =>
|
||||||
|
x.FullName.Contains(term) ||
|
||||||
|
(x.Email != null && x.Email.Contains(term)) ||
|
||||||
|
(x.Phone != null && x.Phone.Contains(term)) ||
|
||||||
|
(x.InternalPhone != null && x.InternalPhone.Contains(term)) ||
|
||||||
|
(x.EmployeeCode != null && x.EmployeeCode.Contains(term)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = await query
|
||||||
|
.OrderBy(x => x.DepartmentName)
|
||||||
|
.ThenBy(x => x.FullName)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
return rows
|
||||||
|
.Select(x => new DirectoryItemDto(
|
||||||
|
x.Id,
|
||||||
|
x.FullName,
|
||||||
|
x.Position,
|
||||||
|
x.PhotoUrl,
|
||||||
|
x.DepartmentId,
|
||||||
|
x.DepartmentName,
|
||||||
|
x.EmployeeCode,
|
||||||
|
x.Email,
|
||||||
|
x.Phone,
|
||||||
|
x.InternalPhone,
|
||||||
|
x.PersonalEmail,
|
||||||
|
x.WorkLocation))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -85,6 +85,15 @@ public static class MenuKeys
|
|||||||
public const string Hrm = "Hrm"; // root group
|
public const string Hrm = "Hrm"; // root group
|
||||||
public const string HrmHoSo = "Hrm_HoSo"; // Hồ sơ Nhân sự (list + detail + edit)
|
public const string HrmHoSo = "Hrm_HoSo"; // Hồ sơ Nhân sự (list + detail + edit)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Module Văn phòng số (Phase 10.2 G-O1+ S34 2026-05-27).
|
||||||
|
// 1 root group `Off` + leaf con: Off_DanhBa (G-O1 Danh bạ nội bộ).
|
||||||
|
// Future Phase 10.2+ add: Off_PhongHop (G-O2 booking) +
|
||||||
|
// workflow apps Off_DeXuat / Off_DonTu / Off_DatXe / Off_ItTicket.
|
||||||
|
// ============================================================
|
||||||
|
public const string Off = "Off"; // root group văn phòng số
|
||||||
|
public const string OffDanhBa = "Off_DanhBa"; // Danh bạ nội bộ (card grid)
|
||||||
|
|
||||||
public static readonly string[] PurchaseEvaluationTypeCodes =
|
public static readonly string[] PurchaseEvaluationTypeCodes =
|
||||||
["DuyetNcc", "DuyetNccPhuongAn"];
|
["DuyetNcc", "DuyetNccPhuongAn"];
|
||||||
|
|
||||||
@ -110,6 +119,7 @@ public static class MenuKeys
|
|||||||
PurchaseEvaluations,
|
PurchaseEvaluations,
|
||||||
Budgets, BudgetList, BudgetCreate, BudgetPending,
|
Budgets, BudgetList, BudgetCreate, BudgetPending,
|
||||||
Hrm, HrmHoSo, // Mig 34 — Phase 10.1
|
Hrm, HrmHoSo, // Mig 34 — Phase 10.1
|
||||||
|
Off, OffDanhBa, // Phase 10.2 G-O1 — Văn phòng số
|
||||||
System, Users, Roles, Permissions, MenuVisibility, Workflows, PeWorkflows,
|
System, Users, Roles, Permissions, MenuVisibility, Workflows, PeWorkflows,
|
||||||
ApprovalWorkflowsV2, ApprovalWorkflowDuyetNccV2, ApprovalWorkflowDuyetNccPhuongAnV2, // Mig 22
|
ApprovalWorkflowsV2, ApprovalWorkflowDuyetNccV2, ApprovalWorkflowDuyetNccPhuongAnV2, // Mig 22
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1483,6 +1483,11 @@ public static class DbInitializer
|
|||||||
// Phase 1 minimal. Phase 1.5 + G-H2/G-H3 thêm Config/Dashboard.
|
// Phase 1 minimal. Phase 1.5 + G-H2/G-H3 thêm Config/Dashboard.
|
||||||
(MenuKeys.Hrm, "Nhân sự", null, 28, "UserCircle"),
|
(MenuKeys.Hrm, "Nhân sự", null, 28, "UserCircle"),
|
||||||
(MenuKeys.HrmHoSo, "Hồ sơ Nhân sự", MenuKeys.Hrm, 1, "ContactRound"),
|
(MenuKeys.HrmHoSo, "Hồ sơ Nhân sự", MenuKeys.Hrm, 1, "ContactRound"),
|
||||||
|
|
||||||
|
// Module Văn phòng số (Phase 10.2 G-O1+ S34). 1 root + leaf Danh bạ.
|
||||||
|
// Future leaf: Off_PhongHop (G-O2) + workflow apps Off_DeXuat/DonTu/DatXe/ItTicket.
|
||||||
|
(MenuKeys.Off, "Văn phòng số", null, 29, "Briefcase"),
|
||||||
|
(MenuKeys.OffDanhBa, "Danh bạ nội bộ", MenuKeys.Off, 1, "BookUser"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Per-type sub-menu under Contracts: 1 group + 3 leaves each
|
// Per-type sub-menu under Contracts: 1 group + 3 leaves each
|
||||||
|
|||||||
Reference in New Issue
Block a user