Как в 1С рассчитать значение в строке документа по формуле, использующей данные из шапки и табличной части?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки конфигураций 1С часто возникает задача предоставить пользователю гибкий инструмент для расчетов. Классический пример — документ, где в табличной части нужно рассчитать итоговую сумму в строке по формуле. Сложность заключается в том, что эта формула может быть произвольной, задаваться самим пользователем и использовать для расчета значения как из текущей строки (например, Количество и Цена), так и из реквизитов шапки документа (например, общая Премия или КурсВалюты).

Рассмотрим, как реализовать такой механизм грамотно, безопасно и эффективно. Основным инструментом для решения этой задачи в платформе 1С является встроенная глобальная функция Вычислить().

Подход 1: Прямая подстановка значений в строку (устаревший)

Первое, что может прийти в голову — это взять строку с формулой и просто заменить в ней имена переменных на их числовые значения с помощью функции СтрЗаменить(). После всех замен полученное математическое выражение можно передать в функцию Вычислить().

Проанализируем пример такого подхода. Предположим, у нас есть документ, в шапке которого хранится формула в реквизите ФормулаРасчета и значение Премия. В табличной части Товары есть реквизиты Количество и Цена.

Пример кода с подстановкой:


Процедура РассчитатьСуммыВТабличнойЧасти()

    // Перебираем все строки табличной части "Товары"
    Для Каждого СтрокаТЧ Из Объект.Товары Цикл
        
        // Получаем шаблон формулы из шапки документа
        ТекстФормулы = Объект.ФормулаРасчета; // Например, "(Количество * Цена) + Премия"
        
        // Последовательно заменяем имена переменных на их значения
        ТекстФормулы = СтрЗаменить(ТекстФормулы, "Количество", СтрокаТЧ.Количество);
        ТекстФормулы = СтрЗаменить(ТекстФормулы, "Цена", СтрокаТЧ.Цена);
        ТекстФормулы = СтрЗаменить(ТекстФормулы, "Премия", Объект.Премия);
        
        // После замен строка может выглядеть так: "(10 * 150.50) + 500"
        
        // Вычисляем и записываем результат
        Попытка
            СтрокаТЧ.Сумма = Вычислить(ТекстФормулы);
        Исключение
            Сообщить("Не удалось рассчитать сумму в строке. Проверьте формулу.");
            СтрокаТЧ.Сумма = 0;
        КонецПопытки;

    КонецЦикла;

КонецПроцедуры

Несмотря на кажущуюся простоту, у этого метода есть существенные недостатки:

  1. Низкая производительность: На больших табличных частях многократный вызов СтрЗаменить() будет заметно замедлять работу.
  2. Потенциальные ошибки: Если имена переменных пересекаются (например, "Скидка" и "СкидкаПроцент"), замены могут произойти некорректно.
  3. Плохая читаемость и поддержка: Код становится громоздким, если в формуле много переменных.

Этот способ можно использовать для очень простых случаев, но для серьезных разработок существует более элегантное и правильное решение.

Подход 2: Использование контекста вычисления (рекомендуемый)

Платформа 1С предоставляет мощный механизм для функции Вычислить() — передачу контекста. Это означает, что мы можем передать в функцию не только строку с формулой, но и специальный объект (обычно Структура), в котором будут содержаться все переменные и их значения. Функция сама найдет в формуле имена переменных и возьмет их значения из переданного контекста.

Синтаксис функции выглядит так: Вычислить(Выражение, [Контекст], [БезопасныйРежим]).

Разберем по шагам, как правильно реализовать расчет с использованием контекста.

Шаг 1: Создание и заполнение контекста

Контекст — это коллекция пар "Ключ" и "Значение". В качестве ключа мы используем имя переменной так, как оно написано в формуле, а в качестве значения — реальное значение из документа. Для этого идеально подходит объект Структура.

В нашем примере нам нужно собрать в одной структуре значения из строки (Количество, Цена) и из шапки (Премия).

Шаг 2: Вызов функции с контекстом и в безопасном режиме

Очень важный аспект — безопасность. Если формулу вводит пользователь, он теоретически может попытаться выполнить в ней вредоносный код. Чтобы это предотвратить, необходимо использовать третий параметр функции Вычислить()безопасный режим. Установка его в значение Истина запрещает выполнение любых действий, которые могут изменить данные в базе или взаимодействовать с операционной системой.

