Commit Graph

422 Commits

Author SHA1 Message Date
2a53107602 [CLAUDE] AwV2: Mig 25 +IsUserSelectable + Designer pin toggle + Workspace filter, bỏ "(clone)"
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Hai yêu cầu UAT 2026-05-08:
1. Bỏ "(clone)" auto-append khi clone version mới — version đã đủ phân biệt.
2. Thêm pin toggle để admin chọn workflows nào cho user pick lúc tạo phiếu.

Migration 25 AddIsUserSelectableToApprovalWorkflows:
- ALTER ApprovalWorkflows ADD IsUserSelectable bit NOT NULL DEFAULT 0
- UPDATE backfill SET IsUserSelectable=1 WHERE IsActive=1 (giữ behavior cũ
  cho active versions, archived = false default — admin tự pin nếu cần)

BE:
- Domain ApprovalWorkflow +property IsUserSelectable
- DTO AwDefinitionDto +field
- CreateAwDefinitionCommandHandler set default true cho version mới
- New SetAwUserSelectableCommand + Handler
- API PATCH /api/approval-workflows-v2/{id}/user-selectable (Workflows.Create policy)
- DbInitializer SeedSampleApprovalWorkflowsV2Async set IsUserSelectable=true

FE Designer (fe-admin):
- DefinitionDto +isUserSelectable
- Badge amber "Pin Cho user chọn" khi true (cạnh Đang áp dụng/Archived)
- Button "Pin/PinOff Ghim cho user / Bỏ ghim" trong action group + mutation toggle
- Auto-fill name khi clone: bỏ "(clone)" suffix → giữ nguyên name

FE Workspace (fe-admin + fe-user):
- approvalWorkflows query filter w.isUserSelectable === true
- User dropdown chỉ thấy workflows admin đã pin

Verify: dotnet build pass · 81 test pass · npm build × 2 pass · Mig 25 apply LocalDB OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:15:23 +07:00
a9c0857a84 [CLAUDE] Fix sidebar highlight: strip transient keys (id/q/awId/...) khỏi queryMatches
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m17s
Bug UAT 2026-05-08: ở leaf "Danh sách" /purchase-evaluations?type=1, click
chọn 1 phiếu → URL thành ?type=1&id=abc → leaf bị mất highlight box.

Root cause: queryMatches exact-set equality — `{type}` (target) vs
`{type, id}` (current) length mismatch → no match → leaf unhighlight.

Fix: TRANSIENT_QUERY_KEYS = {id, q, editHeader, page, phase, awId} —
strip trước khi compare. Match dựa trên "navigation identity" only.

Edge case verify (cả 2 case quan trọng đều OK):
- /pe?type=1 click phiếu → ?type=1&id=abc → strip id → match Danh sách ✓
- /pe?type=1&pendingMe=1 → strip nothing → distinct với Danh sách ?type=1 ✓
- /pe?type=1&phase=10 (filter) → strip phase → match Danh sách ✓
- /pe?type=1&pendingMe=1&awId=xyz → strip awId → match Duyệt ✓

Mirror fe-admin + fe-user.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:01:59 +07:00
f77ea3828a [CLAUDE] Fix: ApprovalWorkflowsV2 GET ai authenticated cũng đc — Drafter pick workflow lúc create PE
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m14s
Bug UAT 2026-05-08: user Drafter (nv.test) login Workspace tạo phiếu B,
dropdown "Quy trình duyệt" empty silent. Sample seed B đã chạy đúng
(Designer admin hiển thị sample + clone v02 active) nhưng Workspace empty.

Root cause: class-level [Authorize(Policy = "Workflows.Read")] →
non-admin role 403 Forbidden khi GET /api/approval-workflows-v2.
TanStack Query catch error silent → dropdown empty không có warning.

Fix:
- Class-level [Authorize] only (any authenticated)
- GET inherit class policy (Drafter cần list workflow để pick — read-only)
- POST + DELETE giữ [Authorize(Policy = "Workflows.Create")] — admin-only Designer

Workflow data không nhạy cảm — chỉ là cấu hình quy trình. Validate
ApplicableType match PE.Type ở Create command đã có.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:45:04 +07:00
937eb2449c [CLAUDE] Workflow V2: clone leaf Designer + sample seed cho DuyetNccPhuongAn (B)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m16s
Mở rộng V2 schema cho type B mirror type A đã chốt S17. Phần lớn đã chung
qua ApplicableType discriminator — chỉ thêm menu key + sample seed.

Changes:
- MenuKeys.cs: +const ApprovalWorkflowDuyetNccPhuongAnV2 (AwV2_DuyetNccPhuongAn) + add vào All array
- DbInitializer.SeedMenusAsync: +leaf "Duyệt NCC và Giải pháp (Mới)" dưới root ApprovalWorkflowsV2
- DbInitializer +SeedSampleApprovalWorkflowsV2Async: seed QT-DN-PA-V2-001 v01 (1 Bước Phòng CCM × 1 Cấp NV test)
  Idempotent — skip nếu admin đã tạo bất kỳ workflow B nào hoặc thiếu test user
- fe-admin/lib/menuKeys.ts: +AwV2_DuyetNccPhuongAn

KHÔNG đụng:
- Migration (V2 schema chung qua ApplicableType — Mig 22-24 đã hỗ trợ B)
- Service ApproveV2Async (không hardcode type)
- Designer page ApprovalWorkflowsV2Page (TYPE_CODE_TO_INT đã có B=2)
- Layout/App.tsx (regex AwV2_(.+) match dynamic)
- Permission default (admin bypass + role khác không cần Designer access)

Verify: dotnet build pass · 81 test pass · npm build fe-admin pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:07:56 +07:00
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
8680f4c849 [CLAUDE] Docs: Session 17 wrap-up — PE Workflow V2 end-to-end consolidation
User chốt MD wrap-up Session 17 (13 commit từ c847dc0de0f38d) — PE
Workflow V2 schema + Service wire + UX iteration đầy đủ.

MD updates:
- STATUS.md — header counts (24 mig, 58 tables, 81 test, 43 gotcha) +
  Recently Done row consolidate Session 17 wrap-up (gộp 4 row iter cũ
  thành 1 row tổng, không cắt narrative quan trọng)
