Как сделать так, чтобы обработка сканирования штрихкодов срабатывала только в активной форме документа ЧекККМ?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При разработке и кастомизации рабочего места кассира (РМК) или формы документа ЧекККМ в конфигурации «1С:Управление торговлей» (УТ 11) разработчики часто сталкиваются с проблемой некорректного поведения глобальных оповещений. Например, при сканировании штрихкода товара в справочнике «Номенклатура» или в другом документе, код получения остатков, написанный для Чека ККМ, внезапно начинает выполняться в фоновом режиме. Это приводит к лишней нагрузке на сервер, замедлению работы интерфейса и потенциальным ошибкам выполнения кода.

В этой статье мы подробно разберем, почему возникает эта проблема, как правильно ограничить вызов процедуры ОбработкаОповещения только активным окном и как оптимизировать код получения остатков, избавившись от критической ошибки производительности — запроса в цикле.

Выясняем причину: почему обработчик срабатывает в фоновых формах?

Для начала проанализируем, как устроен механизм работы с торговым оборудованием в типовых конфигурациях 1С, использующих БПО (Библиотеку подключаемого оборудования). Когда сканер штрихкодов считывает данные, драйвер генерирует внешнее событие. БПО перехватывает его и рассылает по системе с помощью глобального метода платформы Оповестить("ScanData", Параметры).

Особенность метода Оповестить() заключается в том, что он рассылает сигнал всем открытым формам текущего сеанса, у которых определен обработчик ОбработкаОповещения. Если у кассира или менеджера одновременно открыты справочник «Номенклатура» (активное окно) и документ «Чек ККМ» (в фоновом режиме), событие сканирования примут обе формы. Фоновый документ попытается обработать штрихкод и обновить остатки, что абсолютно не требуется в данный момент.


Решение №1: Системный метод ВводДоступен() — стандарт платформы 1С

Самым правильным и надежным способом отсечь фоновые вызовы является использование встроенного метода платформы 1С:Предприятие ВводДоступен(). Этот метод проверяет, является ли текущее окно активным для пользователя и готово ли оно принимать ввод.

Рассмотрим, как применить этот метод в обработчике события формы документа:


&НаКлиенте
Процедура Расш1_ОбработкаОповещенияПосле(ИмяСобытия, Параметр, Источник)
    
    // Проверяем, что событие пришло от торгового оборудования и наша форма активна
    Если Источник = "ПодключаемоеОборудование" И ВводДоступен() Тогда
        
        Если ИмяСобытия = "ScanData" Тогда
            // Здесь выполняем обработку данных штрихкода
            ОбработатьСканированиеШтрихкода(Параметр);
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

Важный нюанс при отладке: Обратите внимание, что при пошаговой отладке в Конфигураторе метод ВводДоступен() вернет Ложь, если выполнение остановилось на точке останова. Это происходит потому, что в фокус ввода переходит сам Конфигуратор, а не клиентское приложение 1С. Для корректной проверки работы алгоритма рекомендуется ставить точку останова уже внутри условия, после успешного прохождения проверки ВводДоступен().


Решение №2: Использование уникального имени события

Если в вашей системе реализован кастомный вызов оповещения (например, вы сами программно вызываете Оповестить() из другой формы), обязательно используйте уникальные имена событий вместо стандартного ScanData.

Вместо универсального имени укажите специфическое имя события, которое точно не обрабатывается другими стандартными механизмами конфигурации:


// В форме-источнике (откуда посылаем сигнал):
Оповестить("СобытиеОбновленияОстатковРМК", ДанныеДляОбработки, ЭтотОбъект);

// В форме-приемнике (ЧекККМ):
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
    Если ИмяСобытия = "СобытиеОбновленияОстатковРМК" Тогда
        // Обрабатываем только наше уникальное событие
        ОбновитьОстаткиВТабличнойЧасти();
    КонецЕсли;
КонецПроцедуры

Оптимизация кода: Устраняем запрос в цикле

В исходном обсуждении на форуме автор темы продемонстрировал код, в котором при каждом сканировании штрихкода происходил обход всей табличной части товаров в цикле, и для каждой строки вызывалась серверная функция с запросом к базе данных:


// ПЛОХОЙ ПРИМЕР: ТАК ДЕЛАТЬ НЕЛЬЗЯ!
Для Каждого Строка Из Объект.Товары Цикл
    Строка.Остаток = ПолучитьОстатки(Строка.Номенклатура); // Вызов сервера и запрос в цикле!
КонецЦикла;

