При автоматизации импорта данных из внешних источников (текстовых файлов, XML, JSON или API) разработчики часто сталкиваются с неприятной ситуацией. Программа пытается записать новый документ (например, ПоступлениеТоваровУслуг), но процесс прерывается ошибкой платформы: "Значение поля 'Номер' не уникально". При этом, если зайти в базу под тем же пользователем, открыть форму документа и нажать кнопку "Провести", система без каких-либо возражений сохраняет и проводит документ с тем же самым номером.
В этой статье мы подробно разберем, почему возникает такое поведение платформы 1С:Предприятие, проанализируем внутренние механизмы автонумерации и рассмотрим несколько надежных способов устранения этой ошибки в программном коде.
Для того чтобы понять суть проблемы, нам нужно заглянуть "под капот" платформы и проанализировать разницу между интерактивными действиями пользователя в интерфейсе и программным выполнением кода.
Существует три основные причины, по которым возникает подобная коллизия:
Когда пользователь открывает форму нового документа в интерфейсе, платформа автоматически вызывает метод УстановитьНовыйНомер(). Номер приводится к стандартному типу конфигурации (например, дополняется лидирующими нулями до фиксированной длины: вместо короткого "175" система формирует строку "0000-000175").
Если же мы программно присваиваем значение напрямую: НовДок.Номер = "175";, то в базу данных совершается попытка записать именно короткую строку "175" длиной 3 символа. Из-за отсутствия лидирующих нулей или префиксов платформа может расценивать этот номер как уникальный на этапе предварительной проверки, но при физической записи в таблицу базы данных происходит конфликт с уже существующим форматированным номером "0000-000175".
В метаданных большинства документов (особенно товарных и финансовых) установлена периодичность нумерации — Год. Это значит, что внутри одного календарного года все номера документов данного типа обязаны быть уникальными.
Частой ошибкой разработчиков является создание "быстрых" проверок перед загрузкой. Например, автор темы проверял базу на наличие дублей только за последние 3 дня. Если за эти 3 дня документа с номером "175" не было, алгоритм принимал решение создать новый документ. Однако документ с номером "175" (или "0000-000175") уже был создан в базе, например, месяц назад. Платформа, зная, что периодичность установлена в рамках года, блокирует запись из-за нарушения уникальности за весь текущий год.
При записи "кривых" номеров без лидирующих нулей программным путем встроенный нумератор платформы сбивается. 1С сравнивает строковые номера посимвольно слева направо. Для платформы строка "175" (без нулей) "больше", чем строка "0000-000175". Система начинает считать некорректно записанный номер самым старшим в текущем периоде и пытается генерировать последующие номера, отталкиваясь от него. Когда свободные разряды в такой последовательности заканчиваются, автонумерация блокируется.
---
Рассмотрим подробнее несколько практических сценариев, которые помогут полностью исключить возникновение этой ошибки при программной работе с документами.
Если вы загружаете документы от внешних контрагентов, их номера почти никогда не будут совпадать с вашей внутренней сквозной нумерацией. Пытаться записать сторонний номер (например, номер накладной поставщика) в системное поле Номер — это архитектурная ошибка, которая неизбежно приведет к сбою автонумерации базы.
Для хранения номеров документов сторонних контрагентов в типовых конфигурациях 1С (таких как "Бухгалтерия предприятия", "Управление торговлей" и др.) предусмотрены специальные реквизиты: НомерВходящегоДокумента (или ВходящийНомер) и ДатаВходящегоДокумента. Системное поле Номер при этом должно заполняться автоматически платформой.
Посмотрим на пример правильного программного создания документа с использованием стандартной автонумерации и заполнением входящего номера:
// Создаем новый документ программно
НовДок = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
// Заполняем дату и основные реквизиты шапки
НовДок.Дата = ТекущаяДата();
// Устанавливаем автонумерацию платформы
НовДок.УстановитьНовыйНомер();
// Записываем исходный номер из файла во входящие реквизиты
НомерИзФайла = ТекстовыйДокумент.ПолучитьСтроку(1);
НовДок.НомерВходящегоДокумента = СокрЛП(НомерИзФайла);
НовДок.ДатаВходящегоДокумента = ТекущаяДата();
// Заполнение табличной части и проведение
// ...
Попытка
НовДок.Записать(РежимЗаписиДокумента.Проведение);
Сообщить("Документ успешно проведен с внутренним номером: " + НовДок.Номер);
Исключение
Сообщить("Ошибка проведения: " + ОписаниеОшибки());
КонецПопытки;
---
Если перед вами стоит жесткая задача сохранить именно оригинальный номер из внешнего файла в системное поле Номер, вам необходимо предварительно отформатировать его. Номер должен строго соответствовать длине и структуре номеров в вашей базе данных (содержать префиксы организации, префиксы узлов распределенной базы и лидирующие нули).
Для форматирования номера можно написать собственную функцию или воспользоваться функциями стандартных библиотек конфигурации (например, БСП). Разберем ручной вариант дополнения номера лидирующими нулями:
Функция ПолучитьФорматированныйНомер(Знач ИсходныйНомер, ДлинаНомера) Экспорт
// Очищаем от пробелов
НомерОчищенный = СокрЛП(ИсходныйНомер);
// Вычисляем, сколько нулей нужно добавить в начало
КоличествоНулей = ДлинаНомера - СтрДлина(НомерОчищенный);
Если КоличествоНулей > 0 Тогда
СтрокаНулей = "";
Для Счетчик = 1 По КоличествоНулей Цикл
СтрокаНулей = СтрокаНулей + "0";
КонецЦикла;
Возврат СтрокаНулей + НомерОчищенный;
КонецЕсли;
Возврат НомерОчищенный;
КонецФункции
Применим эту функцию при заполнении документа, предварительно определив длину номера из метаданных конфигурации:
НовДок = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
НовДок.Дата = ТекущаяДата();
НомерИзФайла = "175"; // Значение, полученное из файла загрузки
ДлинаНомераМетаданных = Метаданные.Документы.ПоступлениеТоваровУслуг.ДлинаНомера;
// Приводим номер к виду "00000000175"
НовДок.Номер = ПолучитьФорматированныйНомер(НомерИзФайла, ДлинаНомераМетаданных);
Попытка
НовДок.Записать(РежимЗаписиДокумента.Запись);
Исключение
Сообщить("Не удалось записать документ: " + ОписаниеОшибки());
КонецПопытки;
---
Для того чтобы избежать попытки записи дублирующегося номера, необходимо выполнять проверку существования документа не за 3 дня, а за весь период уникальности (то есть за текущий год). Сделать это эффективнее всего с помощью запроса.
Проанализируем следующий алгоритм проверки:
Функция ДокументСНомеромСуществует(Знач ПроверяемыйНомер, ДатаДокумента)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ПоступлениеТоваровУслуг.Ссылка КАК Ссылка
|ИЗ
| Документ.ПоступлениеТоваровУслуг КАК ПоступлениеТоваровУслуг
|ГДЕ
| ПоступлениеТоваровУслуг.Номер = &Номер
| И ПоступлениеТоваровУслуг.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
// Задаем границы года, в котором создается документ
Запрос.УстановитьПараметр("НачалоПериода", НачалоГода(ДатаДокумента));
Запрос.УстановитьПараметр("КонецПериода", КонецГода(ДатаДокумента));
Запрос.УстановитьПараметр("Номер", ПроверяемыйНомер);
РезультатЗапроса = Запрос.Выполнить();
Возврат Не РезультатЗапроса.Пустой();
КонецФункции
Используя эту функцию в цикле обработки импорта данных, вы гарантированно избежите попыток создать документ с номером, который уже есть в системе:
НомерИзФайла = ТекстовыйДокумент.ПолучитьСтроку(1);
ДлинаНомераМетаданных = Метаданные.Документы.ПоступлениеТоваровУслуг.ДлинаНомера;
ФорматированныйНомер = ПолучитьФорматированныйНомер(НомерИзФайла, ДлинаНомераМетаданных);
ТекущаяДатаДокумента = ТекущаяДата();
Если ДокументСНомеромСуществует(ФорматированныйНомер, ТекущаяДатаДокумента) Тогда
Сообщить("Документ с номером " + ФорматированныйНомер + " уже существует в базе за этот год! Пропускаем.");
Иначе
НовДок = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
НовДок.Дата = ТекущаяДатаДокумента;
НовДок.Номер = ФорматированныйНомер;
// Дальнейшее заполнение и запись...
КонецЕсли;
---
Если вам необходимо выполнить разовый перенос старых исторических данных из другой системы и вы абсолютно уверены в корректности номеров, но платформа продолжает выдавать ошибки уникальности, можно использовать специальный режим записи.
Установка свойства ОбменДанными.Загрузка = Истина отключает большинство стандартных проверок платформы и прикладного кода конфигурации (включая проверку уникальности номеров в подписках на события):
НовДок = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
// Заполнение реквизитов
НовДок.Номер = "175";
// Включаем режим загрузки данных для обхода проверок
НовДок.ОбменДанными.Загрузка = Истина;
Попытка
// Документ просто запишется в базу данных "как есть"
НовДок.Записать(РежимЗаписиДокумента.Запись);
Исключение
Сообщить("Критическая ошибка базы данных: " + ОписаниеОшибки());
КонецПопытки;
Важно: Данный метод следует использовать с крайней осторожностью. Отключение проверок может привести к физическому появлению документов с абсолютно одинаковыми номерами в одном периоде, что нарушит логику работы стандартных отчетов и усложнит последующий поиск документов пользователями.
---
Если в результате программных экспериментов пользователи начали жаловаться, что при ручном создании новых документов система больше не подставляет автономер или выдает ошибку уникальности, значит, кэш автонумерации был сбит некорректными записями.
Для исправления ситуации выполните следующие шаги:
ОбновитьНумерациюОбъектов(Метаданные.Документы.ПоступлениеТоваровУслуг);
Мы надеемся, что приведенные рекомендации помогут вам построить надежный и отказоустойчивый алгоритм импорта документов в базу данных 1С!