Commit Graph

195 Commits

Author SHA1 Message Date
917446dbeb [CLAUDE] PE-Lịch sử: chỉ hiện events Trả lại + Gửi duyệt lại
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m11s
User UAT 2026-05-08: bỏ "trạng thái duyệt" (Cấp 1 → 2 → DaDuyet) +
bỏ thay đổi trước Trả lại lần đầu. Chỉ giữ:
- Workflow transition về TraLai (Reject)
- Workflow transition từ TraLai → ChoDuyet (Drafter gửi lại)
- Mọi sửa nội dung khi phaseAtChange = TraLai (giai đoạn chờ gửi lại)

Filter ở FE (PeDetailTabs HistoryTab). BE giữ audit data đầy đủ —
chỉ thay logic display, reversible. Mirror fe-admin + fe-user.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:22:13 +07:00
aaa1c6cba6 [CLAUDE] PE-Duyệt: ẩn dropdown trạng thái + filter cứng "Đã gửi duyệt"
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m12s
Leaf "Duyệt" (pendingMe=1) chỉ load phiếu trạng thái "Đã gửi duyệt".
Nháp / Trả lại / Đã duyệt / Từ chối lọc bỏ client-side.

- Replace <Select> trạng thái bằng hint amber "Lọc cố định: Đã gửi duyệt"
- allRows.filter qua getPeDisplayStatus === DaGuiDuyet
- Header count dùng rows.length khi pendingMe (inbox không paged)
- Mirror fe-admin + fe-user

Workaround BE /inbox loose UAT (trả phiếu Nháp). Phân quyền strict V2
pending Session 18+.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:07:23 +07:00
de0f38dd25 [CLAUDE] PE Panel 3: bỏ phase cards + render flow workflow V2 thực tế
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m14s
User feedback: "bỏ luôn cái quy trình phía trên đi nhé, vì nó là trạng
thái rồi (đã có badge), update cái flow quy trình mới vào bên panel 3
đang đến ai".

BE — ApprovalFlow DTO mới (full snapshot Bước → Cấp → NV với Status):
- PurchaseEvaluationApprovalFlowDto { CurrentStepIndex, CurrentLevelOrder,
  Steps[] }
- PurchaseEvaluationApprovalFlowStepDto { Order, Name, DepartmentId/Name,
  Status, Levels[] }
- PurchaseEvaluationApprovalFlowLevelDto { Order, Name, Approvers[], Status }
- Status: "Done" | "Current" | "Pending"

Handler GetById compute Status logic:
  - Phase=DaDuyet  → tất cả Steps/Levels "Done"
  - Phase=Nháp/Trả lại/Từ chối → tất cả "Pending"
  - Phase=ChoDuyet:
    * Step.Index < currentIdx          → all Levels "Done"
    * Step.Index == currentIdx:
        Level.Order < currentLevelOrder → "Done"
        Level.Order == currentLevelOrder → "Current"
        Level.Order > currentLevelOrder → "Pending"
    * Step.Index > currentIdx           → all "Pending"
- Load Approvers info (FullName + Email) qua UserManager batch query

FE (cả 2 app mirror):
- types/purchaseEvaluation.ts: +PeApprovalFlow + Step + Level + Status union
  PeDetail.approvalFlow optional
- PeWorkflowPanel:
  * BỎ phase cards section (4 ô Nháp/TraLai/ChoDuyet/DaDuyet) — đã
    duplicate với status badge ở header
  * Header mới: "Quy trình duyệt" + Code + Version + Name workflow pin
  * Render Flow vertical: Bước (icon ✓/●/○) → border + bg theo status
    + dept badge → list Cấp (icon nhỏ) với label "đang chờ" / "đã
    duyệt" + tên NV duyệt
  * Phiếu V1 legacy (no flow): show note "dùng quy trình cũ — không
    khả dụng chi tiết"
  * Bỏ helper isPastPhase() (orphan sau khi xóa cards)

Verify: BE build 0 error · 2 FE builds OK.

Test eoffice:
1. Mở phiếu V2 đang ChoDuyet → thấy flow Bước 1 (Phòng A):
   ✓ Cấp 1 NV X (đã duyệt)
   ● Cấp 2 NV Y (đang chờ)  ← highlight
   ○ Cấp 3 NV Z (chưa)
2. Phase=DaDuyet → all Steps/Levels green ✓
3. Phase=Nháp/TraLai → all greyed ○
4. V1 legacy → fallback note
2026-05-08 16:16:40 +07:00
74745a77a7 [CLAUDE] PE: Dropdown quy trình duyệt chỉ ở Duyệt (Inbox), bỏ ở Danh sách
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m11s
User feedback: "Danh sách sửa lại như cũ nhé, cho hiển thị hết tất cả
các phiếu nhé."

→ Đảo ngược điều kiện hiển thị Select "Tất cả quy trình duyệt":
- TRƯỚC: hiện cả 2 view (Duyệt + Danh sách)
- SAU: CHỈ hiện ở Duyệt (pendingMe=1)

