Почему двоятся номера документов при программной нумерации в 1С и как это исправить?

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

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

Разберем подробно, почему возникают коллизии номеров при одновременной работе пользователей и как правильно организовать программное присвоение номеров в конфигурациях на платформе 1С:Предприятие.

Анализ причин задвоения номеров

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

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

Способ 1. Генерация номера в событии ПередЗаписью модуля объекта

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

Разберем пример кода для модуля объекта документа:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Проверяем, что документ новый и номер действительно требует генерации
    Если ЭтоНовый() И Не ЗначениеЗаполнено(ЭтотОбъект.Номер) Тогда
        
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ ПЕРВЫЕ 1
            |	Документ.Номер КАК Номер
            |ИЗ
            |	Документ.РеализацияТоваровУслуг КАК Документ
            |ГДЕ
            |	Документ.Дата МЕЖДУ &НачалоПериода И &КонецПериода
            |	И НЕ ПОДСТРОКА(Документ.Номер, 7, 2) В (""43"", ""44"")
            |
            |ДЛЯ ИЗМЕНЕНИЯ
            |
            |УПОРЯДОЧИТЬ ПО
            |	Документ.Номер УБЫВ";
            
        Запрос.УстановитьПараметр("НачалоПериода", НачалоГода(ЭтотОбъект.Дата));
        Запрос.УстановитьПараметр("КонецПериода", КонецГода(ЭтотОбъект.Дата));
        
        Выборка = Запрос.Выполнить().Выбрать();
        
        Если Выборка.Следующий() Тогда
            ЧисловойНомер = Число(Выборка.Номер) + 1;
            ЭтотОбъект.Номер = Прав("00000000000" + Формат(ЧисловойНомер, "ЧГ="), 11);
        Иначе
            ЭтотОбъект.Номер = "00000000001";
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

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

Способ 2. Формирование номера по маске с датой

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

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


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

Способ 3. Сброс платформенного кэша нумерации и исправление данных

Программные «костыли» в обход автонумерации усложняют сопровождение конфигурации. Наиболее надежным решением является нормализация уже записанных номеров в информационной базе и восстановление штатной работы платформы.

Выполним следующие шаги для полного устранения проблемы:

  1. Нормализация номеров. С помощью групповой обработки реквизитов исправим длину сбитых номеров, добавив ведущие нули, либо присвоим некорректным номерам отдельный префикс (например, ИСП-00001).
  2. Сброс кэша нумерации на сервере. Чтобы платформа заново пересчитала максимальные значения по таблицам базы данных без перезапуска службы сервера 1С, вызовем метод: ОбновитьНумерациюОбъектов(Метаданные.Документы.РеализацияТоваровУслуг);
  3. Возврат к типовой автонумерации. Отключим кастомные переопределения в модулях и доверим генерацию номеров встроенному механизму платформы, который корректно управляет блокировками счетчиков при записи.
← На главную