-**Miglatestrepo:****Mig 59 `20260624084608_AddCcmBudgetPeriodToPurchaseEvaluation`**(S88#345;PE+1AddColumn`CcmBudgetPeriodAmount`decimal(18,2)NULLno-backfill—VERIFIED-APPLIED-PRODhistory-HEAD+col-exists0-nonnull).PrevMig58`AddPeMultiWinnerAndLevelFinalize`(S87#344).Path`src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/`.Prodcheck`sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC TOP 5`.⚠️**`sys.tables`(is_ms_shipped=0)=88**(S61Budget-replaceDROPPED93→88—narrative-93STALE;whencommittouchesnoschema,88correct,don'tFAILon88.Alwayscross-refCOMMITscopevsambientcount).enum-additive(e.g.`ApprovalAttachment=5`)=int-storedNO-mig→expecthistoryNOT-advance+empty`git diff -- '*Migrations*'`.
-**Bundlehashlive(S89Run #348):**adminjs`BQq2eTP6`/css`Pltrs5ku`·userjs`dHa64I59`/css`Foiw55B6`(JSBOTHROTATEDfrom#347`CP_iKhTu`/`DUe0fndl`byrealfe-srcD2 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.**RealFEship =diff`fe-*/src`incommit/range+real-size-vs-fake(1.6/1.5MBvs~919bSPA-trap),NOThashdelta.ASYMMETRIC-deployframing(S66)RETIREDby#69.
-**Bundlehashlive(S90Run#465/id=465):**adminjs`BXixQDuS`/css`DWQ-DgfZ`·userjs`BMgckUPd`/css`Dr0-ikKX`(JSBOTHROTATEDfrompre-deploylive`BeyLzf5i`/`CPe970sY`byrealfe-srcPeDetailTabs×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-deploybaselinewas`BeyLzf5i`/`CPe970sY`(Run#350web.configrotatedsince#348`BQq2eTP6`/`dHa64I59`—alwaysre-snapshotLIVEbeforedeploy,brief'sstaleprev-hashmisleads).⚠️**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.**RealFEship =diff`fe-*/src`incommit/range+real-size-vs-fake(1.6/1.5MBvs~919bSPA-trap),NOThashdelta.ASYMMETRIC-deployframing(S66)RETIREDby#69.
@ -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ấpX> 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.
- [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.
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.
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.
- 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").
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.