Практическое руководство

Как организовать фискализацию продаж фитнес-клуба

Фискальный чек должен точно повторять завершённую продажу и сохранять проверяемый статус при любой ошибке провайдера. Разбираем модель, интерфейс и порядок приёмки для клуба в Узбекистане.

01

Начните с одной завершённой продажи

Фитнес-клуб продаёт разные по смыслу позиции: абонемент, персональную тренировку, аренду шкафчика, товар бара, подарочную карту и дополнительную услугу. Для клиента это одна корзина, но бухгалтерский и налоговый смысл строк различается. Поэтому фискализация должна получать уже рассчитанную продажу, а не заново собирать её из интерфейса кассира.

Источником суммы служит завершённая sale в кассовом контуре. В ней зафиксированы строки, количество, цена, скидка, валюта, способы оплаты и касса. Чек ссылается на идентификатор этой продажи. Сотрудник может перейти от чека к операции и проверить, почему итог равен конкретной сумме.

Полезный приёмочный сценарий начинается с корзины, где есть абонемент и товар бара. Добавьте скидку, разделите оплату между наличными и сертификатом, завершите продажу и сравните итог чека со строками. Повтор команды завершения обязан вернуть прежнюю sale и прежний чек, без второй фискальной операции.

  • одна sale создаёт одно фискальное обязательство;
  • сумма чека совпадает с суммой продажи;
  • каждая строка имеет понятный источник;
  • повтор запроса не создаёт дубль;
  • связь sale и receipt доступна для сверки.

02

Создавайте чек в той же транзакции

Между фиксацией оплаты и постановкой чека в очередь не должно быть окна потери. Если sale стала paid, а процесс остановился до создания receipt, бухгалтер увидит деньги без фискального обязательства. Обратная ситуация тоже опасна: чек появился, хотя продажа откатилась из-за ошибки склада или членства.

В ProGym завершение продажи и создание фискального обязательства проходят в одной PostgreSQL-транзакции. Ошибка критичной проверки отменяет весь результат. Успешная транзакция сохраняет paid sale и pending receipt вместе. Внешний OFD вызывается позже через контролируемую очередь, поэтому медленная сеть провайдера не удерживает кассовую блокировку.

Идемпотентный ключ строится из типа источника, идентификатора sale и операции. Повтор после таймаута на кассе находит существующий receipt. Если клиент пытается использовать тот же ключ для другой продажи, Core возвращает conflict. Такая защита нужна и в интерфейсе, и в адаптере терминала.

03

Фиксируйте фискальные реквизиты до отправки

Название услуги, единица измерения, IKPU и налоговая ставка могут измениться в каталоге после продажи. Чек должен отражать состояние на момент расчёта. Поэтому ProGym сохраняет серверный fiscal snapshot в каждой строке sale и использует его при создании receipt.

Для абонемента и услуги фискальные атрибуты берутся из правил каталога. Для товара используются карточка номенклатуры, единица, IKPU и ставка налога. Клиентский запрос не может подменить зарезервированный snapshot. Это важно для кассовых интеграций, браузера и white-label приложения, где входные данные всегда считаются недоверенными.

Перед запуском бухгалтер составляет таблицу соответствий по реальному ассортименту клуба. В ней указываются категория, название в чеке, IKPU, единица и налоговая ставка. Сначала проверяются самые частые продажи, затем персональные услуги, аренда, сертификаты и редкие позиции. Ошибка каталога исправляется до production-отправки.

  • название позиции;
  • количество и единица;
  • цена и скидка;
  • IKPU и налоговая ставка;
  • тип оплаты и итоговая сумма.

04

Распределяйте общую скидку точно

Промокод или ручная скидка часто применяется ко всей корзине. OFD при этом получает сумму по каждой строке. Простое округление долей может дать расхождение в один сум или тийин. Провайдер отклонит чек либо сохранит итог, который отличается от кассовой продажи.

ProGym распределяет общую скидку пропорционально сумме строк. Каждая промежуточная доля округляется до денежной точности, а остаток применяется к последней строке. После расчёта сумма line amount обязана точно совпасть с sale total. Этот инвариант проверяется автоматическим PostgreSQL-тестом.

