При переводе старых конфигураций (таких как 1С:Управление производственным предприятием или специализированных решений на её базе, например, УПП:Мясокомбинат) на управляемые формы разработчики часто сталкиваются с проблемой адаптации сервисных механизмов. Один из самых популярных и часто используемых механизмов — это обработка «Цены и валюта» (вызываемая по кнопке «Цены и валюта» в табличной части «Товары» документов поступления и реализации).
В обычных формах этот механизм работал напрямую с переменной ЭтотОбъект, которая представляла собой полноценный ДокументОбъект. В управляемом же приложении на клиенте мы имеем дело только с типом ДанныеФормыСтруктура (переменная Объект). Попытка передать этот тип в старые экспортные процедуры общих модулей приводит к ошибкам преобразования метаданных. Разберем по шагам, как решить эту задачу правильно и обеспечить совместимость со стандартной бизнес-логикой конфигурации.
Проанализируем типовой код вызова формы цен в обычной форме. Он выглядит следующим образом:
СтруктураЗначений = Ценообразование.ОткрытьФормуЦеныИВалюта(ЭтотОбъект, СтруктураРеквизитовДокумента, ...);
Если мы попытаемся в управляемой форме передать вместо ЭтотОбъект реквизит Объект или ЭтаФорма, система выдаст ошибку. Дело в том, что функция Ценообразование.ОткрытьФормуЦеныИВалюта внутри себя ожидает получить именно прикладной объект типа ДокументОбъект. Ей требуются метаданные документа, его табличные части и реквизиты для выполнения расчетов и заполнения реквизитов формы.
Попытка получить ДокументОбъект на сервере через функцию вида:
&НаСервере
Функция ДокументОбъектВЗначениеНаСервере()
Возврат РеквизитФормыВЗначение("Объект");
КонецФункции
и передать результат обратно на клиент также обречена на провал в «чистом» управляемом приложении (в Тонком клиенте). Объект типа ДокументОбъект является мутабельным (изменяемым) значением. Такие типы физически не могут существовать на клиенте и не могут быть переданы в качестве возвращаемого значения из серверного вызова на клиент. Платформа мгновенно сгенерирует ошибку передачи данных между клиентом и сервером.
В Толстом клиенте (в режиме управляемого приложения) такой код может «случайно» заработать, так как там грань между клиентом и сервером размыта. Но для полноценной и надежной работы в веб-клиенте или тонком клиенте нам потребуется полностью переписать логику взаимодействия.
Если ваше приложение гарантированно эксплуатируется только в режиме Толстого клиента, можно использовать простую схему синхронизации данных. Мы получаем объект на сервере, передаем его во вспомогательную процедуру и сразу же записываем изменения обратно в реквизит формы.
Для этого в модуле управляемой формы документа реализуем следующую логику:
&НаСервере
Процедура ДокументОбъектВРеквизитНаСервере(ДокументОбъект)
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
В этом случае на клиенте мы получаем объект во временную переменную, открываем форму, получаем результат изменений и сразу же отправляем объект обратно на сервер для обновления данных формы. Однако помните: этот способ не будет работать в Тонком клиенте!
Рассмотрим правильный, методически верный подход. Чтобы обойти ограничение на передачу мутабельных значений, нам необходимо разделить процесс на три логических этапа:
РеквизитФормыВЗначение("Объект") в реальный ДокументОбъект, применяем к нему стандартные процедуры пересчета цен и НДС из общих модулей и возвращаем измененный объект в реквизит формы с помощью метода ЗначениеВРеквизитФормы.Посмотрим на пример реализации этого алгоритма в модуле управляемой формы документа.
&НаКлиенте
Процедура ДействиеЦеныВалюта(Команда)
// Шаг 1. Получаем подготовленные параметры и структуры с сервера за один вызов
Контекст = КонтекстИзмененияЦен();
// Шаг 2. Вызов формы "Цены и валюта"
// Передаем форму-владелец и параметры. В классической УПП это общая форма.
СтруктураЗначений = Ценообразование.ОткрытьФормуЦеныИВалюта(
ЭтотОбъект,
Контекст.СтруктураРеквизитовДокумента,
Контекст.ВалютаРегламентированногоУчета,
Контекст.СтруктураЗапрещенных,
Контекст.ИмяТабличнойЧасти
);
// Шаг 3. Если пользователь применил изменения, обрабатываем их на сервере
Если СтруктураЗначений <> Неопределено Тогда
ИзменитьЦеныИВалютуНаСервере(Контекст, СтруктураЗначений);
КонецЕсли;
УстановитьВидимость();
КонецПроцедуры
Теперь детально разберем серверную функцию КонтекстИзмененияЦен. Она собирает все необходимые данные для корректной работы алгоритмов ценообразования:
&НаСервере
Функция КонтекстИзмененияЦен()
СпособЗаполненияЦен = Перечисления.СпособыЗаполненияЦен.ПоЦенамНоменклатурыКонтрагентов;
// Получаем прикладной объект из данных формы
ОбъектФормы = РеквизитФормыВЗначение("Объект");
// Заполняем структуру реквизитов для анализа ценообразования
СтруктураРеквизитовДокумента = Ценообразование.ПолучитьСтруктуруРеквизитовДокументаДляЦенообразования(ОбъектФормы);
СтруктураРеквизитовДокумента.Вставить("НДСВключенВСтоимость");
// Определяем доступность полей в зависимости от вида операции и договора
СтруктураЗапрещенных = Неопределено;
Если Объект.ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОперацийПоступлениеТоваровУслуг.ВПереработку")
ИЛИ Объект.ДоговорКонтрагента.ВидДоговора = ПредопределенноеЗначение("Перечисление.ВидыДоговоровКонтрагентов.СКомитентом") Тогда
Если Объект.ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОперацийПоступлениеТоваровУслуг.ПокупкаКомиссия") Тогда
СтруктураЗапрещенных = Новый Структура("НДСВключенВСтоимость");
Иначе
СтруктураЗапрещенных = Новый Структура("НДСВключенВСтоимость, УчитыватьНДС, СуммаВключаетНДС, РегистрироватьЦеныПоставщика");
КонецЕсли;
КонецЕсли;
// Получаем валюту регламентированного учета
ВалютаРегламентированногоУчета = Константы.ВалютаРегламентированногоУчета.Получить();
// Формируем легкий пакет параметров для клиента
Контекст = Новый Структура;
Контекст.Вставить("СпособЗаполненияЦен", СпособЗаполненияЦен);
Контекст.Вставить("СтруктураРеквизитовДокумента", СтруктураРеквизитовДокумента);
Conтекст.Вставить("СтруктураЗапрещенных", СтруктураЗапрещенных);
Контекст.Вставить("ВалютаРегламентированногоУчета", ВалютаРегламентированногоУчета);
Контекст.Вставить("ИмяТабличнойЧасти", "Товары");
Возврат Контекст;
КонецФункции
После того как пользователь сделал выбор в форме «Цены и валюта» и закрыл её, управление возвращается в клиентский метод, который вызывает серверную процедуру для применения изменений. Проанализируем её код:
&НаСервере
Процедура ИзменитьЦеныИВалютуНаСервере(Контекст, СтруктураЗначений)
// 1. Превращаем данные формы обратно в полноценный ДокументОбъект на сервере
ОбъектФормы = РеквизитФормыВЗначение("Объект");
// 2. Вызываем стандартный общий модуль УПП для пересчета цен и валют
ЗаполнениеДокументов.ИзменитьЦеныВалюту(
ОбъектФормы,
Контекст.СпособЗаполненияЦен,
Контекст.СтруктураРеквизитовДокумента,
Контекст.СтруктураЗапрещенных,
Контекст.ИмяТабличнойЧасти,
Контекст.ВалютаРегламентированногоУчета,
,
СтруктураЗначений
);
// 3. Загружаем измененный объект назад в реквизит формы "Объект"
ЗначениеВРеквизитФормы(ОбъектФормы, "Объект");
КонецПроцедуры
Если вы работаете в современной версии платформы «1С:Предприятие 8.3» с включенным режимом конфигурации «Не использовать модальные вызовы», синхронный вызов формы через метод ОткрытьФормуЦеныИВалюта приведет к ошибке. В этом случае обычную форму «Цены и валюта» необходимо перерисовать как управляемую и открывать её асинхронно.
Для этого мы используем объект ОписаниеОповещения. Код вызова формы трансформируется следующим образом:
&НаКлиЕнте
Процедура ДействиеЦеныВалютаАсинхронно(Команда)
Контекст = КонтекстИзмененияЦен();
// Создаем описание оповещения, указывая процедуру обратного вызова
Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияФормыЦены", ЭтотОбъект, Контекст);
// Открываем управляемую форму параметров цен асинхронно
ОткрытьФорму("ОбщаяФорма.ЦеныИВалютаУправляемая", Контекст, ЭтотОбъект, , , , Оповещение);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗакрытияФормыЦены(РезультатЗакрытия, ДополнительныеПараметры) Экспорт
// Если пользователь закрыл форму нажатием кнопки "ОК" и передал структуру новых значений
Если ТипЗнч(РезультатЗакрытия) = Тип("Структура") Тогда
ИзменитьЦеныИВалютуНаСервере(ДополнительныеПараметры, РезультатЗакрытия);
УстановитьВидимость();
КонецЕсли;
КонецПроцедуры
Использование связки методов РеквизитФормыВЗначение и ЗначениеВРеквизитФормы совместно с асинхронным открытием окон — это единственный архитектурно верный способ адаптации старых сложных алгоритмов пересчета под стандарты современной платформы 1С.