feat: разряды в полях сумм заявки на оплату
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { overLimitAmount, parseAmount, previousPaymentsTotal, recipientMatchesInvoice } from './amounts';
|
import { formatAmountInput, overLimitAmount, parseAmount, previousPaymentsTotal, recipientMatchesInvoice } from './amounts';
|
||||||
|
|
||||||
describe('parseAmount', () => {
|
describe('parseAmount', () => {
|
||||||
it('понимает пробелы и запятую', () => {
|
it('понимает пробелы и запятую', () => {
|
||||||
@@ -45,3 +45,22 @@ describe('recipientMatchesInvoice', () => {
|
|||||||
expect(recipientMatchesInvoice({ name: 'ТОО «Альфа»', bin: '1' }, { name: '', bin: '' })).toBe(true);
|
expect(recipientMatchesInvoice({ name: 'ТОО «Альфа»', bin: '1' }, { name: '', bin: '' })).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('formatAmountInput', () => {
|
||||||
|
it('разбивает на разряды', () => {
|
||||||
|
expect(formatAmountInput('1234567')).toBe('1 234 567');
|
||||||
|
expect(formatAmountInput('1 528 642')).toBe('1 528 642');
|
||||||
|
});
|
||||||
|
it('держит недописанную дробную часть и режет её до двух знаков', () => {
|
||||||
|
expect(formatAmountInput('1234,')).toBe('1 234,');
|
||||||
|
expect(formatAmountInput('1234.567')).toBe('1 234,56');
|
||||||
|
expect(formatAmountInput(',5')).toBe('0,5');
|
||||||
|
});
|
||||||
|
it('выкидывает мусор и лишние разделители', () => {
|
||||||
|
expect(formatAmountInput('12a3,4,5')).toBe('123,45');
|
||||||
|
expect(formatAmountInput('')).toBe('');
|
||||||
|
});
|
||||||
|
it('согласован с parseAmount', () => {
|
||||||
|
expect(parseAmount(formatAmountInput('154100,5'))).toBe(154100.5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -9,6 +9,22 @@ export function parseAmount(raw: string): number {
|
|||||||
return Number.isFinite(value) ? value : 0;
|
return Number.isFinite(value) ? value : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Текст поля суммы с разрядами («1234567,5» → «1 234 567,5»), как его видно при
|
||||||
|
* вводе: остаются цифры и один десятичный разделитель (запятая), в дробной
|
||||||
|
* части не больше двух знаков. Недописанное «12,» сохраняется — иначе запятую
|
||||||
|
* нельзя было бы ввести.
|
||||||
|
*/
|
||||||
|
export function formatAmountInput(raw: string): string {
|
||||||
|
const cleaned = raw.replace(/[^\d.,]/g, '').replace('.', ',');
|
||||||
|
const comma = cleaned.indexOf(',');
|
||||||
|
const intPart = (comma < 0 ? cleaned : cleaned.slice(0, comma)).replace(/,/g, '').replace(/^0+(?=\d)/, '');
|
||||||
|
const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
|
||||||
|
if (comma < 0) return grouped;
|
||||||
|
const fraction = cleaned.slice(comma + 1).replace(/,/g, '').slice(0, 2);
|
||||||
|
return `${grouped || '0'},${fraction}`;
|
||||||
|
}
|
||||||
|
|
||||||
/** Сумма ранее поданных оплат по основанию. */
|
/** Сумма ранее поданных оплат по основанию. */
|
||||||
export function previousPaymentsTotal(payments: IPreviousPayment[]): number {
|
export function previousPaymentsTotal(payments: IPreviousPayment[]): number {
|
||||||
return payments.reduce((sum, p) => sum + p.amount, 0);
|
return payments.reduce((sum, p) => sum + p.amount, 0);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { TextInput } from '../../../shared/ui';
|
import { TextInput } from '../../../shared/ui';
|
||||||
import { parseAmount } from '../amounts';
|
import { formatAmountInput, parseAmount } from '../amounts';
|
||||||
|
|
||||||
interface IAmountInputProps {
|
interface IAmountInputProps {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -8,10 +8,10 @@ interface IAmountInputProps {
|
|||||||
onChange: (value: number) => void;
|
onChange: (value: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toText = (value: number): string => (value ? String(value) : '');
|
const toText = (value: number): string => (value ? formatAmountInput(String(value)) : '');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Поле суммы с десятичной частью. Хранит значение числом, но пока пользователь
|
* Поле суммы с десятичной частью и разрядами («1 234 567,50»). Хранит значение числом, но пока пользователь
|
||||||
* печатает, держит собственный текст: иначе «12,» превращалось бы в «12» на
|
* печатает, держит собственный текст: иначе «12,» превращалось бы в «12» на
|
||||||
* каждом нажатии и дробную часть ввести было бы нельзя.
|
* каждом нажатии и дробную часть ввести было бы нельзя.
|
||||||
*/
|
*/
|
||||||
@@ -28,8 +28,9 @@ export const AmountInput: React.FC<IAmountInputProps> = ({ id, value, onChange }
|
|||||||
id={id}
|
id={id}
|
||||||
value={text}
|
value={text}
|
||||||
onChange={(next) => {
|
onChange={(next) => {
|
||||||
setText(next);
|
const formatted = formatAmountInput(next);
|
||||||
onChange(parseAmount(next));
|
setText(formatted);
|
||||||
|
onChange(parseAmount(formatted));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user