- HANDOFF.md — TL;DR Session 17 + Chunk E (Designer iter + State
  machine + Service wire + UX) + Pending Session 18+
- CLAUDE.md — count (22→24 mig, 77→81 test) + V2 schema overview
- changelog/migration-todos.md — section  Session 17 done với 7 task
  ticked + Defer Session 18+ explicit
- database/schema-diagram.md — §14 ApprovalWorkflow V2 schema (3 bảng
  + 2 column PE + state transitions + Service branch + Designer
  constraints + match approver V2 vs V1 table)
- gotchas.md — +#42 Dual schema branch + #43 Step.Order ≠ index 0-based
- skill contract-workflow — +section "Phase 9+ done Mig 22-24" với V2
  spec + Service branch + match logic table + Designer constraints +
  UX V2-aware + Defer Session 18+
- changelog/sessions/2026-05-08-1100-pe-workflow-v2-end-to-end.md (NEW)
  — full session log với 13 commit timeline + stats + gotchas + pending

Memory:
- project_solution_erp.md — entry Session 17 wrap-up đầy đủ (KHÔNG tạo
  file feedback mới — decisions specific cho project, không reusable
  cross-project per §9.5 anti-pattern)

Verify:
- 81 test pass (58 Domain + 23 Infra) — không thay đổi sau wrap-up
- 0 BE error
- 2 FE builds OK (đã verify ở các commit trước)
- Quy tắc consolidate §6.5: KHÔNG cắt narrative, chỉ phân tầng + remove
  duplicate. Session 17 row dài cố ý — cover full 13 commit context cho
  Session 18+ đọc lại.
2026-05-08 16:40:49 +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
ac41d5e0d8 [CLAUDE] Scripts: SQL clean transactional UAT (Session 17 V2 testing)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m12s
User UAT V2 schema cần clean phiếu cũ trên prod để đỡ rối mắt + giữ
master data (Users / Suppliers / Projects / Departments / Workflows).

scripts/sql/clean-transactional-uat.sql:
- DELETE theo FK order (child → parent):
  1. PE child rows (DeptApprovals/Opinions/Attachments/Changelogs/
     Approvals/Quotes/Details/Suppliers)
  2. PE main (giải phóng FK PE.ContractId/BudgetId)
  3. Contract per-type Details (7 bảng) + Contract child rows
  4. Contract main
  5. Budget child rows + main
  6. CodeSequences (PE + Contract reset)
  7. Notifications (dangling refs sau khi xóa phiếu)
- Wrap BEGIN/COMMIT TRANSACTION
- Verify queries cuối in count master KEEP + transactional after-clean

KEEP master: Users, Roles, MenuItems, Permissions, Suppliers, Projects,
Departments, UnitsOfMeasure, MaterialItems, ServiceItems, WorkItems,
ContractTemplates, ContractClauses, WorkflowTypeAssignments,
WorkflowDefinitions (V1 legacy), PurchaseEvaluationWorkflowDefinitions,
ApprovalWorkflows + Steps + Levels (V2 Mig 22).

Required SET QUOTED_IDENTIFIER ON; ANSI_NULLS ON; cho filtered indexes
Mig 19/20 (sqlcmd default off → DELETE fail).

Verify local Dev: chạy OK, 1 Contract + 0 PE + 0 Budget cleaned, master
giữ (Users=2 admin seed, Suppliers=3, Projects=1, Departments=9, V1
WfDef=7).
2026-05-08 15:28:40 +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
b41484b702 [CLAUDE] PE Workflow: wire Service V2 (Mig 24) — fix bug duyệt phiếu pin schema mới
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m14s
User báo bug eoffice: phiếu tạo mới không duyệt được + không bắt đc quy
trình mới. Root cause: Mig 23 pin ApprovalWorkflowId vào entity nhưng
Service vẫn đọc WorkflowDefinitionId legacy → match approver theo schema
cũ (Dept+PositionLevel/Role/User) thay vì ApproverUserId V2.

BE Domain — Migration 24 `AddCurrentApprovalLevelOrderToPe`:
- PurchaseEvaluation +CurrentApprovalLevelOrder int? (track Cấp 1/2/3
  đang chờ duyệt trong Step hiện tại khi pin V2). Null khi terminal/V1.
- RejectedAtStepIndex giữ deprecated DB column cho data cũ.

BE Service PE — branch theo schema pin:
- V2 (`ApprovalWorkflowId` set): ApproveV2Async() — load
  ApprovalWorkflows.Steps.Levels Include 3-level. Group Levels by Order
  = Cấp (OR-of-N approvers). Match `actor.Id ∈ levelGroup.ApproverUserId`
  (KHÔNG match Dept+Level/Role/User như V1). Advance:
    Còn cấp tiếp trong Step → levelOrder++
    Hết cấp → idx++, levelOrder=1
    Hết Step → DaDuyet
- V1 legacy (chỉ `WorkflowDefinitionId` set): ApproveV1LegacyAsync() —
  giữ nguyên logic Mig 21 (Dept+PositionLevel match)
- Drafter trình từ Nháp/Trả lại: init CurrentWorkflowStepIndex=0 +
  CurrentApprovalLevelOrder=1 (chỉ khi V2 pin)
- Reject (Trả lại): clear CurrentApprovalLevelOrder=null
- Reject (Từ chối): clear all tracking

BE Synthetic Policy V2:
- `PurchaseEvaluationPolicyRegistry.ForV2Schema()` — simple state machine
  policy (DangSoanThao/TraLai → ChoDuyet/TuChoi; ChoDuyet → ChoDuyet/
  TraLai/TuChoi). Roles="*" cho ChoDuyet branch — Service tự enforce
  ApproverUserId, Policy chỉ expose 3 nút FE.
- GetPurchaseEvaluationByIdQuery handler: ưu tiên ForV2Schema() khi pin
  V2 (FE đọc workflow.nextPhases để show button).

Verify: 81 test pass · BE 0 error · Mig 24 applied cả 2 LocalDB.

