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