Как программно проверить, был ли проведен документ после его открытия пользователем в 1С?

Программист 1С v8.3 (Обычные формы)
← На главную

При разработке в 1С часто возникает задача, когда необходимо открыть форму нового или существующего документа для заполнения пользователем, а затем программно проконтролировать результат его закрытия. Нас интересует, был ли документ проведен или же пользователь просто закрыл форму, отменив изменения или сохранив документ без проведения. Более того, при успешном проведении первого документа, может потребоваться автоматически создать и провести связанный документ — для этой задачи есть конструктор создания связанных документов по правилам пользователя.

Давайте рассмотрим эту проблему со всех сторон и предложим несколько эффективных решений, применимых в различных сценариях.

1. Контроль результата закрытия формы при модальном открытии (для обычных форм)

В обычных формах 1С наиболее прямым и понятным способом дождаться действий пользователя и получить обратную связь является модальное открытие формы. При таком подходе выполнение кода приостанавливается до тех пор, пока пользователь не закроет форму. После закрытия мы сможем проанализировать, был ли документ проведен.

Процесс модального открытия и проверки:

  1. Создание нового документа: Прежде чем открывать форму, мы создаем новый объект документа. Это позволит нам предварительно заполнить его необходимые реквизиты.
  2. Получение формы и модальное открытие: Получаем форму документа и открываем ее модально с помощью метода ОткрытьМодально().
  3. Проверка результата после закрытия: После того как форма будет закрыта, мы обновляем данные документа из базы данных и проверяем значение реквизита Проведен (поможет автоматические контроли и проверки документов без кода).

Рассмотрим пример кода:


// Создание нового документа "Анкета"
НовыйДокумент = Документы.Анкета.СоздатьДокумент();

// Заполнение некоторых реквизитов документа
НовыйДокумент.Респондент = ВасяПупкин; // Предполагаем, что ВасяПупкин - это ссылка на элемент справочника
НовыйДокумент.Дата = ТекущаяДата();
// ... Дополнительное заполнение других реквизитов ...

// Получение формы документа.
// Метод ПолучитьФорму может принимать необязательный параметр имени формы,
// если у документа несколько форм.
Форма = НовыйДокумент.ПолучитьФорму("ФормаДокумента");

// Модальное открытие формы. Выполнение кода будет приостановлено до ее закрытия.
Форма.ОткрытьМодально();