Test thử (Drafter eoffice):
1. Designer V2 tạo quy trình QT-DN-V2-001: Bước 1 (Phòng A), Cấp 1 (NV X)
2. Workspace tạo phiếu mới, Select QT-DN-V2-001 → Lưu phiếu + Gửi duyệt
3. Phiếu Phase=ChoDuyet, idx=0, levelOrder=1. NV X login → thấy phiếu
   trong Inbox + duyệt được. Sau approve → idx++, levelOrder reset 1.
4. Cấu hình level mismatch: NV Y khác → thấy ForbiddenException rõ tên.

Logic Contract V2 chưa wire (chỉ PE), defer Session sau khi user UAT PE OK.
2026-05-08 14:54:51 +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
d642fd361e [CLAUDE] Docs: STATUS Session 17 — 5 trạng thái + Designer V2 iter 3
Update header line + Recently Done row tổng hợp 3 commit:
- 9712778 lock 3 cấp (UAT iter 1)
- f3bea3c max 3 + N NV/cấp (UAT iter 2 — đúng intent user)
- ff21120 state machine 5 trạng thái (Trả lại = Phase riêng)

81 test pass (+4 TraLai entry point tests).
2026-05-08 14:14:00 +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
12daa7f6b0 [CLAUDE] Docs: Session 17 schema mới ApprovalWorkflowsV2 (Chunk D)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m16s
Update STATUS row mới + HANDOFF brief Session 17 + CLAUDE.md count
22 migration / 58 bảng.

Tóm tắt session:
- User chốt sau S16: schema flat Mig 21 vẫn không đúng intent → yêu
  cầu viết lại + thêm Menu mới "Duyệt NCC (Mới)" với cấu trúc explicit
  Quy trình > Bước (Phòng) > Cấp (NV cụ thể).
- 4 commit (3 chunk per-commit + docs): Mig 22 + 3 bảng mới +
  Application CQRS + API + FE Designer mới.
- PE/Contract Service CHƯA wire — vẫn pin Mig 21 legacy.
- Sau UAT UIUX OK → Session sau pin ApprovalWorkflowId song song +
  Service rewrite + migrate data + drop legacy.
- Backward compat 100%, 77 test pass no regression.
2026-05-08 12:48:06 +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
f6047d5218 [CLAUDE] Workflow: App CQRS + API ApprovalWorkflowsV2 (Chunk B)
3 handler MediatR + Validator + Controller cho schema mới Mig 22.

Files:
- Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs
  - GetAwAdminOverviewQuery (filter optional ApplicableType)
  - CreateAwDefinitionCommand + Validator (auto-increment Version
    theo Code, deactivate active version cùng ApplicableType)
  - DeleteAwDefinitionCommand (UAT helper — chưa pin nên unconditional)
  - DTO: AwDefinition/AwStep/AwLevel + TypeSummary
- Application/Common/Interfaces/IApplicationDbContext.cs (3 DbSet)
- Api/Controllers/ApprovalWorkflowsV2Controller.cs
  - Route /api/approval-workflows-v2
  - GET ?applicableType=N | POST | DELETE/{id}
  - Reuse policy Workflows.Read/Workflows.Create

Verify: build OK 0 error, IApplicationDbContext expose 3 DbSet mới.

Next: Chunk C — FE Designer page + route + Layout resolver.
2026-05-08 12:42:03 +07:00
c847dc0b24 [CLAUDE] Workflow: Mig 22 schema mới ApprovalWorkflowsV2 (Chunk A)
Session 17 — schema riêng UAT trước khi drop legacy WorkflowDefinition.
Cấu trúc 3 bảng theo yêu cầu user:
  Quy trình (Code+Name+ApplicableType)
    Bước (Phòng A — DepartmentId hint)
      Cấp (NV X — ApproverUserId 1 user cụ thể, KHÔNG OR-of-many)

Khác Mig 21: Levels match 1 NV CHÍNH XÁC qua ApproverUserId, không
match group Dept+PositionLevel/Role/User. Service sau UAT iterate
Steps OrderBy Order → Levels OrderBy Order → ApproverUserId duyệt.

Files:
- Domain/ApprovalWorkflowsV2/ApprovalWorkflow.cs (3 entity + enum
  ApplicableType: DuyetNcc/DuyetNccPhuongAn/Contract)
- Infra/Persistence/Configurations/ApprovalWorkflowConfiguration.cs
  (FK Cascade Step→Workflow, Level→Step; Restrict Department + User)
- Infra/Persistence/ApplicationDbContext.cs (3 DbSet)
- Infra/Persistence/DbInitializer.cs (2 menu mới: ApprovalWorkflowsV2
  root dưới System icon Workflow + AwV2_DuyetNcc leaf icon FileCheck)
- Domain/Identity/MenuKeys.cs (2 const + All array)
- Migration 20260508053749_AddApprovalWorkflowsV2 (3 table CREATE +
  2 UNIQUE + 3 index)

Verify:
- Build OK, 77 test pass (54 Domain + 23 Infra) ~3s
- Mig applied cả _Design + _Dev LocalDB

Next chunks:
- B: Application CQRS (Get/Create) + ApprovalWorkflowsV2Controller
- C: FE Designer page /system/approval-workflows-v2/:typeCode
- D: Docs + STATUS update
2026-05-08 12:39:37 +07:00
21ee36390e [CLAUDE] Docs+Skill: chốt Session 16 wrap-up — drastic refactor flat workflow (Chunk D)
Session 16 (2026-05-08) docs/skill/memory wrap-up:

STATUS.md:
- Last updated Session 16 (2 commit Chunk A+B)
- Phase summary count (20→21 mig, 96→77 test, 57→55 bảng)
- Recently Done row Session 16 chi tiết (drastic refactor hoàn tất)
- Phase enum simplified semantic post-Mig 21

HANDOFF.md:
- TL;DR Session 16 prepend với 2 chunk + Chunk C SKIP rationale
- Per-chunk implementation chi tiết (Domain + Mig 21 + Service + Tests
  + FE Designer)
- 8 cảnh báo Session 17+: UAT live test / old data migration / Sample
  seed / Budget N-stage / schema-diagram / skill refresh / tests flat /
  Hard blockers Ops

migration-todos.md: Phase 9 + Session 16 block 2 chunk done + 7 defer task

