Files
solution-erp/docs/changelog/sessions/2026-05-11-1100-pe-ui-restructure-s20.md
pqhuy1987 f8e5675edf [CLAUDE] Docs: chốt Session 20 — PE Detail UI restructure 3 yêu cầu UX (Chunk D)
Wrap-up docs cho 3 chunk code đã push:
- 9dee00d Chunk A — BE auto-seed Hạng mục mặc định + FE reorder section
- 2bba851 Chunk B — Nested grid HangMucCard, NCC expand inline + drop SuppliersTab
- f2f01f4 Chunk C — Section Ý kiến gộp đồng cấp cùng Phòng (1 box/Step, chỉ hiện signed)

Files updated:
- docs/STATUS.md — Last updated + Recently Done row S20 trên cùng (giữ S19 nguyên văn §6.5)
- docs/HANDOFF.md — Last updated + TL;DR Session 20 section ở đầu + pending S21+ + hard blocker ops (giữ S19 nguyên văn §6.5)
- docs/changelog/migration-todos.md — Phase 9 Session 20 done section + 9 defer item S21+ (giữ S19 nguyên văn §6.5)
- docs/changelog/sessions/2026-05-11-1100-pe-ui-restructure-s20.md (NEW) — session log

KHÔNG đụng rules/architecture/PROJECT-MAP/workflow-contract/forms-spec/database-guide/
schema-diagram/CLAUDE.md per §6.5 (S20 không thêm migration / gotcha mới, drift unchanged từ S19 → defer cron audit 2026-06-01).

Path filter CI sẽ skip (docs-only commit).

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

