Commit Graph

129 Commits

Author SHA1 Message Date
f3db9e6cc0 [CLAUDE] PurchaseEvaluation: Chunk L1 — Fix F2 skipToFinal semantic: skip pointer tới NV cuối (KHÔNG terminate DaDuyet)
Bro UAT S23 t2 catch: Plan K K2 implement F2 SAI semantic — set
Phase=DaDuyet terminal auto-approve. Bro intent: "Duyệt thẳng đến CEO,
bỏ qua các bước khác chứ ko phải chuyển sang đã duyệt."

Refactor Service.cs ApproveV2Async F2 branch:
- Resolve lastStepIdx = steps.Count - 1, lastLevelMaxOrder = max(LevelOrder)
  trong Step cuối
- Advance pointer: CurrentWorkflowStepIndex = lastStepIdx + CurrentApprovalLevelOrder = lastLevelMaxOrder
- Phase GIỮ NGUYÊN ChoDuyet — NV cuối (CEO/last approver) vẫn cần ký thật
  để tiến DaDuyet
- Audit log "Approver skip thẳng tới Bước X Cấp Y (NV cuối) — bỏ qua các Bước/Cấp trung gian"
- Guard no-op: actor đã ở slot cuối → fall through advance logic (normal → DaDuyet)
  (KHÔNG double-advance khi skipToFinal=true ngay slot cuối)
- Reset SLA 7d cho NV cuối nhận lại

FE × 2 app PeWorkflowPanel.tsx (mirror rule §3.9):
- Description text update: "Phiếu sẽ skip tới NV cuối (CEO/cấp ký cuối) —
  NV cuối vẫn cần duyệt thật để hoàn tất."
- Amber warning update: "Bỏ qua mọi Cấp/Bước trung gian, phiếu chuyển thẳng
  tới NV cuối. NV cuối vẫn phải ký duyệt thật để phiếu thành 'Đã duyệt'."

Verify:
- dotnet build production projects clean (0 err, 2 pre-existing warn)
- npm run build × 2 app pass

Pattern lesson saved memory: Service skipToFinal semantic = advance pointer
NOT terminate. K7 tests TODO update: 3 Approver F2 tests assert pointer
moved to last slot, NOT Phase=DaDuyet. Defer test fix sau UAT confirm UX.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:39:03 +07:00
098baa6da6 [CLAUDE] Docs: Chunk G — K8 Plan K wrap S23 t1: docs + session log + Designer comment cleanup + 3 agent MEMORY drift
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m30s
Plan K Mig 31 F2 refactor sang per-Approver-slot DONE — 8 commits cumulative
S23 t1 (`56868bf..<this>`). K8 wrap docs + dirty MEMORY.md commit:

Docs updates:
- docs/STATUS.md: Last updated S23 t1 entry với Plan K summary 8 chunk
- docs/HANDOFF.md: TL;DR S23 t1 đầy đủ (top) — multi-agent ROI evidence
- docs/database/schema-diagram.md §14: title Mig 22-31 (was 22-29) + add
  Mig 30 F4 + Mig 31 F2 blocks per slot Approver + DROP Users column note
- NEW docs/changelog/sessions/2026-05-14-s23-turn1-plan-k-mig31-f2-refactor.md
  session log đầy đủ 8 chunk timeline + multi-agent spawn cost table + pattern
  reinforced 3×

FE Admin Designer comment cleanup (Reviewer K2 follow-up):
- ApprovalWorkflowsV2Page.tsx lines 73-75 + 502-504: 2 stale narratives "F2
  AllowDrafterSkipToFinal xuống per User (User Management)" rewrite Mig 29+30+31
  cumulative narrative "7 Allow* ALL xuống per Level slot, pattern proven 3×"