Lý do: Danh sách = view tổng (tất cả phiếu), không cần filter quy
trình. Duyệt = inbox chờ tôi duyệt → cần filter quy trình để focus.
Trạng thái dropdown giữ ở cả 2 view.

Files (mirror cả 2 app):
- fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx
- fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx

Verify: 2 FE builds OK.
2026-05-08 15:55:36 +07:00
d250ae4e71 [CLAUDE] PE Inbox: nhận filter approvalWorkflowId + show dropdown cả 2 view
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m3s
User báo:
- Filter "Tất cả quy trình duyệt" hiện chỉ ở Danh sách → muốn ở cả 2
- Filter chọn quy trình → không thấy phiếu V2 trong Duyệt (Inbox)

BE — wire filter vào Inbox:
- GetMyPurchaseEvaluationInboxQuery +ApprovalWorkflowId? param
- Handler thêm filter `q.Where(x => x.e.ApprovalWorkflowId == awId)`
- PurchaseEvaluationsController.Inbox +approvalWorkflowId query param

FE (cả 2 app mirror):
- PurchaseEvaluationsListPage: bỏ điều kiện `!pendingMe` ở Select dropdown
  → hiển thị filter quy trình duyệt CẢ Duyệt + Danh sách
- Inbox API call: pass approvalWorkflowId từ URL param

Verify: BE 0 error · 2 FE builds OK.

Test luồng eoffice:
1. Vào "Duyệt NCC > Duyệt" → 2 dropdown filter hiện đầy đủ
2. Chọn 1 quy trình V2 từ dropdown → list filter chỉ phiếu pin quy trình đó
3. Vào "Duyệt NCC > Danh sách" → 2 dropdown vẫn show, filter cũng work
2026-05-08 15:37:03 +07:00
9e63e2da10 [CLAUDE] PE: V2-aware Inbox/List + 2 dropdown filter quy trình + trạng thái
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m12s
User báo: "Phiếu chưa thấy lên trong danh sách duyệt — chắc do chưa ăn
vào flow. Tách thành 2 cái dropdown là list quy trình duyệt và list
trạng thái. Debug trước, phân quyền rút gọn lại sau."

BE — V2-aware permission + filter (Application/PurchaseEvaluations/
PurchaseEvaluationFeatures.cs):
- ListPurchaseEvaluationsQuery +ApprovalWorkflowId? Guid? param +
  IDOR loose: phiếu pin V2 → mọi authenticated user thấy được (UAT)
- GetMyPurchaseEvaluationInbox V2-aware: ResolveV2InboxIdsAsync helper
  precompute Set<Guid> phiếu Phase=ChoDuyet pin V2 + actor ∈ Cấp hiện
  tại approvers (CurrentWorkflowStepIndex + CurrentApprovalLevelOrder
  match Step.Order + Level.Order). Inbox where = eligiblePhases.Contains
  || v2InboxIds.Contains. eligiblePhases admin +ChoDuyet.
- GetById Detail loose: V2 pin → cho non-Drafter xem (skip
  eligiblePhases check).

API Controller:
- PurchaseEvaluationsController.List +approvalWorkflowId query param

FE — 2 dropdown filter (cả 2 app mirror):
- PurchaseEvaluationsListPage: +URL param `awId` filter quy trình
- useQuery `approval-workflows-v2-filter` load list V2 active+history
  theo applicableType=typeFilter (chỉ enabled khi có type)
- Render Select riêng "Tất cả quy trình duyệt" (chỉ show !pendingMe vì
  Inbox dùng API endpoint khác) + Select "Tất cả trạng thái" giữ
- Display option: "QT-DN-V2-001 v01 — Tên quy trình"

Verify: BE build 0 error · 2 FE builds OK.

Test luồng eoffice:
1. Drafter trình phiếu V2 → Phase=ChoDuyet
2. Login NV X (approver Cấp 1) vào "Duyệt NCC > Duyệt"
   (?pendingMe=1) → phiếu hiện trong list
3. Login NV Y (không phải approver) → list rỗng (đúng spec)
4. Vào "Duyệt NCC > Danh sách" (không pendingMe) → 2 dropdown:
   - Quy trình duyệt: filter theo workflow specific
   - Trạng thái: filter theo Phase
2026-05-08 15:18:22 +07:00
d814429cee [CLAUDE] PE Workflow V2: disable nút Duyệt nếu actor không trong cấp hiện tại
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m14s
User feedback: "Nếu không đúng bước duyệt thì nút duyệt cho Disable luôn cũng đc."

BE — DTO + Handler populate "Bước/Cấp đang chờ duyệt":
- Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs:
  +PurchaseEvaluationApprovalLevelApproverDto { UserId, FullName, Email }
  +PurchaseEvaluationCurrentApprovalDto { StepIndex, StepName,
    StepDepartmentId/Name, LevelOrder, LevelName, Approvers[] }
  PurchaseEvaluationDetailBundleDto +CurrentApproval? optional field
- Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs handler
  GetById: khi pin V2 + Phase=ChoDuyet → load AW.Steps.Levels Include
  3-level + group by Order = Cấp + resolve user names → populate
  CurrentApproval. Null khi V1 legacy hoặc không phải ChoDuyet.