На приёмке используйте суммы, которые дают неудобные дроби: три позиции разной стоимости и процентную скидку. Сравните gross, line discount, распределённую global discount и итог. Отдельно проверьте корзину из одной позиции, нулевую скидку и максимально разрешённую скидку.

05

Сохраняйте способы оплаты без лишних данных

Одна продажа может быть оплачена наличными, банковской картой, Click, Payme, подарочным сертификатом или комбинацией способов. Фискальный payload хранит тип и сумму каждой части. Их общий итог сверяется с sale total и позволяет объяснить расчёт кассовой смены.

В payload не должны попадать полный номер карты, secret сертификата, промокод, customer ID или ключ провайдера. Для фискализации достаточно денежных и товарных реквизитов. Секретные значения остаются в своих доменах, а payload чека хранится зашифрованным на сервере.

При раздельной оплате проверьте сумму каждого метода, итог receipt и отчёт смены. Если сертификат покрывает часть покупки, его внутренний код не нужен OFD. Если Click или Payme ещё не подтвердили intent, sale не должна переходить в paid и создавать успешный чек.

06

Разделяйте обязательство и ответ OFD

Статус pending означает, что клуб создал обязательство и ждёт отправки. Processing показывает взятую worker-операцию. Fiscalized подтверждает ответ провайдера и содержит безопасные внешние реквизиты. Failed сообщает об ошибке с возможностью повторить, dead требует ручного разбора, cancelled фиксирует осознанную отмену.

Пользовательский интерфейс обязан показывать эти состояния текстом. Один цвет не подходит для доступности и создаёт риск неверной трактовки. В журнале ProGym видны сумма, дата, provider, число попыток и текущий статус. Карточка раскрывает строки, способы оплаты и историю доставки.

Система не показывает успешную фискализацию до ответа OFD. Если connection отсутствует, receipt остаётся в ожидании с пометкой о настройке. Такой режим позволяет заранее включить политику кассы и увидеть накопленные обязательства, сохраняя честный статус каждой операции.

  • pending: создано и ждёт отправки;
  • processing: взято адаптером;
  • fiscalized: подтверждено OFD;
  • failed или dead: требуется повтор или разбор;
  • cancelled: отменено с причиной.

07

Настраивайте OFD отдельно для каждой кассы

Сеть может использовать разные юридические лица, кассы и договоры OFD по филиалам. Политика хранится в карточке register: обязательна ли фискализация и какой provider выбран. Изменение проходит с проверкой версии, чтобы два администратора не перезаписали настройки друг друга.

Если provider указан, Core ищет активное connection с точным ключом. Случайное переключение на первый доступный OFD запрещено. При повторе receipt сохраняет исходный provider key и снова ищет именно его. Это защищает юридическую принадлежность чека после добавления нового подключения в сеть.

В staff-интерфейсе руководитель видит все кассы, флаг создания чеков и список разрешённых fiscal connections. Изменение требует права управления продажами. Provider secrets в браузер не передаются, сотрудник выбирает только публичное имя подключения.

08

Повторяйте ошибки управляемо

Внешний OFD может вернуть временную ошибку, ограничение частоты, недоступность сети или бизнес-отказ по реквизитам. Техническая ошибка получает backoff и следующую попытку. Ошибка данных переводится в состояние для ручного разбора. Бесконечный быстрый retry создаёт нагрузку и мешает увидеть первопричину.

Ручная кнопка повторения использует текущую версию receipt. Если worker уже изменил состояние, сервер вернёт conflict и предложит обновить карточку. Успешный, отменённый или находящийся в обработке чек нельзя отправить повторно. Это исключает конкурирующие команды кассира и фоновой очереди.

На FIELD-приёмке отключите sandbox OFD после создания продажи, дождитесь failed, восстановите provider и выполните retry. Проверьте число попыток, отсутствие второго receipt и финальную ссылку. Затем повторите с неверным IKPU и убедитесь, что сотрудник видит понятный отказ без ложного успеха.

09

Отмена и возврат требуют разных операций

Отмена фискального обязательства подходит для ситуации, когда receipt создан ошибочно и ещё не подтверждён провайдером. Сотрудник подтверждает действие, вводит причину, а Core сохраняет audit event. Тихое удаление строки запрещено, потому что оно разрушает связь с продажей и сверкой.

