Как реализовать формулы с условиями в динамическом ценообразовании 1С:УНФ?

Программист 1С v8.3 (Управляемые формы) 1С:Управление нашей фирмой Управленческий учет Торговля и дистрибуция
← На главную

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

Однако на практике часто требуется сложная условная логика: например, если задана рекомендованная цена производителя, использовать её, а если она равна нулю или не заполнена — рассчитывать розничную цену как закупочную с наценкой 35%. Разберем, с какими ограничениями платформы приходится сталкиваться при решении этой задачи и как реализовать поддержку условий.

Ограничения метода Вычислить() и тернарный оператор

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

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

?([РекомендованнаяЗаводом] > 0, [РекомендованнаяЗаводом], [Закупочная] * 1.35)

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

Решение через переопределение процедуры в расширении конфигурации

Разберем по шагам реализацию пользовательского синтаксиса условий с помощью расширения конфигурации. В модуле ЦенообразованиеФормулыСервер переопределим процедуру РасчетДанныхПоФормуле с аннотацией &Вместо.

Логика работы механизма строится следующим образом:

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

Посмотрим на пример реализации алгоритма:


&НаСервере
&Вместо("РасчетДанныхПоФормуле")
Процедура Расш1_РасчетДанныхПоФормуле(Знач ФормулаСтрокой, СтруктураОперандов, РасчетныеДанные = Неопределено, ОкруглятьВБольшуюСторону = Неопределено, ПравилоОкругления = Неопределено) Экспорт
    
    // Заменяем идентификаторы операндов на их фактические значения
    Для Каждого Операнд Из СтруктураОперандов Цикл
        ФормулаСтрокой = СтрЗаменить(ФормулаСтрокой, Операнд.Ключ, Операнд.Значение);
    КонецЦикла;
    
    // Проверяем наличие условий в формуле
    Если СтрНайти(ВРег(ФормулаСтрокой), "ЕСЛИ") > 0 Тогда
        ЗначениеФормулы = Неопределено;
        МассивУсловий = РазложитьУсловияВМассив(ФормулаСтрокой);
        
        Для Каждого ЭлементУсловия Из МассивУсловий Цикл
            Если Вычислить(ЭлементУсловия.Условие) Тогда
                ЗначениеФормулы = ЭлементУсловия.Значение;
                Прервать;
            КонецЕсли;
        КонецЦикла;
        
        Если ЗначениеФормулы <> Неопределено Тогда
            ФормулаСтрокой = ЗначениеФормулы;
        КонецЕсли;
    КонецЕсли;
    
    // Вызов типового расчета финального числового выражения
    ПродолжитьВызов(ФормулаСтрокой, Новый Структура, РасчетныеДанные, ОкруглятьВБольшуюСторону, ПравилоОкругления);
    
КонецПроцедуры

Для разбора исходного текста формулы на пары «Условие – Формула» используем вспомогательные функции:


