[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
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:
@ -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 HĐ 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)),
|
||||
})
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -141,6 +141,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 = {
|
||||
|
||||
Reference in New Issue
Block a user