Багфиксы — стадия 4 (2026-05-06)

Продолжение серии stage1/2/3. Здесь только пользовательские сценарии — то, что реально видит/ловит человек в админке, на POS, на KDS. API-only находки (silent-accept на полях, которые UI не использует) вынесены в конец как «низкий приоритет, контракт» без репро.

Стенд, креды, helper — те же (см. BUGS-FOR-DEV.md шапку). Дополнительно работали на пустой франшизе demo3@nirbi.ru / <password — см. private/creds.md> — отдельная франшиза для проверки полного онбординга с нуля.


📋 Что произошло после stage3

✅ Подтверждено пофикшено — 14 пунктов (повторно не трогать)

F3, F8, F9, F10, F13, F14, F15, F21, F30, F31, F33, F34, F35 — после API-регресса 2026-05-06 закрыты. Также BUG-066 (склад автосоздаётся при создании ТТ).

🔁 Ретракты — баги либо by-design, либо невоспроизводимы из UI

  • F4 — «6 admin-routes 404»: это были API-only пути, в SPA реальные роуты /admin/warehouse/inventory, /admin/warehouse/receipt-acts, и т.д. — все живые
  • F32, F44 — поля цены опции в UI-форме модификатора нет вообще (цена живёт в прейскуранте), пользователь не может ввести
  • F45 — by-design soft-delete: UI явно сообщает «перемещена в удалённые», есть отдельная вкладка
  • F37 — by-design на текущем этапе: закрытые заказы временно через «Возвраты», расширить логику в будущем
  • F40 — окно menu-availability работает корректно, прошлое наблюдение было неверной интерпретацией
  • F42 — UI показывает лимиты опций модификатора и блокирует превышение
  • F57 — UI для подключения кассы 3-в-1 существует: карточка ТТ → вкладки «Интеграции» (PayKeeper) и «Терминалы» (ФН)

🔴 Critical

F26 — После оплаты картой не приходят RRN, последние 4 цифры карты и фискальные данные

Кассир видит закрытый заказ без всякой информации о платеже: чек пустой, при споре с клиентом нечего показать («вы платили картой ****1234»), вернуть деньги через эквайринг невозможно (банк требует RRN), фискальный чек в ОФД не попадает (нарушение 54-ФЗ).

