[CLAUDE] Docs: chot session 3 — PE polish iter 2 + domain rebrand + 5 gotcha moi
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m55s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m55s
User request: 'Chot lai toan bo MD de sang session moi'. Session 3 (2026-04-24) — ~15 commit feat/fix PE module + domain migration: - Domain 3 subdomain huypham.vn → solutions.com.vn E2E live - PE rename 'Phuong An' → 'Giai phap' + backfill DB - Menu tree inheritance extend Pe_*/PeWf_* - Accordion mutex Pe_* + sidebar w-72 + label nowrap - NavLink queryMatches (fix 2 leaf cung highlight) - PE detail flat layout: Panel 2 = 4 section, Panel 3 + approvals/history - Upload file dinh kem per-NCC (SupplierAttachmentsCell) + Bang so sanh tong - readOnly mode menu 'Duyet' (pendingMe=1) - HD move Lich su dieu chinh → Panel 3 - Demo email rebrand @solutionerp.local → @solutions.com.vn + BackfillUserEmailDomain Docs updated (6 file): - STATUS.md: +9 row Recently Done session 3. In Progress tick 10+ done. Phase hien tai = 'UX polish hoan thien, UAT-ready'. - HANDOFF.md: TL;DR session 3 summary. Priority 0 = 3 task MISSING cuoi (Designer UI, Y kien 4 phong ban, Export PDF). Login email moi. - gotchas.md: +5 entry (#34 NavLink query, #35 menu inheritance extend, #36 Vite env rebuild, #37 PS 5.1 ASCII, #38 Identity rename 4 field) + checklist debug +5 entry. - ef-core-migration SKILL: migration 13 AddPurchaseEvaluationCodeSequences + Phase 6 update section (ComparisonTable enum + BackfillUserEmail). - skills/README: ef-core-migration 13 migration label updated. - docs/changelog/sessions/2026-04-24-chot-session-3-pe-polish.md: session log 15 commit + bugs + stats + next priorities session 4. Memory project_solution_erp.md: Phase 6 iter 2 DONE. Domain rebrand DONE. Session 4 priority 3 PE gap remaining. Stats: 47 DB tables (+1 MaPhieu seq), ~113 endpoint (+3 PE attachments), 13 migrations, 38 gotchas, ~85 commits total.
This commit is contained in:
135
docs/gotchas.md
135
docs/gotchas.md
@ -333,6 +333,136 @@ subdomain có ARR proxy về `:3000`.
|
||||
deploy Kestrel standalone qua NSSM → PHẢI apply 3 rules trên
|
||||
- Scripts + skill doc đã update `localhost` → `127.0.0.1` để đồng bộ
|
||||
|
||||
## FE routing + state (Phase 6)
|
||||
|
||||
### 34. React Router NavLink `isActive` chỉ match pathname, không query string
|
||||
|
||||
**Triệu chứng:** 2 NavLink cùng pathname (`/purchase-evaluations?type=2` vs
|
||||
`/purchase-evaluations?type=2&pendingMe=1`) cùng highlight khi URL là một
|
||||
trong 2. User thấy menu "Danh sách" + "Duyệt" active đồng thời.
|
||||
|
||||
**Nguyên nhân:** React Router v6 `NavLink`'s built-in `isActive` chỉ so
|
||||
pathname. `end` prop chỉ thêm exact-match cho pathname segment, không check
|
||||
query string.
|
||||
|
||||
**Fix:** Custom `isActive` với `queryMatches` helper (URLSearchParams set
|
||||
equality). Xem `Layout.tsx` cả 2 FE:
|
||||
|
||||
```tsx
|
||||
function queryMatches(current: string, target: string): boolean {
|
||||
const a = new URLSearchParams(current)
|
||||
const b = new URLSearchParams(target)
|
||||
const aKeys = [...a.keys()].sort()
|
||||
const bKeys = [...b.keys()].sort()
|
||||
if (aKeys.length !== bKeys.length) return false
|
||||
return aKeys.every((k, i) => bKeys[i] === k && a.get(k) === b.get(k))
|
||||
}
|
||||
|
||||
function MenuLeaf({ node }: { node: MenuNode }) {
|
||||
const location = useLocation()
|
||||
const path = resolvePath(node.key)
|
||||
const [targetPath, targetQuery = ''] = path.split('?')
|
||||
const isActive = location.pathname === targetPath
|
||||
&& queryMatches(location.search.replace(/^\?/, ''), targetQuery)
|
||||
return <NavLink to={path} className={isActive ? 'active' : ''}>...</NavLink>
|
||||
}
|
||||
```
|
||||
|
||||
### 35. Menu tree inheritance phải extend khi thêm root mới
|
||||
|
||||
**Triệu chứng:** Admin/role đã grant `PurchaseEvaluations.Read` (inherit parent)
|
||||
nhưng menu children `Pe_DuyetNcc_List` / `Pe_DuyetNcc_Create` không hiển thị.
|
||||
Chỉ thấy root `PurchaseEvaluations` ở Layout sidebar.
|
||||
|
||||
**Nguyên nhân:** `GetMyMenuTreeQuery` hardcode 2 inherit root: `Contracts` và
|
||||
`Workflows`. Descendant Ct_*/Wf_* auto-inherit CRUD flags từ parent qua switch
|
||||
statement. Khi thêm root mới (`PurchaseEvaluations`, `PeWorkflows`) — không có
|
||||
trong switch → children mặc định (false,false,false,false) → filter
|
||||
`HasAccess` hide children.
|
||||
|
||||
**Fix:** Extend switch + `nextInherit` propagation:
|
||||
|
||||
```csharp
|
||||
var contractsFlags = GetFlags(MenuKeys.Contracts);
|
||||
var workflowsFlags = GetFlags(MenuKeys.Workflows);
|
||||
var peFlags = GetFlags(MenuKeys.PurchaseEvaluations); // NEW
|
||||
var peWorkflowsFlags = GetFlags(MenuKeys.PeWorkflows); // NEW
|
||||
|
||||
// Trong BuildChildren:
|
||||
if (inheritFromKey is not null && !resolved.ContainsKey(m.Key))
|
||||
{
|
||||
flags = inheritFromKey switch
|
||||
{
|
||||
var k when k == MenuKeys.Contracts => contractsFlags,
|
||||
var k when k == MenuKeys.Workflows => workflowsFlags,
|
||||
var k when k == MenuKeys.PurchaseEvaluations => peFlags, // NEW
|
||||
var k when k == MenuKeys.PeWorkflows => peWorkflowsFlags, // NEW
|
||||
_ => flags,
|
||||
};
|
||||
}
|
||||
|
||||
var nextInherit = inheritFromKey
|
||||
?? (m.Key == MenuKeys.Contracts ? MenuKeys.Contracts
|
||||
: m.Key == MenuKeys.Workflows ? MenuKeys.Workflows
|
||||
: m.Key == MenuKeys.PurchaseEvaluations ? MenuKeys.PurchaseEvaluations
|
||||
: m.Key == MenuKeys.PeWorkflows ? MenuKeys.PeWorkflows
|
||||
: null);
|
||||
```
|
||||
|
||||
**Rule:** Khi thêm 1 root mới có child leaves (vd `PeWorkflows` → `PeWf_*`) —
|
||||
PHẢI update cả 3 chỗ: (1) MenuKeys.All, (2) GetMyMenuTreeQuery GetFlags + switch,
|
||||
(3) nextInherit propagation.
|
||||
|
||||
### 36. Vite env var embed compile-time — đổi `.env.production` phải rebuild FE
|
||||
|
||||
**Triệu chứng:** Đổi `VITE_API_BASE_URL=...` trong `.env.production` nhưng FE
|
||||
vẫn gọi URL cũ. Hot reload không giúp.
|
||||
|
||||
**Nguyên nhân:** Vite inline `import.meta.env.VITE_*` tại build time vào JS
|
||||
bundle (minified). File `.env*` chỉ đọc khi `vite build` — không runtime.
|
||||
|
||||
**Fix:** Sau đổi env:
|
||||
1. Rebuild: `cd fe-admin ; npm run build`
|
||||
2. Deploy dist mới lên IIS
|
||||
3. Clear CDN/browser cache (Ctrl+Shift+R)
|
||||
|
||||
Verify bundle có URL mới: `curl dist/assets/index-*.js | grep -oE 'https://[^"]+api'`.
|
||||
|
||||
## Deploy / Production (continued)
|
||||
|
||||
### 37. PowerShell 5.1 diacritics trong script — gotcha #30 tái phát
|
||||
|
||||
**Triệu chứng (bis):** `migrate-domains.ps1` viết với "Phương Án", "→",
|
||||
em-dash → PS 5.1 parser fail `Missing closing ''`, `Unexpected character`.
|
||||
|
||||
**Fix:** Luôn ASCII-only cho .ps1 — rule lặp lại gotcha #30. Cách phát hiện:
|
||||
grep file cho UTF-8 multi-byte chars trước khi deploy:
|
||||
```bash
|
||||
grep -P '[\x80-\xff]' scripts/*.ps1
|
||||
# Nếu có match → rewrite ASCII-only
|
||||
```
|
||||
|
||||
## Email / Users
|
||||
|
||||
### 38. Email rename Identity user — 4 field cần update đồng thời
|
||||
|
||||
**Triệu chứng:** Đổi `user.Email` xong login với email mới vẫn 401. Hoặc
|
||||
UserManager.FindByEmail trả null.
|
||||
|
||||
**Nguyên nhân:** Identity lookup qua `NormalizedEmail` (uppercase), không
|
||||
`Email`. Username cũng dùng email. 4 field phải sync:
|
||||
|
||||
```csharp
|
||||
u.Email = newEmail;
|
||||
u.NormalizedEmail = newEmail.ToUpperInvariant();
|
||||
u.UserName = newEmail;
|
||||
u.NormalizedUserName = newEmail.ToUpperInvariant();
|
||||
await userManager.UpdateAsync(u);
|
||||
```
|
||||
|
||||
**Bonus:** Check conflict trước khi rename (user khác đã có email mới) →
|
||||
skip để tránh duplicate.
|
||||
|
||||
## Checklist debug bug mới
|
||||
|
||||
1. Build pass không? → fail → check using + package version compat
|
||||
@ -347,3 +477,8 @@ subdomain có ARR proxy về `:3000`.
|
||||
10. Nếu SignalR 401 → dùng `accessTokenFactory` + BE OnMessageReceived hook (#26)
|
||||
11. Nếu PS 5.1 script fail → check encoding UTF-8 / BOM / ASCII-only (#30)
|
||||
12. Nếu subdomain trả sai content / bị hijack → check IPv4/IPv6 port collision trên VPS shared (#33)
|
||||
13. Nếu 2 NavLink cùng active / không đúng highlight → custom isActive match query string (#34)
|
||||
14. Nếu menu item có quyền nhưng không hiện → check GetMyMenuTreeQuery inheritance extend (#35)
|
||||
15. Nếu FE gọi API sai URL sau đổi env → rebuild + clear bundle cache (#36)
|
||||
16. Nếu .ps1 fail parser trên PS 5.1 → ASCII-only, grep multi-byte chars (#30, #37)
|
||||
17. Nếu rename email Identity vẫn 401 → update 4 field NormalizedEmail/UserName (#38)
|
||||
|
||||
Reference in New Issue
Block a user