diff --git a/src/webparts/ctFormsPortal/forms/ct/components/CtCreateForm.tsx b/src/webparts/ctFormsPortal/forms/ct/components/CtCreateForm.tsx index d4da69c..1e87c39 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/CtCreateForm.tsx +++ b/src/webparts/ctFormsPortal/forms/ct/components/CtCreateForm.tsx @@ -8,6 +8,7 @@ import { Step1PositionSelection } from './Step1PositionSelection/Step1PositionSe import { Step2CommercialOffers } from './Step2CommercialOffers/Step2CommercialOffers'; import { Step3ComparisonTable } from './Step3ComparisonTable/Step3ComparisonTable'; import { Step4Approval } from './Step4Approval/Step4Approval'; +import { buildColumnsFromSpOffers, buildSupplierColumnId, findColumnForRecord } from './utils/supplierColumns'; import styles from './CtCreateForm.module.scss'; import '@tnsu/ui-kit/styles.css'; @@ -85,17 +86,23 @@ const CtCreateForm: React.FC = ({ context, userDisplayName, const rateById = new Map(loadedRates.map(r => [Number(r.id), r])); const spCurrencyRates = loadedRates; - // Build unique suppliers from offers (used only as fallback when session has none) - const supplierCurrencyMap = new Map(); - rawOffers.forEach(o => { - supplierCurrencyMap.set(o.supplierSpId, o.valutaSpId ? (rateById.get(o.valutaSpId)?.code ?? 'KZT') : 'KZT'); - }); + // Колонки из офферов SP — по одной на (поставщик, файл КП): у поставщика может быть несколько + // КП в разных валютах. Используются, только когда в сессии колонок нет. + const currencyCodeOf = (valutaSpId?: number): string => + valutaSpId ? (rateById.get(valutaSpId)?.code ?? 'KZT') : 'KZT'; + const spSuppliers = buildColumnsFromSpOffers(rawOffers.map(o => ({ + supplierSpId: o.supplierSpId, + supplierName: o.supplierName, + offerFileSpId: o.offerFileSpId, + currencyCode: currencyCodeOf(o.valutaSpId), + }))); const supplierMap = new Map(); rawOffers.forEach(o => { if (!supplierMap.has(o.supplierSpId)) supplierMap.set(o.supplierSpId, o.supplierName); }); - const spSuppliers = Array.from(supplierMap.entries()).map(([spId, name]) => ({ - id: String(spId), spId, name, hasOffer: true as const, - currency: supplierCurrencyMap.get(spId) ?? 'KZT', - })); + // Колонка оффера: из сессии, если она есть (в т.ч. старая колонка без файла КП), иначе — из SP. + const visibleColumns = formData.suppliers.length > 0 ? formData.suppliers : spSuppliers; + const columnIdOf = (supplierSpId: number, offerFileSpId?: number): string => + findColumnForRecord(visibleColumns, supplierSpId, offerFileSpId)?.id + ?? buildSupplierColumnId(supplierSpId, offerFileSpId); // Build step3DeliveryKm from SP offers. Step 3 reads km by React-id key // (`${pos.id}::${s.id}`), so map SP ids → React ids; for delivery the position is an @@ -106,26 +113,23 @@ const CtCreateForm: React.FC = ({ context, userDisplayName, [...formData.selectedPositions, ...(formData.step3ExtraPositions ?? [])].forEach(p => { if (p.spId !== undefined) posReactIdBySpId.set(p.spId, p.id); }); - const supReactIdBySpId = new Map(); - formData.suppliers.forEach(s => { - if (s.spId !== undefined) supReactIdBySpId.set(s.spId, s.id); - }); const step3DeliveryKm: Record = {}; rawOffers.forEach(o => { if (o.deliveryDistanceKm !== undefined) { const posKey = posReactIdBySpId.get(o.positionSpId) ?? String(o.positionSpId); - const supKey = supReactIdBySpId.get(o.supplierSpId) ?? String(o.supplierSpId); + const supKey = columnIdOf(o.supplierSpId, o.offerFileSpId); step3DeliveryKm[`${posKey}::${supKey}`] = o.deliveryDistanceKm; } }); - // Build offers in domain format, restoring offerId from session + // Build offers in domain format. Файл КП берём из самой записи (offerFileId) — карта из сессии + // знает один файл на поставщика и при нескольких КП отдала бы обеим колонкам один и тот же. const offers = rawOffers.map(o => { - const offerIdNum = offerIdBySupplierSpId.get(o.supplierSpId) ?? o.offerFileSpId; + const offerIdNum = o.offerFileSpId ?? offerIdBySupplierSpId.get(o.supplierSpId); const currencyCode = o.valutaSpId ? (rateById.get(o.valutaSpId)?.code ?? 'KZT') : 'KZT'; return { id: '', - supplierId: String(o.supplierSpId), + supplierId: columnIdOf(o.supplierSpId, o.offerFileSpId), positionId: String(o.positionSpId), itemIndex: o.itemIndex, unit: o.unit, diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx index 8b849a2..066e5d9 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx @@ -75,18 +75,19 @@ export const Step2CommercialOffers: React.FC = ({ // Смена валюты уже загруженного КП (список «Загруженные КП»). Если файл ещё в очереди этой сессии, // синхронизируем и её: иначе persistStep2 пересоберёт поставщика из kpFiles со старой валютой. + // Валюта — у конкретного файла: у поставщика может быть несколько КП в разных валютах. const handleUploadedCurrencyChange = (fileSpId: number, currencyCode: string): void => { const uploaded = formData.uploadedKpFiles.find(u => u.spId === fileSpId); if (!uploaded?.supplierName) return; onSave(applySupplierCurrency( formData.suppliers ?? [], formData.offers ?? [], - uploaded.supplierName, + uploaded, currencyCode, currencyRates, )); kp.kpFiles - .filter(f => f.supplierName === uploaded.supplierName) + .filter(f => f.spId === fileSpId) .forEach(f => kp.handleCurrencyChange(f.localId, currencyCode)); }; @@ -152,7 +153,7 @@ export const Step2CommercialOffers: React.FC = ({ files={formData.uploadedKpFiles} service={service} currencyOptions={currencyOptions} - getCurrency={supplierName => getSupplierCurrency(formData.suppliers ?? [], supplierName)} + getCurrency={file => getSupplierCurrency(formData.suppliers ?? [], file)} onCurrencyChange={handleUploadedCurrencyChange} onRemove={(spId) => onSave({ uploadedKpFiles: formData.uploadedKpFiles.filter(u => u.spId !== spId) })} disabled={isNotificationLink || isReadOnly} diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/UploadedKpList.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/UploadedKpList.tsx index e4de8ba..536bbb7 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/UploadedKpList.tsx +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/UploadedKpList.tsx @@ -2,13 +2,14 @@ import * as React from 'react'; import { IconButton } from '@tnsu/ui-kit'; import { CtService } from '../../services/CtService'; import { CTFormData } from '../hooks/useCtForm'; +import { KpFileRef } from './kpCurrency'; import styles from './Step2CommercialOffers.module.scss'; interface Props { files: CTFormData['uploadedKpFiles']; service: CtService; currencyOptions: string[]; - getCurrency: (supplierName: string | undefined) => string; + getCurrency: (file: KpFileRef) => string; onCurrencyChange: (spId: number, currencyCode: string) => void; onRemove: (spId: number) => void; disabled?: boolean; @@ -40,12 +41,12 @@ export const UploadedKpList: React.FC = ({ {f.name} {disabled ? ( - {getCurrency(f.supplierName)} + {getCurrency(f)} ) : ( <>