From fa6d88f2abc2a7a3cbd35db8c9a8c4cd2e6c52ce 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: Thu, 1 Oct 2026 16:45:10 +0500 Subject: [PATCH] =?UTF-8?q?feat(ct):=20=D0=BF=D0=BE=D0=B4=D1=81=D0=B2?= =?UTF-8?q?=D0=B5=D1=82=D0=BA=D0=B0=20=D0=BD=D0=B5=D1=81=D0=BE=D0=BE=D1=82?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D0=B8=D1=8F=20=D0=B5=D0=B4?= =?UTF-8?q?.=20=D0=B8=D0=B7=D0=BC.=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2?= =?UTF-8?q?=D1=89=D0=B8=D0=BA=D0=B0=20=D0=B8=20=D0=B7=D0=B0=D1=8F=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BD=D0=B0=20=D1=88=D0=B0=D0=B3=D0=B5=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Несоответствие единиц было видно только по незаметной «*» и только при включённом тумблере. Теперь «Ед. изм.» поставщика подсвечивается жёлтым, если отличается от заявки (подсказка «В заявке: кг, у поставщика: т»). С включённым тумблером единица, кол-во и цены подсвечиваются голубым, если пересчитаны в ед. заявки, и красным, если пересчитать нельзя (шт ↔ кг). Статус — чистая функция getUnitStatus; у тумблера — легенда цветов вместо «*». --- .../Step3ComparisonTable/ExtraPositionRow.tsx | 11 ++++--- .../Step3ComparisonTable/MatrixRowView.tsx | 21 ++++++++----- .../Step3ComparisonTable.module.scss | 29 +++++++++++++++++ .../Step3ComparisonTable.tsx | 4 ++- .../Step3ComparisonTable/unitStatusStyles.ts | 15 +++++++++ .../components/utils/unitConversion.test.ts | 29 ++++++++++++++++- .../ct/components/utils/unitConversion.ts | 31 +++++++++++++++++++ 7 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/unitStatusStyles.ts diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx index 19e4920..c8105b9 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx @@ -3,7 +3,8 @@ import { IconButton, TextInput } from '@tnsu/ui-kit'; import { CurrencyRate } from '../types/domain'; import { PositionRow } from './matrixTypes'; import { Step3Matrix } from './useStep3Matrix'; -import { convertQuantity, unitsEquivalent } from '../utils/unitConversion'; +import { getUnitStatus } from '../utils/unitConversion'; +import { unitStatusClass } from './unitStatusStyles'; import { formatKzt, formatNum, formatQty, round2Str, buildOfferKey } from './matrixHelpers'; import styles from './Step3ComparisonTable.module.scss'; @@ -67,8 +68,8 @@ export function ExtraPositionRow({ pos, posIndex, regularPositionsCount, matrix, // Перевод в ед. изм. заявки (тумблер) — как в обычных позициях. const reqUnit = pos.unit; const supUnit = item?.unit || pos.unit; - const tRatio = translateUnits && !unitsEquivalent(supUnit, reqUnit) ? convertQuantity(1, supUnit, reqUnit) : null; - const cannotTranslate = translateUnits && !unitsEquivalent(supUnit, reqUnit) && tRatio === null; + const unitStatus = getUnitStatus(supUnit, reqUnit, translateUnits); + const tRatio = unitStatus.ratio ?? null; const dispUnit = tRatio !== null ? reqUnit : (item?.unit || pos.unit); const dispQty = (tRatio !== null && qty !== undefined) ? qty * tRatio : qty; const dispPrice = (tRatio !== null && price !== undefined) ? price / tRatio : price; @@ -91,7 +92,7 @@ export function ExtraPositionRow({ pos, posIndex, regularPositionsCount, matrix, {item?.supplierProductName || pos.materialName} )} - + {showInput ? (
) : ( - {(dispUnit || '—') + (cannotTranslate ? ' *' : '')} + {dispUnit || '—'} )} diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/MatrixRowView.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/MatrixRowView.tsx index 69cf9bb..8759a65 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/MatrixRowView.tsx +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/MatrixRowView.tsx @@ -3,7 +3,8 @@ import { TextInput, Icon } from '@tnsu/ui-kit'; import { CurrencyRate } from '../types/domain'; import { PositionRow } from './matrixTypes'; import { Step3Matrix } from './useStep3Matrix'; -import { convertQuantity, unitsEquivalent } from '../utils/unitConversion'; +import { getUnitStatus } from '../utils/unitConversion'; +import { unitStatusClass } from './unitStatusStyles'; import { getSubCols, formatKzt, formatNum, formatQty, round2Str, buildOfferKey } from './matrixHelpers'; import styles from './Step3ComparisonTable.module.scss'; @@ -85,8 +86,12 @@ export function MatrixRowView({ pos, posIndex, matrix, currencyRates, isRestrict // null — единицы совпадают или пересчёт невозможен. const reqUnit = pos.unit; const supUnit = item.unit || pos.unit; - const tRatio = translateUnits && !unitsEquivalent(supUnit, reqUnit) ? convertQuantity(1, supUnit, reqUnit) : null; - const cannotTranslate = translateUnits && !unitsEquivalent(supUnit, reqUnit) && tRatio === null; + const unitStatus = getUnitStatus(supUnit, reqUnit, translateUnits); + const tRatio = unitStatus.ratio ?? null; + // Подсветка: «Ед. изм.» — при любом несоответствии заявке; кол-во и цены — когда тумблер включён + // (они показаны пересчитанными или их нельзя сравнивать напрямую). + const unitCls = unitStatusClass(unitStatus.status); + const valueCls = translateUnits ? unitCls : ''; const dispUnit = tRatio !== null ? reqUnit : (item.unit || pos.unit); const dispQty = (tRatio !== null && qty !== undefined) ? qty * tRatio : qty; const dispPrice = (tRatio !== null && price !== undefined) ? price / tRatio : price; @@ -123,7 +128,7 @@ export function MatrixRowView({ pos, posIndex, matrix, currencyRates, isRestrict )} - + {showInput ? (
) : ( - {(dispUnit || '—') + (cannotTranslate ? ' *' : '')} + {dispUnit || '—'} )} - + {showInput ? (
{!isRestrictedView && isForeign && ( <> - + {showInput ? (
)} - {!isRestrictedView && + {!isRestrictedView && {showInput ? (
= ({ {translateUnits && ( - кол-во и цена показаны в ед. изм. заявки; «*» — пересчёт невозможен (ред. недоступно) + кол-во и цена — в ед. изм. заявки (ред. недоступно):{' '} + пересчитано,{' '} + пересчёт невозможен )}
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/unitStatusStyles.ts b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/unitStatusStyles.ts new file mode 100644 index 0000000..d00573f --- /dev/null +++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/unitStatusStyles.ts @@ -0,0 +1,15 @@ +import { UnitStatusKind } from '../utils/unitConversion'; +import styles from './Step3ComparisonTable.module.scss'; + +// Подсветка ячеек по статусу ед. изм. поставщика (см. getUnitStatus): жёлтым — единица отличается +// от заявки, голубым — пересчитано в ед. заявки, красным — пересчитать нельзя. +const UNIT_STATUS_CLASS: Record = { + same: '', + mismatch: styles.unitMismatch, + converted: styles.unitConverted, + impossible: styles.unitImpossible, +}; + +export function unitStatusClass(status: UnitStatusKind): string { + return UNIT_STATUS_CLASS[status]; +} diff --git a/src/webparts/ctFormsPortal/forms/ct/components/utils/unitConversion.test.ts b/src/webparts/ctFormsPortal/forms/ct/components/utils/unitConversion.test.ts index 241e647..615559b 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/utils/unitConversion.test.ts +++ b/src/webparts/ctFormsPortal/forms/ct/components/utils/unitConversion.test.ts @@ -1,4 +1,4 @@ -import { convertQuantity, normalizeUnit, unitsEquivalent } from './unitConversion'; +import { convertQuantity, getUnitStatus, normalizeUnit, unitsEquivalent } from './unitConversion'; describe('unitsEquivalent — синонимы одной единицы', () => { // СТ №12: в заявке «Штука», у поставщика «шт.» — тумблер помечал «шт. *» (пересчёт невозможен). @@ -62,3 +62,30 @@ describe('normalizeUnit', () => { expect(normalizeUnit(' шт. ')).toBe(normalizeUnit('Штука')); }); }); + +describe('getUnitStatus — подсветка несоответствия ед. изм. (п.6)', () => { + it('одинаковые единицы — ничего не подсвечиваем', () => { + expect(getUnitStatus('шт.', 'Штука', false)).toEqual({ status: 'same' }); + expect(getUnitStatus('шт.', 'Штука', true)).toEqual({ status: 'same' }); + }); + + // Тумблер выключен: несовпадение видно сразу, а не только по незаметной «*» при включённом тумблере. + it('тумблер выключен — разные единицы: несоответствие с подсказкой', () => { + expect(getUnitStatus('т', 'кг', false)).toEqual({ status: 'mismatch', hint: 'В заявке: кг, у поставщика: т' }); + }); + + it('тумблер включён — пересчитали: коэффициент и подсказка', () => { + expect(getUnitStatus('т', 'кг', true)).toEqual({ status: 'converted', ratio: 1000, hint: 'Пересчитано из т в кг' }); + }); + + it('тумблер включён — пересчитать нельзя: подсказка сравнивать вручную', () => { + expect(getUnitStatus('шт', 'кг', true)).toEqual({ + status: 'impossible', + hint: 'Нельзя перевести шт → кг: сравнивайте вручную', + }); + }); + + it('пустая единица поставщика — не несоответствие', () => { + expect(getUnitStatus('', 'кг', false)).toEqual({ status: 'same' }); + }); +}); diff --git a/src/webparts/ctFormsPortal/forms/ct/components/utils/unitConversion.ts b/src/webparts/ctFormsPortal/forms/ct/components/utils/unitConversion.ts index 71e7201..ba66903 100644 --- a/src/webparts/ctFormsPortal/forms/ct/components/utils/unitConversion.ts +++ b/src/webparts/ctFormsPortal/forms/ct/components/utils/unitConversion.ts @@ -96,3 +96,34 @@ export function convertQuantity(qty: number, fromUnit: string | undefined, toUni if (!from || !to || from.dim !== to.dim) return null; return qty * (from.toBase / to.toBase); } + +// Статус ед. изм. поставщика относительно заявки — для подсветки ячеек на шаге 3: +// same — единицы совпадают (или у поставщика не указана) — без подсветки; +// mismatch — тумблер выключен, единицы разные — показать, что сравнивать надо внимательно; +// converted — тумблер включён, пересчитали в ед. заявки (ratio — коэффициент кол-ва); +// impossible — тумблер включён, пересчитать нельзя (шт ↔ кг, шт ↔ компл). +export type UnitStatusKind = 'same' | 'mismatch' | 'converted' | 'impossible'; + +export interface UnitStatus { + status: UnitStatusKind; + ratio?: number; + hint?: string; +} + +export function getUnitStatus( + supplierUnit: string | undefined, + requestUnit: string | undefined, + translateUnits: boolean, +): UnitStatus { + const supplier = (supplierUnit ?? '').trim(); + const request = (requestUnit ?? '').trim(); + if (!supplier || !request || unitsEquivalent(supplier, request)) return { status: 'same' }; + if (!translateUnits) { + return { status: 'mismatch', hint: `В заявке: ${request}, у поставщика: ${supplier}` }; + } + const ratio = convertQuantity(1, supplier, request); + if (ratio === null) { + return { status: 'impossible', hint: `Нельзя перевести ${supplier} → ${request}: сравнивайте вручную` }; + } + return { status: 'converted', ratio, hint: `Пересчитано из ${supplier} в ${request}` }; +}