Commit Graph

58 Commits

Author SHA1 Message Date
6df1b2d7c1 [CLAUDE] FE-PE: Link hồ sơ auto-detect — http(s) -> hyperlink bấm-mở / đường dẫn ổ mạng -> chữ + nút Copy (x2 app SHA256)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m22s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:28:51 +07:00
5a0aaa4e83 [CLAUDE] PurchaseEvaluation: +mục E "Link hồ sơ" (hyperlink NAS) + rename "Dự trù PRO"->"Ngân sách PRO"
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m25s
Anh Kiệt (FDC UAT): (1) thêm mục "e. Link hồ sơ" dưới mục "d. Bản so sánh" — 1 ô dán
hyperlink tới thư mục hồ sơ trên NAS công ty, hiện dạng <a> bấm-mở (target _blank rel
noopener noreferrer), null-safe; (2) đổi nhãn "Dự trù PRO"->"Ngân sách PRO" (cả badge
+ row label; GIỮ "Ghi chú từ PRO" + field-code/biến).
- BE: PurchaseEvaluation +HoSoLink string? (Mig AddHoSoLinkToPurchaseEvaluation —
  nvarchar(1000) nullable, no new table, Down reversible) + Create/Update command
  (+trailing optional =null -> backward-compat, 0 call-site break) + Detail DTO + projection.
  Build slnx PASS.
- FE x2 app SHA256 mirror (PeDetailTabs + PeWorkspaceCreateView): mục E input/hyperlink +
  rename. types +hoSoLink.
