aurora-1544 добавление таблицы гудвил #220

Merged
aep merged 18 commits from feat/goodWill into main 2026-11-06 18:08:01 +03:00
4 changed files with 40 additions and 4 deletions
Showing only changes of commit 6be3936d9f - Show all commits

View File

@ -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): поле доли у
неотмеченной строки скрыто, неверный ввод там не исправить. Подсказка у «Сохранить» называет строки с ошибкой и
предупреждает, если они скрыты фильтром. Цена ошибки: неверный ввод в скрытом поле молча отбрасывается.

View File

@ -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');
});
});

View File

@ -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,
};
}

View File

@ -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>