[CLAUDE] PurchaseEvaluation: luy ke TAM TINH vs CHINH XAC — ComputePendingAsync + 5-field DTO + FE 2-app sub-dong amber/banner man duyet + D4 audit-note (UAT anh Kiet 15-07, 0 mig, test 520→528)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m27s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m27s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@ -318,6 +318,11 @@ public record PurchaseEvaluationDetailBundleDto(
|
||||
string? FinalizeLevelName,
|
||||
PurchaseEvaluationWorkflowSummaryDto Workflow);
|
||||
|
||||
// [S134 pe-luyke-tam-tinh] Phiếu TRƯỚC cùng cặp (ProjectId, WorkItemId) CHƯA terminal —
|
||||
// render list "cần lưu ý" ở block ngân sách. MaPhieu nullable (entity string?, data cổ có null).
|
||||
// Phase = int (PurchaseEvaluationPhase underlying) để FE map label VN qua PurchaseEvaluationPhaseLabel.
|
||||
public record PePendingPriorPeDto(Guid Id, string? MaPhieu, int Phase);
|
||||
|
||||
// [S61 Mig 50] Bảng "TỔNG HỢP NGÂN SÁCH TRÌNH KÝ" theo Excel anh Kiệt — BE compute
|
||||
// kèm PE detail GET. Record PeWorkItemBudgets dùng chung mọi phiếu cùng cặp
|
||||
// (ProjectId, WorkItemId). BudgetId null = phiếu cũ chưa gắn Hạng mục.
|
||||
@ -350,4 +355,12 @@ public record PeBudgetSummaryDto(
|
||||
// [S133] true = phiếu DaDuyet serve từ snapshot ngân sách (bất biến, đọc cột ApprovedBudget*
|
||||
// thay record PeWorkItemBudgets live); false = phiếu chưa duyệt đọc LIVE. FE badge "đã chốt" +
|
||||
// ẩn nút sửa. Append CUỐI record (default false) → construction site cũ không gãy.
|
||||
bool BudgetFrozen = false);
|
||||
bool BudgetFrozen = false,
|
||||
// [S134 pe-luyke-tam-tinh] Lũy kế TẠM TÍNH — phiếu TRƯỚC cùng cặp đã trình/chọn NHƯNG CHƯA duyệt
|
||||
// xong (BỔ SUNG số CHÍNH XÁC PreviousSubmitted*/PreviousSelected*, KHÔNG thay). Append CUỐI record
|
||||
// (default 0/null) → construction site cũ + nhánh frozen (DaDuyet bất biến) không gãy.
|
||||
decimal PendingSubmittedTotal = 0,
|
||||
int PendingSubmittedCount = 0,
|
||||
decimal PendingSelectedTotal = 0,
|
||||
int PendingSelectedCount = 0,
|
||||
IReadOnlyList<PePendingPriorPeDto>? PendingPriorPes = null);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Application.PurchaseEvaluations.Dtos;
|
||||
using SolutionErp.Domain.PurchaseEvaluations;
|
||||
|
||||
namespace SolutionErp.Application.PurchaseEvaluations;
|
||||
@ -24,6 +25,16 @@ public readonly record struct PeBudgetAccumulation(
|
||||
int PrevSelectedCount,
|
||||
decimal CurrentProposalTotal);
|
||||
|
||||
// [S134 pe-luyke-tam-tinh] Lũy kế TẠM TÍNH — phiếu TRƯỚC cùng cặp (ProjectId, WorkItemId)
|
||||
// đã trình/đã chọn NHƯNG CHƯA duyệt xong. Bổ sung display "tạm tính" — KHÔNG đổi số CHÍNH XÁC
|
||||
// (PeBudgetAccumulation qua ComputeAsync bất động).
|
||||
public readonly record struct PePendingAccumulation(
|
||||
decimal PendingSubmittedTotal,
|
||||
int PendingSubmittedCount,
|
||||
decimal PendingSelectedTotal,
|
||||
int PendingSelectedCount,
|
||||
IReadOnlyList<PePendingPriorPeDto> PriorPes);
|
||||
|
||||
public static class PeBudgetAccumulator
|
||||
{
|
||||
public static async Task<PeBudgetAccumulation> ComputeAsync(
|
||||
@ -69,4 +80,61 @@ public static class PeBudgetAccumulator
|
||||
prevSelectedTotal, prevSelectedCount,
|
||||
currentProposalTotal);
|
||||
}
|
||||
|
||||
// [S134] Lũy kế TẠM TÍNH — CHỈ ĐỌC, KHÔNG mutate. peers = window Y HỆT ComputeAsync (:38-40).
|
||||
// PendingSubmitted* = peers Phase ∉ {DangSoanThao, DaDuyet, ChoDuyet, TuChoi} = {TraLai + legacy 2-6}
|
||||
// → Count + SUM(BudgetPeriodAmount ?? 0). ChoDuyet/DaDuyet ĐÃ nằm trong
|
||||
// ComputeAsync PrevSubmitted (chính xác) → loại-trừ tránh double-count.
|
||||
// PendingSelected* = peers Phase ∉ {DangSoanThao, DaDuyet, TuChoi} AND ≥1 Supplier IsWinner
|
||||
// (GỒM ChoDuyet-có-winner = gap chính đề bài) → Count + SUM(ThanhTien IsSelected).
|
||||
// Mirror shape ComputeAsync :50-60 (join Suppliers→Quotes).
|
||||
// PriorPes = peers CHƯA terminal (Phase ∉ {DaDuyet, TuChoi}) → list render, order CreatedAt.
|
||||
public static async Task<PePendingAccumulation> ComputePendingAsync(
|
||||
IApplicationDbContext db,
|
||||
Guid projectId,
|
||||
Guid workItemId,
|
||||
Guid peId,
|
||||
DateTime peCreatedAt,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var peers = db.PurchaseEvaluations.AsNoTracking()
|
||||
.Where(p => p.ProjectId == projectId && p.WorkItemId == workItemId
|
||||
&& p.Id != peId && p.CreatedAt < peCreatedAt);
|
||||
|
||||
var pendingSubmitted = await peers
|
||||
.Where(p => p.Phase != PurchaseEvaluationPhase.DangSoanThao
|
||||
&& p.Phase != PurchaseEvaluationPhase.DaDuyet
|
||||
&& p.Phase != PurchaseEvaluationPhase.ChoDuyet
|
||||
&& p.Phase != PurchaseEvaluationPhase.TuChoi)
|
||||
.Select(p => p.BudgetPeriodAmount)
|
||||
.ToListAsync(ct);
|
||||
var pendingSubmittedCount = pendingSubmitted.Count;
|
||||
var pendingSubmittedTotal = pendingSubmitted.Sum(v => v ?? 0m);
|
||||
|
||||
var pendingSelectedPeers = peers.Where(p => p.Phase != PurchaseEvaluationPhase.DangSoanThao
|
||||
&& p.Phase != PurchaseEvaluationPhase.DaDuyet
|
||||
&& p.Phase != PurchaseEvaluationPhase.TuChoi
|
||||
&& p.Suppliers.Any(s => s.IsWinner));
|
||||
var pendingSelectedCount = await pendingSelectedPeers.CountAsync(ct);
|
||||
var pendingSelectedTotal = await (
|
||||
from p in pendingSelectedPeers
|
||||
join s in db.PurchaseEvaluationSuppliers.AsNoTracking()
|
||||
on p.Id equals s.PurchaseEvaluationId
|
||||
join q in db.PurchaseEvaluationQuotes.AsNoTracking()
|
||||
on s.Id equals q.PurchaseEvaluationSupplierId
|
||||
where q.IsSelected
|
||||
select (decimal?)q.ThanhTien).SumAsync(ct) ?? 0m;
|
||||
|
||||
var priorPes = await peers
|
||||
.Where(p => p.Phase != PurchaseEvaluationPhase.DaDuyet
|
||||
&& p.Phase != PurchaseEvaluationPhase.TuChoi)
|
||||
.OrderBy(p => p.CreatedAt)
|
||||
.Select(p => new PePendingPriorPeDto(p.Id, p.MaPhieu, (int)p.Phase))
|
||||
.ToListAsync(ct);
|
||||
|
||||
return new PePendingAccumulation(
|
||||
pendingSubmittedTotal, pendingSubmittedCount,
|
||||
pendingSelectedTotal, pendingSelectedCount,
|
||||
priorPes);
|
||||
}
|
||||
}
|
||||
|
||||
@ -952,7 +952,11 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
e.ApprovedBudgetPrevSelectedTotal ?? 0m, e.ApprovedBudgetPrevSelectedCount ?? 0,
|
||||
currentProposalTotal,
|
||||
e.ApprovedBudgetProInitialAmount, e.ApprovedBudgetProAdjustmentAmount,
|
||||
BudgetFrozen: true);
|
||||
BudgetFrozen: true,
|
||||
// [S134] phiếu DaDuyet bất biến — KHÔNG lũy kế tạm-tính (giữ defaults 0/null).
|
||||
PendingSubmittedTotal: 0m, PendingSubmittedCount: 0,
|
||||
PendingSelectedTotal: 0m, PendingSelectedCount: 0,
|
||||
PendingPriorPes: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -962,6 +966,12 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
var acc = await PeBudgetAccumulator.ComputeAsync(
|
||||
db, e.ProjectId, wiKey, e.Id, e.CreatedAt, curSupplierRowIds, ct);
|
||||
|
||||
// [S134] Lũy kế TẠM TÍNH — phiếu TRƯỚC cùng cặp đã trình/chọn NHƯNG CHƯA duyệt xong
|
||||
// (row 5/6 "tạm tính" + list phiếu cần lưu ý). CHỈ nhánh live (phiếu chưa chốt); nhánh
|
||||
// frozen giữ defaults 0/null. KHÔNG đổi số CHÍNH XÁC của acc (ComputeAsync bất động).
|
||||
var pending = await PeBudgetAccumulator.ComputePendingAsync(
|
||||
db, e.ProjectId, wiKey, e.Id, e.CreatedAt, ct);
|
||||
|
||||
// [S76] Full mỗi cột = Initial + Adjustment (cột đó). Authoritative full cho Block B =
|
||||
// CCM nếu đã nhập, else PRO (FullIsEstimate=true → FE badge "ngân sách PRO"). PRO full =
|
||||
// ProInitial + ProAdjust (migrate từ ProEstimate cũ qua Mig 56). Cả 2 trống → full 0.
|
||||
@ -981,7 +991,13 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
acc.PrevSelectedTotal, acc.PrevSelectedCount,
|
||||
acc.CurrentProposalTotal,
|
||||
pairRec?.ProInitialAmount, pairRec?.ProAdjustmentAmount,
|
||||
BudgetFrozen: false);
|
||||
BudgetFrozen: false,
|
||||
// [S134] lũy kế tạm-tính chỉ nhánh live.
|
||||
PendingSubmittedTotal: pending.PendingSubmittedTotal,
|
||||
PendingSubmittedCount: pending.PendingSubmittedCount,
|
||||
PendingSelectedTotal: pending.PendingSelectedTotal,
|
||||
PendingSelectedCount: pending.PendingSelectedCount,
|
||||
PendingPriorPes: pending.PriorPes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user