fix(counter-pairs): правки финального ревью (I-1, I-2, I-3, M-1, M-2, M-3, M-5)
Подпись пары без служебных слов, тест пары на фикстурах РБ, модалка при уходе с «ОСВ» на любую вкладку, без «Экспорт» в режиме пар, README и ARCHITECTURE, таблица решений design.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
50f28b5672
commit
397a1a7aef
@ -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 | ручное сопоставление контрагента строки свода |
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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:<version_id>`, значение — ключ списка
|
||||
status, pairs, storage)`), с тестами vitest. Ключ `sessionStorage` — `armka:counter-pairs:<version_id>`, значение — ключ списка
|
||||
пар (`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.
|
||||
|
||||
@ -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] | панель «Организации» выбранной версии и чек-лист |
|
||||
|
||||
@ -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
|
||||
}}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600 }}>{pairLabel(p)}</div>
|
||||
<div style={{ fontSize: 11, color: MUTED }}>
|
||||
строк у держателя: {p.holder_rows} · без ИНН у Б: {p.missing_unmapped_rows}
|
||||
записей: {p.holder_rows} · без ИНН у {p.missing_name}: {p.missing_unmapped_rows}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -101,9 +101,12 @@ export function OsvContent({ card, onSummaryChange }: Props) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button icon={<DownloadOutlined />} loading={exporting} onClick={download}>
|
||||
Экспорт
|
||||
</Button>
|
||||
{/* выгружает весь свод, а не пару — в режиме пар не нужна */}
|
||||
{!inPairs && (
|
||||
<Button icon={<DownloadOutlined />} loading={exporting} onClick={download}>
|
||||
Экспорт
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Segmented
|
||||
value={pairsView.view}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user