Как в 1С:Предприятии (управляемые формы) отменить запись документа с проверкой условий и диалогом?

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

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

1. Отказ от записи с простым предупреждением (событие ПередЗаписью)

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

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

Рассмотрим пример. Допустим, нам нужно проверить некоторое условие на сервере. Для этого мы вызовем серверную функцию из клиентского обработчика.


&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
    Если Не ПроверитьНекотороеУсловиеНаСервере() Тогда
        Перем ТекстСообщения;
        ТекстСообщения = "Документ не может быть записан, так как не выполнено одно из обязательных условий: ";
        ТекстСообщения = ТекстСообщения + Символы.ПС + "  - Не заполнен обязательный реквизит АБВ;";
        ТекстСообщения = ТекстСообщения + Символы.ПС + "  - Сумма документа превышает допустимый лимит.";
        
        ПоказатьПредупреждение(, ТекстСообщения, 10);
        Отказ = Истина; // Отменяем запись документа
    КонецЕсли;
КонецПроцедуры

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

В этом примере, если функция ПроверитьНекотороеУсловиеНаСервере() возвращает Ложь, мы показываем пользователю предупреждение и устанавливаем Отказ = Истина, что приводит к отмене записи документа.

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

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

Давайте разберем алгоритм действий:

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

Рассмотрим пример реализации этого подхода:


&НаКлиенте
Перем глПроверкаВыполнена; // Глобальная переменная формы, используем как флаг

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

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

&НаСервере
Функция ПроверитьНекотороеУсловиеНаСервере()
    // Эта функция может быть очень сложной и выполнять любые проверки.
    // Для примера, пусть она всегда возвращает Ложь.
    Возврат Ложь; 
КонецФункции

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

3. Открытие произвольной формы для сложной проверки

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

Алгоритм действий будет схож:

  1. В обработчике ПередЗаписью устанавливаем Отказ = Истина.
  2. Вызываем ОткрытьФорму(), передавая в нее объект ОписаниеОповещения, указывающий на экспортную процедуру-обработчик. В параметры формы можно передать необходимые данные документа.
  3. В открытой произвольной форме пользователь выполняет необходимые действия, вводит данные или подтверждает что-либо.
  4. При закрытии этой произвольной формы (например, по кнопке "ОК" или "Отмена") результат и, возможно, введенные данные передаются обратно в процедуру-обработчик оповещения на основной форме.
  5. В процедуре-обработчике оповещения, в зависимости от полученного результата, производится программная запись документа (аналогично примеру с ПоказатьВопрос, используя флаг для предотвращения зацикливания) или отмена записи.

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

Важные аспекты программной записи

При использовании программной записи документа важно понимать различия между методами:

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

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

ЭтотОбъект.Записать(РежимЗаписиДокумента.Проведение);

Это позволит программно записать и провести документ, если это необходимо.

Определение режима "Записать" или "Записать и закрыть"

Одной из сложностей при использовании ПоказатьВопрос в событии ПередЗаписью является то, что стандартными средствами практически невозможно определить, какую именно команду нажал пользователь: "Записать" или "Записать и закрыть". Это затрудняет корректное закрытие формы после программной записи в обработчике оповещения.

Рассмотрим возможные решения этой проблемы:

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

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

← На главную