При разработке обработок массового создания документов в 1С:Предприятие программисты часто сталкиваются с ошибкой: «Значение поля "Номер" не уникально». Эта проблема особенно актуальна, когда документы создаются в автоматическом режиме (например, обработкой распределения сумм), но параллельно пользователи продолжают вводить документы вручную. В результате нумерация сбивается, и автоматический процесс аварийно завершается.
В этой статье мы подробно разберем, как программно обойти эту ошибку, исправим критические уязвимости в коде, предложенном на форуме, а также рассмотрим архитектурно правильный транзакционный подход для работы в многопользовательском режиме.
Рассмотрим ситуацию, когда в коде обработки жестко или расчетным путем задается начальный номер документа, который затем увеличивается на единицу на каждом шаге цикла. Если в базе данных уже существует документ с таким номером (например, созданный бухгалтером вручную или выгруженный из другой системы), СУБД заблокирует запись нового документа для предотвращения нарушения логической целостности базы данных.
Для решения этой проблемы нам необходимо научить систему реагировать на занятый номер: если номер уже существует в системе, обработка должна автоматически прибавить к нему единицу (выполнить инкремент) и повторять эту проверку до тех пор, пока не будет найден первый свободный номер.
На форуме было предложено решение, основанное на предварительном поиске существующего документа по его номеру и дате перед непосредственной записью. Проанализируем этот алгоритм по шагам.
Сначала мы формируем текстовое представление номера с учетом префикса организации и лидирующих нулей. Для этого используется встроенная функция Прав(), которая дополняет номер нулями слева до нужной длины (в данном случае — 6 символов).
Затем мы выполняем поиск документа в базе данных. В кодовой вставке из Сообщения 9 на форуме содержалась критическая ошибка: поиск выполнялся по метаданным Документы.ПоступлениеТоваровУслуг, в то время как сама обработка создавала документы типа РеализацияТоваровУслуг. Ниже представлен исправленный и оптимизированный вариант этого кода:
// Определяем исходный номер для проверки
НомерДляПроверки = Объект.Префикс + Прав("00000000" + Формат(НомерДок, "ЧГ=0"), 6);
// Ищем документ "Реализация товаров и услуг" по номеру и дате
СсылкаНаДокумент = Документы.РеализацияТоваровУслуг.НайтиПоНомеру(НомерДляПроверки, ДатаДок);
// Если документ с таким номером найден, запускаем цикл инкремента
Пока СсылкаНаДокумент <> Документы.РеализацияТоваровУслуг.ПустаяСсылка() Цикл
НомерДок = НомерДок + 1; // Увеличиваем числовой счетчик на 1
НомерДляПроверки = Объект.Префикс + Прав("00000000" + Формат(НомерДок, "ЧГ=0"), 6);
// Повторяем поиск с новым номером
СсылкаНаДокумент = Документы.РеализацияТоваровУслуг.НайтиПоНомеру(НомерДляПроверки, ДатаДок);
КонецЦикла;
// Присваиваем свободный номер нашему документу
Реализация.Номер = НомерДляПроверки;
Важное примечание: Обратите внимание на метод НайтиПоНомеру(). Вторым параметром в него передается дата документа. Это критически важно, так как в 1С периодичность нумерации документов обычно установлена в пределах года. Передача даты позволяет платформе искать дубли только в рамках соответствующего периода (года).
Несмотря на то, что вышеописанный метод успешно решает локальную задачу автора темы, он обладает серьезным архитектурным недостатком, который может проявиться при одновременной работе нескольких пользователей в базе данных.
Представим сценарий:
000045. Метод НайтиПоНомеру() возвращает пустую ссылку — номер свободен.000045.Реализация.Записать(), но транзакция прерывается СУБД с ошибкой неуникальности, так как номер уже занят другим процессом мгновением ранее.Такое явление называется «состоянием гонки». Кроме того, постоянные запросы к базе данных через НайтиПоНомеру() внутри большого цикла создают избыточную нагрузку на дисковую подсистему сервера.
Для создания отказоустойчивого кода, работающего в многопользовательской среде, рекомендуется использовать конструкцию Попытка ... Исключение непосредственно в момент записи документа. В этом случае мы не тратим ресурсы на предварительный поиск, а доверяем контроль уникальности самой СУБД на уровне транзакции.
Рассмотрим, как реализовать этот подход на практике:
Записано = Ложь;
Пока Не Записано Цикл
Попытка
// Формируем строковое представление номера
Реализация.Номер = Объект.Префикс + Прав("00000000" + Формат(НомерДок, "ЧГ=0"), 6);
// Пытаемся записать документ в режиме транзакции
Реализация.Записать(РежимЗаписиДокумента.Запись);
// Если запись прошла без ошибок, выходим из цикла
Записано = Истина;
Исключение
// Если СУБД вернула ошибку (например, из-за неуникальности номера),
// перехватываем её, увеличиваем счетчик и пробуем снова
НомерДок = НомерДок + 1;
КонецПопытки;
КонецЦикла;
Этот метод гарантирует 100% надежность записи. Даже если номер будет перехвачен другим пользователем за долю секунды до записи, система просто уйдет на следующую итерацию цикла, увеличит номер еще раз и успешно завершит операцию, не прерывая выполнение всей обработки.
Частые ошибки неуникальности номеров — это следствие нарушения правил работы с нумерацией в системе. Проанализируем основные причины сбоев:
ОБ00-0000123), а пользователь вручную сократил номер нового документа до 124 (удалив лидирующие нули), встроенный механизм автонумерации 1С ломается. Платформа больше не может автоматически инкрементировать строку и начинает выдавать ошибки.123/1) или дефисов в конец номера лишает платформу возможности корректно распознавать числовую часть.Чтобы восстановить штатную работу нумератора в базе данных, выполните следующие административные действия:
125 на ОБ00-0000125). Сделать это можно с помощью стандартной обработки «Групповое изменение реквизитов».ОбновитьНумерациюОбъектов(Метаданные.Документы.РеализацияТоваровУслуг);