FE — types + PeWorkflowPanel (cả 2 app mirror):
- types/purchaseEvaluation.ts: +PeCurrentApproval + PeCurrentApprovalLevelApprover
  + PeDetail.currentApproval optional
- PeWorkflowPanel:
  * Banner V2 hiển thị "Đang chờ Bước N (TênBước · Phòng X) — Cấp K"
    + list NV được duyệt + status emerald (đến lượt) / amber (không phải lượt)
  * useAuth() để check currentUser.id ∈ approvers + Admin bypass
  * Button "Duyệt forward" disabled khi V2 pin + actor không khớp.
    Title tooltip "Cấp K chỉ {NV X / NV Y} mới duyệt được."
  * Button "Trả lại" + "Từ chối" vẫn enabled (BE không gating 2 hành
    động này theo Cấp — Approver có thể reject bất cứ lúc nào).
  * Send-back logic update: target = DangSoanThao OR TraLai (V2 dùng TraLai)
- Admin role bypass mọi check.

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

Test thử:
1. NV X (approver Cấp 1 V2) login → banner emerald "Đến lượt bạn duyệt"
   + nút "✓ Duyệt → ChoDuyet" enabled
2. NV Y (không phải approver) login → banner amber "Không phải lượt
   bạn — chỉ NV X mới duyệt được" + nút Duyệt grey disabled, hover tooltip
3. Admin login → bypass, button enabled
2026-05-08 15:03:29 +07:00
0a40c65421 [CLAUDE] PurchaseEvaluation: User chọn quy trình duyệt V2 lúc tạo phiếu (Mig 23)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m11s
User feedback: thay field "Loại quy trình (theo menu — khóa)" disabled
→ Select dropdown cho User pick quy trình ApprovalWorkflowsV2 (Mig 22)
ngay từ workspace tạo mới. Hiển thị "Mã + Tên + Version".

BE Domain:
- PurchaseEvaluation +ApprovalWorkflowId Guid? (nullable, FK Restrict)
- EF Configuration: Index + FK Restrict to ApprovalWorkflows
- Migration 23 `AddApprovalWorkflowIdToPurchaseEvaluation` (1 ALTER +
  1 IX + 1 FK), applied cả _Design + _Dev LocalDB
- Field WorkflowDefinitionId (Mig 21 legacy) giữ song song để Service
  PE chạy logic cũ tới khi Session sau wire qua schema mới

BE Application:
- CreatePurchaseEvaluationCommand +ApprovalWorkflowId? Guid? optional
  param (default null)
- Validate: nếu set, phải tồn tại + ApplicableType khớp PE.Type
  (DuyetNcc=1 → ApprovalWorkflowApplicableType.DuyetNcc, etc)
- Handler set entity.ApprovalWorkflowId từ request
- UpdatePurchaseEvaluationDraftCommand mirror — cho User đổi quy trình
  khi sửa Nháp/Trả lại (validate same)
- PurchaseEvaluationDetailBundleDto +ApprovalWorkflowId/Code/Name/Version
- GetPurchaseEvaluationByIdQuery handler load workflow info join
- Update Phase guard: cho sửa cả DangSoanThao + TraLai (Trả lại =
  editable per Session 17 spec)

FE (cả 2 app mirror):
- types/purchaseEvaluation.ts: PeDetail +approvalWorkflowId/Code/Name/Version
- PeWorkspaceCreateView.tsx:
  - Replace field disabled "Loại quy trình" → Select bắt buộc
  - useQuery `/api/approval-workflows-v2?applicableType=N` filter theo
    defaultType (1=DuyetNcc / 2=DuyetNccPhuongAn)
  - Display option: "QT-DN-V2-001 v01 — Quy trình Duyệt NCC (đang áp dụng)"
  - List cả version active + archived (UAT cần test compare)
  - Empty state hint amber "Chưa có quy trình, vào /system/approval-workflows-v2"
  - canSubmit require approvalWorkflowId set
  - POST payload include approvalWorkflowId

Verify: dotnet build OK · 81 test pass · npm build × 2 OK · Mig 23 applied
cả 2 LocalDB.

Logic Service PE chưa wire qua ApprovalWorkflowId — vẫn pin
WorkflowDefinitionId Mig 21 legacy chạy. Session sau wire Service iterate
ApprovalWorkflowSteps + match approver theo schema V2 + drop legacy.
2026-05-08 14:34:54 +07:00
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
f3bea3c616 [CLAUDE] Workflow: Max 3 cấp/bước + N NV/cấp + sequential gating (V2 UAT iter 2)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m16s
User feedback: "tối đa 3 cấp (không có cấp 4)" — không phải bắt buộc 3.
Mỗi cấp = N NV (add bao nhiêu cũng được). Quy trình chạy theo số cấp
thật sự cấu hình (1/2/3). C2 chưa thao tác được khi C1 chưa có NV.

Convention DB: nhiều `ApprovalWorkflowLevel` row cùng Order = same Cấp,
mỗi row = 1 NV. Service iterate group by Order; trong cùng cấp =
OR-of-N (1 NV duyệt → cấp pass).

