Commit Graph

162 Commits

Author SHA1 Message Date
75df04ec82 [CLAUDE] Workflow: fix workflow picker 2 bug (P11-A Max re-review) + SetWorkflow endpoint
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m5s
Double-check chất lượng P11-A ở Max (agents trước chạy High + truncate 3×) →
phát hiện 2 bug THẬT trong workflow-picker FE của WorkflowAppDetailPage (core
approve/reject/return ĐÚNG, chỉ sub-flow chọn quy trình hỏng):

Bug #1 (HIGH) — pinWorkflow PUT /{id} chỉ gửi {approvalWorkflowId} → UpdateDraft
  validator (Reason NotEmpty, NumDays>0...) fail → 400. Nút "Lưu quy trình" vỡ.
Bug #2 (HIGH) — fetch workflow expect flat array nhưng endpoint trả
  AwAdminOverviewDto {types:[...]} → picker rỗng/crash. FE copy nhầm pattern hỏng
  của ProposalCreatePage thay vì PE/Contract proven.

Fix:
- BE: thêm endpoint chuyên dụng PUT /{id}/workflow + Set{Module}WorkflowCommand/Handler
  cho 4 module — chỉ set ApprovalWorkflowId trên draft Nhap/TraLai (verify ApplicableType
  per module), KHÔNG validate field khác. Single-responsibility, bulletproof.
- FE: sửa fetch mirror PE/Contract (data.types.find(t=>t.applicableType===X)?.history
  .filter(isUserSelectable)) + pin gọi endpoint mới. fe-admin+fe-user SHA256 identical.
- Test: +3 SetWorkflow (happy no-status-change / wrong ApplicableType Conflict / submitted
  guard) → 141→144 PASS.

Verify: BE build 0 error · 144 test PASS · FE build ×2 · SHA256 identical.
Bonus phát hiện: ProposalCreatePage (S37) có bug #2 có sẵn (latent, chưa exercise UAT)
  → flag spawn task riêng, KHÔNG fix trong commit này.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 10:14:34 +07:00
e7b66cd52b [CLAUDE] Workflow: wire ApproveV2 + LevelOpinions cho 4 WorkflowApps module (Phase 11 P11-A)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m6s
Wire full approval workflow V2 cho Leave/OT/Travel/Vehicle — cookie-cutter
mirror Proposal (Mig 38). Trước đây skeleton Phase 1 (Create+List), giờ
ApproveV2 advance-level + UPSERT LevelOpinion + atomic codegen.

Schema (Mig 41 WireWorkflowAppsApprovalV2 — 84→89 tables, pure additive):
- 4 bảng {Leave,Ot,Travel,Vehicle}LevelOpinions (UNIQUE composite + Cascade
  parent + Restrict Level — mirror ProposalLevelOpinion)
- 1 bảng WorkflowAppCodeSequences (shared atomic MaDonTu, Prefix-keyed)
- 4 cột RejectedFromStatus (smart return tracking)
- enum ApprovalWorkflowApplicableType.TravelRequest = 9

Application (LeaveOt + TravelVehicle ApprovalFeatures.cs — 30 handler):
- GetById detail (Include LevelOpinions + JOIN Step/Level) · UpdateDraft
- Submit (gen MaDonTu + DaGuiDuyet + level=1, verify ApplicableType per module)
- Approve (verify actor==ApproverUserId OR Admin, UPSERT opinion latest-write-wins,
  advance level OR terminal DaDuyet, empty comment → placeholder)
- Reject (→TuChoi) · Return (→TraLai + RejectedFromStatus)

Api: 4 controller +6 route mỗi cái (GET/{id}, PUT/{id}, submit/approve/reject/return)
Infra: DbInitializer seed 4 workflow V2 mẫu (QT-NP/OT/CT/XE-V2-001) → UAT test ngay
FE: WorkflowAppDetailPage.tsx declarative 4-kind (fe-admin+fe-user SHA256 identical)
  — workflow status + opinion timeline + action buttons; gỡ banner skeleton + row nav

Tests: +11 WorkflowAppApproveV2Tests (130→141 PASS) — state machine + UPSERT
  invariant + guards + codegen + forbidden + placeholder (Leave full + Ot smoke)

Verify: build 0 error · 141 test PASS · FE build ×2 · reviewer checklist
  (ApplicableType per-module + cross-module DbSet + [Authorize] — no copy-paste bug)
Known-minor (unreachable): Reject/Return actor-check skip nếu CurrentApprovalLevelOrder
  null — nhưng DaGuiDuyet luôn có set (defer hardening).
