[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:
@ -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 tư 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.
|
||||
- **SSH→PS quoting (S42):** nested bash→ssh→powershell 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 93→88 — 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-history−1 (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`)_
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -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.
|
||||
@ -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
BIN
docs/Huong-dan-Duyet-NCC.docx
Normal file
BIN
docs/Huong-dan-Duyet-NCC.docx
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -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.
|
||||
Reference in New Issue
Block a user