Commit Graph

88 Commits

Author SHA1 Message Date
350b2bfb28 [CLAUDE] FE-Admin/FE-User: login subtitle contrast a11y fix (slate-500 -> 600)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m12s
FD2 spawn-test outcome — login subtitle contrast ~4.6 -> ~7.5:1 over translucent
card (FD5 WCAG-AA floor). On-scale token, no layout shift. Mirror x2 app.
Build x2 PASS, 0 TS error. Test 181 unchanged (FE-only, no .cs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 00:06:18 +07:00
82d7fcff4d [CLAUDE] Workflow: LeaveBalance business logic — trừ phép khi duyệt + số dư (Phase 11 P11-B)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m8s
Số dư phép theo (User × LeaveType × Year) + trừ tự động khi đơn nghỉ duyệt cuối.
Policy: cho phép vượt số dư (âm) + cảnh báo (anh main chốt), tích hợp vào trang đơn nghỉ.

Schema (Mig 42 AddLeaveBalances — pure additive, 1 bảng):
- LeaveBalance: UserId + LeaveTypeId + Year + EntitledDays + UsedDays + AdjustmentDays.
  UNIQUE (UserId,LeaveTypeId,Year), FK LeaveType Restrict, decimal(5,2).
  Remaining = Entitled + Adjustment − Used (computed, không store).

Deduction hook (ApproveLeaveRequestHandler nhánh terminal DaDuyet — exactly-once):
- Upsert LeaveBalance(RequesterUserId, LeaveTypeId, StartDate.Year), auto-create từ
  LeaveType.DaysPerYear, UsedDays += NumDays. Guard Status!=DaGuiDuyet chặn re-approve.

FK invariant guard (em main thêm sau test reveal FK risk):
- Create + UpdateDraft validate LeaveTypeId tồn tại (AnyAsync) → ConflictException.
  Đóng cửa vào — bogus type không thể tới deduction FK insert (tránh 500 kẹt đơn).

CQRS LeaveBalanceFeatures.cs: GetMy (self, lazy merge active LeaveType) + GetUser (admin)
  + AdjustLeaveBalance (admin upsert carry-over). Controller [Authorize] + admin Roles=Admin.
Embed: GetLeaveRequestByIdHandler trả balance NGƯỜI TẠO (approver xem thấy đúng).
FE: WorkflowAppDetailPage ×2 — block "Số dư phép" + cảnh báo vượt khi kind=leave (SHA256 identical).

Tests (+11, 130→154 PASS): deduction single/multi-level/accumulate/negative-allowed/
  reject-return-no-deduct + lazy-merge + adjust upsert + Create guard bogus→Conflict.
  Cũng repair 2 test S42 terminal FK-fail (template BuildLeave +seed LeaveType).

Verify: build 0 error · 154 test · FE ×2 · reviewer Max PASS (deduction exactly-once +
  FK invariant fully closed, 2 minor concurrency/comment defer).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 11:10:44 +07:00
e47ef1d4ee [CLAUDE] FE-User: fix ProposalCreatePage workflow dropdown shape (latent S37 bug)
Follow-up cho bug flagged trong 75df04e. ProposalCreatePage (fe-admin + fe-user)
fetch /approval-workflows-v2 expect flat array, nhưng endpoint trả AwAdminOverviewDto
{types:[{applicableType, history:[...]}]} → workflows.data.map chạy .map trên object
→ TypeError/dropdown rỗng. Latent vì create-with-workflow chưa exercise UAT.

Fix: mirror pattern proven của ContractCreatePage + WorkflowAppDetailPage —
res.data.types.find(t=>t.applicableType===4)?.history.filter(isUserSelectable).
WorkflowDto +isUserSelectable. Render .map giữ nguyên (data giờ là array đúng).

Verify: tsc -b + vite build ×2 PASS (exit 0) · fe-admin/fe-user SHA256 identical
(91565D47...7697C771).

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:32:56 +07:00
130903fe1b [CLAUDE] FE-Admin+FE-User: PE Danh sách bỏ button "+ Tạo phiếu mới" header
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m8s
User chỉ thị: bỏ hết button tạo phiếu mới góc phải màn hình.

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:41:33 +07:00
d15398fafe [CLAUDE] Domain+FE: PE thêm phase TraLai + pencil always visible + edit gating
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 2m0s
User feedback 2026-05-07:
1. Pencil edit icon LUÔN hiện (không chỉ hover) trong workspace Panel 1
2. Pencil sáng (active brand-color) khi phase editable, xám/disabled khi không
3. Click pencil khi sáng → row sáng + auto-open edit toàn bộ (header + detail)
4. Thêm phase mới "Trả lại" — approver gửi về Drafter sửa (vs Từ chối terminal)
5. Edit chỉ cho 2 trạng thái: Đang soạn thảo + Trả lại
   (Từ chối + Đã gửi duyệt + Đã duyệt → không edit/thao tác gì)

Implementation:
  ~ Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs
    + TraLai = 98 (giữa DaDuyet=7 và TuChoi=99)
    Comment ghi rõ "approver trả về Drafter sửa, vẫn cho edit, khác TuChoi"
  ~ types/purchaseEvaluation.ts (× 2 app)
    + PurchaseEvaluationPhase enum: TraLai = 98
    + PurchaseEvaluationPhaseLabel/Color cho TraLai (yellow)
    + isEditablePhase(phase) helper: true cho DangSoanThao + TraLai
    + PeDisplayStatus thêm "TraLai" (separate, không gộp DaGuiDuyet)
    + getPeDisplayStatus map TraLai → "Trả lại" badge yellow
  ~ components/pe/PeListPanel.tsx (× 2 app)
    - Pencil icon: bỏ opacity-0 hover-only → LUÔN visible
    - editable=isEditablePhase(p.phase): bright text-brand-600 + cursor-pointer
    - !editable: text-slate-300 + cursor-not-allowed + onClick guard ignored
    - title tooltip rõ ràng "đã gửi duyệt / đã duyệt / từ chối — không sửa được"
    - Bỏ forcedPhase prop → editableOnly prop (filter client-side cả 2 phase
      DangSoanThao + TraLai vì BE chưa support multi-phase param)
    - Khi editableOnly: hiển thị "Lọc cố định: Bản nháp + Trả lại" indicator
  ~ components/pe/PeDetailTabs.tsx (× 2 app)
    - Header bar: isDraft → canEditPhase = isEditablePhase(phase)
    - InfoTab: canEdit = !readOnly && isEditablePhase
    - BudgetFieldRow: canEdit = !readOnly && isEditablePhase
    → Đồng nghĩa Drafter sửa được phiếu Trả lại sau approver send back
  ~ pages/pe/PurchaseEvaluationWorkspacePage.tsx (× 2 app)
    - PeListPanel forcedPhase=DangSoanThao → editableOnly
    - Bỏ import PurchaseEvaluationPhase

Workflow service BE chưa wire transition → TraLai (defer — user sẽ thêm button
"Trả lại" trong PeWorkflowPanel duyệt sau, hoặc dùng API PATCH manual). Phase
TraLai chỉ là enum value sẵn sàng FE hiển thị + BE ánh xạ HasConversion<int>.

UAT mode: skip verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:38:46 +07:00
0c5db1385f [CLAUDE] FE-Admin+FE-User: PE display status meta — Bản nháp / Đã gửi duyệt / Đã duyệt / Từ chối
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m59s
User feedback 2026-05-07: thêm 2 trạng thái meta hiển thị "Bản nháp" + "Đã
gửi duyệt". Bản nháp chỉ hiện ở Thao tác workspace, không hiện ở Duyệt menu.

Implementation:
  ~ types/purchaseEvaluation.ts
    + PeDisplayStatus enum (BanNhap / DaGuiDuyet / DaDuyet / TuChoi)
    + PeDisplayStatusLabel + PeDisplayStatusColor
    + getPeDisplayStatus(phase) helper:
        DangSoanThao → BanNhap
        DaDuyet → DaDuyet
        TuChoi → TuChoi
        else (any middle phase) → DaGuiDuyet
  ~ components/pe/PeListPanel.tsx
    - Phase Select filter → Display status Select (4 option, "Đã gửi duyệt"
      KHÔNG filter exact phase do multi-phase, để client-side TODO BE)
    - Row badge dùng display status (gọn 4 màu)
    + Prop forcedPhase?: number — workspace dùng để khóa filter Bản nháp
      (DangSoanThao). Khi forcedPhase set: ẩn Select, show "Lọc cố định: Bản
      nháp" indicator.
  ~ components/pe/PeDetailTabs.tsx
    - Header badge dùng display status meta + secondary text "(Phase chi tiết)"
      nhỏ bên cạnh để approver/dev vẫn biết phase exact
  ~ pages/pe/PurchaseEvaluationsListPage.tsx
    - Phase filter Select → display status options
    - Row badge → display status
  ~ pages/pe/PurchaseEvaluationWorkspacePage.tsx
    - PeListPanel forcedPhase={PurchaseEvaluationPhase.DangSoanThao}
      → workspace chỉ list Bản nháp (đúng UX user yêu cầu)

Workflow timeline Panel 3 + workflow service BE KHÔNG đổi (giữ phase chi tiết
DangSoanThao/ChoPurchasing/ChoCCM/etc cho approval logic).

Pe_*_Pending Duyệt: dùng /inbox endpoint vốn đã filter chỉ phiếu cần user duyệt
→ DangSoanThao auto-không xuất hiện (không có active approver). Nên Bản nháp
auto-hidden từ Duyệt menu, không cần filter thêm.

UAT mode: skip verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:27:29 +07:00
7dfeb1a486 [CLAUDE] FE-Admin+FE-User: PE Danh sách view — disable toàn bộ tương tác
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
User feedback 2026-05-07: "Pe_*_List Danh sách disable toàn bộ tương tác, chỉ
show thông tin." Pending (Duyệt) vẫn cho approver chuyển phase + sign opinion.

Implementation:
  ~ PurchaseEvaluationsListPage.tsx (× 2 app)
    - PeDetailTabs readOnly={true} hardcoded (was readOnly={pendingMe})
      → ẩn "Sửa header" / "Xóa" / inline edit Section 1 / BudgetFieldRow edit /
        SuppliersTab edit / ItemsTab edit / OpinionBox sign forms ở MỌI view
        (Danh sách + Duyệt — Duyệt vẫn dùng được vì opinion sign ko phải in
        ListPage scope, ý kiến nhập ở leaf khác per session 11)
    - PeWorkflowPanel readOnly={!pendingMe}
      → Danh sách: hide "Chuyển tiếp" buttons + Dialog
      → Duyệt: vẫn show transition buttons (giữ cho approver work)
  ~ PeWorkflowPanel.tsx (× 2 app)
    - Add prop `readOnly?: boolean` default false
    - Khi readOnly=true: hide Chuyển tiếp section + transition Dialog
    - Show hint "Vào menu Duyệt để chuyển phase" thay vì button

Tạo phiếu mới button GIỮ ở header List page (không phải tương tác trên data
hiện có, là navigation tới create flow workspace).

UAT mode: skip verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:12:13 +07:00
66fa4691dc [CLAUDE] FE-Admin+FE-User: PE workspace "new" mode — sectioned create view 5 sections
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m4s
User feedback 2026-05-07: "Thêm mới list ra hết trường dữ liệu giống chỉnh sửa
nhưng trống, mở rộng từng phần. Save header xong mới cho nhập chi tiết."

Implementation:
  + PeWorkspaceCreateView.tsx (~230 LOC, mirror fe-admin + fe-user)
    - Sectioned card layout giống PeDetailTabs (5 section divider + title style)
    - Section 1 "Thông tin gói thầu": editable inputs (Loại / Tên */Dự án */Địa
      điểm/Mô tả/Payment) — 2-col grid responsive
    - Section 2 "Chọn NCC/TP":
      a. NCC chọn: text "(sau khi thêm NCC + chốt winner)" placeholder
      b. Ngân sách: editable inline (toggle "Nhập tay" + Select OR 2 input —
         giống BudgetFieldRow pattern)
      c. Giá chào thầu: text "(auto-tính sau winner)" placeholder
      d. Bản so sánh: LockedHint icon + text "Tải sau khi tạo"
    - Section 3 "NCC tham gia (0)": LockedHint "Lưu phiếu trước → thêm NCC..."
    - Section 4 "Hạng mục + Báo giá (0)": LockedHint
    - Section 5 "Ý kiến 4 PB": amber banner "nhập khi duyệt"
    - Action bar bottom: "Tạo phiếu" (disabled khi !tenGoiThau || !projectId)
      + Hủy
    - POST /pe full payload (header + budget mode A or B). onSuccess: toast +
      invalidate pe-list + onSaved(id, type) callback
  ~ PurchaseEvaluationWorkspacePage.tsx (× 2 app)
    - Replace <PeHeaderForm> → <PeWorkspaceCreateView> trong mode='new'
    - PeHeaderForm vẫn còn (dùng cho /new?id= deep-link "Sửa header" cũ)

Helpers duplicate trong PeWorkspaceCreateView (Section + FormRow + LockedHint)
để tránh circular import từ PeDetailTabs.

UAT mode rule applied (per memory feedback_uat_skip_verify): skip dotnet test
+ npm build verify, push ngay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:06:31 +07:00
5a89dd2188 [CLAUDE] FE-Admin: PE InfoTab inline edit Section 1 + PeListPanel pencil edit hover
User feedback 2026-05-07: muốn thêm nút edit kế bên row trong Panel 1, click →
Panel 2 sáng nội dung Section 1 lên cho user sửa header inline (KHÔNG cần đi
"Sửa header" page). Cũng muốn create new interface gần giống detail view
sectioned (defer cho chunk sau, hoặc keep PeHeaderForm nếu user OK).

Implementation:
  ~ PeDetailTabs.tsx
    - InfoTab thêm prop `readOnly` + `autoEdit` (trigger edit mode tự động khi
      mount nếu URL ?editHeader=1)
    - canEdit = !readOnly && isDraft (DangSoanThao):
      → display mode: hiển thị FormRow + button "✎ Sửa" góc trên phải Section 1
      → editing mode (click Sửa hoặc autoEdit): card border brand-200 + 4 input
        (Tên * / Dự án disabled / Địa điểm / Mô tả / Payment) + nút Lưu/Hủy
    - Save: PUT /pe/:id full payload (current ev values + new editable fields).
      onSuccess: invalidate ['pe-detail', 'pe-list'] + setEditing(false)
    - PeDetailTabs prop `autoEditHeader` mới — pass-through xuống InfoTab
  ~ PeListPanel.tsx
    - Thêm prop `onEditClick?: (id) => void`
    - Pencil icon (lucide) absolute right-2 top-2 trong mỗi <li>, opacity-0
      group-hover:opacity-100 — chỉ hiện khi user hover row + onEditClick set
    - Click → trigger onEditClick(id) callback (different from row click)
  ~ PurchaseEvaluationWorkspacePage.tsx
    - Đọc URL ?editHeader=1 → pass autoEditHeader xuống PeDetailTabs
    - PeListPanel onEditClick → setParams({ id, mode: null, editHeader: '1' })
    - onSelect (click row thường) → editHeader: null (clear flag)
    - onBack → clear editHeader

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

Pending: workspace "new" mode wrap PeHeaderForm trong sectioned layout giống
detail view (defer — user có thể chấp nhận PeHeaderForm hiện tại nếu OK).

Next: Chunk 2 fe-user mirror.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:55:49 +07:00
bab503189a [CLAUDE] FE-Admin: PE + HĐ toggle "Nhập tay" + 2 fields manual budget (Mig 17)
Chunk 3/5 — UI cho manual budget fallback Mig 17. Toggle checkbox "Nhập tay
(không link)" cạnh Label Ngân sách. Khi ON: hide Select Budget, show 2 input
field grid 2-col (Tên tham chiếu text + Số tiền number formatted VND).

Files sửa:
  ~ fe-admin/src/types/purchaseEvaluation.ts — PeDetailBundle +2 field
  ~ fe-admin/src/types/contracts.ts — ContractDetail +2 field
  ~ fe-admin/src/components/pe/PeHeaderForm.tsx — toggle + 2 input + payload
    conditional (manual mode → clear budgetId, link mode → clear manual). Auto-
    detect manual mode khi load existing PE có manual data + !budgetId.
  ~ fe-admin/src/components/pe/PeDetailTabs.tsx — Section 2 "b. Ngân sách"
    fallback display khi !ev.budget + có manual data: render text "Tên · Số tiền"
    + badge "nhập tay" thay vì "(chưa link)".
  ~ fe-admin/src/pages/pe/PurchaseEvaluationCreatePage.tsx — refactor wrap
    PeHeaderForm để DRY (auto-inherit toggle pattern, không drift). 222 LOC → 30 LOC.
  ~ fe-admin/src/pages/contracts/ContractCreatePage.tsx — apply same toggle
    pattern cho cả NewContractForm + EditContractForm. EditForm thêm read-only
    display branch khi !isDraft + có manual data.

Verify: npm run build fe-admin pass · 1922 modules · không TS error.

Next: Chunk 4 fe-user mirror (PeHeaderForm + PeDetailTabs + ContractCreatePage
+ types).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:37:42 +07:00