Учёт абонементов: посещения, остаток и отмена списания
Как разработать учёт абонементов: пакет посещений, срок действия, списание, отмена, заморозка, остаток и проверка спорных операций.
Автор: Александр Поркулевич, CEO и основатель, 404gen.
Учёт абонементов связывает выданный пакет услуг, фактическое использование, срок действия и историю исправлений. Остаток должен объясняться конкретными движениями, а повторное нажатие не должно списывать ещё одно посещение. Поэтому задача шире поля «осталось занятий»: рабочая система различает бронирование, состоявшийся визит, списание и отмену операции.
Статья предназначена заказчику сервиса для студии, клуба, образовательной программы или другой компании с пакетами посещений. Рассматриваем операционную модель, а не расчёт платежей и не юридические правила возврата. Условия продажи, заморозки и восстановления утверждает компания. Учебные примеры не описывают действующие тарифы или результаты клиента.
Определить, что продаётся в пакете
Абонемент может включать определённое число посещений, доступ на период, набор разных услуг или сочетание ограничений. Эти варианты требуют разной модели. «Десять занятий» не равно «месяц безлимитного доступа», а пакет на группу не обязательно подходит для индивидуальной встречи. Перед разработкой перечисляют допустимые услуги, единицы, места использования и основания списания.
Шаблон абонемента описывает условия нового пакета, а выданный экземпляр сохраняет условия конкретного клиента. Изменение шаблона не должно автоматически переписывать уже выданные права. Если компания меняет действующие условия, это отдельный управляемый процесс. Иначе клиент увидит другой остаток или срок без понятного основания, а поддержка не сможет восстановить историю.
Для первой версии лучше выбрать один тип пакета и явные ограничения. Не нужно сразу поддерживать семейные балансы, перенос между филиалами, составные услуги и сложные бонусы, если основной маршрут списания ещё не проверен. Каждый дополнительный вариант увеличивает число сочетаний в приёмке. Оценка объёма строится по правилам продукта, а не по числу экранов.
Выданный абонемент и журнал движений
У экземпляра есть устойчивый номер, владелец, условия, дата активации, срок и состояние. Движение показывает выдачу, использование, восстановление, корректировку или другое разрешённое изменение. Текущий остаток получается из этих операций по согласованной формуле. Если хранить только изменяемое число, невозможно понять, кто и почему исправил значение вчера.
Журнал не должен состоять из свободных комментариев к текущему остатку. У движения есть тип, величина, основание, время, исполнитель и связь с исходной операцией. При отмене списания создаётся обратное движение, а не удаляется первая строка. Так сохраняется причинная цепочка: посещение учли, затем обнаружили ошибку и восстановили право в определённом объёме.
Для баланса задают единицу. Посещения обычно целые, но занятия разной длительности могут расходовать условные единицы или минуты. Нельзя смешивать их без явной модели. Если одна услуга стоит две единицы, пользователь видит это до подтверждения. Необъяснимое уменьшение остатка выглядит как ошибка, даже если внутренняя формула разработчика технически верна.
Начало действия и дата окончания
Срок может начинаться при покупке, первом посещении или ручной активации. Выбор должен быть записан в условиях экземпляра. Пустая дата активации не означает бессрочность. Для неактивированного пакета могут действовать отдельные ограничения, которые компания определяет заранее. Интерфейс показывает понятный статус и условие начала, а не только серую кнопку.
Окончание требует договориться о границах: включает ли указанная дата весь день площадки, какой часовой пояс используется и что происходит с занятием, которое началось до границы, а закончилось после. Это продуктовые правила, а не мелкая техническая деталь. Автоматическое сравнение с полуночью сервера может дать другой результат, чем ожидает клиент на площадке.
Изменение срока сохраняют отдельным событием с причиной. Администратор не должен просто переписать дату без объяснения. При миграции старых пакетов полезно обозначить восстановленный источник и ограничения данных. Если неизвестно, когда началось действие, нельзя выдумывать точную дату ради заполненной карточки. Для таких записей нужен управляемый маршрут уточнения.
Запись на занятие и резерв права
Бронирование места и расходование абонемента связаны, но могут происходить в разное время. Одни компании списывают при записи, другие после посещения, третьи резервируют единицу до наступления события. Выбор влияет на доступный остаток и отмену. Пользователь должен видеть, сколько прав свободно, сколько зарезервировано и какие операции уже окончательно учтены.
Если резерв предусмотрен, он имеет ссылку на запись и срок. Отмена бронирования освобождает соответствующий резерв по правилам компании. Нельзя уменьшать общий остаток и затем пытаться понять по косвенным признакам, было ли это списание или временная блокировка. Раздельные состояния упрощают объяснение спорного случая и защиту от повторной операции.
Одновременные записи с разных устройств требуют серверной проверки. Два экрана могут видеть одну последнюю доступную единицу. Рабочая реализация должна гарантировать, что согласованное ограничение не нарушается конкурентными запросами. Отключение кнопки после нажатия полезно, но не защищает от второго устройства, повтора сети или обращения к API напрямую.
Основание списания посещения
Списание связывают с фактом, который компания считает достаточным: подтверждённый визит, завершённое занятие или определённый статус записи. Сигнал открытия приложения или входа в здание может не доказывать оказание конкретной услуги. До интеграции нужно назвать источник и владельца этого факта. Если автоматического источника нет, операцию выполняет сотрудник с определёнными полномочиями.
Перед подтверждением показывают пакет, услугу, дату и величину расхода. Если у клиента несколько подходящих абонементов, задают правило выбора: явный выбор, ближайший срок или другой утверждённый приоритет. Автоматически списывать с первого найденного пакета опасно. Пользователь и администратор должны понимать основание, особенно если разные пакеты имеют разные ограничения.
У каждой операции устойчивый идентификатор. Повтор запроса после задержки возвращает уже принятое списание, а не уменьшает остаток снова. Если одно посещение может использовать несколько единиц, это одна операция с известной величиной, а не серия необъяснимых нажатий. История позволяет найти действие по записи на занятие и восстановить результат независимо от интерфейса.
Отмена и восстановление
Отмена записи, отмена посещения и восстановление списания различаются. Например, поздняя отмена по условиям компании может не возвращать единицу. Статья не определяет допустимость такого условия — это отдельное договорное решение. Система должна реализовать согласованное правило и объяснить его пользователю, а не скрывать за одинаковой кнопкой все возможные последствия.
Восстановление связывается с исходным движением. Повторная попытка не создаёт второй возврат. Если частичное восстановление допустимо, суммарная величина не превышает разрешённый предел. Причина и исполнитель сохраняются. Редактирование исходного расхода вместо обратной операции уничтожает историю и затрудняет проверку того, как получился сегодняшний остаток.
Важен случай, когда срок пакета уже закончился к моменту исправления. Восстановленная единица может быть недоступна без отдельного продления, либо компания предусматривает специальный порядок. Это правило нужно определить заранее. Нельзя просто увеличить число на закрытом абонементе и считать вопрос решённым: клиент увидит остаток, которым не может воспользоваться.
Заморозка без двусмысленного срока
Заморозка может приостанавливать доступ, продлевать срок или только запрещать новые записи. Эти эффекты не следуют автоматически из названия. Опишите допустимые даты, количество периодов, минимальную длительность и последствия для уже забронированных занятий. Для первой версии иногда разумнее оставить ручное согласованное продление, чем реализовать неясное правило заморозки.
Периоды заморозки проверяют на пересечение и границы. Если два согласованных интервала накладываются, продление не должно дважды учитывать одни и те же дни. При досрочном возобновлении сохраняют исходный запрос и фактическое завершение. История помогает объяснить изменение окончания, а не только показать сегодняшнюю дату без основания.
Перенос существующих записей на время заморозки требует отдельного решения. Можно запретить заморозку при активных бронированиях или предложить сначала их отменить. Нельзя незаметно удалить занятия пользователя. Интерфейс показывает затронутые записи до подтверждения, а сервер проверяет, не изменился ли их состав за время открытой формы.
Состояния абонемента
| Состояние | Что видит клиент | Что проверить |
|---|---|---|
| Ожидает активации | Условие начала использования | Основание и предельную дату активации |
| Действует | Доступный остаток и ограничения | Срок, услугу и текущие резервы |
| Приостановлен | Период и последствия заморозки | Новые записи и уже созданные бронирования |
| Исчерпан | Историю расхода | Отличие от окончания срока |
| Срок завершён | Причину недоступности | Правило исправлений и восстановления |
Одно слово «закрыт» скрывает несколько разных причин. Пакет может закончиться по сроку при ненулевом остатке или быть полностью использован раньше. Для поддержки важна конкретная причина недоступности. В интерфейсе полезно одновременно показывать срок, свободные единицы и зарезервированные посещения, если резерв используется. Пользователь не должен вычислять это по длинному журналу.
Модель состояний не обязана храниться одним полем. Часть признаков вычисляется из условий и движений. Существенно, чтобы правила вычисления были едиными для кабинета, администратора, мобильного приложения и API. Разные локальные формулы приводят к ситуации, когда один экран разрешает запись, а другой показывает истёкший пакет.
Личный кабинет клиента
Главный экран отвечает на три вопроса: чем можно воспользоваться, до какого срока и почему изменился остаток. История использует человеческие названия услуг и ссылки на связанные записи. Технический код движения можно оставить в деталях. Рядом с неоднозначной операцией должен быть понятный путь обращения, чтобы клиенту не приходилось пересказывать весь список по телефону.
Не стоит выдавать локально показанное число за подтверждённый серверный баланс при недоступной сети. Можно отображать последнее известное состояние с моментом обновления. Новая запись требует проверки по действующим правилам. Если приложение принимает намерение в очередь, оно явно обозначается как неподтверждённое, а не как состоявшееся списание или гарантированное место.
При нескольких пакетах полезна компактная сводка отличий: услуга, площадка, срок и доступные единицы. Красивые одинаковые карточки без условий заставляют угадывать, какой выбрать. Проверяют длинные названия и увеличенный шрифт на телефоне. Действия отмены и продления должны быть различимы; случайное нажатие не должно сразу изменять важное состояние без понятного подтверждения.
Рабочее место администратора
Администратору нужен поиск клиента в разрешённой области, доступные пакеты и основание предполагаемого списания. История располагается рядом с текущим состоянием. Если клиент оспаривает посещение, сотрудник находит связанную запись, время и исполнителя. Поле «исправить остаток» без причины можно заменить отдельными типами корректировок с ограниченными полномочиями.
Массовые операции требуют отчёта до и после выполнения. Например, продление пакетов из-за закрытия площадки должно иметь известную область и не применяться повторно при следующем запуске. Сначала показывают затронутые записи и исключения, затем фиксируют операцию с устойчивым номером. Это удобнее и безопаснее ручного редактирования сотен дат по очереди.
Смена сотрудника не должна менять авторство старых движений. Права выдачи, списания, восстановления и изменения условий могут различаться. Проверки выполняются на сервере по конкретному пакету и филиалу. Возможность открыть карточку клиента не даёт автоматически права восстанавливать любое посещение. Отдельно определяют, кто рассматривает спорные случаи и подтверждает исключения.
Целостность и интеграции
Операции с балансом выполняют с согласованной защитой от конкурентных изменений. В зависимости от модели используют транзакции, ограничения и проверку версии. Документация PostgreSQL описывает уровни изоляции и структурные ограничения, но выбор конкретного механизма проверяют на нагрузке и бизнес-инвариантах. Важен результат: нельзя подтвердить расход, нарушающий утверждённые правила доступного остатка.
Если данные приходят из кассы, расписания или внешней CRM, назначают владельца каждого события. Платёж может выдать пакет, а посещение — изменить его использование. Эти события не должны независимо создавать два одинаковых экземпляра. Таблица связей хранит внешние ключи и результат обработки. При повторе обмена система возвращает известную операцию вместо нового начисления.
Отмена платежа и операционное закрытие абонемента требуют отдельного согласования. Нельзя автоматически решить финансовый вопрос одной правкой числа посещений. Если взаиморасчёты находятся вне первой версии, интерфейс честно показывает эту границу. Поддержка должна знать, где находится окончательное решение, а не считать, что любой статус кабинета подтверждён бухгалтерской системой.
Перенос исходных пакетов
Перед миграцией соберите источник условий и остатков, правила дат и известные корректировки. Если доступны только сегодняшние числа, их можно перенести как начальное состояние с отмеченным происхождением, не выдумывая историю посещений. В отчёте сохраняют строку исходной таблицы и номер созданного пакета, чтобы спорное значение можно было проверить.
Пробная загрузка выявляет дубли клиентов, разные единицы и неоднозначные даты. Совпадение имени не является достаточным ключом объединения. Повторный запуск не должен повторно выдать тот же абонемент. Несопоставленные записи помещаются в отчёт, а не получают случайного владельца. Для массового перехода нужен план заморозки старого ввода или учёта изменений после исходного среза.
После загрузки сравнивают не только общую сумму единиц, но и конкретные экземпляры, сроки и ограничения. Два ошибочных переноса могут дать правильный общий итог. Полезна выборка спорных случаев и подтверждение владельцем процесса. Пользователям объясняют, откуда взялось начальное состояние и как сообщить о расхождении, не обещая, что старая история восстановлена полностью.
Приёмка по движениям
Подготовьте пакет с небольшим числом единиц и вручную рассчитайте последовательность: выдача, резерв, списание, отмена, повтор отмены, окончание срока. Затем добавьте конкурентные запросы на последнюю единицу и потерю ответа после списания. Для каждого шага проверяют остаток, историю и доступность следующего действия. Скриншот числа в карточке не доказывает целостность процесса.
Отдельно проверяют заморозку с активной записью, восстановление после окончания срока, смену филиала и пакет с неподходящей услугой. Ошибка должна объяснять причину и допустимое продолжение. Если сервер отклонил операцию, интерфейс не сохраняет видимость успешного расхода. После обновления страницы результат должен совпадать с принятой сервером историей.
Пилот ведут на ограниченной группе с возможностью ручной сверки. Смотрят число спорных корректировок, время поиска основания и расхождения между журналом и расписанием. Уменьшение жалоб полезно только при доступном канале обращения. Рост числа списаний не является самостоятельным показателем пользы разработки: он может отражать сезонность или изменение количества клиентов.
Перед завершением пилота проверьте объяснимость без разработчика. Администратор должен по номеру пакета найти начальные условия, конкретный расход, обратную операцию и итоговый остаток. Если для ответа клиенту приходится исследовать техническую базу вручную, рабочий интерфейс ещё не закрыл задачу поддержки, даже когда арифметика верна.
Частые вопросы
Можно ли хранить только остаток?
Для простой демонстрации можно, но рабочий процесс требует объяснения изменений. Журнал движений позволяет восстановить основание и безопасно оформить исправление.
Когда списывать посещение?
В момент, установленный условиями компании: запись, подтверждённый визит или другой факт. Программа не должна выбирать это правило случайно.
Отмена записи всегда возвращает единицу?
Нет, последствия определяются утверждёнными условиями. Система различает отмену бронирования и восстановление уже учтённого расхода.
Нужна ли заморозка в первой версии?
Только если её эффекты и границы описаны. При редких исключениях управляемая ручная корректировка может быть разумной начальной границей.
Как не допустить двойного списания?
Связать операцию с устойчивым ключом и защищать изменение на сервере. Блокировка кнопки полезна для интерфейса, но не заменяет проверку повторов и конкурентных запросов.
Пример и разработка
Концепт «Серия» показывает вымышленный пакет, однократное списание и восстановление с историей. Платежи и настоящий баланс не подключены. Рабочее решение можно обсудить в рамках веб-приложения или SaaS. Полезные соседние темы: безопасные повторы операций и история действий.
Технические источники
Механизмы согласованного изменения данных описаны в PostgreSQL: Transaction Isolation, структурные гарантии — в PostgreSQL: Constraints. Условия пакетов, порядок заморозки и основания возвратов этими документами не определяются; их отдельно утверждает владелец продукта.
404gen — креативное digital-агентство полного цикла, работаем удалённо по всей России.
Телефон: +7 993 729-59-59 (пн–пт, 10:00–19:00 МСК). WhatsApp: +7 913 141-20-96. Telegram: @aporkulevich. Почта: ap@404gen.ru.