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

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

При разработке в среде «1С:Предприятие 8» часто возникает задача изменить стандартное поведение системы при проведении документов. Например, при проведении документа Передача материалов в производство необходимо спросить у пользователя: «Нужно ли установить дату документа на текущую?».

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

Почему нельзя просто написать «Вопрос» в модуле объекта или при записи?

Перед тем как перейти к техническим решениям, проанализируем архитектурные ограничения платформы «1С:Предприятие 8»:

  1. Разделение контекстов: Модуль объекта выполняется строго на сервере (или в контексте привилегированного сеанса). Интерактивные методы (такие как предупреждения, вопросы, открытие форм) там физически недоступны. Любая попытка вызвать диалог из модуля объекта приведет к аварийному завершению работы системы.
  2. Проведение без участия пользователя: Документы могут проводиться не только интерактивно. Это может быть групповое перепроведение, проведение при обмене данными, проведение в фоновом задании или программно из внешней обработки. В этих сценариях никакого пользователя перед экраном нет, и система просто зависнет или выдаст ошибку, если в коде зашит интерактивный вопрос.
  3. Групповое выделение в списках: В форме списка пользователь может выделить 10, 50 или 100 документов и нажать кнопку «Провести». Если мы будем задавать вопрос на каждый документ, пользователь получит бесконечную череду всплывающих окон, что сделает работу с программой невыносимой.

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

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

Если мы просто добавим свою кнопку на командную панель, пользователь всё равно сможет провести документ через контекстное меню (правый клик мыши) или с помощью горячих клавиш (Ctrl + Shift + Enter). Чтобы полностью перекрыть лазейки, выполним следующие шаги по шагам:

  1. Добавим форму списка нужного документа в наше расширение.
  2. В дереве свойств формы найдем свойство «Состав команд».
  3. В открывшемся окне снимем флаг видимости и доступности со стандартной команды Провести (а также, при необходимости, Отмена проведения). Это полностью отключит стандартное проведение из контекстного меню и заблокирует платформенные горячие клавиши.
  4. Создадим новую команду формы в расширении, назовем ее, например, Расш_ПровестиСВопросом.
  5. Назначим нашей команде стандартное сочетание клавиш Ctrl + Shift + Enter (чтобы пользователь не заметил разницы).
  6. Выведем созданную команду на командную панель формы списка и в контекстное меню динамического списка.

Способ 2. Реализация асинхронного диалога и обход модальности

Начиная с платформы 8.3, стандартный режим работы конфигураций исключает использование модальных окон. Метод Вопрос() использовать нельзя. Нам необходимо применить асинхронные вызовы.

Рассмотрим пример кода для нашей команды Расш_ПровестиСВопросом на клиенте с использованием конструкции Ждать и асинхронного метода ВопросАсинх() (доступно для платформы 8.3.18 и выше):


&НаКлиенте
Асинх Процедура Расш_ПровестиСВопросом(Команда)
    
    ВыделенныеСтроки = Элементы.Список.ВыделенныеСтроки;
    Если ВыделенныеСтроки.Количество() = 0 Тогда
        ПоказатьПредупреждение(, "Не выбраны документы для проведения!");
        Возврат;
    КонецЕсли;
    
    // Если выделен один документ, задаем простой вопрос
    Если ВыделенныеСтроки.Количество() = 1 Тогда
        
        ТекстВопроса = "Нужно ли установить дату документа на текущую перед проведением?";
        РежимДиалога = РежимДиалогаВыбораИВхода.ДаНетОтмена;
        Ответ = Ждать ВопросАсинх(ТекстВопроса, РежимДиалога, 0, КодВозвратаДиалога.Да, "Внимание");
        
        Если Ответ = КодВозвратаДиалога.Отмена Тогда
            Возврат;
        КонецЕсли;
        
        УстановитьДату = (Ответ = КодВозвратаДиалога.Да);
        ПровестиДокументНаСервере(ВыделенныеСтроки[0], УстановитьДату);
        
    Иначе
        // Обработка группового выделения (массовое проведение)
        ПровестиПакетДокументов(ВыделенныеСтроки);
    КонецЕсли;
    
КонецПроцедуры

Способ 3. Решение проблемы массового проведения

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

Разберем, как спроектировать процедуру ПровестиПакетДокументов:


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

Способ 4. Архитектурно правильное проведение на сервере через ДополнительныеСвойства

Теперь выясним, как правильно провести документы на сервере, изменив их дату в зависимости от выбора пользователя. Самая частая ошибка новичков — попытка менять дату непосредственно в коде формы, а затем вызывать метод ПолучитьОбъект().Записать(РежимЗаписиДокумента.Проведение). Это порождает избыточные транзакции и не гарантирует корректность блокировок.

Правильный подход заключается в передаче признака интерактивного выбора («нужно ли менять дату») в модуль объекта через коллекцию ДополнительныеСвойства. Это гарантирует, что бизнес-логика проведения останется внутри объекта метаданных, а форма списка лишь передаст нужный параметр.

Рассмотрим реализацию серверной части в модуле формы списка:


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

Теперь перейдем в модуль объекта нашего документа (через расширение) и добавим обработчик события ПередЗаписью:


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

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

Альтернативный вариант: Использование возможностей БСП

Если ваша конфигурация разработана на основе Библиотеки Стандартных Подсистем (БСП) (например, ERP, Комплексная автоматизация или Управление торговлей), командные панели списков формируются динамически с использованием подсистемы «Подключаемые команды».

В этом случае ручное удаление кнопок из формы списка в расширении может привести к конфликтам при последующих обновлениях конфигурации. Для таких систем рекомендуется внедрять собственные команды через переопределяемые модули БСП (например, через модуль УправлениеПечатьюПереопределяемый или механизмы подключаемых команд БСП). Это позволит встроить новую кнопку проведения бесшовно в стандартную группу кнопок или в меню «Еще».

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

Подведем итоги нашего разбора:

← На главную