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}` }; +}