В процессе разработки конфигураций на платформе 1С:Предприятие 8 часто возникает классическая задача: реализовать автоматический расчет одного поля на основании данных других полей. Например, когда пользователь вводит стоимость аренды и стоимость страховки, система должна моментально сложить эти показатели и вывести итоговую сумму еще до того, как элемент будет записан в базу данных.
В этой статье мы подробно разберем, как правильно реализовать эту логику, почему типичные попытки написать запрос к базе данных в этот момент приводят к ошибкам, и как сделать код чистым, безопасным и оптимизированным в соответствии со стандартами разработки 1С.
Часто начинающие разработчики пытаются решить задачу "в лоб" через создание команды, кнопки или написание запроса. Посмотрим на пример кода, который часто вызывает сбои:
|Выбрать
|ЦенаА(Авто.ЦенаАренды) КАК ЦенаА
|ЦенаС(Авто.ЦенаСтраховки) КАК ЦенаС
|ИЗ
|Справочник.Автомобили КАК Авто
Выясним причину, почему этот подход ошибочен:
Решение этой задачи должно происходить исключительно на клиенте "на лету" с помощью обработки событий элементов формы.
Если ваши реквизиты ЦенаАренды, ЦенаСтраховки и Сумма находятся непосредственно в самом объекте (в шапке справочника или документа), то расчет выполняется очень просто.
Для реализации этого механизма нам понадобятся обработчики событий ПриИзменении. Разберем по шагам, как это настроить:
ЦенаАренды, откройте его палитру свойств (нажатием правой кнопки мыши или клавиши F4).ПриИзменении и нажмите на лупу для создания клиентской процедуры.ЦенаСтраховки.В модуле формы будут созданы две процедуры. Чтобы система автоматически рассчитывала результат, обратимся к реквизитам через ключевое слово Объект:
&НаКлиенте
Процедура ЦенаАрендыПриИзменении(Элемент)
Объект.Сумма = Объект.ЦенаАренды + Объект.ЦенаСтраховки;
КонецПроцедуры
&НаКлиенте
Процедура ЦенаСтраховкиПриИзменении(Элемент)
Объект.Сумма = Объект.ЦенаАренды + Объект.ЦенаСтраховки;
КонецПроцедуры
Теперь, как только пользователь изменит значение в любом из этих полей и переведет фокус, сумма мгновенно пересчитается.
Дублирование одного и того же алгоритма в нескольких местах модуля — это плохая практика программирования. Если завтра формула расчета усложнится (например, появится скидка или налог), вам придется вносить изменения в каждый обработчик.
Проанализируем ситуацию и оптимизируем код. Мы можем выделить расчет в отдельную вспомогательную процедуру:
&НаКлиенте
Процедура РассчитатьИтоговуюСумму()
Объект.Сумма = Объект.ЦенаАренды + Объект.ЦенаСтраховки;
КонецПроцедуры
&НаКлиенте
Процедура ЦенаАрендыПриИзменении(Элемент)
РассчитатьИтоговуюСумму();
КонецПроцедуры
&НаКлиенте
Процедура ЦенаСтраховкиПриИзменении(Элемент)
РассчитатьИтоговуюСумму();
КонецПроцедуры
Но можно пойти еще дальше и назначить **один и тот же** обработчик для обоих элементов управления. Для этого в палитре свойств поля ЦенаСтраховки в строке события ПриИзменении не создавайте новую процедуру, а выберите из выпадающего списка уже созданную процедуру ЦенаАрендыПриИзменении. Переименуем её для понятности:
&НаКлиенте
Процедура ПересчитатьСуммуПриИзменении(Элемент)
Объект.Сумма = Объект.ЦенаАренды + Объект.ЦенаСтраховки;
КонецПроцедуры
Такой подход делает модуль формы лаконичным, легко читаемым и простым в поддержке.
Ситуация кардинально меняется, если расчет должен происходить внутри строки табличной части (например, у вас есть список автомобилей в документе, и в каждой строке считается своя сумма).
При работе с табличной частью обращаться напрямую к свойству Объект нельзя, так как платформа не поймет, в какой именно строке пользователь редактирует данные. В этом случае нам нужно получить контекст текущей строки через свойство формы ТекущиеДанные.
Рассмотрим пример реализации для табличной части с именем СписокАвтомобилей:
&НаКлиенте
Процедура СписокАвтомобилейЦенаАрендыПриИзменении(Элемент)
// Получаем строку, в которой сейчас находится пользователь
СтрокаТЧ = Элементы.СписокАвтомобилей.ТекущиеДанные;
// Обязательно делаем проверку на существование строки
Если СтрокаТЧ <> Неопределено Тогда
СтрокаТЧ.Сумма = СтрокаТЧ.ЦенаАренды + СтрокаТЧ.ЦенаСтраховки;
КонецЕсли;
КонецПроцедуры
Аналогично предыдущему примеру, эту процедуру можно назначить обработчиком события ПриИзменении как для колонки ЦенаАренды, так и для колонки ЦенаСтраховки вашей таблицы.
В управляемом приложении 1С числовые реквизиты по умолчанию инициализируются нулем (0). При сложении чисел ошибок не возникает. Однако при программном заполнении или интеграции с внешними системами значения реквизитов могут принять значение Неопределено или Null. Попытка сложить число со значением другого типа приведет к аварийному завершению работы программы.
Для предотвращения подобных ситуаций рекомендуется обезопасить код, приведя значения к числу с помощью встроенной функции Число() или выполнив проверку на заполненность:
&НаКлиенте
Процедура ПересчитатьСуммуБезопасно(Элемент)
Аренда = ?(ЗначениеЗаполнено(Объект.ЦенаАренды), Объект.ЦенаАренды, 0);
Страховка = ?(ЗначениеЗаполнено(Объект.ЦенаСтраховки), Объект.ЦенаСтраховки, 0);
Объект.Сумма = Аренда + Страховка;
КонецПроцедуры
Использование тернарного оператора ?() гарантирует, что даже если одно из полей окажется незаполненным или пустым, в расчете будет использован ноль, и программа продолжит стабильную работу.