Session log NEW `2026-05-08-0500-drastic-refactor-flat-workflow.md`:
- Bối cảnh resume từ S15 defer
- Spec implementation (Phase enum, state machine, schema Mig 21)
- Per-chunk Chunk A + Chunk B detail
- Chunk C skip rationale
- Memory `feedback_drastic_refactor_scope` validation:
  scope estimate 30% accurate (3h actual vs 10h conservative)
- Plan organization sau S16

Skill ef-core-migration:
- description + heading: 20→21 migration
- + Mig 21 row "RefactorWorkflowToFlatModel" với detail (4 ALTER + 2
  ALTER + DROP TABLE × 2 + DROP COLUMN × 2 + restore simple unique × 2)
- Total 57→55 bảng (-2 InnerStep tables)
- Tests: 96→77 (drop 19 legacy)

CLAUDE.md root:
- Migration count 20→21
- DB tables 57→55
- Workflow flat description thay N-stage description

docs/rules.md §7: 96→77 test (Mig 21 simplified)

Memory `project_solution_erp.md`:
- Add "Tổng sau session 16" block với drastic refactor details

🎉 Session 16 wrap-up. Cumulative since session start (S15 wrap-up
`38d10b7`): 4 commit (S15 wrap-up was final S15) + 3 commit Session 16
(A `dbb0089` + B `88a5be1` + D current).

Defer Session 17+ priority:
1. UAT live test workflow flat (3 phòng × N cấp realistic)
2. Old PE/HĐ legacy phase data migration
3. Sample data seed (Task 2 carry-over)
4. Hard blockers Ops

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:16:32 +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
dbb0089e28 [CLAUDE] Drastic refactor: flat workflow Phòng × Cấp + Migration 21 (Chunk A)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m18s
User chốt drastic refactor — bỏ phase enum hoàn toàn, dùng ChoDuyet=10
đơn nhất + currentStepIndex tracking. Workflow flat list (Phòng × Cấp ×
Approvers). Mỗi PE/HĐ pin WorkflowDefinitionId chạy hết quy trình đó.

Schema (Migration 21 `RefactorWorkflowToFlatModel`):
- Phase enum +ChoDuyet=10 (PE + Contract). Legacy 2-9 + 98 deprecated.
- WorkflowStep + DepartmentId Guid? (FK Restrict) + PositionLevel int?
  (PE + Contract — mirror).
- PE/Contract + CurrentWorkflowStepIndex int? + RejectedAtStepIndex int?
- DROP table PurchaseEvaluationWorkflowStepInnerSteps (Mig 18)
- DROP table WorkflowStepInnerSteps (Mig 20)
- DROP column ContractDeptApproval.InnerStepId (Mig 20)
- DROP column PEDeptApproval.InnerStepId (Mig 18)
- DROP filtered indexes (Mig 19/20) + restore simple unique
  (TargetId, Phase, Dept, Stage) non-filtered

Service rewrite (PE + Contract WorkflowService.TransitionAsync):
- Phase transitions: DangSoanThao → ChoDuyet (Drafter trình, init idx=0)
- ChoDuyet → ChoDuyet (advance idx per approve)
- ChoDuyet → DaDuyet/DaPhatHanh (idx >= steps.Count → terminal)
- ChoDuyet → DangSoanThao (Trả lại — save RejectedAtStepIndex)
- ChoDuyet → TuChoi (Từ chối — khoá vĩnh viễn)
- DangSoanThao + RejectedAtStepIndex → ChoDuyet jump-back to saved idx
- Approver match: actor.Dept == step.Dept AND actor.PositionLevel >=
  step.PositionLevel (OR-of-many cùng cấp/dept = pass) OR
  Approvers.Any(Kind=User AND id match) OR
  Approvers.Any(Kind=Role AND actorRoles contains)
- Admin role bypass policy. Last step done → gen mã HĐ (Contract only)

App CQRS:
- WorkflowStepDto + WorkflowStepInput drop InnerStep, add DepartmentId
  + PositionLevel fields. PE + Contract mirror.

Tests rewrite:
- DROP PeNStageApprovalTests.cs (6 test) + ContractNStageApprovalTests.cs
  (6 test) + PeTwoStageApprovalTests.cs (7 test) — legacy N-stage/2-stage
  no longer applicable
- UPDATE PeWorkflowAdminTests signature to new flat input
- 96 → 77 test pass (drop 19 legacy)

Reference Domain entities removed:
- WorkflowStepInnerStep (Contract)
- PurchaseEvaluationWorkflowStepInnerStep (PE)
- DTOs WorkflowStepInnerStepDto / CreateWorkflowStepInnerStepInput per module

Memory `feedback_drastic_refactor_scope.md` validated: drastic refactor
done in dedicated session với context fresh, scope ~5h actual (planned ~8-10h
with 2x buffer).

Verify:
- dotnet build SolutionErp.slnx 0 error
- dotnet ef database update Mig 21 LocalDB applied OK
- dotnet test 77 pass (54 Domain + 23 Infra)
- 3-file rule: Migration .cs + Designer.cs + Snapshot updated

Pending Chunk B: FE Designer flat UI (PeWorkflowsPage + WorkflowsPage).
Pending Chunk C: FE PeWorkflowPanel + workflow timeline display.
Pending Chunk D: Docs + Skill + Memory + session log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:04:51 +07:00
38d10b7897 [CLAUDE] Docs: chốt Session 15 wrap-up — tooltip diagnose + drastic refactor DEFER
Session 15 (2026-05-07) docs update:

STATUS.md:
- Last updated Session 15 (1 commit `835cc7f` tooltip + working tree
  drastic refactor revert)
- Recently Done row Session 15 chi tiết (diagnose + plan drastic +
  attempt 12 file edits + REVERT decision)

HANDOFF.md:
- TL;DR Session 15 prepend với 2 phần: Diagnose tooltip + Drastic
  refactor DEFER decision
- 4 cảnh báo Session 16+: Drastic refactor pending (8-10h dedicated
  hoặc fallback Approach Y), Task 2 sample seed pending, schema-diagram
  defer cron audit, Hard blockers giữ nguyên

