Как правильно реализовать собственную генерацию строковых кодов справочника в 1С при переходе от 999 к 1000?

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

При работе с системой 1С:Предприятие мы часто сталкиваемся с необходимостью генерировать уникальные коды для элементов справочников. Иногда разработчики создают собственную логику генерации, например, когда код состоит из префикса и числовой части: [Префикс]-[СамКод]. Однако такой подход может привести к неочевидным проблемам, особенно при переходе числовой части от 999 к 1000. Давайте разберем эту ситуацию и рассмотрим, как ее корректно решить.

Суть проблемы:

Предположим, у нас есть справочник с пользовательской генерацией кодов по шаблону [Префикс]-[СамКод]. При создании нового элемента мы пытаемся найти максимальное существующее значение СамКод, увеличить его на 1 и использовать для нового элемента. Для этого мы могли бы выполнить запрос к базе данных, пытаясь найти МАКСИМУМ(Спр.Код). Допустим, в справочнике есть следующие коды:

  1. префикс1-001
  2. префикс1-100
  3. префикс1-999

Когда мы создаем новый элемент, все работает корректно: максимальное числовое значение равно 999, и следующий код будет префикс1-1000. Проблема возникает, когда в справочнике уже есть код префикс1-1000. Если мы снова попробуем найти МАКСИМУМ(Спр.Код) с помощью обычного запроса, платформа выполнит лексикографическое (строковое) сравнение. В этом случае, строковое значение "префикс1-999" будет считаться "больше" или "максимальнее" чем "префикс1-1000", поскольку при сравнении строк символы сравниваются по порядку: '9' > '1'. В результате, запрос вернет префикс1-999 как максимум, и система попытается создать новый код префикс1-1000, который уже существует, что приведет к ошибке уникальности или некорректной нумерации.

Пользователи могут предъявлять специфические требования к внешнему виду кода: например, минимальная длина числовой части должна быть 3 символа (001, 050), но при этом недопустимы избыточные нули (0000001) или фиксированная длина, ограничивающая переход на большее число разрядов (001001). Это еще больше усложняет задачу.

Разберем различные подходы к решению этой проблемы.

1. Использование стандартной автонумерации 1С с префиксами (Рекомендуемый подход)

Самый надежный и правильный способ избежать подобных проблем – это максимально использовать встроенные механизмы платформы 1С. Платформа 1С:Предприятие 8 уже имеет мощный механизм автонумерации, который:

  1. Гарантирует уникальность кодов.
  2. Корректно обрабатывает числовые последовательности (т.е., 0999 за 1000).
  3. Автоматически добавляет лидирующие нули для обеспечения правильной строковой сортировки (например, 0001, 0010, 0100, 1000).

