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