From 397a1a7aef0eee7e4c3effe56030f4dacc23d646 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 27 Oct 2026 19:22:25 +0400 Subject: [PATCH] =?UTF-8?q?fix(counter-pairs):=20=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D1=84=D0=B8=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20(I-1,=20I-2,=20?= =?UTF-8?q?I-3,=20M-1,=20M-2,=20M-3,=20M-5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Подпись пары без служебных слов, тест пары на фикстурах РБ, модалка при уходе с «ОСВ» на любую вкладку, без «Экспорт» в режиме пар, README и ARCHITECTURE, таблица решений design.md. Co-Authored-By: Claude Opus 5.5 --- README.md | 4 ++-- api/tests/test_consolidation_fixtures.py | 3 +++ openspec/changes/counter-pairs/design.md | 7 ++++--- web/ARCHITECTURE.md | 4 +++- web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx | 6 +++--- web/src/widgets/ConsolidatedOsv/OsvTabs.tsx | 2 +- .../ConsolidatedOsvContent/ConsolidatedOsvContent.tsx | 9 ++++++--- 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4c13e12..20f9316 100644 --- a/README.md +++ b/README.md @@ -786,8 +786,8 @@ advisory-блокировкой. | `POST /consolidations/versions/{id}/status` | EDIT | ручной переход: `CANCELLED` (с комментарием) или возврат в `INITIATED` | | `PUT /consolidations/versions/{id}/members` | EDIT | заменить периметр (ЮЛ должны принадлежать ГК, ≥ 2) | | `GET /consolidations/versions/{id}/checklist` | VIEW | комплект документов: ЮЛ × правила, статус ячейки; `?format=csv` — тот же чек-лист файлом CSV | -| `GET /consolidations/versions/{id}/osv` | VIEW | страница сводной ОСВ и фильтры (`limit` ≤ 2000, по умолчанию 500) | -| `GET /consolidations/versions/{id}/osv/summary` | VIEW | `rows_total`, `unmapped_total`, `auto_matched_total`, `documents_total`, `gaps[]` | +| `GET /consolidations/versions/{id}/osv` | VIEW | страница сводной ОСВ и фильтры, в т. ч. `counterparty_id` — строки с этим контрагентом (`limit` ≤ 2000, по умолчанию 500) | +| `GET /consolidations/versions/{id}/osv/summary` | VIEW | `rows_total`, `unmapped_total`, `auto_matched_total`, `documents_total`, `gaps[]`, `counter_pairs[]` (пары «участник не видит встречных записей») | | `GET /consolidations/versions/{id}/osv/export` | VIEW | xlsx: лист «Сводная ОСВ» и лист «Пробелы» | | `GET /consolidations/versions/{id}/counterparty-candidates` | VIEW | кандидаты для сопоставления: `scope=group\|all`, `q`, `entry_id` | | `POST /consolidations/versions/{id}/osv/entries/{entry_id}/counterparty` | EDIT | ручное сопоставление контрагента строки свода | diff --git a/api/tests/test_consolidation_fixtures.py b/api/tests/test_consolidation_fixtures.py index 4132741..834456a 100644 --- a/api/tests/test_consolidation_fixtures.py +++ b/api/tests/test_consolidation_fixtures.py @@ -162,6 +162,8 @@ async def test_consolidation_on_russian_biscuit_fixtures( # noqa: PLR0912, PLR0 # Числа сверены независимым подсчётом по сырым golden-JSON (склейка Q1–Q3, пробелы по наличию файлов). assert summary["rows_total"] == len(rows) == ROWS_TOTAL assert summary["unmapped_total"] == UNMAPPED_TOTAL + # РБ записал «Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ» без ИНН: Счастливочная видит РБ, а РБ — её нет (§3 п. 9) + assert [(p["holder_id"], p["missing_id"]) for p in summary["counter_pairs"]] == [(str(schastl.id), str(rb.id))] # строки свода: 60 и 62 у обеих, 76 у Счастливочной present = {(row["legal_entity_id"], row["account_root"]) for row in rows} @@ -219,6 +221,7 @@ async def test_consolidation_on_russian_biscuit_fixtures( # noqa: PLR0912, PLR0 assert body["propagated"] == PROPAGATED after = await _get(client, f"{version_url}/osv/summary") + assert after["counter_pairs"] == [] # после сопоставления пара закрыта rows_after = await _all_rows(client, version_url) # `propagated` считает записи `osv_entry` (в том числе в парах-пробелах 66/76, строк в своде не дающих), а # `unmapped_total` — строки свода: квартальные записи одного контрагента склеены (D-2). Поэтому `unmapped_total` diff --git a/openspec/changes/counter-pairs/design.md b/openspec/changes/counter-pairs/design.md index 194f6d3..c02c535 100644 --- a/openspec/changes/counter-pairs/design.md +++ b/openspec/changes/counter-pairs/design.md @@ -49,7 +49,7 @@ из шапки). Правая таблица — постранично, `OSV_PAGE_SIZE`, «Указать» → `CounterpartyMapModal` (существующая). - `ConsolidationReportPage` / `OsvTabs`: вместо `unmappedTotal` получают сводку (пары и `unmapped_total`); правило показа модалки — чистая функция в `utils/consolidations.ts` (`counterPairsKey(pairs)`, `shouldWarnCounterPairs(versionId, - pairs, storage)`), с тестами vitest. Ключ `sessionStorage` — `armka:counter-pairs:`, значение — ключ списка + status, pairs, storage)`), с тестами vitest. Ключ `sessionStorage` — `armka:counter-pairs:`, значение — ключ списка пар (`holder_id>missing_id`, отсортированные, через запятую). Чтение и запись — в `try/catch`. - `UnmappedInnModal` заменяется модалкой «Проверьте встречные записи участников»; склонение «пара/пары/пар», «участника/участников» — существующая `pluralRu` (`web/src/utils/labels.ts`). @@ -89,8 +89,9 @@ | 27.10.2026 | Тексты баннера и модалки — из спек этого change | Владелец согласовал смысл, формулировки мои | Низкая | | 27.10.2026 | Поиск `q` над правой таблицей режима (находка builder check) | У РБ ~1000 строк без ИНН; без поиска разбор пары — листание страниц | Низкая: существующий фильтр API | | 27.10.2026 | Модалка не показывается для версий в `FINISHED_STATUSES` (находка builder check) | Замороженную версию не исправить — предупреждение было бы шумом | Низкая | - -Отложено решением владельца 27.10.2026: строки Б с ИНН вне справочника — AURORA-1866 (разбор); подсветка кандидата по сумме — AURORA-1867. | 27.10.2026 | R-1 (design #9): `needsUnmappedWarning` удаляется в Task 4 вместе с его использованием в `OsvTabs`, а не в Task 3 | Иначе между коммитами Task 3 и 4 сборка (tsc) красная | Нулевая | | 27.10.2026 | R-2 (design #10): в тексте модалки «Ещё K строк без ИНН…» слово «строк» не склоняется, M — число разных `missing_id` (по спеке) | Спека дословна; склонение K=1 даёт всё равно неграмматичную фразу («учтены») | Низкая | | 27.10.2026 | R-3 (design #11): отдельное ревью Task 6 не проводится — её diff в arm-core (одна фраза в CLAUDE.md) и docs-PR #176 проверяются финальным ревью всей ветки | Задача не содержит кода; отдельный ревьюер дублировал бы финальный | Низкая | +| 27.10.2026 | R-4 (design #12): подпись под парой в списке — «записей: N · без ИНН у <Б>: M» вместо служебных «держатель»/«Б» | Аналитик не знает ролей «держатель» и «Б»; название организации понятнее | Низкая | + +Отложено решением владельца 27.10.2026: строки Б с ИНН вне справочника — AURORA-1866 (разбор); подсветка кандидата по сумме — AURORA-1867. diff --git a/web/ARCHITECTURE.md b/web/ARCHITECTURE.md index 12e7f29..b3a44ec 100644 --- a/web/ARCHITECTURE.md +++ b/web/ARCHITECTURE.md @@ -30,7 +30,9 @@ SPA поверх API arm-core (`/api/v1`). Своих данных и бизне | `ConsoleTable/LockModal.tsx` | [35] | «Взять в работу» (`POST …/lock`) и перехват с причиной (`takeover_reason`) | | `../utils/consolidations.ts` | — | правила без React: подписи и пояснения статусов версии, ручные переходы (`MANUAL_TRANSITIONS` — зеркало бэка), своя блокировка, «Версия», `in_work_only`, причина ≤ 1000 | | `ConsoleTable/consts.ts` | — | цвета бейджей статусов версии | -| `ConsolidatedOsv/OsvTabs.tsx` | [30], [31-00] | вкладки шагов отчёта и «Остались неуказанные ИНН» при уходе с «ОСВ» | +| `ConsolidatedOsv/OsvTabs.tsx` | [30] | вкладки шагов отчёта и модалка «Проверьте встречные записи участников» при уходе с «ОСВ» (список пар — `counter_pairs` сводки) | +| `ConsolidatedOsv/CounterPairsView.tsx` | [30] | режим «Встречные записи» вкладки «ОСВ»: пары, строки держателя, строки Б без ИНН с «Указать» | +| `ConsolidatedOsv/counterPairs.ts` | — | правила без React: подпись пары, параметры адреса режима (`?view=pairs&holder=&missing=`) | | `ConsolidatedOsv/useNumberDisplay.tsx` | [30] | «Единицы» и «Знаков после запятой» сводной ОСВ (`formatScaledAmount` из `utils/money.ts`) | | `../hooks/useBeforeUnload.ts` | — | предупреждение браузера о несохранённых правках в открытой модалке | | `ConsoleLegalEntities/` | [34] | панель «Организации» выбранной версии и чек-лист | diff --git a/web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx b/web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx index c2bc981..7fe11ae 100644 --- a/web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx +++ b/web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx @@ -55,9 +55,9 @@ function usePagedRows(versionId: string, filters: OsvFilters | null, reloadKey: getConsolidatedOsv(versionId, filters, OSV_PAGE_SIZE, (page - 1) * OSV_PAGE_SIZE) .then((r) => { if (requestId !== requestRef.current) return; - // страница ушла за конец (последнюю строку сопоставили) — назад на допустимую + // страница ушла за конец (последнюю строку сопоставили) — сразу на последнюю существующую if (r.items.length === 0 && page > 1) { - setPage(page - 1); + setPage(Math.max(1, Math.ceil(r.total / OSV_PAGE_SIZE))); return; } setRows(r.items); @@ -171,7 +171,7 @@ export function CounterPairsView({ versionId, pairs, selected, numFormat, onSele }}>
{pairLabel(p)}
- строк у держателя: {p.holder_rows} · без ИНН у Б: {p.missing_unmapped_rows} + записей: {p.holder_rows} · без ИНН у {p.missing_name}: {p.missing_unmapped_rows}
); diff --git a/web/src/widgets/ConsolidatedOsv/OsvTabs.tsx b/web/src/widgets/ConsolidatedOsv/OsvTabs.tsx index 9523035..a2524b6 100644 --- a/web/src/widgets/ConsolidatedOsv/OsvTabs.tsx +++ b/web/src/widgets/ConsolidatedOsv/OsvTabs.tsx @@ -76,7 +76,7 @@ export function OsvTabs({ summary, versionId, status }: Props) { const newKey = String(value); if (newKey === activeKey) return; const pairs = summary?.counter_pairs ?? []; - if (activeKey === 'osv' && newKey === 'chess' && shouldWarnCounterPairs(versionId, status, pairs, safeSessionStorage())) { + if (activeKey === 'osv' && shouldWarnCounterPairs(versionId, status, pairs, safeSessionStorage())) { rememberCounterPairsShown(versionId, pairs, safeSessionStorage()); setPending(newKey); } else { diff --git a/web/src/widgets/ConsolidatedOsvContent/ConsolidatedOsvContent.tsx b/web/src/widgets/ConsolidatedOsvContent/ConsolidatedOsvContent.tsx index 4550630..a7b4f90 100644 --- a/web/src/widgets/ConsolidatedOsvContent/ConsolidatedOsvContent.tsx +++ b/web/src/widgets/ConsolidatedOsvContent/ConsolidatedOsvContent.tsx @@ -101,9 +101,12 @@ export function OsvContent({ card, onSummaryChange }: Props) { /> - + {/* выгружает весь свод, а не пару — в режиме пар не нужна */} + {!inPairs && ( + + )}