From e4b6582e3c437d7288db34c00a5607da2d7b72ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D1=83=D1=80=20=D0=93=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D1=86=D0=BA=D0=B8=D0=B9?= Date: Wed, 23 Sep 2026 15:32:43 +0500 Subject: [PATCH] =?UTF-8?q?feat(ct):=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=D1=8E=D1=82=D1=8B=20=D1=83=20=D1=83=D0=B6?= =?UTF-8?q?=D0=B5=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B6=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D0=9A=D0=9F=20=D0=BD=D0=B0=20=D1=88=D0=B0=D0=B3?= =?UTF-8?q?=D0=B5=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit После возврата с шага 3 очередь загрузки пуста, и КП, по умолчанию сохранённое в тенге, нельзя было перевести в валюту без перезагрузки файла. Теперь валюта меняется в «Загруженные КП»: обновляются supplier.currency и пересчёт офферов в тенге; в режиме просмотра показывается код валюты. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Step2CommercialOffers.module.scss | 24 +++++++++++ .../Step2CommercialOffers.tsx | 23 +++++++++- .../Step2CommercialOffers/UploadedKpList.tsx | 43 +++++++++++++++---- .../Step2CommercialOffers/kpCurrency.ts | 38 ++++++++++++++++ 4 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/kpCurrency.ts diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.module.scss b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.module.scss index ec5459a..27a549b 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.module.scss +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.module.scss @@ -109,6 +109,30 @@ } } +// Размеры совпадают с селектом валюты в очереди загрузки (FileUploadArea), чтобы строки выглядели одинаково. +.kpCurrencySelect { + font-size: 11px; + padding: 2px 6px; + border: 1px solid $border; + border-radius: 3px; + background: $surface; + color: $text; + font-family: inherit; + cursor: pointer; + outline: none; + flex-shrink: 0; + height: 26px; + + &:focus { border-color: $accent; } +} + +.kpCurrencyCode { + flex-shrink: 0; + font-size: 12px; + font-weight: 600; + color: $muted; +} + .noOfferBadge { background: $danger-bg; color: $danger; diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx index aa706fd..8b849a2 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/Step2CommercialOffers.tsx @@ -11,6 +11,7 @@ import { useKpProcessing } from './useKpProcessing'; import { persistStep2 } from './step2Persist'; import { SuppliersWithoutKp } from './SuppliersWithoutKp'; import { UploadedKpList } from './UploadedKpList'; +import { applySupplierCurrency, getSupplierCurrency } from './kpCurrency'; import styles from './Step2CommercialOffers.module.scss'; interface Props { @@ -72,6 +73,23 @@ export const Step2CommercialOffers: React.FC = ({ onSave({ suppliers: [...withOffer, ...withoutOffer] }); }; + // Смена валюты уже загруженного КП (список «Загруженные КП»). Если файл ещё в очереди этой сессии, + // синхронизируем и её: иначе 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, + currencyCode, + currencyRates, + )); + kp.kpFiles + .filter(f => f.supplierName === uploaded.supplierName) + .forEach(f => kp.handleCurrencyChange(f.localId, currencyCode)); + }; + const handleSave = async (): Promise => { setIsSaving(true); setSaveError(undefined); @@ -133,8 +151,11 @@ export const Step2CommercialOffers: React.FC = ({ getSupplierCurrency(formData.suppliers ?? [], supplierName)} + onCurrencyChange={handleUploadedCurrencyChange} onRemove={(spId) => onSave({ uploadedKpFiles: formData.uploadedKpFiles.filter(u => u.spId !== spId) })} - disabled={isReadOnly} + 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 88fac4c..e4de8ba 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/UploadedKpList.tsx +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/UploadedKpList.tsx @@ -7,12 +7,24 @@ import styles from './Step2CommercialOffers.module.scss'; interface Props { files: CTFormData['uploadedKpFiles']; service: CtService; + currencyOptions: string[]; + getCurrency: (supplierName: string | undefined) => string; + onCurrencyChange: (spId: number, currencyCode: string) => void; onRemove: (spId: number) => void; disabled?: boolean; } -// Секция «Загруженные КП» (постоянный список из formData): ссылка на файл + удаление из списка. -export const UploadedKpList: React.FC = ({ files, service, onRemove, disabled }) => ( +// Секция «Загруженные КП» (постоянный список из formData): ссылка на файл, валюта КП и удаление. +// Валюту здесь можно поправить и после возврата с Шага 3 — очередь загрузки к этому моменту уже пуста. +export const UploadedKpList: React.FC = ({ + files, + service, + currencyOptions, + getCurrency, + onCurrencyChange, + onRemove, + disabled, +}) => (

Загруженные КП

    @@ -27,12 +39,27 @@ export const UploadedKpList: React.FC = ({ files, service, onRemove, disa > {f.name} - onRemove(f.spId)} - disabled={disabled} - /> + {disabled ? ( + {getCurrency(f.supplierName)} + ) : ( + <> + + onRemove(f.spId)} + /> + + )} ))}
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/kpCurrency.ts b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/kpCurrency.ts new file mode 100644 index 0000000..8ca42f6 --- /dev/null +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step2CommercialOffers/kpCurrency.ts @@ -0,0 +1,38 @@ +import { CurrencyRate, Supplier, SupplierOffer } from '../types/domain'; + +const BASE_CURRENCY = 'KZT'; + +// Валюта хранится на поставщике (Шаг 3 читает supplier.currency), а офферы несут цену в валюте КП +// и её пересчёт в тенге. При смене валюты уже загруженного КП обновляем и то и другое, чтобы +// таблица сравнения и сохранение офферов в SP (valutaId) видели одну и ту же валюту. +export function applySupplierCurrency( + suppliers: Supplier[], + offers: SupplierOffer[], + supplierName: string, + currencyCode: string, + currencyRates: CurrencyRate[], +): { suppliers: Supplier[]; offers: SupplierOffer[] } { + const supplierIds = new Set( + suppliers.filter(s => s.hasOffer && s.name === supplierName).map(s => s.id), + ); + const rate = currencyCode === BASE_CURRENCY + ? 1 + : (currencyRates.find(r => r.code === currencyCode)?.rateToKzt ?? 1); + + const nextSuppliers = suppliers.map(s => + supplierIds.has(s.id) ? { ...s, currency: currencyCode } : s, + ); + const nextOffers = offers.map(o => { + if (!supplierIds.has(o.supplierId)) return o; + const pricePerUnitKzt = o.pricePerUnit * rate; + const totalPriceKzt = pricePerUnitKzt * o.quantity; + return { ...o, currencyCode, pricePerUnitKzt, totalPriceKzt, totalWithExtraKzt: totalPriceKzt }; + }); + + return { suppliers: nextSuppliers, offers: nextOffers }; +} + +export function getSupplierCurrency(suppliers: Supplier[], supplierName: string | undefined): string { + const supplier = suppliers.find(s => s.hasOffer && s.name === supplierName); + return supplier?.currency ?? BASE_CURRENCY; +}