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