Сохраняются ли измененные реквизиты при программном проведении документа в 1С:Предприятии 7.7?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

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

Постановка задачи и три ключевых вопроса

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


Док = СоздатьОбъект("Документ.Новый1");
Если Док.НайтиДокумент(КакойТоДокумент) = 1 Тогда
    // Было какое-то старое значение реквизита, присваиваем новое
    Док.Реквизит1 = КакоеТоНовоеЗначение1; 
    Док.Провести();
КонецЕсли;

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

  1. Выполняется ли физическая запись документа в базу данных при вызове метода Провести()?
  2. Какое значение останется в поле Реквизит1 в случае успешного проведения документа — новое или старое?
  3. Какое значение останется в поле Реквизит1 в случае неуспешного проведения (например, если проведение прервано программно или произошла критическая ошибка выполнения модуля)?

Для того чтобы ответить на эти вопросы, нам необходимо детально изучить двухфазную модель фиксации данных в 1С:Предприятии 7.7.

Разбор Вопроса №1: Выполняется ли запись при проведении?

Да, при вызове метода Провести() платформа 1С 7.7 всегда принудительно и неявно выполняет физическую запись документа в базу данных. Это поведение жестко зашито в ядро платформы. Документ не может находиться в проведенном состоянии, не будучи предварительно записанным в таблицы базы данных. В процессе вызова метода Провести() происходит сериализация всех реквизитов объекта из оперативной памяти и их сохранение в физические файлы таблиц (например, в файлы DH* для шапки документа и DT* для табличной части в версии DBF, либо в соответствующие таблицы SQL-базы).

Разбор Вопросов №2 и №3: Какое значение примет реквизит при успешном и неуспешном исходе?

Ответ на оба эти вопроса может удивить программистов, привыкших к транзакционной модели современных платформ, таких как 1С:Предприятие 8. В обоих случаях — и при успешном, и при неуспешном проведении — реквизит примет НОВОЕ значение!

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

Первая фаза: Неявная запись. Платформа берет текущее состояние объекта из памяти (включая все измененные реквизиты, такие как Реквизит1) и записывает их в базу данных. Эта операция выполняется до открытия транзакции проведения и фиксируется в СУБД окончательно.

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

Если во второй фазе (внутри процедуры ОбработкаПроведения()) происходит ошибка — например, не хватает остатков товаров, вызывается СтатусВозврата(0), происходит деление на ноль или обращение к несуществующему объекту метаданных, — платформа инициирует откат транзакции (Rollback). Однако этот откат затрагивает только действия, совершенные во второй фазе. То есть удаляются сформированные движения регистров и возвращается флаг «Не проведен» в общем журнале. Физические реквизиты документа, которые были сохранены в таблицы DH* и DT* на первой фазе, назад к старым значениям не откатываются! Таким образом, новые значения реквизитов гарантированно остаются в базе данных.

Практический эксперимент: проверка поведения транзакции

Рассмотрим пример реального теста, который наглядно демонстрирует эту особенность платформы. Создадим документ, у которого есть реквизит Контрагент (тип «Справочник.Контрагенты») и числовой реквизит-флаг Ошибка. В модуле проведения этого документа напишем код, который умышленно вызывает ошибку времени выполнения при взведенном флаге:


Процедура ОбработкаПроведения()
    Если Ошибка = 1 Тогда
        // Намеренно обращаемся к несуществующему объекту метаданных для вызова системного сбоя
        СоздатьОбъект("Справочник.НесуществующийСправочник"); 
    КонецЕсли;
КонецПроцедуры

Теперь напишем внешнюю обработку, которая находит данный документ, меняет реквизиты и пытается выполнить проведение:


БегомПоДокам = СоздатьОбъект("Документ.Новый1");
Если БегомПоДокам.НайтиДокумент(ТекДок) = 1 Тогда
    БегомПоДокам.Контрагент = ВыбранныйКонтрагент;
    БегомПоДокам.Ошибка = 1; // взводим флаг для генерации ошибки в модуле
    БегомПоДокам.Провести();
КонецЕсли;

При выполнении этого кода произойдет следующее:

  1. Платформа неявно запишет документ в базу данных. В полях документа зафиксируются новые значения: Контрагент примет значение ВыбранныйКонтрагент, а реквизит Ошибка станет равным 1.
  2. Платформа запустит процедуру ОбработкаПроведения().
  3. Внутри процедуры сработает условие, и попытка создания несуществующего справочника вызовет фатальную ошибку.
  4. Проведение документа прервется. Транзакция проведения откатится: документ останется непроведенным.
  5. Если мы откроем этот документ интерактивно через журнал или программно перечитаем его из базы, мы увидим, что реквизиты Контрагент и Ошибка сохранили новые значения, несмотря на то, что документ так и не был проведен.

В чем разница между архитектурой 1С:Предприятия 7.7 и 1С:Предприятия 8?

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

Проблема избыточного кода: почему связка Записать(); Провести(); вредна?

В процессе обсуждений на форумах начинающие разработчики или специалисты, желающие перестраховаться («лучше перестраховаться, чем недоглядеть»), часто предлагают писать код следующим образом:


Док.НайтиДокумент(КакойТоДокумент);
Док.Реквизит1 = КакоеТоНовоеЗначение1;
Док.Записать(); // Предварительная явная запись
Док.Провести(); // Последующее проведение

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

Таким образом, для программной записи изменений и последующего проведения документа всегда достаточно использовать один-единственный метод — Провести().

Ограничения программной записи: почему обходят процедуру ПриЗаписи()?

При программной работе с объектом документа важно помнить еще об одном критическом ограничении платформы 1С 7.7. Когда вы изменяете реквизиты объекта в коде и вызываете методы Записать() или Провести(), предопределенная процедура модуля формы документа ПриЗаписи() не вызывается.

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

Если вам необходимо гарантировать выполнение этих проверок при программном проведении, вы должны следовать следующим правилам:

  1. Выносите критически важные алгоритмы проверки заполнения реквизитов и расчетные механизмы из модуля формы во внешние функции общих модулей (глобального модуля) или непосредственно в процедуру ОбработкаПроведения() модуля документа.
  2. При программном манипулировании объектом дублируйте необходимые проверки непосредственно в вызывающем коде перед вызовом метода Провести().

Итоговый чек-лист для разработчика

Резюмируя особенности работы с документами в 1С 7.7, сформулируем основные правила:

← На главную