ItTicket KHÔNG đụng (kanban, no workflow V2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 09:44:00 +07:00
e54a22de0c [CLAUDE] Domain+App+Infra+Api+FE-Admin+FE-User: S38 G-O4+G-O5+G-O6+G-P1+G-H3 SKELETON full-stack
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m53s
Phase 10.3-10.4 SKELETON 5 plan combo finish — Mig 39+40 + BE skeleton 7 module +
FE 2 app SHA256 IDENTICAL + 11 menu key. UAT visible end-to-end.

⚠️ SKELETON Phase 1 trade-off rõ:
  - Status flat 5-state WorkflowAppStatus enum share Leave/OT/Travel/Vehicle
  - ApproveV2 workflow advance DEFER Phase 11 (Drafter Create OK, Approve flow chưa wire)
  - LevelOpinions per-module DEFER Phase 11
  - LeaveBalance calc + Auto-assign + SLA timer DEFER Phase 11
  - CodeGen atomic + MaDonTu/MaTicket gen DEFER Phase 11
  - Vehicle catalog + Driver catalog DEFER Phase 11 (free text VehicleLicense)
  - ItTicketComments thread DEFER Phase 11 (free text Resolution field)

Mig 39 (em main solo): 5 entity Workflow Apps schema
  - LeaveRequest (G-O4, FK LeaveType Hrm Mig 35, ApplicableType=5)
  - OtRequest (G-O4, FK OtPolicy optional, ApplicableType=6)
  - TravelRequest (G-O4, reuse ApplicableType=4 Proposal)
  - VehicleBooking (G-O5, free text vehicle, ApplicableType=7)
  - ItTicket (G-O6, NO workflow V2 — kanban status flow)

Mig 40 (em main solo): Attendance entity (G-P1)
  - GPS lat/long check-in/out + Source enum Web/Mobile/Device
  - UNIQUE composite (UserId, AttendanceDate)
  - WorkHours computed simple diff (NO OtPolicy multiplier yet)

BE CQRS (em main solo, single mega ~1100 LOC):
  - WorkflowAppsFeatures.cs 7 region (5 module Create+List + Attendance CheckIn/Out/GetMonth + HrDashboard)
  - 7 Controller: /api/leave-requests + /ot-requests + /travel-requests + /vehicle-bookings + /it-tickets + /attendances + /hr/dashboard
  - Class-level [Authorize] any authenticated
  - 13 endpoint total

FE 2 app (em main solo fallback gotcha #53 risk):
  - types/workflowApps.ts × 2 SHA256 IDENTICAL 77470e182a15de88 (all DTOs + Status badge)
  - WorkflowAppsListPage.tsx × 2 IDENTICAL 58139d0301a60ddf — generic declarative KIND_CONFIG handles 4 module (Leave/OT/Travel/Vehicle)
  - ItTicketsPage.tsx × 2 IDENTICAL d3062de2f54c794c — kanban 5 status column
  - MyAttendancePage.tsx × 2 IDENTICAL 86da48ae147db012 — GPS check-in/out + tháng calendar
  - HrmDashboardPage.tsx × 2 IDENTICAL d9c6c12a5a8694f8 — 4 KPI card + gender ratio + status breakdown
  - Pattern 16-bis 9× cumulative (App.tsx +4 routes + menuKeys +8 const + Layout staticMap +7 entry)
  - 7 amber banner "Skeleton Phase 1 — full feature Phase 11" rõ ràng UAT

Menu seed: +11 const + SeedMenuTreeAsync 8 row (Off_DonTu sub-group + 3 leaf + Off_DatXe + Off_ItTicket + Off_ChamCong + Hrm_Dashboard).
DbInitializer Sample workflow seed DEFER (workflows V2 already seeded từ S29+S37 reuse — admin clone tạo riêng per ApplicableType=5/6/7).

Verify:
- dotnet build PASS 0 error 2 pre-existing warning
- dotnet test 130/130 PASS baseline preserve
- npm build × 2 PASS clean
- SHA256 verify 5 file × 2 app all IDENTICAL

Plan G-* progress 11/11  (100% COMPLETE):
   G-H1 (S33) + G-O1 (S34) + G-H2 (S35) + G-O2 (S36) + G-O3 (S37) +
   G-O4 + G-O5 + G-O6 + G-P1 + G-H3 (S38 skeleton)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 16:19:42 +07:00
de1c378279 [CLAUDE] Domain+App+Infra+Api+FE-Admin+FE-User: S37 Mig 37 enum + Plan G-O3 Đề xuất full-stack
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m53s
Phase 10.3 G-O3 Đề xuất (Proposal) — Mig 37 enum extend +5 values + Mig 38
Proposal schema + BE CQRS 8 endpoint + FE 2 app SHA256 IDENTICAL.

Mig 37 (em main solo): extend ApprovalWorkflowApplicableType enum +5 values
  ProposalGeneral=4 / LeaveRequest=5 / OtRequest=6 / VehicleBooking=7 / ItTicket=8
  cookie-cutter Mig 22 pattern (Up/Down empty — enum mức Domain).

Mig 38 (em main solo): 4 entity Proposal (Code DX/YYYY/NNN) + ProposalAttachment
  + ProposalLevelOpinion (UNIQUE composite PEId+LevelId mirror PE Mig 26) +
  ProposalCodeSequence (Prefix PK atomic seq). 4 EF Config + 2 DbContext mod.

BE CQRS (em main solo ~700 LOC ProposalFeatures.cs sau Implementer truncate phase
exploration gotcha #53 5th + 529 Overload):
  - 4 Header handler (List paged + GetById detail + Create + UpdateDraft owner-OR-admin)
  - 4 Workflow handler (Submit gen MaDeXuat atomic + Approve UPSERT LevelOpinion advance + Reject + Return)
  - SERIALIZABLE transaction CodeGen
  - DTOs nested LevelOpinion với Step+Level metadata JOIN

ProposalsController 8 endpoint /api/proposals (List/GetById/Create/Update/Submit/Approve/Reject/Return)
class-level [Authorize] + handler-level owner-OR-admin guard.

DbInitializer: SeedSampleProposalWorkflowV2Async ~40 LOC seed QT-DX-V2-001 IsUserSelectable=true
NOT gated DemoSeed per gotcha #51. SeedMenuTreeAsync +4 row (Off_DeXuat sub-group + 3 leaf).

FE 2 app (em main solo + Implementer 529 fail fallback):
  - types/proposal.ts × 2 SHA256 IDENTICAL 95607052ff1138f2
  - ProposalsListPage.tsx × 2 IDENTICAL 603f0d9cf74cd09a — table 6 cột + Status badge + filter
  - ProposalCreatePage.tsx × 2 IDENTICAL 6aed3a76563dd576 — Form Header card
  - ProposalDetailPage.tsx × 2 IDENTICAL 3dc229ea8dcc9bc0 — 3 Section + WorkflowActions
  - Pattern 16-bis 8× cumulative (App.tsx + menuKeys + Layout staticMap 3 entry)

Verify:
- dotnet build PASS 0 error 2 warning pre-existing DocxRenderer
- dotnet test 130/130 PASS baseline preserve
- npm build × 2 PASS (fe-admin 14.72s + fe-user 6.40s)
- SHA256 verify 4 file × 2 app all IDENTICAL

Pattern reinforced cumulative S37:
- Pattern 12-bis cross-module mirror 11× (PE V2 → Proposal V2 ApproveV2)
- Pattern 16-bis 4-place mirror cross-app 8×
- gotcha #53 5th occurrence Implementer mid-exploration truncation + 529 Overload 1× — em main solo fallback proven

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 15:51:14 +07:00
f45090b654 [CLAUDE] Domain+App+Infra+Api+FE-Admin+FE-User: S36 Plan G-O2 Phòng họp Mig 36 + BE CRUD + FE 2 app
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m55s
Phase 10.2 G-O2 Phòng họp BookingCalendar — Mig 36 schema + BE CQRS + FE 2 app
mirror cookie-cutter G-H2 HrmConfig pattern. Standalone không depend workflow.

BE schema (Mig 36 — em main solo Step 4):
- 4 Domain new: MeetingRoom catalog + MeetingBooking header + MeetingBookingAttendee
  join table N-to-N (NOT JSON per Investigator verdict) + Enums (MeetingBookingStatus
  3-state: Confirmed/Cancelled/Completed)
- 3 EF Config: UNIQUE Code + composite index (RoomId, StartAt) range query + UNIQUE
  composite (BookingId, UserId) join
- FK strategy: Room→Restrict (preserve history) + Booking→Cascade attendees +
  User→Restrict (denorm FullName+Email tránh cascade wipe)
- Mig 36 3-file rule + ApplicationDbContextModelSnapshot updated + apply Dev+Design DB

BE CQRS (~584 LOC — Implementer Case 2):
- MeetingFeatures.cs 479 LOC 9 handler: 4 Room CRUD + 5 Booking (List + GetById +
  Create + Update + Cancel)
- SERIALIZABLE transaction overlap check via EXISTS query — throw 409 Conflict
  "Phòng đã được đặt trong khoảng thời gian này"
- MeetingRoomsController 49 LOC + MeetingBookingsController 56 LOC — class-level
  [Authorize] + Roles="Admin" for write
- Application.csproj +Microsoft.EntityFrameworkCore.Relational package (em main fix
  IsolationLevel overload — Implementer gotcha #53 4th truncation diagnose mid-task)
- MenuKeys.cs +4 const (Off_PhongHop sub-group + View/Manage/Book leaf)
- DbInitializer +SeedMeetingRoomsAsync 4 sample (PH-A Phòng họp lớn cap=20 + PH-B
  cap=8 + PHG-501 Giám đốc cap=6 + ONL-1 Online Zoom cap=50) — NOT gated DemoSeed
  per gotcha #51 INFRASTRUCTURE seed

FE 2 app (~1770 LOC × 2 — Implementer Case 2):
- types/meeting.ts × 2 SHA256 IDENTICAL (ce0ad9c6d017cde2) — DTO interface mirror
- MeetingCalendarPage.tsx × 2 SHA256 IDENTICAL (d6d160ae1e4f2285) ~530 LOC — custom
  HTML 7-day grid 8h-20h slot, NO FullCalendar dep (~80 KB bundle saved per
  Investigator verdict alternative)
- MeetingRoomsPage.tsx × 2 SHA256 IDENTICAL (ba35a7ef379a5e9c) ~270 LOC — admin
  catalog CRUD table + Dialog
- 4-place mirror Pattern 16-bis 7× cumulative: types + page + App.tsx route +
  menuKeys + Layout staticMap 3 entry (gotcha #50 silent sidebar drop prevention)

Verify:
- dotnet build SolutionErp.slnx PASS 0 error 2 pre-existing DocxRenderer warning
- dotnet test 130/130 PASS baseline preserve (58 Domain + 72 Infra)
- npm build × 2 app PASS 0 TS error (fe-admin 16.91s bundle 1490 KB / fe-user 8.56s
  bundle 1404 KB, +23 KB gzip both)

Pattern reinforced cumulative S36:
- Pattern 12-bis cross-module mirror 10× (PE → Contract V2 → Hrm → Office)
- Pattern 16-bis 4-place mirror cross-app 7×
- Smart Friend Implementer truncation gotcha #53 4th — mitigation tight brief WORK
  (FE 2 app no truncation, BE truncate diagnose mid only)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 15:06:12 +07:00
021674a66a [CLAUDE] FE-Admin+FE-User: S35 Plan G-H2 Task 4 — HrmConfigsPage declarative 4 catalog × 2 app
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m37s
Pattern 12-bis × 16-bis cookie-cutter cumulative 9× (Smart Friend Implementer catch).
Single page declarative KIND_CONFIG Record + URL `:kind` param + 4 sub-tab mirror
Master/Catalogs/CatalogsPage.tsx 321 LOC pattern. 4 catalog HRM (LeaveTypes/Holidays/
Shifts/OtPolicies) wire BE 16 endpoint /api/hrm-configs/{kind} từ commit `909655c`.

## Scope (Implementer Case 2 ~25K spawn)
- types/hrm-config.ts (NEW 98 LOC × 2 app SHA256 IDENTICAL `228917e5fac2cdc6`)
- HrmConfigsPage.tsx (NEW ~470 LOC × 2 app SHA256 IDENTICAL `6378fbc71ff90260`)
- App.tsx +3 LOC × 2 app (route + Navigate redirect default `/hrm/configs` → `leave-types`)
- Layout.tsx staticMap +6 LOC × 2 app — Pattern 16-bis 4-place enforcement
  (em main spec line 24 GAP — Smart Friend Implementer caught + fixed proactive)

## 4 kind FieldDef declarative
- leave-types: code/name/daysPerYear/isPaid/requiresAttachment/description
- holidays: year/date/name/isRecurring/isPaid/description
- shifts: code/name/startTime/endTime/breakMinutes/workDays(multiselect-weekday)/description
- ot-policies: code/name/multiplier×3/maxHours×3/description

## Pattern 16-bis 4-place mirror Pattern 16-bis 6× cumulative
- types/hrm-config.ts (place 1)
- pages/hrm/HrmConfigsPage.tsx (place 2)
- App.tsx routes (place 3)
- Layout.tsx staticMap (place 4 — em main MISS, Implementer caught via gotcha #50 prior knowledge)

## Verify
- npm build × 2 PASS (fe-admin 14.33s + fe-user 744ms, 0 TS error)
- SHA256 IDENTICAL × 2 NEW pair (types + page)
- Reviewer pre-commit PASS Cat A-D clean (5K token tight scope no truncation)
- gotcha #50 silent sidebar drop prevention — Pattern 16-bis discipline reinforced
- Smart Friend 9× clean cumulative S22+S25+S29×2+S33×2+S35×3

## Multi-agent ROI S35 chunk 3 ~30K
- Implementer Case 2 declarative single-page mega + spec gap catch
- Reviewer tight 5K verdict Cat A-D PASS

## State delta S35 cumulative
- 0 mig add (Mig 35 schema S34)
- 0 BE endpoint add (16 endpoint commit `909655c`)
- +1 FE page (HrmConfigsPage) × 2 app routes
- Pattern 12-bis cumulative 3× + Pattern 16-bis 6× + Pattern 12-ter 6× (FE forms)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 10:01:43 +07:00
c3cd343bae [CLAUDE] FE-Admin+FE-User: S35 Phase 1.5 Item 3-FE — inline forms 5 satellite cookie-cutter
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m51s
Pattern 12-ter (within-module N-satellite) × Pattern 16-bis (cross-app mirror) cumulative
6×. FE inline expandable form Add/Edit/Delete cho 5 satellite EmployeeProfile (WorkHistory
+ Education + FamilyRelation + Skill + Document) — close Phase 1.5 backlog Item 3 FE side.

## Scope (Implementer Case 2 ~30K spawn)
- Section component extend optional `actions?` prop (backward-compat 100%)
- 5 inline form components per app: WorkHistory/Education/FamilyRelation/Skill/Document
- 3 DRY helpers: FormField + FormFooter + RowActions (45 usage site)
- DRY helper `invalidate()` line 268 — 15 mutations × 1 shared = clean
- 5 Input types append types/employee.ts (CreateEmployeeWorkHistoryInput + 4 more)
- 15 useMutation per app (5 sat × 3 verb) = 30 total cross-app
- Wire BE 15 endpoint S34 ready: POST/PUT/DELETE /api/employees/{id}/{satellite-path}
- gotcha #44 mitigation ACTIVE: per-action policy Hrm_HoSo.{Create|Update|Delete}

## Stats
- fe-admin EmployeesListPage.tsx 573 → 1200 LOC (+627, +110%)
- fe-user EmployeesListPage.tsx mirror SHA256 IDENTICAL `802d01fd1ee79925`
- 2 types file +53 LOC each, SHA256 IDENTICAL `db29156a61af76e9`
- npm build × 2 PASS (fe-admin 31.57s + fe-user 23.39s, 0 TS error)
- BE files NOT touched (15 endpoint scaffold S34 ready)
- Test gate 130 PASS baseline preserve (FE-only)

## Multi-agent ROI S35 ~70K
- Implementer Case 2 ~30K (FE cookie-cutter scaffold)
- Investigator ~8K (G-H2 BE CRUD pre-flight, defer Plan G-H2 next chunk)
- Reviewer pre-commit ~15K Cat 1 wire BE PERFECT + em main verify Cat 2-6 PASS
- CICD warm-up ~15K (Run #241 baseline verified + standby S35 push)

Smart Friend 7× clean cumulative S22+S25+S29×2+S33×2+S35.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:38:56 +07:00
07b3f3b284 [CLAUDE] Domain+App+Infra+FE-Admin+FE-User: S34 Plan 4 G-H2 Mig 35 schema foundation
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m30s
Phase 10.2 G-H2 Cấu hình HRM — 4 catalog lookup foundation deploy (BE CRUD +
FE forms DEFER S35 cho clean handoff).

Mig 35 `AddHrmConfigs` — 4 table mới:
- LeaveTypes (Code unique + Name + DaysPerYear decimal(5,2) + IsPaid +
  RequiresAttachment) — 5 sample seed (ANNUAL 12d + SICK 30d + MATERNITY 180d
  + COMPASSIONATE 3d + UNPAID 0d)
- Holidays (Year + Date UNIQUE composite + Name + IsRecurring + IsPaid) —
  10 sample VN 2026 (Tết Dương + 5 Tết Nguyên đán placeholder + Giỗ tổ +
  30/4 + 1/5 + 2/9 + Quốc khánh)
- ShiftPatterns (Code unique + Name + StartTime/EndTime TimeOnly + BreakMinutes +
  WorkDays comma string) — 3 sample (HC 8-17 T2-T6 + CA1 6-14 T2-T7 + CA2 14-22)
- OtPolicies (Code unique + 3 Multiplier decimal(4,2) + 3 MaxHours int) —
  1 sample STANDARD (1.5x/2.0x/3.0x weekday/weekend/holiday + 4h/40h/200h cap
  Luật Lao động VN 2019)

Files:
- Domain/Hrm/{LeaveType,Holiday,ShiftPattern,OtPolicy}.cs (4 entity AuditableEntity)
- Infrastructure/Persistence/Configurations/{LeaveType,Holiday,ShiftPattern,OtPolicy}Configuration.cs (4 EF Config UNIQUE indexes)
- IApplicationDbContext + ApplicationDbContext +4 DbSet
- Migrations/20260527075940_AddHrmConfigs.{cs,Designer.cs} + Snapshot updated (3-file rule)
- DbInitializer.SeedHrmConfigsAsync ~120 LOC seed sample (NOT gated DemoSeed per gotcha #51)
- MenuKeys.cs +HrmConfig sub-group + 4 leaf (LeaveTypes/Holidays/Shifts/OtPolicies) Order=2 dưới Hrm
- DbInitializer.SeedMenuTreeAsync +5 entry (sub-group + 4 leaf)
- fe-admin + fe-user menuKeys.ts +5 const mirror BE (Pattern 16-bis sync)

Verify:
- dotnet build PASS (2 warn DocxRenderer baseline, 0 error)
- dotnet test 130/130 PASS baseline preserve
- Mig 35 applied LocalDB SolutionErp_Dev — verified via dotnet ef database update
- 4 catalog table created + 5+10+3+1 = 19 sample row seed

Defer S35:
- Task 2 BE CQRS 4 catalog CRUD (16 endpoint) — Implementer Case 2 cookie-cutter
- Task 4 FE 2 app 4 catalog page (list/create/edit dialog) — Implementer Case 2

Cumulative S34 mig: 34 → 35 (+1). Tables 67 → 71 (+4). Menu keys 64 → 69 (+5).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 15:04:10 +07:00
61e9ce5b3b [CLAUDE] Domain+App+Api+Tests+FE-Admin+FE-User: S34 Plan 3 Phase 1.5 batch 4 item
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m48s
Phase 1.5 backlog G-H1 EmployeeProfile hardening batch (Items 6+2+1+4 of 6).

Item 6 — menuKeys FE drift sync × 2 app:
- fe-admin add: Catalogs + 4 Catalog leaves + Workflows + Budgets + Bg_List/Create/Pending (10 key)
- fe-user add: Budgets + Bg_List/Create/Pending + ApprovalWorkflowsV2 + 2 AwV2 leaf + MenuVisibility + Workflows (8 key)
- Cả 2 file giờ identical mirror BE MenuKeys.cs (28 key cumulative)

Item 2 — UpdateEmployeeProfileCommand bool→bool? safe partial update:
- 3 field IsCommunistParty/IsYouthUnion/IsTradeUnion → bool?
- Handler: HasValue check, null = giữ giá trị cũ (Reviewer minor #(b) S33 fixed)
- FE không bắt buộc gửi 3 field every PUT — tránh accidental reset

Item 1 — EmployeesController per-action policy (gotcha #44 mitigation):
- Class-level [Authorize(Policy = "Hrm_HoSo.Read")] — non-admin thiếu Read → 403
- POST [Authorize(Policy = "Hrm_HoSo.Create")]
- PUT  [Authorize(Policy = "Hrm_HoSo.Update")]
- DELETE [Authorize(Policy = "Hrm_HoSo.Delete")]

Item 4 — Test bundle Phase 1.5 (+10 [Fact], baseline 120 → 130/130 PASS):
- EmployeeCodeGeneratorTests (3 [Fact]) — atomic SERIALIZABLE NV/YYYY/NNNN
  + first call + sequential increment + year boundary preserve old year
- CreateEmployeeProfileCommandTests (4 [Fact]) — Create handler edge case
  + first profile + duplicate UserId Conflict + soft-deleted Conflict-restore
  + UserNotFound NotFoundException
- ListEmployeesQueryTests (3 [Fact]) — filter + paging logic
  + status filter + departmentId filter + search by EmployeeCode partial

Implementer Case 3 test gen caught spec mismatch (allow new after soft-delete
vs throws Conflict-restore) — chose CODE source of truth + renamed test
documenting discriminator message branch. Em main verify behavior correct
(admin UX khôi phục thay vì tạo mới — explicit flow defer Phase 1.5+).

Verify:
- dotnet build PASS (2 warn DocxRenderer baseline, 0 error)
- dotnet test 130/130 PASS (58 Domain + 72 Infra = +10)
- 4 endpoint /api/employees policy wired (gotcha #44 active mitigation)
- 4 MEMORY agent updated post-spawn (CICD Run #238 + Implementer test bundle)

Deferred Phase 1.5 next batch:
- Item 3 Satellite CRUD endpoints (WorkHistory/Education/FamilyRelation/Skill/
  Document) + FE inline edit forms — heavy ~2-3h
- Item 5 UAT smoke non-admin role verify silent 403 catch — defer post-deploy

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:57:08 +07:00
ea440da990 [CLAUDE] Domain+App+Api+Infra+FE-Admin+FE-User: S34 Plan 2 G-O1 Danh bạ nội bộ
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m46s
Phase 10.2 Văn phòng số — Internal Directory (1 endpoint reuse Users +
EmployeeProfiles + Departments, FE card grid avatar/dept/email/phone/Ext).

BE Task 1+2 (em main solo):
- Application/Office/DirectoryFeatures.cs — GetDirectoryQuery + DirectoryItemDto
  12 field LEFT JOIN Users.IsActive + Departments + EmployeeProfiles
- Api/Controllers/DirectoryController.cs — GET /api/directory?search=&departmentId=
  class-level [Authorize] (mọi authenticated NV tra cứu danh bạ nội bộ)
- MenuKeys.cs +Off+OffDanhBa const + All[] update
- DbInitializer.SeedMenuTreeAsync Off Order=29 + OffDanhBa Order=1 dưới Off

FE Task 3 (Implementer Case 2 Pattern 16-bis 4-place mirror cross-app — 5×):
- types/directory.ts SHA256 7349d9f64e78 × 2 app IDENTICAL
- pages/office/InternalDirectoryPage.tsx SHA256 2aa7e0eed2c8 × 2 app IDENTICAL
  Card grid responsive 1/2/3/4 col + filter dept dropdown + search input
  Avatar 14×14 initials gradient PALETTE 6 màu (Pattern 14 Tailwind JIT)
  EmployeeCode badge + Department emerald badge + email mailto + phone tel
  Internal phone Ext: amber badge + empty/loading state Vietnamese 100%
- App.tsx route /directory × 2 app
- lib/menuKeys.ts Off+OffDanhBa const × 2 app
- components/Layout.tsx resolvePath staticMap Off_DanhBa:/directory × 2 app
  (gotcha #50 — 5 places mirror crossapp DON'T MISS)

Verify:
- dotnet build PASS (2 warn DocxRenderer existing, 0 error)
- dotnet test 120/120 PASS (58 Domain + 62 Infra baseline preserve)
- npm build × 2 app PASS 0 TS err (fe-admin 1436KB / fe-user 1350KB)

Implementer MEMORY Pattern 16-bis reinforced 5× cumulative (S29 Plan CA HF1 +
S29 Plan B Chunk D + S33 Plan B G-H1 Task 5 + S34 Plan G-O1 Task 3).

Endpoint smoke pending CICD post-deploy Stage 4 (Run #XXX expected ~3m30s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:39:10 +07:00
9616ae219c [CLAUDE] FE-Admin+FE-User: Plan B G-H1 Task 5 — EmployeesPage 2-panel + EmployeeCreatePage cookie-cutter mirror
Phase 10.1 G-H1 Phase 2 Task 5 — FE 2 app cookie-cutter mirror PE pattern.
Phase 1 ULTRA-MINIMAL scope (Implementer afdc812 scaffold):
- 2-panel ListPage (filter left + list+detail right, KHÔNG 3-panel vì Hrm
  no workflow)
- 6-section inline collapsible detail (Cơ bản/Công tác/Đào tạo/Thân nhân/
  Kỹ năng/Hồ sơ) — NO separate DetailTabs component file
- CreatePage Header form minimal (UserId picker + Status + DateOfBirth +
  Gender + Phone + HireDate + Nationality)
- Display read-only Phase 1 satellite (no inline edit — defer Phase 1.5)

## Files (6 new + 6 modified × 2 app = 12)

### NEW (3 × 2 app, SHA256 IDENTICAL cross-app mirror)

| File | LOC | SHA256 prefix |
|------|----:|---|
| `fe-{admin,user}/src/types/employee.ts` | 283 | CCFC70666568 |
| `fe-{admin,user}/src/pages/hrm/EmployeesListPage.tsx` | 417 | DC859C897C5C |
| `fe-{admin,user}/src/pages/hrm/EmployeeCreatePage.tsx` | 178 | C796F25D01AC |

10 const-object enum mirror BE Domain.Hrm.Enums + DTOs:
- EmployeeStatus/Gender/MaritalStatus/EmployeeType/DegreeLevel/
  EducationMode/GradeLevel/FamilyRelationKind/SkillKind/EmployeeDocumentType
- EmployeeListItem + EmployeeDetail + 5 satellite DTO type

### MODIFIED (3 × 2 app)

- `fe-{admin,user}/src/lib/menuKeys.ts` — +Hrm + HrmHoSo const
- `fe-{admin,user}/src/components/Layout.tsx` — +Hrm_HoSo:'/employees' staticMap
  (LESSON Plan CA Hotfix 1 gotcha #50: page route mới phải thêm staticMap
  entry cùng commit, else silent sidebar drop)
- `fe-{admin,user}/src/App.tsx` — +2 route /employees + /employees/new

## Pattern reinforcement

- **Pattern 16-bis 4-place mirror cross-app** reinforced 4× cumulative (S29
  Plan CA HF1 + S29 Plan B Chunk D + S33 Task 5 admin + S33 Task 5 user).
  Comment header trong Layout.tsx ghi explicit Plan CA Hotfix 1 #50 lesson.
- **Pattern 12-bis cross-module entity FE port PE → Hrm** reinforced 4× (Plan
  B Chunk C Mig 33 + G-H1 Task 4 BE + Task 5 FE types mirror PE types/page
  structure mirror PE 2-panel scope-down 3→2 panel).

## Reviewer ae752c0 verdict: PASS (commit 0e191de earlier)

- Smart Friend 6× cumulative clean (em main + Implementer quality genuine)
- gotcha #50 Layout staticMap mirror ✓ (cả fe-admin + fe-user)
- menuKeys.ts FE drift pre-existing intentional (fe-admin minimal vs fe-user
  expanded Catalogs/Suppliers/Projects/Departments) — NOT blocking, follow-up
  task add Budgets/Catalogs to fe-admin OR document intentional minimal scope.

## Verify

- fe-admin npm build: PASS 21.4s · 0 TS6 err · 1,431 KB bundle
- fe-user npm build: PASS 9.2s · 0 TS6 err · 1,345 KB bundle
- dotnet build: PASS 1.59s · 0 warn 0 err (no BE change)
- dotnet test: 120/120 PASS baseline preserved

## Defer Phase 1.5 (per Reviewer recommend)

1. PermissionGuard wrapper menuKey HrmHoSo + per-action Hrm_HoSo_View/Create
2. Convert 3 bool field UpdateCommand thành bool? safe partial update
3. Satellite CRUD endpoint + form (WorkHistory/Education/FamilyRelation/
   Skill/Document)
4. Test bundle (Create UNIQUE conflict + List filter + codeGen race)
5. Add Bg_*/Catalog* to fe-admin menuKeys.ts sync với fe-user

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 20:27:25 +07:00
14feb6955d [CLAUDE] FE-Admin+FE-User: Plan B Chunk E3 — ContractDetailPage Section 5 LevelOpinionsV2 dynamic
Em main solo sau Implementer E3 stuck mid-task. Minimum viable mirror PE
Section 5 LevelOpinionsSectionV2 pattern. V2 contract pin ApprovalWorkflowId
→ render dynamic Section 5 với opinion data UPSERT từ Service ApproveV2Async
(Plan B Chunk B2 1f199b0).

Changes × 2 app:

types/contracts.ts (×2):
- ContractDetail +3 fields V2 (default null backward compat):
  - approvalWorkflowId: string | null
  - currentApprovalLevelOrder: number | null
  - levelOpinions: ContractLevelOpinion[] | null
- NEW type ContractLevelOpinion (12 fields mirror BE DTO)

components/contracts/ContractDetailContent.tsx (×2):
- Add Section 5 sau "Chi tiết HĐ" section
- Conditional render: chỉ khi c.approvalWorkflowId (V2 mode)
- Empty placeholder "Chưa có ý kiến" khi workflow vừa start
- forEach opinion render card:
  - Title: Bước X (StepName) — Cấp Y (LevelName)
  - Comment + signedAt vi-VN format
  - NV duyệt: approverFullName
  - Banner " Admin duyệt thay" khi signedByUserId !== approverUserId
- Style: emerald palette mirror PE Section 5

V1 contract: 3 fields null → Section 5 KHÔNG render (backward compat).

Verify:
- npm run build × 2 app PASS 0 TS err
- Mirror 2 app §3.9 byte-similar (4 file edit cùng pattern)
- BE wire OK: E2 commit 48f6d22 expose approvalWorkflowId + levelOpinions

Plan B COMPLETE 9/9 chunks LOCAL:
- A1 58898e8  Entity +2 fields
- A2 a85e437  Mig 32 + Config + Seed
- B 138469d  Service ApproveV2Async branch
- C 26c98d3  Mig 33 LevelOpinions
- B2 1f199b0  UPSERT block
- E1 ef23308  CreateContractCommand +V2
- D 62b50d1  FE Workspace V2
- E2 48f6d22  ContractDetailDto + populate
- E3 (this)  FE Section 5 LevelOpinionsV2

Implementer E3 spawn stopped mid-task ("check ContractDetail type" judgment
call) → em main solo finish. Pattern lesson: complex FE feature mirror với
type extend + new component → em main solo more reliable than Implementer.

Pending: Reviewer pre-commit Plan B cumulative + push remote + CICD verify
+ docs/STATUS update.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:45:33 +07:00
62b50d112b [CLAUDE] FE-Admin+FE-User: Plan B Chunk D — ContractCreatePage Workspace V2 Select dropdown
Mirror PE PeWorkspaceCreateView Workspace pattern. Drafter pick V2
workflow IsUserSelectable=true filter ApplicableType=Contract(3).

Changes × 2 app:
- Add useQuery fetch /api/approval-workflows-v2?applicableType=3 + filter
  client-side isUserSelectable=true (mirror PE Mig 25 pattern)
- Add Select dropdown "Quy trình duyệt V2 (tùy chọn)" trong
  ContractHeaderForm (create mode panel 2)
- Wire approvalWorkflowId vào CreateContractCommand POST body
- Conditional UI: blank = V1 fallback auto pick (7 prod contract behavior
  giữ nguyên); user pick V2 → pin ApprovalWorkflowId Mig 32 schema
- Hint khi 0 workflows V2 admin ghim → message rõ V1 fallback

Verify:
- npm run build × 2 app PASS 0 TS err (1.32MB fe-user, 1.40MB fe-admin)
- Mirror 2 app §3.9: +44 LOC mỗi file = +88 LOC total byte-similar
- API endpoint /api/approval-workflows-v2 existing (Mig 25 Plan AA S24)
- BE CreateContractCommand.ApprovalWorkflowId field đã add Chunk E1
  (em main commit prior) — FE wire safe
- Backward compat: V1 contract path unchanged khi user bỏ trống dropdown

Plan B chain (6 chunks):
- A1 58898e8  Entity ApprovalWorkflowId + CurrentApprovalLevelOrder
- A2 a85e437  Mig 32 + Seed sample V2 Contract workflow
- B 138469d  Service ApproveV2 branch (PE pattern mirror)
- C 26c98d3  Mig 33 ContractLevelOpinions
- B2 1f199b0  UPSERT LevelOpinion block (PE Mig 26 mirror)
- D (this)  FE Workspace V2 dropdown
- E FE Section 5 V2 (em main + Implementer split E1+E2 sau)

Pattern 16-bis 4-place mirror check:
- Page file × 2 app: edited (insertion mirror byte-similar)
- App.tsx Routes: N/A (enhance existing /contracts/new route)
- menuKeys.ts: N/A (không thêm menu key mới)
- Layout staticMap: N/A (route unchanged)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:34:00 +07:00
c995f42e0d [CLAUDE] FE-Admin+FE-User: Plan CA Chunk C — Sidebar filter 2 app (admin HIDE + user SHOW)
fe-admin/src/components/Layout.tsx:
- Add ADMIN_HIDDEN_MASTER_KEYS Set với 9 menu key danh mục
- Extend isAdminHidden predicate: hide Ct_* + ADMIN_HIDDEN_MASTER_KEYS
- Master/Suppliers/Projects/Departments/Catalogs/4 sub-catalogs giờ ẩn khỏi admin sidebar

fe-user/src/components/Layout.tsx:
- Remove `Master, Suppliers, Projects, Departments` khỏi USER_HIDDEN_KEYS
- Catalogs (root + 4 leaf) auto-visible qua tree-inherit từ Master
- Giữ ẩn `System, Users, Roles, Permissions, Forms, Reports` (admin tools)

Verify:
- npm run build × 2 app PASS 0 TS err
- fe-admin bundle `index-BQidGwKU.js` 1,404 KB gz 357 KB (rotate)
- fe-user bundle `index-Co8LTtad.js` 1,317 KB gz 342 KB (rotate)
- Pattern 5 (mirror 2 app §3.9) applied — cùng 1 plan touch 2 file Layout

Plan CA chain:
- Chunk A 80d39a0 BE Role + Seed (em main solo)
- Chunk B 06a441c FE move 4 pages (Implementer Case 2)
- Chunk C (this) sidebar filter (em main solo)
- Pending Chunk D: smoke verify + tạo demo user catalog.manager@solutions.com.vn

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:00:29 +07:00
d99069a305 [CLAUDE] FE-User+FE-Admin: Plan AG6 — Compact PE card 3 row gọn đẹp
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m21s
Anh feedback 2026-05-21: "Cho thông tin bên trong này nó gọn đẹp lại nhé".
PE card hiện 4-5 row hơi dài. Compact xuống 3 row tightly packed:

Row 1: [Tên gói thầu]                        [Status badge]
Row 2: PE/2026/A/035 · 10:40 19/05/2026
Row 3: 👤 Drafter · Phòng ban                [✓ HĐ if any]

Changes:
- py-2.5 → py-2 (compact vertical padding)
- Drop Type label "Duyệt NCC" badge redundant (page header đã hiện)
- Combine maPhieu + createdAt vào 1 row với separator ·
- Combine drafter + department + contract badge vào 1 row (conditional render)
- "✓ Đã tạo HĐ" → "✓ HĐ" short label inline cuối row 3
- Separator color slate-300 nhẹ hơn slate-500

Verify:
- npm build fe-user PASS 0 TS err 1292.66 KB (gzip 337.19 KB)
- npm build fe-admin PASS 0 TS err 1404.01 KB (gzip 357.70 KB)
- 2 file SHA256 IDENTICAL 3645307C... (mirror §3.9)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:50:14 +07:00
083b601ea4 [CLAUDE] FE-User+FE-Admin: Plan AG5 — PE List tree 3-level Project > Năm > NCC > PE
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m32s
Anh feedback 2026-05-21: "Folder cấp dưới dự án là theo năm và dưới năm là theo NCC nhé".
Plan AG3 chỉ 1-level Project > PE. Plan AG5 extend xuống 3 cấp: Năm + NCC.

Group structure:
- Level 1: 📁 Project (bg-slate-50, font-medium 13px)
- Level 2: 📅 Năm {year} (border-l ml-3, 12px)
- Level 3: 🏢 NCC (border-l ml-3, 12px, italic slate-400 nếu "Chưa chọn NCC")
- Leaf: PE card (border-l ml-3, giữ nguyên content)

Sort:
- Project A-Z (vi locale)
- Năm DESC (2026 trước 2025)
- NCC A-Z (vi locale)
- PE within NCC: createdAt DESC

Fallback:
- empty projectName → "(Dự án đã xoá)"
- selectedSupplierName null (PE chưa DaDuyet) → "(Chưa chọn NCC)" group + italic style

Drop redundant selectedSupplierName line trong PE card (đã hiện ở NCC group header).

localStorage keys:
- Project: projectId
- Năm: `${projectId}::y${year}`
- NCC: `${projectId}::y${year}::s${supplierId|'_none_'}`

Verify:
- npm build fe-user PASS 0 TS err 1292.68 KB (gzip 337.18 KB) 1907 modules
- npm build fe-admin PASS 0 TS err 1404.02 KB (gzip 357.70 KB) 1926 modules
- 2 file SHA256 IDENTICAL E5FE4979... (mirror §3.9)
- KHÔNG BE change, KHÔNG Mig, KHÔNG test (UAT mode)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:41:58 +07:00
2bf01184ca [CLAUDE] App+FE-User+FE-Admin: Plan AG4 — bổ sung Drafter + Department vào PE List card
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m27s
Anh UAT 2026-05-21: PE card danh sách thiếu người tạo + phòng ban tạo. Bổ sung 4 field
qua BE JOIN Users + Departments LEFT (cả 2 nullable theo PE entity).

BE — 4 file:
- PurchaseEvaluationDtos.cs: +4 fields DrafterUserId/DrafterName/DepartmentId/DepartmentName
- PurchaseEvaluationFeatures.cs ListHandler: JOIN Users + Departments LEFT, projection +4
- PurchaseEvaluationFeatures.cs InboxHandler: mirror JOIN + projection +4
- CreateContractFromEvaluationFeatures.cs ListApproved: mirror JOIN + projection +4

FE — 4 file × 2 app mirror:
- types/purchaseEvaluation.ts: PeListItem +4 fields
- pages/pe/PurchaseEvaluationsListPage.tsx: PE card render thêm dòng "👤 {drafterName} · {departmentName}"
  giữa Mã phiếu và Supplier. Conditional: chỉ render khi có ít nhất 1 field.

Verify:
- dotnet build clean 0 err
- dotnet test SolutionErp.slnx 111/111 PASS (58 Domain + 53 Infra) — no regression
- npm build fe-user PASS 0 TS err 1290.31 KB (gzip 336.79 KB) 1907 modules
- npm build fe-admin PASS 0 TS err 1401.66 KB (gzip 357.30 KB) 1926 modules
- 2 FE PE List file SHA256 IDENTICAL C6996194... (mirror §3.9)
- KHÔNG Mig (chỉ DTO + projection extend)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:52:04 +07:00
fbad4a9251 [CLAUDE] FE-User+FE-Admin: Plan AG3 — PE List tree consistent (drop single-PE flat branch)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m19s
Anh feedback 2026-05-21: "nếu có 1 thì cũng để tương tự luôn nhé, đừng để khác các thằng kia".
Plan AG2 render single-PE project flat card + UPPERCASE label phía trên — khác phong cách
với multi-PE project (folder <details>). UX inconsistent.

Plan AG3 drop nhánh single-PE flat. Mọi dự án dù 1 hay nhiều PE đều render <details>
folder collapsed với badge count "(N)" — consistent visual.

Diff: -60 LOC (drop entire single-PE flat block).

Verify:
- npm build fe-user PASS 0 TS err
- npm build fe-admin PASS 0 TS err
- 2 file SHA256 IDENTICAL 749FF703... (mirror §3.9)
- KHÔNG BE change, KHÔNG Mig, KHÔNG test (UAT mode)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:35:25 +07:00
c5429c0d10 [CLAUDE] FE-User+FE-Admin: Plan AG2 — Simplify PE List tree 1-level + Panel 1 widen 400px
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m29s
Anh feedback Plan AG (2-level Project > Gói thầu > PE) cầu kỳ quá. Simplify
xuống 1-level + widen panel cho dễ đọc.

3 changes:
1. Panel 1 widen 340px → 400px (lg:grid-cols-[400px_1fr_360px])
2. Drop GoiThauGroup nested type + inner <details> tree, useMemo group 1-level
   Project > PE[]; PE sort by createdAt DESC trong group (mirror BE sort)
3. Smart render: single-PE project → flat card (no <details> wrapper, project
   name UPPERCASE label inline) / multi-PE project → <details> tree expand
4. localStorage key rename 'pe_list_expanded_projects' (drop ::gtKey composite suffix)

UAT visual: dự án solo PE hiện flat (không cần click expand), dự án có nhiều
phiếu render tree compact.

Drop redundant projectName ở PE card (đã có ở group header / UPPERCASE label).

Verify:
- npm build fe-user PASS 0 TS err 1291.76 KB (gzip 336.90 KB) 1907 modules
- npm build fe-admin PASS 0 TS err 1403.10 KB (gzip 357.41 KB) 1926 modules
- 2 file SHA256 IDENTICAL 37520D01... (mirror §3.9)
- KHÔNG BE change, KHÔNG Mig, KHÔNG test (UAT mode per feedback_uat_skip_verify)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:24:59 +07:00
0bf6c7ec63 [CLAUDE] FE-User+FE-Admin: Plan AG Chunk A+B+C — PE List tree view 2-level Project > Gói thầu
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m39s
UAT feedback bro Tra Sol 2026-05-21: UI Duyệt NCC flat list "đám rừng" → tree view giống Outlook folder.

Phase 1 FE-only mirror 2 app §3.9 — KHÔNG schema mới (Phase 2 ProjectPackage defer sau UAT confirm).

Chunk A — Data transform useMemo group:
- Map<projectId, Map<normalizedGoiThau, PeListItem[]>>
- Normalize TenGoiThau: trim + toLowerCase, display raw đầu tiên trong group
- Sort: Project A-Z + gói thầu A-Z (vi locale)
- Fallback: "(Dự án đã xoá)" empty projectName + "(Chưa phân loại)" empty TenGoiThau
- Filter (pendingMe → DaGuiDuyet) áp dụng TRƯỚC group

Chunk B — UI render <details>/<summary> 2-level:
- Replace flat <ul><li> bằng nested <details> HTML native (no shadcn Accordion — gap component lib)
- 📁 Project + 📄 Gói thầu icon + count badge inline
- Chevron rotation via Tailwind group-open/proj + group-open/gt named groups
- PE card content preserve nguyên (line 209-248 unchanged)

Chunk C — Expand state localStorage persist:
- Key 'pe_list_expanded_groups' Set<string>
- Project level key: projectId; Gói thầu level key: ${projectId}::${normalizedGoiThau}
- Default empty Set (all collapse) — bro Tra Sol expect Outlook-style closed default

Verify:
- npm build fe-user PASS 0 TS err 1291.33 KB (gzip 337.00 KB) 1907 modules 16.05s
- npm build fe-admin PASS 0 TS err 1402.68 KB (gzip 357.51 KB) 1926 modules 6.86s
- KHÔNG BE change, KHÔNG Mig, KHÔNG test (UAT mode per feedback_uat_skip_verify)

Pending: Reviewer pre-commit + CICD Run #222 verify

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:46:17 +07:00
506cada86b [CLAUDE] FE-User FE-Admin: Plan AF — userMap fallback resolve historical entries pre-Plan AE
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m23s
Bro UAT 2026-05-19 post-Plan AE: phiếu cũ entries vẫn show "Hệ thống" thay
vì user name. Plan AE chỉ forward fix — entries CŨ pre-deploy có
userName="" empty, FE fallback "Hệ thống".

Fix Plan AF — Option A bro chốt (FE fallback lookup, no DB write):

ApprovalsTab + HistoryTab build userMap useMemo từ PeDetailBundle data
có sẵn (KHÔNG cần extra fetch /api/users admin permission):
- ev.drafterUserId + ev.drafterName
- ev.approvals[].approverUserId + approverName
- ev.approvalFlow.steps[].levels[].approvers[].userId + fullName
- ev.levelOpinions[].signedByUserId + signedByFullName
- ev.departmentOpinions[].userId + userName

resolveUserName / resolveActorName helper:
1. Trust entry.userName nếu non-empty
2. Lookup userMap qua entry.userId
3. Fallback 'Hệ thống' nếu không match

Cover gần hết users tham gia phiếu (drafter + approver + signer). Edge
case: user edit phiếu nhưng KHÔNG xuất hiện trong workflow → vẫn fallback.

Pattern reusable: synthetic data recovery cho audit trail từ embedded
domain data sources, no extra API contract change.

Mirror 2 app §3.9 identical logic.

Verify:
- npm build × fe-user PASS 0 TS err (9.12s)
- npm build × fe-admin PASS 0 TS err (8.91s)
- BE unchanged from 9ea62be

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 13:04:59 +07:00
0aaf2df04a [CLAUDE] FE-User FE-Admin: Plan AD — Lịch sử duyệt redesign drop phase badges + next-target hint
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m24s
Bro UAT 2026-05-19 post-Plan AC2 deploy: phase badges "Đã gửi duyệt →
Đã gửi duyệt" gây nhầm (Reject event nhìn giống Approve, không rõ gửi
duyệt cho ai).

Fix Plan AD — Option A bro chốt:

1. DROP fromPhase→toPhase badges entirely khỏi ApprovalsTab — redundant
   visual noise khi 3/4 mode return giữ Phase=ChoDuyet, và misleading cho
   user thấy "Đã gửi duyệt → Đã gửi duyệt" lặp lại.

2. ADD next-target hint parse từ comment via helper extractNextTargetHint():
   Approve patterns:
     - Comment "sang Cấp X" → "→ Cấp X"
     - Comment "sang Bước X" → "→ Bước X (Cấp 1)"
     - Comment "[Duyệt vượt cấp tới Cấp cuối]" → "→ Vượt cấp tới Cấp cuối"
     - toPhase=DaDuyet(20) → "→ Đã duyệt hoàn tất"
   Reject patterns:
     - Comment "không lùi được" → "→ Không lùi được"
     - Comment "Người chỉ định" + Bước/Cấp → "→ Trả về Người chỉ định (Bước X Cấp Y)"
     - Comment "Người soạn thảo"/"Drafter" → "→ Trả về Người soạn thảo"
     - Comment "Trả về 1 Cấp"/"Trả về Cấp X" → "→ Lùi về Cấp X" / "→ Lùi 1 Cấp"
     - Comment "Trả về 1 Bước"/"Trả về Bước X" → "→ Lùi về Bước X" / "→ Lùi 1 Bước"
     - toPhase=TuChoi(99) → "→ Từ chối hoàn toàn"

3. Layout cleaner: [Decision badge] [Next-target hint] flex-wrap min-w-0 +
   timestamp shrink-0 right. Comment + actor stays below.

4. Cleanup import: drop unused PurchaseEvaluationPhaseColor (no longer
   needed after dropping phase badges). Keep PurchaseEvaluationPhaseLabel
   (still used at line 157+ for InfoTab phase label).

Mirror 2 app §3.9 identical logic.

Verify:
- npm build × fe-user + fe-admin PASS 0 TS err
- BE/test unchanged from 25837b6

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 11:19:43 +07:00
25837b6220 [CLAUDE] FE-User FE-Admin: Plan AC2 — FE merge view recover historical Reject events PE cũ
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m22s
Bro UAT 2026-05-19 phản hồi sau Plan AC deploy: phiếu cũ PE/2026/A/032 vẫn KHÔNG
show events Trả lại pre-deploy (Bro test trả lại Phan Văn Chương → Trà từ TRƯỚC
cdfd542 không có trong Lịch sử duyệt).

Root cause: Plan AC chỉ add Approval row cho events POST-deploy. Events
pre-deploy chỉ có Changelog (LogTransitionAsync) — Approval table miss.

Fix Plan AC2 — FE merge view (Option 2A bro chọn):

ApprovalsTab fetch BOTH approvals + changelogs (cùng endpoint HistoryTab dùng):
- Reconstruct synthetic PeApproval rows từ Changelog Workflow+Reject events:
  - Filter: entityType=Workflow(5) + summary "→ TraLai"/"→ TuChoi" OR
    contextNote chứa "Trả về"/"không lùi được" (3 mode OneLevel/OneStep/Assignee
    giữ ChoDuyet → distinguish qua ContextNote keywords)
  - Parse fromPhase/toPhase từ summary regex "Chuyển phase X → Y"
  - id prefix "syn-" để distinct vs real Approval rows
- Dedupe synthetic vs real Reject Approval (post-Plan AC) qua
  approverUserId + timestamp 5s bucket key
- Merge approvals + dedupedSynthetic → sort by approvedAt → render

Reversible: KHÔNG touch DB, KHÔNG migration. FE-only fix recover history
cho mọi PE cũ trước deploy.

Mirror 2 app §3.9 identical logic.

Verify:
- npm build × fe-user + fe-admin PASS 0 TS err
- BE/test unchanged from a734bf2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 11:00:05 +07:00
a734bf2b8b [CLAUDE] PurchaseEvaluation: Plan AC — fix Lịch sử duyệt panel show Trả lại + Duyệt vượt cấp
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m27s
Bro UAT 2026-05-19 screenshot: panel "Lịch sử duyệt" KHÔNG show Return mode
events (Bro Trả lại từ Phan Văn Chương → Trà missing) + KHÔNG distinct
event Duyệt vượt cấp (skipToFinal F2).

Root cause:
- PurchaseEvaluationApprovals.Add() chỉ ở Approve branch (line 472 V2 + 660 V1)
- Reject branch line 75-103 NEVER adds Approval row — chỉ log Changelog
- skipToFinal advance branch line 532-572 dùng existing line 472 row nhưng
  comment KHÔNG distinct "vượt cấp" semantic vs approve thường

Fix Plan AC:

1. BE Service.cs Reject branch (line 75-103): capture pre-call Step/Level
   trước ApplyReturnModeAsync mutate pointer, add Approval row sau khi mutate:
   Decision=Reject + FromPhase + ToPhase=evaluation.Phase + Comment carry
   from-position + mode summary. Cover cả Trả lại (TraLai+pointer-mode) +
   Từ chối (TuChoi terminal).

2. BE Service.cs line 472 Approve branch: enrich Comment với prefix
   "[Duyệt vượt cấp tới Cấp cuối]" khi skipToFinal=true để Lịch sử duyệt
   distinguish vượt cấp với approve thường.

3. FE PeDetailTabs.tsx × 2 app ApprovalsTab: add Decision badge phân biệt
   Approve (emerald) / Trả lại (amber) / Từ chối (rose). Vì 3/4 mode Trả
   lại (OneLevel/OneStep/Assignee) giữ Phase=ChoDuyet → fromPhase→toPhase
   badge giống Approve. Decision badge bù visual phân biệt.

Verify:
- dotnet build clean 0 err 2 warn (pre-existing DocxRenderer)
- dotnet test 111/111 PASS
- npm build × fe-user + fe-admin PASS 0 TS err

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:45:25 +07:00
cdfd54212c [CLAUDE] PurchaseEvaluation: Plan AB Chunk A — fix Changelog visibility Bug 1 Budget Adjust + Bug 2 Return Mode
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m6s
- BE ApplyReturnModeAsync 4 mode add Changelog.Add() common path (refactor Drafter early return)
- FE PeDetailTabs.tsx HistoryTab filter extend cover Header+ngân sách (B1) + Workflow+Trả lại (B2)
- FE empty placeholder + comment update reflect new filter scope
- Mirror 2 app §3.9

Bug 1: Budget Adjust handler đã log (Header+Update) nhưng FE filter strict TraLai-only
Bug 2: Return mode Service không log Changelog — chỉ approval phase transition

Verify:
- Build clean 0 err
- npm build × 2 app pass 0 TS err
- 111 test baseline preserve (UAT skip test-after defer)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:07:44 +07:00
ee0902ac13 [CLAUDE] FE-User FE-Admin: Plan AA wrap fix - sidebar label dài wrap về đầu hàng + text smaller
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m25s
UAT feedback 2026-05-15 sau Run #213 deploy: bro screenshot sidebar label custom
Mig 27 dài "1. Duyệt Nhà Cung Cấp - Thầu phụ (NCC -TP)" wrap 2 dòng, dòng 2
"(NCC -TP)" indent SAU icon thay vì về đầu hàng.

Root cause: flex container [items-center, gap-2] + inner span chứa Icon + label
text → text wraps within INNER span (đã indent past icon area). Pattern phù
hợp cho 1-dòng label, KHÔNG phù hợp khi multi-line.

Fix pattern (3 sites fe-user + 2 sites fe-admin mirror rule §3.9):

- MenuGroup button: flex → relative block + inline-block icon + inline text +
  absolute ChevronDown right. Text wrap về left edge button (under icon).
- MenuLeaf NavLink: flex → block + inline-block icon + inline text.
- StaticLeaf NavLink (fe-user only): mirror MenuLeaf pattern.

Smaller text:
- text-[13px] → text-[12px] (medium label group + leaf)
- text-sm (14px) → text-[12px] (MenuLeaf top level)
- text-[12px] → text-[11px] (MenuLeaf deep level)
- leading-snug (1.375) compact 2-line height

Icon adjust: -mt-0.5 align with inline text baseline.
Button px-3 pr-7: pad right 28px reserve cho absolute ChevronDown (KHÔNG bị đẩy
xuống khi label wrap).

Verify:
- npm run build fe-user PASS clean 432ms
- npm run build fe-admin PASS clean 494ms

Em main solo CSS polish < 30 min (criteria #6 REFUSE Implementer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:32:21 +07:00
ee776d5745 [CLAUDE] Domain+App+Api+FE-User+FE-Admin: Plan AA Chunk A - BE IsUserSelectable filter + menu seed Pe_DuyetNcc_WfView + sidebar widen w-72 xl:w-80 + revert Plan U truncate
BE changes:
- MenuKeys.cs +helper PurchaseEvaluationWorkflowView(typeCode) => "Pe_{typeCode}_WfView"
- DbInitializer.cs SeedMenuTreeAsync:
  - tree.Add LuongDuyet (Order=2 first child) cho 2 type PE
  - INSERT-only loop -> INSERT-OR-UPDATE-Order (shift existing prod rows Order+1)
  - Idempotent: skip nếu Order match, UPDATE nếu mismatch
- DbInitializer.cs SeedPurchaseEvaluationPermissionDefaultsAsync +WfView leaf cho 7 role Read
- ApprovalWorkflowV2AdminFeatures.cs GetAwAdminOverviewQuery +IsUserSelectable bool? = null
  + handler conditional Where(d => d.IsUserSelectable == ius)
- ApprovalWorkflowsV2Controller.cs Overview signature +[FromQuery] bool? isUserSelectable
  pass-through to mediator (gotcha #44 fix preserved class-level [Authorize] bare)

FE Layout changes (mirror 2 app rule §3.9):
- fe-user resolvePath regex (List|Create|Pending|WfView) + route
  /purchase-evaluations/workflow-matrix?type=N
- fe-user + fe-admin sidebar w-60 xl:w-72 -> w-72 xl:w-80 (+48/+32px gain)
- Revert Plan U S23 t11 truncate × 5 sites (3 fe-user MenuGroup+MenuLeaf+StaticLeaf
  + 2 fe-admin MenuGroup+MenuLeaf). Keep min-w-0 flex-1 + shrink-0 + title
  tooltip (no harm). Bro request hiển thị đầy đủ label custom Mig 27 dài.

Why:
- User UAT request 2026-05-15: thêm menu "Luồng duyệt" trên Danh sách hiển thị
  ma trận phân quyền workflow V2 admin Designer ghim ra cho user xem trước khi
  tạo phiếu. Filter IsUserSelectable=true (Mig 25).
- Sidebar Plan U S23 t11 truncate hiển thị "..." → bro muốn full label.
  Widen sidebar +32-48px + bỏ truncate cho phép wrap natural khi cực dài.

Verify:
- dotnet build SolutionErp.slnx PASS clean 0 err 2 warn pre-existing DocxRenderer
- Investigator Pre-A confirm gotcha #44 đã fix permanent từ 2026-05-08
- Reviewer cumulative PASS 0 critical / 0 major / 0 minor blocker

Pending Chunk B: FE WorkflowMatrixViewPage.tsx ~215 LOC + types + App.tsx route.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:34:40 +07:00
86d8806afc [CLAUDE] FE-Admin FE-User: Chunk U — Sidebar truncate long label + tooltip (Mig 27 DisplayLabel dài wrap fix)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m30s
Bro UAT screenshot 2026-05-15: Submenu "1. Duyệt Nhà Cung Cấp - Thầu phụ
(NCC -TP)" trong sidebar fe-user wrap 2 dòng (label dài ~50 chars vs
sidebar w-60 = 240px chỉ fit ~25 chars).

Root: Admin đã set DisplayLabel custom qua Mig 27 (S20 t7 Menu eOffice
admin page) — `MenuItems.Pe_DuyetNcc` DisplayLabel = "1. Duyệt Nhà Cung
Cấp - Thầu phụ (NCC -TP)" (Label gốc = "Duyệt NCC" ngắn). FE render
{effectiveLabel(node)} thẳng vào span flex KHÔNG có truncate.

Fix Plan U mirror 2 app (rule §3.9):

3 nơi render label trong fe-user/Layout.tsx + 2 nơi mirror fe-admin:
1. MenuNodeRenderer button (accordion toggle):
   ```diff
   - <span className="flex items-center gap-2">
   + <span className="flex min-w-0 flex-1 items-center gap-2">
   -   <Icon className="h-4 w-4" />
   -   {effectiveLabel(node)}
   +   <Icon className="h-4 w-4 shrink-0" />
   +   <span className="truncate" title={effectiveLabel(node)}>{effectiveLabel(node)}</span>
     </span>
   - <ChevronDown ... transition />
   + <ChevronDown ... shrink-0 ... transition />
   ```

2. MenuLeaf NavLink:
   ```diff
   - <NavLink to={path} className={cn('flex items-center gap-2.5...')}>
   + <NavLink to={path} title={effectiveLabel(node)} className={cn('flex min-w-0 items-center gap-2.5...')}>
   -   <Icon className={cn(isDeep ? 'h-3.5 w-3.5' : 'h-4 w-4')} />
   -   {effectiveLabel(node)}
   +   <Icon className={cn('shrink-0', isDeep ? 'h-3.5 w-3.5' : 'h-4 w-4')} />
   +   <span className="truncate">{effectiveLabel(node)}</span>
     </NavLink>
   ```

3. StaticLeaf NavLink (fe-user only — Hộp thư static entry):
   Pattern tương tự MenuLeaf

fe-admin dùng `node.label` thay vì `effectiveLabel(node)` (admin sidebar
luôn show Label gốc, KHÔNG đụng DisplayLabel per S20 t7 Q2=b).

Pattern key:
- `min-w-0 flex-1` trên flex parent — cần thiết để truncate child shrink
- `shrink-0` trên Icon + ChevronDown — giữ size không co
- `truncate` (Tailwind = overflow-hidden text-ellipsis whitespace-nowrap) trên span text
- `title={label}` tooltip hover show full label nếu user cần đọc đầy đủ

Verify:
- npm run build fe-user PASS 16.79s clean
- npm run build fe-admin PASS 8.16s clean
- 0 TS error

KHÔNG đụng BE. Admin tự control DisplayLabel qua Mig 27 Menu eOffice page
— Plan U chỉ ensure FE render gracefully với label dài (truncate +
tooltip hover) thay vì wrap broken visual.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:33:17 +07:00
108268a2e2 [CLAUDE] FE-Admin FE-User: Chunk Q — Fix layout banner F3 violet mx-5 inset gap khiến button "+ Thêm hạng mục" lệch
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m15s
Bro UAT screenshot 2026-05-15 sau Plan O+P deploy: PE/2026/A/025 Phase=ChoDuyet
actor NV Test có F3 AllowApproverEditDetails=TRUE — banner violet "Bạn được
phép chỉnh sửa Hạng mục / NCC / Báo giá" render ĐÚNG nhưng layout:

```
[Section padding px-5 = 20px]
   [Banner mx-5 inset 20px both sides]  ← gap 20px right edge
[ItemsTab header flex justify-between]
   [text "1 hạng mục..."]    [Button "+ Thêm hạng mục"]
```

Banner mx-5 đẩy inset 20px khỏi Section padding x-5 → tạo gap visual 20px
bên phải banner. Phía dưới gap đó là button right-aligned (full Section
width) → trông button "lệch" so với banner end + có khoảng trắng phía trên.

Fix mirror 2 app (rule §3.9):

```diff
-     <div className="mx-5 mt-2 rounded border border-violet-200 bg-violet-50 px-3 py-2 text-[11px] text-violet-800">
+     <div className="mb-3 rounded border border-violet-200 bg-violet-50 px-3 py-2 text-[11px] text-violet-800">
```

- `mx-5` → drop (banner full Section padding width)
- `mt-2` → `mb-3` (consistent spacing với ItemsTab header `mb-3` style)

Visual sau fix:
```
[Section padding px-5]
   [Banner full width]
   [ItemsTab header: text + button align Section right edge]
```

Button "+ Thêm hạng mục" align cùng phải edge với banner. KHÔNG còn gap visual.

Files (2 mirror):
- fe-user/src/components/pe/PeDetailTabs.tsx:218-223
- fe-admin/src/components/pe/PeDetailTabs.tsx:213-218

Verify:
- npm run build fe-user PASS clean (0 TS err, 7.67s)
- npm run build fe-admin PASS clean (0 TS err, 7.50s)

KHÔNG đụng BE. KHÔNG đụng logic. CSS layout polish only.

Pending: bro UAT verify layout fix + Plan P CICD Monitor verify F1+F2 wire
(spawn earlier, vẫn running).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 13:38:34 +07:00
508b17a43c [CLAUDE] FE-Admin FE-User: Chunk M3 — Rename Phase=TraLai display label "Trả lại" → "Cần chỉnh sửa lại"
Plan M Chunk M3 — UAT disconnect fix: bro UAT phát hiện label "Trả lại" + "Bản
nháp" không phân biệt rõ với end-user → rename Phase=TraLai (98) display label
sang "Cần chỉnh sửa lại" để self-descriptive.

Scope HẸP — chỉ status display reference (KHÔNG đụng action verb):
- types/purchaseEvaluation.ts × 2 app: PurchaseEvaluationPhaseLabel[98] +
  PeDisplayStatusLabel.TraLai = 2 const map rename
- components/pe/PeWorkflowPanel.tsx × 2 app: 2 inline literal hardcode trong
  F1 dialog tooltip `Phase → "..."` + confirm message `Phiếu sẽ về "..."` —
  status reference, KHÔNG phải action verb

KHÔNG đụng:
- Action button label `← Trả lại` (verb, giữ nguyên)
- F1 mode picker label `Trả về Người soạn thảo` (4 mode action, giữ nguyên)
- Comments narrative giữ rationale dev (rule §6.5)
- types/contracts.ts + types/budget.ts Phase 98 'Trả lại' — module Contract +
  Budget khác PE, ngoài scope M3

Mirror 2 app rule §3.9 strict applied.

Verify:
- npm run build fe-admin PASS clean (0 TS err, 9.40s)
- npm run build fe-user PASS clean (0 TS err, 6.92s)

Diff: +4/-4 LOC × 4 file = 8 LOC total

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:15:01 +07:00
83c9f7b45d [CLAUDE] PurchaseEvaluation FE-Admin FE-User: Chunk L5 — PE list UX: ngày tạo thay SLA countdown + sort UpdatedAt DESC
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m10s
Bro UAT S23 t2 yêu cầu 2 UX changes PE list:

1. Đổi "Còn N ngày Mh" (SlaTimer countdown) → "DD/MM/YYYY HH:mm" (ngày giờ tạo phiếu).
2. Sort: phiếu vừa update (Tạo / Gửi duyệt / Trả lại) đưa lên đầu, phiếu cũ phía dưới.

BE changes:
- PurchaseEvaluationListItemDto +UpdatedAt: DateTime? field (auto AuditingInterceptor refresh
  mọi SaveChanges — covers Insert/Update/Transition events natural).
- ListPurchaseEvaluationsQueryHandler sort: OrderByDescending(UpdatedAt ?? CreatedAt)
  (was: OrderByDescending(CreatedAt)).
- GetMyPurchaseEvaluationInboxQueryHandler sort: OrderByDescending(UpdatedAt ?? CreatedAt)
  (was: OrderBy(SlaDeadline ?? MaxValue) — SLA priority deprecated).
- CreateContractFromEvaluationFeatures.cs: +UpdatedAt arg trong DTO ctor (compile fix
  consumer downstream).
- Select projection 3 callsites populate UpdatedAt.

FE × 2 app (mirror rule §3.9):
- PeListItem type +updatedAt: string | null (optional — null khi phiếu chưa Update).
- PurchaseEvaluationsListPage: replace <SlaTimer deadline={p.slaDeadline} ... /> với
  Vietnamese date format "{DD/MM/YYYY HH:mm}" qua Intl.DateTimeFormat (vi-VN locale,
  full date+time options). title tooltip hiện full timestamp.
- Remove SlaTimer import (unused warning).

UpdatedAt sort logic insight: AuditingInterceptor (Infrastructure) auto-refresh
UpdatedAt mọi SaveChanges → mọi event tự nhiên (Drafter tạo / Gửi duyệt từ Workspace
/ Approver duyệt Cấp tiếp / Approver trả lại / Admin override) đều bump UpdatedAt
→ phiếu vừa action lên đầu list. Phiếu mới Insert UpdatedAt=null → fallback CreatedAt
→ vẫn lên đầu (vì CreatedAt vừa now).

Verify:
- dotnet build production projects clean (0 err, 2 pre-existing warn)
- dotnet test SolutionErp.slnx 104/104 PASS (DTO change KHÔNG impact test — tests
  don't construct ListItemDto)
- npm run build × 2 app pass clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:53:19 +07:00
f212f04365 [CLAUDE] FE-Admin FE-User: Chunk L3 — Fix Trả lại dialog default mode = first available F1 (mode đang gửi duyệt)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 48s
Bro UAT S23 t2 catch screenshot: tick AllowReturnToAssignee + untick AllowReturnToDrafter
cho slot Approver → user click "Trả lại" → dialog mở với default state `returnMode=Drafter`
(S17 backward compat fallback). Radio Drafter HIDDEN vì allowReturnToDrafter=false
→ user thấy radio Assignee đã pick + Bùi Lê Thủy Trà từ dropdown → click Xác nhận →
BE receive `returnMode: 4` (Drafter từ initial state) → throw "Cấp Approver hiện tại
không bật mode 'Drafter'. Liên hệ Admin Designer".

Bro intent: "cho duyệt trong muốn cho trả lại trong mode đang gửi duyệt chứ ko phải
draft, draft chỉ khi trả lại cho người soạn thôi" — 3 F1 modes (OneLevel/OneStep/
Assignee) là "trả lại trong mode đang gửi duyệt" (Phase=ChoDuyet lùi pointer);
Drafter mode = trả về Người soạn (Phase=TraLai), CHỈ default khi không có F1 nào.

Fix FE × 2 app PeWorkflowPanel.tsx (mirror rule §3.9):
- Import useEffect
- useEffect khi target=TraLai → compute first available F1 mode:
  - allowReturnOneLevel ? OneLevel
  - : allowReturnOneStep ? OneStep
  - : allowReturnToAssignee ? Assignee
  - : Drafter (fallback)
- setReturnMode(firstAvailable)

→ Dialog mở với mode đúng selected → user click Xác nhận → BE receive correct
mode → ApplyReturnModeAsync check correct flag → PASS.

Pattern lesson saved: dialog initial state phải compute từ permission flags
KHÔNG hardcode default — admin có thể disable mọi mode khác Drafter, hoặc
ngược lại enable F1 only.

Verify:
- npm run build × 2 app pass (0 TS err)
- Bundle hash rotate × 2 app

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:44:05 +07:00
10ddc8761b [CLAUDE] FE-Admin FE-User: Chunk L2 — Fix F4 BudgetAdjustSection bypass readOnly khi Approver scope (menu Duyệt)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m1s
Bro UAT S23 t2 catch: "Đã stick cho edit trong luồng duyệt nhưng trong menu
duyệt -> vẫn không edit đc ngân sách". Investigator audit root cause:
- BudgetAdjustSection canAdjust = !readOnly && (...) — `!readOnly` short-circuit
  block F4 logic
- Menu Duyệt route truyền readOnly=true xuống PeDetailTabs → button "Điều chỉnh"
  hidden dù admin đã tick AllowApproverEditBudget cho slot + actor match
- F3 wire ItemsTab ĐÚNG via `itemsReadOnly = readOnly && !approverEditMode`
  pattern bypass — F4 không follow same pattern

Refactor canAdjust × 2 app (rule §3.9 mirror):
```
- canAdjust = !readOnly && (isAdmin || (isDrafter && isDrafterPhase) || isApproverChoDuyet)
+ canAdjust = isAdmin
+   || (!readOnly && isDrafter && isDrafterPhase)
+   || isApproverChoDuyet
```

→ F4 Approver scope (Mig 30) BYPASS readOnly:
- Admin: bypass readOnly (full quyền)
- Drafter (Nháp/TraLai): chỉ Workspace (readOnly=false)
- Approver ChoDuyet + flag tick + actor match: bypass readOnly → button "Điều chỉnh"
  visible trong menu Duyệt

Mirror F3 pattern (itemsReadOnly line 118). F4 wire S22+5 ban đầu miss BYPASS
pattern — fixed S23 t2.

Verify:
- npm run build × 2 app pass (0 TS err, bundle hash rotated)
- Bro UAT verify: tick F4 → vào menu Duyệt → click "Điều chỉnh ngân sách"
  → modal open editable

Pattern lesson saved memory: per-NV admin opt-in flag wire RULE — FE bypass
readOnly khi flag tick + actor match + phase match (mirror F3 itemsReadOnly).
F4 BudgetAdjustSection retroactive fix.

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