[CLAUDE] PurchaseEvaluation: Section B (THỰC HIỆN) tách 3 cột per-cột kỳ-này (Mig 59, anh Kiệt FDC / CCM)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m8s

Section B "Tổng hợp ngân sách trình ký" tách 3 cột Dự án|PRO|CCM giống Section A —
mỗi cột nhập "NS kỳ này" RIÊNG + tính thực-hiện độc-lập (lũy-kế / còn-lại / % /
so-sánh) theo ngân-sách-full của cột đó:
- PRO: budgetPeriodAmount (có sẵn) đối chiếu proFull
- CCM: ccmBudgetPeriodAmount (MỚI, Mig 59) đối chiếu ccmFull — CCM nhập (canEditCcm)
- Dự án: — (chưa wire, như A)
Số thực-tế dùng chung mọi cột (NCC đề xuất, lũy kế lịch sử). Submit-guard giữ trên
budgetPeriodAmount (không đụng).

Mig 59 AddCcmBudgetPeriodToPurchaseEvaluation (1 cột nullable, no backfill). NEW
endpoint PATCH /budget/ccm-period role-gate Admin|CostControl (fail-closed). FE
Section B rewrite <BudgetRow> list -> <table> 3 cột (colCalc per-cột), 2 app SHA-mirror.
Test 395->402 (+7 authz setter). Build sạch BE + 2 FE. Workflow wf_aafb18fe-d9d
(BE->FE->test + 3-lane review: math PASS, authz PASS, mirror self-gated identical).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-24 16:06:59 +07:00
parent f1b10e9c33
commit 9b1ee0c3a5
14 changed files with 7121 additions and 397 deletions

View File

@ -115,6 +115,18 @@ public class PurchaseEvaluationsController(IMediator mediator) : ControllerBase
}
public record SuggestedPriceCcmBody(decimal? CcmPrice, string? Note);
// [Mig 59 2026-06-24 — anh Kiệt FDC / CCM] "NS kỳ này" cột CCM (Section B THỰC HIỆN
// tách 3 cột Dự án|PRO|CCM). PRO dùng /budget-adjust sẵn có; CCM nhập cột riêng.
// Class [Authorize] any-auth; handler fine-grained Forbidden (CCM=CostControl | Admin).
// Absolute-set (null=clear). Budget=0 hợp lệ.
[HttpPatch("{id:guid}/budget/ccm-period")]
public async Task<IActionResult> SetCcmBudgetPeriod(Guid id, [FromBody] CcmBudgetPeriodBody body, CancellationToken ct)
{
await mediator.Send(new SetPeCcmBudgetPeriodCommand(id, body.CcmBudgetPeriodAmount), ct);
return NoContent();
}
public record CcmBudgetPeriodBody(decimal? CcmBudgetPeriodAmount);
[HttpPost("{id:guid}/transitions")]
public async Task<IActionResult> Transition(Guid id, [FromBody] TransitionPeBody body, CancellationToken ct)
{