Как это работает:

  1. Настройка префикса: Вы можете установить префикс для кода элемента справочника, используя обработчик события ПриУстановкеНовогоКода в модуле объекта справочника. Этот обработчик позволяет программно управлять префиксом нового кода.

    
    Процедура ПриУстановкеНовогоКода(СтандартнаяОбработка, Префикс)
    
        // ПриУстановкеНовогоКода вызывается платформой перед генерацией нового кода.
        // Параметр Префикс позволяет установить или изменить префикс, который будет использоваться
        // стандартным механизмом автонумерации.
    
        // Пример: устанавливаем фиксированный префикс для данного справочника
        // Если требуется более сложная логика (например, префикс по организации, подразделению),
        // ее можно реализовать здесь.
        Префикс = "ПРФ-"; 
    
        // СтандартнаяОбработка по умолчанию имеет значение Истина.
        // Если мы оставим ее Истина (или не будем ее менять), платформа сама сгенерирует
        // числовую часть кода, добавив лидирующие нули и обеспечив уникальность и последовательность.
        // Например, если максимальный код был ПРФ-0001, следующий будет ПРФ-0002.
        // Если максимальный код был ПРФ-0999, следующий будет ПРФ-1000,
        // поскольку платформа учитывает числовое значение, а не только строковое сравнение.
    
    КонецПроцедуры
    

    В этом случае платформа сама заботится о правильной нумерации и добавлении лидирующих нулей (например, ПРФ-0001, ПРФ-0050, ПРФ-0999, ПРФ-1000).

  2. Отображение кода для пользователя: Пользовательские требования к внешнему виду кода, такие как отсутствие избыточных лидирующих нулей (например, ПРФ-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"
    

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

2. Доработка собственной логики генерации кода (При крайней необходимости)

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

  1. Извлечение числовой части: Извлекаем из всех существующих кодов справочника только числовую часть после префикса.

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

    
    // Пример функции для безопасного преобразования строки в число
    Функция ПреобразоватьВЧисло(СтрокаЧисла)
        Перем ЧисловоеЗначение;
        Попытка
            ЧисловоеЗначение = Число(СтрокаЧисла);
        Исключение
            // Если строка содержит не только цифры или пуста, возвращаем 0 или другое значение
            ЧисловоеЗначение = 0; 
        КонецПопытки;
        Возврат ЧисловоеЗначение;
    КонецФункции
    
  3. Поиск максимального числа: Среди всех полученных чисел находим максимальное значение.

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

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

  4. Форматирование и присвоение: Увеличиваем максимальное число на 1 и форматируем его в строку с необходимым количеством лидирующих нулей (или минимальной длиной), а затем объединяем с префиксом.

    
    // Форматируем следующее число, чтобы оно соответствовало шаблону (например, минимум 3 цифры)
    // Используем функцию Формат для добавления лидирующих нулей
    // "ЧЦ=3" - минимальная ширина 3 символа. Если число 1, будет "001". Если 1200, будет "1200".
    // "ЧГ=" - убирает разделители групп.
    ФорматированноеСледующееЧисло = Формат(СледующееЧисло, "ЧЦ=3; ЧГ="); 
    
    НовыйКод = "префикс1-" + ФорматированноеСледующееЧисло; // Собираем полный код
    
    // Устанавливаем полученный код элементу
    // Например: ЭтотОбъект.Код = НовыйКод;
    

Вопросы безопасности и транзакций при собственной генерации:

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

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

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


// Пример использования транзакции и блокировки для безопасной генерации кода
// Этот код следует размещать в обработчике ПриУстановкеНовогоКода 
// или ПередЗаписью, но только если СтандартнаяОбработка = Ложь.

Процедура УстановитьСвойНовыйКод(Знач Объект)

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

        // 1. Получаем текущий максимальный номер
        Запрос = Новый Запрос;
        Запрос.Текст = 
        "ВЫБРАТЬ
        |    Спр.Код КАК Код
        |ИЗ
        |    Справочник.ВашСправочник КАК Спр
        |ГДЕ
        |    Спр.Код ПОДОБНО &Префикс + ""%""";

        Запрос.УстановитьПараметр("Префикс", "префикс1-"); // Ваш префикс

        РезультатЗапроса = Запрос.Выполнить().Выгрузить();

        МаксимальноеЧисло = 0;
        Для Каждого СтрокаКода Из РезультатЗапроса Цикл
            ЧисловаяЧастьСтрока = ИзвлечьЧисловуюЧасть(СтрокаКода.Код); // Использовать функцию, описанную выше
            ТекущееЧисло = ПреобразоватьВЧисло(ЧисловаяЧастьСтрока); // Использовать функцию, описанную выше
            Если ТекущееЧисло > МаксимальноеЧисло Тогда
                МаксимальноеЧисло = ТекущееЧисло;
            КонецЕсли;
        КонецЦикла;

        СледующееЧисло = МаксимальноеЧисло + 1;

        // 2. Форматируем и присваиваем новый код
        ФорматированноеСледующееЧисло = Формат(СледующееЧисло, "ЧЦ=3; ЧГ="); 
        Объект.Код = "префикс1-" + ФорматированноеСледующееЧисло;

        // Фиксируем транзакцию (освобождаем блокировку)
        ЗафиксироватьТранзакцию();

    Исключение
        // При возникновении ошибки откатываем транзакцию
        ОтменитьТранзакцию();
        Сообщить("Ошибка при генерации кода: " + ОписаниеОшибки());
        ВызватьИсключение; // Передаем ошибку дальше
    КонецПопытки;

КонецПроцедуры

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

3. Использование дополнительного реквизита вместо системного кода

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

  1. Создание нового реквизита: Добавьте в справочник новый строковый реквизит, например, ПользовательскийКод или Артикул.

  2. Собственная логика для нового реквизита: Для этого реквизита вы можете реализовать любую свою логику генерации кода, как описано в пункте 2, не затрагивая системный код. Таким образом, системный код будет генерироваться платформой автоматически (например, просто последовательными числами 000000001, 000000002), а пользователь будет работать с вашим ПользовательскийКод.

  3. Отображение: Отображайте новый реквизит на форме элемента и в списках вместо стандартного кода.

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

Дополнительные рекомендации

  1. Контроль за ручным вводом: Независимо от выбранного подхода, мы должны предусмотреть контроль за ручным вводом кодов. Если пользователь вручную введет нечисловую часть (например, "префикс1-ZZZ"), это нарушит вашу логику извлечения числовой части и поиска максимума. В случае использования стандартной нумерации, ручной ввод может сбить последовательность.

  2. Длина кода: Помните, что длина кода справочника в 1С ограничена (обычно 9 или 11 символов для номеров, но может быть увеличена для строкового кода до 150 символов). Учитывайте это при формировании длинных префиксов и числовых частей.

  3. Целостность данных: Сбои в автоматической нумерации могут возникать не только из-за ошибок в логике, но и из-за ручного редактирования кодов или ошибок в самой 1С. Для типовых конфигураций 1С существуют инструменты для проверки и исправления нумерации (например, "Экспресс-проверка ведения учета"). В случае собственных механизмов, возможно, потребуется разработка специальных обработок для выявления и исправления таких проблем.

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

← На главную