В рамках разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью автоматизировать процесс заполнения данных в документах. Одним из ключевых механизмов для этого является обработчик события ОбработкаЗаполнения, который находится в модуле объекта документа. Однако возникает вопрос: как вызвать этот обработчик для документа, который находится в форме, особенно если он еще не записан в базу данных и существует только в оперативной памяти как данные формы? Рассмотрим эту проблему подробно и предложим эффективное решение.
Представьте ситуацию: нам необходимо, чтобы при определенных условиях (например, перед записью документа или при изменении какого-либо ключевого реквизита) система автоматически заполняла или корректировала данные в документе, находящемся в открытой форме. При этом пользователь может уже ввести какие-то данные, но логика программы должна их дополнить или даже переопределить. Автор темы форума прямо заявляет, что "все надо сделать полностью автоматическим вопреки воле человека", то есть "человек может посмотреть и даже что-то поменять, но решать все равно должен компьютер".
Основная сложность заключается в том, что обработчик ОбработкаЗаполнения живет в модуле объекта, а мы работаем с формой документа. В обычном сценарии, метод Заполнить() вызывается на самом объекте документа (например, Документы.ТвойДокумент.СоздатьДокумент().Заполнить(Основание)), что приводит к срабатыванию ОбработкаЗаполнения. Но как быть, когда у нас уже есть открытая форма с объектом, который, возможно, новый и еще не имеет ссылки в базе данных?
Автор темы сталкивается с типичными затруднениями. Изначально мы можем подумать о создании нового объекта, вызове на нем метода Заполнить(), а затем попытке скопировать данные обратно на форму. Однако такой подход не всегда работает эффективно или вовсе невозможен:
ПолучитьСсылкуНового() возвращает ссылку, но это не объект в базе, а лишь уникальный идентификатор), и, соответственно, невозможно получить объект из этой ссылки методом ПолучитьОбъект().ОбработкаЗаполнения модуля объекта, что снижает централизацию и переиспользуемость кода.Программисты часто задаются вопросом, почему нельзя просто применить метод Заполнить() к реквизиту формы Объект. Ответ кроется в архитектуре платформы: реквизит формы Объект сам по себе не является чистым объектом базы данных, это лишь представление данных, которое платформа умеет сериализовать и десериализовать между клиентом и сервером. Для работы с объектной логикой, определенной в модуле объекта, нам нужна именно полноценная объектная сущность, которая обладает всеми методами, присущими объектам базы данных.
Платформа 1С:Предприятие 8.3 предоставляет элегантное и стандартное решение этой задачи, используя пару методов для работы с реквизитами формы на сервере. Эти методы позволяют нам получить временную серверную копию основного реквизита формы, работать с ней как с полноценным объектом базы данных, а затем вернуть изменения обратно на форму.
Рассмотрим по шагам, как это делается.
Первым делом нам необходимо получить текущее состояние основного реквизита формы (который обычно называется Объект) в виде полноценного объекта базы данных, с которым мы сможем работать на сервере. Для этого мы используем метод РеквизитФормыВЗначение().
Метод РеквизитФормыВЗначение("Объект") выполняет следующее:
Объект. Это означает, что платформа берет все данные, которые в данный момент отображаются и хранятся в элементах формы, и "собирает" из них полноценный ДокументОбъект (или другой объект базы данных, соответствующий типу основного реквизита формы, например, СправочникОбъект, ОтчетОбъект и т.д.).Таким образом, мы получаем в серверной переменной Док объект, который полностью отражает актуальное состояние формы на текущий момент, и с которым мы можем взаимодействовать как с обычным объектом базы данных, вызывая его методы и процедуры, определенные в модуле объекта.
Пример использования:
// Выполняется на сервере
// Получаем серверную копию основного реквизита формы "Объект"
Док = РеквизитФормыВЗначение("Объект");
После того как мы получили временную серверную копию объекта документа, мы можем вызвать на ней метод Заполнить(). Именно этот метод инициирует выполнение обработчика события ОбработкаЗаполнения, который находится в модуле объекта документа. Это позволяет нам централизованно использовать всю логику заполнения, уже разработанную в модуле объекта, не дублируя ее на форме.
Метод Заполнить() принимает один параметр, который передается в обработчик ОбработкаЗаполнения как ДанныеЗаполнения. Тип этого параметра может варьироваться:
ОбработкаЗаполнения будет передан этот параметр, и логика может получить доступ ко всем данным документа-основания. Например, при вводе документа "Реализация товаров и услуг" на основании "Заказа покупателя", в ДанныеЗаполнения придет ссылка на "Заказ покупателя".ОбработкаЗаполнения может анализировать эту структуру и выполнять заполнение в соответствии с переданными параметрами.ОбработкаЗаполнения обычно приходит Неопределено. Однако при программном вызове Заполнить() мы всегда можем явно передать необходимый параметр.Внутри обработчика ОбработкаЗаполнения в модуле объекта мы можем реализовать всю необходимую логику: заполнение реквизитов объекта, заполнение табличных частей, выполнение запросов к данным для получения дополнительной информации, определение значений по умолчанию, установка ответственных и так далее.
Пример вызова метода Заполнить() с передачей основания:
// Допустим, у нас есть ссылка на документ-основание на форме
// Например, она хранится в реквизите формы "ОснованиеДокумента"
// Передаем ее в качестве параметра Заполнить()
ОснованиеДляЗаполнения = РеквизитФормыВЗначение("ОснованиеДокумента"); // Если ОснованиеДокумента - это реквизит формы с ссылкой
Док.Заполнить(ОснованиеДляЗаполнения);
Если же нам нужно передать произвольные данные для более гибкой настройки заполнения, мы можем сформировать структуру:
// Формируем структуру с данными для заполнения
ДанныеДляЗаполнения = Новый Структура;
ДанныеДляЗаполнения.Вставить("Ответственный", Пользователи.ТекущийПользователь());
ДанныеДляЗаполнения.Вставить("ВалютаДокумента", ПредопределенноеЗначение("Справочник.Валюты.Рубль"));
ДанныеДляЗаполнения.Вставить("ТипОперации", "Возврат");
// И так далее, любые данные, которые нужны в ОбработкаЗаполнения
Док.Заполнить(ДанныеДляЗаполнения);
Важно: убедитесь, что ваш обработчик ОбработкаЗаполнения в модуле объекта документа корректно обрабатывает переданный параметр ДанныеЗаполнения в зависимости от его типа. Вот пример, демонстрирующий обработку различных типов данных:
// Пример обработчика ОбработкаЗаполнения в модуле объекта документа
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
// Стандартная обработка обычно всегда Истина, если не нужно ее отключать
СтандартнаяОбработка = Истина;
Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ЗаказПокупателя") Тогда
// Сценарий: Заполнение документа на основании другого документа (ЗаказПокупателя)
ЭтотОбъект.ЗаказПокупателя = ДанныеЗаполнения;
ЭтотОбъект.Контрагент = ДанныеЗаполнения.Контрагент;
// Заполнение табличных частей из ЗаказаПокупателя
Для Каждого СтрокаЗаказа Из ДанныеЗаполнения.Товары Цикл
НоваяСтрока = ЭтотОбъект.Товары.Добавить();
НоваяСтрока.Номенклатура = СтрокаЗаказа.Номенклатура;
НоваяСтрока.Количество = СтрокаЗаказа.Количество;
НоваяСтрока.Цена = СтрокаЗаказа.Цена;
НоваяСтрока.Сумма = СтрокаЗаказа.Сумма;
КонецЦикла;
// ... другая логика заполнения из Заказа ...
ИначеЕсли ТипЗнч(ДанныеЗаполнения) = Тип("Структура") Тогда
// Сценарий: Заполнение по переданной программно структуре параметров
Если ДанныеЗаполнения.Свойство("Ответственный") Тогда
ЭтотОбъект.Ответственный = ДанныеЗаполнения.Ответственный;
КонецЕсли;
Если ДанныеЗаполнения.Свойство("ВалютаДокумента") Тогда
ЭтотОбъект.Валюта = ДанныеЗаполнения.ВалютаДокумента;
КонецЕсли;
Если ДанныеЗаполнения.Свойство("ТипОперации") Тогда
Если ДанныеЗаполнения.ТипОперации = "Возврат" Тогда
ЭтотОбъект.ВидОперации = Перечисления.ВидыОперацийРеализации.ВозвратПокупателя;
Иначе
ЭтотОбъект.ВидОперации = Перечисления.ВидыОперацийРеализации.Реализация;
КонецЕсли;
КонецЕсли;
// ... другая логика заполнения из структуры ...
ИначеЕсли ТипЗнч(ДанныеЗаполнения) = Тип("Неопределено") Тогда
// Сценарий: Стандартное заполнение по умолчанию, если нет явного основания
ЭтотОбъект.Дата = ТекущаяДата();
ЭтотОбъект.Организация = УправлениеПользователями.ПолучитьЗначениеПоУмолчанию(
"ОсновнаяОрганизацияДляНовыхДокументов");
// ...
КонецЕсли;
// Дополнительная логика заполнения, общая для всех случаев
// Например, установка статуса или проверка прав
Если ЭтотОбъект.Статус = Неопределено Тогда
ЭтотОбъект.Статус = ПредопределенноеЗначение("Перечисление.СтатусыДокументов.Черновик");
КонецЕсли;
КонецПроцедуры
После того как метод Заполнить() отработал и внес все необходимые изменения во временный серверный объект Док, нам необходимо отобразить эти изменения на форме, чтобы пользователь мог их видеть и, возможно, продолжить работу. Для этого служит метод ЗначениеВРеквизитФормы().
Метод ЗначениеВРеквизитФормы(Док, "Объект") выполняет обратную операцию:
Док, который был модифицирован после вызова Заполнить().Объект. Этот процесс включает в себя обновление всех связанных с реквизитами формы элементов управления.Пример использования:
// Выполняется на сервере
// Возвращаем измененный объект Док обратно в реквизит формы "Объект"
ЗначениеВРеквизитФормы(Док, "Объект");
Собираем все шаги вместе. Предположим, мы хотим вызывать автоматическое заполнение перед записью документа, основываясь на некотором значении (например, на выборе типа операции или на данных из другого реквизита формы). Это позволит нам реализовать логику, при которой система "автоматически решает", как должны быть заполнены некоторые поля, даже если пользователь мог их изменить.
Разместим этот код в обработчике события формы ПередЗаписьюНаСервере. Это гарантирует, что автоматическое заполнение произойдет непосредственно перед сохранением документа в базу данных.
// Процедура обработки события формы "ПередЗаписьюНаСервере"
Процедура ПередЗаписьюНаСервере(Отказ, Замещать)
// 1. Получаем объект документа из реквизита формы.
// Эта операция создает временную копию объекта со всеми текущими данными формы
// на сервере, даже если документ новый и не записан в базу.
Док = РеквизитФормыВЗначение("Объект");
// 2. Вызываем метод Заполнить() для этого объекта.
// Метод Заполнить() вызывает обработчик события ОбработкаЗаполнения
// в модуле объекта документа.
// В качестве параметра (ДанныеЗаполнения) можно передать:
// - Ссылку на документ/справочник, на основании которого заполняем
// - Структуру с произвольными параметрами для заполнения
// - Неопределено, если заполнение без явного основания
// Пример: создадим структуру для передачи параметров,
// чтобы ОбработкаЗаполнения могла гибко реагировать на причину вызова.
// Предположим, у нас на форме есть реквизит "СценарийАвтоЗаполнения",
// который определяет, как именно должен заполниться документ.
ДанныеДляАвтоматическогоЗаполнения = Новый Структура;
ДанныеДляАвтоматическогоЗаполнения.Вставить("ПричинаЗаполнения", "АвтоматическоеПередЗаписью");
// Допустим, на форме есть поле "ВалютаДокумента", которое мы хотим использовать как один из параметров
// для ОбработкаЗаполнения, если оно заполнено.
Если ЗначениеЗаполнено(Валюта) Тогда // Валюта - это реквизит формы
ДанныеДляАвтоматическогоЗаполнения.Вставить("ВыбраннаяВалюта", Валюта);
КонецЕсли;
// Вызов Заполнить() и, как следствие, ОбработкаЗаполнения модуля объекта.
// Логика в ОбработкаЗаполнения должна быть готова обработать "ДанныеДляАвтоматическогоЗаполнения".
Док.Заполнить(ДанныеДляАвтоматическогоЗаполнения);
// 3. Возвращаем измененный объект обратно в реквизит формы.
// Это обновляет все поля на форме в соответствии с изменениями,
// внесенными в Док после вызова Заполнить().
ЗначениеВРеквизитФормы(Док, "Объект");
// Если есть какая-то дальнейшая логика, которую нужно выполнить
// после автоматического заполнения, но до окончательной записи,
// она может быть размещена здесь.
// Например, проверка заполнения критически важных полей, которые могли
// быть не заполнены даже после автоматизации.
// Если НЕ ЗначениеЗаполнено(Док.Ответственный) Тогда
// Сообщить("Поле 'Ответственный' не заполнено!");
// Отказ = Истина;
// КонецЕсли;
КонецПроцедуры
Как верно отметил автор в сообщении 5, такое решение может быть частью "гениально спроектированной" системы. Почему? Мы выясним причины, которые делают этот подход не просто рабочим, но и оптимальным для платформы 1С:Предприятие 8.3:
ОбработкаЗаполнения). Это делает код более поддерживаемым, легко тестируемым и переиспользуемым, так как она вызывается как при интерактивном вводе на основании, так и при программном создании или, как в нашем случае, при автоматическом заполнении на форме. Это соответствует принципам DRY (Don't Repeat Yourself).Заполнить() не только ссылку на объект-основание, но и структуру с произвольными параметрами (ДанныеЗаполнения) делает этот механизм чрезвычайно гибким для адаптации к самым разнообразным сценариям автоматического заполнения. Мы можем передавать контекст вызова, пользовательские настройки, результаты предварительных расчетов и многое другое, позволяя обработчику ОбработкаЗаполнения максимально точно настроить поведение.РеквизитФормыВЗначение() и ЗначениеВРеквизитФормы() является стандартным и рекомендованным платформой 1С:Предприятие 8.3 для работы с данными формы на сервере. Это гарантирует стабильность, производительность и совместимость с будущими версиями платформы.Такое использование РеквизитФормыВЗначение и ЗначениеВРеквизитФормы позволяет нам, находясь в контексте формы, "поручить" серверу выполнить сложную объектную логику, описанную в модуле объекта, с текущими данными формы. Это мощный инструмент для создания интеллектуальных, автоматизированных и надежных систем на платформе 1С:Предприятие, где бизнес-логика должна быть не только гибкой, но и строго контролируемой.
В заключение, мы выяснили, что программный вызов ОбработкаЗаполнения для объекта формы перед записью не только возможен, но и является стандартным, хорошо продуманным механизмом платформы 1С. Используя связку РеквизитФормыВЗначение(), метод Заполнить() и ЗначениеВРеквизитФормы(), мы можем эффективно реализовать сложную логику автоматического заполнения, обеспечивая при этом гибкость, переиспользуемость кода и высокую степень автоматизации бизнес-процессов.