Workflow fan-out (BE song song FE -> review); FE+reviewer return-rỗng -> em main recover
disk + build-verify x3 + self-gate (bắt badge sót rename).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 11:13:39 +07:00
7926c2129c [CLAUDE] PurchaseEvaluation: cho luu khi vuot ngan sach - chi canh bao mem (bo chan so am "Gia tri thuc hien du kien con lai") (UAT anh Kiet S62)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m40s
- Root cause: o "Gia tri thuc hien du kien con lai" (row 8 bang Tong hop ngan sach) khi gia tri NCC vuot ngan sach -> so du con lai ra AM; BE validator ExpectedRemainingAmount>=0 + FE VndInlineEdit khong bat allowNegative -> chan cung "am ko luu duoc" (testing bao qua anh Kiet)
- BE: AdjustPurchaseEvaluationBudgetCommandValidator GO rule ExpectedRemainingAmount.GreaterThanOrEqualTo(0) -> cho luu so am (mirror tien le LeaveBalance AllowsNegativeRemaining). GIU BudgetPeriodAmount>0 + submit-guard "da nhap NS ky nay" khong doi
- FE x2 app SHA256 identical: (a) allowNegative cho VndInlineEdit row 8; (b) banner amber "Vuot ngan sach - van luu & gui duyet duoc" trong PeBudgetSummaryTable khi cmpPeriod<0 || cmpFull<0. Tang to mau do cu GIU NGUYEN
- Spec change: flip test AdjustBudget_Validator_ExpectedRemainingNegative_FailsValidation -> _PassesValidation (am gio hop le); test BudgetPeriodZero_FailsValidation GIU (budget>0 van enforced)
- Build FE x2 PASS + test 263 PASS (45 Domain + 218 Infra, 0 fail/skip). Reviewer PASS 0 issue (row8 am an toan arithmetic additive-only, submit guard nguyen, mirror byte-identical, no scope creep)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 11:13:10 +07:00
79ef8da9f4 [CLAUDE] PurchaseEvaluation: ngan sach goi thau theo Excel anh Kiet - bang tong hop 2 block + nhap theo role PRO/CCM + xoa module Budget cu (Mig 50)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m31s
- Mig 50 ReplaceBudgetModuleWithPeWorkItemBudgets: bang moi PeWorkItemBudgets (1 record/cap Du an x Hang muc, UNIQUE filtered [IsDeleted]=0) + drop 5 bang Budget cu + PE/Contracts drop BudgetId + backfill BudgetManualAmount->BudgetPeriodAmount TRUOC DropColumn (phieu UAT giu so) + DELETE menu/permission Bg_* IN-list children-first
- BE: PUT {id}/budget/pro (role Procurement) + {id}/budget/ccm (role CostControl, Adjustment cho phep AM) fail-closed Forbidden-truoc-side-effect + EnsureTrackedAsync race-safe (catch unique -> re-fetch winner, loi khac rethrow) + auto-create record khi tao phieu + budgetSummary DTO (luy ke trinh-truoc/chon-thau-truoc/de-xuat-ky-nay + full fallback du-tru-PRO + canEdit flags) + submit-guard (3) doi predicate BudgetPeriodAmount -> "chua nhap Ngan sach ky nay" + PATCH budget-adjust absolute-set 2 field moi + Contract GIU BudgetManual* (HD nhap tay khong doi) + ke thua HD map BudgetPeriodAmount
- FE x2 app SHA256 identical: bang "TONG HOP NGAN SACH TRINH KY" block A (full dam + ban hanh + V0 hieu chinh + du tru PRO + ghi chu, editable theo canEditPro/canEditCcm) + block B 9 dong cong thuc Excel (5=1+3, 6=2+4, 7=full-5, 8 tu nhap default 7, 9=4+8) + to mau vuot ngan sach #C00000 / am do / red-soft row8>row7 + "Chua chon" khi count=0 + banner phieu chua gan Hang muc + o "Ngan sach ky nay" o create/header + XOA pages/components/types budgets + routes + menuKeys + Layout staticMap 4-place
- Tests: +22 PeWorkItemBudgetTests (auto-create x3, ensure/race x2, authz matrix PRO x5 + CCM x3, budgetSummary aggregates x5, adjust x4) - 14 BudgetPolicyTests xoa theo module - 1 test via-BudgetId -> 263 PASS (45 Domain + 218 Infra, 0 fail)
- database-agent advise adopted: khong FK vat ly PE/Contracts->Budgets (DropColumn khong can DropForeignKey) + DropIndex truoc DropColumn (SQL 5074) + IN-list thay LIKE Bg_% (underscore wildcard + miss root) + khong Serializable wrap (nested-tx conflict codegen)
- Reviewer PASS-with-minor 0 blocker (verdict-first survived); 2 minor da sua truoc commit (comment adjustMut absolute-set + dead key budgetId); note: F4 approver-edit-budget UI entry tam drafter-only, BE van cho approver scope - cho UAT anh Kiet
- Scaffold-bug caught: EF tu sinh RenameColumn BudgetManualAmount->ExpectedRemainingAmount (SAI semantics) -> thay bang Add+UPDATE+Drop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 01:07:27 +07:00
37122f0f64 [CLAUDE] PurchaseEvaluation: rang buoc du 4 thong tin muc 3 moi gui duyet + bypass nguoi soan trong chuoi duyet (UAT anh Kiet S60)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m38s
- Rename muc 3: "Chon NCC / TP thang thau" -> "Don vi NCC/TP duoc chon" (anh Kiet chot chu) x2 app + wording phu nhat quan
- Guard gui duyet du CA 4 (anh chot): don vi duoc chon + gia chao thau >0 + ngan sach (Budget link HOAC nhap tay) + bang so sanh dinh kem
  + BE ConflictException gop moi muc thieu 1 lan, ap ca Admin (TransitionAsync submit branch)
  + FE pre-check missingForApproval cung predicate -> disable nut + tooltip liet ke du (computeGiaChaoThau extract single-source)
- Bypass drafter-in-chain (luat GENERIC theo cap, anh chot): V2-only, BUOC DAU only - nguoi soan la approver cap k -> auto qua Cap 1..k khi gui
  + Audit 3 tang: Approval row AutoApprove per cap + LevelOpinion CHI slot chinh chu (khong gan chu ky NV bi skip) + Changelog
  + Pointer: k<max -> Cap k+1; het buoc -> Buoc 2 Cap 1; workflow 1 buoc -> terminal DaDuyet
  + TraLai resubmit ap lai idempotent (opinion UPSERT)
