При разработке в среде «1С:Предприятие 8» часто возникает задача изменить стандартное поведение системы при проведении документов. Например, при проведении документа Передача материалов в производство необходимо спросить у пользователя: «Нужно ли установить дату документа на текущую?».
Если с формой документа всё относительно просто, то при проведении из формы списка разработчики сталкиваются с серьезными трудностями. Попытка написать код в модуле объекта обречена на провал, так как серверный контекст модуля объекта не позволяет взаимодействовать с пользователем и вызывать диалоговые окна. Попытка перехватить событие в расширении без изменения стандартных команд часто приводит к неполному решению проблемы. В этой статье мы подробно разберем, как правильно реализовать подобную задачу, избежав типичных ошибок проектирования.
Перед тем как перейти к техническим решениям, проанализируем архитектурные ограничения платформы «1С:Предприятие 8»:
Самый очевидный способ реализовать интерактивное проведение из формы списка — подменить стандартную системную команду проведения своей собственной. Рассмотрим, как сделать это правильно в расширении конфигурации.
Если мы просто добавим свою кнопку на командную панель, пользователь всё равно сможет провести документ через контекстное меню (правый клик мыши) или с помощью горячих клавиш (Ctrl + Shift + Enter). Чтобы полностью перекрыть лазейки, выполним следующие шаги по шагам:
Провести (а также, при необходимости, Отмена проведения). Это полностью отключит стандартное проведение из контекстного меню и заблокирует платформенные горячие клавиши.Расш_ПровестиСВопросом.Ctrl + Shift + Enter (чтобы пользователь не заметил разницы).Начиная с платформы 8.3, стандартный режим работы конфигураций исключает использование модальных окон. Метод Вопрос() использовать нельзя. Нам необходимо применить асинхронные вызовы.
Рассмотрим пример кода для нашей команды Расш_ПровестиСВопросом на клиенте с использованием конструкции Ждать и асинхронного метода ВопросАсинх() (доступно для платформы 8.3.18 и выше):
&НаКлиенте
Асинх Процедура Расш_ПровестиСВопросом(Команда)
ВыделенныеСтроки = Элементы.Список.ВыделенныеСтроки;
Если ВыделенныеСтроки.Количество() = 0 Тогда
ПоказатьПредупреждение(, "Не выбраны документы для проведения!");
Возврат;
КонецЕсли;
// Если выделен один документ, задаем простой вопрос
Если ВыделенныеСтроки.Количество() = 1 Тогда
ТекстВопроса = "Нужно ли установить дату документа на текущую перед проведением?";
РежимДиалога = РежимДиалогаВыбораИВхода.ДаНетОтмена;
Ответ = Ждать ВопросАсинх(ТекстВопроса, РежимДиалога, 0, КодВозвратаДиалога.Да, "Внимание");
Если Ответ = КодВозвратаДиалога.Отмена Тогда
Возврат;
КонецЕсли;
УстановитьДату = (Ответ = КодВозвратаДиалога.Да);
ПровестиДокументНаСервере(ВыделенныеСтроки[0], УстановитьДату);
Иначе
// Обработка группового выделения (массовое проведение)
ПровестиПакетДокументов(ВыделенныеСтроки);
КонецЕсли;
КонецПроцедуры
Как мы уже выяснили, если пользователь выделил несколько документов в списке, нельзя задавать вопрос по каждому из них. Правильным решением будет задать один общий вопрос для всей группы выделенных документов.
Разберем, как спроектировать процедуру ПровестиПакетДокументов:
&НаКлиенте
Асинх Процедура ПровестиПакетДокументов(МассивДокументов)
КоличествоДокументов = МассивДокументов.Количество();
ШаблонВопроса = "Выбрано %1 документов. Установить для ВСЕХ документов текущую дату при проведении?";
ТекстВопроса = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(ШаблонВопроса, КоличествоДокументов);
Кнопки = Новый СписокЗначений;
Кнопки.Добавить("ДаДляВсех", "Да (установить текущую дату)");
Кнопки.Добавить("НетОставить", "Нет (провести с исходными датами)");
Кнопки.Добавить("Отмена", "Отменить операцию");
Ответ = Ждать ВопросАсинх(ТекстВопроса, Кнопки, 0, "ДаДляВсех", "Групповое проведение");
Если Ответ = "Отмена" Или Ответ = Неопределено Тогда
Возврат;
КонецЕсли;
УстановитьДату = (Ответ = "ДаДляВсех");
// Передаем массив документов на сервер для пакетной обработки
РезультатПроведения = ПровестиПакетНаСервере(МассивДокументов, УстановитьДату);
// Сообщаем пользователю о результатах
ПоказатьОповещениеПользователя("Проведение завершено", ,
СтрШаблон("Успешно проведено документов: %1", РезультатПроведения));
Элементы.Список.Обновить();
КонецПроцедуры
Теперь выясним, как правильно провести документы на сервере, изменив их дату в зависимости от выбора пользователя. Самая частая ошибка новичков — попытка менять дату непосредственно в коде формы, а затем вызывать метод ПолучитьОбъект().Записать(РежимЗаписиДокумента.Проведение). Это порождает избыточные транзакции и не гарантирует корректность блокировок.
Правильный подход заключается в передаче признака интерактивного выбора («нужно ли менять дату») в модуль объекта через коллекцию ДополнительныеСвойства. Это гарантирует, что бизнес-логика проведения останется внутри объекта метаданных, а форма списка лишь передаст нужный параметр.
Рассмотрим реализацию серверной части в модуле формы списка:
&НаСервереБезКонтекста
Функция ПровестиПакетНаСервере(МассивДокументов, УстановитьДату)
УспешноПроведено = 0;
Для Каждого СсылкаНаДокумент Из МассивДокументов Цикл
Попытка
// Блокируем и получаем объект документа
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
// Записываем наш параметр в ДополнительныеСвойства объекта
ДокументОбъект.ДополнительныеСвойства.Вставить("Расш_УстановитьТекущуюДатуПриПроведении", УстановитьДату);
// Запускаем проведение
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
УспешноПроведено = УспешноПроведено + 1;
Исключение
// Записываем ошибку в журнал регистрации, чтобы не прерывать весь пакет
ЗаписьЖурналаРегистрации("Проведение из списка",
УровеньЖурналаРегистрации.Ошибка,
СсылкаНаДокумент.Метаданные(),
СсылкаНаДокумент,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЦикла;
Возврат УспешноПроведено;
КонецФункции
Теперь перейдем в модуль объекта нашего документа (через расширение) и добавим обработчик события ПередЗаписью:
&После("ПередЗаписью")
Процедура Расш_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, что запись инициирована в режиме проведения
Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
// Проверяем наличие нашего параметра в ДополнительныеСвойства
Если ЭтотОбъект.ДополнительныеСвойства.Свойство("Расш_УстановитьТекущуюДатуПриПроведении") Тогда
НужноЛиМенятьДату = ЭтотОбъект.ДополнительныеСвойства.Расш_УстановитьТекущуюДатуПриПроведении;
// Если пользователь согласился, и дата отличается от текущей - меняем ее
Если НужноЛиМенятьДату И НачалоДня(ЭтотОбъект.Дата) <> НачалоДня(ТекущаяДатаСеанса()) Тогда
ЭтотОбъект.Дата = ТекущаяДатаСеанса();
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Этот подход невероятно надежен. Если документ проводится программно сторонним обменом или регламентным заданием, коллекция ДополнительныеСвойства не будет содержать нашего параметра, и документ проведется стандартно со своей исходной датой, без каких-либо зависаний и ошибок интерфейса.
Если ваша конфигурация разработана на основе Библиотеки Стандартных Подсистем (БСП) (например, ERP, Комплексная автоматизация или Управление торговлей), командные панели списков формируются динамически с использованием подсистемы «Подключаемые команды».
В этом случае ручное удаление кнопок из формы списка в расширении может привести к конфликтам при последующих обновлениях конфигурации. Для таких систем рекомендуется внедрять собственные команды через переопределяемые модули БСП (например, через модуль УправлениеПечатьюПереопределяемый или механизмы подключаемых команд БСП). Это позволит встроить новую кнопку проведения бесшовно в стандартную группу кнопок или в меню «Еще».
Подведем итоги нашего разбора:
ДополнительныеСвойства объекта документа — это золотой стандарт разработки в 1С.