При автоматизации массовых операций в 1С 7.7 программисты часто сталкиваются с архитектурной особенностью этой платформы: значительная часть бизнес-логики (например, формирование сообщений для МДЛП, заполнение сложных табличных частей или создание связанных справочников) может быть прописана непосредственно в модуле формы документа. Когда требуется выполнить такие действия в цикле для сотен документов, обычный программный вызов методов объекта не срабатывает, так как эти методы "видны" только внутри самой формы.
В этой статье мы подробно разберем, как обойти это ограничение, заставить систему выполнить код формы без участия пользователя и корректно закрыть документ, избежав блокировок и ошибок записи.
Проанализируем типичную ситуацию: программист пытается использовать WScript.Shell для эмуляции нажатия клавиш (например, SendKeys("{F12}")). Рассмотрим, почему это плохой путь:
SendKeys отправляет нажатие в активное окно Windows. Если форма документа открывается долго или фокус успел переключиться на другое приложение, нажатие уйдет "в никуда".Записать() из внешнего кода приведет к ошибке "Объект заблокирован".Выясним причину ошибки "Поле агрегатного объекта не обнаружено". Она возникает при попытке вызвать процедуру модуля формы напрямую через объект документа (например, Док.МояПроцедура()). В 1С 7.7 процедуры модуля формы являются локальными и не доступны для вызова "через точку" извне, если не используются специальные расширения типа 1CPP.
Наиболее стабильный способ без использования внешних компонент — это использование метода ОткрытьФормуМодально() с передачей специального контекста в качестве параметра. Разберем этот алгоритм по шагам.
Шаг 1. Подготовка вызывающего кода
В вашей обработке, где происходит перебор документов, мы будем открывать форму каждого документа модально. Модальный режим остановит выполнение цикла до тех пор, пока форма не закроется, что гарантирует последовательную обработку.
// В цикле обработки документов
ДокУведомлениеМДЛП.Записать(); // Предварительно записываем документ
Конт = "ВыполнитьДействиеИЗакрыть";
// Открываем форму модально, передавая строку в качестве параметра
ОткрытьФормуМодально(ДокУведомлениеМДЛП.ТекущийДокумент(), Конт);
// Важно: после закрытия формы объект в переменной ДокУведомлениеМДЛП может стать неактуальным
// Если нужно продолжать работу с этим же документом, его нужно перечитать
ДокУведомлениеМДЛП.НайтиДокумент(ДокУведомлениеМДЛП.ТекущийДокумент());
Шаг 2. Модификация модуля формы документа
Теперь нам нужно научить форму документа реагировать на этот параметр. Переходим в модуль формы нужного документа и ищем предопределенную процедуру ПриОткрытии(). Проанализируем, как внедрить туда наш обработчик.
Процедура ПриОткрытии()
// Проверяем, передан ли параметр
Парам = Форма.Параметр;
Если Парам = "ВыполнитьДействиеИЗакрыть" Тогда
// 1. Вызываем нужную процедуру (ту, что висит на кнопке)
СоздатьСообщениеПротоколаКОтправке();
// 2. Записываем изменения, если процедура их внесла
Записать();
// 3. Отменяем открытие формы
// СтатусВозврата(0) завершает работу формы, она даже не отобразится на экране
СтатусВозврата(0);
Возврат;
КонецЕсли;
// Обычная логика открытия формы для пользователя...
КонецПроцедуры
Рассмотрим подробнее работу СтатусВозврата(0). Этот метод внутри ПриОткрытии() сообщает системе, что открытие формы нужно прервать. Поскольку мы вызывали её модально, управление мгновенно возвращается в исходный цикл обработки, но при этом весь необходимый код внутри формы уже успел выполниться.
Типичная ошибка, которую совершают разработчики — попытка вызвать Док.Записать() в цикле сразу после ОткрытьФормуМодально(). Запомните: если внутри формы вы уже вызвали Записать(), то объект в основной обработке нужно обновить.
Рассмотрим ситуацию: при выполнении кода в форме создаются новые записи в справочниках и ссылка на одну из них записывается в реквизит документа. Если вы не выполните НайтиДокумент() после закрытия формы, ваша переменная ДокУведомлениеМДЛП будет содержать старые данные (состояние до открытия формы). Попытка записи такого объекта приведет либо к потере данных, либо к конфликту блокировок.
Если вы не хотите менять код типовых документов (чтобы не усложнять будущие обновления), можно воспользоваться внешними компонентами. Рассмотрим, как это делает библиотека FormEx.
С помощью FormEx можно программно нажать кнопку на форме по её идентификатору, используя метод ВыполнитьКнопку(). Однако более элегантный способ предоставляет 1CPP. Если процедура в модуле формы объявлена с ключевым словом Экспорт, то при использовании расширения формы её можно вызвать напрямую.
Пример вызова через 1CPP (при условии Экспорта процедуры):
// Создаем расширение формы для доступа к её контексту
Сервис = СоздатьОбъект("Сервис");
КонтФормы = "";
ОткрытьФорму(Док.ТекущийДокумент(), КонтФормы);
// Получаем доступ к процедурам формы
Расширение = СоздатьОбъект("РасширениеФормы");
Расширение.УстановитьФорму(КонтФормы);
// Вызываем экспортную процедуру модуля формы
КонтФормы.СоздатьСообщениеПротоколаКОтправке();
КонтФормы.Форма.Закрыть(0);
Этот метод сложнее в реализации и требует наличия установленных DLL в системе, но он позволяет избежать модификации метода ПриОткрытии().
Проанализировав проблему, стоит отметить, что самый правильный путь с точки зрения архитектуры 1С — это вынос бизнес-логики из модуля формы в модуль объекта или в общую процедуру. Если код создания сообщения протокола будет находиться в модуле документа, вы сможете вызвать его простой командой Док.СоздатьСообщение() без всяких манипуляций с формами, окнами и эмуляцией нажатий.
Подведем итоги:
ОткрытьФормуМодально() с передачей управления через Форма.Параметр.СтатусВозврата(0) в процедуре ПриОткрытии(), если работаете в автоматическом режиме.НайтиДокумент() после выполнения действий в форме.SendKeys, так как это крайне нестабильный метод, зависящий от внешних факторов.Следуя этим правилам, вы обеспечите надежную работу ваших алгоритмов массовой обработки документов в среде 1С:Предприятие 7.7.