В процессе разработки и администрирования систем на платформе 1С:Предприятие 8 регулярно возникает необходимость массового изменения реквизитов документов с их последующим перепроведением. Если просто изменить реквизит объекта и записать его, проводки и движения по регистрам не обновятся автоматически. Чтобы изменения отразились в отчетах, документ необходимо именно перепровести.
В этой статье мы подробно разберем, как правильно реализовать программное проведение документов, проанализируем распространенные ошибки разработчиков на примере реального кейса с форума, а также изучим важные нюансы, связанные с флагом ручной корректировки, оптимизацией производительности и транзакциями.
Рассмотрим ситуацию, с которой столкнулся автор темы. Ему требовалось заменить счет учета расчетов по авансам в табличной части документов «Расходный кассовый ордер» с 62.02 на 62.01 и программно перепровести измененные документы. Автор написал следующий код:
ВыборкаРасход = Документы.РасходныйКассовыйОрдер.Выбрать(Объект.ДатаДо, Объект.ДатаПосле);
Пока ВыборкаРасход.Следующий() Цикл
ДокОбъектРасход = ВыборкаРасход.ПолучитьОбъект();
ПроверкаПроводкиРасход = ВыборкаРасход.Проведен;
НН = ДокОбъектРасход.РасшифровкаПлатежа.Получить(0);
НН.СчетУчетаРасчетовПоАвансам = Планысчетов.Хозрасчетный.НайтиПоКоду("62.01");
Если ПроверкаПроводкиРасход = "Да" Тогда
ДокОбъектРасход.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
Иначе
ДокОбъектРасход.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецЦикла;
Выясним причину, почему этот код не выполнял проведение. Проблема кроется в строке сравнения: Если ПроверкаПроводкиРасход = "Да" Тогда. Свойство Проведен любого документа имеет тип Булево (может принимать значения только Истина или Ложь). Автор попытался сравнить булево значение с текстовой строкой "Да".
В 1С:Предприятии такое сравнение всегда возвращает Ложь. В результате условие не выполнялось, и программа всегда уходила по ветке Иначе, выполняя обычную запись документа без проведения: ДокОбъектРасход.Записать(РежимЗаписиДокумента.Запись). Изменения записывались в сам документ, но новые проводки по регистрам не формировались.
Для исправления этой логической ошибки достаточно написать условие корректно:
Если ПроверкаПроводкиРасход Тогда
ДокОбъектРасход.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
Иначе
ДокОбъектРасход.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
Или еще проще, без использования лишней переменной:
Если ВыборкаРасход.Проведен Тогда
ДокОбъектРасход.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
Иначе
ДокОбъектРасход.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
Теперь разберем более глубокие и неочевидные нюансы программного проведения документов, знание которых убережет вас от серьезных ошибок в реальных базах данных.
В типовых конфигурациях (например, «1С:Бухгалтерия 3.0») у большинства документов присутствует реквизит РучнаяКорректировка с типом Булево. Если бухгалтер вручную отредактировал проводки документа в интерфейсе, этот флаг принимает значение Истина.
Важный нюанс: Если у документа включена ручная корректировка, то при вызове метода Записать(РежимЗаписиДокумента.Проведение) система не станет перегенерировать движения по новым реквизитам. Она просто оставит старые, созданные вручную проводки.
Если перед вами стоит задача принудительно обновить движения документа по новым правилам, необходимо программно сбросить этот флаг перед записью:
ДокОбъект = СсылкаНаДокумент.ПолучитьОбъект();
// Сбрасываем флаг ручной корректировки, чтобы проводки переформировались автоматически
ДокОбъект.РучнаяКорректировка = Ложь;
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
Многие разработчики привыкли отключать проверки при записи объектов с помощью конструкции ОбменДанными.Загрузка = Истина. Это действительно ускоряет процесс, но использовать данный прием при проведении категорически запрещено.
Почему это опасно: Если установить ОбменДанными.Загрузка = Истина и вызвать метод Записать(РежимЗаписиДокумента.Проведение), то стандартный обработчик проведения ОбработкаПроведения внутри модуля документа вообще не будет выполнен. Система просто установит документу статус «Проведен» (зеленую галочку), но новые проводки и движения по регистрам созданы не будут. Документ станет «пустым» с точки зрения регистров.
Правило: Никогда не используйте ОбменДанными.Загрузка = Истина, если вам требуется полноценное перепроведение и формирование движений.
Использование метода Выбрать() для обхода документов, как в коде автора темы, крайне неэффективно на больших базах. Платформа при этом считывает из базы данных абсолютно все реквизиты всех документов за указанный период, что создает колоссальную нагрузку на сервер.
Правильный подход — использовать Запрос для первоначального отбора документов по нужным критериям, получать только ссылки документов, и лишь затем в цикле получать объект для изменения.
Разберем оптимизированный пример:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасходныйКассовыйОрдер.Ссылка КАК Ссылка,
| РасходныйКассовыйОрдер.Проведен КАК Проведен
|ИЗ
| Документ.РасходныйКассовыйОрдер КАК РасходныйКассовыйОрдер
|ГДЕ
| РасходныйКассовыйОрдер.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
| И РасходныйКассовыйОрдер.ПометкаУдаления = ЛОЖЬ";
Запрос.УстановитьПараметр("ДатаНачала", Объект.ДатаДо);
Запрос.УстановитьПараметр("ДатаОкончания", Объект.ДатаПосле);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Получаем объект только тогда, когда это действительно необходимо
ДокОбъект = ВыборкаДетальныеЗаписи.Ссылка.ПолучитьОбъект();
// Вносим необходимые изменения в реквизиты или табличные части
// ...
// Проводим только те документы, которые были проведены ранее
Если ВыборкаДетальныеЗаписи.Проведен Тогда
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
Иначе
ДокОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецЦикла;
При массовом перепроведении документов (особенно в работающей многопользовательской базе) существует огромный риск возникновения конфликтов блокировок данных. Если выполнять проведение тысяч документов в рамках одной транзакции, другие пользователи не смогут работать, а система может завершить процесс аварийно по таймауту.
Для решения этой проблемы рекомендуется:
Посмотрим на пример реализации обработки порциями с транзакциями:
// Пример разделения на порции по 100 документов
Счетчик = 0;
НачатьТранзакцию();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
ДокОбъект = ВыборкаДетальныеЗаписи.Ссылка.ПолучитьОбъект();
// Блокируем объект от изменения другими транзакциями
ДокОбъект.Заблокировать();
// Внесение изменений
// ...
Если ВыборкаДетальныеЗаписи.Проведен Тогда
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
Иначе
ДокОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
Счетчик = Счетчик + 1;
// Каждые 100 документов фиксируем транзакцию и открываем новую
Если Счетчик % 100 = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
// Фиксируем остаток документов
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
В редких случаях при существенном изменении ключевых аналитик документа (например, организации, склада или даты) старые движения в регистрах могут некорректно заменяться новыми. Чтобы избежать «зависания» некорректных остатков, движения документа можно предварительно принудительно очистить:
ДокОбъект = СсылкаНаДокумент.ПолучитьОбъект();
// Принудительно очищаем коллекцию движений определенного регистра
ДокОбъект.Движения.Хозрасчетный.Записывать = Истина;
ДокОбъект.Движения.Хозрасчетный.Очистить();
// Записываем пустые движения в базу данных перед основным проведением
ДокОбъект.Движения.Записать();
// Выполняем проведение с чистого листа
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
Если вы работаете в современных конфигурациях на базе БСП (Библиотеки Стандартных Подсистем), таких как ERP, КА, УТ или БП, старайтесь избегать написания собственных велосипедов для групповой обработки данных. Используйте стандартную обработку «Групповое изменение реквизитов» или программные вызовы фонового проведения БСП. Эти механизмы уже включают в себя оптимизацию запросов, корректный обход блокировок и корректную обработку ручных корректировок.