В практической разработке на платформе 1С часто возникает задача, когда расчетный показатель (например, поправочный коэффициент) зависит сразу от нескольких параметров, выбранных пользователем. Это может быть сочетание текстовых категорий и числовых интервалов (например, диаметр трубопровода, попадающий в определенный диапазон).
Начинающие разработчики часто пытаются решить эту задачу «в лоб»: создают таблицу значений на форме, вешают расчет на событие активизации строки и пытаются использовать стандартный поиск через отборы. В этой статье мы подробно разберем, почему такие подходы приводят к критическим ошибкам, детально проанализируем неправильный код и рассмотрим два надежных архитектурных решения этой задачи.
Давайте разберем типичные ошибки, которые часто совершают разработчики при работе с коллекциями данных в 1С. Рассмотрим фрагмент кода, где выполняется попытка настроить отбор:
Отбор = Новый Структура("ТопографическаяМестность", "Равнинно-Холмистый", "КапитальныеВложения", "Линейная_часть_трубопровода");
МассивСтрок = ПоправочныйКоэффициентНаТопографическиеУсловияТрассы.НайтиСтроки(Отбор);
СтрокаТаблицыЗначений = МассивСтрок[0];
В этом коде скрыты сразу три серьезные проблемы:
Структура принимает параметры парами: сначала строка со списком имен свойств через запятую, затем — значения этих свойств. Написание Новый Структура("Ключ1", "Значение1", "Ключ2", "Значение2") синтаксически неверно. Платформа воспримет это некорректно. Правильный синтаксис инициализации выглядит так:
Отбор = Новый Структура("ТопографическаяМестность, КапитальныеВложения", "Равнинно-Холмистый", "Линейная_часть_трубопровода");
НайтиСтроки() работает исключительно по принципу строгого равенства. Если вам нужно определить, входит ли диаметр трубопровода в диапазон (например, больше 219 и меньше 426), метод НайтиСтроки() здесь бессилен. Передача пустого значения или попытка наложить условие диапазона через отбор вернет пустой массив строк.СтрокаТаблицыЗначений = МассивСтрок[0]; вызовет аварийное завершение работы программы, если метод НайтиСтроки() не найдет ни одного совпадения и вернет пустой массив. Перед обращением к элементу массива по индексу всегда нужно делать проверку:
Если МассивСтрок.Количество() > 0 Тогда
СтрокаТаблицыЗначений = МассивСтрок[0];
Иначе
// Обработка ситуации, когда строка не найдена
КонецЕсли;
Еще одна грубая ошибка — это попытка динамически добавлять параметры в структуру через метод Вставить(), передавая туда цепочку значений:
Отбор.Вставить("ТопографическаяМестность", "Пустынный", "КапитальныеВложения", "Линейная_часть_трубопровода", ...);
Метод Вставить() принимает строго два параметра: имя ключа и его значение. Более того, ключи в структуре уникальны. При повторном вызове Отбор.Вставить("ТопографическаяМестность", "Пустынный") предыдущее значение ключа ТопографическаяМестность (например, "Равнинно-Холмистый") будет просто перезаписано. Структура не может хранить несколько альтернативных условий для одного и того же поля.
Также крайне не рекомендуется использовать событие таблицы ПриАктивизацииСтроки для расчетов коэффициентов. Это событие генерируется системой постоянно при любом перемещении курсора по таблице, даже когда строка еще пуста или заполнена частично, что приводит к избыточным вызовам сервера и ошибкам.
---
Хранить матрицы коэффициентов непосредственно в коде модуля или внутри ручной таблицы на форме — плохая практика разработки. Если коэффициенты изменятся или добавятся новые типы местности, вам придется переписывать программный код формы и обновлять конфигурацию у пользователей.
Правильный архитектурный подход — перенести эти данные в непериодический Регистр сведений (назовем его, например, ПоправочныеКоэффициенты).
Структура регистра сведений:
ТопографическаяМестность (Тип: СправочникСсылка.ТопографическаяМестность или ПеречислениеСсылка)КапитальныеВложения (Тип: СправочникСсылка.ВидыКапитальныхВложений или ПеречислениеСсылка)ДиаметрМинимум (Тип: Число, длина 10, точность 0)ДиаметрМаксимум (Тип: Число, длина 10, точность 0)Коэффициент (Тип: Число, длина 5, точность 2)При таком подходе поиск нужного коэффициента сводится к быстрому и простому запросу к базе данных на стороне сервера. Мы передаем в запрос выбранную местность, вид вложений и текущий диаметр, а СУБД эффективно находит нужную запись по условию вхождения числа в диапазон.
Рассмотрим пример серверной функции для получения коэффициента:
&НаСервереБезКонтекста
Функция ПолучитьКоэффициентИзРегистра(Знач Местность, Знач Вложения, Знач Диаметр) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Рег.Коэффициент КАК Коэффициент
|ИЗ
| РегистрСведений.ПоправочныеКоэффициенты КАК Рег
|ГДЕ
| Рег.ТопографическаяМестность = &Местность
| И Рег.КапитальныеВложения = &Вложения
| И Рег.ДиаметрМинимум <= &Диаметр
| И Рег.ДиаметрМаксимум >= &Диаметр";
Запрос.УстановитьПараметр("Местность", Местность);
Запрос.УстановитьПараметр("Вложения", Вложения);
Запрос.УстановитьПараметр("Диаметр", Диаметр);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Коэффициент;
КонецЕсли;
// Возвращаем дефолтное значение 1.0, если коэффициент не найден в базе данных
Возврат 1.00;
КонецФункции
---
Если по каким-то причинам бизнес-требования не позволяют создать полноценный регистр сведений, и расчетная матрица должна находиться в оперативной памяти (в локальной Таблице значений на форме), нам придется отказаться от метода НайтиСтроки() в пользу ручного перебора строк в цикле. Только цикл позволит нам гибко проверить условия вхождения числа в диапазон значений.
Допустим, на форме создана таблица со следующими колонками: ТопографическаяМестность, КапитальныеВложения, ДиаметрМинимум, ДиаметрМаксимум, Коэффициент.
Рассмотрим алгоритм ручного поиска нужного коэффициента:
&НаСервереБезКонтекста
Функция ПолучитьКоэффициентИзТаблицы(Знач ТаблицаДанных, Знач Местность, Знач Вложения, Знач Диаметр)
// Перебираем строки коллекции (входным параметром может быть как ТаблицаЗначений, так и ДанныеФормыКоллекция)
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
// Сначала проверяем строгие равенства для текстовых/ссылочных полей
Если СтрокаДанных.ТопографическаяМестность = Местность
И СтрокаДанных.КапитальныеВложения = Вложения Тогда
// Теперь проверяем, попадает ли переданное число в диапазон текущей строки
Если Диаметр >= СтрокаДанных.ДиаметрМинимум И Диаметр <= СтрокаДанных.ДиаметрМаксимум Тогда
Возврат СтрокаДанных.Коэффициент;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Возвращаем дефолтное значение, если совпадений не обнаружено
Возврат 1.00;
КонецФункции
---
Чтобы расчет происходил вовремя и без ошибок, нам нужно правильно спроектировать интерфейсное поведение управляемой формы. Расчет должен запускаться автоматически только тогда, когда пользователь заполнил все три исходных поля.
Для этого мы привяжем обработчик события ПриИзменении к каждому из трех реквизитов ввода на форме.
Разберем полный рабочий пример кода модуля управляемой формы:
&НаКлиенте
Процедура ТопографическаяМестностьПриИзменении(Элемент)
ВыполнитьРасчетКоэффициента();
КонецПроцедуры
&НаКлиенте
Процедура КапитальныеВложенияПриИзменении(Элемент)
ВыполнитьРасчетКоэффициента();
КонецПроцедуры
&НаКлиенте
Процедура ДиаметрТрубопроводаПриИзменении(Элемент)
ВыполнитьРасчетКоэффициента();
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьРасчетКоэффициента()
// Шаг 1. Проверяем, что пользователь заполнил все необходимые данные на форме
Если НЕ ЗначениеЗаполнено(Объект.ТопографическаяМестность)
ИЛИ НЕ ЗначениеЗаполнено(Объект.КапитальныеВложения)
ИЛИ НЕ ЗначениеЗаполнено(Объект.ДиаметрТрубопровода) Тогда
// Если хотя бы одно поле не заполнено, расчет не производим и выходим
Возврат;
КонецЕсли;
// Шаг 2. Вызываем расчет на сервере в зависимости от выбранной архитектуры
// В данном примере мы используем рекомендуемый Подход №1 (получение данных из Регистра сведений)
НайденныйКоэффициент = ПолучитьКоэффициентИзРегистра(
Объект.ТопографическаяМестность,
Объект.КапитальныеВложения,
Объект.ДиаметрТрубопровода
);
// Шаг 3. Записываем полученный результат в итоговый реквизит формы
Объект.ТопогКоэфф1 = НайденныйКоэффициент;
КонецПроцедуры
Благодаря такому подходу интерфейс программы становится отзывчивым и предсказуемым для пользователя. Расчет выполняется мгновенно при заполнении последнего недостающего реквизита, а разработчик защищен от зависаний интерфейса и ошибок некорректного индексирования массивов.