При работе с системой 1С:Предприятие мы часто сталкиваемся с необходимостью генерировать уникальные коды для элементов справочников. Иногда разработчики создают собственную логику генерации, например, когда код состоит из префикса и числовой части: [Префикс]-[СамКод]. Однако такой подход может привести к неочевидным проблемам, особенно при переходе числовой части от 999 к 1000. Давайте разберем эту ситуацию и рассмотрим, как ее корректно решить.
Суть проблемы:
Предположим, у нас есть справочник с пользовательской генерацией кодов по шаблону [Префикс]-[СамКод]. При создании нового элемента мы пытаемся найти максимальное существующее значение СамКод, увеличить его на 1 и использовать для нового элемента. Для этого мы могли бы выполнить запрос к базе данных, пытаясь найти МАКСИМУМ(Спр.Код). Допустим, в справочнике есть следующие коды:
префикс1-001префикс1-100префикс1-999Когда мы создаем новый элемент, все работает корректно: максимальное числовое значение равно 999, и следующий код будет префикс1-1000. Проблема возникает, когда в справочнике уже есть код префикс1-1000. Если мы снова попробуем найти МАКСИМУМ(Спр.Код) с помощью обычного запроса, платформа выполнит лексикографическое (строковое) сравнение. В этом случае, строковое значение "префикс1-999" будет считаться "больше" или "максимальнее" чем "префикс1-1000", поскольку при сравнении строк символы сравниваются по порядку: '9' > '1'. В результате, запрос вернет префикс1-999 как максимум, и система попытается создать новый код префикс1-1000, который уже существует, что приведет к ошибке уникальности или некорректной нумерации.
Пользователи могут предъявлять специфические требования к внешнему виду кода: например, минимальная длина числовой части должна быть 3 символа (001, 050), но при этом недопустимы избыточные нули (0000001) или фиксированная длина, ограничивающая переход на большее число разрядов (001001). Это еще больше усложняет задачу.
Разберем различные подходы к решению этой проблемы.
Самый надежный и правильный способ избежать подобных проблем – это максимально использовать встроенные механизмы платформы 1С. Платформа 1С:Предприятие 8 уже имеет мощный механизм автонумерации, который:
0999 за 1000).0001, 0010, 0100, 1000).Как это работает:
Настройка префикса: Вы можете установить префикс для кода элемента справочника, используя обработчик события ПриУстановкеНовогоКода в модуле объекта справочника. Этот обработчик позволяет программно управлять префиксом нового кода.
Процедура ПриУстановкеНовогоКода(СтандартнаяОбработка, Префикс)
// ПриУстановкеНовогоКода вызывается платформой перед генерацией нового кода.
// Параметр Префикс позволяет установить или изменить префикс, который будет использоваться
// стандартным механизмом автонумерации.
// Пример: устанавливаем фиксированный префикс для данного справочника
// Если требуется более сложная логика (например, префикс по организации, подразделению),
// ее можно реализовать здесь.
Префикс = "ПРФ-";
// СтандартнаяОбработка по умолчанию имеет значение Истина.
// Если мы оставим ее Истина (или не будем ее менять), платформа сама сгенерирует
// числовую часть кода, добавив лидирующие нули и обеспечив уникальность и последовательность.
// Например, если максимальный код был ПРФ-0001, следующий будет ПРФ-0002.
// Если максимальный код был ПРФ-0999, следующий будет ПРФ-1000,
// поскольку платформа учитывает числовое значение, а не только строковое сравнение.
КонецПроцедуры
В этом случае платформа сама заботится о правильной нумерации и добавлении лидирующих нулей (например, ПРФ-0001, ПРФ-0050, ПРФ-0999, ПРФ-1000).
Отображение кода для пользователя: Пользовательские требования к внешнему виду кода, такие как отсутствие избыточных лидирующих нулей (например, ПРФ-1 вместо ПРФ-0001 или ПРФ-50 вместо ПРФ-0050), могут быть удовлетворены без изменения способа хранения кода. Вы можете хранить код в стандартном формате 1С (с лидирующими нулями для корректной сортировки), а для отображения в формах, отчетах или печатных формах формировать его по требуемому шаблону.
// Пример формирования отображаемого кода без избыточных лидирующих нулей,
// но с учетом минимальной длины числовой части (например, 3 символа),
// как было в примере "001", "050", "1200".
Функция ПолучитьКодДляОтображения(КодЭлемента)
Перем ПрефиксЧасть;
Перем ЧисловаяЧастьСтрока;
Перем ЧисловоеЗначение;
Перем ОтображаемыйКод;
// Ищем разделитель для отделения префикса от числовой части
ИндексРазделителя = СтрНайти(КодЭлемента, "-");
Если ИндексРазделителя > 0 Тогда
// Отделяем префикс
ПрефиксЧасть = Лев(КодЭлемента, ИндексРазделителя);
// Отделяем числовую часть (строку с лидирующими нулями)
ЧисловаяЧастьСтрока = Сред(КодЭлемента, ИндексРазделителя + 1);
Попытка
// Преобразуем числовую часть в число, чтобы убрать лидирующие нули
ЧисловоеЗначение = Число(ЧисловаяЧастьСтрока);
Исключение
// Если преобразование не удалось (например, в коде есть буквы),
// оставляем строковую часть как есть
ЧисловоеЗначение = Неопределено;
КонецПопытки;
Если ЧисловоеЗначение <> Неопределено Тогда
// Форматируем числовую часть:
// "ЧЦ=3" - минимальная ширина 3 символа, добавляет лидирующие нули при необходимости (1 -> 001, 50 -> 050)
// "ЧВН=" - не выводить незначащие нули в дробной части
// "ЧГ=" - не использовать разделитель групп цифр (например, 1 000)
// Примечание: если число > 999 (например, 1200), то "ЧЦ=3" просто выведет "1200",
// не добавляя нулей, так как это превышает минимальную длину.
ФорматированнаяЧисловаяЧасть = Формат(ЧисловоеЗначение, "ЧЦ=3; ЧВН=; ЧГ=");
ОтображаемыйКод = ПрефиксЧасть + ФорматированнаяЧисловаяЧасть;
Иначе
// Если не удалось преобразовать в число, отображаем исходный код
ОтображаемыйКод = КодЭлемента;
КонецЕсли;
Иначе
// Если разделителя нет, значит код без префикса или другого формата, отображаем как есть
ОтображаемыйКод = КодЭлемента;
КонецЕсли;
Возврат ОтображаемыйКод;
КонецФункции
// Пример использования:
// КодХранимый = "ПРФ-0001";
// КодДляПользователя = ПолучитьКодДляОтображения(КодХранимый); // Результат: "ПРФ-001"
//
// КодХранимый = "ПРФ-0050";
// КодДляПользователя = ПолучитьКодДляОтображения(КодХранимый); // Результат: "ПРФ-050"
//
// КодХранимый = "ПРФ-0999";
// КодДляПользователя = ПолучитьКодДляОтображения(КодХранимый); // Результат: "ПРФ-999"
//
// КодХранимый = "ПРФ-1000";
// КодДляПользователя = ПолучитьКодДляОтображения(КодХранимый); // Результат: "ПРФ-1000"
//
// КодХранимый = "ПРФ-0000001"; // Неправильный формат, но если такой код попадет
// КодДляПользователя = ПолучитьКодДляОтображения(КодХранимый); // Результат: "ПРФ-001"
Такой подход обеспечивает корректное хранение и сортировку кодов внутри системы, одновременно удовлетворяя специфические требования пользователя к их представлению. Вы можете использовать эту функцию при выводе кода в элементах формы, в запросах, формирующих отчеты, или в макетах печатных форм.
Если по каким-то причинам вы не можете или не хотите использовать стандартную автонумерацию, и вам абсолютно необходимо реализовать собственную логику, мы должны устранить недостаток строкового сравнения. Для этого нам потребуется:
Извлечение числовой части: Извлекаем из всех существующих кодов справочника только числовую часть после префикса.
// Пример функции для извлечения числовой части из строкового кода
// Предполагается, что формат "ПРЕФИКС-ЧИСЛО"
Функция ИзвлечьЧисловуюЧасть(Код)
Перем ИндексРазделителя;
Перем ЧисловаяЧастьСтрока;
ИндексРазделителя = СтрНайти(Код, "-");
Если ИндексРазделителя > 0 Тогда
ЧисловаяЧастьСтрока = Сред(Код, ИндексРазделителя + 1);
Возврат ЧисловаяЧастьСтрока;
Иначе
// Если код не соответствует шаблону, можно вернуть пустую строку или сам код
Возврат ""; // Или Код
КонецЕсли;
КонецФункции
Преобразование в число: Каждую извлеченную числовую часть преобразуем в число.
// Пример функции для безопасного преобразования строки в число
Функция ПреобразоватьВЧисло(СтрокаЧисла)
Перем ЧисловоеЗначение;
Попытка
ЧисловоеЗначение = Число(СтрокаЧисла);
Исключение
// Если строка содержит не только цифры или пуста, возвращаем 0 или другое значение
ЧисловоеЗначение = 0;
КонецПопытки;
Возврат ЧисловоеЗначение;
КонецФункции
Поиск максимального числа: Среди всех полученных чисел находим максимальное значение.
// Пример запроса для поиска максимальной числовой части
// Этот запрос должен быть выполнен в контексте вашей генерации кода
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр.Код КАК Код
|ИЗ
| Справочник.ВашСправочник КАК Спр
|ГДЕ
| Спр.Код ПОДОБНО &Префикс + ""%"""; // Учитываем префикс
Запрос.УстановитьПараметр("Префикс", "префикс1-"); // Ваш префикс
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
МаксимальноеЧисло = 0;
Для Каждого СтрокаКода Из РезультатЗапроса Цикл
ЧисловаяЧастьСтрока = ИзвлечьЧисловуюЧасть(СтрокаКода.Код);
ТекущееЧисло = ПреобразоватьВЧисло(ЧисловаяЧастьСтрока);
Если ТекущееЧисло > МаксимальноеЧисло Тогда
МаксимальноеЧисло = ТекущееЧисло;
КонецЕсли;
КонецЦикла;
СледующееЧисло = МаксимальноеЧисло + 1;
Важно: убедитесь, что ваш запрос фильтрует коды только с нужным префиксом, чтобы не учитывать коды других групп.
Форматирование и присвоение: Увеличиваем максимальное число на 1 и форматируем его в строку с необходимым количеством лидирующих нулей (или минимальной длиной), а затем объединяем с префиксом.
// Форматируем следующее число, чтобы оно соответствовало шаблону (например, минимум 3 цифры)
// Используем функцию Формат для добавления лидирующих нулей
// "ЧЦ=3" - минимальная ширина 3 символа. Если число 1, будет "001". Если 1200, будет "1200".
// "ЧГ=" - убирает разделители групп.
ФорматированноеСледующееЧисло = Формат(СледующееЧисло, "ЧЦ=3; ЧГ=");
НовыйКод = "префикс1-" + ФорматированноеСледующееЧисло; // Собираем полный код
// Устанавливаем полученный код элементу
// Например: ЭтотОбъект.Код = НовыйКод;
Вопросы безопасности и транзакций при собственной генерации:
При собственной генерации кодов в многопользовательской среде критически важно обеспечить транзакционную безопасность. Если несколько пользователей одновременно попытаются создать новый элемент, используя вашу логику:
Чтобы избежать этого, мы должны явно использовать транзакции и управляемые блокировки. Процесс генерации кода должен быть обернут в транзакцию, а таблица, из которой мы ищем максимальный код, должна быть заблокирована.
// Пример использования транзакции и блокировки для безопасной генерации кода
// Этот код следует размещать в обработчике ПриУстановкеНовогоКода
// или ПередЗаписью, но только если СтандартнаяОбработка = Ложь.
Процедура УстановитьСвойНовыйКод(Знач Объект)
// Начинаем транзакцию
НачатьТранзакцию();
Попытка
// Создаем блокировку данных.
// Это предотвращает чтение и изменение данных другими сеансами
// до завершения текущей транзакции.
БлокировкаДанных = Новый БлокировкаДанных;
ЭлементБлокировки = БлокировкаДанных.Добавить("Справочник.ВашСправочник");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный; // Эксклюзивная блокировка
// Устанавливаем фильтр, если блокировка нужна только для части данных
// ЭлементБлокировки.УстановитьЗначение("Префикс", "ПРФ-"); // Пример фильтра
БлокировкаДанных.Заблокировать();
// 1. Получаем текущий максимальный номер
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр.Код КАК Код
|ИЗ
| Справочник.ВашСправочник КАК Спр
|ГДЕ
| Спр.Код ПОДОБНО &Префикс + ""%""";
Запрос.УстановитьПараметр("Префикс", "префикс1-"); // Ваш префикс
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
МаксимальноеЧисло = 0;
Для Каждого СтрокаКода Из РезультатЗапроса Цикл
ЧисловаяЧастьСтрока = ИзвлечьЧисловуюЧасть(СтрокаКода.Код); // Использовать функцию, описанную выше
ТекущееЧисло = ПреобразоватьВЧисло(ЧисловаяЧастьСтрока); // Использовать функцию, описанную выше
Если ТекущееЧисло > МаксимальноеЧисло Тогда
МаксимальноеЧисло = ТекущееЧисло;
КонецЕсли;
КонецЦикла;
СледующееЧисло = МаксимальноеЧисло + 1;
// 2. Форматируем и присваиваем новый код
ФорматированноеСледующееЧисло = Формат(СледующееЧисло, "ЧЦ=3; ЧГ=");
Объект.Код = "префикс1-" + ФорматированноеСледующееЧисло;
// Фиксируем транзакцию (освобождаем блокировку)
ЗафиксироватьТранзакцию();
Исключение
// При возникновении ошибки откатываем транзакцию
ОтменитьТранзакцию();
Сообщить("Ошибка при генерации кода: " + ОписаниеОшибки());
ВызватьИсключение; // Передаем ошибку дальше
КонецПопытки;
КонецПроцедуры
Использование транзакций требует тщательного подхода, так как некорректное применение может привести к взаимоблокировкам или другим проблемам с производительностью. Длительные транзакции следует избегать.
Иногда, чтобы не усложнять работу с системным кодом справочника, который предназначен в первую очередь для внутренней идентификации и сортировки, мы можем создать отдельный реквизит справочника для пользовательского "кода" или "артикула".
Создание нового реквизита: Добавьте в справочник новый строковый реквизит, например, ПользовательскийКод или Артикул.
Собственная логика для нового реквизита: Для этого реквизита вы можете реализовать любую свою логику генерации кода, как описано в пункте 2, не затрагивая системный код. Таким образом, системный код будет генерироваться платформой автоматически (например, просто последовательными числами 000000001, 000000002), а пользователь будет работать с вашим ПользовательскийКод.
Отображение: Отображайте новый реквизит на форме элемента и в списках вместо стандартного кода.
Этот подход позволяет избежать многих сложностей, связанных с обходом стандартных механизмов платформы, и дает полную свободу в реализации пользовательских требований к формату идентификатора.
Контроль за ручным вводом: Независимо от выбранного подхода, мы должны предусмотреть контроль за ручным вводом кодов. Если пользователь вручную введет нечисловую часть (например, "префикс1-ZZZ"), это нарушит вашу логику извлечения числовой части и поиска максимума. В случае использования стандартной нумерации, ручной ввод может сбить последовательность.
Длина кода: Помните, что длина кода справочника в 1С ограничена (обычно 9 или 11 символов для номеров, но может быть увеличена для строкового кода до 150 символов). Учитывайте это при формировании длинных префиксов и числовых частей.
Целостность данных: Сбои в автоматической нумерации могут возникать не только из-за ошибок в логике, но и из-за ручного редактирования кодов или ошибок в самой 1С. Для типовых конфигураций 1С существуют инструменты для проверки и исправления нумерации (например, "Экспресс-проверка ведения учета"). В случае собственных механизмов, возможно, потребуется разработка специальных обработок для выявления и исправления таких проблем.
Таким образом, мы видим, что наиболее надежным и рекомендуемым решением является использование стандартной автонумерации 1С с префиксами и форматированием кода для пользователя при отображении. Если же этот вариант не подходит, то доработка собственной логики должна быть выполнена очень тщательно, с учетом всех нюансов строкового сравнения, преобразования типов и транзакционной безопасности.