Постоянная часть проблемы: rrn и card_last4 всегда null (проверено на 3 заказах подряд: #008, #015 на admin@erp + #001 на demo3).

Нестабильная часть: fiscal_data и pk_fop_receipt_key иногда заполняются, иногда нет — зависит от того, дошёл ли callback «Совершен» от PayKeeper. На demo3 заказ #001 в момент дампа PK ЛК показывал статус «Получен», fiscal_data: null.

Внутренняя инконсистентность: при fiscal_data: null приложение ставит fiscal_failed: false — врёт, что фискализация прошла, при отсутствующих данных. Ожидаемо: fiscal_status: pending пока ждём callback, либо fiscal_failed: true после таймаута.

Где смотреть (для Claude-разраба):

  • paykeeper-adapter — обработчики callback’ов от PK на событиях «оплачено» и «фискальный чек создан». Сейчас извлекается pk_payment_id, не извлекаются rrn и card.PAN.last_four (или как они там у PK называются).
  • order-service — поля orders.rrn, orders.card_last4, orders.fiscal_data (JSONB), orders.pk_fop_receipt_key. Логика выставления fiscal_failed/нового fiscal_status после таймаута.
  • Подробный разбор и таблица состояний — в zones/payments/card-payment.md.

F25 — Заказ исчезает с экрана POS сразу после оплаты

Кассир принял оплату — заказ пропал из активных. Найти можно только в разделе «Возвраты», что семантически абсурдно (это не возврат). Невозможно вернуться к оплаченному заказу до выдачи клиенту: посмотреть состав, поправить, дождаться выдачи.

Связано с F37 (retracted) и F51 — закрытые заказы на POS сейчас живут только в «Возвратах», логика смешана. Лучше всего решается вместе с F51 (отдельный экран «Готов к выдаче» куда заказ попадает после оплаты).

Где смотреть: POS UI, фильтр активного списка. Сейчас, видимо, фильтр по status IN (new, accepted, ready), после оплаты status → closed → выпадает с экрана. Решение — добавить экран «Готов к выдаче» (closed AND handed_over_at IS NULL) до явного жеста «выдан клиенту».


F27 — На POS нет кнопки «Отменить» ни в одном статусе

В UI POS отсутствует функционал отмены заказа — ни на new, ни на accepted, ни на ready, ни на handed_over. На API уровне cancel работает (POST /orders/{id}/cancel), но кассиру это недоступно. Если клиент передумал или ошиблись при пробитии — заказ некуда деть.

Подтверждено на admin@erp: Александр открыл заказы 003 (4 разных статуса) — кнопки нет нигде.

Где смотреть: POS UI карточка заказа. Добавить кнопку с правилами по статусу:

  • new/accepted → отмена без условий
  • ready → с подтверждением (товар уже приготовлен)
  • closed → через возврат, не cancel

F60 — Один и тот же физический ФН можно привязать к разным ТТ одновременно

Админ создаёт терминал в карточке ТТ, вводит заводской номер ФН — система не проверяет что этот ФН уже зарегистрирован где-то ещё. Сейчас на стенде один ФН (9999078902018961) физически закреплён за двумя ТТ: Арбат на admin@erp и Демо ТТ 1 на demo3 (разные франшизы). Оба терминала в статусе active, ни одного предупреждения.

Compliance-риск: фискальные чеки с одного физического устройства могут «считаться» двумя юр.лицами, ОФД получит хаос; при налоговой проверке — расхождение между фактическим устройством и учётом.

Где смотреть: БД терминалов и handler POST /stores/{id}/terminals. Добавить unique constraint на fs_number (хотя бы WHERE status != 'deleted') + пред-валидация с понятным сообщением «Этот ФН уже используется в ТТ “{name}”». Решить политику: жёсткий запрет (409 Conflict) или авто-перенос с подтверждением через UI (POST /terminals/{id}/transfer).


🟡 Major

F50 — Активные заказы на POS не сортируются по времени

Свежий заказ оказывается внизу списка вместо верха. Кассир теряет новые заказы из виду, путается в очереди при наплыве.

Где смотреть: POS UI, сортировка списка активных. Ожидание: ORDER BY created_at DESC (свежие сверху).


F51 — Вкладка «Готовы» на POS смешивает «готов к выдаче» и «уже закрыт»

Кассир не различает заказы, которые ещё надо выдать клиенту, и те, что уже закрыты. На бэкенде статусы разные (ready vs closed/handed_over), но в UI они в одной куче. Кассир не понимает: «выдать или уже выдан?»

Связано с F25 — после оплаты заказ должен попадать именно в «Готов к выдаче», а не «исчезать в никуда».

Где смотреть: POS UI, разделить вкладки. Вариант:

  • «Готов к выдаче» — status=ready (готов, не оплачен) и status=closed AND handed_over_at IS NULL (оплачен, ждёт выдачи)
  • кнопка «Выдать клиенту» → выставляет handed_over_at
  • «Закрытые» — handed_over_at IS NOT NULL, для просмотра/возвратов

Сейчас handed_over в потоке cash/card flow вообще не достигается (paid → closed напрямую) — нужно ввести жест выдачи.


F52 — На POS нет выбора метода оплаты (наличка/карта)

Всё уходит через единый flow «касса 3-в-1», в БД оседает payment_method: "card" независимо от того, как фактически оплатили. Бухгалтерская выручка по способам оплаты не различается, отчётность по 54-ФЗ некорректна (фискальный чек должен содержать признак способа оплаты).

Где смотреть:

  • POS UI — добавить выбор метода до отправки на ФР: «Наличными» / «Картой» / «Смешанная»
  • POS-bff → admin-bff/order-service — пробрасывать выбор в order.payment_method (cash / card / mixed)
  • ФР — корректный признак способа оплаты в чеке для ОФД

F53 — Кнопка «Выйти» в шапке имеет type="submit"

Если пользователь сидит в любой форме админки и нажимает Enter — может случайно сработать сабмит кнопки «Выйти» в шапке (она type="submit" без формы) и выкинуть из системы вместе со всеми незаписанными данными. Однострочный фикс.

Где смотреть: компонент <header> в admin SPA, заменить type="submit" на type="button".


F54 — PATCH /stores/{id} с is_published тихо игнорируется

PATCH возвращает 200 OK, но is_published остаётся прежним. Правильный путь — POST /api/v1/admin/stores/{id}/publish. Если SPA на кнопке «Опубликовать» / «Снять с публикации» шлёт именно PATCH — юзер жмёт, ничего не происходит, никакого сообщения об ошибке нет.

Тот же класс что F6a/F41 (silent-accept), но в отличие от тех — этот точно user-facing, поскольку «Опубликовать» — реальная кнопка в карточке ТТ. Нужно проверить что SPA шлёт; если PATCH — переключить на POST.

Где смотреть:

  • store-handler: либо разрешить запись is_published через PATCH, либо отвечать 400 «Use POST /stores/{id}/publish»
  • SPA: убедиться что обе кнопки (публикация / снятие) идут через корректный endpoint

BUG-051 — В карточке прейскуранта «Назначено ТТ: 0» при существующих привязках

Связь price-list ↔ store записывается только со стороны ТТ (PATCH /stores/{id} с price_list_id работает). Со стороны прейскуранта PATCH /price-lists/{id} с stores: [...] или store_ids: [...] тихо игнорируется (200 OK, не пишет). На экране прейскуранта пользователь видит «Назначено ТТ: 0», думает что привязок нет — а реально ТТ пользуется этим прейскурантом.

Где смотреть: catalog price-list handler. Решения:

  • Двусторонняя запись: PATCH /price-lists/{id} с stores: [...] каскадно обновляет store.price_list_id затронутых ТТ
  • Либо отдельные nested endpoints POST/DELETE /price-lists/{id}/stores/{store_id} (которых пока нет, и SPA на экране прейскуранта похоже ожидает именно их)

🟢 Minor

F47 — После удаления последней группы модификаторов пропадают вкладки «Активные/Удалённые»

UI рендерит общий empty-state на всю секцию модификаторов вместо empty-state внутри активной вкладки. У пользователя нет способа попасть в «Удалённые» и восстановить случайно удалённую группу — soft-deleted записи становятся «потерянными» из UI.

Фикс: оставлять вкладки видимыми всегда. На пустой вкладке показывать локальный empty-state, не вместо всего экрана.


F55 — Default-прейскурант не привязывается к новой ТТ автоматически

При создании ТТ через UI store.price_list_id: null, даже если в системе есть прейскурант с is_default: true. Пользователь обязан помнить дополнительный шаг через карточку ТТ. Усложняет онбординг новой точки.

Фикс: при POST /stores, если у франшизы есть default-прейскурант — присваивать его новой ТТ автоматически.


F56 — KDS показывает не-кухонные позиции серым с пометкой «без станции»

Когда в заказе есть Кола или другая не-кухонная позиция, повар на KDS видит её серой под пометкой «без станции» — выглядит как «ещё не сделано». На самом деле позиция уже ready сразу при создании. Повару не нужно знать о таких позициях в принципе — это не его работа.

Фикс: либо вообще не показывать не-кухонные позиции на KDS (повар не видит «справочно»), либо показывать с явной отметкой ✓ готово / на выдачу вместо «без станции».


F58 — POS говорит «проверить подключение кассы» без подсказки куда идти

При попытке оплаты на ТТ без подключённого PayKeeper или терминала, POS пишет «нужно проверить подключение кассы 3-в-1». Кассир/менеджер не понимают что нужно сделать: 1) открыть карточку ТТ в админке → вкладка «Интеграции» → подключить PayKeeper, 2) вкладка «Терминалы» → добавить терминал с ФН.

