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

9.2 KiB
Raw Blame History

Session 20 — PE Detail UI restructure (3 yêu cầu UX user)

Date: 2026-05-11 Status: All 4 chunks pushed (9dee00d2bba851f2f01f4 → 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:

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):

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)