Шаг 3: Обработка возможных ошибок

Пользователь может ошибиться при вводе формулы (например, написать `5++5` или использовать несуществующую переменную). Такой код вызовет исключение. Чтобы программа не прекратила свою работу аварийно, вызов Вычислить() необходимо обернуть в конструкцию Попытка ... Исключение.

Итоговый код

Давайте объединим все эти принципы в одной процедуре, которую можно разместить в модуле объекта документа.


// Процедура для расчета суммы в одной конкретной строке табличной части
Процедура РассчитатьСуммуВСтроке(СтрокаТЧ)

    // Получаем строку с формулой из реквизита шапки документа
    ТекстФормулы = Объект.Формула; // Например, "(Количество * Цена) + Премия"

    // Если формула не задана, выходим
    Если НЕ ЗначениеЗаполнено(ТекстФормулы) Тогда
        СтрокаТЧ.Сумма = 0;
        Возврат;
    КонецЕсли;

    // 1. Создаем контекст для вычисления
    КонтекстВычисления = Новый Структура;

    // 2. Заполняем контекст данными из строки ТЧ и шапки документа
    // Ключи структуры ("Количество", "Цена", "Премия") должны точно соответствовать
    // именам переменных в строке с формулой.
    КонтекстВычисления.Вставить("Количество", СтрокаТЧ.Количество);
    КонтекстВычисления.Вставить("Цена",       СтрокаТЧ.Цена);
    КонтекстВычисления.Вставить("Премия",     Объект.Премия);
    // При необходимости сюда можно добавить и другие реквизиты
    // Например, КонтекстВычисления.Вставить("КурсВалюты", Объект.КурсВалюты);

    // 3. Выполняем вычисление, обернув его в Попытку и используя безопасный режим
    Попытка
        // Вызываем Вычислить() с тремя параметрами:
        // 1. Сама формула
        // 2. Контекст со значениями переменных
        // 3. Безопасный режим (Истина - ОБЯЗАТЕЛЬНО, если формула вводится пользователем)
        СтрокаТЧ.Сумма = Вычислить(ТекстФормулы, КонтекстВычисления, Истина); 
    Исключение
        // Если в формуле ошибка, сообщаем пользователю и обнуляем сумму
        Сообщить("Ошибка в формуле расчета: " + ОписаниеОшибки());
        СтрокаТЧ.Сумма = 0;
    КонецПопытки;

КонецПроцедуры

Практическое применение: когда и как вызывать расчет

Написанную нами процедуру РассчитатьСуммуВСтроке() необходимо вызывать каждый раз, когда изменяются данные, влияющие на результат.

  1. При изменении данных в строке: Создайте для реквизитов табличной части (Количество, Цена) обработчики события ПриИзменении на форме.
  2. При изменении данных в шапке: Создайте обработчики для реквизитов шапки (Премия, Формула). В этом случае нужно будет пересчитать суммы во всех строках табличной части.

Пример вызова для реквизита табличной части (управляемая форма):


&НаКлиенте
Процедура ТоварыЦенаПриИзменении(Элемент)
    // Получаем объект текущей строки на клиенте
    ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
    // Вызываем серверную процедуру для выполнения расчета
    РассчитатьСуммуНаСервере(ТекущаяСтрока.ПолучитьИдентификатор());
КонецПроцедуры

&НаСервереБезКонтекста
Процедура РассчитатьСуммуНаСервере(ИдентификаторСтроки)
    // Получаем объект документа для изменения
    ОбъектДокумента = РеквизитФормыВЗначение("Объект");
    // Находим нужную строку по идентификатору
    СтрокаТЧ = ОбъектДокумента.Товары.НайтиПоИдентификатору(ИдентификаторСтроки);
    
    // Вызываем нашу основную процедуру расчета из модуля объекта
    ОбъектДокумента.РассчитатьСуммуВСтроке(СтрокаТЧ);
    
    // Возвращаем измененный объект обратно в форму
    ЗначениеВРеквизитФормы(ОбъектДокумента, "Объект");
КонецПроцедуры

Вывод: Использование функции Вычислить() с передачей контекста и включенным безопасным режимом является стандартным, эффективным и безопасным способом реализации динамических расчетов в 1С. Этот подход обеспечивает гибкость для пользователя и надежность для системы.

← На главную