Фикс: расширить сообщение POS до «Касса не настроена. В админке откройте карточку ТТ → вкладка “Интеграции” (PayKeeper) и “Терминалы” (ФН)».


F59 — Список терминалов в карточке ТТ не обновляется после удаления

Удалённый через UI терминал остаётся видимым в таблице до перезагрузки страницы или переключения вкладок. API уже возвращает пустой список — фронт не делает refetch после успешного DELETE.

Фикс: invalidate query / refetch после успешной мутации.


📌 Контекст для Claude-разраба

Кросс-каттинг паттерн «silent-accept на API»

Несколько багов одного класса, все из одного семейства — handlers принимают неподдерживаемые поля в body PATCH/POST и тихо их игнорируют, возвращая 200 OK:

EndpointПолеЧто происходит
PATCH /catalog/products/{id}base_price200, тихо игнор → правильный путь PATCH /price-lists/{id}/items
PATCH /catalog/products/{id}modifier_group_ids200, тихо игнор → правильный путь POST /products/{id}/modifiers
POST /modifier-groupstypeпринимает любую строку без enum-валидации
PATCH /stores/{id}is_published200, тихо игнор → правильный путь POST /stores/{id}/publish
PATCH /price-lists/{id}stores/store_ids200, тихо игнор (BUG-051)

