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

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

При разработке в 1С часто возникает задача, когда заказчик желает видеть интерактивное подтверждение (окно типа "Да/Нет") перед проведением документа при определенных условиях. Однако эта задача усложняется, когда проведение документа осуществляется не только из открытой формы документа, но и из формы списка (например, нажатием кнопки "Провести" на панели или через контекстное меню), а также при групповом проведении. Мы проанализируем эту ситуацию и рассмотрим доступные методы решения.

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

Различие между событиями формы и объекта документа

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

  1. Процедура ПередЗаписью() в модуле формы документа:
    • Эта процедура вызывается только при интерактивной записи объекта из формы. Это происходит, когда пользователь нажимает кнопки вроде "Записать", "ОК" или "Провести и закрыть" в открытой форме документа.
    • Выполнение этой процедуры происходит на клиенте. Это означает, что здесь мы имеем прямой доступ к клиентскому интерфейсу и можем использовать функции для вывода модальных диалогов, например, Вопрос() (хотя в управляемых формах это считается устаревшей практикой) или асинхронную функцию ПоказатьВопрос().
    • Мы можем использовать параметр ПараметрыЗаписи для передачи дополнительной информации, такой как режим записи или проведения документа.
  2. Процедура ПередЗаписью() в модуле объекта документа:
    • Эта процедура вызывается при любых операциях записи, независимо от того, как была инициирована запись: программно, при групповом проведении, или даже при проведении из формы списка, когда сам документ не открыт.
    • Выполнение этой процедуры происходит на сервере. Это критически важно, поскольку на сервере нет прямого доступа к клиентскому интерфейсу для вывода модальных окон. Попытка вызвать модальное окно из серверного контекста приведет к ошибке или непредсказуемому поведению, особенно при групповых операциях.
    • Если мы хотим отменить проведение документа на этом этапе, мы можем установить параметр Отказ = Истина.
  3. Событие ПередЗаписьюНаСервере формы документа:
    • Это событие формы, но оно выполняется на сервере после проверки заполнения. В нем уже есть доступ к объекту, который будет записан (через ТекущийОбъект). Изменения, внесенные в ТекущийОбъект, будут записаны, в отличие от изменений в основном реквизите формы Объект, которые могут быть потеряны.
    • Поскольку оно выполняется на сервере, напрямую вывести модальное окно здесь также невозможно. Однако, зная, что мы находимся в контексте формы, мы можем использовать асинхронные механизмы.
  4. Процедура ОбработкаПроведения(Отказ, РежимПроведения) модуля объекта документа:
    • Это основная логика проведения документа, которая формирует движения в регистрах. Она вызывается при любом способе проведения (интерактивном, программном, групповом).
    • Как и ПередЗаписью() объекта, она выполняется на сервере и не может выводить интерактивные диалоги. Установка Отказ = Истина в этой процедуре отменит проведение.

Почему стандартные методы не подходят для проведения из списка или групповых операций?

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

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

Предлагаемые решения и подходы

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

1. Создание собственных команд проведения

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

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

      В обработчике команды "Провести (с вопросом)" формы документа мы можем использовать асинхронный вызов ПоказатьВопрос(). Мы установим Отказ = Истина в ПередЗаписью формы, а затем зададим вопрос. Если пользователь ответит "Да", мы повторно вызовем стандартную процедуру записи/проведения документа, но уже без вопросов.

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

      В обработчике команды "Провести (с вопросом)" формы списка мы можем проверить количество выделенных документов. Если выделен только один документ, мы можем открыть его форму и вызвать нашу команду проведения с вопросом. Если выделено несколько документов, мы можем либо провести их без вопроса, либо выдать предупреждение о невозможности интерактивного подтверждения для группы, либо вовсе отказать в групповом проведении через эту команду.

      
      // Пример в модуле формы списка документов
      &НаКлиенте
      Процедура МояКомандаПровестиИзСписка(Команда)
          ВыбранныеЭлементы = Элементы.Список.ВыделенныеСтроки;
      
          Если ВыбранныеЭлементы.Количество() = 1 Тогда
              ИДДокумента = ВыбранныеЭлементы[0];
              // Получим ссылку на документ
              ДокументСсылка = Документы.МойДокумент.ПолучитьСсылку(ИДДокумента);
      
              // Открываем форму документа и вызываем там нашу команду проведения
              ФормаДокумента = ПолучитьФорму("Документ.МойДокумент.ФормаДокумента", Новый Структура("Ключ", ДокументСсылка));
              // Передаем параметр, чтобы форма знала, что нужно задать вопрос
              ФормаДокумента.ДополнительныеСвойства.Вставить("ТребуетсяПодтверждениеПроведения", Истина);
              ФормаДокумента.Открыть();
              // В самой форме документа нужно будет вызвать команду проведения после открытия,
              // если ТребуетсяПодтверждениеПроведения == Истина.
              // Или можно передать в ПараметрыОткрытия, чтобы обработать в ПриСозданииНаСервере
              // и вызвать асинхронный вопрос.
      
          ИначеЕсли ВыбранныеЭлементы.Количество() > 1 Тогда
              ПоказатьПредупреждение(Новый ОписаниеОповещения(""), "Для группового проведения интерактивное подтверждение невозможно. Документы будут проведены без вопроса.");
              // Здесь вызываем серверную процедуру для группового проведения без вопросов
              ПровестиГруппуДокументовНаСервере(ВыбранныеЭлементы);
          Иначе
              ПоказатьПредупреждение(Новый ОписаниеОповещения(""), "Не выбрано ни одного документа для проведения.");
          КонецЕсли;
      КонецПроцедуры
      
      &НаСервере
      Процедура ПровестиГруппуДокументовНаСервере(ИДДокументов)
          Для Каждого ИД Из ИДДокументов Цикл
              ДокументОбъект = Документы.МойДокумент.ПолучитьОбъект();
              ДокументОбъект.Прочитать(); // или сразу получить по ссылке
              ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
          КонецЦикла;
      КонецПроцедуры
      

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

