const NBSP = ' '; const PLAIN = /^(-?)(\d+)(?:\.(\d+))?$/; const INPUT = /^-?\d+(\.\d{1,2})?$/; const MONEY = /^(-?)(\d+)(?:\.(\d{1,2}))?$/; export type AmountFormat = { /** Ноль показывать прочерком (свод, ОСВ — как в макете [30]). */ zeroDash?: boolean; /** 'auto' — без копеек, если они нулевые (формы ББ/ОФР в тыс. руб., макет [41]). */ fraction?: 'always' | 'auto'; /** Что показать вместо пустого значения. */ empty?: string; }; /** Сумма из API (строка Decimal) → «1 234 567,80». Без арифметики над float; нераспознанное — как есть. */ export function formatAmount(value: string | null | undefined, format: AmountFormat = {}): string { const empty = format.empty ?? '—'; if (value === null || value === undefined || value === '') { return empty; } const match = PLAIN.exec(value); if (!match) { return value; } const [, sign, intPart, frac = ''] = match; if (frac.length > 2) { return value; } const isZero = /^0+$/.test(intPart) && /^0*$/.test(frac); if (isZero && format.zeroDash) { return '—'; } const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, NBSP); const cents = frac.padEnd(2, '0'); if (format.fraction === 'auto' && cents === '00') { return `${isZero ? '' : sign}${grouped}`; } return `${isZero ? '' : sign}${grouped},${cents}`; } /** Ввод аналитика → строка суммы для API; пустое — null; неверный формат — ok: false. */ export function parseAmountInput(input: string): { ok: true; value: string | null } | { ok: false } { const normalized = input.replace(/[\s ]/g, '').replace(',', '.'); if (normalized === '') { return { ok: true, value: null }; } return INPUT.test(normalized) ? { ok: true, value: normalized } : { ok: false }; } /** Точная сумма строк Decimal из API (копейками, без float) → «123.45»; пустые пропускаются, все пустые — null. * Сумма, которую не удалось разобрать (больше двух знаков, текст), делает итог null — итог не выдумываем. */ export function sumAmounts(values: readonly (string | null | undefined)[]): string | null { let cents = 0n; let any = false; for (const value of values) { if (value === null || value === undefined || value === '') continue; const match = MONEY.exec(value); if (!match) return null; const [, sign, intPart, frac = ''] = match; const amount = BigInt(intPart) * 100n + BigInt(frac.padEnd(2, '0')); cents += sign ? -amount : amount; any = true; } if (!any) return null; const negative = cents < 0n; const abs = negative ? -cents : cents; return `${negative ? '-' : ''}${abs / 100n}.${(abs % 100n).toString().padStart(2, '0')}`; }