В розничной торговле нередко возникает ситуация, когда торговая точка оформлена на два юридических лица (например, два индивидуальных предпринимателя для разных групп товаров), а в пиковые периоды требуется посадить двух кассиров на одну кассовую зону. Покупка дополнительных фискальных регистраторов (ФР/ККТ) не всегда оправдана экономически. Рассмотрим, как программно организовать работу двух независимых сеансов 1С с двумя общими фискальными регистраторами и обеспечить разделение позиций чека по организациям.
Фискальный регистратор, подключенный по интерфейсу COM, USB (в режиме эмуляции COM) или через локальную сеть (TCP/IP), как правило, монопольно захватывается драйвером устройства. Если первый сеанс 1С держит порт постоянным включением (DeviceEnabled = 1), второй сеанс при попытке обращения получает ошибку недоступности порта или тайм-аута соединения.
Разберем основные архитектурные подходы для решения этой проблемы:
DeviceEnabled = 0) после завершения операции.Проанализируем детально третий подход — динамический захват ККТ с обработкой ожидания, так как он не требует дополнительной инфраструктуры и реализуется непосредственно в коде конфигурации 1С.
При одновременной попытке пробития чека двумя кассирами один из сеансов неизбежно обнаружит, что порт ККТ занят. Чтобы избежать аварийного завершения операции, организуем цикл повторных попыток подключения с возможностью отмены пользователем.
Посмотрим на пример кода функции занятия устройства с использованием метода DeviceEnabled:
Если глФР.DeviceEnabled = 0 Тогда
~ещеРаз:
глФР.DeviceEnabled = 1;
Если глФР.DeviceEnabled = 0 Тогда
Если Вопрос("ФР занят другим сеансом, ждем...", РежимДиалогаВопрос.ОКОтмена, 1) = КодВозвратаДиалога.Отмена Тогда
Возврат;
КонецЕсли;
Перейти ~ещеРаз;
КонецЕсли;
КонецЕсли;
Разберем логику работы этого фрагмента: при вызове диалога Вопрос с таймаутом в 1 секунду сообщение автоматически обновляется каждую секунду до тех пор, пока второй кассир не завершит печать и свойство DeviceEnabled не примет значение 1. Если кассир решает отменить операцию, выполнение прерывается.
Важный момент: частое подключение и отключение через DeviceEnabled является штатным режимом работы драйверов торгового оборудования и не приводит к аппаратному износу фискального регистратора.
Когда в кассовой зоне установлено два фискальных регистратора (для ИП №1 и ИП №2), физический денежный ящик обычно подключен через интерфейсный кабель (RJ-12) только к одному из них. Это порождает коллизию: если чек пробивается на устройстве, к которому ящик не подключен, ящик сам по себе не откроется.
Рассмотрим алгоритм переключения логических устройств для корректного открытия денежного ящика:
ПараметрыКассовогоРегистратора = КэшПараметров.КассовыеРегистраторы.Получить(К.Организация);
Если КэшПараметров.РазделятьЧеки Тогда
// Если денежный ящик привязан к конкретному номеру устройства
Если глФР.CurrentDeviceNumber = КэшПараметров.ДенежныйЯщикПодключенКУстройству И Не КэшПараметров.НеОткрыватьЯщик Тогда
ЗанятьФР(глФР);
ОткрытьЯщик(глФР, НужноОткрытьЯщик);
КонецЕсли;
НужноОткрытьЯщик = Ложь;
// Переключение на устройство, привязанное к организации чека
Если глФР.CurrentDeviceNumber <> ПараметрыКассовогоРегистратора.НомерУстройства Тогда
глФР.DeviceEnabled = 0;
глФР.CurrentDeviceNumber = ПараметрыКассовогоРегистратора.НомерУстройства;
ЗанятьФР(глФР);
ОткрытьЯщик(глФР, НужноОткрытьЯщик);
КонецЕсли;
Иначе
Если КэшПараметров.ПодключатьФРТолькоПриПечатиЧека Тогда
ЗанятьФР(глФР);
КонецЕсли;
ОткрытьЯщик(глФР, НужноОткрытьЯщик);
КонецЕсли;
После завершения печати чека обязательно освобождаем порт, чтобы устройство стало доступным для соседнего рабочего места:
Если НужноОткрытьЯщик И Не КэшПараметров.НеОткрыватьЯщик Тогда
Если глФР.CurrentDeviceNumber <> КэшПараметров.ДенежныйЯщикПодключенКУстройству Тогда
Если КэшПараметров.ПодключатьФРТолькоПриПечатиЧека Тогда
глФР.DeviceEnabled = 0; // Освобождаем текущий порт
КонецЕсли;
глФР.CurrentDeviceNumber = КэшПараметров.ДенежныйЯщикПодключенКУстройству;
КонецЕсли;
ЗанятьФР(глФР);
ОткрытьЯщик(глФР, НужноОткрытьЯщик);
КонецЕсли;
// Финальное освобождение оборудования
Если КэшПараметров.ПодключатьФРТолькоПриПечатиЧека Тогда
глФР.DeviceEnabled = 0;
КонецЕсли;
Когда покупатель приносит на кассу товары, относящиеся к разным ИП, документ продажи должен автоматически разделиться на два отдельных фискальных документа с пропорциональным распределением видов оплат (наличные, безналичные).
Разберем по шагам функцию разделения документа Чек:
Функция РазделитьЧекПоОрганизациям() Экспорт
Если Не КэшПараметров.РазделятьЧеки Тогда
Возврат Истина;
КонецЕсли;
Записать();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЧекТовары.НомерСтроки КАК НомерСтроки,
| ЧекТовары.Товар.Вид.РеализацияС КАК РеализацияС
|ИЗ
| Документ.Чек.Товары КАК ЧекТовары
|ГДЕ
| ЧекТовары.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ЭтотОбъект.Ссылка);
СтрокиПоОрганизациям = Запрос.Выполнить().Выгрузить();
Для Каждого Стр Из СтрокиПоОрганизациям Цикл
Если Стр.РеализацияС = Null Или Стр.РеализацияС = Неопределено Или Стр.РеализацияС.Пустая() Тогда
Стр.РеализацияС = УмолчанияПользователя.РозничнаяОрганизация;
КонецЕсли;
Если Стр.РеализацияС <> Организация Тогда
ОрганизацияВторогоЧека = Стр.РеализацияС;
КонецЕсли;
КонецЦикла;
ИспОрганизации = Новый Массив;
ЧастоИспользуемые.ЗаполнитьМассивУникальнымиЗначениями(ИспОрганизации, СтрокиПоОрганизациям.ВыгрузитьКолонку("РеализацияС"));
Если ИспОрганизации.Количество() = 1 Тогда
Если Организация <> ИспОрганизации[0] Тогда
Организация = ИспОрганизации[0];
глПриСменеОрганизации(ЭтотОбъект,,, Ложь);
КонецЕсли;
Возврат Истина;
ИначеЕсли ИспОрганизации.Количество() = 2 Тогда
// В корзине товары двух ИП: создаем второй чек и переносим позиции
Чек2 = Документы.Чек.СоздатьДокумент();
СтрокиПереносим = Новый Массив;
Для Каждого Стр Из СтрокиПоОрганизациям Цикл
Если Стр.РеализацияС <> Организация Тогда
СтрокаДока = Товары[Стр.НомерСтроки - 1];
СтрокиПереносим.Добавить(СтрокаДока);
ЗаполнитьЗначенияСвойств(Чек2.Товары.Добавить(), СтрокаДока);
КонецЕсли;
КонецЦикла;
ЗаполнитьЗначенияСвойств(Чек2, ЭтотОбъект);
Чек2.Организация = ОрганизацияВторогоЧека;
глПриСменеОрганизации(Чек2,,, Ложь);
Чек2.КассовыйРегистратор = КэшПараметров.КассовыеРегистраторы[ОрганизацияВторогоЧека].КассовыйРегистратор;
НачатьТранзакцию();
// Распределение оплат между двумя чеками
РасчетЧека2 = Чек2.Товары.Итог("Всего");
РасчетЧека1 = ЭтотОбъект.Товары.Итог("Всего") - РасчетЧека2;
Для Каждого СтрОплаты Из ЭтотОбъект.Оплата Цикл
Было = СтрОплаты.Получено;
СтрОплаты.Получено = Мин(РасчетЧека1, Было);
Было = Было - СтрОплаты.Получено;
Если РасчетЧека1 > 0 Тогда
РасчетЧека1 = РасчетЧека1 - СтрОплаты.Получено;
КонецЕсли;
Если РасчетЧека2 > 0 И Было > 0 Тогда
НовОплата = Чек2.Оплата.Добавить();
ЗаполнитьЗначенияСвойств(НовОплата, СтрОплаты);
НовОплата.Получено = Мин(РасчетЧека2, Было);
РасчетЧека2 = РасчетЧека2 - НовОплата.Получено;
КонецЕсли;
КонецЦикла;
Чек2.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
Отказ = Ложь;
СерверТО.ПечатьЧекаФР(Чек2, "ПробитьЧек", "Чек", Отказ);
Если Не Отказ Тогда
Чек2.ЧекПробитККМ = 1;
Чек2.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
// Удаляем перенесенные товары из первого чека
Для Каждого Стр Из СтрокиПереносим Цикл
Товары.Удалить(Стр);
КонецЦикла;
Записать();
ЗафиксироватьТранзакцию();
КонецЕсли;
Возврат Истина;
КонецФункции
При внедрении совместного использования фискальных регистраторов необходимо учитывать требования 54-ФЗ и специфику сетевых протоколов:
1021 («Кассир» — ФИО) и 1203 («ИНН кассира»). Поскольку за одним аппаратом работают разные сотрудники, эти реквизиты должны заполняться динамически из текущего сеанса пользователя 1С перед открытием чека.DeviceEnabled = 0 принудительно разрывает сетевую сессию, освобождая сокет для других клиентов.Предложенная схема динамического подключения в связке с алгоритмом разбивки чека позволяет организовать полноценное обслуживание покупателей на двух кассовых местах без приобретения дополнительного торгового оборудования.