2. Проверка количества выделенных документов в серверной процедуре (частичное решение)

Если мы не хотим полностью переписывать команды, но хотим хотя бы избежать блокировки при групповом проведении, можно в процедуре ПередЗаписью() или ОбработкаПроведения() модуля объекта документа попытаться определить контекст. Однако это очень сложно и ненадежно, так как серверный код не знает, из какого именно места был вызван метод. Мы не можем точно знать, сколько документов пытается провести пользователь, если это не явно передано в параметрах.

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

3. Использование асинхронных вызовов для вопросов (ПоказатьВопрос())

Как уже упоминалось, платформа 1С 8.3 для управляемых форм рекомендует использовать ПоказатьВопрос() вместо модальных функций Вопрос(). Это особенно актуально, если мы хотим сохранить возможность интерактивного диалога при проведении из открытой формы документа.

  1. Установим Отказ = Истина: В процедуре ПередЗаписьюНаСервере формы (если мы хотим, чтобы вопрос задавался перед записью) мы можем установить Отказ = Истина, если условия для вопроса выполнены.
  2. Вызовем ПоказатьВопрос() на клиенте: После отмены записи на сервере, управление вернется на клиент. Здесь мы можем вызвать ПоказатьВопрос(), передав в него ОписаниеОповещения.
  3. Обработаем ответ: В процедуре-обработчике, указанной в ОписаниеОповещения, мы проверяем ответ пользователя. Если пользователь ответил "Да", мы повторно вызываем стандартную процедуру записи/проведения документа, но уже с флагом, который сообщит серверу (или ПередЗаписьюНаСервере), что вопрос уже был задан и ответ положительный, чтобы избежать повторного вызова вопроса.

// В модуле формы документа
&НаКлиенте
ПеременнаяФормы_ТребуетсяПодтверждениеПроведения = Ложь;

&НаКлиенте
Процедура ПриЗаписи(Отказ)
    Если ПеременнаяФормы_ТребуетсяПодтверждениеПроведения Тогда
        Возврат; // Пропускаем проверку, если уже ответили на вопрос
    КонецЕсли;

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

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

&НаКлиенте
Функция УсловияДляВопросаВыполнены()
    // Здесь ваша логика определения, нужен ли вопрос
    // Например, если какой-то реквизит имеет определенное значение
    Возврат Объект.Сумма > 100000;
КонецФункции

4. Переосмысление бизнес-задачи

Прежде чем реализовывать сложные технические решения, всегда полезно вернуться к исходной бизнес-задаче. Почему заказчику нужен такой вопрос? Возможно, есть более системные и менее интрузивные способы обеспечить контроль:

  1. Статусы документа: Вместо вопроса о проведении, можно ввести статусы документа ("Черновик", "На согласовании", "К проведению", "Проведен"). Проводить можно только документы в статусе "К проведению", который присваивается после всех проверок.
  2. Механизм согласования: Для критичных документов можно использовать механизм согласования, который требует подтверждения от ответственных лиц, прежде чем документ будет доступен для проведения.
  3. Отчеты и предупреждения: Если цель – обратить внимание пользователя на определенные условия, возможно, достаточно выводить не блокирующий диалог, а просто предупреждение или записывать информацию в специальный регистр/отчет, который руководители могут просматривать.

Что не стоит делать

Мы настоятельно рекомендуем избегать любых попыток вывода модальных окон (например, Вопрос()) из процедур, выполняющихся на сервере (таких как ПередЗаписью() или ОбработкаПроведения() модуля объекта). Это приведет к ошибкам в веб-клиенте, в фоновых заданиях, при групповой обработке и при интерактивной работе в тонком клиенте, блокируя выполнение и создавая неудобства для пользователей.

Заключение

Решение задачи по выводу интерактивного подтверждения перед проведением документа, особенно при проведении из формы списка или групповом проведении, требует глубокого понимания архитектуры 1С:Предприятие и клиент-серверного взаимодействия. Универсального "элегантного" решения, которое бы одинаково хорошо работало во всех сценариях, не существует. Нам предстоит идти на компромиссы, выбирая между кастомизацией интерфейса (создание своих команд), использованием асинхронных диалогов и, возможно, пересмотром бизнес-логики. Самым надежным и контролируемым подходом часто оказывается создание собственных команд проведения с детальной обработкой контекста вызова.

← На главную