В практической работе с базами данных 1С часто возникает задача создания альтернативной системы кодирования товаров. Например, когда стандартный числовой реквизит Код справочника Номенклатура трогать нельзя, но внешняя система (сайт, маркетплейс или парсер) требует уникальный буквенно-цифровой идентификатор вида TV12, TEL45 или NOTE102.
Особую сложность представляет условие, при котором этот код должен наследоваться по иерархии папок любого уровня вложенности (до 5 уровней и более). Если элемент создается внутри группы «Телевизоры» (или любой ее подпапки типа «LED -> LG»), он должен автоматически получить префикс TV и очередной порядковый номер. В этой статье мы подробно и по шагам разберем, как реализовать такую логику на уровне платформы 1С:Предприятие.
Сформулируем требования к нашей доработке:
Номенклатура должен быть создан реквизит ДопКод (тип: Строка). Он должен быть доступен как для элементов, так и для групп.ДопКод будет выступать в качестве хранилища префикса (например, группа «Телевизоры» содержит значение «TV», а группа «Ноутбуки» — «NOTE»).TV125) и записать ее в поле ДопКод элемента.ДопКод на форме элемента должно быть закрыто от ручного редактирования пользователями во избежание нарушения уникальности.Проанализируем ситуацию и рассмотрим два альтернативных метода автонумерации: простой (через запрос к таблице справочника) и высокопроизводительный (с использованием вспомогательного регистра сведений и транзакционных блокировок).
---
Для начала выясним причину, почему рекурсивный поиск префикса «через точку» (Родитель.Родитель.ДопКод) в модуле — не самое лучшее решение с точки зрения производительности СУБД. При глубокой иерархии постоянные обращения к родительским группам порождают множество мелких неоптимальных запросов к базе данных. Чтобы этого избежать, мы напишем эффективный и безопасный цикл обхода дерева групп вверх до корневого элемента.
Разберем по шагам функцию поиска префикса. Данный код мы разместим в Модуле объекта справочника Номенклатура:
Функция ПолучитьПрефиксПоИерархии(НачальныйРодитель) Экспорт
ТекущийРодитель = НачальныйРодитель;
// Ограничиваем цикл 15 итерациями для гарантированной защиты от бесконечного цикла
Для Счетчик = 1 По 15 Цикл
Если НЕ ЗначениеЗаполнено(ТекущийРодитель) Тогда
Прервать;
КонецЕсли;
// Проверяем, задан ли префикс в текущей группе
Если ЗначениеЗаполнено(ТекущийРодитель.ДопКод) Тогда
Возврат СокрЛП(ТекущийРодитель.ДопКод);
КонецЕсли;
// Поднимаемся на один уровень выше
ТекущийРодитель = ТекущийРодитель.Родитель;
КонецЦикла;
Возврат "";
КонецФункции
---
Теперь рассмотрим подробнее, как получить уникальный порядковый номер для найденного префикса. Существует два принципиально разных подхода.
Если в вашей базе товары создаются нечасто (несколько штук в час одним-двумя операторами), можно использовать простой запрос к таблице справочника. Система будет искать максимальный уже существующий код с заданным префиксом, выделять из него числовую часть и увеличивать ее на единицу.
Посмотрим на пример реализации такой функции в модуле объекта:
Функция ПолучитьСледующийНомерПоТаблице(Префикс)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МАКСИМУМ(Номенклатура.ДопКод) КАК МаксКод
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ДопКод ПОДОБНО &ШаблонПоиска
| И НЕ Номенклатура.ЭтоГруппа";
// Передаем шаблон для поиска кодов, начинающихся с нашего префикса
Запрос.УстановитьПараметр("ШаблонПоиска", Префикс + "%");
РезультатЗапроса = Запрос.Выполнить();
Если НЕ РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
МаксКод = Выборка.МаксКод;
Если ЗначениеЗаполнено(МаксКод) Тогда
// Выделяем числовую часть строки, идущую после буквенного префикса
ДлинаПрефикса = СтрДлина(Префикс);
ЧисловаяСтрока = Сред(МаксКод, ДлинаПрефикса + 1);
Попытка
ПоследнийНомер = Число(ЧисловаяСтрока);
Исключение
ПоследнийНомер = 0;
КонецПопытки;
Возврат ПоследнийНомер + 1;
КонецЕсли;
КонецЕсли;
// Если кодов с таким префиксом еще нет, начинаем с единицы
Возврат 1;
КонецФункции
Важное предупреждение: Данный метод имеет критический недостаток — уязвимость к параллельной работе пользователей. Если два оператора одновременно начнут создавать карточки товаров в папке «Телевизоры», система выдаст им один и тот же максимальный номер, что приведет к дублированию кодов в базе данных при записи.
Для надежной работы в многопользовательской среде мы рекомендуем создать вспомогательный регистр сведений НумерацияПоПрефиксам. Он будет хранить текущие счетчики номеров для каждого префикса.
Настройки регистра сведений:
НумерацияПоПрефиксамПрефикс (тип: Строка, длина 10)Номер (тип: Число, длина 10, точность 0)Для исключения дублей мы применим механизм управляемых блокировок. Проанализируем код функции, работающей по этой схеме:
Функция ПолучитьСледующийНомерИзРегистра(Префикс)
// 1. Устанавливаем управляемую блокировку на конкретный префикс в регистре
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.НумерацияПоПрефиксам");
ЭлементБлокировки.УстановитьПараметр("Префикс", Префикс);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// 2. Считываем текущее состояние счетчика
МенеджерЗаписи = РегистрыСведений.НумерацияПоПрефиксам.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Префикс = Префикс;
МенеджерЗаписи.Прочитать();
Если МенеджерЗаписи.Выбран() Тогда
НовыйНомер = МенеджерЗаписи.Номер + 1;
Иначе
НовыйНомер = 1;
МенеджерЗаписи.Префикс = Префикс;
КонецЕсли;
// 3. Записываем обновленный счетчик обратно в базу данных
МенеджерЗаписи.Номер = НовыйНомер;
МенеджерЗаписи.Записать(Истина);
Возврат НовыйНомер;
КонецФункции
Благодаря исключительной блокировке, если второй пользователь попытается записать элемент с тем же префиксом, его сеанс послушно дождется завершения транзакции первого пользователя, после чего получит действительно следующий по порядку уникальный номер.
---
Теперь объединим логику определения префикса и получения нового номера. Наиболее подходящее место для генерации кода — предопределенная процедура ПередЗаписью в модуле объекта справочника Номенклатура. Это гарантирует заполнение реквизита независимо от того, как создается элемент — интерактивно пользователем или программно при импорте с сайта.
Разберем структуру обработчика:
Процедура ПередЗаписью(Отказ)
// Пропускаем выполнение, если идет загрузка данных (например, обмен или восстановление)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Наша автонумерация предназначена только для элементов справочника, группы пропускаем
Если ЭтоГруппа Тогда
Возврат;
КонецЕсли;
// Генерируем новый код только для новых элементов или если реквизит был очищен
Если ЭтоНовый() Или НЕ ЗначениеЗаполнено(ДопКод) Тогда
Префикс = ПолучитьПрефиксПоИерархии(Родитель);
Если ЗначениеЗаполнено(Префикс) Тогда
// Выберите один из двух методов генерации номера:
// Вариант А (простой):
// НовыйНомер = ПолучитьСледующийНомерПоТаблице(Префикс);
// Вариант Б (надежный через регистр сведений):
НовыйНомер = ПолучитьСледующийНомерИзРегистра(Префикс);
// Собираем итоговую строку. При необходимости можно использовать форматирование,
// например, для получения кодов фиксированной длины (TV000045):
// ДопКод = Префикс + Формат(НовыйНомер, "ЧЦ=6; ЧДЦ=0; ЧГ=0");
ДопКод = Префикс + Формат(НовыйНомер, "ЧГ=0");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
---
Чтобы пользователи случайно не изменили сформированный автоматикой код и не нарушили синхронизацию с сайтом, поле ДопКод на форме элемента необходимо сделать доступным только для чтения.
Для этого откроем Форму элемента справочника Номенклатура и пропишем простое правило в обработчике события ПриСозданииНаСервере:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Устанавливаем свойство элемента формы "ТолькоПросмотр"
Если Элементы.Найти("ДопКод") <> Неопределено Тогда
Элементы.ДопКод.ТолькоПросмотр = Истина;
КонецЕсли;
КонецПроцедуры
Теперь пользователи смогут видеть автоматически присвоенный код номенклатуры при просмотре карточки, но не смогут изменить его вручную.
Мы успешно решили поставленную задачу. Реализованная схема является отказоустойчивой, производительной и полностью готовой к использованию в высоконагруженных промышленных базах данных 1С.