В процессе разработки конфигураций 1С часто возникает задача предоставить пользователю гибкий инструмент для расчетов. Классический пример — документ, где в табличной части нужно рассчитать итоговую сумму в строке по формуле. Сложность заключается в том, что эта формула может быть произвольной, задаваться самим пользователем и использовать для расчета значения как из текущей строки (например, Количество и Цена), так и из реквизитов шапки документа (например, общая Премия или КурсВалюты).
Рассмотрим, как реализовать такой механизм грамотно, безопасно и эффективно. Основным инструментом для решения этой задачи в платформе 1С является встроенная глобальная функция Вычислить().
Первое, что может прийти в голову — это взять строку с формулой и просто заменить в ней имена переменных на их числовые значения с помощью функции СтрЗаменить(). После всех замен полученное математическое выражение можно передать в функцию Вычислить().
Проанализируем пример такого подхода. Предположим, у нас есть документ, в шапке которого хранится формула в реквизите ФормулаРасчета и значение Премия. В табличной части Товары есть реквизиты Количество и Цена.
Пример кода с подстановкой:
Процедура РассчитатьСуммыВТабличнойЧасти()
// Перебираем все строки табличной части "Товары"
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
// Получаем шаблон формулы из шапки документа
ТекстФормулы = Объект.ФормулаРасчета; // Например, "(Количество * Цена) + Премия"
// Последовательно заменяем имена переменных на их значения
ТекстФормулы = СтрЗаменить(ТекстФормулы, "Количество", СтрокаТЧ.Количество);
ТекстФормулы = СтрЗаменить(ТекстФормулы, "Цена", СтрокаТЧ.Цена);
ТекстФормулы = СтрЗаменить(ТекстФормулы, "Премия", Объект.Премия);
// После замен строка может выглядеть так: "(10 * 150.50) + 500"
// Вычисляем и записываем результат
Попытка
СтрокаТЧ.Сумма = Вычислить(ТекстФормулы);
Исключение
Сообщить("Не удалось рассчитать сумму в строке. Проверьте формулу.");
СтрокаТЧ.Сумма = 0;
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Несмотря на кажущуюся простоту, у этого метода есть существенные недостатки:
СтрЗаменить() будет заметно замедлять работу.Этот способ можно использовать для очень простых случаев, но для серьезных разработок существует более элегантное и правильное решение.
Платформа 1С предоставляет мощный механизм для функции Вычислить() — передачу контекста. Это означает, что мы можем передать в функцию не только строку с формулой, но и специальный объект (обычно Структура), в котором будут содержаться все переменные и их значения. Функция сама найдет в формуле имена переменных и возьмет их значения из переданного контекста.
Синтаксис функции выглядит так: Вычислить(Выражение, [Контекст], [БезопасныйРежим]).
Разберем по шагам, как правильно реализовать расчет с использованием контекста.
Контекст — это коллекция пар "Ключ" и "Значение". В качестве ключа мы используем имя переменной так, как оно написано в формуле, а в качестве значения — реальное значение из документа. Для этого идеально подходит объект Структура.
В нашем примере нам нужно собрать в одной структуре значения из строки (Количество, Цена) и из шапки (Премия).
Очень важный аспект — безопасность. Если формулу вводит пользователь, он теоретически может попытаться выполнить в ней вредоносный код. Чтобы это предотвратить, необходимо использовать третий параметр функции Вычислить() — безопасный режим. Установка его в значение Истина запрещает выполнение любых действий, которые могут изменить данные в базе или взаимодействовать с операционной системой.
Пользователь может ошибиться при вводе формулы (например, написать `5++5` или использовать несуществующую переменную). Такой код вызовет исключение. Чтобы программа не прекратила свою работу аварийно, вызов Вычислить() необходимо обернуть в конструкцию Попытка ... Исключение.
Давайте объединим все эти принципы в одной процедуре, которую можно разместить в модуле объекта документа.
// Процедура для расчета суммы в одной конкретной строке табличной части
Процедура РассчитатьСуммуВСтроке(СтрокаТЧ)
// Получаем строку с формулой из реквизита шапки документа
ТекстФормулы = Объект.Формула; // Например, "(Количество * Цена) + Премия"
// Если формула не задана, выходим
Если НЕ ЗначениеЗаполнено(ТекстФормулы) Тогда
СтрокаТЧ.Сумма = 0;
Возврат;
КонецЕсли;
// 1. Создаем контекст для вычисления
КонтекстВычисления = Новый Структура;
// 2. Заполняем контекст данными из строки ТЧ и шапки документа
// Ключи структуры ("Количество", "Цена", "Премия") должны точно соответствовать
// именам переменных в строке с формулой.
КонтекстВычисления.Вставить("Количество", СтрокаТЧ.Количество);
КонтекстВычисления.Вставить("Цена", СтрокаТЧ.Цена);
КонтекстВычисления.Вставить("Премия", Объект.Премия);
// При необходимости сюда можно добавить и другие реквизиты
// Например, КонтекстВычисления.Вставить("КурсВалюты", Объект.КурсВалюты);
// 3. Выполняем вычисление, обернув его в Попытку и используя безопасный режим
Попытка
// Вызываем Вычислить() с тремя параметрами:
// 1. Сама формула
// 2. Контекст со значениями переменных
// 3. Безопасный режим (Истина - ОБЯЗАТЕЛЬНО, если формула вводится пользователем)
СтрокаТЧ.Сумма = Вычислить(ТекстФормулы, КонтекстВычисления, Истина);
Исключение
// Если в формуле ошибка, сообщаем пользователю и обнуляем сумму
Сообщить("Ошибка в формуле расчета: " + ОписаниеОшибки());
СтрокаТЧ.Сумма = 0;
КонецПопытки;
КонецПроцедуры
Написанную нами процедуру РассчитатьСуммуВСтроке() необходимо вызывать каждый раз, когда изменяются данные, влияющие на результат.
Количество, Цена) обработчики события ПриИзменении на форме.Премия, Формула). В этом случае нужно будет пересчитать суммы во всех строках табличной части.Пример вызова для реквизита табличной части (управляемая форма):
&НаКлиенте
Процедура ТоварыЦенаПриИзменении(Элемент)
// Получаем объект текущей строки на клиенте
ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
// Вызываем серверную процедуру для выполнения расчета
РассчитатьСуммуНаСервере(ТекущаяСтрока.ПолучитьИдентификатор());
КонецПроцедуры
&НаСервереБезКонтекста
Процедура РассчитатьСуммуНаСервере(ИдентификаторСтроки)
// Получаем объект документа для изменения
ОбъектДокумента = РеквизитФормыВЗначение("Объект");
// Находим нужную строку по идентификатору
СтрокаТЧ = ОбъектДокумента.Товары.НайтиПоИдентификатору(ИдентификаторСтроки);
// Вызываем нашу основную процедуру расчета из модуля объекта
ОбъектДокумента.РассчитатьСуммуВСтроке(СтрокаТЧ);
// Возвращаем измененный объект обратно в форму
ЗначениеВРеквизитФормы(ОбъектДокумента, "Объект");
КонецПроцедуры
Вывод: Использование функции Вычислить() с передачей контекста и включенным безопасным режимом является стандартным, эффективным и безопасным способом реализации динамических расчетов в 1С. Этот подход обеспечивает гибкость для пользователя и надежность для системы.