BE — Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs:
- Validator strict:
  - Order ∈ {1, 2, 3} (`MaxLevelsPerStep`)
  - Sequential gating: HaveSequentialOrders → 1 / 1+2 / 1+2+3, KHÔNG
    cho 2 (thiếu 1) hoặc 1+3 (thiếu 2)
  - HaveNoDuplicateApproverInSameLevel: 1 NV không thêm 2 lần cùng cấp
- Schema KHÔNG đổi (giữ ApprovalWorkflowLevel.ApproverUserId 1-1).
- Handler không đổi — auto handle multiple rows cùng Order.

FE — ApprovalWorkflowsV2Page.tsx rewrite Levels section:
- Type EditStep.levels → levelEntries: { order: 1|2|3; approverUserId }[]
  flat list (group by order trong render).
- 3 SECTION CỐ ĐỊNH C1/C2/C3 trong Designer:
  - Mỗi section: header "Cấp N" + count NV + nút "+ Thêm NV"
  - List rows mỗi NV với Select dropdown filtered theo Phòng + Trash
  - C2 disabled (opacity-60) khi C1 empty. C3 disabled khi C2 empty.
  - Tooltip "+ Thêm NV": "Cấp k-1 phải có ≥1 NV trước"
- Add NV: dropdown chỉ NV thuộc Phòng + chưa được thêm cùng cấp
  (no duplicate same level).
- Xóa NV: chặn xóa NV cuối Cấp k nếu Cấp k+1 còn entries (toast error
  "Hãy xóa hết NV ở Cấp k+1 trước khi rỗng Cấp k").
- Đổi Phòng → clear toàn bộ levelEntries (NV cũ không thuộc Phòng mới).
- DefinitionCard read-only: group s.levels by Order → render mỗi cấp
  là 1 row với badge "Cấp N" + list NV bên dưới.
- Save validate: Phòng required + Cấp 1 ≥1 NV + sequential + NV thuộc
  đúng Phòng (defensive double-check).

Verify: dotnet build BE OK · 77 test pass · npm build fe-admin OK.

Logic Service PE/Contract chưa wire schema mới — vẫn pin Mig 21 legacy.
2026-05-08 13:20:51 +07:00
9712778929 [CLAUDE] FE-Admin: Lock 3 cấp/bước + filter NV theo Phòng (V2 UAT iter 1)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m14s
User feedback Session 17 sau khi UAT Designer V2 lần đầu:
- "Chốt cứng 1 phòng 3 cấp đi nhé (Logic vẫn giữ như thế nhưng giới
  hạn lại, không thay đổi Logic)"
- "Liên kết đúng Phòng A → Thì Select nhân viên phòng A thôi"
- "User có thể cùng cấp với nhau" (không bắt unique level name)

Files: fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx
- FIXED_LEVELS_PER_STEP = 3 const + makeEmptyLevels()/makeEmptyStep()
  helpers. Initial state mỗi Step có sẵn 3 levels (C1/C2/C3).
- copyFromDefinition pad/truncate về đúng 3 cấp (defensive cho data
  legacy >3 hoặc <3).
- Bỏ button "+ Thêm cấp" + nút Trash xóa cấp + chevron move cấp.
  Vẫn giữ Add/Remove + reorder Step (Bước).
- Filter Select NV theo s.departmentId (usersForDept helper):
    deptId=null → fallback all (chưa chọn phòng)
    deptId set → chỉ NV.DepartmentId === deptId
- Đổi Phòng → reset 3 approver về '' (NV cũ có thể không thuộc Phòng
  mới). User select lại 3 NV.
- Phòng required (* + required attr Select) — empty Phòng disable
  Select NV với placeholder "Chọn Phòng trước".
- Empty filtered users → hint amber "Phòng chưa có NV, vào /system/users".
- Save validate: phải có Phòng + đúng 3 cấp + tất cả approverUserId
  thuộc đúng deptId (defensive double-check).
- ApproverUser type +departmentId (đã có sẵn ở UserDto BE+FE types).
- pageSize 200→500 đảm bảo load đủ NV.

Logic BE KHÔNG đổi: Service iterate Levels OrderBy Order. UI giới hạn
3 cấp chỉ là quy ước, BE vẫn handle N cấp nếu DB có.

Verify: npm build fe-admin OK, 1924 modules, 0 TS error.
2026-05-08 13:04:13 +07:00
2781c7ea09 [CLAUDE] FE-Admin: Designer Quy trình duyệt mới V2 (Chunk C)
Page mới `/system/approval-workflows-v2/:typeCode` mirror Designer cũ
nhưng theo schema Mig 22:
  Bước (Phòng) > N Cấp (mỗi cấp = 1 NV cụ thể qua Select duy nhất)

Files:
- fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx (new — 480 LOC)
  - Overview cards (Active version + History list per ApplicableType)
  - DefinitionCard read-only render Bước → Cấp với approver name + email
  - Designer dialog: Mã/Tên/Mô tả + reorder Step/Level (chevron up/down)
    + Add/Remove Step + Add/Remove Level + Select Phòng + Select NV duyệt
  - Validate: mỗi Step phải có ≥1 Level, mỗi Level phải có approverUserId
  - Auto-assign code QT-DN-V2-001 / QT-DN-PA-V2-001 / QT-HD-V2-001