Такой подход парализует работу РМК, как только в чеке появится более 5–10 позиций. База данных будет перегружена единичными запросами при каждом новом сканировании. Разберем два правильных сценария оптимизации.

Вариант А. Получение остатка только для одной, текущей строки (рекомендуется)

В РМК нет необходимости каждый раз обновлять остатки по всем товарам в чеке. Достаточно получить остаток только для той позиции, которую кассир только что отсканировал и добавил в документ.

Посмотрим на пример реализации серверной функции для получения остатка по конкретной номенклатуре и складу:


&НаСервере
Функция ПолучитьОстатокТовара(Номенклатура, Склад)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
        |ИЗ
        |   РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад И Номенклатура = &Номенклатура) КАК ТоварыНаСкладахОстатки";
        
    Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
    Запрос.УстановитьПараметр("Склад", Склад);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Остаток;
    КонецЕсли;
    
    Возврат 0;
    
КонецФункции

Этот метод вызывается единожды на серверном контексте в момент добавления новой строки в табличную часть Товары.

Вариант Б. Пакетный запрос с использованием Соответствия (если нужно обновить всю таблицу)

Если вам все же необходимо обновить остатки по всей табличной части (например, при изменении склада в шапке документа), следует передать всю номенклатуру одним массивом на сервер, выполнить один запрос и вернуть результат в виде объекта Соответствие.

Проанализируем оптимальный алгоритм пакетного обновления:


&НаСервере
Функция ПолучитьТаблицуОстатков(МассивНоменклатуры, Склад)
    
    СоответствиеОстатков = Новый Соответствие;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
        |   ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
        |ИЗ
        |   РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад И Номенклатура В (&МассивНоменклатуры)) КАК ТоварыНаСкладахОстатки";
        
    Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
    Запрос.УстановитьПараметр("Склад", Склад);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        СоответствиеОстатков.Вставить(Выборка.Номенклатура, Выборка.Остаток);
    КонецЦикла;
    
    Возврат СоответствиеОстатков;
    
КонецФункции

&НаКлиенте
Процедура ОбновитьОстаткиВсегоДокумента()
    
    // Собираем уникальный массив номенклатуры из табличной части
    МассивНоменклатуры = Новый Массив;
    Для Каждого СтрокаТовара Из Объект.Товары Цикл
        Если МассивНоменклатуры.Найти(СтрокаТовара.Номенклатура) = Неопределено Тогда
            МассивНоменклатуры.Добавить(СтрокаТовара.Номенклатура);
        КонецЕсли;
    КонецЦикла;
    
    Если МассивНоменклатуры.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Получаем остатки за один серверный вызов
    СоответствиеОстатков = ПолучитьТаблицуОстатков(МассивНоменклатуры, Объект.Склад);
    
    // Заполняем колонки остатков в табличной части на клиенте
    Для Каждого СтрокаТовара Из Объект.Товары Цикл
        ТекущийОстаток = СоответствиеОстатков.Получить(СтрокаТовара.Номенклатура);
        СтрокаТовара.Остаток = ?(ТекущийОстаток = Неопределено, 0, ТекущийОстаток);
    КонецЦикла;
    
КонецПроцедуры

Этот алгоритм работает максимально быстро, так как обращение к базе данных происходит ровно один раз, а сопоставление данных на клиенте выполняется в оперативной памяти с помощью эффективного поиска по хэш-таблице (объект Соответствие).


Штатная альтернатива: Отображение остатков без программирования

Стоит упомянуть, что в типовых конфигурациях 1С:Управление торговлей 11 и 1С:Розница разработчики уже предусмотрели стандартный функционал отображения остатков в РМК. Прежде чем писать собственный код в расширении, проверьте настройки системы. Возможно, ваша задача решается парой кликов в пользовательском режиме.

Выполним следующие шаги для включения штатного механизма:

  1. Перейдите в раздел НСИ и администрирование, затем выберите пункт РМК и оборудование.
  2. Откройте ссылку Настройки РМК и перейдите в используемую настройку для вашей кассы.
  3. На вкладке Общие настройки найдите параметр Отображать остатки (или «Отображать доступный остаток»). Установите этот флаг.
  4. Сохраните изменения. Теперь при сканировании или подборе товара кассир будет видеть актуальный остаток на складе непосредственно в нижней информационной панели или в списке подбора номенклатуры.

Использование штатного механизма предпочтительнее, так как оно полностью поддерживается при последующих обновлениях конфигурации и не требует поддержки кастомного кода.

← На главную