Commit Graph

173 Commits

Author SHA1 Message Date
d15398fafe [CLAUDE] Domain+FE: PE thêm phase TraLai + pencil always visible + edit gating
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 2m0s
User feedback 2026-05-07:
1. Pencil edit icon LUÔN hiện (không chỉ hover) trong workspace Panel 1
2. Pencil sáng (active brand-color) khi phase editable, xám/disabled khi không
3. Click pencil khi sáng → row sáng + auto-open edit toàn bộ (header + detail)
4. Thêm phase mới "Trả lại" — approver gửi về Drafter sửa (vs Từ chối terminal)
5. Edit chỉ cho 2 trạng thái: Đang soạn thảo + Trả lại
   (Từ chối + Đã gửi duyệt + Đã duyệt → không edit/thao tác gì)

Implementation:
  ~ Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs
    + TraLai = 98 (giữa DaDuyet=7 và TuChoi=99)
    Comment ghi rõ "approver trả về Drafter sửa, vẫn cho edit, khác TuChoi"
  ~ types/purchaseEvaluation.ts (× 2 app)
    + PurchaseEvaluationPhase enum: TraLai = 98
    + PurchaseEvaluationPhaseLabel/Color cho TraLai (yellow)
    + isEditablePhase(phase) helper: true cho DangSoanThao + TraLai
    + PeDisplayStatus thêm "TraLai" (separate, không gộp DaGuiDuyet)
    + getPeDisplayStatus map TraLai → "Trả lại" badge yellow
  ~ components/pe/PeListPanel.tsx (× 2 app)
    - Pencil icon: bỏ opacity-0 hover-only → LUÔN visible
    - editable=isEditablePhase(p.phase): bright text-brand-600 + cursor-pointer
    - !editable: text-slate-300 + cursor-not-allowed + onClick guard ignored
    - title tooltip rõ ràng "đã gửi duyệt / đã duyệt / từ chối — không sửa được"
    - Bỏ forcedPhase prop → editableOnly prop (filter client-side cả 2 phase
      DangSoanThao + TraLai vì BE chưa support multi-phase param)
    - Khi editableOnly: hiển thị "Lọc cố định: Bản nháp + Trả lại" indicator
  ~ components/pe/PeDetailTabs.tsx (× 2 app)
    - Header bar: isDraft → canEditPhase = isEditablePhase(phase)
    - InfoTab: canEdit = !readOnly && isEditablePhase
    - BudgetFieldRow: canEdit = !readOnly && isEditablePhase
    → Đồng nghĩa Drafter sửa được phiếu Trả lại sau approver send back
  ~ pages/pe/PurchaseEvaluationWorkspacePage.tsx (× 2 app)
    - PeListPanel forcedPhase=DangSoanThao → editableOnly
    - Bỏ import PurchaseEvaluationPhase

Workflow service BE chưa wire transition → TraLai (defer — user sẽ thêm button
"Trả lại" trong PeWorkflowPanel duyệt sau, hoặc dùng API PATCH manual). Phase
TraLai chỉ là enum value sẵn sàng FE hiển thị + BE ánh xạ HasConversion<int>.

UAT mode: skip verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:38:46 +07:00
0c5db1385f [CLAUDE] FE-Admin+FE-User: PE display status meta — Bản nháp / Đã gửi duyệt / Đã duyệt / Từ chối
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m59s
User feedback 2026-05-07: thêm 2 trạng thái meta hiển thị "Bản nháp" + "Đã
gửi duyệt". Bản nháp chỉ hiện ở Thao tác workspace, không hiện ở Duyệt menu.

Implementation:
  ~ types/purchaseEvaluation.ts
    + PeDisplayStatus enum (BanNhap / DaGuiDuyet / DaDuyet / TuChoi)
    + PeDisplayStatusLabel + PeDisplayStatusColor
    + getPeDisplayStatus(phase) helper:
        DangSoanThao → BanNhap
        DaDuyet → DaDuyet
        TuChoi → TuChoi
        else (any middle phase) → DaGuiDuyet
  ~ components/pe/PeListPanel.tsx
    - Phase Select filter → Display status Select (4 option, "Đã gửi duyệt"
      KHÔNG filter exact phase do multi-phase, để client-side TODO BE)
    - Row badge dùng display status (gọn 4 màu)
    + Prop forcedPhase?: number — workspace dùng để khóa filter Bản nháp
      (DangSoanThao). Khi forcedPhase set: ẩn Select, show "Lọc cố định: Bản
      nháp" indicator.
  ~ components/pe/PeDetailTabs.tsx
    - Header badge dùng display status meta + secondary text "(Phase chi tiết)"
      nhỏ bên cạnh để approver/dev vẫn biết phase exact
  ~ pages/pe/PurchaseEvaluationsListPage.tsx
    - Phase filter Select → display status options
    - Row badge → display status
  ~ pages/pe/PurchaseEvaluationWorkspacePage.tsx
    - PeListPanel forcedPhase={PurchaseEvaluationPhase.DangSoanThao}
      → workspace chỉ list Bản nháp (đúng UX user yêu cầu)

Workflow timeline Panel 3 + workflow service BE KHÔNG đổi (giữ phase chi tiết
DangSoanThao/ChoPurchasing/ChoCCM/etc cho approval logic).

Pe_*_Pending Duyệt: dùng /inbox endpoint vốn đã filter chỉ phiếu cần user duyệt
→ DangSoanThao auto-không xuất hiện (không có active approver). Nên Bản nháp
auto-hidden từ Duyệt menu, không cần filter thêm.

UAT mode: skip verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:27:29 +07:00
18ebfa15f4 [CLAUDE] FE-Admin+FE-User: PE workspace "new" — lock Loại quy trình + Select preset payment terms
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m36s
User feedback 2026-05-07 (annotation screenshot):
1. "Gán cứng Duyệt NCC hoặc Duyệt NCC và Giải pháp theo đúng Menu" — Loại quy
   trình lock theo URL ?type=N (user vào menu nào → loại đó, không chọn lại).
2. "Chỗ này vẫn hiểu code sửa lại thành select" — Điều khoản thanh toán đổi từ
   Textarea (JSON code-style placeholder) → Select preset options + "Khác".

Implementation:
  ~ PeWorkspaceCreateView.tsx (× 2 app)
    - Loại quy trình: <Select> editable → <Input disabled> hiển thị
      PurchaseEvaluationTypeLabel[type] với bg-slate-100. Label đổi sang
      "Loại quy trình (theo menu — khóa)" rõ ý đồ.
    - Điều khoản thanh toán: <Textarea> JSON → <Select> với 8 preset:
      "100% sau khi nghiệm thu" / "Tạm ứng 30% / 70%" / "Tạm ứng 50% / 50%" /
      "TGN-30 ngày" / "TGN-45" / "TGN-60" / "Tiến độ theo đợt" / "Bảo hành 5%"
      + last option "Khác (nhập tay)" → khi chọn show Input text custom.
    - Bỏ import Textarea (không dùng nữa).
    - paymentMode local state điều khiển select; form.paymentTerms vẫn save text.

UAT mode: skip verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:16:05 +07:00
7dfeb1a486 [CLAUDE] FE-Admin+FE-User: PE Danh sách view — disable toàn bộ tương tác
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
User feedback 2026-05-07: "Pe_*_List Danh sách disable toàn bộ tương tác, chỉ
show thông tin." Pending (Duyệt) vẫn cho approver chuyển phase + sign opinion.

Implementation:
  ~ PurchaseEvaluationsListPage.tsx (× 2 app)
    - PeDetailTabs readOnly={true} hardcoded (was readOnly={pendingMe})
      → ẩn "Sửa header" / "Xóa" / inline edit Section 1 / BudgetFieldRow edit /
        SuppliersTab edit / ItemsTab edit / OpinionBox sign forms ở MỌI view
        (Danh sách + Duyệt — Duyệt vẫn dùng được vì opinion sign ko phải in
        ListPage scope, ý kiến nhập ở leaf khác per session 11)
    - PeWorkflowPanel readOnly={!pendingMe}
      → Danh sách: hide "Chuyển tiếp" buttons + Dialog
      → Duyệt: vẫn show transition buttons (giữ cho approver work)
  ~ PeWorkflowPanel.tsx (× 2 app)
    - Add prop `readOnly?: boolean` default false
    - Khi readOnly=true: hide Chuyển tiếp section + transition Dialog
    - Show hint "Vào menu Duyệt để chuyển phase" thay vì button

Tạo phiếu mới button GIỮ ở header List page (không phải tương tác trên data
hiện có, là navigation tới create flow workspace).

UAT mode: skip verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:12:13 +07:00
66fa4691dc [CLAUDE] FE-Admin+FE-User: PE workspace "new" mode — sectioned create view 5 sections
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m4s
User feedback 2026-05-07: "Thêm mới list ra hết trường dữ liệu giống chỉnh sửa
nhưng trống, mở rộng từng phần. Save header xong mới cho nhập chi tiết."

Implementation:
  + PeWorkspaceCreateView.tsx (~230 LOC, mirror fe-admin + fe-user)
    - Sectioned card layout giống PeDetailTabs (5 section divider + title style)
    - Section 1 "Thông tin gói thầu": editable inputs (Loại / Tên */Dự án */Địa
      điểm/Mô tả/Payment) — 2-col grid responsive
    - Section 2 "Chọn NCC/TP":
      a. NCC chọn: text "(sau khi thêm NCC + chốt winner)" placeholder
      b. Ngân sách: editable inline (toggle "Nhập tay" + Select OR 2 input —
         giống BudgetFieldRow pattern)
      c. Giá chào thầu: text "(auto-tính sau winner)" placeholder
      d. Bản so sánh: LockedHint icon + text "Tải sau khi tạo"
    - Section 3 "NCC tham gia (0)": LockedHint "Lưu phiếu trước → thêm NCC..."
    - Section 4 "Hạng mục + Báo giá (0)": LockedHint
    - Section 5 "Ý kiến 4 PB": amber banner "nhập khi duyệt"
    - Action bar bottom: "Tạo phiếu" (disabled khi !tenGoiThau || !projectId)
      + Hủy
    - POST /pe full payload (header + budget mode A or B). onSuccess: toast +
      invalidate pe-list + onSaved(id, type) callback
  ~ PurchaseEvaluationWorkspacePage.tsx (× 2 app)
    - Replace <PeHeaderForm> → <PeWorkspaceCreateView> trong mode='new'
    - PeHeaderForm vẫn còn (dùng cho /new?id= deep-link "Sửa header" cũ)

Helpers duplicate trong PeWorkspaceCreateView (Section + FormRow + LockedHint)
để tránh circular import từ PeDetailTabs.

UAT mode rule applied (per memory feedback_uat_skip_verify): skip dotnet test
+ npm build verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:06:31 +07:00
5a89dd2188 [CLAUDE] FE-Admin: PE InfoTab inline edit Section 1 + PeListPanel pencil edit hover
User feedback 2026-05-07: muốn thêm nút edit kế bên row trong Panel 1, click →
Panel 2 sáng nội dung Section 1 lên cho user sửa header inline (KHÔNG cần đi
"Sửa header" page). Cũng muốn create new interface gần giống detail view
sectioned (defer cho chunk sau, hoặc keep PeHeaderForm nếu user OK).

