В современной разработке на платформе 1С:Предприятие 8.3, особенно в решениях для розничной торговли вроде 1С:Касса или 1С:Розница, разработчики часто сталкиваются с необходимостью интеграции с внешним оборудованием. Основная сложность заключается в том, что большинство методов работы с драйверами торгового оборудования являются асинхронными. Если перед нами стоит задача провести оплату одного чека сразу через несколько платежных терминалов (например, разделение суммы), стандартный цикл Для каждого не сработает корректно. Асинхронные вызовы «выстрелят» одновременно, создав хаос в банковских операциях, а отсутствие контроля за результатом сделает невозможным откат успешных платежей в случае ошибки на одном из этапов.
В этой статье мы подробно разберем, как организовать строгую очередь операций и реализовать механизм программной транзакционности (компенсации) для внешних устройств.
Рассмотрим стандартную ситуацию: у нас есть таблица значений с перечнем терминалов и суммами. В управляемом приложении вызов метода печати чека или оплаты по карте требует использования ОписаниеОповещения. Если мы запустим обычный цикл, платформа инициирует все вызовы практически мгновенно, не дожидаясь ответа от оборудования. В результате:
Проанализируем наиболее эффективные способы решения этой задачи.
Это классический подход для клиентской части 1С, позволяющий имитировать синхронную работу в асинхронной среде. Вместо цикла мы используем процедуру, которая вызывает сама себя по цепочке.
Разберем этот метод по шагам:
Рассмотрим пример кода:
&НаКлиенте
Процедура НачатьОбработкуОчереди()
ДанныеОчереди = ПолучитьСписокТерминалов(); // ТЗ или Массив
ОбработатьСледующийЭлемент(0, ДанныеОчереди);
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьСледующийЭлемент(Индекс, ДанныеОчереди)
Если Индекс >= ДанныеОчереди.Количество() Тогда
ПоказатьОповещениеПользователю("Все операции завершены успешно");
Возврат;
КонецЕсли;
ТекущаяСтрока = ДанныеОчереди[Индекс];
// Подготовка описания оповещения для текущего вызова
ПараметрыВыполнения = Новый Структура("Индекс, Данные", Индекс, ДанныеОчереди);
Оповещение = Новый ОписаниеОповещения("ПослеВыполненияОперации", ЭтотОбъект, ПараметрыВыполнения);
// Вызов асинхронного метода драйвера
МенеджерОборудованияКлиент.ВыполнитьОплату(Оповещение, ТекущаяСтрока.Терминал, ТекущаяСтрока.Сумма);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыполненияОперации(Результат, ДополнительныеПараметры) Экспорт
Если Результат.Успех Тогда
// Переходим к следующему элементу
ОбработатьСледующийЭлемент(ДополнительныеПараметры.Индекс + 1, ДополнительныеПараметры.Данные);
Иначе
// Обработка ошибки и запуск процедуры отката
ЗапуститьОткатОпераций(ДополнительныеПараметры.Индекс, ДополнительныеПараметры.Данные);
КонецЕсли;
КонецПроцедуры
Преимущество: Гарантированная последовательность действий без блокировки интерфейса пользователя.
Для конфигураций, работающих в облаке (например, через 1С:Фреш), клиентское соединение может быть нестабильным. В этом случае логику управления очередью лучше перенести на сервер.
Выясним причину, почему это надежнее: серверное ФоновоеЗадание не зависит от состояния окна браузера или тонкого клиента. Оно может последовательно опрашивать статусы терминалов, записывать промежуточные результаты в базу данных и гарантировать, что процесс не прервется на середине.
При таком подходе на клиенте запускается только индикатор прогресса (длительная операция), а серверный код выполняет следующую логику:
УправляемыеБлокировки, чтобы другие сеансы не вклинились в процесс.РегистрСведений.Поскольку платежные терминалы — это внешние системы, мы не можем использовать стандартное НачатьТранзакцию() и ОтменитьТранзакцию() для отката банковских операций. Нам необходимо реализовать программную компенсацию.
Проанализируем ситуацию: если на третьем терминале произошла ошибка, нам нужно вернуть деньги, списанные на первом и втором терминалах. Для этого мы должны использовать Стек (Stack) выполненных операций.
Алгоритм реализации:
УспешныеДействия.УспешныеДействия с конца (от последнего к первому).ОтменитьОперацию или ВозвратЧека.Пример логики отката:
&НаКлиенте
Процедура ЗапуститьОткатОпераций(ТекущийИндекс, ДанныеОчереди)
// Мы идем в обратном порядке от текущего неудачного индекса
Для Счетчик = 1 По ТекущийИндекс Цикл
ИндексДляОтката = ТекущийИндекс - Счетчик;
ДанныеДляОтката = ДанныеОчереди[ИндексДляОтката];
// Вызов метода возврата на терминале
МенеджерОборудованияКлиент.ВыполнитьВозврат(ДанныеДляОтката.ИдентификаторТранзакции);
КонецЦикла;
Сообщить("Произошла ошибка. Все частичные оплаты были отменены.");
КонецПроцедуры
Если операций много и они приходят из разных источников, стоит рассмотреть вариант с использованием Регистра Сведений как очереди заданий. Это особенно актуально для облачных решений.
Разберем по шагам:
ОчередьЧеков.Новый.Новый, меняет статус на ВРаботе и начинает выполнение.Выполнено или Ошибка.Использование регистра позволяет избежать потери данных при перезагрузке компьютера или обрыве связи. Как отмечалось в обсуждении, запись в регистр в облаке может занимать время, но это мизерные затраты по сравнению с временем ожидания ответа от банковского сервера (которое может длиться до 30-60 секунд).
Не забываем, что в современных конфигурациях на базе БСП (Библиотека стандартных подсистем) уже существуют механизмы для работы с очередями чеков. В БПО есть справочник ОчередьЧеков, предназначенный именно для таких целей. Перед написанием своего велосипеда, рекомендуем проверить, не реализована ли уже поддержка пакетного пробития в вашей версии 1С:Касса. Часто достаточно просто добавить записи в этот справочник, а штатные механизмы БПО сами разберутся с очередностью и повторными попытками в случае сбоев.
Резюме: Для реализации надежной очереди в 1С используйте рекурсивные вызовы для простых клиентских задач и фоновые задания с механизмом компенсации (Сага) для сложных распределенных транзакций. Это обеспечит стабильность работы кассового узла и доверие покупателей.