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