[CLAUDE] PurchaseEvaluation: ngan sach goi thau theo Excel anh Kiet - bang tong hop 2 block + nhap theo role PRO/CCM + xoa module Budget cu (Mig 50)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m31s

- Mig 50 ReplaceBudgetModuleWithPeWorkItemBudgets: bang moi PeWorkItemBudgets (1 record/cap Du an x Hang muc, UNIQUE filtered [IsDeleted]=0) + drop 5 bang Budget cu + PE/Contracts drop BudgetId + backfill BudgetManualAmount->BudgetPeriodAmount TRUOC DropColumn (phieu UAT giu so) + DELETE menu/permission Bg_* IN-list children-first
- BE: PUT {id}/budget/pro (role Procurement) + {id}/budget/ccm (role CostControl, Adjustment cho phep AM) fail-closed Forbidden-truoc-side-effect + EnsureTrackedAsync race-safe (catch unique -> re-fetch winner, loi khac rethrow) + auto-create record khi tao phieu + budgetSummary DTO (luy ke trinh-truoc/chon-thau-truoc/de-xuat-ky-nay + full fallback du-tru-PRO + canEdit flags) + submit-guard (3) doi predicate BudgetPeriodAmount -> "chua nhap Ngan sach ky nay" + PATCH budget-adjust absolute-set 2 field moi + Contract GIU BudgetManual* (HD nhap tay khong doi) + ke thua HD map BudgetPeriodAmount
- FE x2 app SHA256 identical: bang "TONG HOP NGAN SACH TRINH KY" block A (full dam + ban hanh + V0 hieu chinh + du tru PRO + ghi chu, editable theo canEditPro/canEditCcm) + block B 9 dong cong thuc Excel (5=1+3, 6=2+4, 7=full-5, 8 tu nhap default 7, 9=4+8) + to mau vuot ngan sach #C00000 / am do / red-soft row8>row7 + "Chua chon" khi count=0 + banner phieu chua gan Hang muc + o "Ngan sach ky nay" o create/header + XOA pages/components/types budgets + routes + menuKeys + Layout staticMap 4-place
- Tests: +22 PeWorkItemBudgetTests (auto-create x3, ensure/race x2, authz matrix PRO x5 + CCM x3, budgetSummary aggregates x5, adjust x4) - 14 BudgetPolicyTests xoa theo module - 1 test via-BudgetId -> 263 PASS (45 Domain + 218 Infra, 0 fail)
- database-agent advise adopted: khong FK vat ly PE/Contracts->Budgets (DropColumn khong can DropForeignKey) + DropIndex truoc DropColumn (SQL 5074) + IN-list thay LIKE Bg_% (underscore wildcard + miss root) + khong Serializable wrap (nested-tx conflict codegen)
- Reviewer PASS-with-minor 0 blocker (verdict-first survived); 2 minor da sua truoc commit (comment adjustMut absolute-set + dead key budgetId); note: F4 approver-edit-budget UI entry tam drafter-only, BE van cho approver scope - cho UAT anh Kiet
- Scaffold-bug caught: EF tu sinh RenameColumn BudgetManualAmount->ExpectedRemainingAmount (SAI semantics) -> thay bang Add+UPDATE+Drop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-13 01:07:27 +07:00
parent 6db195dd42
commit 79ef8da9f4
70 changed files with 9052 additions and 5956 deletions

View File