migration-todos.md:
- Phase 9 + Session 15 block với 1 task done (tooltip) + 1 defer
  (drastic refactor) + memory entry note
- Defer Session 16+ list

Session log NEW `2026-05-07-2600-tooltip-defer-drastic.md`:
- Bối cảnh user UAT báo button silent disabled
- Phần 1 — Diagnose tooltip (root cause + fix UX + "trùng ID" KHÔNG
  phải bug FE)
- Phần 2 — Plan drastic refactor flat workflow → DEFER:
  * User spec mới (Phòng × Cấp × Users[] flat)
  * Plan 6 chunk + estimate scope realistic ~8-10h
  * Attempt 12 file working tree edits → REVERT decision
  * Memory entry capture decision rule
- Plan organization sau S15 (defer queue)

Memory entry NEW `feedback_drastic_refactor_scope.md`:
- Quy tắc: drastic refactor cần dedicated session, scope conservative
  2x buffer
- Anti-patterns mid-session big refactor + commit broken state
- Defer pattern (revert working tree → document → memory entry → surface
  trade-off cho user)
- Cross-ref `feedback_per_chunk_commit.md` discipline

🎉 Session 15 wrap-up. Cumulative since session start (13h17): 16 commit
(1 button removal + 6 PE N-stage + 5 Contract N-stage + 1 3-button +
1 Session 14 wrap-up + 1 tooltip + 1 Session 15 wrap-up).

Verify: dotnet test 96 pass + working tree clean.

Defer Session 16+ priority order:
1. Drastic refactor flat workflow (dedicated session ~8-10h)
   OR fallback Approach Y (FE flat UI 5 phòng, 1-2h)
