[CLAUDE] Docs: S89 closeout — PE visibility/finalize + user-cleanup + Word guide

- STATUS/HANDOFF: prepend S89 (demote S88); state Mig 59 · 422 test · prod 17 user
- session log 2026-06-25-S89 (D1 draft-privacy IDOR · D2 CCM suggested-price !readOnly · D3 finalize-note 3-surface + flow badge + matrix flag · prod user-delete · Word guide)
- docs/Huong-dan-Duyet-NCC.docx (skill docx — logo Solution, 8 sections, khoanh-do 1->2->3; real screenshots pending anh)
- harvest residual workflow-sub agent-memory (cicd / investigator-codebase / reviewer + 2 plan-reviews)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-25 17:05:49 +07:00
parent 681b1edc7e
commit 946c5b21ae
9 changed files with 107 additions and 5 deletions

View File

@ -32,7 +32,7 @@ Read-only CI/CD + post-deploy verifier SOLUTION_ERP. Polls Gitea Actions API, ve
- **Stage 0 RAG infra:** `Get-Service Qdrant` Running + `http://localhost:6333/healthz`. Collection `proj_solution_erp`.
- **Stage 1 Push+filter:** `git log -1 --format='%H %s'` + `git log origin/main..HEAD` empty + diff vs paths-ignore (docs-only SKIPPED-DOCS return).
- **Stage 2 Gitea poll** (max 10 iter × 60s): API `.../actions/tasks?limit=5` (NOT `/runs` 404). Match `head_sha` (NOT run_number cancelled runs shift numbering). `updated_at` stale ~2min (gotcha #46) cross-check VPS mtime. Foreground-sleep BLOCKED Windows Monitor-style busy-wait-curl-spin ~30s/iter.
- **Stage 3 Test gate:** baseline **413 PASS** (45 Domain + 368 Infra; S88-quad #347 402 + 11 D1 [PeHoSoLinkTests+PeWorkItemBudgetTests EXTEND + new PeUrgentNotifyDirectorTests]; prior S88 #345 402, S87 #344 395). CI runs both proj BEFORE build/deploy status=success test gate passed (`tasks` terminal=`status:success`, `conclusion` NOT populated trust success NOT log-numeric). Local grep undercounts (Theory/InlineData). Phase 9 UAT skip OK.
- **Stage 3 Test gate:** baseline **421 PASS** (45 Domain + 376 Infra; S90 #465 419 + 2 new `PeDraftVisibilityTests` [phiếu nháp riêng người tạo, chặn leak role + V2-approver]; prior S89 #348 419, S88-quad #347 413). CI runs both proj BEFORE build/deploy status=success test gate passed (`tasks` terminal=`status:success`, `conclusion` NOT populated trust success NOT log-numeric). Local grep undercounts (Theory/InlineData). Phase 9 UAT skip OK.
- **Stage 4 Post-deploy** (if SUCCESS): auth login bearer (admin + nv.test gotcha #44; token=`accessToken` route `/api/auth/login`) 3-5 endpoint smoke 2XX (incl new NEW-endpoint bodyless-POST returns **411 IIS Length-Required pre-auth NOT 404**; re-probe WITH body 401 confirms wired) FE bundle hash 2 app (per #69 = real-size-vs-fake not hash-delta) SignalR negotiate (gotcha #25) EF mig prod==repo.
- **Stage 4.6 (S29 CRITICAL):** sqlcmd seed sample verify post-deploy (NOT chỉ schema). Seed=runtime-row-insert (no history/tables advance) verify via DB-query (e.g. Permissions count), NOT bundle-frozen-alone.
- Discovery: ASP.NET 10 record enum needs numeric input unless `JsonStringEnumConverter` (SOL has NO converter FE sends numeric). sqlcmd ssh Windows-auth `\\\\SQLEXPRESS` 4-backslash. INFRASTRUCTURE seed MUST run (NOT inside `if(!demoSeedDisabled)`); DEMO seed gated gotcha #51.
@ -50,9 +50,9 @@ Read-only CI/CD + post-deploy verifier SOLUTION_ERP. Polls Gitea Actions API, ve
- **Gitea:** `git.baocaogiaoduc.vn/vietreport-admin/solution-erp` · workflow `.gitea/workflows/deploy.yml` · paths-ignore `['docs/**','**/*.md','.claude/skills/**']`. Anon API works (public repo) when GITEA_TOKEN absent.
- **Prod:** api/admin/eoffice `.solutions.com.vn` · SSH `ssh vietreport-vps` (Administrator, id_ed25519) · IIS phys paths: API `C:\inetpub\solution-erp\api` · admin `\fe-admin` · user `\fe-user`. DB `.\SQLEXPRESS`/`SolutionErp`/`vrapp` SQL-auth. **Conn key = `ConnectionStrings.Default` (NOT `DefaultConnection`)**. DB pw = `buKL3TGBkD0wDDbYVw65QeX9` (User Id=`vrapp`; Password field has NO `vrapp/` prefix S89 login-failed when prefix included; read verbatim from prod `appsettings.Production.json` ConnectionStrings.Default when `$env:PROD_DB_PASSWORD` empty). skill-doc path `C:\inetpub\apps\SolutionErp\Api` STALE.
- **SSHPS quoting (S42):** nested bashsshpowershell mangles `$var`/`\"` use `iconv UTF-16LE | base64` `powershell -EncodedCommand $B64`; OR write ps1-file + `powershell -File $(cygpath -w)`. sqlcmd over SSH direct `-W -h -1` pw-inline. sys-catalog string-concat `COLLATE DATABASE_DEFAULT` (collation conflict).
- **Tests baseline:** **419 PASS** (45 Domain + 374 Infra · 0 fail/skip; S89 Run #348 sha 1aa3bdb 413 + 6 D2 multi-winner [CreateContractFromEvaluationMultiWinnerTests + PeListWinnerNamesProjectionTests]; prior S88-quad #347 413). Phase 9 UAT skip per chunk OK.
- **Tests baseline:** **421 PASS** (45 Domain + 376 Infra · 0 fail/skip; S90 Run #465 sha a71753b 419 + 2 `PeDraftVisibilityTests` [draft-private-to-creator, chặn leak role + V2-approver]; prior S89 #348 419, S88-quad #347 413). Phase 9 UAT skip per chunk OK.
- **Mig latest repo:** **Mig 59 `20260624084608_AddCcmBudgetPeriodToPurchaseEvaluation`** (S88 #345; PE +1 AddColumn `CcmBudgetPeriodAmount` decimal(18,2) NULL no-backfill VERIFIED-APPLIED-PROD history-HEAD + col-exists 0-nonnull). Prev Mig 58 `AddPeMultiWinnerAndLevelFinalize` (S87 #344). Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/`. Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC TOP 5`. **`sys.tables` (is_ms_shipped=0) = 88** (S61 Budget-replace DROPPED 9388 narrative-93 STALE; when commit touches no schema, 88 correct, don't FAIL on 88. Always cross-ref COMMIT scope vs ambient count). enum-additive (e.g. `ApprovalAttachment=5`) = int-stored NO-mig expect history NOT-advance + empty `git diff -- '*Migrations*'`.
- **Bundle hash live (S89 Run #348):** admin js `BQq2eTP6`/css `Pltrs5ku` · user js `dHa64I59`/css `Foiw55B6` (JS BOTH ROTATED from #347 `CP_iKhTu`/`DUe0fndl` by real fe-src D2 PeDetailTabs+PeListPanel; **css FROZEN `Pltrs5ku`/`Foiw55B6` = #347/#346 — correct, D2 FE logic-only no new utility class**). **PER #69: hash MAY rotate per deploy OR stay frozen if 0 fe-src diff (byte-identical) — SNAPSHOT for THIS run, NOT a frozen baseline.** Real FE ship = diff `fe-*/src` in commit/range + real-size-vs-fake (1.6/1.5MB vs ~919b SPA-trap), NOT hash delta. ASYMMETRIC-deploy framing (S66) RETIRED by #69.
- **Bundle hash live (S90 Run #465/id=465):** admin js `BXixQDuS`/css `DWQ-DgfZ` · user js `BMgckUPd`/css `Dr0-ikKX` (JS BOTH ROTATED from pre-deploy live `BeyLzf5i`/`CPe970sY` by real fe-src PeDetailTabs ×2; **css FROZEN `DWQ-DgfZ`/`Dr0-ikKX` — correct, FE logic-only [gỡ !readOnly gate] no new utility class**). **#465 run_number; head_sha-keyed.** pre-deploy baseline was `BeyLzf5i`/`CPe970sY` (Run #350 web.config rotated since #348 `BQq2eTP6`/`dHa64I59` always re-snapshot LIVE before deploy, brief's stale prev-hash misleads). **PER #69: hash MAY rotate per deploy OR stay frozen if 0 fe-src diff (byte-identical) — SNAPSHOT for THIS run, NOT a frozen baseline.** Real FE ship = diff `fe-*/src` in commit/range + real-size-vs-fake (1.6/1.5MB vs ~919b SPA-trap), NOT hash delta. ASYMMETRIC-deploy framing (S66) RETIRED by #69.
- **Bearer:** admin `admin@solutions.com.vn/Admin@123456` (full) · UAT `nv.test@solutions.com.vn/TestUser@123456` (Drafter CCM, gotcha #44 check).
## 🔑 Critical config (flag commit nếu tái xuất)
@ -70,6 +70,7 @@ BE (test+build) ~90s · FE × 2 ~60s/app · deploy ~30s · **total ~3min code /
## 📅 Recent runs (compressed — full verbatim → `archive/2026-06.md` via `archive/_INDEX.md`)
- **S90 #465 (id=465, run_number 351) `a71753b` PASS ~4m56s (FULL-STACK PE "nháp riêng tư" + "giá đề xuất CCM" fix, NO-MIG — anh Kiệt FDC UAT)** — 2-commit push `d085436..a71753b`: `f7bd97a` FE-only gỡ `!readOnly` over-gate trên PRO/CCM suggested-price inputs (both apps PeDetailTabs ×2, +18/8) + `a71753b` BE PE draft-private-to-creator (PurchaseEvaluationFeatures.cs +18/4 — chặn leak phiếu Nháp sang role khác + V2-approver) + new test PeDraftVisibilityTests. Files 4: FE 2 (PeDetailTabs admin+user) + BE 1 (PurchaseEvaluationFeatures) + 1 test. **NO `*Migrations*`/`*Persistence*` in range diff** (logic-only, no schema). status=success ⟹ test gate passed (**baseline 421** = 45 Domain + 376 Infra = 419 +2; logs 404 anon so count not log-scraped — trusted gate+baseline). **Bundle JS BOTH ROTATE (real fe-src PeDetailTabs ×2 ⟹ new content-hash): admin js `BeyLzf5i`→`BXixQDuS` · user js `CPe970sY`→`BMgckUPd`** (pre-deploy LIVE baseline = Run #350 web.config, NOT brief's stale #348 `BQq2eTP6`/`dHa64I59`); **css FROZEN admin `DWQ-DgfZ` user `Dr0-ikKX` — correct, FE logic-only [un-gate inputs] no new utility class** (gotcha #69-inverse). Real-ship proven (SPA-trap defeated): admin js 1,628,895b Last-Modified 07:43:18Z=14:43:18+07 in-window (run 14:39:38→14:44:34 = 07:39:38→07:44:34Z) · user js 1,532,796b 07:44:10Z in-window · control fake `ZZfakehash00.js`→200 but 919b text/html (size-check meaningful). **BE dll ALL 4 fresh in-window: Api 07:42:07Z · Application 07:40:13Z (PurchaseEvaluationFeatures draft-private live) · Infrastructure 07:40:37Z · Domain 07:39:56Z.** **Mig FROZEN 59** (repo MIGCOUNT=59 + prod history HEAD==`AddCcmBudgetPeriodToPurchaseEvaluation`==repo + HistCount=59 no-pending — correct NO-MIG). Health api ready/live 200 + admin/eoffice 200 (4×200). GET /api/purchase-evaluations + /api/menus + /api/approval-workflows-v2 all 401 unauth (wired NOT 404/500). **S88 cache headers LIVE: admin+eoffice index `Cache-Control: no-cache` + assets `immutable,max-age=31536000`** (web.config S88 hardening confirmed in prod). PE fix BE-internal (draft-visibility filter) + FE un-gate = no new endpoint → 421-test covers draft-visibility behavior, no curl-probe. gotcha #69-inverse re-confirmed (real fe-src ⟹ js rotates, css frozen logic-only). ⚠️ **brief's prev-hash #348 was STALE — Run #350 (`ca136d8` web.config) rotated bundle since; ALWAYS re-snapshot LIVE pre-deploy, don't trust brief's claimed prev.** SSH→PS `$_.LastWriteTimeUtc` mangled nested-double-quote → base64 EncodedCommand worked; sqlcmd `-Q` mangled → scp .sql + `-i` file cleanest. DB pw `buKL3TGBkD0wDDbYVw65QeX9` (no `vrapp/` prefix), conn key `ConnectionStrings.Default`, prod path `C:\inetpub\solution-erp\api`.
- **S89 #348 (id=462) `1aa3bdb` PASS ~5m10s (FULL-STACK PE D2 multi-winner outward, NO-MIG)** — create-contract 1→N per winner (command→List<Guid>, multi-winner handler in CreateContractFromEvaluationFeatures, Controller returns `{contractIds}`) + list-card winner-names projection (PurchaseEvaluationFeatures + Dtos WinnerSupplierNames). Files 12: BE 4 (Controller/CreateContractFromEvaluationFeatures/Dtos/PurchaseEvaluationFeatures) + FE **both apps** 6 (PeDetailTabs+PeListPanel+types ×2) + 2 test (CreateContractFromEvaluationMultiWinnerTests + PeListWinnerNamesProjectionTests). **NO `*Migrations*`/`*Persistence*` in diff** (git diff EMPTY — logic-only, no schema). status=success ⟹ test gate passed (**baseline 419** = 45 Domain + 374 Infra = 413 +6; logs 404 anon so count not log-scraped — trusted gate+baseline). **Bundle JS BOTH ROTATE (real fe-src PeDetailTabs+PeListPanel ⟹ new content-hash): admin js `CP_iKhTu`→`BQq2eTP6` · user js `DUe0fndl`→`dHa64I59`** (#347 baseline); **css FROZEN admin `Pltrs5ku` user `Foiw55B6` = #347/#346 — correct, D2 FE logic-only no new utility class** (gotcha #69-inverse). Real-ship proven (SPA-trap defeated): admin js 1,628,901b Last-Modified 03:34:13Z=10:34+07 in-window (run 10:30:30→10:35:40 = 03:30:30→03:35:40Z) · user js 1,532,802b 03:35:07Z in-window · control fake `ZZfakehash00.js`→200 but 919b text/html (size-check meaningful). **BE dll ALL 4 fresh in-window: Api 03:33:00Z (Controller `{contractIds}` live) · Application 03:31:04Z (CreateContractFromEvaluation multi-winner handler + PE winner-names projection live) · Infrastructure 03:31:28Z · Domain 03:30:47Z.** **Mig FROZEN 59** (repo MIGCOUNT=59 + prod history HEAD==`AddCcmBudgetPeriodToPurchaseEvaluation`==repo + HistCount=59 no-pending) + sys.tables 89 incl __EFMigrationsHistory = 88 business (no schema change). **Endpoint live: `POST /api/purchase-evaluations/{id}/create-contract` bodyless→411 (IIS Length-Required pre-auth), +json-body→401 (wired: NOT 404=route-exists, NOT 500=no-crash; `{contractIds}` multi-winner contract live).** Health api ready/live + admin + eoffice 4×200. GET /api/purchase-evaluations + /api/menus + /api/approval-workflows-v2 all 401 unauth (wired). gotcha #69-inverse re-confirmed (real fe-src ⟹ js rotates, css frozen when logic-only). ⚠️ **DB pw = `buKL3TGBkD0wDDbYVw65QeX9` (NOT `vrapp/buKL3...` — `vrapp/` was user/pw-combined-notation in memory essentials, actual Password field has no prefix; read from prod `appsettings.Production.json` ConnectionStrings.Default).** SSH→PS `$_.LastWriteTimeUtc` mangled in nested double-quote → base64 EncodedCommand worked; sqlcmd `-Q` quoting mangled → scp .sql + `-i` file worked cleanest. conn key `ConnectionStrings.Default`, prod path `C:\inetpub\solution-erp\api`.
- **S88-quad #347 (id=461) `73cce1f` PASS ~5m04s (FULL-STACK PE D1 stability, NO-MIG)** — Block-B multi-winner financials re-key (PurchaseEvaluationFeatures) + CEO-notify SaveChanges-persist (PurchaseEvaluationUrgentFeatures +5 lines) + setter RELOCATE PeSuggestedPriceFeatures→PeWorkItemBudgetFeatures (56/+57) + HoSoLink trim + #70 guard + Dtos comment. Files 12: BE 5 (Dtos+4 features) + FE **both apps** 4 (PeDetailTabs+types ×2) + 3 test (PeHoSoLinkTests+PeWorkItemBudgetTests EXTEND, **new PeUrgentNotifyDirectorTests +237 lines**). **NO `*Migrations*`/`*Persistence*` in diff** (logic-only re-key, no schema). status=success ⟹ test gate passed (**baseline 413** = 45 Domain + 368 Infra = 402 +11; logs 404 anon so count not log-scraped — trusted gate+baseline). **Bundle JS BOTH ROTATE (real fe-src #70-guard+HoSoLink-trim ⟹ new content-hash): admin js `BABhPh2s`→`CP_iKhTu` · user js `DLulO6uP`→`DUe0fndl`** (#346 baseline); **css FROZEN admin `Pltrs5ku` user `Foiw55B6` — correct, D1 FE logic-only no new utility class**. Real-ship proven: admin js 1,628,726b Last-Modified 02:57:57Z=09:57+07 in-window (run 09:54:15→09:59:19 = 02:54:15→02:59:19Z) · user js 1,532,627b 02:58:50Z in-window · SPA-trap control `ZZfakehash00.js`→200 but 919b text/html (size-check meaningful). **BE dll ALL 4 fresh in-window: Api 02:56:42Z · Infrastructure 02:55:16Z (PeWorkItemBudgetFeatures setter-relocate live) · Application 02:54:51Z (PurchaseEvaluationFeatures Block-B + UrgentFeatures CEO-persist live) · Domain 02:54:36Z.** **Mig FROZEN 59** (MIGCOUNT=59, prod history HEAD==`AddCcmBudgetPeriodToPurchaseEvaluation`==repo, no-pending; correct NO-MIG D1) + sys.tables 88 (89 incl __EFMigrationsHistory). Health api ready/live + admin + eoffice 4×200. GET /api/purchase-evaluations + /api/menus + /api/approval-workflows-v2 all 401 unauth (wired NOT 404/500). gotcha #69-REFINE inverse re-confirmed (real fe-src ⟹ js rotates, css frozen when logic-only). conn key `ConnectionStrings.Default`, prod path `C:\inetpub\solution-erp\api` (skill-doc `\apps\...` STALE). ⚠️ SSH→PS table-count `<>'literal'` mangled via base64/CLIXML AGAIN → use count-incl-history1 (88) workaround.
- _(S88-bis #346 `0d3c0cf` FE-only PE layout "4. Thông tin chọn thầu" 2-files js+css-BOTH-rotate baseline-402 · S88 #345 `9b1ee0c` FULL-STACK Section-B 3-col + **Mig 59 `AddCcmBudgetPeriodToPurchaseEvaluation`** AddColumn-no-backfill VERIFIED-PROD + setter `PATCH .../budget/ccm-period` PATCH-401-not-411 + 7 authz-test → PASS, FIFO-trimmed, full verbatim git + archive `_INDEX`)_

View File

@ -70,6 +70,7 @@ Bearer từ `POST api.solutions.com.vn/api/auth/login` → status matrix expecte
## 📅 Recent activity (compressed — full verbatim → `archive/2026-06.md` via `archive/_INDEX.md`)
- **2026-06-25 PE S89 AllowApproverFinalize NOTE feasibility (anh Kiệt FDC — "phiếu kết thúc cấp X không CEO" 3 surface):** ⭐ **GAP CONFIRMED — BE field MỚI cần.** `AllowApproverFinalize` (Mig 58 per-Level bool `ApprovalWorkflow.cs:142`) HIỆN CHỈ expose qua `currentLevelOptions` (CURRENT level only) `PurchaseEvaluationFeatures.cs:981` + DTO `PurchaseEvaluationDtos.cs:123` + FE type `purchaseEvaluation.ts:412`. **`approvalFlow` DTO KHÔNG carry flag**: flow-level record `PurchaseEvaluationApprovalFlowLevelDto` (`Dtos.cs:154-158`) chỉ {Order,Name,Approvers,Status}; build loop `PurchaseEvaluationFeatures.cs:1050-1065` KHÔNG project `l.AllowApproverFinalize`. **LIST DTO KHÔNG có** (`PurchaseEvaluationListItemDto:6-45`, 0 finalize field). **DASHBOARD = reuse list** → cũng thiếu. ⇒ FE detail CHỈ biết finalize khi actor LÀ approver cấp finalize (current). Để render "phiếu này kết thúc ở Bước Y/Cấp X — không CEO" cho MỌI người (kể cả non-approver, list, dashboard) → BE PHẢI expose field mới: bundle `bool EndsBeforeCeo`+`string? FinalizeStepName/FinalizeLevelName` (compute = first Step/Level có AllowApproverFinalize=true trong aw.Steps loop đã load `:985`) + mirror lên list-item + dashboard. BE finalize logic `PurchaseEvaluationWorkflowService.cs:861` (AUTO khi matchingLevel.AllowApproverFinalize → DaDuyet bỏ CEO, NO threshold/role-gate). **FE detail placement (cả 2 app SHA-IDENTICAL** sha `e477c939` PeWorkflowPanel + `49b39404` PeDetailTabs): note nên đặt đầu `PeWorkflowPanel.tsx` flow `<ol>` (sau h3 "Quy trình duyệt" :245, trước :256) — banner emerald mirror style :599 existing approverFinalize callout (chỉ trong dialog hiện). Detail đã có precedent: TraLai banner `PeDetailTabs.tsx:370-378`, V2 current-level banner `PeWorkflowPanel.tsx:346-365`. Edge: phiếu **DaDuyet** → đổi copy "đã kết thúc tại <X>, không qua CEO" (past-tense); copy đề xuất "⚑ Quy trình này duyệt tới <StepName/Cấp X> là KẾT THÚC — không trình CEO". MIRROR 2-app bắt buộc. → _INDEX.
- **2026-06-25 PE FROZEN patchwork audit AREA-6 (FE consumers BOTH apps — multi-winner + mirror-drift + dead-code + #70):** ⭐ Post-Mig58 multi-winner (`isWinner` bool) NOW implemented (supersedes 06-24 "SINGLE" recon). **All 6 PE components + 3 PE pages SHA-IDENTICAL fe-admin↔fe-user** (PeDetailTabs/PeHeaderForm/PeUrgentChips/PeListPanel/PeWorkspaceCreateView/PeWorkflowPanel). type files DIFFER but ONLY badge-tint (-700→-800 ink) + comments (intentional per-app UX, struct fields identical incl ccmBudgetPeriodAmount/isWinner/8 Allow flags). **RESIDUAL single-winner BUG (low):** `PeListPanel.tsx:168` renders `p.selectedSupplierName` (list-card winner label) → BLANK when ≥2 joint winners (BE list-DTO `PurchaseEvaluationFeatures.cs:615,706` projects from `SelectedSupplierId` which BE sets null when count≠1 `PurchaseEvaluationDetailFeatures.cs:431`). List DTO has NO isWinner/winner-list field; detail-view multi-winner correct everywhere (8 detail consumers all converted: computeGiaChaoThau:78, missingForApproval:195, submitChecklist:229, ThongTinChonThau:1949/1970, Section-B row4:1578, CreateContractDialog:2171 fallback `||selectedSupplierName`, HangMucCard toggle:2560). **#70 stale-echo ASYMMETRY:** budget Section-B CCM-period editor gates `ccmPeriodMut.isPending||peFetching` (`PeDetailTabs.tsx:1563`) but PRO sibling row3/row8 `adjustMut` gates `.isPending` ONLY (`:1548,:1649`, NO peFetching) — rapid PRO row3→row8 absolute-set pair can echo stale ev.* pre-refetch (same #70 class CCM guards). 3 peFetching sites exist (945/1268/1736). **Stale comment** PeDetailTabs:2507 references `AddItemDialog` "giữ(dead)" but function DELETED (grep 0 def) — misleading comment only, not dead code. PeWorkflowsPage admin-only = by-design (workflow config not mirrored). → _INDEX.
- **2026-06-25 PE FROZEN patchwork audit AREA-2 (budget matrix + Section B 3-col, Mig 56/59):** ⭐ **BAND-AID CONFIRMED** `SetPeCcmBudgetPeriodCommand+Validator+Handler` (`PeSuggestedPriceFeatures.cs:153-199`) is a BUDGET setter (writes `pe.CcmBudgetPeriodAmount`) MISPLACED in a price-feature file; SHOULD live in `PeWorkItemBudgetFeatures.cs` (where UpdatePeBudgetPro/Ccm sit). Test+comments already self-flag (PeCcmBudgetPeriodSetterAuthzTests.cs:14). ⚠️ **Role-gate ASYMMETRY (intentional, NOT a bug — but inconsistent surface):** CCM budget setters = flat role-gate (CostControl|Admin fail-closed): UpdatePeBudgetCcm `PeWorkItemBudgetFeatures.cs:160-165` + SetPeCcmBudgetPeriod `PeSuggestedPriceFeatures.cs:176-181`. BUT the PRO sibling `/budget-adjust``AdjustPurchaseEvaluationBudgetCommandHandler` `PurchaseEvaluationFeatures.cs:349-394` = **phase/drafter/workflow-slot gate** (Drafter when Nháp/TraLai; Approver+AllowApproverEditBudget F4 when ChoDuyet) NOT role-PRO. So "PRO col" & "CCM col" of same Section-B row 3 have DIFFERENT authz models (PRO=drafter-flow on `pe.BudgetPeriodAmount`; CCM=role-flow on `pe.CcmBudgetPeriodAmount`). Both per-PE fields (NOT PeWorkItemBudget per-pair — that's the matrix rows Initial/Adjustment). Section-B **"Dự án" col = intentional FE-only "—"** hardcoded (`PeDetailTabs.tsx:1520,1542,1596,1606`; Domain `PeWorkItemBudget.cs:19` "chưa wire BE — sau mới có người dự án nhập") — documented, NOT a stub-bug. Controller class-level `[Authorize]` only (`PurchaseEvaluationsController.cs:15`) → handler gates ARE the boundary. Tests solid (PeWorkItemBudgetTests 30+ cases, PeCcmBudgetPeriodSetterAuthzTests 7). → _INDEX.

View File

@ -8,6 +8,8 @@
- [S62 PE budget soft-warning](project_s62_pe_budget_soft_warning.md) — PASS: hard-block→soft-warning; submit-guard intact + validator giữ `BudgetPeriodAmount>0`, row8 negative-safe (additive-only). Validator class `PurchaseEvaluationFeatures.cs:317`.
- [Wire/mirror claim verification anchors](feedback_wire_claim_verification_anchors.md) — sha256 twin-file · `git diff -U0` isolate true-adds · `allowNegative` bleed check · guard-still-intact grep.
- [S89 PE budget-sectionB review](project_s89_pe_sectionb_review.md) — investigator 3/3 upheld (do-not-touch FROZEN) + CAUGHT MISSED #70 race: Block B PRO row3/row8 share adjustMut + cross-echo `ev` stale + NO `||peFetching` (every other cell gated). authz-lens ≠ concurrency-lens.
- [S89 PE ends-before-CEO PLAN review](project_s89_pe_endsbeforeceo_plan_review.md) — CONCERN: BE 3-field design sound (Mig 58 col committed, no mig) but FE coverage gap — 4 PeUrgentChips render-sites, plan covered only 2 (missed InboxPage:290 despite BE Inbox A2 updated + wrong ListPage dir path).
- [S89 finalize-note PLAN review](project_s89_finalize_note_plan_review.md) — CONCERN: plan ground-truth 100% accurate (all file:line verified incl self-flagged); gaps = note doesn't say intermediate approvers still sign + skipToFinal-last-level corner + Dev DB 9 migs behind.
---

View File

@ -0,0 +1,21 @@
---
name: project-s89-finalize-note-plan-review
description: S89 adversarial PLAN review — PE "phiếu kết thúc trước CEO" NOTE 3-surface (dashboard+sidebar+detail); verdict CONCERN (plan sound, 4 UX gaps)
metadata:
type: project
---
# S89 PLAN review — PE AllowApproverFinalize NOTE (3 surface)
**Verdict: CONCERN** (plan technically sound + ground-truth accurate, but UX-completeness gaps vs anh Kiệt's "MỌI NGƯỜI NAM").
**Why:** Plan is a feature-add (no migration — `AllowApproverFinalize` = col từ Mig 58). Every file:line claim VERIFIED true incl self-flagged uncertainties (`PurchaseEvaluationsListPage:489` urgent-chip site EXACT; inbox proj `:713`; DTO ends `WinnerSupplierNames:45`/`Workflow:300`).
**How to apply (future PE-finalize-note review):**
- BE "first-reachable" compute (lowest Step.Order→Level.Order) MATCHES runtime `PurchaseEvaluationWorkflowService.cs:861` — finalize fires when sequential pointer lands on flagged level, later flagged levels unreachable. CORRECT.
- **CORNER missed by plan:** `skipToFinal` (runtime approver opt-in, :803) jumps pointer to LAST level keeping Phase=ChoDuyet; if last level ALSO has AllowApproverFinalize, finalize lands at LAST not first-reachable → note "kết thúc tại {first}" misleads. Config-pathological, MINOR.
- **UX gap (the real CONCERN):** plan's note says WHERE flow ends ("kết thúc tại Bước X") but NOT that intermediate approvers still sign first. "Đang chạy" copy `⚑ Quy trình này kết thúc tại {Step·Level} — KHÔNG trình CEO` reads to a Cấp-1 approver as if THEY finalize. Recommend add "(các cấp trước vẫn duyệt như thường)".
- 2-app mirror: plan EXPLICIT "làm CẢ fe-user+fe-admin" + correctly flags dashboard NOT-mirror (UserDashboardPage vs DashboardPage = genuinely diff files, legit exception). PeWorkflowPanel/PeListPanel SHA-identical confirmed `e477c939`.
- **type-file NOT SHA-mirror** (pre-existing PHASE_BADGE color drift fe-user text-800 vs fe-admin text-700) → plan's type line-claims (147/497) valid for fe-user only; plan acknowledges "edit từng cái" B0. OK.
- **Dev DB 9 migs behind** (history stuck Mig 49 `AddWorkItemToPE`, Mig 50-58 ALL unapplied) — plan flagged "Mig 58 chưa apply" but UNDERSTATED scope. Dev smoke 500 until `ef database update`. Prod unverified.
- E2 dashboard open-question (badge-on-list vs list-band vs KPI) legitimately deferred to anh — NOT a plan defect.

View File

@ -0,0 +1,28 @@
---
name: s89-pe-endsbeforeceo-plan-review
description: S89 adversarial PLAN review (data-sufficiency) for PE "ends-before-CEO" finalize note across 3 surfaces — CONCERN verdict, render-site coverage gap caught
metadata:
type: project
---
# S89 PE "Phiếu kết thúc trước CEO" NOTE — PLAN data-sufficiency review (CONCERN)
Adversarial PLAN review (no code yet). Feature: surface `ApprovalWorkflowLevel.AllowApproverFinalize` (Mig 58) as a "ends at level X, no CEO" note on 3 surfaces (detail / list-sidebar / dashboard).
**Verdict: CONCERN** — BE data-exposure design is SOUND (3 computed DTO fields, no migration; verified `AllowApproverFinalize` column committed in Mig 58 `20260624071509` + snapshot, 59 mig total). But FE render-site coverage is INCOMPLETE.
## Caught (plan missed)
1. **Render-site gap — 4 `PeUrgentChips` sites exist, plan covers only 2.** Plan names `PeListPanel.tsx:160` + `PurchaseEvaluationsListPage.tsx` (real path `pages/pe/`, plan said `pages/purchase-evaluations/` = WRONG dir, file exists at corrected path :489). MISSES `InboxPage.tsx:290` (sidebar inbox — and BE Inbox projection IS updated in plan A2, so data lands but chip never rendered) + `PeDetailTabs.tsx` header (urgent badge inline :280, not via chip but same list-card surface family).
2. **Semantic-overlap nuance.** `PeDetailTabs.tsx:113-116` already derives `approverFinalizeEligible = levelOptions?.allowApproverFinalize` (current-level-only, fires for current approver). Plan correctly rejects reusing this for the new note (needs workflow-wide visibility) + correctly adds new `endsBeforeCeo` — but puts new note in DIFFERENT component (`PeWorkflowPanel.tsx:252`) than existing finalize UI (`PeDetailTabs`). Both coexist OK but plan should acknowledge.
## Upheld (plan correct)
- `approvalFlow` DTO genuinely LACKS finalize flag (BE build :1045-1075 + FE types `PeApprovalFlowLevel` :263 both omit) → new field justified, can't reuse flow.
- List/Inbox projections (`:616-631` + `:713-728`) do NOT join workflow → subquery-per-PE needed (mirror winner-names). `l.Step!.ApprovalWorkflowId` translates server-side (PROVEN existing :582 `.Select` + :808 `.AnyAsync`).
- DTO constructor insertion points (`:1135` detail tail, `:45` list-item tail) structurally accurate.
- Edge-case table (multi-finalize first-reachable, V1 null-workflow, DaDuyet past-tense, no-finalize) all correct vs `PurchaseEvaluationWorkflowService.cs:861` return-at-first-match.
## Must-fix before code
- Add InboxPage.tsx render site (+ decide PeDetailTabs header). 4 sites total, not 2.
- Correct ListPage path to `fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx`.
- EF risk: ordered `.FirstOrDefault()` selecting STRING name across join (FinalizeStepName) — bool `.Any()` is safe but verify name-subquery translates (plan's 3-separate-subquery option-a is the safer pick, keep it).
- Dev DB drift: applied only to `20260611044424_AddWorkItem` — Mig 50-59 unapplied → smoke-test 500 until `dotnet ef database update`. Plan flag correct, severity understated (DB further behind than "Mig 58 only").

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,49 @@
# S89 (2026-06-25) — PE visibility/finalize batch + xóa User test prod + Word guide
> Phiên CỰC LỚN, HMW-mode ON. anh Kiệt FDC UAT realtime. ~6 commit prod-verified + 1 prod data-op + 1 deliverable Word. em-main-led + Workflow fan-out (2: invest finalize-note + impl finalize-note) + cicd-monitor ×3. Nhiều #53 truncation (impl-frontend + cicd return rỗng) → recover-disk + self-gate.
## Bối cảnh
anh giao liên tiếp qua Zalo (anh Kiệt FDC + screenshot): (1) CCM không nhập được giá đề xuất, (2) finalize-note "không trình CEO" ra 3 surface, (3) màn hình CEO hiện nháp người khác, (4) Word hướng dẫn + danh sách user + xóa user test.
## D1 — Phiếu nháp RIÊNG TƯ người tạo (`a71753b`, Run #465 PASS, test 419→421)
**Triệu chứng:** màn hình CEO ở "chỗ thao tác" hiện bản nháp/phiếu chưa xong của NGƯỜI KHÁC.
**Root cause (2 đường lộ):** `PurchaseEvaluationFeatures.cs` IDOR filter — nháp (DangSoanThao) lộ qua **cond #2** role-eligible (`GetEligiblePhases` map Drafter/DeptManager→DangSoanThao :645) + **cond #3 V2-approver** (workflow PIN lúc TẠO phiếu `:149 ApprovalWorkflowId=request.*` → CEO là approver → thấy MỌI nháp). cond #3 = leak chính trên màn CEO.
**Fix 4 chỗ (nháp = owner-only + Admin):** GetEligiblePhases bỏ DangSoanThao · admin inbox-array bỏ DangSoanThao · list cond#3 guard `!=DangSoanThao` · detail-authz guard `isV2Approver` cho draft. +`PeDraftVisibilityTests` (2 test, test-before security — owner thấy / drafter khác KHÔNG / **CEO V2-approver KHÔNG** / admin thấy hết / phiếu đã-gửi vẫn hiện). Inbox: anh chốt "bỏ nháp khỏi inbox hẳn".
**Lesson:** `feedback_cardinality_change_grep_consumers` mở rộng — category-error (DangSoanThao=drafting phase đặt nhầm vào approval-eligible set); FK recon trước khi đụng visibility.
## D2 — CCM giá đề xuất nhập được (`f7bd97a`, cùng Run #465)
**Triệu chứng:** "PRO oki nhưng CCM giá đề xuất không nhập được" (anh Trần Xuân Lưu CCM).
**Root cause:** `SuggestedPriceRows` (PeDetailTabs:1726) gate `!readOnly && ev.canEdit*SuggestedPrice` — thêm tầng `!readOnly` mà ô **ngân sách song song KHÔNG có** (`editable={bs.canEditCcm}` role-only). Ở view readOnly=true (preview / Duyệt chưa-tới-lượt `!pendingMe`), CCM sửa ngân-sách được nhưng giá-đề-xuất rớt read-only. Vi phạm BE intent "không ràng Phase, chỉnh như tài liệu sống".
**Fix:** gỡ `!readOnly`, gate thuần role (BE setter fail-closed Forbidden = chốt chặn). Gỡ luôn prop readOnly không dùng. 2 app SHA-mirror (6 edit).
**Lesson:** 2 ô cạnh nhau cùng "nhập theo role" mà khác hành vi = gần chắc 1 ô gate sai trục (role ⟂ view-state).
## D3 — Finalize-note "không trình CEO" × 3 surface (`f0bca77`, Run cicd, test 421→422) + flow badge (`9c920ad`) + matrix flag (`fef4c33`)
**Yêu cầu:** phiếu mà quy trình có cấp bật `AllowApproverFinalize` (Mig 58) → note "kết thúc, không trình CEO" cho mọi người nắm ở dashboard + sidebar + trong phiếu.
**Invest Workflow** `wf_341f7b29-467` (4 invest investigator-codebase → design → 2 reviewer): bắt finalize-flag CHỈ lộ cho current-level → cần expose whole-flow. 2 reviewer bắt: **InboxPage SÓT** (surface sidebar) + path sai `pages/pe/` + copy phải nói "các cấp trước vẫn duyệt" (boundary cho cấp giữa) + dashboard chưa cam-kết.
**Impl Workflow** `wf_3f57edb6-514` (BE ∥ FE): BE OK (+caught CreateContractFromEvaluationFeatures.cs cùng DTO); **FE return RỖNG #53** → em-main self-gate recover-disk: fe-user 6 file + new PeFinalizeChip OK, **fe-admin chỉ types** → em-main mirror 4 file SHA-identical (cp) + verify 5 render-site + +PeFinalizeProjectionTests (EF-translate smoke 3-subquery).
**BE (no migration):** +EndsBeforeCeo/FinalizeStepName/FinalizeLevelName trên detail+list DTO + AllowApproverFinalize trên flow-level; first-reachable Step.Order→Level.Order; list/inbox/approved 3 subquery EF-safe (lv.Step!.Name); V1 null→false.
**Flow badge (`9c920ad`):** PeWorkflowPanel sơ-đồ — badge tím "⚑ Duyệt thay CEO" trên cấp finalize + làm-mờ Bước CEO "không trình tới" (anh muốn thấy NGAY trong sơ đồ).
**Matrix flag (`fef4c33`):** `WorkflowMatrixViewPage` (user, build S24 với 7 flag) **thiếu flag thứ 8** `allowApproverFinalize` (thêm Mig 58 S87 chỉ vào Designer) → "chưa hiển thị chính xác". BE đã gửi sẵn, FE matrix + AwLevelDto thiếu → thêm.
**Lesson:** (a) versioning — Designer "Lưu" = `CreateAwDefinitionCommandHandler` tạo **version MỚI** (Version++ theo Code + deactivate cũ); phiếu pin ApprovalWorkflowId lúc tạo → phiếu CŨ giữ version cũ, finalize chỉ hiện trên phiếu MỚI (đây là lý do anh "check rồi mà không thấy"). (b) doc-drift điển hình: thêm flag vào 1 surface (Designer) quên surface song song (matrix) — thêm field phải grep MỌI nơi render flag-set.
## Xóa User TEST khỏi prod (`681b1ed`, prod data-op)
anh: "Xóa hết User test, giữ User Solution thật". Quy trình S59/S85 chuẩn: **recon → AskUserQuestion chốt → backup → script → run → verify**.
**Recon (ssh sqlcmd prod):** 55 user. FK: `ApproverUserId` NO_ACTION (chặn) nhưng **KHÔNG user test nào là approver** (10 approver đều thật) → an toàn; 5 bảng con CASCADE; `nv.test` 1 PE (DrafterUserId loose, không FK). Edge: `chuong.phan@solution.com.vn` typo-domain (anh chốt xóa).
**Script `scripts/s89-delete-test-users.sql`:** whitelist-keep 17 + BACKUP (`SolutionErp_S89_preuserdelete.bak`) + XACT_ABORT + count-guard[30,45]. Chạy: count=38, PE=1, **còn đúng 17 user thật**.
**Seed (`DbInitializer.cs`):** gỡ 16 user mẫu khỏi `SeedDemoUsersAsync` + gỡ `LockDemoSampleUsersAsync` (call+method). SeedItDepartmentStaff + sample-workflow seeds null-guard → deploy KHÔNG resurrect/crash. **PHẢI deploy** (binary cũ chứa seed-test sẽ tái-tạo khi prod restart).
**Lesson:** whitelist-keep > blacklist (an toàn — chỉ giữ tập đã verify); count-guard + backup + XACT_ABORT = 3 lớp chống xóa-nhầm; `chuong.phan@solution`(typo) KHÔNG phải approver → xóa an toàn, anh Chương dùng bản `@solutions` (pass seed `User@1234567`).
## Word guide — `docs/Huong-dan-Duyet-NCC.docx`
anh: "làm file Word hướng dẫn Duyệt NCC theo mẫu, logo Solution, kiểu 1→2→3 khoanh đỏ". Dùng skill `docx` (docx-js Node). Logo `fe-user/public/logo.png` (407×145) nhúng. 8 phần step-by-step + 12 ô chừa ảnh nét-đứt kiểu "khoanh đỏ theo số (1→2→3)" + Quy ước ảnh + TOC + cover + footer. Valid (ZIP+XML UTF-8 OK; validator charmap-error = bug encoding script trên Windows, không phải lỗi file). Copy sang Downloads.
**Pending:** ẢNH THẬT chưa chèn — em KHÔNG chụp được app từ máy (không trình-duyệt nối prod + pass role; lái máy anh cần quyền + tạo rác prod vừa-dọn). Cần anh chụp 12 màn gửi → em annotate khoanh-đỏ + chèn. (Mẫu PDF anh không render được pdftoppm/pypdf/fitz đều thiếu → dựng layout-chuẩn-Solution.)
## State THẬT (cuối S89)
**Mig 59 · 88 bảng · 422 test (45D+377I) · gotcha 73 · menu 54 · bundle admin `DalcOhvb`/user `CdS4tX_2`** (sau `9c920ad`; `fef4c33` matrix rotate user→`CdS4tX_2` — verify "không trình tiếp CEO" in-bundle). Prod **17 user thật**.
## 🔴 NEXT
- **Anh:** ⚠️ restart CLI (carry budget/cmd). **Chèn ảnh guide:** chụp 12 màn (theo nhãn ô) gửi em → em khoanh-đỏ 1→2→3 + chèn → xuất bản. Hoặc bật Chrome+extension Claude-in-Chrome + pass admin → em tự chụp.
- **Em (carry):** 🔴 curate `test-specialist` L1 **34.8KB OVER-SOFT** + cicd 28KB + reviewer 27.7KB (archive-gate). **Monthly 2026-07-01:** ef-core skill Mig→59 · STATUS/HANDOFF re-tier mega-line · 3 vocab-fork alias-map · docs/CLAUDE Budget-removed · matrix-flag-count comment.
- **Pending product:** Section B cột Dự-án wire · Ngưỡng giá CEO Mig 54 Designer UAT · "C" chuyển phiếu→dự án spec.
- **Ops:** email typo anh Chương (đã xóa bản typo S89) · 5 real-staff pw · gán CNTT.
**Residual:** 5 file agent-memory (cicd/inv/reviewer MEMORY + 2 plan-review reviewer) = harvest hợp-lệ sub-workflow, commit kèm closeout.