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