fix(ct): поставщики без КП с шага 2 не попадают в таблицу сравнения

Они выводились на шаге 3 колонкой с нулевыми ценами, хотя сравнивать у них
нечего. Остаются в списке шага 2 и в «Не получены КП»; ручной ввод цен
сохранён за поставщиками, добавленными на самом шаге 3.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-09-23 15:56:14 +05:00
parent e4b6582e3c
commit 64973a2232
4 changed files with 10 additions and 33 deletions
@@ -80,7 +80,6 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
</div> </div>
<div className={styles.supplierMetaRow}> <div className={styles.supplierMetaRow}>
<AdataIndicator supplier={s} state={adata.adataMap[s.id]} onCheck={() => void adata.checkSupplier(s.id)} /> <AdataIndicator supplier={s} state={adata.adataMap[s.id]} onCheck={() => void adata.checkSupplier(s.id)} />
{s.isNoOffer && <span className={styles.noOfferTag}>Без КП</span>}
{currency !== 'KZT' && <span className={styles.currencyTag}>{currency}</span>} {currency !== 'KZT' && <span className={styles.currencyTag}>{currency}</span>}
</div> </div>
<div className={styles.supplierControlsRow}> <div className={styles.supplierControlsRow}>
@@ -74,7 +74,7 @@ export function MatrixRowView({ pos, posIndex, matrix, currencyRates, isRestrict
} }
const rate = isForeign ? (currencyRates.find(r => r.code === currency)?.rateToKzt ?? 1) : 1; const rate = isForeign ? (currencyRates.find(r => r.code === currency)?.rateToKzt ?? 1) : 1;
const qty = item.quantity ?? ((s.isNoOffer || s.isExtraSupplier) && pos.quantity ? pos.quantity : undefined); const qty = item.quantity ?? (s.isExtraSupplier && pos.quantity ? pos.quantity : undefined);
const price = item.pricePerUnit; const price = item.pricePerUnit;
const totalInCurrency = (price ?? 0) * (qty ?? 0); const totalInCurrency = (price ?? 0) * (qty ?? 0);
const priceKzt = (price ?? 0) * rate; const priceKzt = (price ?? 0) * rate;
@@ -213,16 +213,6 @@
white-space: nowrap; white-space: nowrap;
} }
.noOfferTag {
font-size: 11px;
background: $danger-bg;
color: $danger;
padding: 1px 7px;
border-radius: 10px;
white-space: nowrap;
font-weight: 600;
}
.deleteSupplierBtn { .deleteSupplierBtn {
width: 20px; width: 20px;
height: 20px; height: 20px;
@@ -290,9 +290,13 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
}, []); }, []);
const suppliersWithOffer = useMemo(() => [ const suppliersWithOffer = useMemo(() => [
...suppliers.map(s => ({ ...s, isExtraSupplier: false as const, isNoOffer: !s.hasOffer })), // Поставщики без КП с Шага 2 (hasOffer:false) в таблицу сравнения не попадают — сравнивать
// у них нечего. Ручной ввод цен остаётся за поставщиками, добавленными на самом Шаге 3 (extras).
...suppliers
.filter(s => s.hasOffer !== false)
.map(s => ({ ...s, isExtraSupplier: false as const })),
...extraSuppliers.map(es => ({ ...extraSuppliers.map(es => ({
id: es.id, name: es.name, hasOffer: true as const, isExtraSupplier: true as const, isNoOffer: false as const, id: es.id, name: es.name, hasOffer: true as const, isExtraSupplier: true as const,
// spId/bin известны сразу для поставщиков, добавленных из «База данных поставщиков» // spId/bin известны сразу для поставщиков, добавленных из «База данных поставщиков»
// (см. handleAddSupplierFromDatabase) — без этого useAdataCheck не увидит их БИН. // (см. handleAddSupplierFromDatabase) — без этого useAdataCheck не увидит их БИН.
spId: es.spId, bin: es.bin, spId: es.spId, bin: es.bin,
@@ -343,10 +347,10 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
const rate = currency !== 'KZT' const rate = currency !== 'KZT'
? (currencyRates.find(r => r.code === currency)?.rateToKzt ?? 1) ? (currencyRates.find(r => r.code === currency)?.rateToKzt ?? 1)
: 1; : 1;
// For noOffer/extra suppliers quantity may be undefined in edits (user only entered price). // For extra suppliers quantity may be undefined in edits (user only entered price).
// Fall back to the position's own quantity so totalWithExtraKzt stays non-zero and the // Fall back to the position's own quantity so totalWithExtraKzt stays non-zero and the
// offer is included in filteredComputed → persisted in offersForSession → restored on remount. // offer is included in filteredComputed → persisted in offersForSession → restored on remount.
const posQtyFallback = (s.isNoOffer || s.isExtraSupplier) ? (pos.quantity || undefined) : undefined; const posQtyFallback = s.isExtraSupplier ? (pos.quantity || undefined) : undefined;
items.forEach(item => result.push(computeOffer( items.forEach(item => result.push(computeOffer(
{ {
...item, ...item,
@@ -694,20 +698,6 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
extraOfferIds[es.id] = offerId; extraOfferIds[es.id] = offerId;
} }
const noOfferSupSpIds: Record<string, number> = {};
const noOfferOfferIds: Record<string, number> = {};
for (const s of suppliers.filter(sup => !sup.hasOffer)) {
const hasData = computedOffers.some(o => o.supplierId === s.id && o.totalWithExtraKzt > 0);
if (!hasData) continue;
if (s.spId) {
noOfferSupSpIds[s.id] = s.spId;
continue;
}
const { spId, offerId } = await service.saveManualSupplierWithKp(s.name);
noOfferSupSpIds[s.id] = spId;
noOfferOfferIds[s.id] = offerId;
}
const extraPosSpIds: Record<string, number> = {}; const extraPosSpIds: Record<string, number> = {};
for (const ep of extraPositions) { for (const ep of extraPositions) {
if (ep.spId) { if (ep.spId) {
@@ -735,7 +725,7 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
const offersToSave = computedOffers const offersToSave = computedOffers
.filter(o => o.totalWithExtraKzt > 0) .filter(o => o.totalWithExtraKzt > 0)
.map(o => { .map(o => {
const supSpId = extraSupSpIds[o.supplierId] ?? noOfferSupSpIds[o.supplierId]; const supSpId = extraSupSpIds[o.supplierId];
const posSpId = extraPosSpIds[o.positionId]; const posSpId = extraPosSpIds[o.positionId];
const existingSup = suppliers.find(s => s.id === o.supplierId); const existingSup = suppliers.find(s => s.id === o.supplierId);
const existingPos = selectedPositions.find(p => p.id === o.positionId); const existingPos = selectedPositions.find(p => p.id === o.positionId);
@@ -761,7 +751,6 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
: (existingPos?.spId ? String(existingPos.spId) : o.positionId), : (existingPos?.spId ? String(existingPos.spId) : o.positionId),
offerId: originalOffer?.offerId offerId: originalOffer?.offerId
?? (extraOfferIds[o.supplierId] !== undefined ? String(extraOfferIds[o.supplierId]) : undefined) ?? (extraOfferIds[o.supplierId] !== undefined ? String(extraOfferIds[o.supplierId]) : undefined)
?? (noOfferOfferIds[o.supplierId] !== undefined ? String(noOfferOfferIds[o.supplierId]) : undefined)
?? (kpFileOfferId !== undefined ? String(kpFileOfferId) : undefined), ?? (kpFileOfferId !== undefined ? String(kpFileOfferId) : undefined),
...(kmVal !== undefined ? { deliveryDistanceKm: kmVal } : {}), ...(kmVal !== undefined ? { deliveryDistanceKm: kmVal } : {}),
}; };
@@ -784,7 +773,6 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
...suppliers.map(s => ({ ...suppliers.map(s => ({
...s, ...s,
status: supplierStatuses[s.id] ?? s.status, status: supplierStatuses[s.id] ?? s.status,
...(noOfferSupSpIds[s.id] !== undefined ? { spId: noOfferSupSpIds[s.id] } : {}),
})), })),
// Move extras into suppliers (with React ID kept as `id` — required so formData.offers // Move extras into suppliers (with React ID kept as `id` — required so formData.offers
// entries with React IDs still match sup.id on remount). Skip extras that are already in // entries with React IDs still match sup.id on remount). Skip extras that are already in