[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>
This commit is contained in:
pqhuy1987
2026-05-11 10:12:13 +07:00
parent f2f01f4765
commit f8e5675edf
4 changed files with 404 additions and 2 deletions

View File

@ -157,6 +157,32 @@ Session log: `2026-04-28-chot-session-4-budget.md`.
## 📝 Phase 9 — UAT + Ops + carry over (Session 6+ active)
### ✅ Session 20 done (2026-05-11) — PE Detail UI restructure 3 yêu cầu UX user (4 chunk `9dee00d` → `2bba851` → `f2f01f4` → Chunk D Docs)
User UAT live feedback: "Logic khá OK rồi, điều chỉnh UI Duyệt NCC 1 tý". 3 yêu cầu cụ thể chốt qua Q&A 4 câu (Q1=a giữ Section "Chọn NCC TP" / Q2=a NCC shared + 1 hạng mục demo / Q3=a chỉ hiện NV đã ký / Q4 public luôn skip dotnet test). FE-only restructure (1 hook BE nhẹ auto-seed Detail).
- [x] **Chunk A (`9dee00d`) BE auto-seed + FE reorder section**`CreatePurchaseEvaluationCommandHandler` thêm 1 PurchaseEvaluationDetail mặc định khi tạo phiếu: GroupCode="01", GroupName="Hạng mục chính", NoiDung=TenGoiThau, DonGiaNganSach=ThanhTienNganSach=Budget.TongNganSach (nếu link) hoặc BudgetManualAmount fallback hoặc 0; Changelog Insert audit. FE reorder PeDetailTabs section (mirror 2 app): 1.Thông tin / **2.Hạng mục lên #2** / 3.Chọn NCC / 4.NCC tham gia / 5.Ý kiến. Verify dotnet build pass.
- [x] **Chunk B (`2bba851`) Nested grid Hạng mục → NCC expand** — ItemsTab restructure thành list `HangMucCard` (1 card / 1 hạng mục, expanded=true mặc định cho 1 hạng mục demo). Header: GroupCode + NoiDung + 3 stat (KL/ĐG/TT) + NS link Δ + Pencil/Trash + ▼/▶ toggle. Expand body: NCC inline table 8 cột (NCC/Liên hệ/Điều khoản TT/**File báo giá**/ĐG chưa VAT/ĐG có VAT/Thành tiền/Action). Click cell quote → QuoteDialog reuse. + Thêm NCC / Sửa NCC reuse 2 dialog cũ. Winner ✓ button per row. Drop `SuppliersTab` function dead code ~134 LOC. Giữ AddSupplierDialog + EditSupplierDialog + SupplierAttachmentsCell (HangMucCard call lại). Section 4 NCC tham gia cũ bỏ → 4 section final (1.Thông tin / 2.Hạng mục nested / 3.Chọn NCC TP thắng thầu / 4.Ý kiến). Verify npm build × 2 app pass sau khi catch TS6133 SuppliersTab + SupplierAttachmentsCell unused.
- [x] **Chunk C (`f2f01f4`) Section Ý kiến gộp đồng cấp cùng Phòng** — FE-only KHÔNG đụng Mig 26 schema (vẫn UPSERT 1 row / Level qua Service). LevelOpinionsSectionV2 forEach step → 1 `StepOpinionsBox` (replace grid-cols-2 N approvers). Header: "Bước N — Tên" + dept badge emerald + "X/Y đã duyệt" counter. Body filter opinions theo step.order → sort levelOrder asc + signedAt asc → render `StepOpinionEntry` per signed (tên NV + Cấp badge slate + admin override badge amber nếu có + emerald rounded-full timestamp + comment whitespace-pre-wrap). NV chưa duyệt KHÔNG hiển thị (Q3=a). Drop `LevelOpinionBox` function. Mirror fe-admin + fe-user. Verify npm build × 2 app pass.
- [x] **Chunk D Docs (current)** — STATUS Recently Done top + header narrative · HANDOFF TL;DR Session 20 + pending S21+ + hard blockers ops carry · migration-todos Phase 9 Session 20 done section (file này) · Session log mới `2026-05-11-1100-pe-ui-restructure-s20.md`. KHÔNG đụng rules/architecture/PROJECT-MAP/workflow-contract/forms-spec/database-guide/schema-diagram/CLAUDE.md (defer cron audit 2026-06-01 — per §6.5 không cố sửa khi không cần; S20 không thêm migration / gotcha mới nên count drift không đổi từ S19).
**Stats final Session 20:** 26 mig (no new), 59 DB tables (no new), ~141 endpoints (no new — reuse + 1 BE hook trong existing CreatePE handler), 33 FE pages (no new), **81 test pass** (no change — Phase 9 UAT iteration skip test mỗi chunk Q4), 44 gotcha (no new). LOC delta net ~+25 FE (gross ~+700 / ~725).
**Defer Session 21+:**
- [ ] **Test V2 Service wire** (Chunk B Service hook S19 + Section gộp Chunk C S20) — defer khi UAT user confirm + có sample data Production.
- [ ] **Test regression B4 silent 403 S18** (HIGH §7 priority — vi phạm rule test-before bug fix) — per-action `[Authorize(Policy=...)]` ApprovalWorkflowsV2Controller.
- [ ] **Test Mig 25 PATCH user-selectable** endpoint (MED — admin scope hẹp).
- [ ] **Contract V2 wire (Mig 27/28 mirror PE pattern)** — biggest pending Plan. Mig 27 Contract.ApprovalWorkflowId + CurrentApprovalLevelOrder; Mig 28 ContractLevelOpinions; Service ApproveV2Async; ContractCreatePage Workspace Select V2; pin V2 mặc định cho ContractType; ContractDetailContent Section "Ý kiến cấp duyệt" V2 dynamic mirror S20 Chunk C.
- [ ] **Phân quyền strict V2** — vẫn loose UAT. Sau confirm V2 flow → list/inbox/detail filter actor scope.
- [ ] **Drop legacy V1 cleanup** sau UAT chốt (drop tables WorkflowDefinitions/Steps/Approvers + drop column RejectedAtStepIndex/RejectedFromPhase deprecated S17 + drop ApproveV1LegacyAsync branch).
- [ ] **Drop Mig 15 cho V2 phiếu cleanup** sau UAT confirm (Mig 30 drop PurchaseEvaluationDepartmentOpinions, hoặc giữ cả 2 backward compat — Q3 user chốt giữ legacy).
- [ ] **schema-diagram §16 PE Level Opinions V2 + §17-21 Mig 18-21** — defer cron audit 2026-06-01.
- [ ] **Skill `ef-core-migration` frontmatter "21 migration" stale (thực 26) + `dependency-audit-erp` count gotcha 41 stale (thực 44)** — defer cron audit 2026-06-01.
### ✅ Session 19 done (2026-05-09) — PE Section 5 V2 dynamic theo ApprovalWorkflowLevel + Mig 26 (4 commit `873e7a1` → Chunk D Docs)
User UAT live tiếp Session 18. 1 polish nhỏ + 1 feature lớn (Section 5 dynamic). Spec chốt 5 câu Q&A trước code (Q1=1B sync auto / Q2=2A+Admin / Q3=V2 hết / Q4=4C+placeholder / Q5=5A grid-cols-2).

View File

@ -0,0 +1,206 @@
# 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)