[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: -9dee00dChunk A — BE auto-seed Hạng mục mặc định + FE reorder section -2bba851Chunk B — Nested grid HangMucCard, NCC expand inline + drop SuppliersTab -f2f01f4Chunk 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:
206
docs/changelog/sessions/2026-05-11-1100-pe-ui-restructure-s20.md
Normal file
206
docs/changelog/sessions/2026-05-11-1100-pe-ui-restructure-s20.md
Normal 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)
|
||||
Reference in New Issue
Block a user