- Tests: +14 PeSubmitGuardAndBypassTests (240 -> 254 PASS)
- Reviewer die mid-run (gotcha #53 class) -> em main self-gate evidence-checklist PASS 0 blocker

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 11:53:50 +07:00
792c0307e9 [CLAUDE] PurchaseEvaluation: bo nut "+ Them hang muc" Section 2 (UAT vong 6 - anh chot)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m0s
- 1 phieu = 1 hang muc chon tu header (S57bis/S58, hang muc dau auto-seed khi tao
  phieu) -> nut them hang muc thu 2+ sai mo hinh nghiep vu, go khoi ItemsTab header.
- AddItemDialog giu (dead code) de flip lai de neu doi y.
- SHA256 mirror x2 app IDENTICAL, build x2 PASS.
2026-06-11 18:37:27 +07:00
80b64dd514 [CLAUDE] PurchaseEvaluation: go field "Dieu khoan thanh toan" khoi TAT CA form phieu (UAT vong 5 - anh chot "bo not ra luon tat ca cac form")
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
- PeWorkspaceCreateView: bo Select preset + Textarea custom + PAYMENT_PRESETS/
  PAYMENT_CUSTOM/paymentMode drop, payload paymentTerms: null.
- PeHeaderForm + PeDetailTabs inline-edit: bo Textarea; state paymentTerms GIU
  (load tu phieu cu + save giu nguyen -> data cu KHONG mat, van hien read-only).
- GIU cot "Dieu khoan TT" per-NCC trong bang so sanh (data tung NCC, khac field).
- SHA256 mirror x2 app IDENTICAL, build tsc+vite x2 PASS.
2026-06-11 18:33:33 +07:00
69997da74f [CLAUDE] PurchaseEvaluation: bo o "Ten ngan sach" o manual budget (UAT vong 4 - anh chot "cho ten ngan sach bo di nhe")
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m23s
- PeDetailTabs Section 5 Dieu chinh ngan sach: bo input "Ten (khong bat buoc)"
  (user khong hieu "y nghia du phong la gi") - manual budget chi con So tien (VND).
  State manualName drop, payload budgetManualName: null. Ten cu phieu truoc van
  hien read-only, ve null khi Luu dieu chinh lan toi.
- PeHeaderForm: payload budgetManualName null + hasManual detect theo CA amount
  (phieu moi name=null sau khi bo o Ten -> van nhan dung manual mode).
- PeWorkspaceCreateView: khong doi (chua tung co o Ten, payload '' || null = null san).
- SHA256 mirror x2 app IDENTICAL, build tsc+vite x2 PASS.
2026-06-11 18:22:26 +07:00
f21c55d04a [CLAUDE] PurchaseEvaluation: bang NCC tham gia table-fixed + width tung cot (UAT vong 3 - "them file giao dien bi thay doi khong can xung")
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
- Bang NCC trong HangMucCard dung auto-layout -> cell File (chip ten file dai) phinh,
  bop doc cot NCC tung chu. Fix: table-fixed + w-% tung cot (NCC 24 / SDT 9 / Email 14 /
  DKTT 14 / File 22 / So tien 12 / action w-10) + min-w-[860px] (panel hep -> scroll
  ngang qua wrapper overflow-x-auto thay vi bop nat) + email span block truncate.
- Chip file/email truncate san - kich hoat khi cell khoa width.
- SHA256 mirror x2 app IDENTICAL, build tsc+vite x2 PASS.
2026-06-11 18:17:56 +07:00
9c330d26c4 [CLAUDE] PurchaseEvaluation: UAT dot 2 - an Tra lai/Tu choi khi tu duyet phieu minh soan + quick-add NCC ngay form + NCC go-tim sort A-Z + upload nhieu file 1 lan
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m25s
- PeWorkflowPanel x2: nguoi duyet == nguoi soan (drafterUserId == currentUser.id)
  -> an ca "Tra lai" + "Tu choi" (anh chot: tra cho chinh minh vo nghia, huy phieu
  = nho cap khac tu choi / xoa phieu Nhap).
- SuppliersController: POST tao NCC mo cho moi user dang nhap (anh chot - nghiep vu
  di thau phat sinh NTP moi lien tuc); PUT/DELETE van khoa Admin+CatalogManager (S57).
- PeDetailTabs AddSupplierDialog x2: Select -> SearchableSelect (go-tim bo dau,
  sort A-Z theo ma) + nut "+ NCC moi" quick-create (Ma/Ten/Loai/SDT/Email) ->
  POST /suppliers -> auto-select vao phieu.
- Upload file bao gia + bang so sanh x2: input multiple + upload tuan tu tung file
  (UAT "moi lan chi chon duoc 1 file").
- SHA256 mirror x2 app, build tsc+vite x2 PASS, BE 0 err, test 240/240 PASS local.
2026-06-11 17:51:28 +07:00
faed59f4c4 [CLAUDE] FE-Admin+FE-User: UX form tao phieu theo UAT feedback - combobox go-de-loc (Hang muc + Du an) + auto dia diem + dieu khoan da dong
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m31s
- NEW ui/SearchableSelect: combobox tu render, go loc theo label, match BO DAU
  tieng Viet (go "be tong" trung "Be tong"), keyboard arrows/Enter/Esc, clear (x),
  style mirror ui/Input density S55. Khong them lib ngoai.
- PeWorkspaceCreateView + PeHeaderForm: Hang muc + Du an doi Select -> SearchableSelect
  (UAT: "nen co loc de tu danh chu" / "nen co tu go chu" - 70+ muc kho do).
- Auto dia diem (UAT "dia chi nen tu auto"): chon Du an tu dien diaDiem tu
  Project.Location (S55), chi ghi de khi user chua go tay (track lastAutoLoc ref).
- Dieu khoan thanh toan nhap tay: Input 1 dong -> Textarea 3 dong (UAT "khong cho
  xuong dong?") o CreateView + PeDetailTabs inline-edit; render detail da pre-wrap san.
- SHA256 mirror x2 app (4 file IDENTICAL), build tsc+vite x2 PASS.
2026-06-11 17:39:17 +07:00
dd117b749c [CLAUDE] PurchaseEvaluation: PE gắn Hạng mục công việc (Mig 49) + mở quyền Pe all-role + menu Cá nhân + khóa 14 demo user
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m24s
Sếp chốt deadline 15:00 (Zalo 11:02-11:17): flow tạo phiếu chọn quy trình → dự án → HẠNG MỤC → NCC/TP; phiếu dạng «Dự án – Hạng mục»; all-user thấy Duyệt NCC + master config; clear data cũ.

- Mig 49 AddWorkItemToPurchaseEvaluation: PE.WorkItemId Guid? loose-Guid + index (KHÔNG FK vật lý — convention PE, database-agent design). Validator NotEmpty (create) + FK-guard AnyAsync(IsActive) → Conflict + UpdateDraft NULL-SAFE (client không gửi → giữ, chống null-hóa bug-class S42). 3 projection ListItemDto LEFT-join WorkItems.
- FE ×2 app: PeWorkspaceCreateView select «c. Hạng mục *» + PeHeaderForm (load existing + PUT gửi lại, SHA256 IDENTICAL) + PeDetailTabs (header «Dự án – Hạng mục» + FormRow + inline khóa) + types. Route reuse /catalogs/work-items.
- Perm: SeedAllRolesReviewReadPermissionsAsync extend Pe_* 11 key (factory — Pe leaf không nằm All) CanRead+CanCreate upgrade-only mọi role; PeWf_*/AwV2 GIỮ Admin. HRM/Office/Master/Catalogs CanRead (S57). Master write-lock Admin,CatalogManager ×3 controller.
- Menu «Cá nhân» (Personal root 30, mirror Puro) + Chấm công re-parent + HrmConfig→Master + parentBackfill idempotent + admin bỏ ẩn Master (đảo S29).
- LockDemoSampleUsersAsync: khóa 14/16 sample (GIỮ nv.cao+nv.truong IT-pool + catalog.manager) — ungated idempotent, IsActive=0+Lockout+SecurityStamp rotate.
- Tests +12 PeWorkItemGuardTests (validator/FK-guard/null-safe) → 240 PASS. npm ×2 + BE 0W/0E.
- Excel (3) đối chiếu: 62/71/3 identical S55 — no data change.
- Gate: em main evidence-checklist (2 reviewer-spawn die-0-byte — resume-kill; backstop 12 guard-test + authz-key/role-string/Mig-49 evidence-lệnh).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Mirror fe-admin + fe-user.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

KHÔNG đụng schema BE.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Pending Chunk D: Docs S20 changelog + STATUS + HANDOFF

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:15:39 +07:00
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
27b291ccea [CLAUDE] FE-User: PE InfoTab inline edit + PeListPanel pencil edit hover mirror
Chunk 2/3 — mirror y hệt Chunk 1 sang fe-user (rule §3.9). 3 file:
  ~ components/pe/PeDetailTabs.tsx — InfoTab inline edit + autoEditHeader prop
  ~ components/pe/PeListPanel.tsx — pencil icon group-hover absolute right
  ~ pages/pe/PurchaseEvaluationWorkspacePage.tsx — URL editHeader=1 wiring

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:57:24 +07:00
d5c6f12fc6 [CLAUDE] FE-User: PE BudgetFieldRow inline editor mirror fe-admin
Chunk 2/3 — mirror y hệt Chunk 1 sang fe-user (rule §3.9 duplicate có chủ đích).
Cùng BudgetFieldRow component + same imports + same FormRow replacement.

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:13:25 +07:00
14f8d9d808 [CLAUDE] FE-User: PE + HĐ toggle "Nhập tay" + 2 fields manual budget mirror fe-admin
Chunk 4/5 — mirror y hệt Chunk 3 sang fe-user (rule §3.9 duplicate có chủ đích).

Files:
  ~ fe-user/src/types/purchaseEvaluation.ts — PeDetailBundle +2 field
  ~ fe-user/src/types/contracts.ts — ContractDetail +2 field
  ~ fe-user/src/components/pe/PeHeaderForm.tsx (copy từ fe-admin)
  ~ fe-user/src/components/pe/PeDetailTabs.tsx — Section "b. Ngân sách"
    fallback display khi !ev.budget + có manual data
  ~ fe-user/src/pages/pe/PurchaseEvaluationCreatePage.tsx (copy refactor wrap)
  ~ fe-user/src/pages/contracts/ContractCreatePage.tsx — toggle pattern cho
    NewContractForm + EditContractForm (giống fe-admin)

Verify: npm run build fe-user pass · 1904 modules · 0 TS error.

Next: Chunk 5 docs + push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:40:29 +07:00
ecf3c5945b [CLAUDE] FE-User: PE Thao tác 2-panel workspace mirror fe-admin
Chunk 2/3 — mirror y hệt Chunk 1 sang fe-user (rule §3.9 duplicate có chủ đích
giữa 2 app — copy + sync tay khi breaking).

Files (cùng diff Chunk 1, content identical):
  + fe-user/src/components/pe/PeListPanel.tsx
  + fe-user/src/components/pe/PeHeaderForm.tsx
  + fe-user/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx
  ~ fe-user/src/components/pe/PeDetailTabs.tsx — add mode prop + Section 5 hint
  ~ fe-user/src/components/Layout.tsx — resolver Pe_*_Create map workspace
  ~ fe-user/src/App.tsx — route /purchase-evaluations/workspace

Verify: npm run build (fe-user) pass. dotnet test 83 không bị ảnh hưởng (đã
verify Chunk 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:37:52 +07:00
5d94bb449a [CLAUDE] PE: Workflow designer admin UI + Ý kiến 4 phòng ban (P1 Session 5)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m51s
==== Task 1: PE Workflow Designer admin ====

BE (mirror Contract WorkflowAdminFeatures pattern):
- Application/PurchaseEvaluations/PeWorkflowAdminFeatures.cs ~250 LOC:
  - GetPeWorkflowAdminOverviewQuery → list 2 EvaluationType (DuyetNcc / DuyetNccPhuongAn) với Active + History versions + count phiếu đang dùng
  - CreatePeWorkflowDefinitionCommand + Validator: auto-increment Version per Code, deactivate Active cũ trong cùng EvaluationType (1 active per type invariant)
  - DTOs: PeWorkflowStepApproverDto / PeWorkflowStepDto / PeWorkflowDefinitionDto / PeWorkflowTypeSummaryDto / PeWorkflowAdminOverviewDto
  - Phase validation 1..7 (state thường, không bao gồm 99=TuChoi)
- Api/Controllers/PeWorkflowsController.cs: 2 endpoint GET /api/pe-workflows + POST. Reuse policy "Workflows.Read" + "Workflows.Create" (admin chung quyền cho cả 2 nhóm WF).

FE:
- pages/system/PeWorkflowsPage.tsx ~500 LOC mirror WorkflowsPage:
  - Landing 2-card grid khi /system/pe-workflows (chưa pick type)
  - TypePanel khi /system/pe-workflows/:typeCode (DuyetNcc / DuyetNccPhuongAn)
  - DefinitionCard read-only view với active badge + version + steps + approvers (Role/User chip)
  - PeWorkflowDesigner dialog: clone từ existing, edit Code/Name/Description, add/remove steps, +Role / +User approvers per step, save → version mới + deactivate cũ
- App.tsx route /system/pe-workflows + /system/pe-workflows/:typeCode
- Layout đã có resolver PeWf_<Code> → /system/pe-workflows/<code> từ session 3

==== Task 2: Ý kiến 4 phòng ban PE ====

Domain:
- PurchaseEvaluationDepartmentOpinion entity (AuditableEntity) — PEId + Kind + Opinion text + SignedAt + UserId + UserName denorm
- PeDepartmentKind enum (PheDuyet / Ccm / MuaHang / SmPm)
- PE entity + collection navigation DepartmentOpinions

Infrastructure:
- PurchaseEvaluationDepartmentOpinionConfiguration EF: UNIQUE(PEId, Kind) — max 1 row per phòng ban per phiếu (UPDATE in-place)
- ApplicationDbContext + IApplicationDbContext DbSet
- Migration 15 AddPurchaseEvaluationDepartmentOpinions (15 migration total / 52 DB tables)

Application:
- PeDepartmentOpinionFeatures.cs: UpsertPeDepartmentOpinionCommand (sign=true → set SignedAt+UserId, sign=false chỉ lưu text giữ chữ ký cũ) + DeletePeDepartmentOpinionCommand
- DTO bundle update: + DepartmentOpinions list trong PurchaseEvaluationDetailBundleDto
- GetPurchaseEvaluationQueryHandler load DepartmentOpinions + KindLabel resolution

API:
- POST /api/purchase-evaluations/{id}/opinions (upsert)
- DELETE /api/purchase-evaluations/{id}/opinions/{kind}

FE:
- types/purchaseEvaluation.ts: + PeDepartmentKind enum + PeDepartmentKindLabel + PeDepartmentOpinion type + departmentOpinions vào bundle
- PeDetailTabs Section "5. Ý kiến 4 phòng ban (sign-off)" — 2x2 grid OpinionBox per kind:
  - Read mode (readOnly menu Duyệt): hiển thị text + chữ ký
  - Edit mode: textarea + 2 button "Lưu text" / "Lưu & Ký"
  - Badge "Đã ký" emerald + tên người ký + ngày khi signedAt != null

==== Task 3: User seed verify ====

Seed `SeedDemoUsersAsync` đã match đúng user list authoritative (5 PRO TPB+NV / 7 CCM TPB+NV / 1 ISO / 1 CEO) từ prior commit. DbInitializer reconcile sẽ tự sync khi API restart. Typo trong list user (soluttions / trương) đã fixed sensibly trong seed.

==== Build verify ====
- dotnet build clean (0 error)
- fe-admin TS build pass (1 module mới PeWorkflowsPage)
- fe-user TS build pass (PE detail mirror)

Total: 8 file mới (BE 4 + FE 1 + Migration 2 + 1 Domain) + 13 file modified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:17:14 +07:00