Разработчики 1С при решении прикладных задач нередко сталкиваются с ситуацией: определенный алгоритм или расчет уже написан в модуле формы документа и прекрасно работает в интерактивном режиме. Когда же возникает необходимость выполнить ту же логику при проведении или групповом перепроведении документов, появляется соблазн вызвать готовую процедуру формы напрямую из модуля объекта. Разберем детально, почему такая архитектура в корне неверна, к каким фатальным ошибкам она приводит и как правильно организовать вызов общих алгоритмов.
Платформа 1С:Предприятие построена на строгом разделении уровней визуализации (интерфейса) и бизнес-логики данных. Проанализируем ключевые причины, делающие вызов формы из объекта невозможным:
ДанныеФормыСтруктура. При вызове сервера для записи создается отдельный экземпляр объекта базы данных ДокументОбъект. Он не имеет и не должен иметь обратной ссылки на визуальный интерфейс конкретного клиентского сеанса.ОбработкаПроведения) и проверки перед записью должны гарантировать целостность данных независимо от того, как именно проводится документ — интерактивно пользователем через кнопку или программно в фоновом задании.Часто попытка вызвать форму возникает из-за того, что разработчик перенес код запроса из формы в модуль объекта, но в модуле объекта запрос перестал возвращать данные (не заходит в цикл обработки). Выясним основные причины, из-за которых одинаковый запрос может вести себя по-разному на форме и в модуле объекта:
ОбработкаПроведения этих записей в регистре уже нет.ЭтотОбъект или свойства шапки).Посмотрим на классический пример неоптимального кода, который часто пишут начинающие разработчики:
// Неэффективный подход: ручное создание колонок, цикл и свертка внутри обхода
КвалификаторыЧисла = Новый КвалификаторыЧисла(10, 2, ДопустимыйЗнак.Любой);
ТЗВирт = Новый ТаблицаЗначений;
ТЗВирт.Колонки.Добавить("ОрганизацияОтправитель", Новый ОписаниеТипов("СправочникСсылка.Организации"));
ТЗВирт.Колонки.Добавить("Период", Новый ОписаниеТипов("Дата"));
ТЗВирт.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", КвалификаторыЧисла));
// Выполнение запроса и ручная перегрузка
Результат = Запрос.Выполнить().Выгрузить();
Для Каждого Стр Из Результат Цикл
ЗаполнитьЗначенияСвойств(ТЗВирт.Добавить(), Стр);
ТЗВирт.Свернуть("ОрганизацияОтправитель", "Количество"); // Ошибка: нельзя сворачивать таблицу в цикле
КонецЦикла;
Разберем допущенные здесь ошибки:
Выгрузить() объекта РезультатЗапроса автоматически создает таблицу значений с правильными типами и именами колонок.Свернуть() на каждой итерации цикла многократно замедляет работу системы. Свертка — это групповая операция, которая выполняется один раз для всей таблицы целиком.Период нужно только для отбора в условии ГДЕ, его не нужно помещать в секцию ВЫБРАТЬ, иначе оно мешает корректной группировке данных.Чтобы алгоритм работал надежно и в форме, и при проведении документа, применим правильный шаблон проектирования: перенесем общую логику проверки в модуль менеджера или серверный общий модуль, а вычисления переложим на сам запрос.
Рассмотрим реализацию проверки в модуле объекта с получением уже сгруппированных данных напрямую из СУБД:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Формируем запрос с агрегацией на уровне базы данных
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Услуги.ОрганизацияОтправитель КАК ОрганизацияОтправитель,
| СУММА(Услуги.Количество) КАК Количество
|ИЗ
| РегистрНакопления.УслугиПодразделениям КАК Услуги
|ГДЕ
| Услуги.Период МЕЖДУ &НачалоПериода И &КонецПериода
| И Услуги.ОрганизацияОтправитель = &ОрганизацияОтправитель
|СГРУППИРОВАТЬ ПО
| Услуги.ОрганизацияОтправитель
|ИМЕЮЩИЕ
| СУММА(Услуги.Количество) > 1";
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ЭтотОбъект.Дата));
Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ЭтотОбъект.Дата));
Запрос.УстановитьПараметр("ОрганизацияОтправитель", ЭтотОбъект.Организация);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Формируем платформенное сообщение пользователю
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = СтрШаблон(
"Превышен лимит количества по организации %1. Текущее значение: %2",
Выборка.ОрганизацияОтправитель,
Выборка.Количество
);
Сообщение.Поле = "Организация";
Сообщение.УстановитьДанные(ЭтотОбъект);
Сообщение.Сообщить();
КонецЦикла;
КонецПроцедуры
Если один и тот же расчет или проверку необходимо выполнять и на форме документа (например, при нажатии кнопки проверки или изменении реквизита), и в модуле объекта при проведении, применим модуль менеджера документа:
ОбработкаПроведения модуля объекта.Посмотрим на пример оформления такой процедуры в модуле менеджера:
// В модуле менеджера документа
Процедура ПроверитьПревышениеЛимитов(Организация, ДатаДокумента, Отказ = Ложь) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Услуги.ОрганизацияОтправитель КАК ОрганизацияОтправитель,
| СУММА(Услуги.Количество) КАК Количество
|ИЗ
| РегистрНакопления.УслугиПодразделениям КАК Услуги
|ГДЕ
| Услуги.Период МЕЖДУ &НачалоПериода И &КонецПериода
| И Услуги.ОрганизацияОтправитель = &ОрганизацияОтправитель
|СГРУППИРОВАТЬ ПО
| Услуги.ОрганизацияОтправитель
|ИМЕЮЩИЕ
| СУММА(Услуги.Количество) > 1";
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ДатаДокумента));
Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ДатаДокумента));
Запрос.УстановитьПараметр("ОрганизацияОтправитель", Организация);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Обнаружено превышение допустимого объема услуг!";
Сообщение.Сообщить();
Отказ = Истина;
КонецЦикла;
КонецПроцедуры
Теперь в модуле объекта достаточно написать краткий вызов:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
Документы.АктОбОказанииУслуг.ПроверитьПревышениеЛимитов(ЭтотОбъект.Организация, ЭтотОбъект.Дата, Отказ);
Если Отказ Тогда
Возврат;
КонецЕсли;
КонецПроцедуры
Такая структура полностью исключает зависимость сервера от клиентской формы, сохраняет работоспособность при групповом проведении документов и делает код чистым и легким для сопровождения.