2. Task 2 sample data seed N-stage
3. Hard blockers Ops (UAT, SMTP, etc.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:03:56 +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
7c83ac8f17 [CLAUDE] Docs+Skill: chốt Session 14 wrap-up — PE 3-button workflow + Task 2 defer
Session 14 (2026-05-07) docs/skill update:

STATUS.md:
- Last updated + Phase summary count (95→96 test, 20 mig, 57 bảng, 41 gotcha)
- Recently Done row Session 14 chi tiết (3-button + Task 2 in-progress + DesignTime/Runtime DB gotcha)

HANDOFF.md:
- TL;DR Session 14 prepend với 1 commit + Task 2 defer
- 5 cảnh báo Session 15+: TraLai phase orphan / Task 2 sample seed / DesignTime
  vs Runtime DB / Budget N-stage defer / schema-diagram §17-19 defer

migration-todos.md: Phase 9 + Session 14 block 4 sub-task done + 2 defer task

Session log NEW `2026-05-07-2500-3-button-workflow.md`:
- Bối cảnh + spec 3-button (Duyệt/Trả lại/Từ chối) + implementation chi tiết
  (Domain policy expand + Service tách reject + FE button + dialog warning)
- Tests update (95→96 với +1 NEW Reject_To_TuChoi_Locks_Permanently)
- Task 2 in-progress: DesignTime vs Runtime DB gotcha + API exit 255 sớm
- Plan organization sau S14

Skill ef-core-migration:
- description + heading: 17→20 migration
- Bảng migration history thêm Mig 18-19 (PE) + Mig 20 (Contract)
- Section MỚI "N-stage workflow pattern (Mig 18-20)" — architecture decision
  với filtered unique trick + per-module migration packaging guideline
- Phase 8 update: 83→96 test breakdown

Skill contract-workflow:
- Section MỚI "Phase 9+ done (Mig 18-20 — Session 12/13/14)":
  * N-stage workflow PE + Contract (entity + filtered unique split + service
    refactor + tests + FE Designer + UsersPage cấp + API)
  * PE 3-button approval (Duyệt/Trả lại/Từ chối) Session 14
  * Defer: Budget N-stage / Phase TraLai=98 orphan

CLAUDE.md root: 16→20 migration + 55→57 bảng + 83→96 test
docs/rules.md §7: Phase 9 active 83→96 test

Verify: dotnet test 96 pass + npm build (skip — pure docs/skill update).

🎉 Session 14 wrap-up complete. Pushed 1 task (3-button) + Task 2 defer.
Cumulative since session start (13h17): 13 commit (1 button removal +
6 PE N-stage Chunk A-F + 5 Contract N-stage Chunk A,B,C,D,F + 1 3-button).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:40:00 +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
7c0772acca [CLAUDE] Tests: Contract N-stage approval 6 test mirror PE (Chunk D)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m1s
ContractNStageApprovalTests.cs (NEW, 6 test) cover N-stage logic Mig 20
mirror PeNStageApprovalTests pattern:

1. NStage_FirstInner_NV_Approve_Blocks_Phase_Transition
   NV cấp 1 → 1 row InnerStepId set, phase chưa đổi.

2. NStage_All_3_Levels_Sequential_Pass_Allow_Phase_Transition
   NV → PP → TP duyệt lần lượt → 3 rows + phase chuyển.

3. NStage_TP_Bypass_Skips_Lower_Levels_Same_Dept
   TP có CanBypassReview → 1 transition tạo 3 rows (NV+PP IsBypassed,
   TP exact match).

4. NStage_Wrong_Department_Throws_Forbidden
   Actor dept khác inner step's dept → ForbiddenException.

5. NStage_Reject_Clears_InnerStep_Rows_At_Phase
   NV approve → 1 row. Admin reject → DangSoanThao + RejectedFromPhase
   set + N-stage rows cleared.

6. LegacyFallback_NoInnerSteps_Uses_2Stage_Logic
   Contract không pin WorkflowDefinitionId → fallback hardcoded Standard
   policy → no inner steps → legacy 2-stage Stage=Review row.
   Phase pair DangKiemTraCCM → DangTrinhKy + role CostControl khớp
   Standard.Transitions (DangGopY → DangDamPhan trong test ban đầu fail
   vì policy chỉ cho [Drafter, DeptManager]; switched to phase pair work).

Helper SeedWorkflowDefinitionAsync 2 step adjacent (DangGopY + DangDamPhan)
+ SeedContractAsync với Project + Supplier seed cho FK.

FakeChangelogService + FakeContractCodeGenerator stubs (no-op cho tests
không cần verify changelog/codegen path).

Verify: 89 → **95 test pass** (54 Domain + 41 Infra: 17 codegen + 6 PE WF
versioning + 6 PE 2-stage + 6 PE N-stage + 6 Contract N-stage).

Pending Chunk E: API check (Workflows controller likely auto-bind via DTO,
skip nếu OK).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:07:32 +07:00
e247b67681 [CLAUDE] Infra: ContractWorkflowService N-stage logic mirror PE (Chunk C)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Refactor TransitionAsync mirror PurchaseEvaluationWorkflowService Mig 18
N-stage pattern:
- Reject branch: clear N-stage approval rows tại fromPhase (resume sẽ
  approve lại từ inner đầu)
- Load definition with InnerSteps eager + assign outer scope
- Department approval block split:
  * hasInnerSteps=true → N-stage logic:
    - Yêu cầu actor có DeptId + PositionLevel set (else throw 403)
    - Match firstPending (Order asc IsRequired) same dept + (exact level
      OR canBypass + level≥)
    - exact match: upsert 1 row InnerStepId, IsBypassed=false
    - bypass: batch upsert NV+PP+TP cùng dept ≤ actor (audit IsBypassed
      cho cấp dưới)
    - Recheck stillPending → BLOCK + log Approval/Changelog "duyệt cấp X
      (còn Y pending)"
    - All done → fall through phase transition
  * hasInnerSteps=false → Legacy 2-stage Mig 16 (giữ nguyên với
    InnerStepId=null filter)

Backward compat 100%: workflow Contract no InnerSteps configured →
service fallback legacy 2-stage NV.Review/TPB.Confirm. Tests 89 pass —
no regression.

Verify: dotnet build 0 error, dotnet test 89 pass.

Pending Chunk D: ContractNStageApprovalTests 6 test mirror PE pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:04:16 +07:00
04cf2a0385 [CLAUDE] App: Contract workflow inner steps DTO + designer mirror PE (Chunk B)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
DTO + Validator + Handler mở rộng cho N-stage admin designer Contract
(Mig 20). Mirror PE Mig 18 pattern (PeWorkflowAdminFeatures Chunk B).

WorkflowAdminFeatures:
- WorkflowStepInnerStepDto record (Id, Order, DeptId, DeptName,
  PositionLevel, Name, SlaDays, IsRequired)
- WorkflowStepDto extend +InnerSteps List
- GetWorkflowAdminOverviewQueryHandler — Include InnerSteps OrderBy +
  resolve DeptNames cho display
- CreateWorkflowStepInnerStepInput record
- CreateWorkflowStepInput extend +InnerSteps (nullable, default null —
  backward compat existing test code positional new())
- Validator child rules cho InnerSteps (Order ≥1, DeptId not empty,
  PositionLevel 1-3, SlaDays ≥0)
- CreateWorkflowDefinitionCommandHandler — convert InnerSteps khi build
  entity (atomic batch insert qua nav collection)

Verify: dotnet build 0 error, 89 test pass (no regression).

Pending Chunk C: ContractWorkflowService.TransitionAsync N-stage logic
+ legacy 2-stage fallback mirror PE pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:02:10 +07:00
951ffa3ed8 [CLAUDE] Domain+Infra: Migration 20 Contract workflow inner steps mirror PE (Chunk A)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Mirror PE N-stage Mig 18+19 pattern sang Contract module. Gộp 1 migration
(CREATE TABLE + ALTER + filtered unique alter) thay vì tách 2 như PE.

Schema:
- entity WorkflowStepInnerStep (Domain/Contracts/) — Order, DeptId,
  PositionLevel, Name, SlaDays, IsRequired
- WorkflowStep nav +InnerSteps List
- ContractDepartmentApproval +InnerStepId Guid? FK Restrict
- CREATE TABLE WorkflowStepInnerSteps (no Contract prefix vì module dùng
  entity gốc Workflow* từ Mig 8)
- DROP UX_ContractDeptApprovals_Contract_Phase_Dept_Stage cũ
- RECREATE filtered: WHERE InnerStepId IS NULL (legacy 2-stage Mig 16)
  + new filtered UNIQUE (ContractId, Phase, InnerStepId) WHERE InnerStepId
  IS NOT NULL (N-stage)
- 3 INDEX: IX_InnerStepId, IX_(StepId, Order), IX_DeptId

Backward compat 100%: workflow Contract no InnerSteps configured →
service fallback legacy 2-stage. Data legacy InnerStepId=null vẫn enforce
unique cũ qua filtered index.

Note: Budget defer (chưa có versioned WorkflowDefinition entity —
hardcoded BudgetPolicy.Default). Cần migration AddBudgetVersionedWorkflow
trước khi mirror N-stage Budget.

Verify:
- dotnet build SolutionErp.slnx 0 error
- dotnet ef database update LocalDB applied OK
- dotnet test 89 pass (54 + 35) — no regression

Pending Chunk B: WorkflowAdminFeatures.cs DTO/Input/Validator/Handler
extend mirror PE Chunk B pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:00:45 +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
83ffabd0b5 [CLAUDE] Api: PATCH /users/{id}/position-level endpoint (Chunk E)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m1s
Endpoint mới mirror SetBypassReview pattern:
- PATCH /api/users/{id}/position-level
- Body: { positionLevel: int? } (1=NV, 2=PP, 3=TP, null=clear)
- Authorize Policy "Users.Update"
- Send SetUserPositionLevelCommand qua MediatR

PE Workflow Designer admin Create/Get endpoint KHÔNG cần đụng — record
DTO `CreatePeWorkflowDefinitionCommand` đã extend với InnerSteps (default
null) ở Chunk B, JSON body bind tự động qua existing controller.

Verify: dotnet build 0 error 0 warning · 89 test pass (no regression).

Pending Chunk F: FE Designer InnerSteps sub-section + UsersPage column
"Cấp" + Docs/Skill update (cuối cùng).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:24:58 +07:00
3d76c6bc0c [CLAUDE] Tests: PE N-stage workflow approval (6 test) + IdentityFixture extend (Chunk D)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
PeNStageApprovalTests.cs (NEW, 6 test) cover N-stage logic Mig 18:

1. NStage_FirstInner_NV_Approve_Blocks_Phase_Transition
   NV.PRO duyệt cấp 1 → 1 row InnerStepId set, phase chưa đổi (còn 2 cấp).

2. NStage_All_3_Levels_Sequential_Pass_Allow_Phase_Transition
   NV → PP → TP duyệt lần lượt → 3 rows + phase chuyển. Order asc enforce.

3. NStage_TP_Bypass_Skips_Lower_Levels_Same_Dept
   TP có CanBypassReview → 1 transition tạo 3 rows (NV+PP IsBypassed=true,
   TP exact match). Audit chuẩn.

4. NStage_Wrong_Department_Throws_Forbidden
   Actor dept khác inner step's dept → ForbiddenException.

5. NStage_Reject_Clears_InnerStep_Rows_At_Phase
   NV approve → 1 row. Reject → DangSoanThao + RejectedFromPhase set +
   N-stage rows cleared (resume sẽ approve lại).

6. LegacyFallback_NoInnerSteps_Uses_2Stage_Logic
   PE không pin WorkflowDefinitionId → service fallback hardcoded policy →
   no inner steps → legacy 2-stage Stage=Review/Confirm logic kick in.

IdentityFixture.CreateUserAsync extend +PositionLevel? param (default null
cho admin/system user).

Helper SeedWorkflowDefinitionAsync: tạo definition với 2 steps adjacent
(ChoPurchasing có inner steps + ChoCCM next) — đủ cho FromDefinition build
transition policy guard pass actor role Procurement.

Verify: 83 → **89 test pass** (54 Domain + 35 Infra: 17 codegen + 6 PE WF
versioning + 6 PE 2-stage + 6 PE N-stage). 0 fail.

Pending Chunk E: API endpoints (PATCH /users/{id}/position-level + DTO
extend in PeWorkflowsController bind tự động).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:23:59 +07:00
0c62e241d0 [CLAUDE] Infra: PE workflow service N-stage logic + Migration 19 unique filter (Chunk C)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m2s
Migration 19 `AlterPeDeptApprovalsUniqueFilteredForInnerSteps` — fix
UNIQUE constraint conflict khi N-stage có nhiều inner step cùng dept:
- Drop UX_PEDeptApprovals_PE_Phase_Dept_Stage (Mig 16)
- Recreate filtered: UNIQUE (PEId, Phase, Dept, Stage) WHERE InnerStepId IS NULL
  (legacy 2-stage rows giữ nguyên invariant)
- New filtered: UNIQUE (PEId, Phase, InnerStepId) WHERE InnerStepId IS NOT NULL
  (N-stage 1 row per inner step per phase)

PurchaseEvaluationWorkflowService.TransitionAsync refactor:
- Load definition with InnerSteps eager (.Include ThenInclude)
- Reject branch: clear N-stage approval rows tại fromPhase (resume sẽ
  approve lại từ inner step đầu — clean state)
- Department approval block split:
  * hasInnerSteps=true → N-stage logic:
    - Yêu cầu actor có DepartmentId + PositionLevel set (else throw 403)
    - Match firstPending inner step (Order asc, IsRequired only):
      same dept AND (exact PositionLevel OR canBypass + level ≥ pending)
    - exact match: upsert 1 row (Stage=Confirm, InnerStepId=that)
    - bypass: batch upsert NV+PP+TP cùng dept ≤ actor level (audit
      IsBypassed=true cho cấp dưới skip)
    - Recheck stillPending → BLOCK transition + log Approval/Changelog
      "đã duyệt cấp X (còn Y cấp pending)"
    - All done → fall through phase transition
  * hasInnerSteps=false → Legacy 2-stage (Mig 16) — giữ nguyên logic
    NV.Review/TPB.Confirm + InnerStepId=null filter

Backward compat: workflow cũ (no InnerSteps configured) chạy đúng logic
2-stage Mig 16. Data legacy InnerStepId=null vẫn match unique cũ qua
filtered index. Tests 83 pass — no regression.

Verify:
- dotnet build SolutionErp.slnx 0 error
- dotnet ef database update LocalDB applied Mig 19
- dotnet test 83 pass

Pending Chunk D: Tests N-stage workflow (~6-7 test mới: sequential pass /
bypass cùng dept / reject reset / resume jump-back / legacy fallback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:20:17 +07:00
0e56bd0a67 [CLAUDE] App: PE workflow inner steps DTO + UpdateUserPositionLevel CQRS (Chunk B)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m42s
DTO + Validator + Handler mở rộng cho N-stage admin designer (Mig 18):

PeWorkflowAdminFeatures:
- PeWorkflowStepInnerStepDto record (Order/Dept/PositionLevel/Name/SlaDays/IsRequired)
- PeWorkflowStepDto extend +InnerSteps List
- GetPeWorkflowAdminOverviewQuery: Include InnerSteps OrderBy Order +
  resolve DeptNames cho display
- CreatePeWorkflowStepInnerStepInput record
- CreatePeWorkflowStepInput extend +InnerSteps (nullable, default null —
  backward compat existing test PeWorkflowAdminTests positional new())
- Validator child rules cho InnerSteps (Order >=1, DeptId not empty,
  PositionLevel 1-3, SlaDays >=0)
- Handler convert InnerSteps khi build entity (atomic batch insert)

UserFeatures:
- UserDto +PositionLevel int? field
- ListUsers + GetUser handlers map (int?)u.PositionLevel
- SetUserPositionLevelCommand + Validator + Handler mirror
  SetUserBypassReviewCommand pattern (admin set qua UserManager UI)

Verify:
- dotnet build SolutionErp.slnx 0 error
- dotnet test 83 pass (54+29) — no regression
- Backward compat: PeWorkflowAdminTests existing 6 test pass (named-arg
  positional record vẫn work với InnerSteps default null)

Pending Chunk C: PurchaseEvaluationWorkflowService.TransitionAsync N-stage
logic + legacy 2-stage fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:14:39 +07:00
13ab533fe7 [CLAUDE] Domain+Infra: Migration 18 PE workflow inner steps + User.PositionLevel (Chunk A)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
N-stage workflow approval — mỗi WorkflowStep cha (= 1 phase) cấu hình
được chuỗi InnerSteps con theo Department × PositionLevel với Order
sequential. Phase 9+ feature, mở rộng từ 2-stage Mig 16.

Schema:
- enum PositionLevel { NhanVien=1, PhoPhong=2, TruongPhong=3 } (Domain/Identity)
- ALTER Users + PositionLevel int? NULL (admin/system user vẫn null)
- CREATE TABLE PurchaseEvaluationWorkflowStepInnerSteps:
  Id PK, PurchaseEvaluationWorkflowStepId FK Cascade,
  Order int, DepartmentId FK Restrict, PositionLevel int,
  Name nvarchar(200), SlaDays int?, IsRequired bit
- ALTER PurchaseEvaluationDepartmentApprovals + InnerStepId Guid? FK Restrict
  (null cho data legacy 2-stage Review/Confirm Mig 16)

Backward compat: step KHÔNG có InnerSteps → service fallback logic
2-stage Stage=Review|Confirm cũ (Chunk C). Data Mig 16 hiện có giữ
nguyên, InnerStepId=null.

Verify:
- dotnet build SolutionErp.slnx pass (0 error, 2 pre-existing warning DocxRenderer)
- dotnet ef database update LocalDB applied OK
- dotnet test SolutionErp.slnx 83 pass (54 Domain + 29 Infra) — no regression
- 3-file rule: Migration.cs + Designer.cs + Snapshot updated

Pending Chunk B: Application CQRS — extend CreatePeWorkflowDefinitionCommand
với InnerSteps DTO + UpdateUserPositionLevelCommand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:11:42 +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
edc66602c1 [CLAUDE] Docs: chốt Session phase 2 wrap-up — B12-B14 PE detail polish iterate
Tổng hợp 3 commit từ `378c993` → `d2306b8` (B12-B14) sau wrap-up `6e7a6db`.
KHÔNG cắt narrative cũ — thêm row STATUS, TL;DR HANDOFF, block migration-todos,
append session log (rule §6.5).

Files:
  ~ docs/STATUS.md
    - Last updated phase 2 + Phase summary cập nhật 8→9 PE phase enum
    + Recently Done: 1 row B12-B14 polish (commit SHA + chi tiết narrative
      đầy đủ context)
  ~ docs/HANDOFF.md
    - Last updated phase 2 + TL;DR Session phase 2 prepend với 3 batch
    + 4 cảnh báo Session 12+ bổ sung (8-11): isSelected per-quote BE field
      legacy, winner column logic, loading overlay scope, useEffect deps risk
  ~ docs/changelog/migration-todos.md
    + Session phase 2 done block với 3 task tick (B12-B14 commit SHA)
  ~ docs/changelog/sessions/2026-05-07-2359-pe-workspace-ux-overhaul.md
    + Append "Session phase 2" section với 3 batch chi tiết (B12-B14) + bug
      log + stats cumulative phase 2

Skill: KHÔNG update (no skill-relevant changes — pure FE polish).
Memory: KHÔNG add mới (rule UAT skip-verify đã update mid-session).
Tests: 83 pass (no test changes — UAT iter mode rule §7).

Verify: dotnet test 83 pass · git status clean · push pending.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:04:07 +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
6e7a6db4e8 [CLAUDE] Docs+Skill: chốt Session S10-11+++++++ wrap-up — PE Workspace UX overhaul
Tổng hợp 23 commit từ `b7a153e` (post Session 9+) → `4c0625c` (last) thành
1 wrap-up entry. KHÔNG cắt narrative cũ — thêm 1 row đầu STATUS + 1 TL;DR
prepend HANDOFF + 1 block migration-todos + 1 session log mới (rule §6.5).

Files:
  ~ docs/STATUS.md
    - Last updated S10-11+++++++ wrap-up + Phase summary 16→17 mig + 5 display
      status + 8→9 phase enum (TraLai)
    + Recently Done: 1 row tổng hợp 11 batch deliverable (B1-B11) với commit
      SHA range, narrative đầy đủ context per §6.5 KEEP rule
  ~ docs/HANDOFF.md
    - Last updated + TL;DR Session S10-11+++++++ prepend với 11 batch summary
    + Stats table cumulative (BE LOC +450, Mig 17, FE pages 31→32, +5 component
      mới, +1 phase TraLai, +5 display status, 23 commit)
    + 7 cảnh báo Session 12+ (TraLai workflow transition pending, multi-phase
      filter, opinion sign Duyệt mode, UAT skip-verify exception, Workspace
      vs Danh sách vs Duyệt matrix, Mig 17 backward compat, CI deploy status)
  ~ docs/changelog/migration-todos.md
    + Session S10-11+++++++ done block với 11 task tick + commit SHA references
    + 3 defer task cho Session 12+ (TraLai workflow, multi-phase filter, opinion sign)
  + docs/changelog/sessions/2026-05-07-2359-pe-workspace-ux-overhaul.md
    Session log đầy đủ (11 batch chi tiết + bug log + docs updates checklist
    + stats cumulative + Plan organization hierarchy)
  ~ .claude/skills/ef-core-migration/SKILL.md
    - "16 migration" → "17 migration" header
    + Row 17 `AddManualBudgetFieldsToPeAndContract` table entry
    - Total: 55 bảng giữ nguyên (+4 cột không thêm bảng) + note clarification
    ~ Phase 7 pending PaymentTermFields cập nhật note (Workspace UI đã thay
      Select preset, BE schema giữ nvarchar(max), defer migration tách field)

Defer cho cron audit 2026-06-01:
  - contract-workflow/SKILL.md TraLai phase note (chờ wire workflow xong)
  - schema-diagram.md §15 Mig 17 +4 columns (small, không drift major)
  - gotchas count update (KHÔNG add vì TS strict CI fail là process issue,
    addressed via memory rule, không phải code-bug pattern)

Memory updated trước đó (commit không có vì memory ở ngoài repo):
  - feedback_uat_skip_verify.md: thêm exception "rename/remove → BẮT BUỘC
    npm run build" + lesson hotfix CI 0ae3fe2

Verify: `dotnet test` 83 pass · git log clean · branch up-to-date sau push.

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