aurora-1544 добавление таблицы гудвил #220
@ -24,3 +24,13 @@
|
||||
- **R-3 (2026-11-06). Предпросмотр — только у строк;** «Всего» и «Итого по листу» в режиме правки показывают сохранённое
|
||||
и обновляются после «Сохранить». Обоснование: агрегаты не считаем на фронте (R-2). Цена ошибки: аналитик видит итог
|
||||
после сохранения, а не во время ввода (DOC-259025 §5.1 п. 3 говорит «предварительные … итоги»).
|
||||
- **R-4 (2026-11-06). `buildTree`/`rowClassName` без unit-теста.** Компоненты в проекте не тестируются (`web/CLAUDE.md`),
|
||||
логика тривиальна; чистая логика вкладки (`utils/goodwill.ts`, в т. ч. `effectiveRow`) покрыта vitest. Цена ошибки:
|
||||
регрессия дерева видна только глазами.
|
||||
- **R-5 (2026-11-06). Проверка на стенде — частично моками.** На локальном стенде нет пар 58.01 между участниками периметра:
|
||||
реальный бэк ветки проверен на пустой таблице, переключателе и экспорте, насыщенные состояния и 422 — подменой ответов
|
||||
в Playwright. Цена ошибки: совпадение предпросмотра P/G с сервером на реальных данных подтверждено только тестами; после
|
||||
мержа #222 пройти один сценарий на реальной паре.
|
||||
- **R-6 (2026-11-06). Снятая галочка с неверной долей уходит с сохранённой долей** (финальное ревью, I-1): поле доли у
|
||||
неотмеченной строки скрыто, неверный ввод там не исправить. Подсказка у «Сохранить» называет строки с ошибкой и
|
||||
предупреждает, если они скрыты фильтром. Цена ошибки: неверный ввод в скрытом поле молча отбрасывается.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { GoodwillRow } from '../types/goodwill/goodwill';
|
||||
import { changedRows, matchesSign, parseShare, previewAmounts, rowKey } from './goodwill';
|
||||
import { changedRows, effectiveRow, matchesSign, parseShare, previewAmounts, rowKey } from './goodwill';
|
||||
|
||||
function row(patch: Partial<GoodwillRow> = {}): GoodwillRow {
|
||||
return {
|
||||
@ -98,3 +98,15 @@ describe('changedRows', () => {
|
||||
expect(changedRows([fresh], edits)[0].ownership_share).toBe('80.5');
|
||||
});
|
||||
});
|
||||
|
||||
describe('effectiveRow', () => {
|
||||
it('снятая галочка с неверной долей уходит с сохранённой долей (ошибку у скрытого поля не исправить)', () => {
|
||||
const saved = row({ ownership_share: '80.00' });
|
||||
expect(effectiveRow(saved, { include_in_kfo: false, ownership_share: '80,123' }).ownership_share).toBe('80.00');
|
||||
const fresh = row();
|
||||
expect(effectiveRow(fresh, { include_in_kfo: false, ownership_share: 'abc' }).ownership_share).toBeNull();
|
||||
});
|
||||
it('у отмеченной строки неверная доля остаётся как введена — ошибку видно у поля', () => {
|
||||
expect(effectiveRow(row(), { include_in_kfo: true, ownership_share: '80,123' }).ownership_share).toBe('80,123');
|
||||
});
|
||||
});
|
||||
|
||||
@ -58,14 +58,18 @@ export function matchesSign(goodwill: string | null, sign: GoodwillSign): boolea
|
||||
|
||||
/** Значения строки с учётом правки: что уйдёт в пакет. */
|
||||
export function effectiveRow(row: GoodwillRow, edit: GoodwillEdit | undefined): GoodwillRowKeyIn {
|
||||
const include = edit?.include_in_kfo ?? row.include_in_kfo;
|
||||
const share = edit && 'ownership_share' in edit ? (edit.ownership_share ?? null) : row.ownership_share;
|
||||
const parsed = share === null ? null : parseShare(share);
|
||||
const comment = edit && 'comment' in edit ? (edit.comment ?? null) : row.comment;
|
||||
let ownership = parsed?.ok ? parsed.value : share;
|
||||
// у неотмеченной строки поле доли скрыто: неверный ввод не исправить, поэтому уходит сохранённая доля
|
||||
if (!include && parsed && !parsed.ok) ownership = row.ownership_share;
|
||||
return {
|
||||
head_legal_entity_id: row.head_legal_entity.id,
|
||||
subsidiary_legal_entity_id: row.subsidiary_legal_entity.id,
|
||||
ownership_share: parsed?.ok ? parsed.value : share,
|
||||
include_in_kfo: edit?.include_in_kfo ?? row.include_in_kfo,
|
||||
ownership_share: ownership,
|
||||
include_in_kfo: include,
|
||||
comment: comment?.trim() ? comment : null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -142,6 +142,16 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => {
|
||||
|
||||
const filteredRows = useMemo(() => (data?.rows ?? []).filter((row) => matchesSign(row.goodwill_amount, sign)), [data, sign]);
|
||||
|
||||
/** Подсказка у «Сохранить»: какие строки мешают, и не скрыты ли они фильтром или настройкой колонок. */
|
||||
const issuesHint = useMemo(() => {
|
||||
if (!data || !hasIssues) return undefined;
|
||||
const keys = Object.keys(issues);
|
||||
const names = data.rows.filter((r) => keys.includes(rowKey(r))).map((r) => r.subsidiary_legal_entity.name);
|
||||
const visible = new Set(filteredRows.map(rowKey));
|
||||
const hidden = keys.some((k) => !visible.has(k)) ? ' Часть строк скрыта фильтром — сбросьте фильтр.' : '';
|
||||
return `Заполните долю и комментарий: ${names.join(', ')}.${hidden}`;
|
||||
}, [data, hasIssues, issues, filteredRows]);
|
||||
|
||||
const closeEditing = () => {
|
||||
setEdits({});
|
||||
setServerIssues({});
|
||||
@ -209,7 +219,7 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => {
|
||||
action={
|
||||
<Space>
|
||||
<Button onClick={closeEditing}>Отменить изменения</Button>
|
||||
<Tooltip title={hasIssues ? 'Заполните долю и комментарий у отмеченных строк' : undefined}>
|
||||
<Tooltip title={issuesHint}>
|
||||
<Button type='primary' disabled={hasIssues} loading={saving} onClick={handleSave}>
|
||||
Сохранить
|
||||
</Button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user