---
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").