- fe-admin/src/lib/menuKeys.ts (+2 const sync với BE MenuKeys)
- fe-admin/src/components/Layout.tsx (resolver: ApprovalWorkflowsV2 root +
  AwV2_<TypeCode> leaf → /system/approval-workflows-v2/<code>)
- fe-admin/src/App.tsx (import + 2 route)

Verify: npm build fe-admin OK, 1924 modules transformed, 0 TS error.

Next: Chunk D — STATUS + HANDOFF + CLAUDE.md update + final commit.
2026-05-08 12:45:00 +07:00
88a5be1afd [CLAUDE] FE-Admin: Designer flat UI Phòng × Cấp + types ChoDuyet=10 (Chunk B)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m1s
PeWorkflowsPage + WorkflowsPage rewrite for flat workflow model (Mig 21):
- Drop InnerStepDto + EditInnerStep types
- Drop PHASE_OPTIONS (auto-assign ChoDuyet=10 behind scenes)
- StepDto + EditStep extend với departmentId, positionLevel
- copyFromDefinition simplified
- Designer step UI: Tên + Phòng Select + Cấp Select + SLA + Approvers
  Role/User optional fallback (drop entire InnerSteps sub-section)
- DefinitionCard view: hiển thị badge Phòng (emerald) + Cấp NV/PP/TP
  (violet) + SLA per step
- Save payload: phase=10 (ChoDuyet), departmentId, positionLevel
- Hint amber: "Mig 21 flat workflow: User cùng Phòng + Cấp ≥ step → duyệt
  được (OR-of-many)"

types/purchaseEvaluation.ts (fe-admin + fe-user mirror):
- + ChoDuyet=10 enum value + label "Đang duyệt" + color amber
- Legacy 2-6 + 98 keep cho data cũ display OK
- getPeDisplayStatus: ChoDuyet + legacy intermediate → "Đã gửi duyệt"

Verify: npm build fe-admin + fe-user pass.

Pending Chunk D: Docs + Skill + Memory + session log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:11:39 +07:00
835cc7f17f [CLAUDE] FE-Admin+FE-User: PE diagnose "Lưu & Gửi Duyệt" — tooltip + confirm rõ phase
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m17s
User báo button "Lưu & Gửi Duyệt" KHÔNG hoạt động + suy đoán "trùng ID".
Phân tích: button disabled khi `evaluation.workflow.nextPhases` không có
forward phase (chỉ TuChoi/TraLai). Hiện FE silent — không cách nào biết.

Improvement (cả 2 app, mirror):
- Compute `forwardPhase` once thay vì 2 lần (.find / .some).
- Add `submitDisabledReason` string giải thích reason:
  * canEditPhase=false → "Phiếu đã ở phase X — chỉ Bản nháp / Trả lại
    mới sửa + gửi được"
  * readOnly → "Chế độ chỉ đọc"
  * !forwardPhase → "Workflow không có phase tiếp theo từ X. Liên hệ
    admin kiểm tra cấu hình quy trình"
- Button title attribute show reason (hover tooltip) hoặc forward phase
  label khi enabled: "Gửi phiếu sang 'Chờ Purchasing'"
- Confirm dialog show forward phase explicit: 'Gửi phiếu vào quy trình
  duyệt? Sẽ chuyển sang "Chờ Purchasing". Sau khi gửi sẽ KHÔNG sửa
  được nữa (trừ khi approver Trả lại).'

Note "trùng ID" KHÔNG phải bug FE: PurchaseEvaluationWorkspacePage
URL state đúng (`+ Thêm mới` clear `id`, save set new). Mỗi PE row
unique GUID + MaPhieu. User feedback có thể due to button silent
disabled — tooltip giờ rõ reason.

Verify: npm build fe-admin + fe-user pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:35:08 +07:00
0d776987e4 [CLAUDE] PE workflow 3-button Duyệt/Trả lại/Từ chối (Task 4)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m6s
User chỉ thị thay 2-button hiện tại bằng 3 hành động rõ ràng:
- Duyệt = forward phase tiếp theo
- Trả lại = về DangSoanThao + Drafter sửa → workflow tự jump tới phase
  đã reject (smart reject Mig 16 pattern + clear N-stage rows)
- Từ chối = phiếu khoá hoàn toàn (Phase=TuChoi → 17 handler Mig 16 lock
  edit). Drafter phải tạo phiếu mới.

Domain (PurchaseEvaluationPolicy.cs):
- NccOnly + NccWithPlan: thêm (X → TuChoi) transition cho mọi phase
  trung gian (ChoPurchasing/ChoCCM/ChoCEODuyetNCC/ChoDuAn/ChoCEODuyetPA)
  với roles của phase đó. Trước đây chỉ DangSoanThao → TuChoi (Drafter).
- FromDefinition expand: mỗi step (trừ DangSoanThao) thêm
  (step.Phase → TuChoi) với roles của step.

