fix(web): окно корректировки — база черновика, подпись выбора (#206)

T6.11: при пересборке окна из нового агрегата (правок не было) база черновика
обновляется до текущей редакции корректировки, иначе при следующем открытии
показывалась ложная плашка об изменении.
T6.12: подпись кнопки «Выбрать» называет контрагента; ошибки полей статьи
объявляются role=alert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-28 21:55:35 +04:00
parent c1d402f0e3
commit 84c4f25a9b
5 changed files with 19 additions and 6 deletions

View File

@ -1,7 +1,7 @@
// Выбор нераспределённой строки для «Добавить строку ОСВ»: клик по строке отправляет ASSIGN (в окне, через onPick). // Выбор нераспределённой строки для «Добавить строку ОСВ»: клик по строке отправляет ASSIGN (в окне, через onPick).
import { Button, Modal, Table } from 'antd'; import { Button, Modal, Table } from 'antd';
import type { UnallocatedEntryOut } from '../../types/corrections'; import type { UnallocatedEntryOut } from '../../types/corrections';
import { formatRub } from './editorView'; import { formatRub, pickLabel } from './editorView';
type Props = { type Props = {
open: boolean; open: boolean;
@ -48,7 +48,7 @@ export function AllocationPicker({ open, sideLabel, article, candidates, busy, o
<Button <Button
size='small' size='small'
disabled={busy} disabled={busy}
aria-label={`Выбрать: ${u.account_code}, ${formatRub(u.amount)}`} aria-label={pickLabel(u.counterparty.name, u.account_code, u.amount)}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onPick(u); onPick(u);

View File

@ -96,7 +96,11 @@ export function CorrectionEditorModal({ versionId, table, aggregateKey, editable
if (prevInitial.current === initial) return; if (prevInitial.current === initial) return;
const next = rebaseState(prevInitial.current, initial, plain(stateRef.current)); const next = rebaseState(prevInitial.current, initial, plain(stateRef.current));
prevInitial.current = initial; prevInitial.current = initial;
if (next) setState(withIds(next)); if (next) {
setState(withIds(next));
// правок не было — окно целиком на новой редакции, база черновика тоже; иначе при следующем открытии плашка «изменили после ваших правок» ложная
setBase(aggregate?.correction?.version_no ?? null);
}
}, [initial]); }, [initial]);
// без агрегата окно только на просмотр: правок и «несохранённого» нет // без агрегата окно только на просмотр: правок и «несохранённого» нет

View File

@ -153,7 +153,7 @@ function LineRow({ line, index, sideName, err, articleCodes, editable, onRemoveL
<Input maxLength={4} status={err.line_code ? 'error' : undefined} aria-label={`Строка баланса, ${where}`} aria-describedby={err.line_code ? codeErrId : undefined} /> <Input maxLength={4} status={err.line_code ? 'error' : undefined} aria-label={`Строка баланса, ${where}`} aria-describedby={err.line_code ? codeErrId : undefined} />
</AutoComplete> </AutoComplete>
{err.line_code && ( {err.line_code && (
<div id={codeErrId} style={{ color: '#d92d20', fontSize: 12 }}> <div id={codeErrId} role='alert' style={{ color: '#d92d20', fontSize: 12 }}>
{err.line_code} {err.line_code}
</div> </div>
)} )}
@ -170,7 +170,7 @@ function LineRow({ line, index, sideName, err, articleCodes, editable, onRemoveL
onChange={(e) => onChangeLine(line.id, { amount: e.target.value })} onChange={(e) => onChangeLine(line.id, { amount: e.target.value })}
/> />
{err.amount && ( {err.amount && (
<div id={amountErrId} style={{ color: '#d92d20', fontSize: 12 }}> <div id={amountErrId} role='alert' style={{ color: '#d92d20', fontSize: 12 }}>
{err.amount} {err.amount}
</div> </div>
)} )}

View File

@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { ApiError } from '../../api/client'; import { ApiError } from '../../api/client';
import type { CorrectionAggregateOut } from '../../types/corrections'; import type { CorrectionAggregateOut } from '../../types/corrections';
import { CONFLICT_MESSAGE, controlText, draftNotice, formatRub, saveErrorOutcome, timeHHMM } from './editorView'; import { CONFLICT_MESSAGE, controlText, draftNotice, formatRub, pickLabel, saveErrorOutcome, timeHHMM } from './editorView';
const err = (status: number, code: string, detail: Record<string, unknown> = {}) => const err = (status: number, code: string, detail: Record<string, unknown> = {}) =>
new ApiError(status, code, `msg ${code}`, { detail: { code, message: `msg ${code}`, ...detail } }); new ApiError(status, code, `msg ${code}`, { detail: { code, message: `msg ${code}`, ...detail } });
@ -69,3 +69,9 @@ describe('formatRub', () => {
expect(formatRub(null)).toBe('—'); expect(formatRub(null)).toBe('—');
}); });
}); });
describe('pickLabel', () => {
it('содержит контрагента, счёт и сумму', () => {
expect(pickLabel('ООО Ромашка', '6001', '1234.5')).toBe('Выбрать: ООО Ромашка, 6001, 1 234,50');
});
});

View File

@ -13,6 +13,9 @@ export const DISCARD_QUESTION = 'Есть несохранённые измен
/** Сумма в рублях с двумя знаками («1 000 000,00»); окно показывает рубли независимо от единиц панели таблицы. */ /** Сумма в рублях с двумя знаками («1 000 000,00»); окно показывает рубли независимо от единиц панели таблицы. */
export const formatRub = (value: string | null): string => formatScaledAmount(value, { scale: 0, precision: 2 }); export const formatRub = (value: string | null): string => formatScaledAmount(value, { scale: 0, precision: 2 });
/** Подпись кнопки «Выбрать» в списке нераспределённых: читалка экрана называет контрагента, счёт и сумму. */
export const pickLabel = (counterparty: string, account: string, amount: string): string => `Выбрать: ${counterparty}, ${account}, ${formatRub(amount)}`;
/** «394.27» → «394,27%»; строка в число не переводится. */ /** «394.27» → «394,27%»; строка в число не переводится. */
export const percentText = (p: string): string => `${p.replace('.', ',')}%`; export const percentText = (p: string): string => `${p.replace('.', ',')}%`;