Почему программное проведение документа не сохраняет изменения и как это исправить в 1С?

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

Давайте подробно разберем эту ситуацию, проанализируем частые ошибки в коде и выясним истинные причины такого поведения платформы на примере конфигурации «1С:Управление торговлей 10.3».

Проанализируем исходный код с ошибкой

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


Выборка = Документы.РеализацияТоваровУслуг.Выбрать(ДатаНач, ДатаКон);
Пока Выборка.Следующий() Цикл
    Если Выборка.Проведен = Ложь Тогда 
        Продолжить; 
    КонецЕсли;
    
    ДокументИзменен = 0;
    Док = Выборка.ПолучитьОбъект();
    
    Если ЗначениеЗаполнено(Док.Грузополучатель) Тогда
        Если ЗначениеЗаполнено(Док.Грузополучатель.ИНН) Тогда
            Док.Контрагент = Док.Грузополучатель;
            Док.ДоговорКонтрагента = Справочники.ДоговорыКонтрагентов.НайтиПоРеквизиту("Организация", Док.Организация,, Док.Грузополучатель);
        КонецЕсли;                  
        Док.Грузополучатель = NULL; // Критическая ошибка
        ДокументИзменен = 1;
    КонецЕсли;
    
    // Если в документе были внесены изменения, тогда проводим его
    Если ДокументИзменен = 1 Тогда
        Док.Записать(РежимЗаписиДокумента.Проведение);
        Сообщить("Реализация N " + Док.Номер + " от " + Док.Дата + " изменен");
    КонецЕсли;
КонецЦикла;

На первый взгляд логика ясна: мы получаем объект, меняем реквизиты и отправляем документ на проведение. Но этот код сработает некорректно по нескольким причинам. Давайте разберем по шагам, что именно здесь не так.

Причина 1: Некорректное использование значения NULL

В строке Док.Грузополучатель = NULL; допущена грубая архитектурная ошибка. В платформе 1С тип NULL предназначен исключительно для описания отсутствующих значений в результатах запросов базы данных (например, при левом или полном соединении таблиц). Использовать его для очистки реквизитов ссылочного типа (таких как справочники) категорически нельзя.

В типовых конфигурациях в обработчиках ПередЗаписью или ОбработкаПроведения система проверяет, изменились ли ключевые реквизиты. Сравнение старого значения с NULL может приводить к непредсказуемым результатам или сбоям, из-за чего выполнение алгоритма формирования движений прерывается.

Как правильно: Для очистки реквизита ссылочного типа используем пустую ссылку конкретного справочника. Если тип составной — используем Неопределено.


// Правильная очистка реквизита типа СправочникСсылка.Контрагенты
Док.Грузополучатель = Справочники.Контрагенты.ПустаяСсылка();

Причина 2: Чтение данных запросом в обработке проведения

Типовые конфигурации (УТ 10.3, Комплексная Автоматизация, УПП) при проведении документа формируют объемные запросы к базе данных. Эти запросы получают параметры учетной политики, состояния взаиморасчетов, текущие остатки и прочее. Особенность в том, что эти запросы опираются на ссылку документа в базе данных (ГДЕ Ссылка = &Ссылка).

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

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

Причина 3: Оптимизация перепроведения в типовых конфигурациях

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

Причина 4: Оперативное и неоперативное проведение

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

Итоговое рабочее решение

Учитывая все вышесказанное, перепишем наш исходный код так, чтобы изменения гарантированно сохранялись, а регистры корректно переформировывались. Рассмотрим исправленный вариант:


Выборка = Документы.РеализацияТоваровУслуг.Выбрать(ДатаНач, ДатаКон);

Пока Выборка.Следующий() Цикл
    Если Не Выборка.Проведен Тогда 
        Продолжить; 
    КонецЕсли;
    
    ДокументИзменен = Ложь;
    Док = Выборка.ПолучитьОбъект();
    
    // Проверяем наличие грузополучателя
    Если ЗначениеЗаполнено(Док.Грузополучатель) Тогда
        Если ЗначениеЗаполнено(Док.Грузополучатель.ИНН) Тогда
            Док.Контрагент = Док.Грузополучатель;
            // Ищем договор
            НайденныйДоговор = Справочники.ДоговорыКонтрагентов.НайтиПоРеквизиту("Организация", Док.Организация,, Док.Грузополучатель);
            Если ЗначениеЗаполнено(НайденныйДоговор) Тогда
                Док.ДоговорКонтрагента = НайденныйДоговор;
            КонецЕсли;
        КонецЕсли;                  
        
        // Правильно очищаем ссылочный реквизит
        Док.Грузополучатель = Справочники.Контрагенты.ПустаяСсылка();
        ДокументИзменен = Истина;
    КонецЕсли;
    
    Если ДокументИзменен Тогда
        // 1. Сначала записываем изменения в базу без проведения
        Док.Записать(РежимЗаписиДокумента.Запись);
        
        // 2. Затем выполняем проведение (неоперативно, чтобы не было конфликтов с остатками в прошлом)
        Док.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
        
        Сообщить("Реализация N " + Док.Номер + " от " + Док.Дата + " успешно изменена и проведена.");
    КонецЕсли;
КонецЦикла;

Дополнительные факторы, о которых следует помнить

  1. Ручная корректировка: В УТ 10.3 и аналогичных системах есть флаг «Ручная корректировка». Если он установлен в документе, стандартное программное проведение проигнорирует обновления регистров, чтобы не затереть ручные правки бухгалтера или пользователя.
  2. Отложенное проведение: Убедитесь, что система не находится в режиме отложенного проведения (часто бывает после обменов данными). В этом случае движения формируются не сразу, а помещаются в специальный регистр сведений для фоновой доработки регламентным заданием.

Используя комбинацию предварительной записи (РежимЗаписиДокумента.Запись) перед проведением и соблюдая правила работы с пустыми ссылками, мы избегаем проблем с кэшированием запросов и обходим агрессивную оптимизацию проведения в типовых конфигурациях 1С.

← На главную