Service (PurchaseEvaluationWorkflowService.cs):
- Reject branch tách 2 case:
  * target=TuChoi → giữ nguyên (KHÔNG override + KHÔNG set
    RejectedFromPhase + KHÔNG clear N-stage rows). Phiếu khoá vĩnh viễn.
  * target khác (thường DangSoanThao) → smart reject (set
    RejectedFromPhase + force DangSoanThao + clear N-stage rows).

FE (PeWorkflowPanel.tsx, fe-admin + fe-user mirror):
- next.phases render 3 button rõ ràng:
  * "✓ Duyệt → <label>" brand (forward)
  * "← Trả lại (về Drafter sửa)" red (target=DangSoanThao + isSendBack)
  * "✗ Hủy / Từ chối" red (target=TuChoi)
- Decision logic: target=TuChoi || isSendBack → Reject (2), else Approve (1)
- Dialog confirm:
  * Title rõ theo loại hành động
  * Cancel case: warning red "Phiếu sẽ bị khoá hoàn toàn"
  * SendBack case: hint amber "Phiếu sẽ về Đang soạn thảo, Drafter sửa
    rồi trình lại — workflow tự jump tới phase này"

Tests update + add 1 test mới:
- Reject_Sets_RejectedFromPhase_And_Forces_DangSoanThao →
  Reject_To_DangSoanThao_Sets_RejectedFromPhase_TraLai (rename + change
  target từ TuChoi → DangSoanThao để test Trả lại pattern)
- + Reject_To_TuChoi_Locks_Permanently_No_RejectedFromPhase (NEW test
  Từ chối — phase=TuChoi + RejectedFromPhase null)
- NStage_Reject_Clears_InnerStep_Rows_At_Phase: target TuChoi →
  DangSoanThao (test Trả lại + clear N-stage rows pattern)

Verify:
- dotnet build 0 error
- dotnet test 95 → **96 pass** (+1 test mới Từ chối)
- npm build fe-admin + fe-user pass

Pending Task 2: Sample data seed N-stage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:25:44 +07:00
b06bdce694 [CLAUDE] FE-Admin+Docs: Contract workflow N-stage Designer mirror PE + Docs (Chunk F)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m3s
FE Admin WorkflowsPage Designer extend mirror PeWorkflowsPage:
- Type InnerStepDto + extend StepDto +innerSteps
- Type EditInnerStep + extend EditStep +innerSteps
- copyFromDefinition include innerSteps map
- Default new step +innerSteps:[]
- departmentsList useQuery
- Save mutation payload include innerSteps Order asc
- UI sub-section "Cấp duyệt nhỏ trong phòng" drag-list per step card với
  Phòng × Cấp + required checkbox + button "+ Thêm cấp duyệt" emerald
- Empty state hint fallback 2-cấp legacy

KHÔNG đụng fe-user — WorkflowsPage admin-only.

Reuse PositionLevel const + Label maps từ Session 12 types/users.ts.

Docs:
- STATUS.md Last updated + Phase summary (19→20 mig, 89→95 test, 56→57
  bảng) + 1 row Recently Done Session 13 (KEEP narrative cũ)
- HANDOFF.md TL;DR Session 13 prepend + 7 cảnh báo Session 14+
- migration-todos.md Phase 9 + Session 13 block 5 chunk
- Session log NEW `2026-05-07-2400-n-stage-contract-mirror.md` đầy đủ
  rationale + per-chunk + bug log

Defer cron audit 2026-06-01: schema-diagram §17 Mig 20, skill
ef-core-migration row, skill contract-workflow N-stage cross-ref.

🎉 SESSION 13 COMPLETE: Mirror N-stage Contract module (Mig 20). 5
commit per-chunk + skip Chunk E auto-bind. Total 95 test pass.
Backward compat 100% với 2-stage Mig 16 legacy.

Pending Task 4: Wire BE TraLai PE transition + Task 2: Sample data seed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:13:27 +07:00
5e5042d717 [CLAUDE] FE-Admin+Docs: PE workflow N-stage Designer + UsersPage cấp + Docs (Chunk F)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m0s
FE Admin:
- types/users.ts: User +positionLevel field + PositionLevel const +
  PositionLevelLabel/Short maps (NV/PP/TP).
- PeWorkflowsPage.tsx Designer extend: InnerStepDto + EditInnerStep types,
  copyFromDefinition include, departmentsList query, sub-section "Cấp duyệt
  nhỏ trong phòng" per step card với drag-list { Phòng × Cấp + required }
  + button "+ Thêm cấp duyệt" emerald + payload include (Order asc).
  Empty state hint fallback 2-cấp legacy.
- UsersPage.tsx: column "Cấp" badge NV/PP/TP emerald (— nếu null) +
  action button cycle null→1→2→3→null call PATCH /users/{id}/position-level.

KHÔNG đụng fe-user — admin-only feature (PeWorkflowsPage + UsersPage ở
fe-admin only).

Docs:
- STATUS.md Last updated + Phase summary count (17→19 mig, 83→89 test,
  55→56 bảng) + 1 row Recently Done Session 12 (KEEP narrative cũ).
- HANDOFF.md TL;DR Session 12 prepend + 8 cảnh báo Session 13+ + giữ
  Session phase 2 narrative.
