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