Бизнес-день¶
Бизнес-день (операционный день) — это учётная дата, к которой привязываются все денежные операции балльного леджера: начисления, списания, переводы, обмены и сгорания. Штамп бизнес-дня фиксирует, в рамках какого операционного периода была проведена операция, и служит границей для дневной отчётности и корректировок.
В каждый момент времени открыт ровно один текущий бизнес-день. По завершении операционных суток оператор закрывает текущий день — при этом автоматически открывается следующий. Закрытый день переоткрыть нельзя: исправление операции прошлого дня проводится встречной операцией текущим днём (см. «Исправление операции прошлого дня»).
Жизненный цикл операционного дня¶
stateDiagram-v2
[*] --> OPEN: открыт текущий день
OPEN --> CLOSED: закрытие (открывается следующий день)
CLOSED --> [*]
| Статус | Значение |
|---|---|
OPEN |
Текущий операционный день. Новые операции получают штамп этого дня. |
CLOSED |
День закрыт окончательно. Новые операции получают штамп следующего дня. |
Один открытый день
Закрытие текущего дня атомарно открывает следующий, поэтому система всегда располагает ровно одним открытым операционным днём. Отдельная операция «открыть день» не требуется.
Это не соглашение, а ограничение базы данных: второй открытый день отвергается уникальным индексом. Учётная дата операции читается как «единственный открытый день», и при нарушении инварианта операция завершится ошибкой, а не тихо возьмёт день наугад.
Контур без открытого дня
Если открытого дня нет вовсе (справочник рабочих дней пуст), денежная операция не отклоняется:
она датируется календарной датой в поясе программы (program.timezone, см.
«Конфигурация» → «Часовой пояс программы»). Пояс берётся
из переопределения в операционной конфигурации, иначе из LOYALTY_PROGRAM_TIMEZONE, иначе
Europe/Moscow. Тем же поясом датируется день, который заводится первым обращением, — например,
первым закрытием на пустом контуре: около полуночи операция и заведённый день не оказываются в разных
сутках. Учтите, что первое закрытие на пустом контуре заводит сегодняшний день, сразу закрывает его и
открывает следующий.
Текущий день и недавняя история¶
GET /v1/business-day возвращает текущий операционный день и список недавних дней (последние 14)
для контроля непрерывности учёта.
{
"current": {
"businessDate": "2026-06-25",
"status": "OPEN",
"openedAt": "2026-06-25T00:05:00+03:00",
"closedAt": null
},
"recent": [
{
"businessDate": "2026-06-24",
"status": "CLOSED",
"openedAt": "2026-06-24T00:05:00+03:00",
"closedAt": "2026-06-25T00:05:00+03:00"
}
]
}
| Поле | Описание |
|---|---|
businessDate |
Учётная дата операционного дня. |
status |
Статус дня (OPEN / CLOSED). |
openedAt |
Момент открытия дня (с таймзоной). |
closedAt |
Момент закрытия; null для открытого дня. |
Закрытие дня¶
POST /v1/business-day/close закрывает текущий операционный день и открывает следующий. В ответе
возвращается представление только что закрытого дня.
Закрытие необратимо
После закрытия дня новые операции получают штамп уже следующего дня. Вернуть контур в закрытый день нельзя.
Исправление операции прошлого дня¶
Ошибка, допущенная в закрытом дне, исправляется встречной операцией текущим днём — ручным списанием или начислением, — а не изменением прошлого. Проводки неизменяемы: история не переписывается, а дополняется.
Что это значит на практике:
- учётная дата исправления — сегодняшний открытый день, а не дата ошибки;
- дневной отчёт закрытого дня остаётся ровно таким, каким его сдали;
- в причине операции указывается, что именно исправляется, — по ней исправление связывается с исходной операцией при разборе.
Почему не переоткрытие
Раньше существовала операция переоткрытия дня. Она не решала задачу: программа лояльности работает круглосуточно, и «возврат» контура в прошлый день означал бы, что прошлую дату получает вся живая выручка — покупки, начисления внешнего движка правил, команды из очереди приёма. Так же устроены и другие платформы лояльности: исправление проводится текущей датой, а дата исходной операции служит лишь параметром расчёта.
Дневной отчёт¶
GET /v1/business-day/{date}/summary формирует сводный отчёт по операциям за указанный
операционный день.
{
"businessDate": "2026-06-24",
"accruedMinor": 1540000,
"redeemedMinor": 320000,
"operations": 1287
}
| Поле | Описание |
|---|---|
businessDate |
Учётная дата отчётного дня. |
accruedMinor |
Суммарно начислено за день, в минимальных единицах баллов. |
redeemedMinor |
Суммарно списано за день, в минимальных единицах баллов: траты клиентов вместе со сгоранием баллов (плановым и ручным). Сторно начислений (например, при возврате по чеку) и корректировки оператора сюда не входят. |
operations |
Общее число записей журнала бонусных операций, отнесённых к этому дню. |
Сгорание в дневном отчёте не отделяется
В отличие от сводки мониторинга и отчётов, где сгоревшие баллы показываются отдельно от трат
клиентов, дневной отчёт и сверка итогов с внешней системой по-прежнему включают сгорание в
redeemedMinor: эти величины сравниваются с итогами внешней системы, и их трактовка — часть
интеграционного контракта сверки.
Минимальные единицы
Суммы accruedMinor и redeemedMinor выражены в минимальных единицах баллов (minor units),
что исключает погрешности округления при агрегировании.
Та же выборка — в сверке итогов
Собственные итоги дня для приёма итогов внешней системы (см. «Сверка итогов и контроль целостности») считаются той же выборкой, что и дневной отчёт: расхождение с контрагентом не может объясняться тем, что отчёт и сверка смотрят на разные данные.
Операционный день есть у каждой записи журнала операций
Учётная дата хранится не только у проводки леджера, но и у каждой записи журнала бонусных операций, по которому считаются дневной отчёт и сверка итогов. Записи, сделанные до появления этой колонки, заполнены задним числом — днём связанной проводки, а если связи нет — календарной датой в UTC; новые записи без явно указанного дня получают текущий операционный день. Поэтому старые операции больше не выпадают из отчёта и сверки за свой день.
Эндпоинты и доступ¶
| Метод и путь | Назначение | Требуемая роль |
|---|---|---|
GET /v1/business-day |
Текущий день и недавняя история | LOYALTY_MONITORING_READ |
POST /v1/business-day/close |
Закрыть текущий день, открыть следующий | LOYALTY_BUSINESS_DAY_WRITE |
GET /v1/business-day/{date}/summary |
Дневной отчёт по операциям | LOYALTY_MONITORING_READ |
Безопасность
Все эндпоинты защищены OAuth2 (resource server на Keycloak, JWT) с принципом deny-by-default. Чтение текущего дня и отчётов доступно роли мониторинга, а закрытие дня — отдельной роли управления бизнес-днём.