- migration-todos.md Phase 9 + Session 12 block 6 chunk + 5 defer task.
- session log NEW `2026-05-07-2300-n-stage-workflow.md` đầy đủ rationale
  + per-chunk + bug log + plan hierarchy.

Defer cron audit 2026-06-01: schema-diagram §15 Mig 18 + §16 Mig 19,
skill ef-core-migration Mig 18+19 row, skill contract-workflow N-stage
cross-ref section.

Verify:
- npm run build fe-admin pass (✓ built, 0 TS error)
- dotnet test 89 pass (no regression)
- dotnet build 0 error

🎉 SESSION 12 COMPLETE: N-stage workflow approval Phòng × PositionLevel
PE-only. Backward compat 100% với 2-stage Mig 16. 6 commit per-chunk
A→F. Total 89 test pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:32:56 +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
d2306b88d1 [CLAUDE] FE-Admin+FE-User: PE QuoteDialog bỏ checkbox isSelected + winner column highlight + loading overlay/spinner
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m1s
User feedback 2026-05-07 (annotation):
1. Bỏ checkbox "Chọn NCC này cho hạng mục" trong QuoteDialog (consolidate winner
   selection chỉ ở Section 2.a NccSelectorRow — tránh 2 nơi pick winner).
2. Khi NCC là winner (selectedSupplierId === s.supplierId) → cell giá Section 4
   matrix ăn theo màu xanh emerald (header + cells trong column).
3. Save có delay → hiện loading spinner / overlay để user biết đang xử lý.

Implementation:
  ~ QuoteDialog (× 2 app):
    - Remove `isSelected` từ form state + UI checkbox
    - Vẫn gửi `isSelected: existing?.isSelected ?? false` lên API (giữ nguyên
      trạng thái cũ — không expose UI để tránh confusion)
    - Disable Xóa/Hủy/Lưu khi `isSaving = mut.isPending || del.isPending`
    - Button text: "Đang lưu báo giá…" / "Đang xóa…" thay "Lưu" / "Xóa"
    - Full overlay loading: absolute z-10 + bg-white/70 backdrop-blur-sm + spinner
      ring brand-600 + status text rõ ràng
  ~ ItemsTab matrix (× 2 app):
    - Column header `<th>`: thêm `isWinner` check → bg-emerald-50 + text-emerald-700
      + prefix "✓ " trước tên NCC khi winner
    - Cell `<td>`: thay `q?.isSelected` highlight → `isWinnerColumn` (entire
      column ăn theo Section 2.a winner). Cells của winner column LUÔN xanh
      bất kể quote đã nhập hay chưa (visual trace winner rõ ràng).
  ~ NccSelectorRow (× 2 app):
    - Wrap Select trong `relative` div
    - Thêm inline spinner + text "Đang chọn NCC + sync cột giá Section 4…"
      khi setWinner.isPending — báo cho user biết delay đang xử lý

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

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:49:48 +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
378c9939e6 [CLAUDE] FE-Admin+FE-User: PE detail polish B12 — Lưu (no close), Xóa phiếu, header bar simplify, NCC name col, no-delete có quotes
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m12s
User feedback 2026-05-07 (annotation screenshot):
1. "Lưu" thay "Lưu (đóng)" — KHÔNG đóng workspace, chỉ toast + invalidate sync
2. Thêm nút "Xóa phiếu" bottom — CHỈ Bản nháp (DangSoanThao), KHÔNG xóa Trả lại
   (đã có lịch sử workflow). Soft-delete (AuditableEntity IsDeleted=true,
   không xóa hoàn toàn DB).
3. Bỏ nút "Sửa header" + "Đóng" + "Xóa" header bar workspace mode (chuyển
   xuống bottom action bar). Header bar chỉ còn nhóm display info + nút "Đóng"
   cho non-workspace view (Danh sách / Duyệt readOnly).
4. Section 4 column header NCC: dùng s.supplierName (master) thay vì
   displayName ?? supplierName (custom). displayName fallback sang title tooltip.
5. Section 3 row Xóa: nếu NCC đã có quotes (báo giá ở Section 4) → KHÔNG cho
   xóa (tránh mất báo giá). Hiển thị icon disabled + tooltip "xóa báo giá
   trước rồi mới xóa NCC".