// После закрытия формы, нам нужно обновить объект документа,
// чтобы получить его актуальное состояние из базы данных.
// Если документ был создан и записан, у него появится ссылка.
// Если форму просто закрыли без записи, ссылка будет пустой.
Если ЗначениеЗаполнено(НовыйДокумент.Ссылка) Тогда
    // Получаем сам объект документа по его ссылке, чтобы увидеть изменения
    ОбъектДокументаИзБазы = НовыйДокумент.Ссылка.ПолучитьОбъект();

    Если ОбъектДокументаИзБазы.Проведен Тогда
        // Документ был успешно записан и проведен пользователем
        Сообщить("Документ """ + ОбъектДокументаИзБазы + """ успешно проведен.");

        // Здесь мы можем выполнить логику по созданию и проведению второго документа
        // Например, создание документа "ОпросныйЛист" на основании "Анкеты"
        ВторойДокумент = Документы.ОпросныйЛист.СоздатьДокумент();
        ВторойДокумент.Дата = ТекущаяДата();
        // Присваиваем ссылку на проведенную анкету в качестве основания
        // Важно использовать Ссылка, если реквизит составного типа
        ВторойДокумент.ДокументОснование = ОбъектДокументаИзБазы.Ссылка; 
        ВторойДокумент.Респондент = ОбъектДокументаИзБазы.Респондент;
        // ... заполнение других реквизитов ВторогоДокумента ...

        Попытка
            ВторойДокумент.Записать(РежимЗаписиДокумента.Проведение);
            Сообщить("Второй документ """ + ВторойДокумент + """ успешно проведен.");
        Исключение
            Сообщить("Ошибка проведения второго документа: " + ОписаниеОшибки());
        КонецПопытки;

    Иначе
        // Документ был записан, но не проведен, или просто закрыт без проведения
        Сообщить("Документ """ + ОбъектДокументаИзБазы + """ был сохранен, но не проведен.");
    КонецЕсли;
Иначе
    // Пользователь закрыл форму нового документа, не сохранив его
    Сообщить("Форма нового документа была закрыта без сохранения.");
КонецЕсли;

Важный момент: если реквизит документа имеет составной тип, для корректного присвоения значения-ссылки необходимо явно указывать свойство Ссылка у объекта-источника, как показано в примере: ВторойДокумент.ДокументОснование = ОбъектДокументаИзБазы.Ссылка;

2. Механизм оповещений (для управляемых форм)

В управляемых формах 1С подход с модальным открытием форм крайне не рекомендуется, а иногда и вовсе невозможен, так как это блокирует интерфейс пользователя. Вместо этого используется механизм оповещений. Он позволяет выполнить код асинхронно после закрытия формы, не блокируя работу пользователя — для этого подойдёт настройка собственных обработчиков событий объектов и форм.

Принцип работы оповещений:

  1. Создание процедуры-обработчика: В модуле, откуда мы открываем форму, создаем экспортируемую процедуру, которая будет вызвана после закрытия формы документа.
  2. Открытие формы с оповещением: При открытии формы документа с помощью метода ОткрытьФорму() мы передаем специальный параметр ОповещениеОЗакрытии. Этот параметр содержит описание нашей процедуры-обработчика.
  3. Оповещение о закрытии из формы документа: В самой форме документа, например, в обработчике команды "Провести и закрыть" или при интерактивном проведении, мы вызываем метод ОповеститьОЗакрытии(), передавая ему результат действия пользователя.

Рассмотрим пример кода для управляемых форм:

В модуле, откуда открывается форма (например, в модуле команды или формы):


&НаКлиенте
Процедура ОткрытьФормуДокументаАнкета(Команда)
    // Создание нового документа
    НовыйДокумент = Документы.Анкета.СоздатьДокумент();
    НовыйДокумент.Респондент = ВасяПупкин; // Пример, ВасяПупкин должен быть заранее определен как ссылка

    // Подготовка параметров формы, если нужно открыть существующий документ
    // или передать начальные значения.
    ПараметрыФормы = Новый Структура("Ключ", НовыйДокумент.Ссылка);
    
    // Создаем описание оповещения:
    // "ПослеЗакрытияФормыДокумента" - имя процедуры-обработчика,
    // ЭтотОбъект - контекст (модуль), в котором находится процедура.
    Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияФормыДокумента", ЭтотОбъект);

    // Открываем форму документа, передавая оповещение.
    ОткрытьФорму("Документ.Анкета.Форма.ФормаДокумента", ПараметрыФормы, , , , , Оповещение);
КонецПроцедуры

&НаКлиенте
// Процедура-обработчик, которая будет вызвана после закрытия формы документа.
// РезультатЗакрытия - это то, что мы передали в ОповеститьОЗакрытии().
Процедура ПослеЗакрытияФормыДокумента(РезультатЗакрытия, ДополнительныеПараметры) Экспорт
    Если РезультатЗакрытия = "Проведен" Тогда
        // Документ был проведен. ДополнительныеПараметры могут содержать ссылку на проведенный документ.
        Если ДополнительныеПараметры <> Неопределено И ТипЗнч(ДополнительныеПараметры) = Тип("ДокументСсылка.Анкета") Тогда
            Сообщить("Документ """ + ДополнительныеПараметры + """ успешно проведен.");

            // Здесь мы можем выполнить логику по созданию и проведению второго документа
            // Например, создание документа "ОпросныйЛист"
            ВторойДокумент = Документы.ОпросныйЛист.СоздатьДокумент();
            ВторойДокумент.Дата = ТекущаяДата();
            ВторойДокумент.ДокументОснование = ДополнительныеПараметры; // Ссылка на проведенную анкету
            // ... заполнение других реквизитов ВторогоДокумента ...
            
            // Проведение второго документа на сервере
            ПараметрыПроведения = Новый Структура("СсылкаДокументаОснования", ДополнительныеПараметры);
            ВыполнитьПроведениеВторогоДокументаНаСервере(ВторойДокумент, ПараметрыПроведения);
        КонецЕсли;
    ИначеЕсли РезультатЗакрытия = "Записан" Тогда
        Сообщить("Документ был записан, но не проведен.");
    Иначе
        Сообщить("Форма документа была закрыта без сохранения или проведения.");
    КонецЕсли;
КонецПроцедуры

&НаСервере
Процедура ВыполнитьПроведениеВторогоДокументаНаСервере(ВторойДокументОбъект, ПараметрыПроведения)
    // В объект ВторогоДокумента уже переданы данные с клиента
    // Нужно только записать и провести его.
    Попытка
        ВторойДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
        Сообщить("Второй документ """ + ВторойДокументОбъект.Ссылка + """ успешно проведен на сервере.");
    Исключение
        Сообщить("Ошибка проведения второго документа на сервере: " + ОписаниеОшибки());
    КонецПопытки;
КонецПроцедуры

В модуле формы документа "Анкета" (ФормаДокумента):


&НаКлиенте
// Обработчик команды "Провести и закрыть" на форме
Процедура ПровестиИЗакрыть(Команда)
    // Вызываем стандартный обработчик проведения и закрытия
    ЭтотОбъект.ПровестиИЗакрыть(Команда); // Или ПростоПровести(), если нужно только провести

    // После успешного проведения и закрытия формы, оповещаем вызывающую сторону.
    // Передаем строку "Проведен" как результат и ссылку на документ.
    // Важно, что на момент вызова ОповеститьОЗакрытии() документ уже должен быть записан и иметь ссылку.
    Если Объект.Проведен Тогда
        ОповеститьОЗакрытии("Проведен", Объект.Ссылка);
    Иначе
        ОповеститьОЗакрытии("Записан", Объект.Ссылка);
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
// Обработчик события ПриЗакрытии для случая, если форма закрыта без проведения
Процедура ПриЗакрытии()
    // Проверяем, было ли уже оповещение о проведении.
    // Если ОповеститьОЗакрытии() уже вызвано в ПровестиИЗакрыть, то здесь не нужно дублировать.
    // Если оповещения не было, значит, форма закрыта без сохранения или проведения.
    // Можно добавить флаг, чтобы избежать двойного оповещения.
    // Для простоты примера, предположим, что оповещение происходит только в ПровестиИЗакрыть.
    // Если же пользователь просто закрыл форму без записи, то Объект.Ссылка будет пустой.
    Если Не ЗначениеЗаполнено(Объект.Ссылка) Тогда
        ОповеститьОЗакрытии("ЗакрытБезСохранения");
    КонецЕсли;
КонецПроцедуры

3. Подписка на событие "ОбработкаПроведения"

Подписка на событие — это мощный, централизованный и наиболее предпочтительный инструмент (удобно через автоматизация изменения состояний документов по регламенту), когда нам нужно реагировать на проведение документа независимо от того, как оно было инициировано: из формы, программно, в результате группового проведения или даже при обмене данными. Подписки позволяют инкапсулировать логику, связанную с проведением, в одном месте.

Что необходимо учесть при использовании подписки:

  1. Создание подписки: В конфигураторе создаем новый объект "Подписка на событие". Указываем "Источник" (например, Документ.Анкета), "Событие" (ОбработкаПроведения) и "Обработчик" (имя общей серверной процедуры).
  2. Доступ к документу: В процедуре-обработчике подписки нам доступен параметр Источник, который является самим проводимым документом (объектом). Через него мы можем получить все необходимые данные для нашей логики.
  3. Контекст выполнения: Код в подписке выполняется на сервере в момент транзакции проведения документа. Это обеспечивает высокую надежность, но накладывает ограничения: из подписки нельзя взаимодействовать с пользовательским интерфейсом (открывать формы, задавать вопросы).
  4. Отмена проведения: Если в процессе выполнения логики в подписке выясняется, что проведение основного документа невозможно (например, не удалось создать и провести связанный документ), мы можем установить параметр Отказ в Истина. Это отменит проведение основного документа.
  5. Избегание рекурсии: Крайне важно избегать ситуаций, когда в подписке на проведение документа А мы инициируем проведение документа Б, а у документа Б, в свою очередь, есть подписка, которая вызывает повторное проведение документа А. Это приведет к зацикливанию.

Рассмотрим пример создания и использования подписки:

1. Создание подписки в конфигураторе:

2. Код в общем модуле ПроведениеДокументовСервер:


// Это процедура-обработчик подписки на событие "ОбработкаПроведения" для документа "Анкета".
// Она будет вызвана на сервере перед штатным проведением документа.
Процедура ПроведениеАнкеты_СоздатьОпросныйЛист(Источник, Отказ, РежимПроведения) Экспорт

    // Источник - это объект проводимого документа "Анкета"
    Анкета = Источник;

    // Выполняем логику только если анкета должна быть проведена
    // и если нам нужен второй документ
    Если Не Анкета.Проведен Тогда // Если документ проводится первый раз или перепроводится
        Попытка
            // Создаем новый документ "ОпросныйЛист"
            ВторойДокумент = Документы.ОпросныйЛист.СоздатьДокумент();
            ВторойДокумент.Дата = ТекущаяДата();
            ВторойДокумент.ДокументОснование = Анкета.Ссылка; // Присваиваем ссылку на анкету
            ВторойДокумент.Респондент = Анкета.Респондент;
            ВторойДокумент.Комментарий = "Сформирован автоматически на основании Анкеты №" + Анкета.Номер + " от " + Формат(Анкета.Дата, "ДЛФ=Д");
            // ... Дополнительное заполнение реквизитов ВторогоДокумента ...

            // Записываем и проводим второй документ.
            // Если в ОпросномЛисте есть свои проверки проведения, они будут выполнены.
            ВторойДокумент.Записать(РежимЗаписиДокумента.Проведение);

            // Если все прошло успешно, можем записать ссылку на созданный ОпросныйЛист
            // в какой-нибудь реквизит Анкеты, если это требуется.
            // Анкета.ОпросныйЛист = ВторойДокумент.Ссылка;
            // Важно: если мы меняем реквизиты Анкета (Исходника) в подписке,
            // то их надо записать до того как подписка завершится,
            // но в режиме ОбработкаПроведения это обычно происходит автоматически,
            // т.к. основной документ еще не записан окончательно.

        Исключение
            // Если при создании или проведении второго документа произошла ошибка,
            // мы можем отменить проведение основного документа (Анкеты).
            Отказ = Истина;
            Сообщить("Ошибка при автоматическом создании и проведении ОпросногоЛиста: " + ОписаниеОшибки(), СтатусСообщения.Важное);
        КонецПопытки;
    КонецЕсли;

КонецПроцедуры

Использование подписки на событие является самым гибким и надежным способом для выполнения дополнительных действий при проведении документов, так как он не зависит от конкретного способа инициирования проведения и обеспечивает целостность данных за счет выполнения в рамках транзакции.

4. Программное создание и проведение без открытия формы

Если взаимодействие с пользователем для заполнения документа не требуется, и все необходимые данные для создания и проведения документа уже известны программно, то открывать форму вовсе не нужно. Этот подход является самым простым, быстрым и предпочтительным для полностью автоматизированных операций.

Процесс:

  1. Создание документа: Используем метод СоздатьДокумент() для получения нового объекта документа.
  2. Заполнение реквизитов: Заполняем все необходимые реквизиты программно.
  3. Запись и проведение: Используем метод Записать() с параметром РежимЗаписиДокумента.Проведение.

Рассмотрим пример кода:


// 1. Создание и проведение первого документа (например, "Анкета")
ПервыйДок = Документы.Анкета.СоздатьДокумент();
ПервыйДок.Дата = ТекущаяДата();
ПервыйДок.Респондент = ВасяПупкин; // Предполагаем, что ВасяПупкин - ссылка на элемент справочника
ПервыйДок.ОтветНаВопрос1 = "Да";
ПервыйДок.ОтветНаВопрос2 = "Нет";
// ... заполнение остальных реквизитов первого документа ...

Попытка
    ПервыйДок.Записать(РежимЗаписиДокумента.Проведение);
    Сообщить("Документ """ + ПервыйДок.Ссылка + """ успешно создан и проведен программно.");

    // 2. Создание и проведение второго документа (например, "ОпросныйЛист") на основании первого
    ВторойДок = Документы.ОпросныйЛист.СоздатьДокумент();
    ВторойДок.Дата = ТекущаяДата();
    ВторойДок.ДокументОснование = ПервыйДок.Ссылка; // Присваиваем ссылку на проведенную анкету
    ВторойДок.Респондент = ПервыйДок.Респондент;
    ВторойДок.Комментарий = "Автоматически создан по Анкете " + ПервыйДок.Номер;
    // ... заполнение других реквизитов второго документа ...

    ВторойДок.Записать(РежимЗаписиДокумента.Проведение);
    Сообщить("Документ """ + ВторойДок.Ссылка + """ успешно создан и проведен программно на основании первого.");

Исключение
    Сообщить("Произошла ошибка при программном создании/проведении документов: " + ОписаниеОшибки());
КонецПопытки;

Этот подход является наиболее предпочтительным для полностью автоматизированных процессов, так как он не зависит от пользовательского интерфейса, выполняется последовательно и предсказуемо, и обеспечивает максимальную производительность.

Резюме и рекомендации

  1. Если вам необходимо взаимодействие с пользователем для заполнения документа, а затем контроль его проведения:
    • В обычных формах используйте Форма.ОткрытьМодально(); и после ее закрытия проверяйте ОбъектДокумента.Проведен.
    • В управляемых формах используйте механизм ОповещениеОЗакрытии при открытии формы и ОповеститьОЗакрытии() из самой формы.
  2. Если вам нужно автоматически реагировать на проведение документа независимо от того, как оно было инициировано (интерактивно, программно, групповое проведение):
  3. Если вам не требуется взаимодействие с пользователем, и все данные известны программно:
    • Просто создавайте документ через Документы.<ИмяДокумента>.СоздатьДокумент(), заполняйте его реквизиты и вызывайте Записать(РежимЗаписиДокумента.Проведение). Это самый эффективный способ для автоматизации.

Выбирайте подход, который наилучшим образом соответствует вашим задачам и архитектуре вашей конфигурации.

← На главную