Commit Graph

13 Commits

Author SHA1 Message Date
ff21120c8c [CLAUDE] Workflow: State machine 5 trạng thái — Trả lại = Phase riêng
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m17s
Session 17 spec: chốt 5 trạng thái phiếu PE/HĐ/Budget theo state diagram:
  Nháp ─trình──► Đã gửi duyệt ─approve cấp cuối──► Đã duyệt (terminal)
                              ├─ Trả lại ────────► Trả lại
                              └─ Từ chối ────────► Từ chối (terminal)
  Trả lại ──Drafter sửa+gửi lại──► Đã gửi duyệt (chạy LẠI từ đầu)

Khác Mig 21 (Session 16): bỏ smart-reject jump-back. Trả lại = Phase
RIÊNG (TraLai=98), không revert về DangSoanThao + không jump-back step.
Drafter từ TraLai gửi lại như case Nháp — workflow chạy lại từ Cấp 1
Bước 1 (Option A diagram chốt với user).

BE Domain:
- ContractPhase + TraLai = 98
- BudgetPhase + TraLai = 98
- PurchaseEvaluationPhase: TraLai=98 đổi từ [LEGACY deprecated] thành
  primary state. Comment update enum docs cho cả 3.

BE Policy (PE/HĐ/Budget):
- Reject transitions trỏ về TraLai (thay DangSoanThao)
- Mirror entry transitions: TraLai → next phase (cho Drafter resubmit)
- ActivePhases thêm TraLai
- FromDefinition mirror: TraLai → step.Phase + reject → TraLai
- DefaultSla cho TraLai = same as DangSoanThao

BE Service (PE + Contract):
- Reject branch: target=TuChoi giữ; else set Phase=TraLai, clear
  CurrentWorkflowStepIndex=null
- Bỏ ResumeAfterReject branch + RejectedAtStepIndex/RejectedFromPhase
  assignment (DB column giữ deprecated cho data cũ)
- Drafter trình branch: từ DangSoanThao HOẶC TraLai → ChoDuyet, init
  CurrentWorkflowStepIndex=0 (cùng entry point, chạy lại từ đầu)
- Notification: TraLai when fromPhase=ChoDuyet → "bị trả lại"
- Budget Handler: simplify reject → TraLai, bỏ smart-reject + isResuming

BE Tests update:
- WorkflowPolicyTests: Standard_RejectFromCCM → TraLai (rename + assert)
  + Standard_TraLai_To_DangGopY_Allowed_For_Drafter (new)
- PurchaseEvaluationPolicyTests: BothPolicies_RejectFromCCM → TraLai
  + BothPolicies_TraLai_To_ChoPurchasing_AllowedForDrafter (new theory)
- BudgetPolicyTests: Default_CostControl_ChoCCM_To_TraLai (rename)
  + ActivePhases All6States (was All5) + NextPhasesFrom_TraLai (new)
  + NextPhasesFrom_ChoCEO_Includes_DaDuyet_And_TraLai (rename)
- 77 → 81 test pass (+4 tests TraLai entry point)

FE rename "Bản nháp" → "Nháp" (cả 2 app + types):
- types/purchaseEvaluation.ts: PurchaseEvaluationPhaseLabel 1=Nháp,
  10=Đã gửi duyệt. PeDisplayStatus.BanNhap → Nhap (key + value).
  PhaseLabel/Color cho TraLai update active.
- types/contracts.ts: +ChoDuyet=10, +TraLai=98 const + label/color.
  Phase 2 'Đang soạn thảo' → 'Nháp'.
- types/budget.ts: +TraLai=98 const + label/color. Phase 1 → 'Nháp'.
- PeListPanel + PurchaseEvaluationsListPage filter dropdown: Nhap +
  TraLai map đúng phase value.

BE label maps update consistent:
- ContractExcelExporter PhaseLabel: DangSoanThao → "Nháp" + add ChoDuyet/
  TraLai entries.
- PeWorkflowAdminFeatures + WorkflowAdminFeatures PhaseLabels: same.

Verify: dotnet test 81 pass · npm build × 2 app pass · BE 0 error.

Field RejectedAtStepIndex/RejectedFromPhase giữ DB column (nullable,
không set value mới). Cleanup migration sau.
2026-05-08 14:12:38 +07:00
130903fe1b [CLAUDE] FE-Admin+FE-User: PE Danh sách bỏ button "+ Tạo phiếu mới" header
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m8s
User chỉ thị: bỏ hết button tạo phiếu mới góc phải màn hình.