Функция РазложитьУсловияВМассив(ТекстУсловия) Экспорт
    
    МассивПодстрок = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(ТекстУсловия, "Тогда", Ложь);
    КоличествоПодстрок = МассивПодстрок.Количество();
    МассивУсловийЗначений = Новый Массив;
    
    Индекс = 0;
    Для Каждого Подстрока Из МассивПодстрок Цикл
        СтруктураУсловий = Новый Структура;
        
        Если Индекс = 0 Тогда
            // Первое условие после ключевого слова ЕСЛИ
            СтруктураУсловий.Вставить("Условие", ПочиститьСтроку(Подстрока));
            МассивУсловийЗначений.Добавить(СтруктураУсловий);
            Индекс = Индекс + 1;
            Продолжить;
        КонецЕсли;
        
        МассивСтроки = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(Подстрока, "Иначе", Ложь);
        
        Если Индекс = (КоличествоПодстрок - 1) Тогда            
            // Финальный блок ИНАЧЕ ... КОНЕЦЕСЛИ
            МассивУсловийЗначений[Индекс - 1].Вставить("Значение", ПочиститьСтроку(МассивСтроки[0]));
            
            СтруктураУсловий = Новый Структура;
            СтруктураУсловий.Вставить("Условие", "1=1");
            СтруктураУсловий.Вставить("Значение", ПочиститьСтроку(МассивСтроки[1]));
            МассивУсловийЗначений.Добавить(СтруктураУсловий);
            Возврат МассивУсловийЗначений;
        КонецЕсли;
        
        // Промежуточные блоки ИНАЧЕЕСЛИ
        МассивУсловийЗначений[Индекс - 1].Вставить("Значение", ПочиститьСтроку(МассивСтроки[0]));
        СтруктураУсловий.Вставить("Условие", ПочиститьСтроку(МассивСтроки[1]));
        МассивУсловийЗначений.Добавить(СтруктураУсловий);
        
        Индекс = Индекс + 1;
    КонецЦикла;
    
    Возврат МассивУсловийЗначений;
КонецФункции

Функция ПочиститьСтроку(Знач Текст)
    Текст = СтрЗаменить(Текст, "Если", "");
    Текст = СтрЗаменить(Текст, "если", "");
    Текст = СтрЗаменить(Текст, "Тогда", "");
    Текст = СтрЗаменить(Текст, "тогда", "");
    Текст = СтрЗаменить(Текст, "Иначе", "");
    Текст = СтрЗаменить(Текст, "иначе", "");
    Текст = СтрЗаменить(Текст, "КонецЕсли", "");
    Текст = СтрЗаменить(Текст, "конецЕсли", "");
    Текст = СтрЗаменить(Текст, "Конецесли", "");
    Текст = СтрЗаменить(Текст, "конец", "");
    Возврат СокрЛП(Текст);
КонецФункции

Альтернативное решение: расчет через схему компоновки данных (СКД)

Если внесение доработок в конфигурацию или подключение расширений нежелательно, рассмотрим типовую альтернативу — расчет цен на основе схемы компоновки данных (произвольный запрос). В настройках вида цен УНФ доступен способ расчета цен на основании СКД.

Преимущества использования СКД:

  1. Поддержка условной логики: на уровне языка запросов и выражений полей СКД доступна стандартная конструкция ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ.
  2. Работа с нулевыми и отсутствующими ценами: функция языка запросов ЕСТЬNULL() позволяет корректно обрабатывать номенклатурные позиции, по которым еще нет записей в регистре сведений ЦеныНоменклатуры.
  3. Производительность: расчет выполняется пакетно на стороне СУБД, что значительно быстрее построчной обработки больших прайс-листов через Вычислить().

Пример выражения в запросе СКД для аналогичной задачи:


ВЫБОР
    КОГДА ЕСТЬNULL(ЦеныРекомендованные.Цена, 0) > 0
        ТОГДА ЦеныРекомендованные.Цена
    ИНАЧЕ ЕСТЬNULL(ЦеныЗакупочные.Цена, 0) * 1.35
КОНЕЦ

Рекомендации по повышению отказоустойчивости

При настройке и разработке механизмов расчета цен с условиями обратим внимание на важные детали:

  1. Контроль деления на ноль: формулы с условиями часто содержат коэффициенты и дроби. Любое вычисление формулы должно быть защищено блоком Попытка ... Исключение, чтобы ошибка в одной позиции номенклатуры не прерывала расчет всего прайс-листа или проведения документа.
  2. Обработка отсутствующих цен: при подстановке значений операндов из регистра сведений проверяйте полученные значения на Неопределено или Null, заменяя их числовым нулем (0).
  3. Валидация синтаксиса при записи: для предотвращения пользовательских ошибок проверяйте синтаксическую корректность введенной формулы в обработчике ПередЗаписью справочника видов цен путем тестового вычисления с фиктивными числовыми данными.
← На главную