Зачем я в это полез
Задача звучала невинно: показать в мобильном приложении остаток по подарочному сертификату и историю его движений. Механизм в 1С уже работал, карты продавались, всё крутилось — нужно было просто прочитать данные.
«Просто прочитать» заняло два дня. Штатный механизм подарочных сертификатов УТ 11.5 размазан по трём регистрам, реквизит с номером карты называется не так, как ожидаешь, а расход по сертификату пишет вообще не тот документ, которым его погасили.
Ниже — разбор того, где что лежит, как это читать запросом и какие ловушки ждут по дороге. Всё проверено на боевой базе розничной сети, где механизм в обороте больше полугода.
Общая картина
Подарочный сертификат в УТ 11.5 — это элемент справочника плюс записи в трёх регистрах:
| Что нужно узнать | Где лежит |
|---|---|
| Сама карта, её номер, вид | Справочник.ПодарочныеСертификаты |
| Остаток денег на карте | РегистрНакопления.ПодарочныеСертификаты |
| Продан или нет, текущий статус | РегистрСведений.ИсторияПодарочныхСертификатов |
| Срок действия конкретной карты | РегистрСведений.АктивацияПодарочныхСертификатов |
Ни один из них не заменяет остальные. Чтобы построить корректный реестр карт с остатками, статусами и сроками, надо соединять все четыре источника.
Вид сертификата: где настраивается поведение
Справочник.ВидыПодарочныхСертификатов задаёт номинал и правила. Практически значимая настройка здесь одна, и о ней стоит знать заранее: разрешена ли частичная оплата.
Если частичная оплата выключена, сертификат тратится за один раз целиком — остаток после покупки на меньшую сумму не сохраняется, разница сгорает. Если включена, карта работает как кошелёк с уменьшающимся балансом.
В моей базе это настроено по-разному для разных номиналов: у самого мелкого частичная оплата отключена осознанно, у остальных включена. Так что не считайте, что механизм ведёт себя одинаково для всех видов — проверяйте настройку на каждом.
Номер карты — это Штрихкод
Первая ловушка, простая и обидная.
Номер, который напечатан на пластиковой карте и который кассир вводит на кассе, лежит в реквизите Штрихкод справочника. Не в Код, не в Номер, не в СерийныйНомер.
Наименование при этом собирается платформой автоматически из вида и серийного номера — получается что-то вроде «Сертификат 3000 3043». Искать карту по наименованию можно, но это строковый поиск по собранному значению, ненадёжно.
Ищите по Штрихкод.
Остаток: регистр накопления
РегистрНакопления.ПодарочныеСертификаты — обычный остаточный регистр:
- измерение —
ПодарочныйСертификат(ссылка на справочник); - ресурс —
Сумма.
Остаток на дату берётся стандартно, через виртуальную таблицу остатков:
ВЫБРАТЬ
Остатки.ПодарочныйСертификат КАК Сертификат,
Остатки.СуммаОстаток КАК Остаток
ИЗ
РегистрНакопления.ПодарочныеСертификаты.Остатки(&НаДату, ) КАК Остатки
Суммарный остаток по всем картам — это, по сути, размер ваших обязательств перед покупателями: деньги получены, товар не отгружен. Полезная цифра для бухгалтерии, которую мало кто считает.
Статус: периодический регистр сведений
РегистрСведений.ИсторияПодарочныхСертификатов — периодический, регистратором. Хранит смену состояний карты по жизненному циклу.
Читается срезом последних:
ВЫБРАТЬ
История.ПодарочныйСертификат,
История.Статус
ИЗ
РегистрСведений.ИсторияПодарочныхСертификатов.СрезПоследних(&НаДату, )
КАК История
Ключевой нюанс: отсутствие записи — это тоже информация. Если по карте нет ни одной записи в истории, значит её не продавали. Карта существует в справочнике, номер выпущен, но в оборот она не поступала.
При построении реестра это надо обрабатывать явно: соединять справочник с историей левым соединением и трактовать NULL как «выпущена, не продана». Внутреннее соединение молча выкинет все невыпущенные карты, и вы получите неполный реестр, не заметив этого.
Срок действия: непериодический регистр сведений
РегистрСведений.АктивацияПодарочныхСертификатов — непериодический, одна запись на карту. Поля:
ДатаНачалаДействияДатаОкончанияДействия
Важно, что срок задаётся на каждую карту отдельно, а не на вид. Две карты одного вида, проданные в разные дни, будут иметь разные сроки.
Отдельное наблюдение из практики: у бессрочных видов дата окончания уезжает далеко в будущее — в моей базе это 3025 год. То есть «бессрочный» реализован не пустым значением, а датой за горизонтом. Если вы фильтруете действующие карты условием «дата окончания больше текущей», бессрочные пройдут корректно. А вот если где-то в отчёте вы показываете эту дату пользователю — он увидит тысячелетие, и это будет выглядеть как баг.
Выпуск карт кодом
Создавать сертификаты руками через СоздатьЭлемент() не нужно и вредно — обязательные реквизиты, которые заполняет форма, останутся пустыми, и карта окажется нерабочей.
Для выпуска есть типовой серверный метод:
Параметры = Новый Структура;
Параметры.Вставить("Штрихкод", НомерКарты);
Параметры.Вставить("МагнитныйКод", МагнитныйКод);
Параметры.Вставить("ВидПодарочногоСертификата", ВидСсылка);
Параметры.Вставить("СерийныйНомер", СерийныйНомер);
Сертификат = ПодарочныеСертификатыСервер.ЗарегистрироватьПодарочныйСертификат(Параметры);
Наименование метод собирает сам, задавать его не нужно.
Практический совет, который я применяю ко всем массовым операциям: делайте выпуск с сухим прогоном по умолчанию. Мой HTTP-метод выпуска карт по умолчанию только считает и показывает, что будет создано, а реальная запись включается отдельным флагом apply. Ошибиться в диапазоне номеров легко, а удалять сотню лишних карт из справочника — занятие на вечер.
Главная ловушка: расход пишет не чек
Вот это стоило мне больше всего времени, и это самое неочевидное во всём механизме.
Логика подсказывает: покупатель погасил сертификат на кассе, значит движение расхода по регистру накопления сделал документ ЧекККМ.
Нет. Расход по регистру ПодарочныеСертификаты пишет ОтчетОРозничныхПродажах — то есть документ закрытия смены, а не сам чек.
Из этого следуют два практических вывода:
Первый. До закрытия смены остаток по карте в регистре не изменится. Если вы строите онлайн-сервис, показывающий баланс сертификата, то между погашением и закрытием смены он будет врать. У меня это выражалось в жалобе «купил на сертификат, а в приложении деньги всё ещё на месте».
Второй, более важный. Если вам нужно связать погашение с конкретным чеком — а это нужно почти всегда, для сверки и для аналитики, — то через регистратор регистра накопления вы этого не сделаете. Регистратор укажет на отчёт о розничных продажах, в котором сотни чеков.
Единственный способ привязать погашение к чеку — табличная часть ЧекККМ.ПодарочныеСертификаты. Именно там лежит, какой сертификат и на какую сумму использован в этом конкретном чеке.
Схема, к которой я пришёл для полной картины движений:
- приход и общий расход — из регистра накопления, с регистратором;
- привязка расхода к чеку — из табличных частей чеков ККМ;
- сопоставление между ними — по сертификату и сумме внутри смены.
Как это выглядит на практике
У меня поверх этого построена синхронизация с внешней CRM: раз в пятнадцать минут сервис забирает через HTTP-метод расширения реестр карт и историю движений, складывает зеркало в свою базу и считает баланс нарастающим итогом.
Что дала первая же сверка на боевых данных: реестр карт, история движений и привязки к чекам сошлись полностью, расхождений не было. Но это результат того, что схема выше была выстроена правильно со второй попытки. Первая версия читала расход по регистратору и не могла объяснить, к каким чекам относятся погашения.
Грабли запроса, которые стоили двух перезаливок
Три вещи, на которых я споткнулся, собирая запрос по этой предметной области. Они не про сертификаты, но встретятся вам в том же коде.
1. У складов и касс нет кода. У справочников Склады и КассыККМ длина кода нулевая, поэтому обращение Ссылка.Склад.Код в запросе даёт «Поле не найдено». В объектной модели то же обращение молча возвращает пустую строку. Магазин надо матчить по наименованию или GUID.
2. Продавец — реквизит табличной части «Товары», а не шапки чека. В шапке есть только Кассир. По выгруженному XML это неразличимо: разметка реквизита шапки и реквизита табличной части выглядит одинаково.
3. ВЫБРАТЬ ПЕРВЫЕ &Лимит не работает. Число должно быть литералом, параметр туда не подставляется. Собирайте конкатенацией через Формат(Лимит, "ЧГ=0") — и убедитесь, что лимит действительно число, а не пришедшая снаружи строка.
Итоговая схема чтения
Собирая всё вместе, корректный реестр сертификатов с остатками и статусами строится так:
- Взять
Справочник.ПодарочныеСертификатыкак основу — это полный список выпущенных карт. - Левым соединением подтянуть
ИсторияПодарочныхСертификатов.СрезПоследних— отсутствие записи означает «выпущена, не продана». - Левым соединением подтянуть остатки из
РегистрНакопления.ПодарочныеСертификаты.Остатки. - Левым соединением подтянуть
АктивацияПодарочныхСертификатовза сроками действия. - Для истории движений — обороты регистра накопления, а привязку к чекам добирать отдельно из табличных частей
ЧекККМ.
Все соединения именно левые. Любое внутреннее незаметно отрежет часть карт, и вы этого не увидите, пока кто-нибудь не спросит про конкретный номер.
Выводы
Штатный механизм подарочных сертификатов УТ 11.5 рабочий и продуманный, но его модель данных неочевидна в трёх местах, и каждое из них способно испортить отчёт:
- Номер карты — это
Штрихкод, не код и не наименование. - Отсутствие записи в истории — значимое состояние, а не пропуск данных.
- Расход пишет отчёт о розничных продажах, а не чек. Привязка к чеку живёт только в табличной части
ЧекККМ.ПодарочныеСертификаты.
Если держать эти три вещи в голове, всё остальное собирается за час. Если не держать — получится отчёт, который выглядит правдоподобно и врёт, а это худший вид отчёта.
Платформа 8.3.27, УТ 11.5. Разбор сделан по боевой базе розничной сети, где механизм в обороте более полугода.
Вступайте в нашу телеграмм-группу Инфостарт