В процессе разработки в среде 1С:Предприятие 8.3 на управляемых формах начинающие специалисты часто сталкиваются с трудностями при обращении к данным формы. Типичный сценарий: пользователь выбирает на форме документа определенный параметр (например, тип цен), и на основании этого выбора программа должна мгновенно обновить цены в табличной части документа. Разберем подробно, как правильно получать значения из реквизитов формы, почему популярные решения из интернета могут содержать грубые методологические ошибки и как построить эффективную архитектуру пересчета цен «на лету».
В рассматриваемой теме автор столкнулся с необходимостью получить значение реквизита ТипЦен (имеющего тип ссылки на справочник СправочникСсылка.ТипыЦенНоменклатуры), который пользователь выбирает вручную на форме документа. Автор самостоятельно пришел к следующему решению:
ТипЦен = ЭтотОбъект.Объект.ТипЦен;
Давайте подробно проанализируем эту строку кода и выясним, почему профессиональный разработчик 1С не должен использовать подобную конструкцию в управляемом приложении.
Во-первых, разберем контекст выполнения кода в модуле управляемой формы. В отличие от обычных форм (где ключевое слово ЭтотОбъект указывало непосредственно на прикладной объект базы данных — документ, справочник или обработку), в управляемом приложении модуль формы и модуль объекта строго разделены. В модуле управляемой формы ключевое слово ЭтотОбъект (или ЭтаФорма) указывает на сам объект формы, имеющий тип УправляемаяФорма.
Во-вторых, на управляемой форме всегда присутствует основной реквизит (обычно с именем Объект), который имеет тип ДанныеФормыСтруктура (для документов и справочников) или ДанныеФормыКоллекция (для списков). Этот реквизит напрямую связывает интерфейс формы с данными прикладного объекта. Поскольку реквизит Объект является базовым свойством самой управляемой формы, обращаться к нему через конструкцию ЭтотОбъект.Объект избыточно. Платформа прекрасно понимает прямое обращение.
Таким образом, правильный и лаконичный вариант обращения выглядит так:
ТипЦен = Объект.ТипЦен;
Использование избыточного префикса ЭтотОбъект не только усложняет чтение кода, но и считается плохим тоном разработки (так называемым «код-смеллом»), унаследованным из старых версий платформы 1С 8.1 и 8.2.
В-третьих, если бы реквизит «Тип цен» был создан не в самом документе, а исключительно как независимый реквизит управляемой формы (для временного хранения данных в сессии пользователя), то обращение к нему происходило бы напрямую по его имени:
ТипЦен = ТипЦен; // Или явно: ТипЦен = ЭтаФорма.ТипЦен;
Просто получить значение реквизита в переменную внутри модуля — это лишь малая часть задачи. Чтобы при изменении типа цен в документе действительно пересчитались цены во всей табличной части, нам необходимо реализовать полноценный алгоритм с учетом клиент-серверной архитектуры 1С. Выясним, почему это важно.
Интерфейс управляемой формы функционирует на стороне клиента (в тонком или веб-клиенте). Однако сами данные о ценах номенклатуры хранятся в базе данных на сервере (обычно в периодическом регистре сведений). На клиенте мы не имеем прямого доступа к базе данных и не можем выполнять запросы. Следовательно, нам необходимо организовать передачу управления с клиента на сервер.
Проанализируем по шагам, как построить правильную цепочку вызовов:
ПриИзменении, привязанное к этому элементу формы.Товары, после чего пересчитываются суммы, НДС и итоги документа.Перейдем к практическому кодированию. Сначала настроим элемент формы. Для поля ввода, связанного с реквизитом Объект.ТипЦен, в палитре свойств создадим обработчик события ПриИзменении. Платформа сгенерирует для нас шаблон процедуры на клиенте.
Рассмотрим код клиентской части:
&НаКлиенте
Процедура ТипЦенПриИзменении(Элемент)
// Перед отправкой запроса на сервер проверим, заполнен ли тип цен
Если Не ЗначениеЗаполнено(Объект.ТипЦен) Тогда
Предупреждение("Пожалуйста, выберите тип цен для корректного расчета!");
Возврат;
КонецЕсли;
// Вызываем серверную процедуру для выполнения расчетов
ПересчитатьЦеныНаСервере();
КонецПроцедуры
Теперь реализуем серверную процедуру ПересчитатьЦеныНаСервере. Обратите внимание, что мы используем директиву компиляции &НаСервере, а не &НаСервереБезКонтекста, так как нам необходим прямой доступ к реквизиту формы Объект и его табличной части Товары.
&НаСервере
Процедура ПересчитатьЦеныНаСервере()
// Получаем дату документа для определения актуальности цен
ДатаСреза = ?(ЗначениеЗаполнено(Объект.Дата), Объект.Дата, ТекущаяДата());
// Обходим строки табличной части "Товары"
Для Каждого СтрокаТовары Из Объект.Товары Цикл
// Получаем актуальную цену из базы данных
НоваяЦена = ПолучитьЦенуНоменклатуры(СтрокаТовары.Номенклатура, Объект.ТипЦен, ДатаСреза);
// Обновляем показатели в строке табличной части
СтрокаТовары.Цена = НоваяЦена;
СтрокаТовары.Сумма = СтрокаТовары.Количество * НоваяЦена;
// Если в документе ведется учет НДС, можно сразу вызвать его пересчет
// СтрокаТовары.СуммаНДС = ...
КонецЦикла;
КонецПроцедуры
Для полноты картины нам необходима вспомогательная функция ПолучитьЦенуНоменклатуры, которая будет обращаться к регистру сведений через запрос. Запросы на сервере работают максимально быстро и эффективно:
&НаСервереБезКонтекста
Функция ПолучитьЦенуНоменклатуры(НоменклатураСсылка, ТипЦенСсылка, ДатаСреза)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, Номенклатура = &Номенклатура И ТипЦен = &ТипЦен) КАК ЦеныНоменклатурыСрезПоследних";
Запрос.УстановитьПараметр("Период", ДатаСреза);
Запрос.УстановитьПараметр("Номенклатура", НоменклатураСсылка);
Запрос.УстановитьПараметр("ТипЦен", ТипЦенСсылка);
УстановитьПривилегированныйРежим(Истина); // Опционально для обхода ограничений RLS
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Цена;
КонецЕсли;
Возврат 0; // Если цена не найдена, возвращаем ноль
КонецФункции
Если вы ведете разработку в современной типовой конфигурации (такой как 1С:Управление торговлей, 1С:ERP или 1С:Комплексная автоматизация), вам крайне не рекомендуется писать собственные запросы к регистрам цен вручную. В типовых конфигурациях заложены мощные и оптимизированные механизмы ценообразования, учитывающие множество нюансов: валюты, скидки, налоги (НДС в сумме или сверху), соглашения с клиентами и индивидуальные виды цен.
Проанализируем, как выглядит вызов пересчета цен с использованием общего модуля типового решения:
&НаСервере
Процедура ПересчитатьЦеныТиповымМеханизмом()
// Структура параметров для расчета цен в БСП
ПараметрыРасчета = ЦенообразованиеКлиентСервер.ПараметрыЗаполненияЦен();
ПараметрыРасчета.Дата = Объект.Дата;
ПараметрыРасчета.ТипЦен = Объект.ТипЦен;
ПараметрыРасчета.Валюта = Объект.ВалютаДокумента;
// Вызов общего серверного модуля для заполнения табличной части
Ценообразование.ЗаполнитьЦеныВТабличнойЧасти(Объект.Товары, ПараметрыРасчета);
// Пересчет сумм по всей табличной части
ОбработкаТабличныхЧастей.ПересчитатьСуммыТабличнойЧасти(Объект.Товары, "Товары", Объект);
КонецПроцедуры
Использование готовых библиотечных модулей гарантирует соответствие вашего кода стандартам разработки фирмы «1С», снижает вероятность возникновения ошибок при обновлении конфигурации и существенно экономит время разработки.
Таким образом, мы выяснили разницу между клиентским и серверным контекстом при работе с реквизитами управляемой формы, разобрали синтаксически верные способы обращения к реквизиту Объект.ТипЦен и научились строить надежную архитектуру автоматического расчета цен в документах.