PurchaseEvaluationsListPage 3-panel view (Danh sách + Duyệt) giờ
header chỉ còn icon + title + count badge. Việc tạo phiếu mới đi
qua menu sidebar "Thao tác" → workspace 2-panel (sticky "+ Thêm
mới" Panel 1) — single entry point, consistent UX.

Remove khỏi 2 file y hệt (rule §3.9 mirror):
- Block <Button> + <Plus> icon ở header
- const createHref unused
- Import Button + Plus unused (rule §UAT exception rename/remove)

Verify:
- npm run build fe-admin pass (✓ built)
- npm run build fe-user pass (✓ built)
- 0 TS error

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:43:03 +07:00
e320027074 [CLAUDE] FE-Admin+FE-User: PE InfoTab auto re-edit on pencil click + active state visual feedback
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m7s
User feedback 2026-05-07: bấm pencil cho phiếu khác KHÔNG sáng + KHÔNG vào edit
mode (do useState init mount-time only, ev.id thay đổi không re-trigger).
Cũng cần visual feedback "sáng lên" để user biết đang edit phiếu nào.

Implementation:
  ~ PeDetailTabs.tsx (× 2 app)
    + import useEffect
    ~ InfoTab: thêm useEffect watch [autoEdit, canEdit, ev.id, ev.tenGoiThau,
      ev.diaDiem, ev.moTa, ev.paymentTerms]. Khi autoEdit && canEdit → setEditing(true)
      + sync values từ ev mới (tránh stale state khi switch giữa 2 phiếu khác id).
    Note: Dự án disabled đã có sẵn (line 458 `<Input value={ev.projectName}
    disabled className="bg-slate-100" />`) — verify hỏi user, KHÔNG thay đổi.
  ~ PeListPanel.tsx (× 2 app)
    + Prop `editingRowId?: string | null` — row đang edit (URL editHeader=1)
    ~ Pencil icon: thêm `isEditingThis = editable && editingRowId === p.id` state
      → bg-brand-100 + text-brand-700 + ring-brand-300 + shadow-sm khi active
      → tooltip đổi "✎ Đang sửa phiếu này — click để toggle / xem khác"
  ~ PurchaseEvaluationWorkspacePage.tsx (× 2 app)
    + Pass `editingRowId={autoEditHeader ? selectedId : null}` xuống PeListPanel

Verify: npm run build fe-admin + fe-user pass · 0 TS error · áp rule strict
verify khi add new prop chain + useEffect.

UAT mode: skip dotnet test (FE-only), push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:41:33 +07:00
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
a1665ee9d0 [CLAUDE] FE-User: PE Danh sách disable interactions mirror fe-admin
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m59s
Mirror commit `7dfeb1a` cho fe-user (rule §3.9 duplicate có chủ đích).
PurchaseEvaluationsListPage readOnly=true cho PeDetailTabs + readOnly={!pendingMe}
cho PeWorkflowPanel. PeWorkflowPanel thêm prop readOnly hide Chuyển tiếp.

UAT mode: skip verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:12:52 +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
27b291ccea [CLAUDE] FE-User: PE InfoTab inline edit + PeListPanel pencil edit hover mirror
Chunk 2/3 — mirror y hệt Chunk 1 sang fe-user (rule §3.9). 3 file:
  ~ components/pe/PeDetailTabs.tsx — InfoTab inline edit + autoEditHeader prop
  ~ components/pe/PeListPanel.tsx — pencil icon group-hover absolute right
  ~ pages/pe/PurchaseEvaluationWorkspacePage.tsx — URL editHeader=1 wiring

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:57:24 +07:00
14f8d9d808 [CLAUDE] FE-User: PE + HĐ toggle "Nhập tay" + 2 fields manual budget mirror fe-admin
Chunk 4/5 — mirror y hệt Chunk 3 sang fe-user (rule §3.9 duplicate có chủ đích).

Files:
  ~ fe-user/src/types/purchaseEvaluation.ts — PeDetailBundle +2 field
  ~ fe-user/src/types/contracts.ts — ContractDetail +2 field
  ~ fe-user/src/components/pe/PeHeaderForm.tsx (copy từ fe-admin)
  ~ fe-user/src/components/pe/PeDetailTabs.tsx — Section "b. Ngân sách"
    fallback display khi !ev.budget + có manual data
  ~ fe-user/src/pages/pe/PurchaseEvaluationCreatePage.tsx (copy refactor wrap)
  ~ fe-user/src/pages/contracts/ContractCreatePage.tsx — toggle pattern cho
    NewContractForm + EditContractForm (giống fe-admin)

Verify: npm run build fe-user pass · 1904 modules · 0 TS error.

Next: Chunk 5 docs + push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:40:29 +07:00
ecf3c5945b [CLAUDE] FE-User: PE Thao tác 2-panel workspace mirror fe-admin
Chunk 2/3 — mirror y hệt Chunk 1 sang fe-user (rule §3.9 duplicate có chủ đích
giữa 2 app — copy + sync tay khi breaking).

Files (cùng diff Chunk 1, content identical):
  + fe-user/src/components/pe/PeListPanel.tsx
  + fe-user/src/components/pe/PeHeaderForm.tsx
  + fe-user/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx
  ~ fe-user/src/components/pe/PeDetailTabs.tsx — add mode prop + Section 5 hint
  ~ fe-user/src/components/Layout.tsx — resolver Pe_*_Create map workspace
  ~ fe-user/src/App.tsx — route /purchase-evaluations/workspace

Verify: npm run build (fe-user) pass. dotnet test 83 không bị ảnh hưởng (đã
verify Chunk 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:37:52 +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
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
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