[CLAUDE] PurchaseEvaluation: CV PRO refinement batch (anh Kiệt FDC) — live budget recompute + clear winner + fullscreen preview + hide create c/d
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m59s

C1 ẩn "c. Giá chào thầu" + "d. Bảng so sánh giá" khỏi form TẠO (vẫn hiện ở Detail tabs)
C3 ô 8 "Giá trị TH dự kiến còn lại" pre-fill từ dòng 7 (Ngân sách còn lại)
C4a live-recompute: VndInlineEdit +onLiveChange, PeBudgetSummaryTable draftRow3/8 → dòng 5/6/7/9 + So sánh + % nhảy NGAY khi gõ ô 3/8 (chưa cần Lưu)
C4b So sánh = 0 → "Bằng ngân sách" / "—" thay "0 đ" (chỉ khi base>0)
C5 hủy/đổi NCC winner: BE SelectWinnerBody(Guid?) + Command/Handler 2 nhánh (null=clear bỏ-qua-validate-list / non-null=giữ logic cũ); FE dropdown ""→clear + nút ✓ toggle. KHÔNG migration (SelectedSupplierId đã Guid?)
C7 nút "Toàn màn hình" preview file báo giá (overlay inset-0 + Esc thoát)

BE 2 file (Api controller record + Application command/handler). FE 3 file × 2 app SHA-identical. Test +10 PeSelectWinnerClearTests (4 clear + 4 select-regression + 2 PE-existence) → 366 PASS. Both FE build PASS, slnx build 0 err.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-23 08:47:51 +07:00
parent 0e159908d6
commit 5a6125494b
9 changed files with 546 additions and 124 deletions

View File

@ -387,7 +387,9 @@ public class DeletePurchaseEvaluationQuoteCommandHandler(
// ========== Select winner (NCC được chọn tổng thể) ==========
public record SelectPurchaseEvaluationWinnerCommand(Guid PurchaseEvaluationId, Guid SupplierId) : IRequest;
// SupplierId nullable [anh Kiệt FDC C5]: null = BỎ CHỌN (xóa đơn vị đã chọn, điền
// lại) — un-select winner về none; có giá trị = chọn/đổi NCC (logic cũ giữ 100%).
public record SelectPurchaseEvaluationWinnerCommand(Guid PurchaseEvaluationId, Guid? SupplierId) : IRequest;
public class SelectPurchaseEvaluationWinnerCommandHandler(
IApplicationDbContext db,
@ -398,15 +400,27 @@ public class SelectPurchaseEvaluationWinnerCommandHandler(
var entity = await db.PurchaseEvaluations.FirstOrDefaultAsync(x => x.Id == request.PurchaseEvaluationId, ct)
?? throw new NotFoundException("PurchaseEvaluation", request.PurchaseEvaluationId);
_ = await db.Suppliers.FirstOrDefaultAsync(s => s.Id == request.SupplierId, ct)
?? throw new NotFoundException("Supplier", request.SupplierId);
if (request.SupplierId is Guid supplierId)
{
// ── Chọn / đổi NCC trúng thầu (logic cũ giữ nguyên 100%) ──
_ = await db.Suppliers.FirstOrDefaultAsync(s => s.Id == supplierId, ct)
?? throw new NotFoundException("Supplier", supplierId);
// Verify supplier nằm trong danh sách phiếu
var hasSupplier = await db.PurchaseEvaluationSuppliers
.AnyAsync(s => s.PurchaseEvaluationId == request.PurchaseEvaluationId && s.SupplierId == request.SupplierId, ct);
if (!hasSupplier) throw new ConflictException("NCC chưa được thêm vào phiếu đánh giá.");
// Verify supplier nằm trong danh sách phiếu
var hasSupplier = await db.PurchaseEvaluationSuppliers
.AnyAsync(s => s.PurchaseEvaluationId == request.PurchaseEvaluationId && s.SupplierId == supplierId, ct);
if (!hasSupplier) throw new ConflictException("NCC chưa được thêm vào phiếu đánh giá.");
entity.SelectedSupplierId = request.SupplierId;
entity.SelectedSupplierId = supplierId;
}
else
{
// ── Bỏ chọn (clear về none) — KHÔNG validate participating list ──
// Row 4 "Giá trị kỳ này" + winnerQuoteTotal tính-khi-đọc theo
// SelectedSupplierId (PurchaseEvaluationFeatures GetDetail) → tự về 0.
// ApprovedPriceAmount/Source là cột chốt giai-đoạn DaDuyet, KHÔNG đụng ở đây.
entity.SelectedSupplierId = null;
}
db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
{
@ -416,7 +430,7 @@ public class SelectPurchaseEvaluationWinnerCommandHandler(
PhaseAtChange = entity.Phase,
UserId = currentUser.UserId,
UserName = currentUser.FullName ?? currentUser.Email,
Summary = "Chọn NCC trúng thầu",
Summary = request.SupplierId is null ? "Bỏ chọn NCC trúng thầu" : "Chọn NCC trúng thầu",
});
await db.SaveChangesAsync(ct);