[CLAUDE] PurchaseEvaluation: cho luu khi vuot ngan sach - chi canh bao mem (bo chan so am "Gia tri thuc hien du kien con lai") (UAT anh Kiet S62)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m40s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m40s
- Root cause: o "Gia tri thuc hien du kien con lai" (row 8 bang Tong hop ngan sach) khi gia tri NCC vuot ngan sach -> so du con lai ra AM; BE validator ExpectedRemainingAmount>=0 + FE VndInlineEdit khong bat allowNegative -> chan cung "am ko luu duoc" (testing bao qua anh Kiet) - BE: AdjustPurchaseEvaluationBudgetCommandValidator GO rule ExpectedRemainingAmount.GreaterThanOrEqualTo(0) -> cho luu so am (mirror tien le LeaveBalance AllowsNegativeRemaining). GIU BudgetPeriodAmount>0 + submit-guard "da nhap NS ky nay" khong doi - FE x2 app SHA256 identical: (a) allowNegative cho VndInlineEdit row 8; (b) banner amber "Vuot ngan sach - van luu & gui duyet duoc" trong PeBudgetSummaryTable khi cmpPeriod<0 || cmpFull<0. Tang to mau do cu GIU NGUYEN - Spec change: flip test AdjustBudget_Validator_ExpectedRemainingNegative_FailsValidation -> _PassesValidation (am gio hop le); test BudgetPeriodZero_FailsValidation GIU (budget>0 van enforced) - Build FE x2 PASS + test 263 PASS (45 Domain + 218 Infra, 0 fail/skip). Reviewer PASS 0 issue (row8 am an toan arithmetic additive-only, submit guard nguyen, mirror byte-identical, no scope creep) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -1052,6 +1052,19 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
Tổng hợp ngân sách trình ký
|
Tổng hợp ngân sách trình ký
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* [S62] Cảnh báo MỀM "vượt ngân sách" (anh Kiệt FDC) — KHÔNG chặn lưu, chỉ báo.
|
||||||
|
Hiện khi đề xuất kỳ này > NS kỳ này (cmpPeriod<0) hoặc tổng thực hiện > NS full
|
||||||
|
(cmpFull<0). Số dư còn lại âm vẫn lưu + gửi duyệt được. */}
|
||||||
|
{(cmpPeriod < 0 || cmpFull < 0) && (
|
||||||
|
<div className="flex items-start gap-2 border-b border-amber-300 bg-amber-50 px-3 py-2 text-[12px] text-amber-800">
|
||||||
|
<span className="shrink-0 text-sm leading-none">⚠️</span>
|
||||||
|
<span>
|
||||||
|
<strong>Vượt ngân sách</strong> — giá trị đề xuất NCC đang cao hơn ngân sách của gói thầu.
|
||||||
|
Phiếu <strong>vẫn lưu & gửi duyệt được</strong>, vui lòng kiểm tra lại số liệu trước khi trình.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ===== Block A — NGÂN SÁCH (gói thầu) ===== */}
|
{/* ===== Block A — NGÂN SÁCH (gói thầu) ===== */}
|
||||||
<BudgetBlockHeader>A. Ngân sách (gói thầu)</BudgetBlockHeader>
|
<BudgetBlockHeader>A. Ngân sách (gói thầu)</BudgetBlockHeader>
|
||||||
|
|
||||||
@ -1253,6 +1266,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
{drafterEditable ? (
|
{drafterEditable ? (
|
||||||
<VndInlineEdit
|
<VndInlineEdit
|
||||||
initial={ev.expectedRemainingAmount}
|
initial={ev.expectedRemainingAmount}
|
||||||
|
allowNegative
|
||||||
saving={adjustMut.isPending}
|
saving={adjustMut.isPending}
|
||||||
label="Giá trị thực hiện dự kiến còn lại"
|
label="Giá trị thực hiện dự kiến còn lại"
|
||||||
onSave={v => adjustMut.mutate({ budgetPeriodAmount: ev.budgetPeriodAmount, expectedRemainingAmount: v })}
|
onSave={v => adjustMut.mutate({ budgetPeriodAmount: ev.budgetPeriodAmount, expectedRemainingAmount: v })}
|
||||||
|
|||||||
@ -1052,6 +1052,19 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
Tổng hợp ngân sách trình ký
|
Tổng hợp ngân sách trình ký
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* [S62] Cảnh báo MỀM "vượt ngân sách" (anh Kiệt FDC) — KHÔNG chặn lưu, chỉ báo.
|
||||||
|
Hiện khi đề xuất kỳ này > NS kỳ này (cmpPeriod<0) hoặc tổng thực hiện > NS full
|
||||||
|
(cmpFull<0). Số dư còn lại âm vẫn lưu + gửi duyệt được. */}
|
||||||
|
{(cmpPeriod < 0 || cmpFull < 0) && (
|
||||||
|
<div className="flex items-start gap-2 border-b border-amber-300 bg-amber-50 px-3 py-2 text-[12px] text-amber-800">
|
||||||
|
<span className="shrink-0 text-sm leading-none">⚠️</span>
|
||||||
|
<span>
|
||||||
|
<strong>Vượt ngân sách</strong> — giá trị đề xuất NCC đang cao hơn ngân sách của gói thầu.
|
||||||
|
Phiếu <strong>vẫn lưu & gửi duyệt được</strong>, vui lòng kiểm tra lại số liệu trước khi trình.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ===== Block A — NGÂN SÁCH (gói thầu) ===== */}
|
{/* ===== Block A — NGÂN SÁCH (gói thầu) ===== */}
|
||||||
<BudgetBlockHeader>A. Ngân sách (gói thầu)</BudgetBlockHeader>
|
<BudgetBlockHeader>A. Ngân sách (gói thầu)</BudgetBlockHeader>
|
||||||
|
|
||||||
@ -1253,6 +1266,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
|
|||||||
{drafterEditable ? (
|
{drafterEditable ? (
|
||||||
<VndInlineEdit
|
<VndInlineEdit
|
||||||
initial={ev.expectedRemainingAmount}
|
initial={ev.expectedRemainingAmount}
|
||||||
|
allowNegative
|
||||||
saving={adjustMut.isPending}
|
saving={adjustMut.isPending}
|
||||||
label="Giá trị thực hiện dự kiến còn lại"
|
label="Giá trị thực hiện dự kiến còn lại"
|
||||||
onSave={v => adjustMut.mutate({ budgetPeriodAmount: ev.budgetPeriodAmount, expectedRemainingAmount: v })}
|
onSave={v => adjustMut.mutate({ budgetPeriodAmount: ev.budgetPeriodAmount, expectedRemainingAmount: v })}
|
||||||
|
|||||||
@ -319,7 +319,11 @@ public class AdjustPurchaseEvaluationBudgetCommandValidator : AbstractValidator<
|
|||||||
public AdjustPurchaseEvaluationBudgetCommandValidator()
|
public AdjustPurchaseEvaluationBudgetCommandValidator()
|
||||||
{
|
{
|
||||||
RuleFor(x => x.BudgetPeriodAmount).GreaterThan(0).When(x => x.BudgetPeriodAmount.HasValue);
|
RuleFor(x => x.BudgetPeriodAmount).GreaterThan(0).When(x => x.BudgetPeriodAmount.HasValue);
|
||||||
RuleFor(x => x.ExpectedRemainingAmount).GreaterThanOrEqualTo(0).When(x => x.ExpectedRemainingAmount.HasValue);
|
// [S62] "Giá trị thực hiện dự kiến còn lại" (row 8) CHO PHÉP ÂM. Khi giá trị NCC
|
||||||
|
// vượt ngân sách → số dư còn lại tự ra âm; nghiệp vụ (anh Kiệt FDC 2026-06-13)
|
||||||
|
// yêu cầu VẪN lưu được, chỉ cảnh báo (FE banner "Vượt ngân sách"). Gỡ ràng buộc
|
||||||
|
// GreaterThanOrEqualTo(0) cũ — mirror tiền lệ LeaveBalance (cho phép số dư âm).
|
||||||
|
// BudgetPeriodAmount vẫn phải > 0; submit guard "đã nhập NS kỳ này" không đổi.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -652,15 +652,15 @@ public class PeWorkItemBudgetTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AdjustBudget_Validator_ExpectedRemainingNegative_FailsValidation()
|
public void AdjustBudget_Validator_ExpectedRemainingNegative_PassesValidation()
|
||||||
{
|
{
|
||||||
// GreaterThanOrEqualTo(0) when HasValue → âm không hợp lệ (row 8 không cho âm).
|
// [S62] SPEC CHANGE — "Giá trị thực hiện dự kiến còn lại" (row 8) ÂM giờ HỢP LỆ.
|
||||||
|
// Vượt ngân sách → số dư còn lại ra âm; nghiệp vụ (anh Kiệt FDC) cho lưu, chỉ
|
||||||
|
// cảnh báo. Rule GreaterThanOrEqualTo(0) cũ đã gỡ. BudgetPeriodAmount=80 > 0 hợp lệ.
|
||||||
var validator = new AdjustPurchaseEvaluationBudgetCommandValidator();
|
var validator = new AdjustPurchaseEvaluationBudgetCommandValidator();
|
||||||
var result = validator.Validate(new AdjustPurchaseEvaluationBudgetCommand(Guid.NewGuid(), 80m, -1m));
|
var result = validator.Validate(new AdjustPurchaseEvaluationBudgetCommand(Guid.NewGuid(), 80m, -1m));
|
||||||
|
|
||||||
result.IsValid.Should().BeFalse();
|
result.IsValid.Should().BeTrue();
|
||||||
result.Errors.Should().Contain(e =>
|
|
||||||
e.PropertyName == nameof(AdjustPurchaseEvaluationBudgetCommand.ExpectedRemainingAmount));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
Reference in New Issue
Block a user