diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx
index ca22477..3aaf94b 100644
--- a/fe-admin/src/components/pe/PeDetailTabs.tsx
+++ b/fe-admin/src/components/pe/PeDetailTabs.tsx
@@ -1052,6 +1052,19 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
Tổng hợp ngân sách trình ký
+ {/* [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) && (
+
+ ⚠️
+
+ Vượt ngân sách — giá trị đề xuất NCC đang cao hơn ngân sách của gói thầu.
+ Phiếu vẫn lưu & gửi duyệt được, vui lòng kiểm tra lại số liệu trước khi trình.
+
+
+ )}
+
{/* ===== Block A — NGÂN SÁCH (gói thầu) ===== */}
A. Ngân sách (gói thầu)
@@ -1253,6 +1266,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
{drafterEditable ? (
adjustMut.mutate({ budgetPeriodAmount: ev.budgetPeriodAmount, expectedRemainingAmount: v })}
diff --git a/fe-user/src/components/pe/PeDetailTabs.tsx b/fe-user/src/components/pe/PeDetailTabs.tsx
index ca22477..3aaf94b 100644
--- a/fe-user/src/components/pe/PeDetailTabs.tsx
+++ b/fe-user/src/components/pe/PeDetailTabs.tsx
@@ -1052,6 +1052,19 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
Tổng hợp ngân sách trình ký
+ {/* [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) && (
+
+ ⚠️
+
+ Vượt ngân sách — giá trị đề xuất NCC đang cao hơn ngân sách của gói thầu.
+ Phiếu vẫn lưu & gửi duyệt được, vui lòng kiểm tra lại số liệu trước khi trình.
+
+
+ )}
+
{/* ===== Block A — NGÂN SÁCH (gói thầu) ===== */}
A. Ngân sách (gói thầu)
@@ -1253,6 +1266,7 @@ function PeBudgetSummaryTable({ ev, readOnly }: { ev: PeDetailBundle; readOnly:
{drafterEditable ? (
adjustMut.mutate({ budgetPeriodAmount: ev.budgetPeriodAmount, expectedRemainingAmount: v })}
diff --git a/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs b/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs
index 59293a5..f63453d 100644
--- a/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs
+++ b/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs
@@ -319,7 +319,11 @@ public class AdjustPurchaseEvaluationBudgetCommandValidator : AbstractValidator<
public AdjustPurchaseEvaluationBudgetCommandValidator()
{
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.
}
}
diff --git a/tests/SolutionErp.Infrastructure.Tests/Application/PeWorkItemBudgetTests.cs b/tests/SolutionErp.Infrastructure.Tests/Application/PeWorkItemBudgetTests.cs
index 11f7901..73f0d93 100644
--- a/tests/SolutionErp.Infrastructure.Tests/Application/PeWorkItemBudgetTests.cs
+++ b/tests/SolutionErp.Infrastructure.Tests/Application/PeWorkItemBudgetTests.cs
@@ -652,15 +652,15 @@ public class PeWorkItemBudgetTests
}
[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 result = validator.Validate(new AdjustPurchaseEvaluationBudgetCommand(Guid.NewGuid(), 80m, -1m));
- result.IsValid.Should().BeFalse();
- result.Errors.Should().Contain(e =>
- e.PropertyName == nameof(AdjustPurchaseEvaluationBudgetCommand.ExpectedRemainingAmount));
+ result.IsValid.Should().BeTrue();
}
[Fact]