[CLAUDE] PurchaseEvaluation: D2 multi-winner outward — create-contract 1→N per winner + list-card winner names
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m10s

Completes the Mig 58 multi-winner (>=2 cung trung) wiring — the audit's joint-winner
dead-ends, CEO-approved scope ("1 HD per winner"):
- create-contract 1->N: one draft HD per IsWinner supplier (was 409 dead-end for >=2).
  Command -> List<Guid>. ContractCodeGenerator self-commits its sequence per call, so
  gen all N codes first (contracts built local) then atomic add + single final
  SaveChanges (no partial create on failure). GiaTri: single = details ThanhTienNganSach
  sum (legacy preserved); >=2 = per-winner quote total (DangSoanThao draft, editable —
  anh Kiet UAT the basis). pe.ContractId = first (idempotency). Controller -> {contractIds}.
- list-card winner names: PurchaseEvaluationListItemDto +WinnerSupplierNames (EF
  collection subquery over IsWinner suppliers; translates + runs on SQLite — test-verified).
  FE PeListPanel shows joined names for >=2 (was blank). Dialog -> contractIds[] + "Da tao N HD".

Tests 413 -> 419 (+6: multi-contract 5 [per-winner GiaTri 140/260 vs single legacy 800,
idempotency, 0-winner, not-DaDuyet] + EF-subquery translation smoke). Full suite GREEN.
FE 2-app SHA-identical. reviewer PASS (codegen-sequencing, positional-record arg-order,
no-partial-create all independently verified).

UAT (anh Kiet): open a >=2-winner phieu -> "Tao HD" creates N draft contracts (1/winner);
confirm per-winner contract GiaTri (quote total) is the wanted basis.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-25 10:30:14 +07:00
parent 73cce1f027
commit 1aa3bdb591
12 changed files with 584 additions and 70 deletions

View File

@ -2143,10 +2143,12 @@ function CreateContractDialog({ evaluation, onClose }: { evaluation: PeDetailBun
})
const mut = useMutation({
mutationFn: async () =>
api.post<{ contractId: string }>(`/purchase-evaluations/${evaluation.id}/create-contract`, form),
// [Mig 58 — anh Kiệt FDC] Multi-winner: BE tạo 1 HĐ / mỗi đơn vị trúng (IsWinner)
// → trả LIST contractIds. ContractType người dùng chọn áp cho mọi HĐ.
api.post<{ contractIds: string[] }>(`/purchase-evaluations/${evaluation.id}/create-contract`, form),
onSuccess: res => {
toast.success('Đã tạo từ phiếu.')
navigate(`/contracts/${res.data.contractId}`)
toast.success(`Đã tạo ${res.data.contractIds.length} hợp đồng từ phiếu.`)
navigate(`/contracts/${res.data.contractIds[0]}`)
},
onError: e => toast.error(getErrorMessage(e)),
})

View File

@ -165,9 +165,16 @@ export function PeListPanel({
<span>·</span>
<span className="truncate">{p.projectName}</span>
</div>
{p.selectedSupplierName && (
{/* [Mig 58 — anh Kiệt FDC] Multi-winner: ưu tiên winnerSupplierNames
(BE null selectedSupplierName khi joint ≥2 winner), fallback
selectedSupplierName cho phiếu 1-winner cũ. */}
{(p.winnerSupplierNames?.length > 0
? p.winnerSupplierNames.join(', ')
: p.selectedSupplierName) && (
<div className="mt-0.5 truncate text-[11px] text-emerald-600">
{p.selectedSupplierName}
{p.winnerSupplierNames?.length > 0
? p.winnerSupplierNames.join(', ')
: p.selectedSupplierName}
</div>
)}
</div>

View File

@ -137,6 +137,10 @@ export type PeListItem = {
// S69 — cờ gấp per-vai (PRO ĐỎ / CCM XANH). FE render chip nhỏ trên card list.
isUrgentByPro: boolean
isUrgentByCcm: boolean
// [Mig 58 — anh Kiệt FDC] Multi-winner: tên MỌI đơn vị trúng thầu (IsWinner). BE
// null selectedSupplierName khi joint ≥2 winner → list card ưu tiên field này,
// fallback selectedSupplierName cho phiếu 1-winner cũ.
winnerSupplierNames: string[]
}
export type PeSupplier = {