Как правильно организовать сканирование серийных номеров после сканирования штрихкода товара в 1С (Обычные формы)

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

При автоматизации рабочих мест кладовщиков, операторов склада и кассиров в конфигурациях на обычных формах (таких как «Управление торговлей 10.3», «Управление производственным предприятием 1.3» или «Комплексная автоматизация 1.1») часто возникает задача пошагового сканирования. Типичный сценарий: оператор сканирует штрихкод товара, система идентифицирует номенклатуру, а затем требует отсканировать определенное количество серийных номеров для этой позиции. Только после ввода всех серийных номеров система разрешает перейти к сканированию следующего товара.

Разработчики, особенно начинающие, часто совершают архитектурную ошибку: пытаются реализовать ожидания ввода серийных номеров через модальные окна, циклы ожидания внутри процедуры ВнешнееСобытие или принудительный программный вызов этой процедуры. В этой статье мы подробно разберем, почему такой подход приводит к зависанию интерфейса и ошибкам, и как правильно реализовать эту задачу на основе архитектуры конечного автомата (State Machine) с использованием штатных механизмов торгового оборудования 1С.

В чем заключается главная ошибка при работе со сканером?

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


Пока Количество < КоличествоФакт Цикл
    Количество = Количество + 1;
    ТребоватьСерийныеНомера = Истина;
    Подсказка = "Введите Серийный номер";
    ВнешнееСобытие("Сканер штрихкода", "ПолученШтрихкод", Данные);
КонецЦикла;

Этот код принципиально неверен по двум причинам:

  1. Асинхронность аппаратных событий. Сканер штрихкодов работает асинхронно. Когда физический луч сканера считывает код, драйвер торгового оборудования генерирует прерывание, которое платформа 1С ловит и передает в процедуру ВнешнееСобытие(Источник, Событие, Данные) модуля формы. Пытаться вызвать эту процедуру программно в цикле бессмысленно — реального сканирования в этот момент не происходит, а переменная Данные будет содержать старое значение, что приведет к бесконечному циклу или ошибкам переполнения стека.
  2. Блокировка потока интерфейса. Если запустить цикл ожидания или использовать модальные окна типа Предупреждение(), поток выполнения интерфейса заблокируется. Пользователь не сможет комфортно работать, а данные со сканера, настроенного в режиме эмуляции клавиатуры, начнут сыпаться мимо активных полей ввода.

Решение: Архитектура конечного автомата (State Machine)

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

Для реализации этого подхода нам потребуются переменные модуля формы, которые будут сохранять состояние между вызовами событий сканирования.

Разберем по шагам, какие переменные нам понадобятся в объявлении модуля формы:


Перем ТекущаяСтрокаНоменклатуры; // Хранит ссылку на строку табличной части "Товары"
Перем РежимВводаСерийныхНомеров; // Булев флаг: Истина, если сейчас ожидается ввод серийных номеров
Перем ТребуемоеКоличествоСерий;  // Число серийных номеров, которое необходимо отсканировать
Перем ВведеноСерийныхНомеров;    // Число уже успешно отсканированных серийных номеров

Теперь рассмотрим, как должна выглядеть основная логика обработки штрихкода при получении внешнего события.

Реализация обработчика ВнешнееСобытие

Ниже представлен чистый и надежный алгоритм обработки событий от сканера штрихкодов:


Процедура ВнешнееСобытие(Источник, Событие, Данные)
    
    // Получаем данные от стандартной библиотеки торгового оборудования (если используется)
    // ПолучитьСерверТО().ОбработатьВнешнееСобытие(Источник, Событие, Данные, ЭтаФорма);
    
    // Если сканер настроен напрямую или данные обрабатываются вручную:
    Штрихкод = СокрЛП(Данные);
    
    Если РежимВводаСерийныхНомеров Тогда
        // Сработал сканер, и система находится в режиме ожидания серийного номера
        ОбработатьСканированиеСерийногоНомера(Штрихкод);
    Иначе
        // Обычный режим: сканируем штрихкод товара
        ОбработатьСканированиеТовара(Штрихкод);
    EndIf;
    
КонецПроцедуры

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


