Как реализовать уникальную нумерацию заказов с ежемесячным обнулением и проверкой дублей в 1С

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

В процессе разработки на платформе 1С часто возникает задача создания кастомной системы нумерации, которая не укладывается в стандартные механизмы платформы. Типичный пример — номер заказа, который должен содержать в себе префикс в виде года и месяца (ГГММ) и порядковый номер, обнуляющийся в начале каждого месяца (например, 25010101). При этом необходимо обеспечить абсолютную уникальность номера, исключив влияние "человеческого фактора" (ручной правки) и параллельной работы нескольких пользователей.

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

Анализ проблемы: почему стандартных средств может быть недостаточно

Стандартная автонумерация 1С работает достаточно надежно, но она имеет свои особенности. Платформа 8.1 и более поздние версии используют кэширование номеров для повышения производительности. Однако, если бизнес-логика требует сложного формата (ГГММ + счетчик) и при этом допускается ручное редактирование номеров, стандартный механизм может начать выдавать дубли или пропускать номера. Рассмотрим ситуацию, когда пользователь вручную установил номер 25010110, в то время как счетчик в системе был на значении 25010105. При создании следующего документа система может попытаться присвоить уже занятый номер 25010110, что приведет к ошибке уникальности.

Решение №1: Использование константы и транзакционной блокировки

Один из самых простых и популярных способов — хранение текущего значения счетчика в Константе. Чтобы избежать ситуации, когда два пользователя одновременно считывают одно и то же значение константы, мы должны использовать объект БлокировкаДанных. Это гарантирует, что пока один процесс увеличивает номер, остальные будут ждать завершения транзакции.

Разберем алгоритм по шагам:

  1. Начинаем транзакцию.
  2. Устанавливаем исключительную блокировку на константу.
  3. Получаем текущее значение константы.
  4. Инкрементируем значение.
  5. Формируем номер и проверяем его наличие в базе данных (на случай ручного ввода).
  6. Записываем новое значение в константу и присваиваем номер документу.

Пример программной реализации в модуле объекта:


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

Решение №2: Регистр сведений для управления нумерацией

Использование константы не всегда удобно, если нужно хранить историю или разделять нумерацию по организациям, годам или месяцам. Более гибкий подход — использование Регистра Сведений (например, НумерацияДокументов) с измерениями Период (тип Дата, начало месяца) и ТипДокумента. В ресурсе будем хранить ПоследнийНомер.

Преимущества этого метода:

Рассмотрим логику поиска "дырок" в нумерации. Если мы хотим быть максимально аккуратными, мы можем использовать запрос для поиска максимального номера в текущем месяце:


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

Запрос.УстановитьПараметр("НачалоМесяца", НачалоМесяца(ТекущаяДата()));
Запрос.УстановитьПараметр("КонецМесяца", КонецМесяца(ТекущаяДата()));

Результат = Запрос.Выполнить().Выбрать();
Если Результат.Следующий() И ЗначениеЗаполнено(Результат.МаксНомер) Тогда
    // Извлекаем числовую часть из строки номера и инкрементируем
    // Логика обработки строки...
КонецЕсли;

Решение №3: Почему "запрос в цикле" — это риск?

Автор темы предлагал выполнять запрос в цикле до 20 раз, проверяя каждый следующий номер. Проанализируем ситуацию: если в системе работают одновременно 50 операторов и происходит массовая запись заказов, "запрос в цикле" может привести к значительным задержкам. Каждый запрос — это обращение к СУБД. Если номер уже занят, и мы начинаем перебор, транзакция удерживается открытой всё это время. Это классический пример того, как небольшая "заплатка" может превратиться в узкое место производительности (bottleneck) при росте базы.

Вместо этого мы рекомендуем:

  1. Блокировать данные сразу: Использование объекта БлокировкаДанных в режиме Исключительный гарантирует, что вы — единственный, кто сейчас работает с этим номером.
  2. Минимизировать тело транзакции: Номер должен присваиваться в самый последний момент перед записью, чтобы время блокировки было ничтожно малым.
  3. Обработка исключений: Всегда используйте конструкцию Попытка...Исключение, так как в многопользовательской среде конфликт блокировок — это штатная ситуация, которую нужно корректно обработать, не пугая пользователя системными ошибками.

Практические советы по работе с нумерацией в 1С 8.1

Выясним причину возможных сбоев в старых версиях платформы. В 1С 8.1 механизм ОбновитьНумерациюОбъектов() часто помогал восстановить сбитую последовательность, но он не спасает от дублей, если логика формирования номера вынесена в код.

Проанализируем еще один важный момент: человеческий фактор. Чтобы избежать проблем, когда "кто-то вручную назначил номер 850", рекомендуется:

Подводя итог, наиболее надежным и современным способом для 1С (даже для версии 8.1) является сочетание Регистра сведений для хранения счетчиков и Объекта БлокировкаДанных для обеспечения целостности данных в момент записи. Это позволит вашей системе работать быстро и без ошибок даже при значительном увеличении потока заказов.

← На главную