# sub-test-specialist-0 — TEST-C: PE lũy kế TẠM TÍNH (S134)

## Nhiệm vụ
Viết test cho code BE ĐÃ LAND (INV-2): `PeBudgetAccumulator.ComputePendingAsync` + `PeBudgetSummaryDto` +5 field + D4 changelog on-finalize. C1-C9. KHÔNG touch prod.

## Bước 1 — Đọc code under test (DONE)
- `tests/.../Services/PeBudgetFreezeTests.cs` — seed-helper pattern :277-336 (SeedWinnerQuoteAsync, SeedPeerAsync CreatedAt-override thủ công :332), T0 flow finalize path, T7 frozen query :730, C8-mirror pattern (Changelog discriminator).
- `src/Backend/SolutionErp.Application/PurchaseEvaluations/PeBudgetAccumulator.cs`:
  - `ComputePendingAsync` :92-139 — peers window Y HỆT ComputeAsync (:100-102: ProjectId+WorkItemId, Id!=peId, CreatedAt<peCreatedAt).
    - PendingSubmitted :104-112 = Phase NOT IN {DangSoanThao, DaDuyet, ChoDuyet, TuChoi} → Count + SUM(BudgetPeriodAmount ?? 0).
    - PendingSelected :114-126 = Phase NOT IN {DangSoanThao, DaDuyet, TuChoi} AND Suppliers.Any(IsWinner) → Count + SUM(ThanhTien WHERE IsSelected).
    - PriorPes :128-133 = Phase NOT IN {DaDuyet, TuChoi} orderby CreatedAt → PePendingPriorPeDto(Id, MaPhieu, (int)Phase).
  - `PePendingAccumulation` record struct :31-36.

## Bước 2 — DTO + query handler + D4 (DONE)
- `PePendingPriorPeDto(Guid Id, string? MaPhieu, int Phase)` @ `Dtos/PurchaseEvaluationDtos.cs:324` (ns `SolutionErp.Application.PurchaseEvaluations.Dtos`).
- `PeBudgetSummaryDto` +5 field cuối :362-366 (PendingSubmittedTotal/Count, PendingSelectedTotal/Count, PendingPriorPes default null).
- Query handler `PurchaseEvaluationFeatures.cs`: nhánh frozen :930-959 (Pending 4 số = 0 + PendingPriorPes null); nhánh live :961-1000 (fill từ ComputePendingAsync :972).
- D4 `ApplyBudgetSnapshotOnFinalizeAsync` @ `PurchaseEvaluationWorkflowService.cs:998` — :1025-1053 nếu ComputePendingAsync.PriorPes.Count>0 → +1 Changelog {EntityType=Workflow, Action=Update, PhaseAtChange=DaDuyet, UserId=null, UserName="Hệ thống", ContextNote chứa "[S134 pending-prior]" + priorList (MaPhieu + label)}.
- Phase enum: DangSoanThao=1, legacy 2-6, DaDuyet=7, ChoDuyet=10, TraLai=98, TuChoi=99.
- BaseEntity.CreatedAt = plain DateTime no-default → phải override thủ công + peCreatedAt muộn hơn.
- Helpers khả dụng cùng ns Services: NoOpNotificationService, FixedDateTime, TestCurrentUser, IdentityFixture.

## Bước 3 — viết PePendingAccumulationTests.cs (DONE)
File MỚI `tests/SolutionErp.Infrastructure.Tests/Services/PePendingAccumulationTests.cs` — 8 [Fact] C1-C8, mirror seed-helper PeBudgetFreezeTests. Helper copy: CreateService, SeedProjectAndWorkItemAsync, SeedPairAsync (CCM-only), SeedWinnerQuoteAsync, SeedPeerAsync (return peer), SeedFinalizeWorkflowAsync, BuildPeAtFinalizeSlot, ApproveWithPriceAsync, ComputePendingAsync convenience (synthetic curPeId + CurCreatedAt=noon).

| Test | Method | Kết quả |
|---|---|---|
| C1 | `ChoDuyetWinner_CountsInPendingSelected_AndListedInPriorPes` :158 | PendingSelected=70tr/1, Submitted=0, PriorPes Id+MaPhieu+Phase=10 |
| C2 | `TuChoiAndDaDuyetPeers_ContributeNothingToPending_AndAbsentFromPriorPes` :185 | delta all 0, PriorPes empty |
| C3 | `DraftPeer_ListOnly_TraLaiPeer_CountsInPendingSubmitted` :208 | Submitted=60tr(TraLai)/1 (Nháp 40tr list-only), PriorPes 2 order CreatedAt |
| C4 | `PeerCreatedAfterCurrent_ExcludedFromWindow` :234 | before lọt (25tr/1), after loại (NotContain) |
| C5 | `NegativeSelectedQuote_ProducesSignedPendingSelectedTotal` :257 | PendingSelected=-500k signed |
| C6 | `Compute_And_ComputePending_AreComplementary_NoDoubleCount` :278 | ComputeAsync PrevSelected=90tr/1 PrevSubmitted=130tr/2 bất động; Pending Selected=50tr/1 Submitted=0 |
| C7 | `FrozenQuery_ReturnsZeroPendingAndNullPriorPes_EvenWithPendingPeer` :312 | BudgetFrozen=true, Pending 4=0, PriorPes null (dù có peer ChoDuyet) |
| C8 | `Finalize_WithPendingPriorPeer_WritesAuditChangelog_ContrastNoPeerWritesNone` :355 | +1 D4 Update/Workflow/UserId=null/Hệ thống/ContextNote⊇MaPhieu A; contrast=0 |

## Bước 4 — chạy suite (DONE)
- Filtered `~PePendingAccumulationTests`: **Passed 8/8, Failed 0** (3s).
- FULL `dotnet test SolutionErp.slnx --nologo --verbosity minimal -p:BuildInParallel=false -maxcpucount:1`:
  - Domain **45 PASS** 0 fail; Infra **483 PASS** 0 fail → **528 total, 0 fail**.
  - Baseline 520 (45D+475I) → **528 (45D+483I) = +8 Infra**. C9 PASS.
- Build 0 error. KHÔNG touch production. KHÔNG git add/commit/push. NO prod bug found.