Из этого набора на пользователя влияет только F54 и BUG-051 (это user-facing — кнопки в админке завязаны на эти поля). Остальные — API-only, фиксить системно если совпадает с приоритетами:

  • Либо strict-валидация на уровне фреймворка (отказывать на неизвестных полях)
  • Либо whitelist-валидация в каждом handler с 400-ответом и подсказкой о правильном endpoint

Связи между багами

  • F25 + F51 + F37 — три симптома одной проблемы «POS UI не имеет нормального экрана для оплаченных-но-не-выданных заказов». Решаются одним архитектурным шагом: отдельный экран «Готов к выдаче» + жест «Выдать клиенту» (выставляет handed_over_at).
  • F26 + F60 — оба относятся к compliance/фискалке. F26 — данные не извлекаются из callback’а, F60 — нет уникальности ФН. Если разраб берёт фискальный модуль — лучше фиксить вместе.
  • F58 + F55 — UX-онбординг новой ТТ. После создания ТТ на свежей франшизе пользователь упирается в несколько шагов которые нужно делать вручную с минимальной подсказкой.

Что Александр (POS-desktop) проверит после фиксов

Когда придёт сборка с фиксами F25/F27/F50/F51/F52 — Александр повторит на admin@erp.local:

  • Заказ через POS → оплата → проверка что заказ виден в «Готов к выдаче»
  • Кнопка отмены на каждом статусе
  • Свежий заказ — наверху списка
  • Выбор «наличка/карта» при оплате → корректный payment_method в БД

Когда придёт сборка с F26 — повторная card-оплата → проверка что rrn, card_last4, fiscal_data заполнены ≤ 5 сек.

Когда придёт сборка с F60 — попытка создать терминал с ФН, который уже привязан где-то ещё → ожидаемо 409 с понятным сообщением.


Источники (для расширенного контекста)

  • findings.md — полная таблица всех F-NN со статусами и вариантами серьёзности
  • sessions/2026-05-06-regression.md — API-регресс stage3
  • sessions/2026-05-06-ui-regression.md — UI-регресс под demo3 с разбором ретрактов
  • sessions/2026-05-06-pos-coordination-playbook.md — координация POS-сессии
  • sessions/2026-05-06-e2e-demo3-playbook.md — план e2e на свежей франшизе
  • zones/payments/card-payment.md — детальный разбор F26 (репро + где смотреть)
  • screenshots/2026-05-06-ui-regression/ — скриншоты UI-сессии для иллюстрации (опционально)