fix(ct): «Перевести в ед. изм. заявки» понимает синонимы и штучные единицы
Тумблер знал только массу, длину, площадь и объём в одном написании. «Штука» в заявке и «шт.» в КП считались разными незнакомыми единицами — тумблер помечал их «пересчёт невозможен», и казалось, что он ничего не делает. Теперь единица приводится к канонической через словарь синонимов (шт/Штука/ед, кг/Килограмм, л/Литр, кв.м./м², пог.м/м.п. и т.д.), добавлены штучные единицы (компл, упак, пара, рулон, лист, мешок — между собой не пересчитываются). Заодно «кв.м.» с точкой на конце снова распознаётся: точку срезали после поиска синонимов.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { convertQuantity, normalizeUnit, unitsEquivalent } from './unitConversion';
|
||||
|
||||
describe('unitsEquivalent — синонимы одной единицы', () => {
|
||||
// СТ №12: в заявке «Штука», у поставщика «шт.» — тумблер помечал «шт. *» (пересчёт невозможен).
|
||||
it('«Штука» = «шт.» = «шт» = «ед.»', () => {
|
||||
expect(unitsEquivalent('Штука', 'шт.')).toBe(true);
|
||||
expect(unitsEquivalent('шт', 'ед.')).toBe(true);
|
||||
expect(unitsEquivalent('Штук', 'ШТ')).toBe(true);
|
||||
});
|
||||
|
||||
it('полные и краткие названия: килограмм, литр, тонна, метр', () => {
|
||||
expect(unitsEquivalent('Килограмм', 'кг')).toBe(true);
|
||||
expect(unitsEquivalent('Литр', 'л.')).toBe(true);
|
||||
expect(unitsEquivalent('Тонна', 'т')).toBe(true);
|
||||
expect(unitsEquivalent('Метр', 'м.')).toBe(true);
|
||||
});
|
||||
|
||||
it('квадратные, кубические и погонные метры в разных написаниях', () => {
|
||||
expect(unitsEquivalent('кв.м.', 'м²')).toBe(true);
|
||||
expect(unitsEquivalent('куб.м.', 'м3')).toBe(true);
|
||||
expect(unitsEquivalent('пог.м', 'м.п.')).toBe(true);
|
||||
});
|
||||
|
||||
it('комплект, упаковка, пара — свои синонимы', () => {
|
||||
expect(unitsEquivalent('Комплект', 'компл.')).toBe(true);
|
||||
expect(unitsEquivalent('Упаковка', 'уп.')).toBe(true);
|
||||
expect(unitsEquivalent('пара', 'пар')).toBe(true);
|
||||
});
|
||||
|
||||
it('разные единицы не равны', () => {
|
||||
expect(unitsEquivalent('шт', 'компл')).toBe(false);
|
||||
expect(unitsEquivalent('кг', 'т')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertQuantity — пересчёт в ед. изм. заявки', () => {
|
||||
it('тонны в килограммы', () => {
|
||||
expect(convertQuantity(0.5, 'т', 'кг')).toBe(500);
|
||||
expect(convertQuantity(1, 'Тонна', 'Килограмм')).toBe(1000);
|
||||
});
|
||||
|
||||
it('миллиметры в метры, литры в кубометры', () => {
|
||||
expect(convertQuantity(1500, 'мм', 'м')).toBeCloseTo(1.5);
|
||||
expect(convertQuantity(1000, 'л', 'куб.м.')).toBeCloseTo(1);
|
||||
});
|
||||
|
||||
// Штуки в комплекты без кратности не перевести — честно говорим «невозможно».
|
||||
it('штучные единицы между собой и с массой не пересчитываются', () => {
|
||||
expect(convertQuantity(1, 'шт', 'компл')).toBeNull();
|
||||
expect(convertQuantity(1, 'шт', 'кг')).toBeNull();
|
||||
});
|
||||
|
||||
it('незнакомая единица — пересчёт невозможен', () => {
|
||||
expect(convertQuantity(1, 'бухта', 'м')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeUnit', () => {
|
||||
// Раньше точку на конце срезали ПОСЛЕ синонимов, и «кв.м.» не распознавалось.
|
||||
it('точки и пробелы не мешают распознать единицу', () => {
|
||||
expect(normalizeUnit('кв.м.')).toBe(normalizeUnit('м2'));
|
||||
expect(normalizeUnit(' шт. ')).toBe(normalizeUnit('Штука'));
|
||||
});
|
||||
});
|
||||
@@ -3,61 +3,84 @@
|
||||
// поставщика из ед. КП в ед. изм. позиции заявки.
|
||||
// Несовместимые размерности (напр. кг ↔ м³ — нужна плотность) не пересчитываются.
|
||||
|
||||
// Штучные единицы — каждая своя «размерность»: шт ↔ компл без кратности не перевести.
|
||||
type Dimension = 'mass' | 'length' | 'area' | 'volume' | 'piece' | 'set' | 'pack' | 'pair' | 'roll' | 'sheet' | 'bag';
|
||||
|
||||
interface UnitDef {
|
||||
dim: 'mass' | 'length' | 'area' | 'volume';
|
||||
dim: Dimension;
|
||||
toBase: number; // множитель к базовой единице размерности
|
||||
}
|
||||
|
||||
// Базовые единицы: масса — кг, длина — м, площадь — м², объём — л.
|
||||
// Канонические единицы. Базовые: масса — кг, длина — м, площадь — м², объём — л, штучные — сама единица.
|
||||
const UNIT_TABLE: Record<string, UnitDef> = {
|
||||
// масса (база — кг)
|
||||
'мг': { dim: 'mass', toBase: 0.000001 },
|
||||
'г': { dim: 'mass', toBase: 0.001 },
|
||||
'гр': { dim: 'mass', toBase: 0.001 },
|
||||
'грамм': { dim: 'mass', toBase: 0.001 },
|
||||
'кг': { dim: 'mass', toBase: 1 },
|
||||
'килограмм': { dim: 'mass', toBase: 1 },
|
||||
'ц': { dim: 'mass', toBase: 100 },
|
||||
'центнер': { dim: 'mass', toBase: 100 },
|
||||
'т': { dim: 'mass', toBase: 1000 },
|
||||
'тн': { dim: 'mass', toBase: 1000 },
|
||||
'тонна': { dim: 'mass', toBase: 1000 },
|
||||
'тонн': { dim: 'mass', toBase: 1000 },
|
||||
// длина (база — м)
|
||||
'мм': { dim: 'length', toBase: 0.001 },
|
||||
'см': { dim: 'length', toBase: 0.01 },
|
||||
'дм': { dim: 'length', toBase: 0.1 },
|
||||
'м': { dim: 'length', toBase: 1 },
|
||||
'метр': { dim: 'length', toBase: 1 },
|
||||
'пм': { dim: 'length', toBase: 1 },
|
||||
'км': { dim: 'length', toBase: 1000 },
|
||||
// площадь (база — м²)
|
||||
'мм2': { dim: 'area', toBase: 0.000001 },
|
||||
'см2': { dim: 'area', toBase: 0.0001 },
|
||||
'дм2': { dim: 'area', toBase: 0.01 },
|
||||
'м2': { dim: 'area', toBase: 1 },
|
||||
// объём (база — л)
|
||||
'мл': { dim: 'volume', toBase: 0.001 },
|
||||
'см3': { dim: 'volume', toBase: 0.001 },
|
||||
'л': { dim: 'volume', toBase: 1 },
|
||||
'дм3': { dim: 'volume', toBase: 1 },
|
||||
'м3': { dim: 'volume', toBase: 1000 },
|
||||
'шт': { dim: 'piece', toBase: 1 },
|
||||
'компл': { dim: 'set', toBase: 1 },
|
||||
'упак': { dim: 'pack', toBase: 1 },
|
||||
'пара': { dim: 'pair', toBase: 1 },
|
||||
'рулон': { dim: 'roll', toBase: 1 },
|
||||
'лист': { dim: 'sheet', toBase: 1 },
|
||||
'мешок': { dim: 'bag', toBase: 1 },
|
||||
};
|
||||
|
||||
// Приводим написание единицы к ключу таблицы: нижний регистр, без пробелов,
|
||||
// ³/² → 3/2, частые синонимы (куб.м → м3, кв.м → м2, пог.м → пм), без хвостовых точек.
|
||||
export function normalizeUnit(raw: string | undefined): string {
|
||||
let u = (raw ?? '').toLowerCase().trim();
|
||||
u = u.replace(/³/g, '3').replace(/²/g, '2');
|
||||
u = u.replace(/\s+/g, '');
|
||||
u = u.replace(/^куб\.?м3?$/, 'м3').replace(/^кубм$/, 'м3');
|
||||
u = u.replace(/^кв\.?м2?$/, 'м2').replace(/^квм$/, 'м2');
|
||||
u = u.replace(/^пог\.?м$/, 'пм');
|
||||
u = u.replace(/\.+$/, '');
|
||||
return u;
|
||||
// Синонимы → каноническая единица. Ключи — уже без пробелов, точек и дефисов, в нижнем регистре
|
||||
// (см. cleanUnit): «Штука», «шт.», «кв.м.», «м.п.», «к-т» и т.п. В заявках и КП пишут по-разному —
|
||||
// без словаря «Штука» и «шт.» считались разными единицами, и тумблер помечал их «пересчёт невозможен».
|
||||
const UNIT_ALIASES: Record<string, string> = {
|
||||
'миллиграмм': 'мг',
|
||||
'гр': 'г', 'грамм': 'г', 'граммов': 'г',
|
||||
'килограмм': 'кг', 'килограммов': 'кг', 'килограмма': 'кг',
|
||||
'центнер': 'ц',
|
||||
'тн': 'т', 'тонна': 'т', 'тонн': 'т', 'тонны': 'т',
|
||||
'миллиметр': 'мм', 'сантиметр': 'см', 'дециметр': 'дм', 'километр': 'км',
|
||||
'метр': 'м', 'метров': 'м', 'метра': 'м',
|
||||
'пм': 'м', 'мп': 'м', 'погм': 'м', 'метрпогонный': 'м', 'погонныйметр': 'м',
|
||||
'квм': 'м2', 'квм2': 'м2', 'квадратныйметр': 'м2', 'метрквадратный': 'м2',
|
||||
'кубм': 'м3', 'кубм3': 'м3', 'куб': 'м3', 'кубометр': 'м3', 'кубическийметр': 'м3', 'метркубический': 'м3',
|
||||
'см3': 'мл', 'дм3': 'л',
|
||||
'миллилитр': 'мл', 'литр': 'л', 'литров': 'л', 'литра': 'л',
|
||||
'штука': 'шт', 'штук': 'шт', 'штуки': 'шт', 'ед': 'шт', 'единица': 'шт', 'единиц': 'шт', 'pcs': 'шт',
|
||||
'комплект': 'компл', 'комплектов': 'компл', 'кмпл': 'компл', 'кт': 'компл',
|
||||
'упаковка': 'упак', 'упаковок': 'упак', 'уп': 'упак',
|
||||
'пар': 'пара', 'пары': 'пара',
|
||||
'рул': 'рулон', 'рулонов': 'рулон',
|
||||
'листов': 'лист', 'листа': 'лист',
|
||||
'меш': 'мешок', 'мешков': 'мешок',
|
||||
};
|
||||
|
||||
// Нижний регистр, ³/² → 3/2, без пробелов, точек и дефисов: «кв. м.» → «квм», «к-т» → «кт».
|
||||
function cleanUnit(raw: string | undefined): string {
|
||||
return (raw ?? '')
|
||||
.toLowerCase()
|
||||
.replace(/³/g, '3')
|
||||
.replace(/²/g, '2')
|
||||
.replace(/[\s.-]+/g, '');
|
||||
}
|
||||
|
||||
// true, если единицы по сути одинаковые (тн == т == Тонна).
|
||||
// Приводим написание единицы к канонической (ключ UNIT_TABLE). Незнакомую возвращаем очищенной —
|
||||
// так одинаково написанные незнакомые единицы всё равно считаются равными.
|
||||
export function normalizeUnit(raw: string | undefined): string {
|
||||
const cleaned = cleanUnit(raw);
|
||||
return UNIT_ALIASES[cleaned] ?? cleaned;
|
||||
}
|
||||
|
||||
// true, если единицы по сути одинаковые (тн == т == Тонна, шт. == Штука).
|
||||
export function unitsEquivalent(a: string | undefined, b: string | undefined): boolean {
|
||||
return normalizeUnit(a) === normalizeUnit(b);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user