Почему при изменении данных документа в процедуре «Обработка проведения» они не сохраняются, и чем отличаются кнопки «Провести» и «Провести и закрыть»?

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

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

Анализируем проблему: почему данные «пропадают»

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

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

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

Выясним причину разного поведения кнопок:

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

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

Разберем правильный подход: использование события «ПередЗаписью»

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

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

Практическая реализация: работа с табличными частями через запрос

Часто разработчики выбирают ОбработкаПроведения, потому что там удобно работать с данными через SQL-запросы, так как документ уже «как бы» в базе. Однако в ПередЗаписью мы также можем использовать всю мощь языка запросов, передавая в них данные текущего объекта, которые еще не записаны. Рассмотрим, как это сделать правильно.

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


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

Почему работа с объектом эффективнее запросов к базе

Проанализируем ситуацию, упомянутую в обсуждении: разработчик пытался «выловить» данные запросом к базе в ОбработкаПроведения. Это плохая практика по нескольким причинам:

Важный совет: Если вам нужно просто перебрать строки и выполнить простые проверки, лучше использовать обычный цикл Для Каждого СтрокаТЧ Из Товары Цикл. Запросы к табличным частям оправданы только тогда, когда требуется сложное соединение данных объекта с данными других таблиц базы (как в примере выше с проверкой реквизита справочника).

Итоги: памятка для разработчика

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

  1. Разделяйте ответственность: Данные документа меняются в ПередЗаписью. Движения по регистрам (проводки) формируются в ОбработкаПроведения.
  2. Не доверяйте визуальному результату: Если после нажатия кнопки «Провести» вы видите изменения в форме, это не значит, что они записаны в базу данных. Всегда проверяйте результат через консоль запросов или переоткрытие формы.
  3. Используйте объектную модель: В модуле объекта вам доступны все данные через ЭтотОбъект. Используйте метод Выгрузить() для передачи табличных частей в запросы, если это необходимо для анализа.
  4. Учитывайте контекст: Помните, что проведение может быть инициировано не только из формы, но и из списка документов или программно. В этих случаях никакой «обратной синхронизации» в форму не будет, и изменения, внесенные в обработке проведения, гарантированно пропадут.

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

← На главную