Как программно создать серии номенклатуры и сгенерировать составной серийный номер?

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

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

Анализ ошибки при записи новой серии номенклатуры

Рассмотрим типовую ситуацию: при попытке создания элемента справочника СерииНоменклатуры простым заполнением наименования система выдает исключение следующего вида:


Ошибка при выполнении обработчика - 'ПередЗаписью'
по причине:
{Справочник.СерииНоменклатуры.МодульМенеджера(221)}: Преобразование значения к типу Булево не может быть выполнено
Если ПараметрыШаблона.ИспользоватьНомерСерии Тогда

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

В обработчике ПередЗаписью система обращается к модулю менеджера справочника для получения параметров шаблона генерации серий. Если у создаваемого объекта реквизит ВидНоменклатуры не заполнен, структура параметров шаблона возвращается пустой (или содержит значения Неопределено), из-за чего проверка флага ПараметрыШаблона.ИспользоватьНомерСерии падает с ошибкой приведения типов.

Для устранения проблемы обязательно перед вызовом метода Записать() указываем вид номенклатуры:


НовыйОбъект = Справочники.СерииНоменклатуры.СоздатьЭлемент();
НовыйОбъект.ВидНоменклатуры = ВыбраннаяНоменклатура.ВидНоменклатуры;
НовыйОбъект.Номер = "0001";
НовыйОбъект.Записать();

Особенности формирования инкрементального номера по артикулу

Разберем следующую задачу: серийный номер должен формироваться по правилу Артикул + ЧисловойНомер (например, для артикула Я1519 номера должны иметь вид Я15190001, Я15190002 и т.д.). При этом длина артикула может варьироваться, а числовая часть номера должна иметь строго фиксированную длину (например, 4 знака) с дополнением лидирующими нулями.

Проанализируем подводные камни, возникающие при реализации поиска последнего номера через запрос к информационной базе:

  1. Лексикографическое сравнение строк в СУБД: Агрегатная функция МАКСИМУМ в запросе 1С при работе со строковыми полями выполняет алфавитное сравнение посимвольно слева направо. Если номера имеют разную разрядность или в них присутствуют лишние пробелы/нули, результат поиска максимума будет математически некорректным.
  2. Отсутствие преобразования строки в число в запросе: Язык запросов 1С не поддерживает конструкцию ВЫРАЗИТЬ(Поле КАК Число) для строковых типов данных. Поэтому выделение числового значения и операцию инкремента (прибавление единицы) необходимо производить во встроенном языке после выборки.
  3. Смещение подстроки: При использовании функции ПОДСТРОКА необходимо точно позиционировать начальный символ выборки, учитывая длину переменного артикула номенклатуры.

Пошаговый алгоритм получения следующего серийного номера

Рассмотрим, как правильно составить запрос и обработать полученный результат во встроенном языке 1С:

  1. Определяем артикул текущей номенклатуры и его длину с помощью встроенной функции СтрДлина().
  2. В тексте запроса отбираем записи справочника СерииНоменклатуры, начинающиеся с нашего артикула, используя оператор ПОДОБНО &МаскаНомера.
  3. С помощью функции ПОДСТРОКА выделяем числовую часть номера. Начальная позиция подстроки вычисляется как ДлинаАртикула + 1, а количество выбираемых символов соответствует разрядности номера (в нашем примере — 4 символа).
  4. Полученную подстроку во встроенном языке преобразуем к числу функцией Число(), что автоматически отсекает все лидирующие нули. Ни в коем случае нельзя удалять нули функцией СтрЗаменить(), так как это исказит внутренние нули числа (например, из 0050 получится 5 вместо 50).
  5. Прибавляем единицу к числу и форматируем строку обратно в четырехзначный вид, используя строковые параметры форматирования ЧЦ=4 (число целых разрядов), ЧВН= (выводить незначащие нули) и ЧГ=0 (отключить группировку разрядов пробелами).

Практическая реализация генератора серийных номеров

Посмотрим на готовый код серверной функции, которая находит максимальный номер, рассчитывает следующий и создает готовый элемент справочника серий:


&НаСервере
Функция СоздатьСериюНоменклатуры(НоменклатураСсылка)
    
    СправочникСерийНомера = Справочники.СерииНоменклатуры.СоздатьЭлемент();
    СправочникСерийНомера.ВидНоменклатуры = НоменклатураСсылка.ВидНоменклатуры;
    
    Артикул = СокрЛП(НоменклатураСсылка.Артикул);
    ДлинаАртикула = СтрДлина(Артикул);
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   МАКСИМУМ(ПОДСТРОКА(СерииНоменклатуры.Номер, &ПозицияНачала, 4)) КАК НомерСтрокой
        |ИЗ
        |   Справочник.СерииНоменклатуры КАК СерииНоменклатуры
        |ГДЕ
        |   СерииНоменклатуры.ВидНоменклатуры = &ВидНоменклатуры
        |   И СерииНоменклатуры.Номер ПОДОБНО &МаскаНомера";
        
    Запрос.УстановитьПараметр("ВидНоменклатуры", НоменклатураСсылка.ВидНоменклатуры);
    Запрос.УстановитьПараметр("ПозицияНачала", ДлинаАртикула + 1);
    Запрос.УстановитьПараметр("МаскаНомера", Артикул + "%");
    
    РезультатЗапроса = Запрос.Выполнить().Выбрать();
    
    Если РезультатЗапроса.Следующий() И ЗначениеЗаполнено(РезультатЗапроса.НомерСтрокой) Тогда
        // Преобразуем строковый номер в число для математического инкремента
        ТекущийНомер = Число(РезультатЗапроса.НомерСтрокой);
        СледующийНомер = ТекущийНомер + 1;
        
        // Форматируем число: ровно 4 символа с заполнением ведущими нулями
        ХвостНомера = Формат(СледующийНомер, "ЧЦ=4; ЧВН=; ЧГ=0");
        СправочникСерийНомера.Номер = Артикул + ХвостНомера;
    Иначе
        // Первый номер для текущего артикула
        СправочникСерийНомера.Номер = Артикул + "0001";
    КонецЕсли;
    
    СправочникСерийНомера.Записать();
    
    Возврат СправочникСерийНомера.Ссылка;
    
КонецФункции

Оптимизация массовой генерации серий

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

  1. Исключение циклов на клиенте: Нельзя вызывать серверную функцию внутри клиентского цикла Для Сч = 1 По Количество Цикл. Каждый вызов приводит к контекстному переключению между клиентом и сервером. Всю пачку номеров следует генерировать за один вызов процедуры на сервере.
  2. Использование транзакций: Массовую запись объектов необходимо оборачивать в транзакцию с помощью методов НачатьТранзакцию() и ЗафиксироватьТранзакцию(). Это снижает нагрузку на дисковую подсистему сервера баз данных (сброс журнала транзакций происходит однократно) и многократно ускоряет запись элементов.
  3. Управляемые блокировки: В многопользовательской среде при одновременной генерации номеров разными пользователями возможна ситуация гонки (race condition), в результате которой оба сеанса получат одинаковый предыдущий максимум и запишут дубликаты. Для исключения дублирования номеров рекомендуется фиксировать счетчики серий в специализированном регистре сведений с наложением исключительной блокировки через объект БлокировкаДанных на время выполнения транзакции.
← На главную