Implementation:
  ~ PeDetailTabs.tsx
    - InfoTab thêm prop `readOnly` + `autoEdit` (trigger edit mode tự động khi
      mount nếu URL ?editHeader=1)
    - canEdit = !readOnly && isDraft (DangSoanThao):
      → display mode: hiển thị FormRow + button "✎ Sửa" góc trên phải Section 1
      → editing mode (click Sửa hoặc autoEdit): card border brand-200 + 4 input
        (Tên * / Dự án disabled / Địa điểm / Mô tả / Payment) + nút Lưu/Hủy
    - Save: PUT /pe/:id full payload (current ev values + new editable fields).
      onSuccess: invalidate ['pe-detail', 'pe-list'] + setEditing(false)
    - PeDetailTabs prop `autoEditHeader` mới — pass-through xuống InfoTab
  ~ PeListPanel.tsx
    - Thêm prop `onEditClick?: (id) => void`
    - Pencil icon (lucide) absolute right-2 top-2 trong mỗi <li>, opacity-0
      group-hover:opacity-100 — chỉ hiện khi user hover row + onEditClick set
    - Click → trigger onEditClick(id) callback (different from row click)
  ~ PurchaseEvaluationWorkspacePage.tsx
    - Đọc URL ?editHeader=1 → pass autoEditHeader xuống PeDetailTabs
    - PeListPanel onEditClick → setParams({ id, mode: null, editHeader: '1' })
    - onSelect (click row thường) → editHeader: null (clear flag)
    - onBack → clear editHeader

Verify: npm run build fe-admin pass · 0 TS error.

Pending: workspace "new" mode wrap PeHeaderForm trong sectioned layout giống
detail view (defer — user có thể chấp nhận PeHeaderForm hiện tại nếu OK).

Next: Chunk 2 fe-user mirror.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:55:49 +07:00
19712d89fc [CLAUDE] FE-Admin: PE BudgetFieldRow inline editor — toggle + 2 fields trong Section 2
User feedback 2026-05-07: muốn toggle "Nhập tay" + 2 input fields hiển thị trực
tiếp trong Section 2 "b. Ngân sách" (PeDetailTabs) — KHÔNG cần đi tới "Sửa
header" page. Visible trong cả 3 view (Workspace / Danh sách / Duyệt). Empty
giá trị thì hiển thị empty.

