Багфиксы — стадия 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_price | 200, тихо игнор → правильный путь PATCH /price-lists/{id}/items |
PATCH /catalog/products/{id} | modifier_group_ids | 200, тихо игнор → правильный путь POST /products/{id}/modifiers |
POST /modifier-groups | type | принимает любую строку без enum-валидации |
PATCH /stores/{id} | is_published | 200, тихо игнор → правильный путь POST /stores/{id}/publish |
PATCH /price-lists/{id} | stores/store_ids | 200, тихо игнор (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-регресс stage3sessions/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-сессии для иллюстрации (опционально)