[CLAUDE] PurchaseEvaluation: require quy trinh duyet V2 o create+submit (dong lo hong quy-trinh-cu)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m58s

Validator NotEmpty(ApprovalWorkflowId) + submit guard (sau Section-3, truoc mutate phase). Dong lo hong validate FE-only -> phieu null-workflow ket nhanh V1-legacy "quy trinh cu" (khong Buoc/Cap, khong route duyet). Test-before (RED confirmed): +2 validator test (PeWorkItemGuardTests) + rewrite test 7/13 PeSubmitGuardAndBypass (V1-submit deprecated, data V1 wipe S59). 356 pass (45D+311I).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-22 17:06:46 +07:00
parent 2c7fd635b9
commit fc1f19db8c
4 changed files with 82 additions and 26 deletions

View File

@ -34,6 +34,12 @@ namespace SolutionErp.Infrastructure.Tests.Services;
//
// LƯU Ý GUARD-FIRST: submit guard chạy TRƯỚC bypass → mọi test bypass phải dựng
// PE ĐỦ 4 điều kiện Section 3 (winner + quote>0 + manual budget + comparison file).
//
// [S83 spec change] Thêm guard require-workflow (SAU Section-3, TRƯỚC mutate phase):
// phiếu KHÔNG pin ApprovalWorkflowId KHÔNG được gửi duyệt (đóng lỗ hổng "quy trình
// cũ" — phiếu null-workflow kẹt nhánh V1-legacy). Test (7) + (13) cập nhật từ
// "submit OK no-workflow" → "throws". V1-submit deprecated (data V1 cũ wipe S59,
// mọi phiếu mới = V2). Bypass tests (9-12,14) pin workflow → KHÔNG ảnh hưởng.
public class PeSubmitGuardAndBypassTests
{
private static (PurchaseEvaluationWorkflowService svc, IdentityFixture fix,
@ -337,12 +343,13 @@ public class PeSubmitGuardAndBypassTests
}
[Fact]
public async Task Submit_AllFourMet_ManualBudget_NoWorkflow_SetsChoDuyet()
public async Task Submit_AllFourMet_NoWorkflow_ThrowsWorkflowRequired_S83()
{
// (7) Đủ 4 (manual budget > 0, KHÔNG BudgetId, KHÔNG ApprovalWorkflowId)
// submit OK Phase=ChoDuyet. V1/no-workflow: pointer StepIdx=0, Level null
// (line 208 — chỉ init level=1 nếu V2).
var (svc, fix, db, clock) = CreateService();
// (7) [S83 spec change] Đủ 4 Section 3 NHƯNG KHÔNG pin ApprovalWorkflowId →
// submit BỊ CHẶN. Trước S83: set ChoDuyet với level pointer null (V1-legacy);
// từ S83: require-workflow guard (SAU Section-3, TRƯỚC mutate) → Conflict,
// phiếu giữ Nháp. Đóng lỗ hổng "quy trình cũ" (phiếu null-workflow kẹt V1).
var (svc, fix, db, _) = CreateService();
using (fix)
{
var pe = BuildPeNhap(budgetPeriodAmount: 750_000m);
@ -353,12 +360,12 @@ public class PeSubmitGuardAndBypassTests
SeedComparisonAttachment(db, pe);
await db.SaveChangesAsync(CancellationToken.None);
await SubmitAsync(svc, pe, Guid.NewGuid());
var act = () => SubmitAsync(svc, pe, Guid.NewGuid());
pe.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet);
pe.CurrentWorkflowStepIndex.Should().Be(0);
pe.CurrentApprovalLevelOrder.Should().BeNull("phiếu không pin V2 → level pointer null");
pe.SlaDeadline.Should().Be(clock.UtcNow.AddDays(7));
var ex = await act.Should().ThrowAsync<ConflictException>();
ex.Which.Message.Should().Contain("quy trình duyệt");
pe.Phase.Should().Be(PurchaseEvaluationPhase.DangSoanThao,
"guard require-workflow chặn TRƯỚC mutate phase");
}
}
@ -541,10 +548,12 @@ public class PeSubmitGuardAndBypassTests
}
[Fact]
public async Task Submit_V1Phieu_NoApprovalWorkflowId_SubmitsOk_NoBypass_NoCrash()
public async Task Submit_V1Phieu_NoApprovalWorkflowId_NowBlocked_S83()
{
// (13) Phiếu V1 (ApprovalWorkflowId null) submit OK, KHÔNG bypass (V2-only),
// KHÔNG crash. Đủ 4 điều kiện Section 3 vẫn áp.
// (13) [S83 spec change] Trước S83: phiếu V1 (ApprovalWorkflowId null) submit OK
// (V1-legacy). Từ S83: require-workflow guard CHẶN — V1-submit deprecated (data
// V1 cũ đã wipe S59, mọi phiếu mới = V2). Đủ 4 Section 3 vẫn bị chặn ở bước
// workflow. Để gửi duyệt: mở Sửa chọn quy trình V2.
var (svc, fix, db, _) = CreateService();
using (fix)
{
@ -557,15 +566,12 @@ public class PeSubmitGuardAndBypassTests
SeedComparisonAttachment(db, pe);
await db.SaveChangesAsync(CancellationToken.None);
await SubmitAsync(svc, pe, drafter);
var act = () => SubmitAsync(svc, pe, drafter);
pe.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet);
pe.CurrentApprovalLevelOrder.Should().BeNull("V1 → level pointer null, không bypass");
var autoApprovals = await db.PurchaseEvaluationApprovals
.Where(a => a.PurchaseEvaluationId == pe.Id
&& a.Decision == ApprovalDecision.AutoApprove).ToListAsync();
autoApprovals.Should().BeEmpty("V1 không bypass → 0 AutoApprove row");
var ex = await act.Should().ThrowAsync<ConflictException>();
ex.Which.Message.Should().Contain("quy trình duyệt");
pe.Phase.Should().Be(PurchaseEvaluationPhase.DangSoanThao,
"V1-submit deprecated S83 → giữ Nháp");
}
}