207 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Session 20 — PE Detail UI restructure (3 yêu cầu UX user)
**Date:** 2026-05-11
**Status:** ✅ All 4 chunks pushed (`9dee00d``2bba851``f2f01f4` → this Docs commit)
**Scope:** FE-only restructure UI PE Detail (Duyệt NCC) + 1 nhánh BE nhẹ auto-seed.
## Bối cảnh
User UAT live phản hồi: "Logic khá OK rồi, điều chỉnh giao diện chỗ Duyệt NCC 1 tý nhé."
3 yêu cầu cụ thể:
1. Hạng mục đưa lên phía trên + auto-tạo 1 hạng mục từ gói thầu (tên = TenGoiThau, giá trị = ngân sách)
2. Thêm NCC = expand dưới hạng mục (tầng 1 = hạng mục, tầng 2 = NCC, thông tin nhập trên grid)
3. Section Ý kiến: gộp comment đồng cấp cùng Phòng → 1 ô / bước (chỉ hiển thị comment NV đã duyệt)
## Q&A trước khi code (chốt scope)
- **Q1 (giữ Section "Chọn NCC TP thắng thầu" riêng?):** = a (giữ riêng, rõ UX)
- **Q2 (NCC shared cross-hạng mục?):** = a (shared) — "**nhưng hiện chỉ cần 1 hạng mục trước tiên**" → đơn giản scope Chunk B
- **Q3 (chỉ hiển thị NV đã ký?):** = a (KHÔNG show NV chưa duyệt với placeholder)
- **Q4 (verify mode?):** "Public luôn đang demo thôi" → Phase 9 UAT iteration skip `dotnet test`, vẫn `npm run build` mỗi chunk
## Chunk A — Hạng mục lên #2 + BE auto-seed 1 row
**Commit:** `9dee00d`
**BE — `PurchaseEvaluationFeatures.cs` `CreatePurchaseEvaluationCommandHandler`:**
Khi tạo phiếu mới, sau khi save PE entity, INSERT thêm 1 `PurchaseEvaluationDetail`
mặc định kèm Changelog entry:
```csharp
var defaultBudgetValue = linkedBudgetTotal ?? request.BudgetManualAmount ?? 0m;
var defaultDetail = new PurchaseEvaluationDetail
{
PurchaseEvaluationId = entity.Id,
GroupCode = "01",
GroupName = "Hạng mục chính",
NoiDung = request.TenGoiThau, // ← tên hạng mục = tên gói thầu
DonViTinh = "gói",
KhoiLuongNganSach = 1m,
KhoiLuongThiCong = 1m,
DonGiaNganSach = defaultBudgetValue, // ← giá trị từ ngân sách link / manual
ThanhTienNganSach = defaultBudgetValue,
Order = 1,
};
```
`linkedBudgetTotal` mới: nếu PE link Budget, fetch `Budget.TongNganSach` (computed
sum BudgetDetails). Nếu không link, fall back `BudgetManualAmount`. Nếu cả 2 null
→ 0 (user sẽ sửa sau).
**FE — `PeDetailTabs.tsx` (mirror fe-admin + fe-user):**
Đổi thứ tự 5 section:
- Cũ: 1.Thông tin / 2.Chọn NCC / 3.NCC tham gia / 4.Hạng mục / 5.Ý kiến
- Mới Chunk A: 1.Thông tin / **2.Hạng mục** ← / 3.Chọn NCC / 4.NCC tham gia / 5.Ý kiến
- (Chunk B sẽ gộp Section 4 vào Section 2 — final 4 section)
## Chunk B — NCC nested expand dưới Hạng mục
**Commit:** `2bba851`
**FE-only mirror fe-admin + fe-user** (~280 LOC mỗi file thay đổi):
Restructure `ItemsTab`:
- Trước: 1 bảng matrix grid (hạng mục × NCC) — sticky left column hạng mục + repeating col / NCC
- Sau: list `HangMucCard` (1 card / 1 hạng mục)
`HangMucCard` mới (sub-component nội bộ file, không export):
```tsx
function HangMucCard({ detail, ev, readOnly, budgetRowMap, showBudgetCol, onEditDetail }) {
const [expanded, setExpanded] = useState(true) // mặc định mở (1 hạng mục demo)
const [addNccOpen, setAddNccOpen] = useState(false)
const [editNccRow, setEditNccRow] = useState<PeSupplier | null>(null)
const [quoteEdit, setQuoteEdit] = useState<{ supplier: PeSupplier; existing: PeQuote | null } | null>(null)
// 3 mutations: removeDetail, removeNcc, setWinner (move từ SuppliersTab cũ)
// Header row: GroupCode + NoiDung + GroupName + KL/ĐG/TT + NS link Δ + Pencil/Trash
// Expand panel: NCC inline table
}
```
**Layout nested grid:**
| Tầng | Component | Hành vi |
|---|---|---|
| 1 Header | `HangMucCard` div header | GroupCode + NoiDung + 3 stat (KL/ĐG/TT) + NS link Δ (nếu có) + Pencil/Trash actions, click ▼/▶ toggle expand |
| 2 Expand body | `<table>` NCC inline | columns: NCC \| Liên hệ \| Điều khoản TT \| **File báo giá** \| ĐG chưa VAT \| ĐG có VAT \| Thành tiền \| Action |
**Quote inline:** click cell (chưa VAT / có VAT / Thành tiền) → mở `QuoteDialog` cũ (reuse).
**Add NCC:** button `+ Thêm NCC` trong expand panel → `AddSupplierDialog` cũ (reuse).
**Sửa NCC info:** ✏ icon mỗi NCC row → `EditSupplierDialog` cũ.
**Winner:** ✓ icon click → `setWinner` mutation, row + cell ăn theo màu emerald.
**File báo giá:** giữ nguyên `SupplierAttachmentsCell` component, nhúng vào cell mới (TS6133 catch khi quên thêm cột → đã fix).
**Drop dead code:**
- Function `SuppliersTab` xóa hoàn toàn (~134 LOC) — replace bằng `HangMucCard` expand panel
- Giữ `AddSupplierDialog` + `EditSupplierDialog` + `SupplierAttachmentsCell` (HangMucCard call lại)
**Section layout cuối Chunk B (4 section):**
1. Thông tin gói thầu
2. Hạng mục + Báo giá NCC (nested expand)
3. Chọn NCC / TP thắng thầu
4. Ý kiến cấp duyệt
(NCC tham gia riêng cũ bỏ — gộp vào Section 2.)
## Chunk C — Section Ý kiến gộp đồng cấp cùng Phòng
**Commit:** `f2f01f4`
**FE-only mirror 2 app** (~134 LOC thay đổi).
**Schema Mig 26 không đụng** — vẫn UPSERT 1 row / Level trong `PurchaseEvaluationLevelOpinions` qua `ApproveV2Async`. Chỉ thay đổi render layer.
**Trước (S19 LevelOpinionsSectionV2):**
```
forEach step:
div.grid-cols-2:
forEach level:
forEach approver:
<LevelOpinionBox /> (1 box / NV)
- Cấp N — Tên NV
- "Đã duyệt" badge or "— chưa duyệt" placeholder italic
- comment text
- admin override badge nếu signedBy !== approver
```
**Sau (Chunk C):**
```
forEach step:
<StepOpinionsBox stepOrder stepName departmentName totalApprovers opinions={stepOpinions} />
- Header: "Bước N — Tên" + dept badge emerald + "X/Y đã duyệt" counter
- Body:
- empty → "— Chưa có ý kiến duyệt." italic gray
- else → list <StepOpinionEntry opinion /> per signed opinion
(sort levelOrder asc, signedAt asc)
```
`StepOpinionEntry`:
- Header: ApproverFullName + "Cấp N" badge slate + admin override badge (amber) nếu có
- Right: emerald rounded-full timestamp "✓ DD/MM/YYYY HH:mm"
- Body: comment text
NV chưa duyệt KHÔNG hiển thị (Q3=a) — chỉ 1 box / Step thay vì N box / NV.
**Drop dead code:**
- Function `LevelOpinionBox` xóa (~50 LOC) — replace bằng `StepOpinionsBox` + `StepOpinionEntry`
## Chunk D — Docs (file này + STATUS + HANDOFF)
Đang commit.
## Tổng metrics Session 20
| Metric | Trước S20 | Sau S20 | Delta |
|---|---|---|---|
| DB tables | 59 | 59 | 0 (FE-only + BE seed hook) |
| Migrations | 26 | 26 | 0 |
| Endpoints | ~141 | ~141 | 0 (reuse + 1 BE hook trong existing CreatePE handler) |
| Unit tests | 81 pass | 81 pass | 0 (UAT skip — Q4 user public luôn) |
| Gotchas | 44 | 44 | 0 (TS6133 unused function declaration caught early bởi `npm run build` — đã biết, không record gotcha mới) |
| Commits S20 | — | 4 (3 code + 1 docs) | — |
| Files changed | — | 3 files (1 BE + 2 FE mirror) | — |
| LOC delta | — | ~+50 BE / ~+700 FE / ~725 FE = net ~+25 FE | — |
## Verify chain mỗi chunk
| Chunk | BE build | FE build admin | FE build user | dotnet test | Push |
|---|---|---|---|---|---|
| A | ✅ 0 warn / 0 err | (no change FE structure) | (no change) | skip (Q4) | `9dee00d` |
| B | (no BE change) | ✅ pass | ✅ pass | skip | `2bba851` |
| C | (no BE change) | ✅ pass | ✅ pass | skip | `f2f01f4` |
CI gate trên Gitea Actions sẽ run `dotnet test SolutionErp.slnx` cho mỗi commit
code (path filter docs-only skip cho Chunk D). Verify 3 run pass trước UAT confirm.
## Pending (defer Session 21+)
Vẫn carry over từ HANDOFF S19:
- Test regression B4 S18 silent 403 fix (HIGH priority — vi phạm rule §7 "test-before bug fix")
- Test V2 Service wire `ApproveV2Async` UPSERT opinion (Mig 26 S19) + Section gộp render (Chunk C)
- Test Mig 25 PATCH `/user-selectable` endpoint
- **Contract V2 wire (Mig 27/28 mirror PE pattern)** — biggest pending Plan
- Phân quyền strict V2 (list/inbox/detail filter actor scope)
- Drop legacy V1 + Mig 15 4-box deprecated sau UAT confirm
Hard blockers ops (S19 carry):
- UAT thật 1 tuần
- SMTP config (chờ user cấp host/user/pass)
- Rotate creds + SQL backup schedule + win-acme fix + remove `.huypham.vn` binding
Audit định kỳ:
- 2026-06-01 combined audit (skill + doc drift). Drift hiện tại cho audit kế:
- `ef-core-migration` skill mention "21 migration" stale (thực 26)
- `dependency-audit-erp` count gotcha 41 stale (thực 44)
- `schema-diagram` §16 PE Level Opinions V2 + §17-21 Mig 18-21 pending
- (S20 không thêm migration / gotcha mới — drift không thay đổi từ S19)
## Cross-ref
- Memory `feedback_uat_skip_verify.md` — Q4 áp dụng đúng pattern (verify build mỗi chunk vì có rename/remove function)
- Memory `feedback_per_chunk_commit.md` — A/B/C/D chunk pattern
- Memory `feedback_audit_reuse_before_clone.md` — Chunk B reuse 3 dialog (AddSupplier/EditSupplier/QuoteDialog) thay vì rewrite
- Session 19 changelog `2026-05-09-0400-pe-section-5-v2-dynamic-mig26.md` — context Mig 26 schema (vẫn giữ nguyên Chunk C)