Implementation:
  + BudgetFieldRow component (~125 LOC) thay cho FormRow tĩnh cũ
  - Detect mode auto khi mount: prefer manual mode nếu ev.budgetManualName/Amount
    set + !ev.budgetId
  - canEdit = !readOnly && isDraft (DangSoanThao):
    → Render toggle "Nhập tay" + Select Budget OR 2 input grid 2-col + nút
      "Lưu ngân sách" (chỉ hiện khi dirty) + "Hủy thay đổi" reset
    → Save: full PUT /pe/:id với current values (tenGoiThau/diaDiem/moTa/
      paymentTerms) + new budget payload conditional (manual mode → clear
      budgetId, link mode → clear manual). Invalidate ['pe-detail', 'pe-list'].
  - canEdit=false (Duyệt mode hoặc !isDraft):
    → Display only — link card / manual values / empty "—" (không text "chưa
      link" verbose nữa per user "giá trị rỗng thì cứ hiển thị rỗng")

Files:
  ~ fe-admin/src/components/pe/PeDetailTabs.tsx
    - import BudgetPhase + BudgetListItem từ types/budget + Paged từ types/master
    - new BudgetFieldRow component
    - ChonNccSection b. Ngân sách FormRow → <BudgetFieldRow> (1-line replacement)

Verify: npm run build fe-admin pass · 1922 modules · 0 TS error.

Next: Chunk 2 fe-user mirror.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:12:20 +07:00
bab503189a [CLAUDE] FE-Admin: PE + HĐ toggle "Nhập tay" + 2 fields manual budget (Mig 17)
Chunk 3/5 — UI cho manual budget fallback Mig 17. Toggle checkbox "Nhập tay
(không link)" cạnh Label Ngân sách. Khi ON: hide Select Budget, show 2 input
field grid 2-col (Tên tham chiếu text + Số tiền number formatted VND).

Files sửa:
  ~ fe-admin/src/types/purchaseEvaluation.ts — PeDetailBundle +2 field
  ~ fe-admin/src/types/contracts.ts — ContractDetail +2 field
  ~ fe-admin/src/components/pe/PeHeaderForm.tsx — toggle + 2 input + payload
    conditional (manual mode → clear budgetId, link mode → clear manual). Auto-
    detect manual mode khi load existing PE có manual data + !budgetId.
  ~ fe-admin/src/components/pe/PeDetailTabs.tsx — Section 2 "b. Ngân sách"
    fallback display khi !ev.budget + có manual data: render text "Tên · Số tiền"
    + badge "nhập tay" thay vì "(chưa link)".
  ~ fe-admin/src/pages/pe/PurchaseEvaluationCreatePage.tsx — refactor wrap
    PeHeaderForm để DRY (auto-inherit toggle pattern, không drift). 222 LOC → 30 LOC.
  ~ fe-admin/src/pages/contracts/ContractCreatePage.tsx — apply same toggle
    pattern cho cả NewContractForm + EditContractForm. EditForm thêm read-only
    display branch khi !isDraft + có manual data.

Verify: npm run build fe-admin pass · 1922 modules · không TS error.

Next: Chunk 4 fe-user mirror (PeHeaderForm + PeDetailTabs + ContractCreatePage
+ types).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:37:42 +07:00
ee0d3608e7 [CLAUDE] FE-Admin: PE Thao tác 2-panel workspace + Panel 1 read-only picker + Section 5 disabled
Chunk 1/3 — restructure leaf "Thao tác" (Pe_*_Create) từ page tạo header riêng
sang workspace 2-panel mirror pattern HĐ Thầu phụ ContractCreatePage:
  Panel 1 (320px): list pure picker (KHÔNG inline edit/delete per Q1 user) +
                   sticky "+ Thêm mới" bottom button.
  Panel 2 (1fr):   empty state | mode=new <PeHeaderForm> | <PeDetailTabs
                   mode="workspace"> (5 section, Section 5 Ý kiến 4PB DISABLED
                   per Q5 user — nhập ở leaf "Duyệt").

Workflow Panel + Approvals + History KHÔNG render trong workspace (Q1) — chỉ
hiện ở leaf "Danh sách" + "Duyệt" giữ nguyên 3-panel hiện tại (Q3).

URL: /purchase-evaluations/workspace?type={1|2}[&id=...][&mode=new][&q=][&phase=]
Menu resolver Pe_*_Create: /purchase-evaluations/new?type=N → /workspace?type=N.
Route mới /workspace; route /new giữ tồn tại cho deep-link "Sửa header" button.

Files:
  + fe-admin/src/components/pe/PeListPanel.tsx (~180 LOC) — pure picker reuseable
  + fe-admin/src/components/pe/PeHeaderForm.tsx (~210 LOC) — extract header form
  + fe-admin/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx (~120 LOC)
  ~ fe-admin/src/components/pe/PeDetailTabs.tsx — add mode prop + Section 5 hint
  ~ fe-admin/src/components/Layout.tsx — resolver Pe_*_Create map workspace
  ~ fe-admin/src/App.tsx — route /purchase-evaluations/workspace

Verify: npm run build pass · dotnet test 83 vẫn pass (54 Domain + 29 Infra).
fe-user mirror = Chunk 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:36:06 +07:00
1fc439b978 [CLAUDE] App+Api+FE: Chunk E5 — Budget 2-stage dept approval (mirror PE/Contract)
Budget complete the trifecta — đồng bộ pattern 2-stage cho 3 module
(Contract + PE + Budget) cùng UX cho user khi UAT.

BE App:
- TransitionBudgetCommandHandler thêm INotificationService + IDateTime DI
- Mirror logic 2-stage từ ContractWorkflowService:
  - actor.DepartmentId != null + KHÔNG admin/system + KHÔNG resume
    - DeptManager (TPB) hoặc CanBypassReview → Stage=Confirm
    - Else (NV) → Stage=Review only, BLOCK transition
  - Upsert BudgetDepartmentApproval (UNIQUE BudgetId+Phase+Dept+Stage)
  - Block khi !hasConfirm: insert Approval + Changelog + Notify TPB → return early
- BudgetDepartmentApprovalFeatures.cs (List query mirror PE/Contract)

Api:
- BudgetsController endpoint GET /budgets/{id}/department-approvals

FE (cả fe-admin + fe-user):
- types/budget.ts thêm ApprovalStage const + BudgetDepartmentApproval type
- BudgetWorkflowPanel section "Tiến trình duyệt 2-cấp phòng ban":
  - Group by phase × dept, show Review NV + Confirm TPB
  - Highlight amber "chờ TPB confirm" + badge fuchsia bypass

Note: low-priority cho Budget (ít user duyệt budget per dept) nhưng giữ
consistent UX 3 module.

Build: BE pass + FE pass cả 2 + 77 test pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:46:56 +07:00
b6f5a16420 [CLAUDE] Infra+App+Api+FE: Chunk E4 — HĐ 2-stage dept approval (mirror PE)
Mở rộng 2-stage logic từ PE sang Contract workflow (Migration 16 đã có schema):

BE Service:
- ContractWorkflowService thêm UserManager<User> DI
- Mirror logic 2-stage từ PurchaseEvaluationWorkflowService.TransitionAsync
  Sau policy guard, trước gen mã HĐ:
  - User.DepartmentId != null + actor không admin/system + KHÔNG resume
    - DeptManager (TPB) → Stage=Confirm trực tiếp
    - CanBypassReview=true → Stage=Confirm + IsBypassed=true
    - Else (NV) → Stage=Review only, BLOCK transition
  - Insert ContractDepartmentApproval row (UPSERT theo UNIQUE)
  - Block transition khi chưa có Stage=Confirm:
    - Insert ContractApproval (FromPhase=ToPhase=fromPhase, [Review NV] comment)
    - Insert ContractChangelog "đã review, chờ TPB confirm"
    - Notify TPB cùng dept (UserManager filter DeptManager role)
    - Return early — phase KHÔNG đổi

App + Api:
- ContractDepartmentApprovalFeatures.cs (List query mirror PE)
- ContractsController endpoint GET /contracts/{id}/department-approvals

FE (cả fe-admin + fe-user):
- types/contracts.ts thêm ApprovalStage const + ContractDepartmentApproval type
- WorkflowHistoryPanel section "Tiến trình duyệt 2-cấp phòng ban":
  - Group by phase × dept, show Review NV + Confirm TPB
  - Highlight amber "chờ TPB confirm" khi current phase có Review chưa Confirm
  - Badge fuchsia "bypass" khi NV.CanBypassReview=true
  - Insert giữa WorkflowSummaryCard và Lịch sử duyệt
- Mirror cả 2 app (rule §3.9)

Use case mirror PE: HĐ ở phase DangGopY (P.CCM) — nv.cao (NV) duyệt thì
phase KHÔNG đổi (Review only), chờ ccm.tran (TPB) confirm mới sang DangXetDuyet.

Build: BE pass + FE pass cả 2 + 77 test pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:43:05 +07:00
4380bdc075 [CLAUDE] App+FE-Admin: Chunk E3 — UserManager toggle CanBypassReview
Admin UI bật/tắt CanBypassReview per user (Migration 16):
- BE: UserDto thêm field CanBypassReview (List + Get queries)
- FE: User type thêm canBypassReview field
- UsersPage: column "Bypass" badge fuchsia khi true + button toggle ShieldCheck
  (icon highlight fuchsia khi enabled, slate khi disabled)
- bypassMut PATCH /users/{id}/bypass-review { canBypassReview: !current }

Use case: phòng ban không có TPB hoặc TPB ủy quyền cho 1 NV cụ thể —
NV được Stage=Confirm trực tiếp (skip Stage Review), IsBypassed=true ghi audit.

Endpoint backend đã có sẵn ở Chunk E1 (commit 3c49316). Chỉ wire FE.

fe-user KHÔNG có UsersPage (admin-only function) — chỉ update fe-admin.

Build: BE pass + FE-admin pass + 77 test pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:38:09 +07:00
f8eebd57d1 [CLAUDE] FE-Admin+FE-User: Chunk E2 — 2-stage dept approval timeline panel (PE)
FE Workflow Panel hiển thị progress 2-cấp duyệt phòng ban (Migration 16):
- Section "Tiến trình duyệt 2-cấp phòng ban" trong PeWorkflowPanel
- Group rows by Phase × Department, show Stage Review NV + Confirm TPB
- Highlight amber "chờ TPB confirm" khi current phase có Review nhưng chưa Confirm
- Badge fuchsia "bypass" khi NV được CanBypassReview
- useQuery fetch endpoint GET /pe/{id}/department-approvals
- Invalidate query sau transition để refresh ngay

Type mới: ApprovalStage const + PeDepartmentApproval DTO trong types/purchaseEvaluation.ts.

User flow anh Kiệt test:
- phuong.nguyen (NV.PRO) Duyệt phase ChoPurchasing
  → row Review xuất hiện, panel hiển thị " chờ TPB confirm" (amber)
- tra.bui (TPB.PRO, DeptManager) Duyệt
  → row Confirm xuất hiện (emerald) + phase chuyển sang ChoCCM

2 file đồng bộ giữa fe-admin + fe-user (rule §3.9 duplicate có chủ đích).

Build: cả 2 FE pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:35:34 +07:00
5d94bb449a [CLAUDE] PE: Workflow designer admin UI + Ý kiến 4 phòng ban (P1 Session 5)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m51s
==== Task 1: PE Workflow Designer admin ====

BE (mirror Contract WorkflowAdminFeatures pattern):
- Application/PurchaseEvaluations/PeWorkflowAdminFeatures.cs ~250 LOC:
  - GetPeWorkflowAdminOverviewQuery → list 2 EvaluationType (DuyetNcc / DuyetNccPhuongAn) với Active + History versions + count phiếu đang dùng
  - CreatePeWorkflowDefinitionCommand + Validator: auto-increment Version per Code, deactivate Active cũ trong cùng EvaluationType (1 active per type invariant)
  - DTOs: PeWorkflowStepApproverDto / PeWorkflowStepDto / PeWorkflowDefinitionDto / PeWorkflowTypeSummaryDto / PeWorkflowAdminOverviewDto
  - Phase validation 1..7 (state thường, không bao gồm 99=TuChoi)
- Api/Controllers/PeWorkflowsController.cs: 2 endpoint GET /api/pe-workflows + POST. Reuse policy "Workflows.Read" + "Workflows.Create" (admin chung quyền cho cả 2 nhóm WF).

FE:
- pages/system/PeWorkflowsPage.tsx ~500 LOC mirror WorkflowsPage:
  - Landing 2-card grid khi /system/pe-workflows (chưa pick type)
  - TypePanel khi /system/pe-workflows/:typeCode (DuyetNcc / DuyetNccPhuongAn)
  - DefinitionCard read-only view với active badge + version + steps + approvers (Role/User chip)
  - PeWorkflowDesigner dialog: clone từ existing, edit Code/Name/Description, add/remove steps, +Role / +User approvers per step, save → version mới + deactivate cũ
- App.tsx route /system/pe-workflows + /system/pe-workflows/:typeCode
- Layout đã có resolver PeWf_<Code> → /system/pe-workflows/<code> từ session 3

==== Task 2: Ý kiến 4 phòng ban PE ====

Domain:
- PurchaseEvaluationDepartmentOpinion entity (AuditableEntity) — PEId + Kind + Opinion text + SignedAt + UserId + UserName denorm
- PeDepartmentKind enum (PheDuyet / Ccm / MuaHang / SmPm)
- PE entity + collection navigation DepartmentOpinions

Infrastructure:
- PurchaseEvaluationDepartmentOpinionConfiguration EF: UNIQUE(PEId, Kind) — max 1 row per phòng ban per phiếu (UPDATE in-place)
- ApplicationDbContext + IApplicationDbContext DbSet
- Migration 15 AddPurchaseEvaluationDepartmentOpinions (15 migration total / 52 DB tables)

Application:
- PeDepartmentOpinionFeatures.cs: UpsertPeDepartmentOpinionCommand (sign=true → set SignedAt+UserId, sign=false chỉ lưu text giữ chữ ký cũ) + DeletePeDepartmentOpinionCommand
- DTO bundle update: + DepartmentOpinions list trong PurchaseEvaluationDetailBundleDto
- GetPurchaseEvaluationQueryHandler load DepartmentOpinions + KindLabel resolution

API:
- POST /api/purchase-evaluations/{id}/opinions (upsert)
- DELETE /api/purchase-evaluations/{id}/opinions/{kind}

FE:
- types/purchaseEvaluation.ts: + PeDepartmentKind enum + PeDepartmentKindLabel + PeDepartmentOpinion type + departmentOpinions vào bundle
- PeDetailTabs Section "5. Ý kiến 4 phòng ban (sign-off)" — 2x2 grid OpinionBox per kind:
  - Read mode (readOnly menu Duyệt): hiển thị text + chữ ký
  - Edit mode: textarea + 2 button "Lưu text" / "Lưu & Ký"
  - Badge "Đã ký" emerald + tên người ký + ngày khi signedAt != null

==== Task 3: User seed verify ====

Seed `SeedDemoUsersAsync` đã match đúng user list authoritative (5 PRO TPB+NV / 7 CCM TPB+NV / 1 ISO / 1 CEO) từ prior commit. DbInitializer reconcile sẽ tự sync khi API restart. Typo trong list user (soluttions / trương) đã fixed sensibly trong seed.

==== Build verify ====
- dotnet build clean (0 error)
- fe-admin TS build pass (1 module mới PeWorkflowsPage)
- fe-user TS build pass (PE detail mirror)

Total: 8 file mới (BE 4 + FE 1 + Migration 2 + 1 Domain) + 13 file modified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:17:14 +07:00
7e36241db9 [CLAUDE] FE PE: restructure InfoTab theo spec PHIẾU TRÌNH KÝ CHỌN TP/NCC
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m3s
Match form chính thức 4 section đánh số:

1. Thông tin gói thầu — chỉ a. Tên gói thầu + b. Dự án (Địa điểm + Mô tả compact bên dưới nếu có).

2. Chọn NCC / TP — đúng a/b/c/d:
   a. NCC/TP được chọn — selectedSupplierName badge xanh
   b. Ngân sách — link Budget với mã + tên + tổng
   c. Giá chào thầu — tự compute = sum quotes của winner supplier (filter quotes.purchaseEvaluationSupplierId === winnerRowId)
   d. Bản so sánh — embed GeneralAttachmentsSection (attachments không gắn supplier-row, purpose=ComparisonTable)
   + ĐKTT + HĐ kế thừa link bonus
   + Banner emerald 'Tạo HĐ từ phiếu' khi DaDuyet + chưa có Contract

3. NCC/TP tham gia — section riêng giữ table 5 cột (NCC/Liên hệ/ĐKTT/File/Action — nhiều info hơn spec table 3 cột, useful cho UX web).

4. Hạng mục + Báo giá — matrix với cột 'NS link · Δ' + footer aggregate (giữ nguyên).

Side change:
- FormRow helper mới (label 176px + value flex) thay cho dl grid 2-col cũ — match style form giấy
- Drop Field helper cũ (now unused)
- InfoTab signature đổi: bỏ readOnly param (chỉ display, action move sang ChonNccSection)

TS build pass cả 2 app. Mirror fe-user identical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:02:45 +07:00
61e5d4d503 [CLAUDE] PE+Contract+Budget integration — link Budget vào PE/HĐ + cột So với ngân sách
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m51s
BE wire BudgetId nullable FK qua command + DTO bundle:
- Budgets.Dtos: + BudgetSummaryDto (compact header snapshot, không kèm Details — gọi /budgets/{id} riêng nếu cần đối chiếu chi tiết)
- PurchaseEvaluations.Dtos: + BudgetId? + Budget? BudgetSummaryDto vào PurchaseEvaluationDetailBundleDto
- Contracts.Dtos: + BudgetId? + Budget? BudgetSummaryDto vào ContractDetailDto
- CreatePE + UpdatePEDraft + handlers: + BudgetId? param + validate (cùng Project + Phase=DaDuyet) + persist
- CreateContract + UpdateContractDraft + handlers: + BudgetId? param + validate + persist + log diff
- GetPE + GetContract handlers: load BudgetSummary nếu có link
- CreateContractFromEvaluation: carry forward pe.BudgetId → contract.BudgetId (nếu phiếu PE đã link)

FE PE (cả 2 app):
- types/purchaseEvaluation.ts: + BudgetSummary type + budgetId/budget vào PeDetailBundle
- PurchaseEvaluationCreatePage: thêm Select 'Ngân sách' filter Phase=DaDuyet + Project match (BE-side filter qua /budgets?projectId=&phase=4). Disabled khi chưa pick Project. Edit mode preserve.
- PeDetailTabs InfoTab: hiển thị Budget link với mã + tên + tổng (clickable → /budgets?id=)
- PeDetailTabs ItemsTab: thêm cột 'NS link · Δ' chỉ hiện khi ev.budgetId. Match per-row qua key groupCode|itemCode → fetch /budgets/{id} riêng. Footer aggregate row 'Tổng' + delta indicator (xanh dưới / đỏ vượt / xám khớp). No-match cell hiện '—'.

FE Contract (cả 2 app):
- types/contracts.ts: + ContractBudgetSummary + budgetId/budget vào ContractDetail
- ContractCreatePage HeaderForm: thêm Budget Select sau FormFields, useEffect reset khi đổi project
- ContractCreatePage EditForm: Select khi isDraft / read-only link card khi !isDraft

TS build pass cả 2 app + dotnet build clean. No new migration (BudgetId? nullable FK đã có từ migration 14).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 16:41:11 +07:00
df12fb19c8 [CLAUDE] FE-Admin+FE-User: Module Ngân sách (Budget) FE — 3-panel List + Create + Detail tabs + Workflow timeline
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m59s
Mirror pattern PE 3-panel cho 2 app (admin + user):

- types/budget.ts (BudgetPhase 5-state enum + label/color, BudgetListItem, BudgetDetailRow, BudgetApproval, BudgetWorkflowSummary, BudgetChangelog, BudgetDetailBundle, BudgetDetailBody)
- components/budgets/BudgetDetailTabs.tsx — flat render Section "Thông tin" Header + Section "Hạng mục" table CRUD inline (Add/Edit/Delete dialog với auto-compute ThanhTien = KL × DonGia). Export BudgetApprovalsSection + BudgetHistorySection cho Panel 3 reuse.
- components/budgets/BudgetWorkflowPanel.tsx — Panel 3 timeline activePhases + nextPhases buttons (Approve/Reject color coding) + Dialog xác nhận có comment + sub-section Approvals + Changelog.
- pages/budgets/BudgetsListPage.tsx — 3-panel [340px_1fr_360px] với search + filter Phase + filter NamNganSach. ?phase=Pending alias FE filter 2 phase ChoCCM/ChoCEO. SlaTimer per row + readOnly mode khi pendingMe.
- pages/budgets/BudgetCreatePage.tsx — form Header (TenNganSach/Năm/Dự án/Phòng ban/Mô tả). Edit mode khóa Project+Department.
- App.tsx routes /budgets, /budgets/new, /budgets/:id cả 2 app
- Layout.tsx menu resolver Bg_List → /budgets, Bg_Create → /budgets/new, Bg_Pending → /budgets?phase=Pending. NavLink active dùng queryMatches helper (gotcha #34 — không conflict Bg_List vs Bg_Pending cùng pathname).

TS build: cả fe-admin + fe-user pass clean (1918 + 1901 modules).
BE: dùng 11 endpoint Budgets từ migration 14 (Phase 7 BE đã deploy commit a05c57b).

Tổng FE: +12 file (5 fe-admin + 5 fe-user + 2 mod App/Layout × 2). ~1100 LOC TSX.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 16:25:22 +07:00
a336997cfe [CLAUDE] PE: section Bang so sanh + rename demo email @solutions.com.vn
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m10s
PART A: Section 'Bang so sanh' (file tong ho so so sanh)

User request: 'theo them cho thong tin ve Bang so sanh, cho dinh kem
file so sanh tong len'.

BE:
 - PurchaseEvaluationAttachmentPurpose.ComparisonTable = 4 (new enum value)
   Backend validator IsInEnum pass, khong can migration (int column).

FE types (2 app):
 - PeAttachmentPurpose.ComparisonTable + Label '4: Bang so sanh'.

FE PeDetailTabs:
 - Them section thu 4 'Bang so sanh (file tong)' sau 'Hang muc + Bao gia'.
 - Component GeneralAttachmentsSection: upload KHONG truyen supplierRowId
   (BE luu NULL) → purpose=ComparisonTable default. Filter attachments
   co supplierRowId===null de render.
 - Card layout khac SupplierAttachmentsCell: full-width card + brand color
   + purpose chip + date. Upload button to hon ([+ Tai len bang so sanh]).
 - readOnly hide upload + delete, giu download.

PART B: Demo email rebrand @solutionerp.local → @solutions.com.vn

User request: 'tao nguoi dung demo theo email cua ben nay'.

BE DbInitializer:
 - Rename 18 email in source: AdminEmail const + 17 demo users
   (bod/pm/ccm/pro/fin/act/equ/hra/qs/nv) — keep password + role unchanged.
 - Them BackfillUserEmailDomainAsync (idempotent): scan user co email
   @solutionerp.local, rename sang @solutions.com.vn, update Email +
   NormalizedEmail + UserName + NormalizedUserName. Skip neu co conflict
   user da ton tai voi email moi. Chay truoc SeedAdmin de tranh tao
   duplicate admin.

Admin permission tao user da co san qua /system/users page.
Comment input khi duyet da co san o PeWorkflowPanel (Ghi chu tuy chon
Textarea) + ContractDetailContent (Yeu cau sua / Duyet tiep dialog).
2026-04-24 15:08:00 +07:00
eda9e84187 [CLAUDE] PE: readOnly mode cho menu 'Duyet' (pendingMe=1)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m50s
User request: 'Menu duyet cua NCC -> chi de duyet thoi nhe khong co cac
action them sua j vao'.

Them readOnly prop vao PeDetailTabs — propagate xuong 3 sub-component
(InfoTab / SuppliersTab / ItemsTab) + SupplierAttachmentsCell. URL
pendingMe=1 (menu 'Duyet') → set readOnly=true.

Hide khi readOnly:
 - Header: [Sua header] [Xoa] button
 - SuppliersTab: [+ Them NCC] button + action column (Check winner/Pencil
   edit/Trash delete per row)
 - ItemsTab: [+ Them hang muc] button + action column (Pencil/Trash per
   row) + click cell bao gia popup
 - SupplierAttachmentsCell: [+ Them file] button + Trash delete icon
   (giu download tren file name)
 - InfoTab: [Tao HD tu phieu] button

Giu:
 - Moi thong tin doc-only
 - Download file dinh kem (click ten file)
 - Panel 3: Quy trinh + transition button (de action duyet phase)
 - [Dong] button
 - Chip 'che do duyet' gan phase badge de user biet mode

Mirror fe-admin + fe-user.
2026-04-24 13:13:40 +07:00
8cf1fe214a [CLAUDE] FE: HĐ move Lich su dieu chinh sang Panel 3 duoi Lich su duyet
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m55s
User request: 'cho hop dong dua cac thong tin lich su dieu chinh sang
duoi lich su duyet nhen'.

ContractDetailContent (Panel 2): xoa section 'Lich su dieu chinh' (cot
3/10 grid 7/3) → Chi tiet HD gio full-width. Remove import History +
ContractChangelogsTab.

WorkflowHistoryPanel (Panel 3): them section Lich su dieu chinh duoi
Lich su duyet. Import History icon + ContractChangelogsTab. Reuse same
component, chi doi vi tri render.

Mirror fe-admin + fe-user.
2026-04-24 13:06:18 +07:00
d1090843a2 [CLAUDE] PE: upload file dinh kem per-NCC (doi chieu bao gia)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m9s
User request: 'cho cac NCC 1,2 va 3 thi cho them cho upload file dinh
kem cho tung NCC de co the doi chieu'.

Entity PurchaseEvaluationAttachment + PurchaseEvaluationSupplierId nullable
da thiet ke san tu migration 12 — gio wire up BE + FE.

BE (Application/Api):
 - PurchaseEvaluationAttachmentFeatures: Upload (multipart + supplierRowId
   optional) + Download + Delete. Reuse IFileStorage + LocalFileStorage.
   Validator 20MB + MIME whitelist (pdf/doc/docx/xls/xlsx/png/jpg/webp).
 - Upload log vao PurchaseEvaluationChangelogs (Attachment + Insert).
 - PurchaseEvaluationAttachmentDto + them field Attachments vao bundle.
 - GetPurchaseEvaluationQueryHandler Include(x => x.Attachments) +
   OrderByDescending(a => a.CreatedAt) projection.
 - PurchaseEvaluationsController 3 endpoint:
   POST /attachments (IFormFile + [FromForm] supplierRowId/purpose/note)
   GET /attachments/{attId}/download (File stream)
   DELETE /attachments/{attId}
 - Storage path: wwwroot/uploads/purchase-evaluations/{id}/{attId}_{safeName}

FE (fe-admin + fe-user):
 - Type PeAttachment + PeAttachmentPurpose/Label (QuoteDocument default)
 - PeDetailBundle.attachments: PeAttachment[]
 - SuppliersTab thay column Hien thi + Ghi chu bang column File dinh kem
   (per-NCC upload + list N attachments + download + delete).
 - SupplierAttachmentsCell component: <input type=file> hidden + [+ Them
   file] button + inline list attachments voi Paperclip icon + filename
   (click tai ve) + size + purpose chip + Trash2 delete.
2026-04-24 12:44:08 +07:00
68938a521a [CLAUDE] FE: PE detail flat layout — Panel 2 gop 3 section, Panel 3 them approvals + history
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m59s
User request: 'cho tat ca cai nay the hien tren dung 1 man hinh nhe, cai
duyet va lich su thi dua sang panel 3'.

Panel 2 (PeDetailTabs): truoc 5 tab (Info/NCC/Items/Approvals/History).
Sau bo tabs, flat render 3 section stack doc voi divider + title uppercase:
  Thong tin → NCC tham gia (N) → Hang muc + Bao gia (N)

Panel 3 (PeWorkflowPanel): truoc chi workflow timeline + transition btn.
Sau them 2 section ben duoi:
  Workflow timeline → Lich su duyet (PeApprovalsSection) → Lich su thay doi
  (PeHistorySection)

Export PeApprovalsSection + PeHistorySection tu PeDetailTabs — reuse
ApprovalsTab + HistoryTab logic cu, wrap them <h3> section title.

Dong bo ca fe-admin + fe-user (copy identical file).
2026-04-24 11:44:19 +07:00
fc4b3d6078 [CLAUDE] FE: NavLink active check query string (khong chi pathname)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m51s
Bug: click leaf 'Duyet' (/purchase-evaluations?type=2&pendingMe=1) khien
leaf 'Danh sach' (/purchase-evaluations?type=2) cung highlight cung luc.
Nguyen nhan: NavLink 'end' prop chi match pathname. 2 leaf cung pathname
/purchase-evaluations → ca 2 active.

Fix: custom isActive voi queryMatches helper — compare query string dang
key-value set (thu tu param khong quan trong). 2 leaf chi active khi
pathname + query dung khop.

Dong bo ca fe-admin + fe-user. Anh huong tat ca menu leaf co ?query=
variants: Ct_* (Danh sach /contracts?type=N vs Duyet /contracts?type=N&
pendingMe=1), Pe_* (tuong tu /purchase-evaluations), admin workflow leaf
Wf_* + PeWf_* (khong dinh vi path khong query params).
2026-04-24 11:23:56 +07:00
79398fb41f [CLAUDE] FE: accordion mutex Pe_* + sidebar width w-72 + label nowrap
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m56s
2 fix user session 3:

1. Accordion mutex cho Pe_ groups (2 phieu Duyet NCC + Duyet NCC va
   Giai phap) — truoc chi Ct_ (7 HD) co mutex, Pe_ dung localOpen rieng
   → user click 1 Pe_ group xong click Pe_ khac → ca 2 expanded cung luc.
   Fix: extend AccordionContextValue + expandedPeCode + INT_TO_PE_CODE
   map. MenuGroup branch: isCtAccordion || isPeAccordion (families doc
   lap — user co the mo 1 Ct_ + 1 Pe_ cung luc). useEffect URL sync:
   /purchase-evaluations?type → Pe; /contracts* ?type → Ct.

2. Sidebar width w-64 (256px) → w-72 (288px) de label QUY TRINH CHON
   THAU PHU - NCC (30 char uppercase tracking-wide) fit single-line
   thay vi wrap 2 dong. Top-level group class:
     text-xs tracking-wider → text-[11px] tracking-wide + whitespace-nowrap
   Dong bo ca fe-admin + fe-user.
2026-04-24 11:13:29 +07:00
7ee105df12 [CLAUDE] PE: rename menu + workflow Phuong An -> Giai phap
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m27s
User request session 3 mở đầu: đổi label menu:
  'Quy trình Duyệt NCC - Phương Án' -> 'Duyệt NCC và Giải pháp'
  'Quy trình Duyệt NCC' -> 'Duyệt NCC' (bỏ prefix 'Quy trình')

Giữ nguyên (không breaking):
  - Enum value PurchaseEvaluationType.DuyetNccPhuongAn = 2
  - Menu key Pe_DuyetNccPhuongAn_* (FK ref)
  - typeCode string 'DuyetNccPhuongAn' (URL routing)
  - Policy name 'NccWithPlan' + WorkflowDefinition.Code QT-DN-B

Changed (display-only):
  - DbInitializer SeedMenuTreeAsync peTypeLabels: 2 entry
  - DbInitializer SeedPurchaseEvaluationWorkflowsAsync WF name B
  - PurchaseEvaluationPolicy.cs NccWithPlan Description
  - fe-admin + fe-user types PurchaseEvaluationTypeLabel[2]

Backfill existing DB (seed idempotent skip-if-exists không update):
  - MenuItems Pe_DuyetNcc group + Pe_DuyetNccPhuongAn group + PeWf_* leaf
    → UPDATE Label nếu khác expected
  - WorkflowDefinitions QT-DN-B v01 → UPDATE Name Replace 'Phương Án' → 'và Giải pháp'
  Run mỗi start API — idempotent, fast no-op sau lần đầu.
2026-04-24 10:43:26 +07:00
66c1a5c170 [CLAUDE] Rebrand: 3 domain huypham.vn → solutions.com.vn + migrate script
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m52s
User request: anh trỏ 3 subdomain mới về VPS IP 103.124.94.38:
  - api.huypham.vn        → api.solutions.com.vn
  - admin.huypham.vn      → admin.solutions.com.vn
  - user.huypham.vn       → eoffice.solutions.com.vn

Verified DNS: cả 3 resolve 103.124.94.38 ✓

Update 17 file repo:
FE (4): fe-admin/.env.production + fe-user/.env.production
        (VITE_API_BASE_URL → https://api.solutions.com.vn)
        fe-admin/src/lib/{api,realtime}.ts + fe-user equivalents (comment)
BE (1): appsettings.Production.json.example — CORS AllowedOrigins
CI/CD (1): .gitea/workflows/deploy.yml — smoke test URL
Scripts (3): setup-iis-sites (DomainApi/Admin/User), setup-ssl (3 host),
             deploy-all (verify curls)
Docs (5): STATUS, HANDOFF, PROJECT-MAP, vps-setup, gotchas
Skill (1): iis-deploy-runbook — 3 site table + description
Email admin@huypham.vn giữ nguyên (Let's Encrypt contact — không phải
domain serve).

Thêm scripts/migrate-domains.ps1 — 1-shot VPS migration:
  1. Pre-flight: resolve DNS 3 domain → verify IP VPS khớp
  2. Add HTTP binding mới cho 3 IIS site (giữ binding cũ làm fallback)
  3. Run win-acme xin 3 cert Let's Encrypt qua HTTP-01 challenge
     (auto add HTTPS binding + http→https redirect)
  4. Verify /health/live + /health/ready + 2 FE endpoint
  5. (Optional -RemoveOld) xóa binding huypham.vn sau verify OK
Rollback: nếu fail, binding cũ vẫn active → site serve qua huypham.vn.

Anh chạy trên VPS:
  cd C:\solution-erp\scripts  ;  .\migrate-domains.ps1
  # Sau 1-2 ngày verify stable:
  .\migrate-domains.ps1 -RemoveOld -SkipCert
2026-04-24 09:43:05 +07:00
a385d70c2e [CLAUDE] App+Api+FE: Kế thừa HĐ từ phiếu Duyệt NCC (Phase 4)
BE:
 - CreateContractFromEvaluationCommand: guard DaDuyet + SelectedSupplier
   + ContractId=null → tạo Contract draft mới với SupplierId/ProjectId/
   DepartmentId kế thừa từ PE. GiaTri = sum(details.thanhTienNganSach).
   DraftData = PE.PaymentTerms. Gen MaHopDong ngay + pin WorkflowDefinitionId
   theo ContractType user chọn. Log Changelog cả 2 bảng (Contract +
   PurchaseEvaluation), link 2 chiều PE.ContractId = contract.Id.
 - ListApprovedPurchaseEvaluationsQuery: DaDuyet + ContractId=null cho
   FE picker.
 - 2 endpoint mới:
   GET  /api/purchase-evaluations/approved-pending-contract
   POST /api/purchase-evaluations/{id}/create-contract

FE:
 - PeDetailTabs InfoTab: nếu Phase=DaDuyet && !ContractId && SelectedSupplierId
   → banner emerald + button "Tạo HĐ từ phiếu" → CreateContractDialog
   (pick ContractType dropdown 7 loại + TenHopDong + bypass CCM flag)
 - Sau khi tạo → navigate /contracts/{newId}
 - Mirror fe-user.

KHÔNG auto-map PE Details → Contract Details per-type (PE schema ≠ 7
ContractType details schemas — user điền lại sau). PE → Contract link
qua FK ContractId cho navigation + history.
2026-04-23 16:58:41 +07:00
a737196b21 [CLAUDE] FE-Admin+FE-User: PurchaseEvaluation pages (3-panel list + tabs detail)
Types + pages + components cho module Duyệt NCC ở cả 2 FE (copy-share).

Pages:
 - PurchaseEvaluationsListPage: 3-panel lg:grid-cols-[340px_1fr_360px]
   * Panel 1: list filter theo type/phase/search + pendingMe inbox mode
   * Panel 2: PeDetailTabs (Thông tin/NCC/Hạng mục/Duyệt/Lịch sử)
   * Panel 3: PeWorkflowPanel với timeline + nextPhase buttons
   * Mobile fallback fullpage /purchase-evaluations/:id
 - PurchaseEvaluationCreatePage: form create/edit header (Type / Tên gói thầu
   / Dự án / Địa điểm / Mô tả / PaymentTerms JSON). Suppliers+Details+Quotes
   thêm sau khi save ở Detail tabs.

Components:
 - PeDetailTabs: 5 tab + dialogs (AddSupplier/EditSupplier/DetailDialog/
   QuoteDialog) + matrix N NCC × M hạng mục clickable cells + select winner
 - PeWorkflowPanel: policy timeline từ BE workflow.activePhases + transition
   confirmation dialog với comment

Routes (cả 2 app):
 - /purchase-evaluations (+ ?type=1|2&pendingMe=1&id=...)
 - /purchase-evaluations/new (+ ?type / ?id để edit)
 - /purchase-evaluations/:id (mobile fullpage)

Menu resolver:
 - Pe_<Code>_List → /purchase-evaluations?type=N
 - Pe_<Code>_Create → /purchase-evaluations/new?type=N
 - Pe_<Code>_Pending → /purchase-evaluations?type=N&pendingMe=1
 - PeWf_<Code> (fe-admin only) → /system/pe-workflows/<code>

Skip MVP: PE Workflow admin designer UI, PE Attachments. TS build pass
cả 2 app.
2026-04-23 16:56:26 +07:00
e53cd3a3b2 [CLAUDE] App+Api+FE+Scripts: Edit detail row inline + deps audit helper
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m45s
## Edit detail row inline (BE)

7 typed UpdateXxxDetailCommand handler trong ContractDetailsFeatures.cs
— pattern lặp giống Add commands, EnsureContractType guard + log
ChangelogAction.Update với summary "Sửa <hạng mục/SP/CV/...>".

7 PUT endpoints trong ContractsController:
- PUT /contracts/{id}/details/{thau-phu|giao-khoan|nha-cung-cap|dich-vu|
  mua-ban|nguyen-tac-ncc|nguyen-tac-dv}/{detailId}

## Edit detail row inline (FE)

ContractDetailsTab.tsx refactor:
- DeleteBtn → ActionBtns (Pencil + Trash) với onEdit + onDelete callbacks
- 7 XxxTable signatures + onEdit prop + pass row data via callback
- New EditRowDialog component:
  * useEffect populate form từ row data khi target thay đổi
  * Reuse FIELDS_BY_TYPE config + buildPayload (compute thanhTien)
  * Date field convert ISO → yyyy-MM-dd cho input[type=date]
  * PUT /contracts/{id}/details/{slug}/{detailId}
- Parent state editTarget — open dialog, close khi save thành công

Mirror fe-admin (file copy).

## Deps audit helper script

scripts/deps-audit.ps1 — chạy thủ công hoặc CI integration:
- dotnet list package --vulnerable --include-transitive (BE)
- npm audit --audit-level=moderate (fe-admin + fe-user)
- Color-coded output (green/red), summary cuối
- -FailOnHigh switch để CI gate

Skill ref .claude/skills/dependency-audit-erp/SKILL.md (đã có) cho
pin constraints + workflow fix.

## Build

- BE: dotnet build pass (0 error)
- fe-user: tsc + vite pass (11.52s)
- fe-admin: tsc + vite pass (577ms)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:18:53 +07:00
072ad6d014 [CLAUDE] App+Api+FE-Admin: RolesPage CRUD (/system/roles)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m58s
User feedback: /system/roles trỏ tới placeholder "chưa được build" — build
trang quản lý 12 role mặc định + custom role admin tự thêm.

## BE — PermissionFeatures.cs

3 command mới:
- CreateRoleCommand — Name regex `^[A-Za-z][A-Za-z0-9_]*$` (chỉ chữ/số/
  underscore, bắt đầu chữ), throw ConflictException nếu code đã tồn tại
- UpdateRoleCommand — CHỈ update ShortName + Description. KHÔNG đổi
  Name (Identity FK trong UserRoles + WorkflowStepApprover.AssignmentValue
  + [Authorize(Roles="...")] attr — đổi = data corruption widespread)
- DeleteRoleCommand — block 2 trường hợp:
  * Role thuộc AppRoles.All hardcoded (workflow guard reference)
  * Còn user assigned (UserManager.GetUsersInRoleAsync count > 0)

ValidationException reference fully-qualified để tránh ambiguous với
FluentValidation.ValidationException.

## BE — RolesController

3 endpoint mới (POST/PUT/DELETE) — Authorize Admin role.

## FE — RolesPage

Table list 12 + custom roles với 5 column (Mã code / Mã viết tắt / Tên
đầy đủ / Loại badge / Ngày tạo) + actions Edit/Delete:
- Edit dialog: chỉ ShortName + Description editable, Name disabled với
  hint "Không đổi được sau khi tạo"
- Delete: block với toast nếu role mặc định (HARDCODED_ROLES set check
  client-side trước khi gọi BE — UX faster, BE vẫn double-check)
- Create dialog: 3 field Name (regex pattern HTML5) + ShortName + Description
- Banner amber warning về Mã code FK constraint
- Loại badge: Mặc định (slate) vs Tùy chỉnh (brand)

## FE — App.tsx

+ import RolesPage + route /system/roles → RolesPage.

## Build

- BE: dotnet build pass (0 error)
- fe-admin: tsc + vite pass (13.88s)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:57:36 +07:00
ae59cfeb5d [CLAUDE] FE-Admin: UsersPage dept/position field + RoleShortName tiếng Việt
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m27s
## types/users.ts

- User type + departmentId/departmentName/position
- CreateUserInput + departmentId/position
- RoleShortName map (Mã viết tắt VN per role): QTV/NV.PB/TPB/PM/PRO/CCM/
  FIN/ACT/EQU/BOD/NĐUQ/HRA
- RoleLabel map (Tên đầy đủ VN per role)
- roleDisplayName(role) → "BOD — Ban Giám đốc" combined helper

## types/menu.ts

Role type + shortName field (mirror BE RoleDto).

## UsersPage redesign

- Column "Phòng ban" (departmentName + position 2 dòng)
- Column "Vai trò" hiển thị badge ShortName ("BOD", "CCM", "PM"), tooltip
  hover full label
- Column actions thêm "Sửa thông tin" (Pencil icon) — dialog edit dept/
  position/active state
- Create dialog 2-col grid: Email | Họ tên / Phòng ban (dropdown) | Chức vụ /
  Password (col-span-2). Roles checkboxes hiển thị "ShortName — full label"
- Edit dialog mới — sửa fullName + dept + position + isActive
- Roles dialog title kèm dept name (context cho user reviewer)
- toggleActive mutation include departmentId/position để không reset

## PermissionsPage

Panel 1 role list:
- 2-line per row: ShortName (semibold) + Description (truncate small)
- Tooltip = description đầy đủ
- Active row vẫn ring-brand-200

Panel 2 header badge: ShortName thay name code English.

## Build

fe-admin: tsc + vite pass (12.17s lần 1, 671ms lần 2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:28:32 +07:00
39031ca33c [CLAUDE] FE: ContractDetailsPreview cho create mode — table headers + disabled add
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m36s
User feedback: thay vì placeholder dashed nhỏ "Chi tiết sẽ hiện sau khi
tạo Header", show structure thật của Chi tiết section ngay từ đầu nhưng
disabled. User thấy trước layout columns + button add → trải nghiệm
liên tục, không bất ngờ khi switch sang edit mode.

## Component mới: ContractDetailsPreview

- Section title "Chi tiết ({TypeLabel})" + amber pill "🔒 Cần tạo Header trước"
- Table opacity-60 với:
  - thead column headers per type (sync với HEADERS_BY_TYPE config)
  - tbody empty state: Lock icon + "Tạo Header xong sẽ thêm được hạng mục"
- Disabled "+ Thêm dòng" button (cursor-not-allowed, slate-400 text)

## HEADERS_BY_TYPE config

7 type × column headers — duplicate nhỏ với ContractDetailsTab.tsx renderers
(acceptable: chỉ là labels visual, không logic).

## Reactive theo type

User đổi dropdown "Loại HĐ" → preview headers update tương ứng (state-driven).

## Build

- fe-user: tsc + vite pass (586ms)
- fe-admin: tsc + vite pass (709ms)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:33:00 +07:00
16e24ed962 [CLAUDE] FE: Admin CatalogsPage CRUD + Details form datalist autocomplete
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m36s
## fe-admin

### CatalogsPage.tsx (mới)

1 page generic CRUD cho 4 master catalogs (units / materials / services /
work-items). Dispatch theo URL param :kind:
- /master/catalogs (redirect units)
- /master/catalogs/:kind (units|materials|services|work-items)

Sub-tabs ở top chuyển nhanh giữa 4 kind. Mỗi kind có FIELDS_BY_TYPE config
riêng (3-7 field). Form dialog nested với input/textarea/checkbox theo type.

### Layout.tsx + App.tsx

- resolvePath thêm 4 CatalogXxx → /master/catalogs/{kind}
- Route /master/catalogs/:kind → CatalogsPage

## fe-user + fe-admin (mirror)

### ContractDetailsTab.tsx

Datalist autocomplete cho Add row form:
- Fetch 4 catalogs via TanStack Query (cache shared key 'catalogs')
- Mỗi field config thêm optional `datalist` + `datalistField` ('code'|'name')
- HTML5 <datalist> render options theo type:
  - ThauPhu: hangMuc → work-items, donViTinh → units
  - GiaoKhoan: maCongViec/tenCongViec → work-items, donViTinh → units
  - NhaCungCap/MuaBan: maSP/tenSP → materials, donViTinh → units
  - DichVu: maDichVu/tenDichVu → services, donViTinh → units
  - NguyenTacNcc: tenSP → materials, donViTinh → units
  - NguyenTacDv: tenDichVu → services, donViTinh → units

Smart-fill (handleFieldChange):
- User pick value khớp catalog → autofill sibling fields cùng catalog:
  - Field name 'maXxx' → fill code; 'tenXxx'/'hangMuc' → fill name
  - donViTinh nếu chưa có giá trị → fill từ defaultUnit của catalog item

Vẫn cho user gõ tự do (free text) — datalist chỉ là suggestion.

## Build

- fe-user: tsc + vite pass (5.69s)
- fe-admin: tsc + vite pass (633ms + 15.84s lúc test CatalogsPage lần đầu)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:27:41 +07:00
7f26ff9d66 [CLAUDE] FE-User+FE-Admin: 2 button luôn hiện, mờ + disabled khi != DangSoanThao
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m45s
User feedback: hiển thị Edit + Xóa cho mọi row (kể cả Phase khác), nhưng
mờ và disabled khi không thao tác được — để user biết button TỒN TẠI mà
không bị bất ngờ phải hover row đúng phase mới thấy.

## Thay đổi

- Bỏ conditional render `{phase === DangSoanThao && ...}`
- Thêm canMutate = c.phase === DangSoanThao biến + className conditional:
  - canMutate=true: text-slate-500 + hover brand/red + clickable
  - canMutate=false: text-slate-300 + cursor-not-allowed + disabled
- Default opacity-60 (luôn visible nhẹ), group-hover:opacity-100 (rõ
  khi hover)
- title tooltip thay đổi theo state — hint user lý do disable
- onClick guard early return nếu !canMutate (defense in depth)

Build: tsc + vite pass cả 2 app

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:25:22 +07:00
501b4de418 [CLAUDE] FE-User+FE-Admin: Edit + Xóa, chỉ hiện khi DangSoanThao
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m45s
User feedback: 2 button trên row Panel 1 phải là Edit + Xóa, và CHỈ
hoạt động trong trạng thái nhập liệu/điều chỉnh (Phase = DangSoanThao).

## Thay đổi

- ✏ Pencil icon thay ExternalLink — hành động Edit (select Panel 2 form)
- 🗑 Trash2 — Xóa (giữ nguyên)
- Cả 2 button bọc trong `c.phase === DangSoanThao` conditional → Phase
  khác (DangGopY, DangDamPhan, ...) → ẩn cả 2
- Lý do: BE chỉ cho update + delete khi Phase=DangSoanThao
  (UpdateContractDraftCommand + DeleteContractCommand throw Conflict-
  Exception nếu khác)

## UX

- Phase=DangSoanThao: hover row → 2 button fade in
- Phase khác: chỉ row click select Panel 2 (form sẽ render read-only +
  banner amber "HĐ đã chuyển khỏi Đang soạn thảo")

Build: tsc + vite pass cả 2 app

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:06:29 +07:00
ec0c983e8e [CLAUDE] FE-User+FE-Admin: action buttons (Mở chi tiết + Xóa) trên row Panel 1 Thao tác
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m35s
User feedback: thêm nút edit/action ở mỗi row trong list Panel 1 trang
Thao tác. Hiện absolute positioned ở góc phải-trên row, opacity-0 → 100
khi hover (group-hover). Sibling không nested để click không trigger
row select propagation.

## 2 button per row

- ⤴ ExternalLink → navigate /contracts/{id} (fullpage detail với
  Workflow + History, khác Panel 2 chỉ có Edit form)
- 🗑 Trash2 → confirm() + DELETE /contracts/{id} (soft delete,
  blocked sau DangInKy ở BE). Nếu xóa HĐ đang select → clear ?id=

## Implementation details

- pr-16 cho row button để chừa khoảng cho action group
- group-hover:opacity-100 transition (smooth fade in)
- Mutation invalidate ['my-contracts'] sau xóa thành công
- Toast success + getErrorMessage cho fail case (vd xóa HĐ đã qua DangInKy)

Build: tsc + vite pass cả 2 app (fe-user 515ms, fe-admin 937ms)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:54:54 +07:00
8c4b4da951 [CLAUDE] FE-User+FE-Admin: 2-panel layout cho Thao tác (Ct_*_Create)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m36s
Trang /contracts/new?type=X (menu "Thao tác") redesign từ single form
→ 2-panel: Panel 1 list HĐ theo type | Panel 2 Header form + Chi tiết.

## Layout

Panel 1 (320px) — flex column 3 vùng:
- Top: Search box (filter mã/tên/NCC client-side)
- Middle: List HĐ theo type (scroll, click row chọn)
- Bottom: + Thêm mới button (sticky, ring-brand khi active mode=new)

Panel 2 (flex) — 3 trạng thái theo URL:
- Empty state — chưa chọn HĐ và chưa bấm + Thêm mới
- ContractHeaderForm (mode=new) — form trống, sau Tạo HĐ draft
  → URL update ?id=newId chuyển edit mode
- ContractEditForm (id=abc) — form populated từ /contracts/{id}, +
  section Chi tiết bên dưới (ContractDetailsTab reuse)

## URL state

- ?type=X            → empty
- ?type=X&mode=new   → form trống
- ?type=X&id=abc     → edit form + Chi tiết
- ?type=X&q=keyword  → search filter Panel 1

## Edit constraints

ContractEditForm respect UpdateContractDraftCommand limits:
- Editable khi Phase=DangSoanThao: Tên HĐ, Giá trị, Template, Nội dung
- Read-only luôn: Loại HĐ, NCC, Dự án, Bypass CCM (không đổi sau create
  qua BE command hiện tại)
- Khi Phase != DangSoanThao: warning amber + tất cả input disabled,
  nhưng Chi tiết section vẫn render để user xem (ContractDetailsTab tự
  disable add/delete khi không phải draft)

## Components

ContractCreatePage.tsx (rewrite) — page entry
ContractHeaderForm — create mode (full fields editable)
ContractEditForm — edit mode (limited fields + Chi tiết section)
FormFields helper — shared form layout cho create

## Build verify

- fe-user: tsc + vite pass (374ms)
- fe-admin: tsc + vite pass (987ms)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:48:29 +07:00
ad0652d590 [CLAUDE] FE-User+FE-Admin: bỏ tabs, Chi tiết + Lịch sử 7/3 grid dưới Overview
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m44s
User feedback: thay vì click tab để switch, hiển thị Chi tiết + Lịch sử
LUÔN ngay dưới Tổng quan content. Tỷ lệ cột 7 (Chi tiết) - 3 (Lịch sử
điều chỉnh).

## Thay đổi (apply 2 app)

ContractDetailContent.tsx:
- Bỏ TabsNav + tab state + TabButton helper
- Bỏ conditional render theo tab
- Tổng quan content (Info / Comments / Attachments) render flat đầu tiên
- Thêm grid lg:grid-cols-10 dưới cùng:
  - lg:col-span-7 → ContractDetailsTab (line items)
  - lg:col-span-3 → ContractChangelogsTab (timeline)
- Mobile (<lg): stack vertical 1 cột, Chi tiết trên, Lịch sử dưới

## Build verify

- fe-user: tsc + vite pass (17.23s)
- fe-admin: tsc + vite pass (8.12s)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:39:07 +07:00
b3762afbc3 [CLAUDE] FE-User+FE-Admin: Panel 2 tabs (Tổng quan / Chi tiết / Lịch sử)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m39s
Phần 2.6 — UI cho 4-bảng data model overhaul. Panel 2 trong 3-panel
layout giờ tabbed thay flat, expose 7 Details + Changelog cho user.

## Files mới (4 per app — cố ý duplicate theo project convention)

### types/contract-details.ts
7 typed interfaces (ThauPhuDetail, GiaoKhoanDetail, NhaCungCapDetail,
DichVuDetail, MuaBanDetail, NguyenTacNccDetail, NguyenTacDvDetail) +
ContractDetailsBundle wrapper (chỉ 1 list có data theo Type) +
ContractChangelog + 4 enum const-objects với Vietnamese label.

### components/contracts/ContractDetailsTab.tsx (~400 dòng)
- Auto-pick render component theo bundle.type (7 table renderers)
- Mỗi table: header per type + columns thanhTien total + delete row btn
- AddRowForm sử dụng FIELDS_BY_TYPE config (5-7 field per type)
- buildPayload auto compute thanhTien (SL × DonGia × (1+VAT/100) cho MuaBan)
- canEdit chỉ khi Phase = DangSoanThao (sau khi nộp HĐ → khóa edit details)
- Banner amber cảnh báo khi không edit được

### components/contracts/ContractChangelogsTab.tsx (~150 dòng)
- Render unified changelog list desc CreatedAt
- Icon + tone color theo EntityType (5 loại: Contract/Detail/Workflow/
  Comment/Attachment)
- Expandable detail row hiển thị FieldChangesJson (parse JSON, render
  table Field|Cũ|Mới với strike-through old + emerald new)
- Show contextNote khi có

## Files sửa (2 per app)

### components/contracts/ContractDetailContent.tsx
- Thêm useState<Tab>('overview')
- Tabs nav (TabButton helper, border-b underline active brand-700)
- 3 tab: Tổng quan (Info + Comments + Attachments) | Chi tiết (Details
  table + add form) | Lịch sử (changelog timeline)
- Wrap Tổng quan content trong fragment, conditionally render

## Build verify

- fe-user: tsc + vite pass (521ms, 1888 modules)
- fe-admin: tsc + vite pass (997ms, 1903 modules)

## Test flow desktop

1. /my-contracts → click HĐ → Panel 2 mở tab "Tổng quan" (default)
2. Click tab "Chi tiết (HĐ Mua bán)" → table line items + add row form
3. Add row "Xi măng / kg / 1000 / 50000" → POST /contracts/{id}/details/mua-ban
   → table refresh + changelog tự log "Thêm SP: Xi măng"
4. Click tab "Lịch sử" → thấy entries: Tạo HĐ, Thêm SP Xi măng, ...
5. Tab giữ state khi switch HĐ khác (per-component state)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:24:00 +07:00
b75448e711 [CLAUDE] FE-User+FE-Admin: 3-panel layout cho danh sách HĐ
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m50s
Redesign trang Danh sách HĐ (Ct_*_List menu fe-user + /contracts admin)
thành 3-panel: List | Detail content | Workflow + lịch sử duyệt. Selected
HĐ giữ qua URL ?id= (bookmarkable + back/forward navigation work).

## Components mới (reuse cho cả 3-panel embedded + fullpage detail)

### fe-user/src/components/contracts/
- ContractDetailContent.tsx — Panel 2 body: header sticky (title + phase
  + actions Yêu cầu sửa/Duyệt) + Info section + Comments thread + form
  thêm góp ý + Attachments. Transition Dialog inline. Prop optional
  onBack — render arrow back button (fullpage) hoặc skip (embedded).
- WorkflowHistoryPanel.tsx — Panel 3: WorkflowSummaryCard (timeline
  policy current+next) + Lịch sử duyệt (approvals: phase from→to + actor
  + timestamp + comment).

### fe-admin/src/components/contracts/
- ContractDetailContent.tsx — variant admin có thêm Phòng ban + Bypass
  CCM trong Info section. Invalidate ['contracts'] khi transition.
- WorkflowHistoryPanel.tsx — identical fe-user.

## Trang refactored

### fe-user
- MyContractsPage.tsx — bỏ DataTable, dùng 3-panel grid
  lg:grid-cols-[320px_1fr_360px] h-[calc(100vh-4rem)]:
    Panel 1: search box + list compact (mã/tên/NCC/phase/SLA/giá), click
             update ?id= active highlight ring-brand
    Panel 2: detail content embedded
    Panel 3: workflow + history
  Mobile (<lg): chỉ Panel 1 visible, click row navigate fullpage
  /contracts/:id (UX khả dụng, không nhồi 3 panel màn hình hẹp).
  URL state: ?type=X (filter loại) + ?id= (selected) + ?q= (search).
- ContractDetailPage.tsx — slim version dùng ContractDetailContent +
  WorkflowHistoryPanel, giữ deep link /contracts/:id work.

### fe-admin
- ContractsListPage.tsx — 3-panel + filter phase + pagination compact
  trong Panel 1 footer. URL state: ?type, ?pendingMe, ?id, ?q, ?phase,
  ?page (full bookmarkable). Title hiển thị loại HĐ + count badge.
- ContractDetailPage.tsx — slim version giống fe-user.

## Build verified

- fe-user: tsc -b + vite build pass (1888 modules, 1.08MB JS)
- fe-admin: tsc -b + vite build pass (1903 modules, 1.15MB JS)

Note: npm install resolved @microsoft/signalr 8.0.7 → 8.0.17 (within
^8.0.7 caret), reverted package.json + lock changes do bump không phải
scope task này. Dev tiếp theo run npm install sẽ tự re-resolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 09:04:46 +07:00
91b2da147f [CLAUDE] FE-Admin: PermissionsPage 3-panel layout
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m33s
Redesign theo yêu cầu user: 3 panel vertical đồng thời trên cùng 1
màn hình (không modal/dialog popup).

Layout grid lg:grid-cols-[280px_1fr_300px]:

Panel 1 — Vai trò (trái, 280px):
  Danh sách roles click-to-select với active highlight (brand-50 bg +
  ring-brand-200 + check icon). Đếm số roles ở header.

Panel 2 — Quyền theo menu (giữa, flex):
  Tìm menu inline header + sticky thead. Click vai trò → lọc menu
  instant. Column toggle header (tick toàn cột) + per-cell checkbox.
  Hover brand-tinted. Menu key hiện mono nhỏ dưới label.

Panel 3 — Tổng quan (phải, 300px):
  Vai trò đang chọn + số quyền (progress bar brand) + chi tiết từng
  CRUD (Xem/Tạo/Sửa/Xóa) với badge color-coded (slate/emerald/amber/
  red) + count "X / Y menus" + tip helper cuối.

Bỏ dialog select + 3-col grid filter ở đầu (thay bằng 3 panel), giữ
logic mutation/toggle/column nguyên.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:02:25 +07:00
f216169039 [CLAUDE] FE-Admin+Domain+Infra+App: Workflows tab → sidebar menu items
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m37s
User request: 7 tab trong /system/workflows thành menu items riêng.

Domain:
- MenuKeys.WorkflowTypeLeaf(code) helper — `Wf_<TypeCode>` pattern

Infrastructure (DbInitializer):
- Seed 7 leaves dưới Workflows group (order 95..101), label matches
  ContractType (HĐ Thầu phụ / Giao khoán / NCC / Dịch vụ / Mua bán /
  Nguyên tắc NCC / Nguyên tắc Dịch vụ). Idempotent.

Application (GetMyMenuTreeQuery):
- Generalized inherit-perm logic: descendants of Contracts AND Workflows
  inherit parent CanRead flag. Single Workflows.Read grant → all 7
  Wf_* leaves visible; no per-leaf permission rows needed.

FE Layout (admin):
- resolvePath: Wf_<Code> → /system/workflows/<code>. Ct_* still hidden
  on admin side.

FE App.tsx:
- New route /system/workflows/:typeCode?

FE WorkflowsPage:
- Removed horizontal tab bar; type selection now comes từ URL param.
- Landing view (no param): 3-col grid card per type với active version
  badge — so admin có visual overview khi click top-level Workflows
  group without selecting a type.
- TYPE_CODE_TO_INT map drives URL→int conversion.

Result: click `Quy trình HĐ > HĐ Mua bán` trong sidebar → opens
/system/workflows/MuaBan directly với designer scoped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:49:42 +07:00
355bbe3a61 [CLAUDE] FE-Admin: Dialog size xl → lg (fix TS2322 CI build)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
2026-04-22 09:31:10 +07:00
e7e5f2d066 [CLAUDE] Domain+Infra+App+Api+FE-Admin: versioned workflow per ContractType
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m32s
User yêu cầu: mỗi loại HĐ có quy trình riêng với admin add roles + users
vào từng bước. Khi tạo version mới → HĐ tương lai chạy theo, HĐ cũ giữ
version cũ.

Domain:
- WorkflowDefinition (Code + Version + ContractType + IsActive + Steps)
- WorkflowStep (Order + Phase + Name + SlaDays + Approvers)
- WorkflowStepApprover (Kind: Role/User + AssignmentValue)
- Contract.WorkflowDefinitionId — pinned at creation
- WorkflowPolicyRegistry.FromDefinition() — build runtime policy từ DB

Infrastructure:
- EF config + migration AddVersionedWorkflows (3 table mới)
- DbInitializer.SeedWorkflowDefinitionsAsync: v01 per 7 ContractType,
  steps sinh từ hardcoded WorkflowPolicies (Role approvers).
- ContractWorkflowService.TransitionAsync: load pinned WorkflowDefinition
  → FromDefinition(), fallback cho HĐ cũ không có pin.

Application:
- CreateContractCommand pin WorkflowDefinitionId = active version cho type
- ContractFeatures.Get(id): load pinned def cho workflow summary
- WorkflowAdminFeatures: GetWorkflowAdminOverviewQuery (7 types + active
  + history + ContractsUsingCount), CreateWorkflowDefinitionCommand
  (validate payload, auto-increment version, deactivate old).

Api:
- GET /api/workflows trả overview
- POST /api/workflows tạo version mới (deactivate old)

FE /system/workflows:
- Tabs per 7 ContractType, mỗi tab hiện active version + lịch sử
- DefinitionCard: steps với badge role/user + SLA + archived indicator
  hiện "N HĐ còn chạy" cho version cũ
- WorkflowDesigner modal: form code/name/desc + danh sách steps
  (phase/name/SLA) + approvers (+ Role hoặc + User). Drop step ok.
  Clone từ version hiện tại để tạo v02 có điểm start sensible.
- Amber banner: HĐ cũ không bị ảnh hưởng khi tạo version mới

Invariants được giữ:
- Unique (Code, Version) index
- Chỉ 1 version IsActive per ContractType tại 1 thời điểm
- Set default sẽ auto xóa override → respect legacy override table
- Role-kind approvers drive transition guards; User-kind fallback
  DeptManager role cho v1 (user-level targeting = iteration 2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:57:41 +07:00
5e0f3801a1 [CLAUDE] Move nested-type menu → fe-user; Admin workflow config page
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m41s
User clarified: menu loại HĐ 3-level (Danh sách/Thao tác/Duyệt) thuộc
fe-user. Admin có page riêng để config quy trình per loại HĐ.

fe-admin Layout:
- filterForAdmin() drops Ct_* entries (hide nested type menu).
- Admin sidebar giờ về lại đơn giản: Dashboard / Master / Hợp đồng
  (leaf) / Forms / Reports / System.

fe-user Layout:
- Dynamic menu tree từ /menus/me (thay fixed USER_MENU hardcoded).
- Recursive MenuNodeRenderer (top-level expanded, nested collapsed).
- resolvePath user-specific: Ct_*_List → /my-contracts?type=X,
  Ct_*_Create → /contracts/new?type=X, Ct_*_Pending → /inbox?type=X.
- filterForUser drops admin-only entries (Master/System/Forms/Reports).
- Static USER_FIXED_TOP prepends "Hộp thư" leaf → /inbox.
- MyContractsPage + InboxPage đọc ?type=X param, filter client-side.

Workflow config (Admin side):
- Domain: WorkflowTypeAssignment entity (ContractType → PolicyName
  override). Registry.ForContractWithOverrides() prefer DB override
  else default.
- Infrastructure: EF config + migration AddWorkflowTypeAssignments,
  unique index trên ContractType. ContractWorkflowService load
  overrides dict mỗi transition. ContractFeatures load overrides khi
  build WorkflowSummaryDto.
- Application: GetWorkflowAdminOverviewQuery returns 7 types × current
  policy + available policies. SetWorkflowAssignmentCommand validate
  policy name tồn tại; nếu = default thì delete override (no stale row).
- Api: GET /api/workflows + PUT /api/workflows/{contractType}
  với policy "Workflows.Read" + "Workflows.Update".
- Menu: new key `Workflows` dưới System, label "Quy trình HĐ".
- FE /system/workflows: 7 card per type, dropdown Standard/SkipCcm +
  'Đã override' badge khi khác default, phase sequence timeline,
  explanation banner ở top. Iteration 2 note: admin-authored custom
  policies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:41:05 +07:00
48e91fe7ca [CLAUDE] Domain+Infra+App+FE-Admin: per-ContractType nested sidebar menu
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
User request: mỗi loại HĐ có menu riêng với 3 action Danh sách /
Thao tác / Duyệt.

Sidebar giờ 3-level under "Hợp đồng":
  Hợp đồng (group, expandable)
  ├── HĐ Thầu phụ (sub-group)
  │   ├── Danh sách → /contracts?type=1
  │   ├── Thao tác  → /contracts/new?type=1
  │   └── Duyệt      → /contracts?type=1&pendingMe=1
  ├── HĐ Giao khoán (sub-group)
  ├── HĐ NCC / Dịch vụ / Mua bán / Nguyên tắc NCC / Nguyên tắc DV
  └── ... (7 types × 4 = 28 new menu items)

BE:
- MenuKeys.cs: ContractTypeCodes array + helpers ContractTypeGroup/
  List/Create/Pending → key format Ct_<TypeCode>[_<Action>]
- DbInitializer.SeedMenuTreeAsync: loop seeds 28 entries under Contracts
- GetMyMenuTreeQuery.BuildChildren: descendants of `Contracts` inherit
  parent permission (avoid adding 28 rows to Permissions table per role)

FE:
- Layout.tsx recursive: MenuNodeRenderer dispatches group vs leaf by
  depth; nested groups collapsed by default (top-level expanded).
  Deeper levels get smaller padding/text + left border guide.
- Pattern-based resolvePath: Ct_<Type>_<Action> → URL with query.
- Contract type code → int map (matches Domain ContractType enum).
- ContractsListPage reads ?type + ?pendingMe, filters client-side.
  Header title + description reflect active filter. "← Tất cả loại"
  quick-reset button.
- ContractCreatePage new cho admin (copy từ fe-user), pre-select type
  từ ?type URL param.
- App.tsx route /contracts/new → ContractCreatePage.

Pure navigation UX; no new permissions needed. Admin + any role with
Contracts.Read see full menu; leaves click-through to filtered views.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:25:00 +07:00
6197c841bb [CLAUDE] App+Infra+FE-Admin: seed master data + MyDashboard widgets
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
Task 1 — Seed master data unblock UAT/demo:
- DbInitializer.SeedDepartmentsAsync: 9 departments từ QT-TP-NCC.docx
  (PM/QS/CCM/PRO/FIN/ACT/EQU/HRA/BOD) — reference data không phải demo.
- DbInitializer.SeedDemoMasterDataAsync: 5 demo suppliers (NCC VLXD, NTP
  Xây dựng, TĐ Hoàng Nam, DV Clean, CĐT Vingroup — covers cả 5
  SupplierType) + 3 demo projects (FLOCK01, SkyGarden, Industrial).
  Chỉ seed nếu tables empty — respect admin's real data khi họ add.

Task 2 — Roles CRUD đã có sẵn trong UsersPage (Shield icon button mở
dialog gán 12 roles từ AppRoles.cs). Skip.

Task 3 — MyDashboard role-specific widgets:
- GetMyDashboardQuery (Reports): returns DraftsInProgress (tôi là
  Drafter + phase soạn thảo), PendingMyApproval (phase eligible role
  tôi + không phải tôi drafter), DueSoon 24h, Overdue, DraftsTotalValue.
- Endpoint GET /api/reports/my-dashboard.
- FE MyDashboardRow ở đầu DashboardPage: 4 card hover → navigate.
  Admin ẩn row nếu tất cả = 0 (ERP noise reduction).
  'Đang soạn thảo' + 'Chờ tôi duyệt' clickable → /contracts?filter=...
  (filter param để wire lần sau; row hiện chưa implement).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:06:28 +07:00
cae4d84830 [CLAUDE] Domain+Infra+App+FE: dynamic workflow policy per ContractType
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m42s
Đọc QT-TP-NCC.docx: quy trình 9 bước chỉ áp dụng cho Thầu phụ/NCC/Tổ đội.
Dịch vụ/Mua bán/Nguyên tắc bypass CCM. Thay hardcoded dict bằng policy
registry.

Domain — WorkflowPolicy.cs:
- Record WorkflowPolicy { Name, Description, Transitions, PhaseSla,
  ActivePhases } — pure data, testable.
- WorkflowPolicies.Standard: 9-phase full (Thầu phụ/Giao khoán/NCC)
- WorkflowPolicies.SkipCcm: 7-phase (Dịch vụ/Mua bán/Nguyên tắc)
- WorkflowPolicyRegistry.For(type) map ContractType → policy
- WorkflowPolicyRegistry.ForContract(c) override nếu BypassProcurement
  AndCCM=true (instance-level escape hatch)

Infrastructure — ContractWorkflowService:
- Xóa hardcoded Transitions/PhaseSla dicts → load từ policy.ForContract
- TransitionAsync: validate qua policy.Transitions thay vì dict local
- Error message include policy.Name để debug dễ hơn
- GetPhaseSla trả SLA từ Standard policy (fallback — SLA hiện tại giống
  nhau giữa 2 policy)

Application — ContractDetailDto:
- Field mới `Workflow: WorkflowSummaryDto { PolicyName, Description,
  ActivePhases, NextPhases }` — FE dùng để render nút chuyển phase
  dynamic + timeline card.
- BuildWorkflowSummary helper trong ContractFeatures.

FE (both apps):
- Type WorkflowSummary + ContractDetail.workflow
- ContractDetailPage xóa hardcoded NEXT_PHASES — dùng
  c.workflow.nextPhases từ BE (single source of truth)
- WorkflowSummaryCard: timeline của ActivePhases với check/current/
  future states + policy name/description ở header
- Card hiển thị trong sidebar, phía trên "Lịch sử duyệt"

Docs:
- gotchas.md #21 marked RESOLVED (NEXT_PHASES sync không còn cần)

Foundation: sau này admin có thể edit policy qua UI khi chuyển sang DB-
backed policy — nhưng API contract (WorkflowSummaryDto) đã stable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:46:31 +07:00
e45909712b [CLAUDE] App+Infra+FE-Admin: DynamicForm + .doc/.xls auto-convert on upload
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m17s
Tier 3 iter2 — form builder UI dùng FieldSpec thay raw JSON textarea.

FE:
- DynamicForm component — parse FieldSpec JSON (record of FieldDef với
  label/type/required/placeholder/hint/options) và render inputs
  dynamic theo type: text/textarea/number/date/currency/select.
- FormsPage render dialog thêm toggle Form ↔ JSON (segmented control).
  Mặc định Form mode khi template có FieldSpec, JSON mode khi không.
  Khi mở dialog cho row khác, reset formValues + chọn đúng default mode.
- parseFieldSpec helper trả { spec, error } — UI báo lỗi nếu JSON
  không parse được, fallback JSON textarea.

BE — generalize converter thành IDocumentConverter:
- IPdfConverter → IDocumentConverter (ConvertAsync(bytes, src, tgt, ct))
  — đủ gánh cả pdf, docx, xlsx targets.
- LibreOfficeDocumentConverter — 1 shell-out pattern cho mọi conversion
  (docx→pdf, doc→docx, xls→xlsx, xlsx→pdf), target arg truyền vào
  --convert-to.
- ExportTemplatePdfCommand update dùng "pdf" target.

Auto-convert .doc/.xls trên upload:
- Validator accept thêm .doc/.xls (thêm note "sẽ tự convert").
- UploadContractTemplateCommandHandler: nếu ext là doc/xls → read stream
  → converter.ConvertAsync → lưu file .docx/.xlsx thay vì format gốc.
  File rendering pipeline (DocxRenderer/XlsxRenderer) chỉ support docx/
  xlsx — convert đảm bảo consistent.
- Display FileName preserve original name nhưng đổi extension.

Unblock 3 file .doc legacy template — admin giờ upload .doc bình thường,
system tự convert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:35:05 +07:00
6bbd894d96 [CLAUDE] App+Infra+Api+FE-Admin: PDF export (LibreOffice headless)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m33s
Pipeline: template.docx → FormRenderer fill placeholders → LibreOffice
soffice --headless --convert-to pdf → PDF byte[] → File() stream to
browser.

Clean-arch split:
- Application: IPdfConverter abstraction (swap to QuestPDF/Aspose later
  without touching caller).
- Infrastructure: LibreOfficePdfConverter — shells out to soffice.exe
  path from config (Pdf:SofficePath, default
  `C:\Program Files\LibreOffice\program\soffice.exe` on Windows).
  Per-request temp workDir để tránh filename collision + -env:
  UserInstallation isolate mỗi conversion (chống "soffice already
  running" khi concurrent). Timeout 60s (configurable). Best-effort
  cleanup. Kill entire process tree nếu timeout.
- Application: ExportTemplatePdfCommand — reuses existing FormRenderer
  + pipes bytes through IPdfConverter. Same data dict signature as
  Render để UI code share.
- Api: POST /api/forms/templates/{id}/export-pdf (same JSON body as
  /render, returns PDF stream).

FE:
- useExport hook chung cho 2 endpoints (DRY render + export-pdf mutations)
- Render dialog thêm nút "Tải PDF" (outline variant) cạnh "Tải file gốc".
  Disabled khi mutation khác đang chạy.
- Hướng dẫn dialog nâng cấp: "file gốc để edit Word/Excel, PDF để
  in/gửi không chỉnh sửa được".

Ops: scripts/install-libreoffice.ps1 — silent MSI install 25.8.6 cho
VPS (đã chạy trên prod).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:28:31 +07:00