Implementation:
  ~ PeDetailTabs.tsx (× 2 app)
    - Header bar workspace mode actions: bỏ "Sửa header" Pencil button (có
      inline edit Section 1 + pencil hover Panel 1 thay thế), bỏ "Xóa" (chuyển
      xuống bottom). "Đóng" giữ chỉ cho readOnly + non-workspace view.
    - useNavigate import bỏ (chỉ dùng còn ở CreateContractDialog scope local).
    - Bottom action bar workspace + canEdit + !readOnly:
      * LEFT: "Xóa phiếu" red button (chỉ phase === DangSoanThao) + confirm
        dialog "soft-delete, không xóa hoàn toàn DB" + onDelete callback (existing
        DELETE /pe/:id endpoint, AuditableEntity IsDeleted=true).
      * CENTER: status text "✓ Các thay đổi đã tự động lưu khi chỉnh sửa..."
      * RIGHT: "Lưu" ghost button → invalidate ['pe-detail', id] + ['pe-list']
        + toast "Đã lưu — sync server" (KHÔNG onBack — workspace stay open).
      * RIGHT: "Lưu & Gửi Duyệt →" giữ nguyên (POST transitions).
    - SuppliersTab row actions: hasQuotes computed (= ev.details.some(d =>
      d.quotes.some(q => q.purchaseEvaluationSupplierId === s.id))). canDelete
      = !isWinner && !hasQuotes. Render Trash button enabled vs disabled span
      với tooltip "xóa báo giá trước".
    - ItemsTab matrix column header: {s.supplierName} (was {s.displayName ??
      s.supplierName}). title attr giữ displayName tooltip.

Verify: npm run build fe-admin + fe-user pass · 0 TS error · áp rule strict
verify khi remove import + button/condition logic changes.

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:37:04 +07:00
4c0625c0d2 [CLAUDE] FE-Admin+FE-User: PE detail Section 2 + 3 tweak + bottom action bar Lưu/Gửi Duyệt
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m9s
User feedback 2026-05-07 (annotation screenshot):
1. "a. NCC/TP được chọn" → dropdown picker chọn từ Section 3 list (canEdit only)
2. "c. Giá chào thầu" → tách 2 message rõ:
   - Chưa chọn NCC → "(chọn NCC/TP ở (a) trước)"
   - Đã chọn nhưng chưa có quotes → "(chưa nhập báo giá ở Section 4)"
   - Có quotes → số tiền
3. Section 3 NCC tham gia row → khi NCC là winner (selected): KHÔNG cho sửa/xóa
   (chỉ giữ icon ✓ active state, ẩn ✏ + 🗑 buttons)
4. Workspace mode bottom action bar: 2 nút "Lưu (đóng)" + "Lưu & Gửi Duyệt →"
   - Lưu: invokes onBack (đóng workspace, các thay đổi đã auto-save inline)
   - Lưu & Gửi Duyệt: confirm dialog → POST /transitions với targetPhase = first
     nextPhase (skip TuChoi/TraLai) → toast + invalidate + onBack
     → workflow chuyển từ Bản nháp/Trả lại → Đã gửi duyệt (ChoPurchasing thường)

Implementation:
  ~ PeDetailTabs.tsx (× 2 app, mirror y hệt)
    + NccSelectorRow component (~50 LOC) — Select dropdown tích hợp /select-winner
      endpoint hiện có. Read-only mode: hiển thị FormRow như cũ. Disable khi
      ev.suppliers empty + hint "Thêm NCC ở Section 3 trước".
    ~ ChonNccSection: thay <FormRow "a. NCC"> → <NccSelectorRow>. Cải tiến text
      "c. Giá chào thầu" empty state.
    ~ SuppliersTab row actions: wrap conditional isWinner = ev.selectedSupplierId
      === s.supplierId. !isWinner → render Pencil + Trash. isWinner → chỉ Check
      icon active state.
    ~ PeDetailTabs root: + qc useQueryClient + submitForApproval mutation +
      canSubmitForApproval flag. Bottom action bar hiển thị khi mode='workspace'
      + canEditPhase + !readOnly.

Verify: npm run build fe-admin + fe-user pass · 0 TS error · áp rule strict
verify (lesson hotfix CI 0ae3fe2 — luôn build trước commit khi có new code).

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:15:39 +07:00
0ae3fe2f39 [CLAUDE] FE-Admin+FE-User: hotfix CI build TS errors — forcedPhase rename + unused import
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m3s
CI run #125 + #126 fail (red  Gitea Actions) do TS compile errors trong commit
`18ebfa1` + `0c5db13` không catch local (UAT skip-verify rule). Errors:

  PeListPanel.tsx:41 — Property 'forcedPhase' does not exist (renamed to
    editableOnly nhưng quên xóa khỏi destructuring args list)
  PeListPanel.tsx:81,106 — Cannot find name 'editableOnly' (do destructuring
    vẫn dùng forcedPhase cũ → editableOnly không được declare ở scope)
  PeWorkspaceCreateView.tsx:20 — 'PurchaseEvaluationType' declared but never
    read (sau khi đổi <Select> Loại quy trình → <Input disabled> chỉ dùng
    PurchaseEvaluationTypeLabel, không cần enum value nữa)

Fix:
  ~ PeListPanel × 2 app: destructuring `forcedPhase,` → `editableOnly = false,`
  ~ PeWorkspaceCreateView × 2 app: bỏ `PurchaseEvaluationType` khỏi import

Verify: npm run build fe-admin + fe-user pass · 0 TS error · dotnet test 83
vẫn pass (Migration 17 + TraLai phase enum đã verify trước).

UAT mode rule: vẫn skip verify cho task FE-only nhỏ — nhưng phát hiện
multi-rename refactor + bỏ import nên check `npm run build` 1 lần trước commit.
TODO update memory feedback_uat_skip_verify.md thêm exception khi prop rename
hoặc remove unused import.

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