Процедура ОбработатьСканированиеТовара(Штрихкод)
    
    // 1. Ищем номенклатуру в базе данных по штрихкоду
    НайденнаяНоменклатура = НайтиНоменклатуруПоШтрихкоду(Штрихкод);
    
    Если НайденнаяНоменклатура = Неопределено Тогда
        ОбщегоНазначения.СообщитьПользователю("Товар со штрихкодом " + Штрихкод + " не найден в базе данных!");
        Возврат;
    КонецЕсли;
    
    // 2. Ищем товар в табличной части документа
    ПараметрыОтбора = Новый Структура("Номенклатура", НайденнаяНоменклатура);
    НайденныеСтроки = Товары.НайтиСтроки(ПараметрыОтбора);
    
    Если НайденныеСтроки.Количество() = 0 Тогда
        // Если товара нет в документе, добавляем новую строку
        НоваяСтрока = Товары.Добавить();
        НоваяСтрока.Номенклатура = НайденнаяНоменклатура;
        НоваяСтрока.Количество = 1;
        ТекущаяСтрокаНоменклатуры = НоваяСтрока;
    Иначе
        // Если товар найден, позиционируемся на первой строке
        ТекущаяСтрокаНоменклатуры = НайденныеСтроки[0];
    КонецЕсли;
    
    // 3. Проверяем, требует ли данный товар ввода серийных номеров
    Если ТребуетВводаСерийныхНомеров(НайденнаяНоменклатура) Тогда
        
        // Включаем режим ввода серийных номеров
        РежимВводаСерийныхНомеров = Истина;
        ТребуемоеКоличествоСерий  = ТекущаяСтрокаНоменклатуры.Количество;
        ВведеноСерийныхНомеров    = 0;
        
        // Визуально оповещаем пользователя и переносим фокус
        ЭлементыФормы.НадписьПодсказка.Заголовок = "ОЖИДАНИЕ СЕРИЙНЫХ НОМЕРОВ! Отсканируйте серии для товара: " + НайденнаяНоменклатура;
        АктивизироватьТабличнуюЧастьСерий(ТекущаяСтрокаНоменклатуры);
        
        Сообщить("Товар требует ввода серийных номеров в количестве: " + ТребуемоеКоличествоСерий + " шт.");
    Иначе
        // Если серийные номера не нужны, просто увеличиваем количество товара
        ТекущаяСтрокаНоменклатуры.Количество = ТекущаяСтрокаНоменклатуры.Количество + 1;
        ЭлементыФормы.НадписьПодсказка.Заголовок = "Товар успешно добавлен: " + НайденнаяНоменклатура;
    КонецЕсли;
    
КонецПроцедуры

Теперь разберем процедуру ОбработатьСканированиеСерийногоНомера, которая вызывается при сканировании штрихкода в режиме ожидания ввода серийных номеров:


Процедура ОбработатьСканированиеСерийногоНомера(ШтрихкодСерии)
    
    // 1. Проверяем серийный номер на уникальность в текущем документе
    ПараметрыОтбора = Новый Структура;
    ПараметрыОтбора.Вставить("Номенклатура", ТекущаяСтрокаНоменклатуры.Номенклатура);
    ПараметрыОтбора.Вставить("СерийныйНомер", ШтрихкодСерии);
    
    ДублирующиеСтроки = СерийныеНомера.НайтиСтроки(ПараметрыОтбора);
    Если ДублирующиеСтроки.Количество() > 0 Тогда
        ОбщегоНазначения.СообщитьПользователю("Этот серийный номер уже был отсканирован для данного товара!");
        Возврат;
    КонецЕсли;
    
    // 2. Добавляем серийный номер в подчиненную табличную часть "СерийныеНомера"
    НоваяСтрокаСерии = СерийныеНомера.Добавить();
    НоваяСтрокаСерии.Номенклатура = ТекущаяСтрокаНоменклатуры.Номенклатура;
    НоваяСтрокаСерии.СерийныйНомер = ШтрихкодСерии;
    
    // Позиционируем курсор на добавленной строке серии
    ЭлементыФормы.СерийныеНомера.ТекущаяСтрока = НоваяСтрокаСерии;
    
    // 3. Увеличиваем счетчики
    ВведеноСерийныхНомеров = ВведеноСерийныхНомеров + 1;
    ТекущаяСтрокаНоменклатуры.КоличествоФакт = ВведеноСерийныхНомеров; // Например, фиксируем факт
    
    // 4. Проверяем, все ли серийные номера введены
    Если ВведеноСерийныхНомеров >= ТребуемоеКоличествоСерий Тогда
        // Все требуемые серийные номера введены, выходим из режима ввода серий
        РежимВводаСерийныхНомеров = Ложь;
        ТекущаяСтрокаНоменклатуры = Неопределено;
        
        ЭлементыФормы.НадписьПодсказка.Заголовок = "Все серии успешно введены. Отсканируйте следующий товар.";
        Сообщить("Ввод серийных номеров для текущего товара успешно завершен.");
    Иначе
        // Ожидаем ввод следующего серийного номера
        ОсталосьВвести = ТребуемоеКоличествоСерий - ВведеноСерийныхНомеров;
        ЭлементыФормы.НадписьПодсказка.Заголовок = "Отсканировано серий: " + ВведеноСерийныхНомеров + " из " + ТребуемоеКоличествоСерий + ". Осталось ввести: " + ОсталосьВвести;
    КонецЕсли;
    
КонецПроцедуры

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

Для удобства работы кладовщика критически важно, чтобы интерфейс формы автоматически переключался на нужные элементы ТЧ. В обычных формах (толстый клиент) управление фокусом ввода и активными элементами отличается от управляемых форм.

Выясним, как правильно активизировать табличную часть и установить курсор в нужную ячейку. Для этого напишем процедуру АктивизироватьТабличнуюЧастьСерий:


Процедура АктивизироватьТабличнуюЧастьСерий(СтрокаТовара)
    
    // 1. Делаем элемент управления табличной части "СерийныеНомера" активным на форме
    ЭлементыФормы.ТекущийЭлемент = ЭлементыФормы.СерийныеНомера;
    
    // 2. Пытаемся позиционироваться на первой строке серийных номеров этого товара
    ПараметрыОтбора = Новый Структура("Номенклатура", СтрокаТовара.Номенклатура);
    НайденныеСерии = СерийныеНомера.НайтиСтроки(ПараметрыОтбора);
    
    Если НайденныеСерии.Количество() > 0 Тогда
        // Если серии уже были введены ранее, выделяем первую из них
        ЭлементыФормы.СерийныеНомера.ТекущаяСтрока = НайденныеСерии[0];
    Иначе
        // Если серий еще нет, добавляем пустую строку для визуального ориентира (необязательно)
        // НоваяСерия = СерийныеНомера.Добавить();
        // НоваяСерия.Номенклатура = СтрокаТовара.Номенклатура;
        // ЭлементыФормы.СерийныеНомера.ТекущаяСтрока = НоваяСерия;
    КонецЕсли;
    
    // 3. Устанавливаем активную колонку в ТЧ "СерийныеНомера" для удобства ручной корректировки
    ЭлементыФормы.СерийныеНомера.ТекущаяКолонка = ЭлементыФормы.СерийныеНомера.Колонки.СерийныйНомер;
    
КонецПроцедуры

Обратите внимание на ошибку, с которой столкнулся автор в сообщении 18 и 24: Поле объекта не обнаружено (ТекущаяСтрока). Эта ошибка возникает, если имя элемента формы ТЧ не совпадает с именем реквизита объекта, либо если вы пытаетесь обратиться к реквизиту формы, имеющему тип «Флажок» или иной простой тип, вместо элемента управления типа ТабличноеПоле. Всегда проверяйте свойства элемента формы в конфигураторе и убеждайтесь, что свойство Данные элемента управления указывает на нужную табличную часть.

Реализация проверки серийных номеров на уникальность

При сканировании серийных номеров важно реализовать проверку уникальности не только в локальном документе, но и в глобальной базе данных (если бизнес-логика требует, чтобы один серийный номер не мог быть продан дважды или присутствовать на складе в нескольких экземплярах).

Проанализируем пример функции глобальной проверки уникальности серийного номера в базе данных:


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

Если серийный номер уникален, но отсутствует в справочнике, его необходимо программно создать и записать перед добавлением в табличную часть документа:


Если СерийныйНомерУникаленВБазеДанных(ТекущаяСтрокаНоменклатуры.Номенклатура, ШтрихкодСерии) Тогда
    НовыйСерийник = Справочники.СерийныеНомера.СоздатьЭлемент();
    НовыйСерийник.Владелец = ТекущаяСтрокаНоменклатуры.Номенклатура;
    НовыйСерийник.Наименование = ШтрихкодСерии;
    НовыйСерийник.Записать();
Иначе
    // Если серийный номер уже существует, можно получить на него ссылку
    // СерияСсылка = Справочники.СерийныеНомера.НайтиПоНаименованию(ШтрихкодСерии, Истина, , ТекущаяСтрокаНоменклатуры.Номенклатура);
КонецЕсли;

Тонкости настройки сканера: Клавиатура vs COM-порт (HID/Virtual COM)

В сообщениях 33-36 обсуждается проблема зависания диалоговых окон (например, ВвестиСтроку) при использовании сканера, настроенного в режиме эмуляции клавиатуры. Выясним причину этой проблемы и способы ее решения:

  1. Режим эмуляции клавиатуры (KB). При таком режиме сканер имитирует нажатие клавиш на клавиатуре компьютера. Если на экране открыто диалоговое окно или фокус стоит в текстовом поле ввода, сканер быстро «напечатает» штрихкод. Но чтобы окно закрылось, системе необходимо получить сигнал нажатия клавиши Enter. Для этого в настройках сканера (путем сканирования управляющих штрихкодов из инструкции к устройству) нужно обязательно включить суффикс возврата каретки (CR/LF или Enter). Без этого суффикса штрихкод запишется в поле ввода, но форма ввода останется висеть на экране.
  2. Режим виртуального COM-порта (RS-232 / USB-COM). Это наиболее профессиональный и рекомендуемый режим работы торгового оборудования в 1С. Сканер передает данные напрямую драйверу торгового оборудования, минуя активное поле ввода клавиатуры. Это позволяет гарантированно отлавливать события сканирования в процедуре ВнешнееСобытие независимо от того, где сейчас находится курсор на форме. При использовании режима COM-порта зависания окон ввода исключены, так как данные обрабатываются платформой на уровне событий.

Резюме и рекомендации

Разработав логику на основе событийного механизма (конечного автомата) без блокирующих циклов, мы получаем гибкое и стабильное решение. Основные правила, которые необходимо соблюдать:

← На главную