Программное добавление префикса к номерам документов в 1С 8.3: пошаговое руководство по исправлению ошибок

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия IT и автоматизация бизнеса
← На главную

В процессе сопровождения и администрирования баз данных «1С:Предприятие 8.3» программисты часто сталкиваются с задачей массового изменения или исправления номеров существующих документов. Одной из классических подзадач является добавление или изменение префикса в уже записанных и проведенных документах. Например, когда требуется выделить определенный пул документов логистическим префиксом (например, "ТМЦ").

На первый взгляд задача кажется простой: выбрать документы за период запросом, обойти их в цикле, заменить символы в номере и записать. Однако при реализации этой схемы в современных типовых конфигурациях (таких как «1С:Бухгалтерия предприятия 3.0», построенных на базе Библиотеки стандартных подсистем — БСП) разработчики регулярно натыкаются на подводные камни. Документы либо не перенумеровываются вовсе, либо система выдает ошибки уникальности номеров, либо ломается последующая автоматическая нумерация.

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

---

Анализ исходного кода и выявление ошибок

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


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

При запуске этого кода внешне ничего не происходило: ошибок не возникало, но и номера документов оставались прежними. Проанализировав ситуацию, мы можем выделить несколько критических ошибок в данном коде.

---

Шаг 1. Исправление работы с реквизитами формы и параметрами запроса

Первая и самая главная причина, по которой код «молчал» — пустой результат выполнения запроса. Конструкция Объект.Свойство(НачалоПериода) написана некорректно по двум причинам:

  1. Метод Свойство() принадлежит объекту типа Структура. Переменная Объект на управляемой форме — это обычно данные формы (типа ДанныеФормыСтруктура), и использовать этот метод таким образом ошибочно. Кроме того, имя свойства должно передаваться в виде строки (в кавычках).
  2. Передача имени переменной НачалоПериода без кавычек приводила к тому, что система пыталась найти переменную с таким именем, получала Неопределено и возвращала Ложь в качестве значения параметра. В результате запрос искал документы с датой между "Ложь" и "Ложь", возвращая пустую выборку. Цикл обхода просто не начинался.

Выясним, как правильно передавать параметры дат из формы в запрос. Если на форме внешней обработки созданы стандартные реквизиты с типами Дата (например, НачалоПериода и КонецПериода), обращаться к ним на сервере нужно напрямую по их именам без использования посредников. Дополнительно крайне важно приводить даты к началу и концу дня, чтобы не упустить документы, созданные внутри граничных суток.

Правильный вариант заполнения параметров выглядит следующим образом:


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

После этого исправления запрос начнет корректно выбирать документы из базы данных, и управление наконец-то перейдет внутрь цикла обработки.

---

Шаг 2. Разбор логики строковых функций при сборке номера

Следующая серьезная логическая ошибка скрывалась в строке формирования нового номера:

ДокОбъект.Номер = СтрЗаменить(Лев((ДокОбъект.Номер),5),000,"ТМЦ");

Проанализируем, что здесь происходит. Функция СтрЗаменить в качестве второго параметра принимает строку поиска. Автор написал число 000 без кавычек. Платформа 1С неявно преобразует число 000 в строку "0". Соответственно, алгоритм пытался заменить все одиночные нули на "ТМЦ", что привело бы к полной порче структуры номера.

Но даже после замены на строковое значение "000" возникла следующая проблема — ошибка неуникальности номеров:

«Значение "ТМЦ0-" поля "Номер" не уникально».

Эта ошибка возникла потому, что функция Лев(ДокОбъект.Номер, 5) обрезает номер до первых пяти символов. Если исходный номер был, например, 0000-000001, то функция возвращала 0000-. После замены трех нулей на "ТМЦ" получалась строка ТМЦ0-. Оставшаяся уникальная часть номера (хвост 000001) просто выбрасывалась! В итоге при обработке второго документа система пыталась записать точно такой же номер ТМЦ0-, что приводило к конфликту уникальности.

Давайте посмотрим на структуру стандартного номера документа в 1С. Обычно номер имеет фиксированную длину (например, 11 символов) и состоит из префикса базы/организации, разделителя (дефиса) и цифровой последовательности с лидирующими нулями (например, 0000-000015).

Чтобы корректно внедрить префикс "ТМЦ" вместо первых трех нулей и сохранить уникальную цифровую часть номера, мы должны использовать функции работы со строками. Предположим, длина номера составляет 11 символов. Первые 3 символа мы заменяем на "ТМЦ", а оставшиеся 8 символов правой части номера (включая дефис и цифры) переносим без изменений:


НовыйПрефикс = "ТМЦ";
ОставшаясяЧастьНомера = Прав(ДокОбъект.Номер, 8);
ДокОбъект.Номер = НовыйПрефикс + ОставшаясяЧастьНомера;

Такой подход гарантирует сохранение уникальности номера каждого обрабатываемого документа.

---

Шаг 3. Запись документов и обход защитных механизмов БСП

Казалось бы, параметры запроса исправлены, алгоритм сборки номера работает корректно, метод Сообщить() выводит правильные красивые номера (например, ТМЦ0-000001), но после окончания работы обработки номера документов в базе данных все равно остаются старыми. Почему так происходит?

В современных конфигурациях 1С (особенно в «Бухгалтерии предприятия 3.0») при интерактивной или стандартной программной записи документа срабатывают подписки на события БСП. Прикладные модули платформы принудительно анализируют записываемый объект, определяют организацию, считывают настройки префиксов и заново генерируют номер документа по своим внутренним правилам. Все ваши ручные изменения номера просто затираются стандартными механизмами автонумерации перед непосредственной записью в базу данных.

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

ДокОбъект.ОбменДанными.Загрузка = Истина;

Установка этого флага в значение Истина отключает выполнение большинства прикладных проверок в модулях объектов и подписках на события, включая автоматическую генерацию номеров. Платформа запишет документ «как есть», сохранив наш новый номер.

Важное примечание относительно проведения документов: В исходном коде автора выполнялось перепроведение документов: ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);. Делать так категорически не рекомендуется! Изменение номера — это чисто реквизитное изменение. Оно никак не влияет на движения документа по регистрам бухгалтерии или регистру накопления. Перепроведение документов задним числом может повлечь за собой:

Для изменения номера достаточно использовать режим простой записи — РежимЗаписиДокумента.Запись.

---

Шаг 4. Обновление кэша нумерации на платформе

После того как мы программно перенумеровали документы с использованием режима загрузки, может возникнуть неприятная ситуация: при попытке пользователя создать новый документ вручную, система может выдать ошибку уникальности или продолжить нумерацию по старому шаблону.

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

ОбновитьНумерациюОбъектов(Метаданные.Документы.ПеремещениеТоваров); ---

Итоговое правильное решение

Соберем все наши исправления в единый готовый алгоритм, который можно использовать во внешней обработке для перенумерации документов ПеремещениеТоваров:


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

Разберем ключевые особенности этого финального решения:

← На главную