@ -1,145 +0,0 @@
using SolutionErp.Domain.Budgets;
using SolutionErp.Domain.Identity;
namespace SolutionErp.Domain.Tests.Budgets;
// Tests cho BudgetPolicy (hardcoded simple 3-step Default).
// Chống regression khi BudgetPhase enum thêm phase hoặc role mapping bị edit.
public class BudgetPolicyTests
{
[Fact]
public void Default_Drafter_DangSoanThao_To_ChoCCM_Allowed()
{
BudgetPolicies.Default
.IsTransitionAllowed(BudgetPhase.DangSoanThao, BudgetPhase.ChoCCM,
[AppRoles.Drafter])
.Should().BeTrue();
}
[Fact]
public void Default_DeptManager_DangSoanThao_To_ChoCCM_Allowed()
{
BudgetPolicies.Default
.IsTransitionAllowed(BudgetPhase.DangSoanThao, BudgetPhase.ChoCCM,
[AppRoles.DeptManager])
.Should().BeTrue();
}
[Fact]
public void Default_RandomRole_DangSoanThao_To_ChoCCM_Denied()
{
BudgetPolicies.Default
.IsTransitionAllowed(BudgetPhase.DangSoanThao, BudgetPhase.ChoCCM,
[AppRoles.Procurement])
.Should().BeFalse();
}
[Fact]
public void Default_CostControl_ChoCCM_To_ChoCEO_Allowed()
{
BudgetPolicies.Default
.IsTransitionAllowed(BudgetPhase.ChoCCM, BudgetPhase.ChoCEO,
[AppRoles.CostControl])
.Should().BeTrue();
}
[Fact]
public void Default_CostControl_ChoCCM_To_TraLai_Allowed()
{
// Session 17 spec: Trả lại = Phase riêng (TraLai), không revert DangSoanThao
BudgetPolicies.Default
.IsTransitionAllowed(BudgetPhase.ChoCCM, BudgetPhase.TraLai,
[AppRoles.CostControl])
.Should().BeTrue();
}
[Fact]
public void Default_Director_ChoCEO_To_DaDuyet_Allowed()
{
BudgetPolicies.Default
.IsTransitionAllowed(BudgetPhase.ChoCEO, BudgetPhase.DaDuyet,
[AppRoles.Director])
.Should().BeTrue();
BudgetPolicies.Default
.IsTransitionAllowed(BudgetPhase.ChoCEO, BudgetPhase.DaDuyet,
[AppRoles.AuthorizedSigner])
.Should().BeTrue();
}
[Fact]
public void Default_CCM_Cannot_Approve_To_DaDuyet()
{
// CCM chỉ chuyển đến ChoCEO, không tự duyệt thành DaDuyet
BudgetPolicies.Default
.IsTransitionAllowed(BudgetPhase.ChoCEO, BudgetPhase.DaDuyet,
[AppRoles.CostControl])
.Should().BeFalse();
}
[Fact]
public void Default_DaDuyet_NoFurtherTransitions()
{
BudgetPolicies.Default.NextPhasesFrom(BudgetPhase.DaDuyet)
.Should().BeEmpty("DaDuyet là terminal");
}
[Fact]
public void Default_TuChoi_NoFurtherTransitions()
{
BudgetPolicies.Default.NextPhasesFrom(BudgetPhase.TuChoi)
.Should().BeEmpty("TuChoi là terminal");
}
[Fact]
public void Default_ActivePhases_Includes_All6States()
{
// Session 17: thêm TraLai = Phase riêng cho Trả lại
BudgetPolicies.Default.ActivePhases.Should().BeEquivalentTo(new[]
{
BudgetPhase.DangSoanThao, BudgetPhase.TraLai, BudgetPhase.ChoCCM,
BudgetPhase.ChoCEO, BudgetPhase.DaDuyet, BudgetPhase.TuChoi,
});
}
[Fact]
public void Default_NextPhasesFrom_DangSoanThao_Includes_ChoCCM_And_TuChoi()
{
var next = BudgetPolicies.Default.NextPhasesFrom(BudgetPhase.DangSoanThao);
next.Should().Contain(BudgetPhase.ChoCCM);
next.Should().Contain(BudgetPhase.TuChoi);
}
[Fact]
public void Default_NextPhasesFrom_TraLai_Includes_ChoCCM_And_TuChoi()
{
// Drafter từ TraLai gửi lại = entry point thứ 2 (mirror DangSoanThao)
var next = BudgetPolicies.Default.NextPhasesFrom(BudgetPhase.TraLai);
next.Should().Contain(BudgetPhase.ChoCCM);
next.Should().Contain(BudgetPhase.TuChoi);
}
[Fact]
public void Default_NextPhasesFrom_ChoCEO_Includes_DaDuyet_And_TraLai()
{
var next = BudgetPolicies.Default.NextPhasesFrom(BudgetPhase.ChoCEO);
next.Should().Contain(BudgetPhase.DaDuyet);
next.Should().Contain(BudgetPhase.TraLai);
next.Should().Contain(BudgetPhase.TuChoi);
}
// SLA — chống regression khi đổi phase deadline accidentally
[Fact]
public void Default_SlaDeadlines_Match_Spec()
{
BudgetPolicies.Default.PhaseSla[BudgetPhase.DangSoanThao]
.Should().Be(TimeSpan.FromDays(5));
BudgetPolicies.Default.PhaseSla[BudgetPhase.ChoCCM]
.Should().Be(TimeSpan.FromDays(3));
BudgetPolicies.Default.PhaseSla[BudgetPhase.ChoCEO]
.Should().Be(TimeSpan.FromDays(2));
BudgetPolicies.Default.PhaseSla[BudgetPhase.DaDuyet]
.Should().BeNull("Terminal phase không có SLA");
}
}