При разработке конфигураций, особенно на начальных этапах обучения, часто возникает задача автоматического заполнения цен и расчета сумм в документах. Например, при выборе инструмента в документе «Аренда» необходимо подтянуть его стоимость (Тариф) из справочника и умножить на количество дней. Однако попытка сделать это «в лоб» часто приводит к ошибке Поле объекта не обнаружено. В этой статье мы подробно разберем, почему возникает эта ошибка, как правильно получать данные с сервера и как грамотно спроектировать структуру документа, чтобы избежать проблем в будущем.
Рассмотрим ситуацию, с которой столкнулся автор вопроса. В табличной части документа есть реквизит Инструмент (ссылка на справочник) и нужно получить его реквизит Тариф. Код, написанный в модуле формы, выглядит так:
// Ошибочный код на клиенте
Строка.Сумма = Строка.КоличествоДней * Строка.Инструмент.Тариф;
При выполнении этого кода система выдает ошибку: «Поле объекта не обнаружено (Тариф)».
Давайте выясним причину. В архитектуре 1С:Предприятие 8 (управляемое приложение) работа разделена на Клиент и Сервер:
Строка.Инструмент) содержит только уникальный идентификатор (GUID) и текстовое представление. Сами данные объекта (в том числе реквизит Тариф) на клиент не передаются ради экономии трафика и ускорения работы.Попытка обратиться к Строка.Инструмент.Тариф через точку на клиенте вызывает ошибку, так как клиентская машина просто не знает, что внутри этой ссылки есть поле Тариф. Это ограничение платформы, которое заставляет нас писать более оптимизированный код.
Чтобы получить значение тарифа, нам необходимо отправить запрос на сервер. Самый оптимальный способ сделать это — использовать бесконтекстный серверный метод.
Рассмотрим алгоритм действий:
&НаСервереБезКонтекста. Она будет принимать ссылку на инструмент и возвращать число (тариф).Пример реализации функции получения цены:
&НаСервереБезКонтекста
Функция ПолучитьТарифИнструмента(СсылкаНаИнструмент)
// Проверяем, что ссылка не пустая
Если СсылкаНаИнструмент.Пустая() Тогда
Возврат 0;
КонецЕсли;
// Получаем значение реквизита "Тариф" напрямую от ссылки
// В серверном контексте это доступно
Возврат СсылкаНаИнструмент.Тариф;
// ПРИМЕЧАНИЕ: Если бы тариф хранился в Регистре Сведений (что правильнее),
// здесь был бы запрос к регистру сведений.
КонецФункции
Теперь, когда мы умеем получать тариф, нам нужно организовать автоматический пересчет. Обратите внимание, что сумма зависит от трех параметров: выбранного инструмента, даты выдачи и даты возврата. Нам потребуется общая процедура расчета.
Для начала, добавим в табличную часть документа реквизит Цена (или Тариф). Это важное архитектурное требование: цена должна храниться в документе, чтобы при изменении цены в справочнике в будущем, старые документы не «поплыли».
Рассмотрим код обработчика события ПриИзменении для поля Инструмент в табличной части:
&НаКлиенте
Процедура ИнструментыИнструментПриИзменении(Элемент)
// Получаем текущую строку табличной части
ТекущиеДанные = Элементы.Инструменты.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// 1. Получаем тариф с сервера
ТекущиеДанные.Тариф = ПолучитьТарифИнструмента(ТекущиеДанные.Инструмент);
// 2. Рассчитываем сумму
РассчитатьСуммуСтроки(ТекущиеДанные);
КонецПроцедуры
Также нам нужна процедура расчета, которая будет вызываться при изменении дат:
&НаКлиенте
Процедура РассчитатьСуммуСтроки(СтрокаТЧ)
// В 1С разница дат - это количество секунд.
// В сутках 86400 секунд (60 * 60 * 24).
Если ЗначениеЗаполнено(СтрокаТЧ.ДатаВыдачи)
И ЗначениеЗаполнено(СтрокаТЧ.ДатаВозврата)
И СтрокаТЧ.ДатаВозврата > СтрокаТЧ.ДатаВыдачи Тогда
// Используем функцию НачалоДня (если время не важно) или считаем как есть.
// Но функция НачалоДня() доступна только на Сервере (в некоторых версиях)
// или требует внимательности на Клиенте.
// Для простоты считаем разницу в секундах и делим на 86400.
РазницаСекунд = СтрокаТЧ.ДатаВозврата - СтрокаТЧ.ДатаВыдачи;
КоличествоДней = РазницаСекунд / 86400;
// Округлим до целых дней в большую сторону, если аренда не почасовая
// Для примера оставим как есть или можно использовать Цел()
СтрокаТЧ.КоличествоДней = КоличествоДней;
СтрокаТЧ.Сумма = СтрокаТЧ.КоличествоДней * СтрокаТЧ.Тариф;
Иначе
СтрокаТЧ.Сумма = 0;
КонецЕсли;
КонецПроцедуры
Обратите внимание на вычисление дней. Тип Дата в 1С содержит время. Если вы просто вычтете одну дату из другой, получите количество секунд. Обязательно делите результат на 86400 для получения дней.
В учебных задачах допустимо хранить тариф в реквизите справочника Инструменты. Однако, если мы посмотрим на реальную автоматизацию бизнеса, этот подход имеет существенный недостаток.
Проблема изменения цен:
Представьте, что сегодня дрель стоит 500 рублей в сутки. Вы оформили документы. Через месяц цена поднялась до 600 рублей. Если вы просто измените реквизит в справочнике, то при перепроведении старых документов (прошлого месяца) программа возьмет новую цену (600), и вся отчетность за прошлый период станет неверной.
Правильное решение:
РегистрСведений (периодический) для хранения цен. Это позволит системе знать, что 1 января цена была 500, а 1 февраля стала 600.Тариф (или Цена) прямо в табличную часть документа Аренда.Рассмотрим логику работы правильной системы:
Инструмент.РегистрСведений.Цены и получает цену, актуальную на дату документа (используя метод ПолучитьПоследнее).Цена в строке документа.Сумма = Цена * КоличествоДней.Такой подход гарантирует, что даже если цены в будущем изменятся, в уже подписанном договоре аренды (документе) останется та сумма, о которой договорились с клиентом.
Для решения вашей задачи выполните следующие шаги:
Цена или Тариф).&НаСервереБезКонтекста, которая принимает ссылку и возвращает цену.ПриИзменении инструмента вызывайте эту функцию и заполняйте реквизит цены в строке.