Возврат оплаченной продажи имеет другой экономический смысл. Он ссылается на исходную sale, корректирует товар, членство, ledger и платёж по согласованному правилу. Для OFD формируется отдельная операция возврата с собственным idempotency key. Успешный sale receipt не превращается задним числом в cancelled.

Пилот должен включать полный возврат, частичный возврат и отказ provider. Сумма возвратного чека сверяется с разрешёнными строками исходной продажи. Права на возврат и отмену фискального обязательства лучше разделить, а крупные суммы провести через approval.

10

Связывайте чек с кассовой сменой и сверкой

Кассовая смена отвечает за ожидаемую и фактическую сумму по методам оплаты. Fiscal journal отвечает за доставку чеков. Эти реестры связаны через sale, но имеют разные статусы. Закрытая смена не означает, что каждый provider уже ответил, а fiscalized receipt не заменяет подтверждение денег банком.

Перед закрытием дня сотрудник проверяет paid sales, возвраты, наличные движения и чеки с проблемным статусом. Бухгалтер сверяет provider reports с payment records и fiscal receipts. Drill-down позволяет перейти от итоговой суммы к конкретной sale и её строкам.

Для операционного закрытия месяца полезен автоматический чек-лист. Он показывает открытые смены, незавершённые продажи, failed receipts, неполные EDO-пакеты и расхождения склада. Каждый пункт содержит источник и количество, поэтому команда исправляет конкретные операции, а не отмечает абстрактную готовность.

11

Ограничивайте доступ и защищайте payload

Просмотр журнала, чтение payload, retry, cancel и настройка register являются разными действиями. Product manifest ProGym подключает общий fiscal operator и точные scope. Соседние продукты Taska Core не получают новые возможности автоматически. Такое разделение сохраняет совместимость OneSwim, Cline, Restora и Oborot.

Организация и tenant применяются на сервере. BFF пропускает только разрешённый root fiscal и блокирует произвольный путь к raw provider. CSRF защищает изменения из браузера, If-Match защищает конкурентное редактирование, audit фиксирует автора. Frontend не получает connection credentials и зашифрованный storage value.

Логи не должны печатать payload целиком. Для диагностики достаточно request ID, receipt ID, provider, error code и безопасного сообщения. Экспорт журнала подчиняется тем же правам, что экран. Срок хранения согласуется с бухгалтером и юридической политикой клуба.

12

Проведите sandbox и FIELD-приёмку

Программный контракт можно проверить локально с mock provider и реальной PostgreSQL. Такой тест подтверждает атомарность, точную сумму, распределение скидки, provider binding, шифрование payload, replay и отсутствие секретов. Он не подтверждает юридическую регистрацию чека у выбранного OFD.

Для FIELD-этапа клуб предоставляет договор, данные кассы, sandbox credentials и требования к чеку. Команда подключает connection, настраивает одну register и выполняет продажу услуги, товара и смешанной корзины. Проверяются успешный ответ, QR или URL чека, номер, fiscal sign и отображение в кабинете провайдера.

Затем выполняются отрицательные сценарии: неверный реквизит, недоступность provider, повтор webhook, retry после восстановления, возврат и сверка дня. Результат фиксируется по идентификаторам sale и receipt, без публикации ключей. Только после этого конкретное подключение получает production-статус.

ProGym поставляется с field acceptance runner. Он проверяет доступность канонических контуров СКУД, оплат, fiscal, EDO и mobile distribution, а также отрицательные webhook и BFF gates. Provider-specific доказательства добавляются к отчёту пилота после подключения реальных аккаунтов.

  • успешная продажа и чек в OFD;
  • смешанная корзина и раздельная оплата;
  • ошибка provider и контролируемый retry;
  • полный и частичный возврат;
  • сверка sale, payment, shift и receipt.

Демонстрация

Проверьте сценарии на примере своего клуба

Подготовим роли, филиал, расписание и путь клиента. Покажем работающие функции в системе.

Покажем CRM, расписание, кассу, СКУД и приложения на сценарии вашего клуба.