Почему не сохраняются изменения реквизитов в процедуре ПриЗаписи() и как правильно заполнять данные документа?

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

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

Разберем причину: архитектура транзакции записи

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

  1. ПередЗаписью: Данные находятся в памяти (в объекте), но еще не отправлены в базу данных. Это идеальный момент для проверок и заполнения «технических» реквизитов.
  2. Физическая запись в БД: Платформа берет текущее состояние объекта и записывает его в таблицы SQL.
  3. ПриЗаписи: Это событие срабатывает после того, как данные уже физически зафиксированы в базе данных.

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

Решение №1: Перенос логики в обработчик ПередЗаписью()

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

Проанализируем ситуацию на примере автора темы: нам нужно найти в табличной части Исполнители строку, помеченную как «Основной ответственный», и записать ФИО этого человека в основной реквизит шапки документа Исполнитель.


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Всегда проверяйте этот флаг, чтобы не замедлять обмены данными
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // Инициализируем переменную для поиска
    НужныйИсполнитель = Неопределено;

    // Обходим табличную часть напрямую через коллекцию объекта
    Для Каждого СтрИсполнитель Из ЭтотОбъект.осиИсполнители Цикл
        Если СтрИсполнитель.ОсновнойОтветственный Тогда
            НужныйИсполнитель = СтрИсполнитель.ФИОИсполнителя;
            Прервать; // Мы нашли то, что искали
        КонецЕсли;
    КонецЦикла;

    // Записываем результат в реквизит шапки
    Если НужныйИсполнитель <> Неопределено Тогда
        ЭтотОбъект.Исполнитель = НужныйИсполнитель;
    КонецЕсли;

КонецПроцедуры

Используя ЭтотОбъект, мы обращаемся к текущему состоянию данных, которые пользователь ввел на форме, но еще не сохранил.

Проблема использования запросов при создании нового документа

Выясним причину еще одной распространенной ошибки. Часто программисты пытаются получить данные табличной части документа с помощью запроса внутри модуля объекта. Для нового (еще ни разу не записанного) документа этот метод всегда будет возвращать пустой результат.

Почему так происходит? Запрос (Новый Запрос) обращается напрямую к таблицам базы данных на сервере СУБД. Но если документ новый, его еще нет в этих таблицах! Ссылка документа (ЭтотОбъект.Ссылка) у нового объекта либо пустая, либо, если она уже сформирована, она еще не «прописана» в таблице документа. Поэтому вместо запроса всегда следует использовать прямой обход коллекций: Для Каждого СтрокаТЧ Из ТабличнаяЧасть Цикл....

Если же логика настолько сложна, что без запроса не обойтись (например, нужно соединить данные ТЧ с какими-то внешними справочниками), мы можем выгрузить табличную часть во временную таблицу запроса:


МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

// Выгружаем текущие данные из памяти объекта в таблицу значений
ТЗ_Исполнители = ЭтотОбъект.осиИсполнители.Выгрузить();

Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Данные ИЗ &ТЗ КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ", ТЗ_Исполнители);
Запрос.Выполнить();

// Далее работаем с ВТ_Данные, которые содержат актуальные строки из памяти

Особенности работы с новыми объектами и ссылками

Иногда требуется получить ссылку нового объекта еще до его физической записи (например, чтобы передать эту ссылку в какой-то другой связанный процесс). Рассмотрим, как это сделать правильно. У любого объекта есть метод УстановитьСсылкуНового().

Если мы просто обратимся к ЭтотОбъект.Ссылка у нового документа, мы получим пустое значение. Чтобы зарезервировать уникальный идентификатор заранее, можно использовать следующий прием в модуле формы или перед программной записью:


// В модуле формы или внешней обработке
НоваяСсылка = Документы.МойДокумент.ПолучитьСсылку();
ДокументОбъект.УстановитьСсылкуНового(НоваяСсылка);
// Теперь ДокументОбъект.Ссылка будет возвращать НоваяСсылка

Однако в большинстве случаев для заполнения реквизитов внутри ПередЗаписью() ссылка не требуется, достаточно работы с контекстом ЭтотОбъект.

Важные рекомендации по оптимизации

Разберем несколько «золотых правил», которые помогут избежать ошибок при записи:

  1. Проверка «ОбменДанными.Загрузка»: Никогда не забывайте добавлять эту проверку в начале обработчиков ПередЗаписью и ПриЗаписи. При загрузке данных из других систем (через планы обмена) ваша логика автоматического заполнения может помешать корректному переносу данных или критически замедлить процесс.
  2. Минимизация кода в транзакции: Старайтесь не выполнять тяжелые вычисления или обращения к внешним веб-сервисам внутри процедур записи. Пока выполняется код в модуле объекта, таблицы базы данных могут быть заблокированы для других пользователей.
  3. Использование Итог(): Если вам нужно просто посчитать сумму или общее количество по колонке табличной части, не используйте циклы. Метод ЭтотОбъект.ТабличнаяЧасть.Итог("Сумма") работает значительно быстрее на больших объемах данных.

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

← На главную