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