3 agent MEMORY.md drift commit (dirty từ session start S23 + S22 chốt):
- Investigator: K0 pre-flight findings + 5 surprises catch
- Reviewer: K2 PASS report + new pattern "transient sentinel zombie" anti-pattern
- CICD Monitor: S22 chốt verify cumulative (Run #193 + S23 t1 pending K9 spawn)

User-level memory updates (cross-project diary persisted ngoài repo):
- feedback_per_nv_permission_scope.md: reinforcement S23 t1 — Pattern 3×
  cumulative (Mig 29 + Mig 30 + Mig 31). Pattern ALSO applies cho refactor existing
  scope, KHÔNG chỉ greenfield. Cross-ref discoveries Plan K (compile-break workaround,
  stale narrative drift, transient sentinel zombie anti-pattern caught Reviewer).
- MEMORY.md index: cumulative reinforcement note 3× Mig 31

Verify:
- dotnet build production projects clean
- npm run build fe-admin pass 17.76s, 0 TS err
- Test 104/104 PASS (S23 t1 K7 chunk maintained baseline)

Plan K state final: 31 mig · 59 tables · ~145 endpoints · 104 test · 47 gotcha
· 20 memory · 6 skills · 4 sub-agents active. CHƯA push remote — chờ bro confirm
K9 spawn CICD Monitor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:48:41 +07:00
ebe2469470 [CLAUDE] FE-Admin FE-User: Chunk E — K6 Workspace DROP Drafter checkbox + ADD Approver toggle (Mig 31 F2 refactor)
Plan K Chunk E mirror 2 app rule §3.9. Refactor F2 UX flow:

DROP fe-admin + fe-user Workspace Drafter checkbox:
- PeDetailTabs.tsx Workspace action bar: REMOVE "Gửi thẳng Cấp cuối (skip trung gian)"
  violet label + state skipToFinal + allowSkipToFinal lookup + skipToFinal payload
- submitForApproval mutation signature simplify: opts: { skipToFinal: boolean } → void
- Confirm dialog text + button label drop skipToFinal conditional

ADD fe-admin + fe-user Approver toggle trong PeWorkflowPanel dialog:
- State skipToFinalApprover default false
- Visible khi Approve forward (NOT Cancel + NOT SendBack) + currentLevelOptions?.allowApproverSkipToFinal
- Checkbox violet panel với description "Phiếu sẽ tiến thẳng tới Đã duyệt (terminal)"
- Amber warning khi checked: "Hành động KHÔNG quay lại được"
- Mutation payload +skipToFinal: !isReject && skipToFinalApprover
- onSuccess reset state

Type ApprovalWorkflowOptions × 2 app: +allowApproverSkipToFinal: boolean (7th)
Type PeDetailBundle × 2 app: REMOVE drafterAllowSkipToFinal field + comment Mig 29+30+31

UX design Dialog approach (consistent với Trả lại Mode picker pattern):
- Skip thẳng Cấp cuối = destructive action → confirm dialog amber warning
- Mirror Mig 28 Trả lại 4 mode picker UX consistency
- Em main solo K6 per UX flow decision criteria

Per bro decision Plan K S23 t1: "Chỗ cấu hình cho phép skip → duyệt thẳng cho phép
trong trạng thái đang duyệt" + "Tất cả đều cấu hình ngay trong chỗ setup quy trình duyệt".

Verify:
- npm run build × 2 app pass clean (0 TS err)
- Pre-existing warnings unchanged (chunk size + INEFFECTIVE_DYNAMIC_IMPORT)
- Bundle hash rotated × 2 app

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:34:39 +07:00
2ea8977d0f [CLAUDE] Backout: Chunk D — K5 cleanup F2 zombie endpoint + UsersPage column + DTO field
Reviewer K2 Major #1: PATCH /api/users/{id}/allow-skip-final endpoint Admin tick =
NoOp swallow silent (K1 sentinel → confusion UX). Full backout Plan D S22 stack:

BE drop (7 files):
- UsersController.cs: DELETE PATCH /allow-skip-final endpoint + SetAllowDrafterSkipToFinalBody record
- UserFeatures.cs: DELETE SetUserAllowDrafterSkipToFinalCommand + Handler
                    + UserDto.AllowDrafterSkipToFinal field
                    + list/get DTO mapping sentinel-false references
- ApprovalWorkflow.cs: REWRITE stale narrative line 78-80 (Reviewer Major #2 Mig 31 semantic)
                       + docstring AllowApproverSkipToFinal line 108 clean stale Users storage ref
- PurchaseEvaluationFeatures.cs: REWRITE Command DTO comment line 401 (Reviewer Minor #3)
- ApprovalWorkflowConfiguration.cs: APPEND Mig 31 narrative line 22-24 (Reviewer Minor #4)
                                     + clean storage move comment line 87
- ApprovalWorkflowV2AdminFeatures.cs: clean DTO comment line 58 stale "F2 xuống User table"
- IPurchaseEvaluationWorkflowService.cs + PurchaseEvaluationDtos.cs: clean stale
  "storage Users.AllowDrafterSkipToFinal" comments

FE Admin drop (2 files):
- UsersPage.tsx: DELETE "Skip cuối" column + FastForward badge + FastForward import
                  + allowSkipMut mutation hook + FastForward toggle button
- types/users.ts: DELETE allowDrafterSkipToFinal field

fe-user KHÔNG đụng (no UsersPage admin-only; K6 sẽ handle Workspace Drafter checkbox).
FE Designer page KHÔNG đụng (K3 done; 2 stale comment leftover deferred K6).

Plan K refactor F2 storage Users → Levels (Mig 31) complete cumulative cleanup.
Pattern reusable: post-refactor full cleanup (BE endpoint + Command + DTO + FE column
+ types + stale narratives) atomic 1 commit thay vì leak zombie state.

Verify:
- dotnet build production projects 0 err (2 pre-existing DocxRenderer warn)
- npm build fe-admin 0 TS err (no new warning)
- Grep AllowDrafterSkipToFinal + allow-skip-final + allowDrafterSkipToFinal zero results
  across src/Backend (excl Migrations history) + fe-admin/src

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:27:12 +07:00
dd52d16ca9 [CLAUDE] FE-Admin: Chunk C — Mig 31 K3 Designer 7th checkbox AllowApproverSkipToFinal + banner rewrite
ApprovalWorkflowsV2Page Designer inline panel mỗi Level entry thêm checkbox thứ 7:
"Cho phép duyệt thẳng Cấp cuối khi đang duyệt" (F2 admin opt-in per-slot Approver).
Group cuối list sau F4 AllowApproverEditBudget (Mig 30) — pattern mirror Mig 29/30
admin opt-in reinforced 3× cumulative.

Types LevelDto + EditLevelEntry +allowApproverSkipToFinal: boolean field.
Helper makeDefaultLevelEntry default false (opt-out — admin tick explicit).
Helper copyFromDefinition propagate flag từ workflow cũ.
POST/PATCH mutation body propagate 7th flag mỗi Level entry.

Banner line ~623-631 rewrite: "F2 cấu hình ở User Management" (Plan D S22 wire) →
"Cấu hình quyền duyệt riêng cho từng NV trong slot Approver bên dưới" — phản ánh
schema Mig 31 (F2 storage moved per-slot).

Per bro decision S23 t1 Plan K: "Tất cả đều cấu hình ngay trong chỗ setup quy trình duyệt".

Verify:
- npm run build fe-admin pass clean
- 0 TS error
- Bundle size 1395.74 KB (unchanged trivial)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:19:48 +07:00
56868bfd7f [CLAUDE] FE-Admin: Chunk pre-A — Mig 31 prep: rename slot label "#NV {order}" -> "Họ tên" user pin
UX polish trong ApprovalWorkflowsV2Page Designer slot label hiển thị tên user pin
(lookup từ usersList query) thay vì số thứ tự "NV #{ei + 1}". Fallback "Chưa chọn NV"
khi slot chưa pick user.

Plan K pre-Mig 31 first chunk. Mig 31 sẽ thêm AllowApproverSkipToFinal 7th checkbox
inline panel (K3 chunk sau).

Per bro decision S23 t1: "Chỗ quy trình duyệt #NV 1 - Họ tên luôn".

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:53:17 +07:00
b04a11a62f [CLAUDE] FE-PE: S22+5 Chunk B — Designer checkbox +AllowApproverEditBudget per slot + Section read flag (mirror 2 app)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m23s
FE Admin Designer (ApprovalWorkflowsV2Page.tsx):
- LevelDto + EditLevelEntry +allowApproverEditBudget
- copyFromDefinition + makeDefaultLevelEntry propagate default false
- POST body include allowApproverEditBudget cho mỗi Level slot
- NEW checkbox UI per Level inline panel:
  "Cho phép chỉnh sửa Section ngân sách lúc đang duyệt"
  (col-span-2, mirror pattern allowApproverEditDetails Mig 29)

FE Types mirror 2 app:
- fe-admin + fe-user `ApprovalWorkflowOptions` +allowApproverEditBudget

FE BudgetAdjustSection refactor (mirror 2 app):
- Trước: isApproverChoDuyet = phase ChoDuyet + actor in approvers
- Sau: isApproverChoDuyet = phase ChoDuyet + actor in approvers
  + currentLevelOptions.allowApproverEditBudget=true (per slot opt-in)
- Drafter scope Nháp/Trả lại unchanged
- Admin bypass unchanged

UX impact:
- Admin Designer phải tick checkbox cho NV slot mới được edit ngân sách lúc duyệt
- Nếu KHÔNG tick → button "Điều chỉnh" trong Section 5 KHÔNG hiện cho approver
- Drafter vẫn edit bình thường khi phiếu Nháp/Trả lại

Verify:
- npm run build fe-admin — 569ms pass
- npm run build fe-user — 528ms pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 23:12:43 +07:00
30d51c89bb [CLAUDE] FE-PE: S22+4 Chunk B — Attachment preview dialog + View button + Section "Điều chỉnh ngân sách" (mirror 2 app)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m26s
Feature 1 (FE attachment preview):
- NEW component `AttachmentPreviewDialog.tsx` (shared 2 app):
  * Fetch BE `/view` endpoint as blob → object URL (bearer auth qua axios)
  * Render iframe (PDF) hoặc img (image) trong Dialog size=lg
  * Helper `isPreviewable(fileName)` check ext PDF/PNG/JPG/JPEG/WEBP/GIF
- Update `SupplierAttachmentsCell` (per-NCC quote files):
  * Click filename KHÔNG còn trigger download — chuyển sang explicit buttons
  * Eye violet button "Xem trước" khi previewable
  * Download brand button cạnh bên (always visible)
- Update `GeneralAttachmentsSection` (bảng so sánh general):
  * Same pattern: Eye + Download split buttons
- Word/Excel (.doc/.docx/.xls/.xlsx) → download-only (UAT users mở local Office)
- Mirror fe-admin + fe-user (rule §3.9)

Feature 2 (Section "Điều chỉnh ngân sách"):
- NEW component `BudgetAdjustSection` in PeDetailTabs (mirror 2 app)
- Section 5 cuối Detail view sau "4. Ý kiến cấp duyệt"
- canAdjust 3 scope:
  * Admin → bypass
  * Drafter của phiếu + Phase DangSoanThao/TraLai
  * Approver currentLevel (match approvalFlow.approvers) + Phase ChoDuyet
- 2 mode edit: Select Budget link OR Manual amount + name
- Banner amber khi Approver điều chỉnh trong duyệt (audit notice)
- Save → PATCH /api/purchase-evaluations/{id}/budget-adjust (Chunk A BE)
- History display defer S22+5 (changelogs fetch separate endpoint, không có
  trong PeDetailBundle — UAT user xem Panel 3 "Lịch sử thay đổi")

Verify:
- npm run build fe-admin — 577ms pass
- npm run build fe-user — 550ms pass
- dotnet test SolutionErp.slnx — 104/104 PASS regression-free

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:32:56 +07:00
40f64c6b32 [CLAUDE] PE-Workflow: UAT S22+1 — disable cả 3 button khi không quyền + BE guard
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m29s
User UAT feedback: "Nếu đã không được quyền thao tác thì ko được quyền thao tác
hết tất cả các hành động" — trước đây chỉ "Duyệt" disabled, "Trả lại" + "Từ chối"
vẫn enabled (design intent S17 cũ).

FE 2 app mirror (PeWorkflowPanel.tsx):
- `isDisabled = blockedByV2Level` (drop `isForwardApprove &&` qualifier)
- Tooltip update "mới thao tác được (Duyệt / Trả lại / Từ chối)"
- Comment refresh ghi UAT S22+1 spec + cross-ref BE EnsureCanRejectV2Async

BE defense-in-depth (PurchaseEvaluationWorkflowService.cs):
- Helper mới `EnsureCanRejectV2Async` mirror FE actorInV2Level logic:
  Skip silent khi admin/V1/non-ChoDuyet/no actor/no pointer. Throw
  ForbiddenException khi V2 + ChoDuyet + actor != currentLevel.ApproverUserId.
- Invoke ở top Reject branch (cover cả TuChoi + Trả lại sub-branches).
- Chặn request forge: non-approver gọi PATCH /transitions direct sẽ 403.

Test (test-before §7 — security guard critical algorithm):
- ReturnMode tests existing 7/7 vẫn PASS (a2.Id = currentLevel approver, guard accept)
- +1 NEW test `Reject_NonApprover_V2_Throws_ForbiddenException` — outsider
  Drafter role gọi Reject phiếu V2 → throw + Phase không mutate

Verify:
- dotnet test SolutionErp.slnx — 104/104 PASS (+1 guard regression)
  Δ: 103 → 104
- npm run build × 2 app — pass (482ms + 583ms)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 21:46:51 +07:00
60efeeda63 [CLAUDE] Users: Plan D — F2 toggle AllowDrafterSkipToFinal per-user (Mig 29 wire UI)
BE: UserDto +AllowDrafterSkipToFinal + SetUserAllowDrafterSkipToFinalCommand
+ Handler + UsersController PATCH /api/users/{id}/allow-skip-final body
{allowDrafterSkipToFinal:bool} Policy=Users.Update.

FE Admin: User type +allowDrafterSkipToFinal. UsersPage column "Skip cuối"
violet FastForward badge + action button toggle mirror bypass-review pattern.

fe-user KHÔNG mirror (UsersPage admin-only).

Verify:
- dotnet build SolutionErp.slnx — 0 err, 2 warning DocxRenderer pre-existing
- npm run build fe-admin — pass 638ms

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 21:03:27 +07:00
5ccb2a7057 [CLAUDE] FE-PE: S21 t5 Chunk C — eOffice read currentLevelOptions + drafterAllowSkipToFinal (per-NV) mirror 2 app
Types refactor `fe-{admin,user}/src/types/purchaseEvaluation.ts`:
- `ApprovalWorkflowOptions` REMOVE allowDrafterSkipToFinal (F2 đã move per-User).
  Còn 5 flag (F1 4 mode + F3 EditDetails).
- `PeDetailBundle`:
  - RENAME `workflowOptions` → `currentLevelOptions` (clearer semantic per-slot)
  - ADD `drafterAllowSkipToFinal: boolean` (BE resolve từ DrafterUserId → User entity)

PeWorkflowPanel.tsx (mirror 2 app):
- RENAME local var `wfOptions` → `levelOptions`
- READ `evaluation.currentLevelOptions` (Cấp hiện tại)
- 4 mode radio render conditional theo levelOptions.allowReturnXxx (unchanged
  logic, just rename source)

PeDetailTabs.tsx (mirror 2 app):
- F3 approverEditMode: READ `evaluation.currentLevelOptions?.allowApproverEditDetails`
  thay vì workflowOptions.allowApproverEditDetails (semantic per-NV slot)
- F2 allowSkipToFinal: READ `evaluation.drafterAllowSkipToFinal` thay vì
  workflowOptions.allowDrafterSkipToFinal (semantic per-Drafter user)

Backward compat verified:
- Phiếu cũ trước Mig 29 vẫn return currentLevelOptions populated (BE backfill
  Mig 29 đã copy 5 Allow* per Level)
- drafterAllowSkipToFinal: BE backfill chỉ TRUE cho user từng Drafter PE link
  workflow.AllowDrafterSkipToFinal=true (preserve admin config S21 t4)
- Phiếu V1 legacy: currentLevelOptions=null → FE fallback chỉ Drafter mode

Verify:
- npm run build × 2 app pass (fe-user 450ms + fe-admin 439ms, cache hot)
- 0 TS6 err, warning chunk size pre-existing

Pending Chunk D: Docs (schema-diagram §14 update + STATUS + HANDOFF + session log).
Note: User Management page chưa có F2 checkbox UX (defer commit sau khi admin
UAT request — BE field đã có, FE chỉ cần thêm 1 toggle vào UserEdit dialog).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 20:09:31 +07:00
63234b2cce [CLAUDE] FE-Admin: S21 t5 Chunk B — Designer move 5 checkbox xuống per-Level slot
ApprovalWorkflowsV2Page.tsx refactor Designer modal theo Mig 29 per-NV:

Types update:
- `LevelDto` +5 Allow* (mirror BE AwLevelDto)
- `DefinitionDto` REMOVE 6 workflow-level Allow* (no longer used)
- `EditLevelEntry` +5 Allow* (form state per slot entry)
- `makeDefaultLevelEntry(order, userId)` helper — 4 false + AllowReturnToDrafter
  true (S17 backward compat)
- `copyFromDefinition` propagate 5 Allow* từ existing Levels

Form state:
- REMOVE 6 useState workflow-level (allowReturnOneLevel...allowApproverEditDetails)
- POST body remove 6 workflow-level field
- POST body levels[].* propagate 5 Allow* per slot

UI refactor:
- REMOVE entire section "Cấu hình nâng cao" workflow-level (amber bg 6 checkbox)
- REPLACE với info banner violet ngắn "ⓘ Cấu hình quyền duyệt riêng cho từng NV
  ở mỗi Cấp dưới đây. F2 cấu hình ở User Management."
- Mỗi Level entry (NV row) ADD inline panel amber-50/30 5 checkbox grid-cols-2:
  - Trả về 1 Cấp trước
  - Trả về 1 Bước trước
  - Trả về Người chỉ định
  - Trả về Drafter (mặc định checked)
  - Cho phép chỉnh sửa Section 2 (col-span-2, full row)
- Header "Quyền duyệt NV #N" [10px] uppercase amber-700
- `updateField()` helper inline update per entry index

F2 (AllowDrafterSkipToFinal) cần UX riêng ở User Management page (per-Drafter
user global). Defer Chunk B Plus hoặc commit sau khi user UAT request.

Verify:
- npm run build fe-admin pass 498ms cached
- 0 TS6 err, warning chunk size pre-existing

Pending Chunk C: FE eOffice (PeWorkflowPanel + PeDetailTabs) read
`evaluation.currentLevelOptions` + `evaluation.drafterAllowSkipToFinal` thay vì
`workflowOptions`. Mirror 2 app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 20:07:10 +07:00
d27caafcf5 [CLAUDE] FE-PE: Chunk D — eOffice Trả lại modes + Skip CEO + Approver edit Section 2 (F1+F2+F3) mirror 2 app
Types (fe-{admin,user}/src/types/purchaseEvaluation.ts):
- ApprovalWorkflowOptions type (6 boolean Allow* flag)
- WorkflowReturnMode const-object {OneLevel,OneStep,Assignee,Drafter}
- PeDetailBundle +workflowOptions field (null nếu V1 legacy)

PeWorkflowPanel.tsx F1 (mirror 2 app):
- State returnMode + returnTargetUserId thêm vào transition mutation payload
- Dialog Trả lại render radio list 1-4 mode enabled theo workflowOptions:
  • Trả về 1 Cấp trước (lùi pointer trong cùng Bước, peer review)
  • Trả về 1 Bước trước (Cấp cuối Bước trước nhận lại)
  • Trả về Người chỉ định (pick từ dropdown NV đã ký levelOpinions)
  • Trả về Người soạn thảo (default Drafter S17 fallback)
- Banner amber rounded box dưới radio list mô tả hành vi mode chọn
- onSuccess reset returnMode về Drafter + returnTargetUserId null

PeDetailTabs.tsx F2 (mirror 2 app):
- State skipToFinal + allowSkipToFinal (từ workflowOptions)
- submitForApproval mutationFn accept opts.skipToFinal → POST body
- Workspace action bar: thêm checkbox violet "Gửi thẳng Cấp cuối (skip trung gian)"
  hiển thị conditional theo allowSkipToFinal + canSubmitForApproval
- Confirm dialog message dynamic: "Gửi thẳng" warning vs default tuần tự
- Button label dynamic: "Lưu & Gửi thẳng CẤP CUỐI →" vs "Lưu & Gửi Duyệt →"

PeDetailTabs.tsx F3 (mirror 2 app):
- useAuth import + compute approverEditMode (phase=ChoDuyet +
  workflow.AllowApproverEditDetails + actor match currentApproval.approvers)
- itemsReadOnly = readOnly && !approverEditMode → ItemsTab nhận
- Banner violet "ⓘ Bạn được phép chỉnh sửa Hạng mục/NCC/Báo giá" khi
  approverEditMode + readOnly (Duyệt menu) — UX nhắc về quyền extended

InfoTab + NccSelectorRow + BudgetFieldRow GIỮ strict isEditablePhase (KHÔNG
trong F3 scope — Header section + Section 3 winner KHÔNG cho Approver edit).

Verify:
- npm run build × 2 app pass (fe-user 7.52s, fe-admin 499ms cached)
- 0 TS6 err, warning chunk size pre-existing
- BE Chunk B đã accept skipToFinal + returnMode + returnTargetUserId trong
  TransitionPurchaseEvaluationCommand → wire E2E complete

Pending Chunk E: Docs schema-diagram §14 update + STATUS + HANDOFF + session log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 19:08:08 +07:00
a508564b45 [CLAUDE] FE-Admin: Chunk C — ApprovalWorkflowDesigner section "Cấu hình nâng cao" 6 checkbox (F1+F2+F3)
Thêm section "Cấu hình nâng cao" trong Designer modal (giữa Description và
Steps), 3 sub-group 6 checkbox per workflow version:

1. Mode Trả lại (Approver chọn khi nhấn ← Trả lại):
   - Trả về 1 Cấp trước (peer review chain trong cùng Bước)
   - Trả về 1 Bước trước (Cấp cuối Bước trước nhận lại)
   - Trả về Người chỉ định (pick runtime từ list NV đã duyệt)
   - Trả về Người soạn thảo (default checked = backward compat S17)

2. Drafter gửi duyệt:
   - Cho phép Drafter gửi thẳng Cấp cuối (F2 skip mọi Bước/Cấp trung gian)

3. Approver chỉnh sửa phiếu:
   - Cho phép Approver chỉnh sửa Section 2 Hạng mục/NCC/Báo giá (F3, giữ Cấp)

DTO types update:
- DefinitionDto +6 boolean field (mirror BE AwDefinitionDto)
- 6 useState cho 6 flag, default từ cloneFrom (giữ config version trước) hoặc
  S17 backward compat (chỉ AllowReturnToDrafter=true)
- POST body extend 6 field gửi BE

Styling:
- Container amber-50/30 + border amber-200 (visual distinction với Steps section)
- Mỗi checkbox: card border-slate-200 bg-white, hover bg-amber-50/40
- Helper text [10px] text-slate-500 dưới label giải thích mode
- Headers [11px] uppercase text-slate-500 group sub-section

fe-user KHÔNG mirror — ApprovalWorkflowsV2Page admin-only. PeWorkspaceCreateView
chỉ filter IsUserSelectable, không cần Allow* flag lúc create phiếu.

Verify:
- npm run build fe-admin pass (8.72s, 0 TS6 err)
- Warning chunk size pre-existing

Pending Chunk D: FE eOffice (Trả lại modal dropdown + Skip submit + Edit
Section 2 enable conditional theo workflow.options) mirror 2 app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:59:45 +07:00
4b29d00716 [CLAUDE] FE-PE: Chunk B — Fix button "Trả lại" gửi decision=Approve thay vì Reject (gotcha #45) mirror 2 app
Bug pattern: button "← Trả lại" trong PeWorkflowPanel.tsx hiển thị đúng label
(L205-207 isSendBack include TraLai) NHƯNG payload `isReject` (L64-66) thiếu
nhánh TraLai → gửi decision=1 (Approve) thay vì 2 (Reject) khi target=TraLai
(98). BE Service vào APPROVE STEP → ApproveV2Async UPSERT opinion "đã duyệt"
+ advance Cấp tiếp theo. User UAT thấy: "Trả về nhưng hệ thống vẫn duyệt".

Inconsistency thứ 2: dialog `isSendBack` (L247-248) cũng thiếu nhánh TraLai
→ dialog title fallback `✓ Duyệt → Trả lại` + KHÔNG hiển thị amber warning.

Fix 3 chỗ × 2 app (fe-user + fe-admin, rule §3.9 mirror):
1. `isReject` payload — thêm nhánh `target=TraLai && phase!=TraLai`
2. dialog `isSendBack` — thêm nhánh TraLai + guard phase != TraLai
3. Comments document context bug + cross-ref BE guard Chunk A

Sync với BE guard (Chunk A `de00887` `PurchaseEvaluationWorkflowService.cs`):
- BE throw ConflictException khi target ∈ {TraLai, TuChoi} && decision != Reject
- 2 phía cùng đúng → no payload mismatch

Verify:
- npm run build × 2 app pass (fe-user 17.91s, fe-admin 6.71s, 0 TS6 err)
- Warning chunk size pre-existing (NOT introduced)

Pending Chunk C: docs gotcha #45 + STATUS + HANDOFF + session log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:43:20 +07:00
6e338f745e [CLAUDE] FE: Responsive cho laptop màn hình nhỏ — sidebar slim + Section/HangMucCard padding tighter + workspace 2-panel breakpoint
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m13s
User Session 20 turn 11: "giao diện hiện tại chưa đáp ứng tốt cho laptop màn
hình nhỏ -> Căn chỉnh lại nhé."

Pain points trên laptop 1280-1366px viewport (fe-user content panel
1280-288=992px hoặc fe-admin workspace 2-panel content còn ~672px):
- Sidebar 288px chiếm nhiều
- PE Workspace list panel 320px = thừa
- Section padding px-5 + HangMucCard p-3 cộng gộp tốn ~16-20px mỗi cell
- Inline NCC table 7 cột bị compressed

FE-only mirror fe-admin + fe-user. Targeted fixes (no breaking change):

### 1. Sidebar slim — w-60 (240px) lg/xl giảm xuống → bump xl:w-72
- fe-admin Layout aside: `w-72` → `w-60 xl:w-72`
- fe-user Layout aside: `w-72` → `w-60 xl:w-72`
- Trên màn ≥xl (1280px+) → giữ 288px như cũ
- Trên màn <xl (laptop nhỏ) → 240px, save 48px cho content

### 2. PE Workspace 2-panel list breakpoint
- `lg:grid-cols-[320px_1fr]` → `lg:grid-cols-[260px_1fr] xl:grid-cols-[320px_1fr]`
- Trên lg (1024-1279): list 260px (đủ pick) → content +60px
- Trên xl+ (1280+): list 320px như cũ
- Save total: ~60px cho NCC table render

### 3. Section + HangMucCard padding responsive
- Section component `<section className="px-5 py-4">`
  → `px-3 py-3 sm:px-5 sm:py-4` (xs/sm: tighter 12px each side, save 16px width)
- HangMucCard header `flex items-start gap-3 ... p-3`
  → `flex flex-wrap items-start gap-2 ... p-2 sm:gap-3 sm:p-3`
  → +flex-wrap: stat "Số tiền NS" wrap xuống dòng khi container hẹp
- HangMucCard expand panel `p-3` → `p-2 sm:p-3`

Net width gain trên laptop 1366px (typical):
- Sidebar slim: +48px
- Workspace list: +60px
- Section padding: +16px
- HangMucCard padding: +8px
Total ~+132px cho NCC table area render comfortable thêm 1-2 cột

KHÔNG đụng dialog widths, schema BE, hoặc semantic breakpoints lớn.

Verify:
- npm run build × fe-admin pass
- npm run build × fe-user pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 12:20:01 +07:00
66551db4d8 [CLAUDE] FE-PE: AddSupplierDialog auto-fill từ master khi chọn NCC
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m0s
User Session 20 turn 10: chọn NCC từ dropdown master → auto-load các field
đã có sẵn (contactPerson/phone/email/note) vào form, đỡ phải nhập tay lại.

FE-only mirror fe-admin + fe-user.

AddSupplierDialog dropdown "NCC (master)" onChange:
  - Lookup suppliers.data find(s => s.id === selectedId) → master row
  - setForm prev → ghi đè 4 field:
    * contactName ← picked.contactPerson ?? ''
    * contactPhone ← picked.phone ?? ''
    * contactEmail ← picked.email ?? ''
    * note ← picked.note ?? ''
  - KHÔNG đụng displayName / paymentTermText / thanhTien (manual cho user)
  - Hint "✓ Đã tự điền từ Master — bạn có thể sửa lại nếu cần." text-[10px]
    text-emerald-600 dưới dropdown khi đã chọn supplier

Mapping master Supplier → PE.Supplier fields (skip address vì không có
field tương ứng — có thể nhét vào note nếu user cần, manual).

User vẫn override các field auto-fill được sau đó (input bình thường).
Đổi supplier giữa lúc đã chỉnh tay → re-fill từ master mới (mặc định ghi đè).

Verify:
- npm run build × fe-admin pass
- npm run build × fe-user pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 12:03:33 +07:00
83aae8ea64 [CLAUDE] FE-PE: Winner NCC revert badge → icon ✓ đậm + hover transition
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m50s
User Session 20 turn 9: bỏ badge "🏆 Trúng thầu" emerald-600 rounded-full
text-white, revert về icon stick ✓ prefix như cũ nhưng:
- Đậm hơn: text-base font-bold text-emerald-700
- Tên NCC khi winner: text-emerald-900 đậm
- Row hover transition: hover:bg-emerald-200/70 (winner) +
  hover:bg-white/80 hover:shadow-sm (non-winner palette)
- Transition smooth qua `transition` class

NCC row visual khi winner:
  - border-l-emerald-500 (giữ)
  - bg-emerald-100/70 (giữ)
  - font-semibold + shadow-sm + ring-1 ring-inset ring-emerald-300 (giữ)
  - +hover:bg-emerald-200/70 (mới — sáng lên hover)
  - Prefix ✓ text-base font-bold emerald-700 (đậm hơn icon cũ)
  - Tên NCC text-emerald-900 (đậm hơn slate-900 mặc định)

NCC row non-winner:
  - palette cycle (blue/purple/sky/teal/pink) giữ
  - +hover:bg-white/80 hover:shadow-sm (mới — nổi nhẹ khi hover)

Mirror fe-admin + fe-user.

Verify:
- npm run build × fe-admin pass
- npm run build × fe-user pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:57:51 +07:00
3ec7b5a1b0 [CLAUDE] FE-PE: AddSupplier +Số tiền inline + NCC 5-màu palette + Winner 🏆 nổi bật
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m8s
User Session 20 turn 8 yêu cầu chuỗi UX NCC grid:
1. Thêm NCC dialog cho nhập luôn Số tiền báo giá cho hạng mục
2. Số tiền hiện ra cột so sánh hạng mục (đã có sẵn cột "Số tiền")
3. Trang trí 3+ NCC khác nhau 3+ màu khác nhau
4. NCC được chọn (winner) nổi bật hơn

FE-only mirror fe-admin + fe-user.

### AddSupplierDialog — sequential POST tạo NCC + Quote
- Thêm prop `detailId?: string` (truyền từ HangMucCard call site)
- Form state +`thanhTien: 0`
- showQuote = !!detailId — chỉ render input "Số tiền báo giá" khi gọi từ
  HangMucCard (call site khác giữ behavior cũ tạo NCC only)
- Mutation 2 step:
  1. POST /purchase-evaluations/{id}/suppliers → response {id} (BE controller
     PurchaseEvaluationsController.AddSupplier trả Ok(new {id = newId}))
  2. Nếu detailId + thanhTien > 0 → POST /quotes với purchaseEvaluationDetailId
     + purchaseEvaluationSupplierId (newSupplierRowId) + thanhTien
- Toast: "Đã thêm NCC + báo giá" (có quote) hoặc "Đã thêm NCC" (no quote)
- Section input "Số tiền" trong card brand-50/40 + VND format suffix đ + hint
  "Để trống / 0 → chỉ tạo NCC, chưa báo giá. Sửa lại sau bằng cách click số
  tiền trong bảng."
- HangMucCard pass detailId={detail.id} khi mount AddSupplierDialog

### NCC row 5-màu cycle palette
- NEW const NCC_PALETTES (literal Tailwind class strings để JIT scan):
  blue / purple / sky / teal / pink (border-l-4 colored + bg subtle 50/40)
- Loop ev.suppliers.map((s, idx) → palette = NCC_PALETTES[idx % 5]
- Tr className: `align-top border-l-4` + palette (non-winner) hoặc winner
  override

### Winner highlight nổi bật
- Tr non-winner: cycle palette (5 màu)
- Tr winner override:
  - border-l-emerald-500 (thay vì palette stripe)
  - bg-emerald-100/70 (đậm hơn 50/60 cũ)
  - font-semibold + shadow-sm
  - ring-1 ring-inset ring-emerald-300 (viền trong cho ô nổi)
- NCC name cell: badge inline-flex rounded-full bg-emerald-600 text-white
  text-[9px] font-bold uppercase "🏆 Trúng thầu" (thay icon ✓ cũ)
- Note text bumped lên text-amber-700 (chút đậm hơn 600 cũ cho visible khi
  winner bg đậm hơn)

KHÔNG đụng schema BE. 2 endpoint sẵn (POST /suppliers + POST /quotes) chain.

Verify:
- npm run build × fe-admin pass
- npm run build × fe-user pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:53:32 +07:00
059bfcbe38 [CLAUDE] FE-Admin+Domain: Chunk C — MenuVisibilityPage + menu key + seed
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Session 20 turn 7 Chunk C. FE Admin page quản lý Ẩn/Hiện + Đổi tên menu
cho fe-user (eOffice). Admin sidebar fe-admin LUÔN dùng Tên gốc — page này
KHÔNG đụng admin navigation (user Q2=b).

Domain MenuKeys.cs:
  +const MenuVisibility = "MenuVisibility"
  All[] thêm MenuVisibility (giữa Permissions + Workflows)

DbInitializer SeedMenuTreeAsync:
  +leaf (MenuVisibility, "Menu eOffice", System, 94, "Eye")
  Workflows shift Order 94 → 95
  Idempotent — chỉ INSERT nếu chưa có trong DB
  Manual seed Mig 27 LocalDB Dev: INSERT MenuItems + Permissions cho Admin role

FE Admin:
  - types/menu.ts: MenuItem/MenuNode +isVisible bool +displayLabel string|null
  - lib/menuKeys.ts: +MenuVisibility const
  - components/Layout.tsx resolver +MenuVisibility → /system/menu-visibility
  - App.tsx +Route + import MenuVisibilityPage

NEW pages/system/MenuVisibilityPage.tsx (~210 LOC):
  - PageHeader + 4 StatCard (Tổng / Hiển thị / Đã ẩn / Đã đổi tên)
  - Search input (key | label | displayLabel)
  - Table: Key (mono + parentKey ↳) | Tên gốc | Input "Tên hiển thị" inline
    (placeholder "Mặc định: ...") | Toggle Hiển thị/Ẩn (emerald/amber) |
    Lưu (khi dirty) / Khôi phục (khi đã custom)
  - PATCH /menus/{key} body { isVisible, displayLabel } — trim whitespace,
    empty string → null
  - onSuccess: invalidate ['menus', 'all'] + ['my-menu'] + clear draft entry
  - "Khôi phục mặc định" button: PATCH isVisible=true, displayLabel=null
  - Footer hint: nhắc admin sidebar luôn dùng Tên gốc, đổi tên áp eOffice

Verify:
- npm run build × fe-admin pass

Pending Chunk D: FE Layout fe-user filter !isVisible + render displayLabel
Pending Chunk E: Docs S20 turn 7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:37:47 +07:00
f568945069 [CLAUDE] FE-PE: Manual budget "Nhập tay" — drop Tên field, format VND
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m12s
User Session 20 turn 6 screenshot: chế độ "Nhập tay (không link)" Section 2
b. Ngân sách vẫn còn input "Tên (vd Tạm tính T11/2025)" cùng số tiền. User
chỉ cần nhập số tiền — bỏ Tên + áp VND format consistent.

3 file × 2 app = 6 file FE update:
  - PeDetailTabs.tsx BudgetFieldRow (Section 2 detail editor)
  - PeWorkspaceCreateView.tsx (workspace mode "new")
  - PeHeaderForm.tsx (Create/Edit header page)

Mỗi file:
  - Drop Input "Tên ngân sách" UI khỏi manual mode (state field giữ '' để
    backward compat — BE save luôn null)
  - Manual mode UI giờ chỉ 1 input số tiền (max-w-xs):
    * type="text" inputMode="numeric" + value={formatVndInput(amount)}
    * onChange={parseVnd} strip non-digit → number
    * Suffix "đ" tuyệt đối inset-y-0 right-3
    * Hint "VND — nhập số, tự format dấu chấm ngàn (vd 1.000.000)"
  - Helpers parseVnd + formatVndInput inline mỗi file (mirror PeDetailTabs)

PeDetailTabs BudgetFieldRow cleanup:
  - Drop state manualName + setManualName
  - Drop manualName từ dirty check
  - Save payload: budgetManualName: null luôn (không phụ thuộc state)
  - Hủy thay đổi: drop reset manualName line

Read-only display (legacy data) giữ ev.budgetManualName nếu data cũ có tên
(đoạn render khi !canEdit) — không xóa hiển thị, chỉ ẩn input UI.

BE schema KHÔNG đụng — endpoint PUT /pe/:id vẫn nhận budgetManualName field,
chỉ FE luôn gửi null.

Verify:
- npm run build × fe-admin pass
- npm run build × fe-user pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:12:43 +07:00
169459e66f [CLAUDE] FE-PE: NCC cell button visual + Hạng mục header gộp 1 ô Ngân sách + DetailDialog rút gọn
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m0s
User Session 20 turn 5: 2 yêu cầu UX rõ ràng chỗ nhập tiền.

FE-only mirror fe-admin + fe-user.

1. NCC grid cell "Số tiền" → button visual rõ ràng cho user biết là chỗ nhập:
   - Trước: <td onClick> trông như text cell (chỉ hover bg → user không
     biết click được)
   - Sau: <button> trong td:
     * Empty (chưa nhập): border-dashed border-slate-300 bg-slate-50
       text-slate-400 + label "+ Nhập số tiền" + hover brand
     * Filled: border-solid border-slate-300 bg-white font-semibold + số tiền
       + suffix " đ" + hover brand
     * Winner: border-emerald-300 bg-emerald-50 text-emerald-700
   - Read-only mode: hiển thị <div> số tiền (không button)
   - Drop `cellHover` var không còn dùng

2. Hạng mục header gộp 3 stat (KL / ĐG ngân sách / Thành tiền NS) → 1 ô
   "Số tiền ngân sách" lớn hơn (text-base font-semibold + suffix đ):
   - Trước: 3 columns hiển thị KL + ĐG + TT (kỹ thuật, user không cần thấy)
   - Sau: 1 column "Số tiền ngân sách: X đ" — duy nhất số quan trọng
   - "NS link Δ" comparison column giữ (nếu có Budget link FYI)

3. DetailDialog rút gọn 11 input → 3 input chính:
   - Trước: groupCode + groupName + itemCode + noiDung + donViTinh +
     KL ngân sách + KL thi công + đơn giá + thành tiền auto + ghi chú (10
     input grid 3 cols)
   - Sau: Tên hạng mục (noiDung) + Số tiền ngân sách (VND format + suffix
     đ + hint) + Ghi chú (3 input vertical)
   - Helper setBudgetAmount: user nhập 1 số → set cả donGia + thanhTien
     (KL=1 ngầm). BE giữ schema 3 field backward compat.
   - Form state default đổi: groupCode="01" groupName="Hạng mục chính"
     donViTinh="gói" KL=1 (consistent với Chunk A BE seed)
   - Drop updateAndRecalc helper (không còn auto-calc KL × ĐG)

KHÔNG đụng schema BE.

Verify:
- npm run build × fe-admin pass
- npm run build × fe-user pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:53:30 +07:00
17c5f14e20 [CLAUDE] FE-PE: NCC table SĐT+Email rõ ràng + validate format + Số tiền format VND
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m0s
User Session 20 turn 4: NCC info cơ bản (SĐT + Email), ràng buộc format,
input tiền VND format dấu chấm 1.000.000.

FE-only mirror fe-admin + fe-user.

1. Helpers cấu hình ở top file (gần fmtMoney):
   - parseVnd(s): strip non-digit → number (0 nếu rỗng)
   - formatVndInput(n): n.toLocaleString('vi-VN') hoặc '' khi n=0
   - PHONE_RE /^0\d{9,10}$/ — VN bắt đầu 0, 10-11 digits sau strip space/dash/dot
   - EMAIL_RE /^[^\s@]+@[^\s@]+\.[^\s@]+$/
   - isValidPhone(s) / isValidEmail(s) — empty OK (optional)

2. NCC inline table HangMucCard — bỏ cột "Liên hệ" tổng hợp (ContactName +
   Phone + Email gộp), thay bằng 2 cột riêng:
     Trước: NCC | Liên hệ | Điều khoản TT | File báo giá | Số tiền | Action
     Sau:   NCC | SĐT | Email | Điều khoản TT | File báo giá | Số tiền | Action
   SĐT cell font-mono (đọc số rõ); Email cell truncate + title hover full.

3. AddSupplierDialog + EditSupplierDialog — validate phone + email:
   - Input type="tel" / type="email" + inputMode + placeholder example
   - border-red-300 khi invalid + dòng error text [10px] mt-0.5 red-600
   - Disable nút Lưu/Thêm khi hasError = phoneError || emailError
   - ContactName + DisplayName + Note + PaymentTermText giữ (optional fields,
     backward compat — user nói "cơ bản thôi" áp cho display, dialog giữ
     đầy đủ field tránh churn schema)

4. QuoteDialog "Số tiền" input format VND:
   - type="text" inputMode="numeric" thay vì type="number" (để format dấu
     chấm ngàn không phá bởi browser number parser)
   - value={formatVndInput(form.thanhTien)} → display "1.000.000"
   - onChange={parseVnd(e.target.value)} → strip non-digit → state raw number
   - Suffix span "đ" tuyệt đối inset-y-0 right-3 pointer-events-none
   - Hint dưới input: "VND — nhập số, tự format dấu chấm ngàn (vd 1.000.000)"
   - Class font-mono text-right pr-12 (chừa chỗ suffix)

Verify:
- npm run build × fe-admin pass
- npm run build × fe-user pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:48:44 +07:00
e03314e2e7 [CLAUDE] FE-PE: NCC table 1 cột "Số tiền" + QuoteDialog 1 input đơn giản hóa
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m11s
User Session 20 turn 3: "Tạm thời chỉ cần nhập số tiền vào là đc, không cần
3 cột có VAT / ko VAT / tổng. 2 cột kia ẩn đi, chỉ 1 cột nhập tiền duy nhất."

FE-only mirror fe-admin + fe-user:

NCC inline table HangMucCard — bỏ 2 th + 2 td:
  Trước: NCC | Liên hệ | Điều khoản TT | File báo giá | ĐG chưa VAT | ĐG có VAT | Thành tiền | Action
  Sau:   NCC | Liên hệ | Điều khoản TT | File báo giá | Số tiền | Action

QuoteDialog — đơn giản hóa form:
  Trước: 3 input (Đơn giá chưa VAT / ĐG có VAT / Thành tiền auto-calc) + Ghi chú
         + display khoiLuong info
  Sau:   1 input "Số tiền" (autoFocus) — map thẳng vào thanhTien field
  Schema BE giữ nguyên (bgVat / chuaVat / note vẫn POST):
    - Row mới: bgVat=0, chuaVat=0, note=''
    - Existing: giữ giá trị cũ
  Bỏ prop khoiLuong (không dùng — không còn auto-calc thanhTien = chuaVat × khoiLuong)
  Bỏ updateAndRecalc helper

KHÔNG đụng schema BE — endpoint POST /purchase-evaluations/{id}/quotes giữ
nguyên payload shape, chỉ FE rút gọn input mặt người dùng nhập.

Verify:
- npm run build × fe-admin pass
- npm run build × fe-user pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:41:48 +07:00
c4ece8071f [CLAUDE] FE-PE: Section Ý kiến revise — ô vuông cards grid-cols-2 + counter Cấp đúng semantic
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m11s
User feedback Session 20 turn 2:
1. "Chỗ ý kiến vẫn hiển thị ô vuông như trước nhé" — revert visual về cards
   grid-cols-2 mirror S19 (Chunk C cũ dùng vertical list inline không phải
   ô vuông như trước).
2. "Số bước duyệt khác số người duyệt trong 1 bước, check lại" — counter cũ
   `{opinions.length}/{totalApprovers}` sai semantic vì OR-of-N (mỗi Cấp chỉ
   cần 1 NV ký, không cần ký tất cả NV). totalApprovers đếm tổng NV gây hiểu
   lầm.

Fix (FE-only mirror fe-admin + fe-user):
- StepOpinionsBox body chuyển từ `space-y-2` (vertical list) sang
  `grid grid-cols-1 md:grid-cols-2 gap-3` — mỗi opinion = 1 card đầy đủ
  border-emerald-200 + bg-white + p-3 (mirror visual S19 LevelOpinionBox).
- StepOpinionEntry restore styling đầy đủ:
  - Header: "Cấp N — Tên NV" font-semibold + admin override badge amber +
    "✓ Đã duyệt" emerald rounded-full badge
  - Body: comment text-sm
  - Footer: signedAt border-t separator (như S19)
- Counter mới: `{signedLevels}/{totalLevels} cấp đã duyệt · {totalApprovers}
  NV tham gia` — đếm Cấp distinct (Set unique levelOrder) thay vì count NV.
  Tooltip giải thích "OR-of-N" cho user hiểu.
- KHÔNG đụng schema Mig 26 (vẫn UPSERT 1 row / Level qua Service).

Verify:
- npm run build × fe-admin pass
- npm run build × fe-user pass
- Test pass mặc định skip (Q4 UAT iteration)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:24:07 +07:00
f2f01f4765 [CLAUDE] FE-PE: Chunk C — Section Ý kiến gộp đồng cấp cùng Phòng (1 box / Step)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m54s
Restructure Section 5 (rename Section 4 sau Chunk B) "Ý kiến cấp duyệt".
User Session 20 Q3=a: gộp các comment đồng cấp cùng Phòng → 1 ô / bước
(dù bước có nhiều người), CHỈ hiển thị comment của NV đã duyệt.

Trước (Mig 26 S19 LevelOpinionsSectionV2):
  forEach step → grid-cols-2 cho forEach Level × forEach Approver → 1 box / NV
  Hiển thị cả NV chưa duyệt với placeholder "— chưa duyệt"

Sau (Chunk C):
  forEach step → 1 StepOpinionsBox (đại diện Phòng)
  Box body: filter opinions có stepOrder == step.order
    → sort theo levelOrder asc, signedAt asc
    → render StepOpinionEntry per signed opinion
  NV chưa duyệt KHÔNG hiển thị
  Header box: "Bước N — Tên · {dept badge} · X/Y đã duyệt"

FE (mirror fe-admin + fe-user):
  - LevelOpinionsSectionV2 forEach step → StepOpinionsBox (replace grid-cols-2)
  - StepOpinionsBox: header phòng + body list signed opinions
  - StepOpinionEntry: tên NV + Cấp badge + Admin override badge nếu có
    + timestamp + comment
  - Drop LevelOpinionBox function (per-NV pattern bỏ)
  - KHÔNG đụng schema Mig 26 (PE Service ApproveV2Async UPSERT giữ 1 row /
    Level — chỉ FE re-group render)

Verify:
  - npm run build × fe-admin pass · fe-user pass
  - Test pass mặc định skip (Phase 9 UAT iteration, Q4 user public luôn)

Pending Chunk D: Docs S20 changelog + STATUS + HANDOFF

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:07:02 +07:00
2bba851135 [CLAUDE] FE-PE: Chunk B — NCC nested expand dưới Hạng mục, bỏ Section 4 riêng
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Restructure ItemsTab → nested cards (tầng 1 = hạng mục, tầng 2 = NCC tham
gia + báo giá inline). Replace bảng matrix grid (hạng mục × NCC) cũ — user
Session 20 yêu cầu UX nested cho 1 hạng mục demo.

FE (mirror fe-admin + fe-user):
  - ItemsTab giờ render list HangMucCard (1 card / 1 hạng mục)
  - HangMucCard mới: header info hạng mục + expand panel default OPEN
  - Expand panel: NCC inline table columns:
      NCC | Liên hệ | Điều khoản TT | File báo giá | ĐG chưa VAT | ĐG có VAT | Thành tiền | Action
  - Quote click cell → QuoteDialog cũ (reuse)
  - NCC button: + Thêm NCC (AddSupplierDialog) / ✏ Sửa (EditSupplierDialog) / ✓ Winner / 🗑 Xóa
  - Budget Δ compute per-card (KHÔNG tfoot tổng — 1 hạng mục)
  - Bỏ Section 4 "NCC tham gia" cũ trong main render — gộp vào Section 2
  - Drop function SuppliersTab (dead code) — giữ AddSupplierDialog +
    EditSupplierDialog + SupplierAttachmentsCell cho HangMucCard reuse

Section layout mới (4 section):
  1. Thông tin gói thầu
  2. Hạng mục + Báo giá NCC (nested)
  3. Chọn NCC / TP thắng thầu
  4. Ý kiến cấp duyệt

Verify:
  - npm run build × fe-admin pass (warning chunk size, không liên quan)
  - npm run build × fe-user pass
  - Test pass mặc định skip (Phase 9 UAT iteration, Q4 user public luôn)

Pending Chunk C: Section 5 (rename 4) gộp đồng cấp cùng Phòng — 1 box / Step
Pending Chunk D: Docs S20 changelog + STATUS + HANDOFF

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:04:49 +07:00
9dee00da01 [CLAUDE] PurchaseEvaluation: Chunk A — reorder section Hạng mục lên #2 + auto-tạo 1 row mặc định
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m20s
Session 20 UI restructure (3 yêu cầu user). Chunk A xử lý:

BE — CreatePurchaseEvaluationCommandHandler thêm 1 PurchaseEvaluationDetail
mặc định khi tạo phiếu mới:
  - GroupCode="01", GroupName="Hạng mục chính"
  - NoiDung = TenGoiThau (tên gói thầu)
  - DonGiaNganSach = ThanhTienNganSach = Budget.TongNganSach (nếu link)
    fallback BudgetManualAmount fallback 0
  - DonViTinh="gói", KL=1, Order=1
  - Changelog entry kèm theo (audit Insert Detail)

FE — Đổi thứ tự 5 section trong PeDetailTabs.tsx (mirror 2 app):
  1. Thông tin gói thầu (giữ)
  2. Hạng mục + Báo giá (chuyển từ #4 lên #2)
  3. Chọn NCC / TP (từ #2 xuống #3)
  4. NCC / TP tham gia (từ #3 xuống #4 — Chunk B sẽ gộp vào #2 nested)
  5. Ý kiến cấp duyệt (giữ)

Q1=a: Giữ Section "Chọn NCC TP thắng thầu" riêng (rõ UX).
Q2=a "1 hạng mục trước tiên": auto-seed đủ, multi-hạng-mục defer.

Verify:
- dotnet build SolutionErp.slnx — 0 warning / 0 error
- Test pass mặc định skip (Phase 9 UAT iteration, Q4 user public luôn)

Pending Chunk B: Nested grid Hạng mục → NCC expand inline edit
Pending Chunk C: Section 5 gộp đồng cấp cùng Phòng (1 box / Step)
Pending Chunk D: Docs S20 changelog + STATUS + HANDOFF

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:54:12 +07:00
6e913b37a1 [CLAUDE] FE-PE: Chunk C Section 5 V2 dynamic theo ApprovalWorkflowLevel
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 14m51s
Section 5 PeDetailTabs render dynamic theo workflow đã pin (V2). Thay
4 box CỨNG (PheDuyet/CCM/MuaHàng/SmPm Mig 15) cho phiếu V2.

Type: `PeLevelOpinion` (15 field) + `PeDetailBundle.levelOpinions[]`.

Section 5 conditional:
- evaluation.approvalWorkflowId set → <LevelOpinionsSectionV2/>
- V1 legacy (no awId) → <DepartmentOpinionsSection/> readOnly fallback (giữ data Mig 15)

LevelOpinionsSectionV2:
- Layout 5A — group theo Step (header "Bước N — <name>" + dept badge emerald)
- grid-cols-2 cho approvers trong tất cả Levels của Step
- Hint "(N người duyệt)" khi totalApprovers > 1
- Empty state khi flow null / 0 steps

LevelOpinionBox (read-only — Q1=1B sync auto từ Workflow Panel):
- Title "Cấp N — <ApproverFullName>"
- Badge amber "⚠ Admin <name> duyệt thay" khi SignedByUserId !== ApproverUserId
- Badge emerald "✓ Đã duyệt" khi opinion tồn tại
- Empty: "— chưa duyệt" italic gray
- Footer: timestamp signedAt format vi-VN

Workspace mode hint giữ amber "Ý kiến + chữ ký auto đồng bộ khi NV duyệt".

Mirror fe-admin + fe-user (rule §3.9).

Verify: npm run build × 2 pass · 0 TS error.

Chunk D kế tiếp: Docs (STATUS/HANDOFF/schema-diagram/session log).
2026-05-09 11:05:03 +07:00
873e7a1b7b [CLAUDE] FE-PE: Hành động button rút gọn label + 3 màu phân biệt + bold
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m19s
PeWorkflowPanel.tsx (fe-admin + fe-user mirror per §3.9):
- Label rút gọn: "✓ Duyệt" / "← Trả lại" / "✗ Từ chối"
  (bỏ "→ Chờ X" + "(về Drafter sửa)" + "Hủy /" verbose)
- Phase đích vẫn hiện qua tooltip title khi hover Duyệt
- 3 màu border + text phân biệt:
  - Duyệt   = emerald (xanh lá positive)
  - Trả lại = amber (vàng request changes, không terminal)
  - Từ chối = red (đỏ terminal negative)
- font-medium → font-bold (đậm hơn theo user request)

Verify: npm run build × 2 pass · 0 TS error.
2026-05-09 02:00:29 +07:00
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
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
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