[CLAUDE] PurchaseEvaluation: Mig 57 ghi chu gia de xuat PRO/CCM + so phan cach VND + sua chinh ta + guard #70
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m52s

Theo Tra Sol + anh Kiet FDC (Zalo): A) o nhap tien VndInlineEdit + BudgetCell nhay phan cach vi-VN (300.000.000) on-keystroke (o dialog da co san). B) them o ghi chu PRO + CCM canh nut Luu trong khoi Gia de xuat (giai thich vi sao chon Min/Max) — Mig 57 AddPeSuggestedPriceNotes (+ProSuggestedPriceNote +CcmSuggestedPriceNote nvarchar(1000) null, additive no-backfill no-table); 2 setter command +Note absolute-set rides role-gate PRO/CCM/Admin; DTO +2 field; controller body +note. C) sua chinh ta 'd. Ban so sanh' -> 'd. Bang so sanh gia' (2 app). GUARD gotcha #70: o gia + ghi chu echo nhau absolute-set -> them peFetching khoa nut Luu toi khi pe-detail refetch land, tranh stale-echo mat du lieu (mirror bang ngan sach S76; em-main review bat impl-frontend sot guard). BE slnx 0-warn 0-err; FE build PASS x2; test 344->351 (+7); PeDetailTabs/PeWorkspaceCreateView 2 app SHA256-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-19 14:08:45 +07:00
parent 3b98845976
commit 94e0e12f77
16 changed files with 6714 additions and 36 deletions

View File

@ -257,6 +257,10 @@ public record PurchaseEvaluationDetailBundleDto(
decimal? ProSuggestedMinPrice,
decimal? ProSuggestedMaxPrice,
decimal? CcmSuggestedPrice,
// [Mig 57 2026-06-19] Ghi chú giải thích giá đề xuất — vì sao min vs max (PRO) / 1 giá
// (CCM). FE render <Textarea> kế ô giá, set cùng endpoint suggested-price/{pro,ccm}.
string? ProSuggestedPriceNote,
string? CcmSuggestedPriceNote,
decimal? ApprovedPriceAmount,
string? ApprovedPriceSource,
bool CanEditProSuggestedPrice,

View File

@ -25,7 +25,8 @@ namespace SolutionErp.Application.PurchaseEvaluations;
public record UpdatePeSuggestedPriceProCommand(
Guid PeId,
decimal? MinPrice,
decimal? MaxPrice) : IRequest;
decimal? MaxPrice,
string? Note = null) : IRequest; // [Mig 57] ghi chú giải thích min vs max
public class UpdatePeSuggestedPriceProCommandValidator : AbstractValidator<UpdatePeSuggestedPriceProCommand>
{
@ -37,6 +38,7 @@ public class UpdatePeSuggestedPriceProCommandValidator : AbstractValidator<Updat
RuleFor(x => x).Must(x => x.MinPrice!.Value <= x.MaxPrice!.Value)
.When(x => x.MinPrice.HasValue && x.MaxPrice.HasValue)
.WithMessage("Giá Min phải ≤ Giá Max.");
RuleFor(x => x.Note).MaximumLength(1000); // [Mig 57] MATCH EF HasMaxLength(1000)
}
}
@ -59,14 +61,18 @@ public class UpdatePeSuggestedPriceProCommandHandler(
var oldMin = pe.ProSuggestedMinPrice;
var oldMax = pe.ProSuggestedMaxPrice;
var oldNote = pe.ProSuggestedPriceNote;
pe.ProSuggestedMinPrice = request.MinPrice; // absolute-set (null = clear)
pe.ProSuggestedMaxPrice = request.MaxPrice;
pe.ProSuggestedPriceNote = request.Note; // [Mig 57] absolute-set (null = clear)
var parts = new List<string>();
if (oldMin != request.MinPrice)
parts.Add($"giá Min {oldMin?.ToString("N0") ?? "(trống)"}đ → {request.MinPrice?.ToString("N0") ?? "(trống)"}đ");
if (oldMax != request.MaxPrice)
parts.Add($"giá Max {oldMax?.ToString("N0") ?? "(trống)"}đ → {request.MaxPrice?.ToString("N0") ?? "(trống)"}đ");
if (oldNote != request.Note)
parts.Add("ghi chú giá");
db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
{
@ -87,13 +93,15 @@ public class UpdatePeSuggestedPriceProCommandHandler(
public record UpdatePeSuggestedPriceCcmCommand(
Guid PeId,
decimal? CcmPrice) : IRequest;
decimal? CcmPrice,
string? Note = null) : IRequest; // [Mig 57] ghi chú giải thích giá CCM
public class UpdatePeSuggestedPriceCcmCommandValidator : AbstractValidator<UpdatePeSuggestedPriceCcmCommand>
{
public UpdatePeSuggestedPriceCcmCommandValidator()
{
RuleFor(x => x.CcmPrice).GreaterThanOrEqualTo(0).When(x => x.CcmPrice.HasValue);
RuleFor(x => x.Note).MaximumLength(1000); // [Mig 57] MATCH EF HasMaxLength(1000)
}
}
@ -114,8 +122,11 @@ public class UpdatePeSuggestedPriceCcmCommandHandler(
}
var oldCcm = pe.CcmSuggestedPrice;
pe.CcmSuggestedPrice = request.CcmPrice; // absolute-set (null = clear)
var oldNote = pe.CcmSuggestedPriceNote;
pe.CcmSuggestedPrice = request.CcmPrice; // absolute-set (null = clear)
pe.CcmSuggestedPriceNote = request.Note; // [Mig 57] absolute-set (null = clear)
var noteChanged = oldNote != request.Note ? " (kèm ghi chú)" : "";
db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
{
PurchaseEvaluationId = pe.Id,
@ -124,7 +135,7 @@ public class UpdatePeSuggestedPriceCcmCommandHandler(
PhaseAtChange = pe.Phase,
UserId = currentUser.UserId,
UserName = currentUser.FullName ?? currentUser.Email,
Summary = $"Giá đề xuất (CCM): {oldCcm?.ToString("N0") ?? "(trống)"}đ → {request.CcmPrice?.ToString("N0") ?? "(trống)"}đ",
Summary = $"Giá đề xuất (CCM): {oldCcm?.ToString("N0") ?? "(trống)"}đ → {request.CcmPrice?.ToString("N0") ?? "(trống)"}đ{noteChanged}",
});
await db.SaveChangesAsync(ct);

View File

@ -1098,6 +1098,7 @@ public class GetPurchaseEvaluationQueryHandler(
e.BudgetPeriodAmount, e.ExpectedRemainingAmount, peBudgetSummary,
e.IsUrgentByPro, e.IsUrgentByCcm, winnerQuoteTotal, awCeoThreshold, // [S69] cờ gấp + giá trị gói + ngưỡng CEO
e.ProSuggestedMinPrice, e.ProSuggestedMaxPrice, e.CcmSuggestedPrice, // [Mig 54] giá đề xuất PRO/CCM
e.ProSuggestedPriceNote, e.CcmSuggestedPriceNote, // [Mig 57] ghi chú giải thích giá
e.ApprovedPriceAmount, e.ApprovedPriceSource, // [Mig 54] giá chốt người duyệt chọn
canEditProSuggested, canEditCcmSuggested, // [Mig 54] capability role-gate
e.ApprovalWorkflowId, awCode, awName, awVersion, currentLevelOptions,