В процессе разработки прикладных решений на платформе «1С:Предприятие 8.3» (особенно в управляемом приложении) часто возникает задача автоматического расчета зависимых показателей в табличных частях. Типичный пример: расчет суммы строки на основе цены и количества или, как в нашем случае, расчет Значимости риска на основе его Вероятности и Последствий.
Несмотря на кажущуюся простоту задачи, начинающие разработчики часто сталкиваются с ошибками преобразования типов данных или доступности контекста формы. В этой статье мы подробно разберем, почему возникают эти ошибки, как правильно реализовать расчет на клиенте и как сделать код профессиональным и отказоустойчивым.
Разберем две классические ошибки, с которыми столкнулся автор темы при самостоятельной реализации задачи.
Ошибка №1: Ошибка преобразования значения к типу Число
В первой попытке код выглядел следующим образом:
&НаСервере
Процедура ПричиныПланиуремыеПоследствияРискаПриИзмененииНаСервере
Элементы.ПричиныПланируемаяЗначимостьРиска = Элементы.ПричиныПланируемаяВероятностьРиска * Элементы.ПричиныПланиуремыеПоследствияРиска;
КонецПроцедуры
Выясним причину сбоя. Коллекция Элементы в управляемом интерфейсе 1С хранит исключительно элементы управления (визуальные поля ввода, кнопки, таблицы формы), а не сами данные, которые в них отображаются. Попытка перемножить два поля ввода аналогична попытке умножить одну пластиковую рамку монитора на другую — платформа логично сообщает об ошибке преобразования типов.
Ошибка №2: Текущие данные таблицы недоступны на сервере
Поняв, что нужно обращаться к данным, разработчик попытался использовать свойство таблицы формы ТекущиеДанные на сервере:
// Ошибка: Попытка обратиться к ТекущиеДанные в серверном контексте
ТекДанные = Элементы.Причины.ТекущиеДанные;
Проанализируем ситуацию. Свойство таблицы формы ТекущиеДанные возвращает строку коллекции (тип ДанныеФормыЭлементКоллекции), на которой в данный момент физически установлен фокус (курсор) пользователя. Поскольку сервер 1С работает в фоновом режиме и «не знает», на какой строке экрана пользователь держит курсор, обращение к визуальному интерактивному контексту на сервере вызывает исключение.
Для выполнения простых арифметических расчетов нет необходимости отправлять запрос на сервер. Это лишняя нагрузка на каналы связи и сервер приложений. Расчет должен выполняться мгновенно прямо на компьютере пользователя — в контексте &НаКлиенте.
Рассмотрим правильный алгоритм реализации:
ТекущиеДанные элемента управления таблицы.Посмотрим на практическую реализацию обработчика события на клиенте:
&НаКлиенте
Процедура ПричиныПоследствияРискаПриИзменении(Элемент)
// Получаем строку табличной части, в которой находится пользователь
ТекДанные = Элементы.Причины.ТекущиеДанные;
// Обязательно проверяем, что строка действительно выбрана
Если ТекДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Производим расчет значения
ТекДанные.ЗначимостьРиска = ТекДанные.ВероятностьРиска * ТекДанные.ПоследствияРиска;
КонецПроцедуры
Описанный выше код решает локальную проблему, но в реальной бизнес-системе он быстро приведет к нарушению целостности данных. Разберем критические уязвимости этого подхода:
ПриИзменении реквизита "Последствия", то при ручном изменении "Вероятности" итоговое поле "Значимость" не пересчитается. Нам необходимо привязать обработчик к обоим влияющим реквизитам.ПриИзменении) генерируются исключительно при ручном, интерактивном вводе данных пользователем в интерфейсе. Если бизнес-процесс или документ будет создаваться или заполняться программно (например, при загрузке из Excel, через API, интеграцию с другими системами или фоновыми заданиями), колонка "Значимость" останется пустой!Чтобы избежать дублирования кода на форме и гарантировать 100% корректность данных при любых сценариях работы системы, применим комплексный архитектурный подход.
Создадим единую вспомогательную процедуру пересчета на клиенте, защищенную от пустых значений, и привяжем ее к событиям изменения обоих реквизитов:
&НаКлиенте
Процедура РассчитатьЗначимостьСтроки(СтрокаТаблицы)
Если СтрокаТаблицы = Неопределено Тогда
Возврат;
КонецЕсли;
// Защита от пустых или незаполненных значений (приводим к нулю)
Вероятность = ?(ЗначениеЗаполнено(СтрокаТаблицы.ВероятностьРиска), СтрокаТаблицы.ВероятностьРиска, 0);
Последствия = ?(ЗначениеЗаполнено(СтрокаТаблицы.ПоследствияРиска), СтрокаТаблицы.ПоследствияРиска, 0);
СтрокаТаблицы.ЗначимостьРиска = Вероятность * Последствия;
КонецПроцедуры
&НаКлиенте
Процедура ПричиныВероятностьРискаПриИзменении(Элемент)
РассчитатьЗначимостьСтроки(Элементы.Причины.ТекущиеДанные);
КонецПроцедуры
&НаКлиенте
Процедура ПричиныПоследствияРискаПриИзменении(Элемент)
РассчитатьЗначимостьСтроки(Элементы.Причины.ТекущиеДанные);
КонецПроцедуры
Для того чтобы колонка "Значимость" гарантированно рассчитывалась при программной записи объекта (без участия формы), продублируем логику расчета в модуле объекта бизнес-процесса в предопределенном обработчике ПередЗаписью:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Исключаем расчет при обмене данными, если это требуется бизнес-логикой
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Обходим всю табличную часть и принудительно пересчитываем каждую строку
Для Каждого СтрокаТЧ Из Причины Цикл
Вероятность = ?(ЗначениеЗаполнено(СтрокаТЧ.ВероятностьРиска), СтрокаТЧ.ВероятностьРиска, 0);
Последствия = ?(ЗначениеЗаполнено(СтрокаТЧ.ПоследствияРиска), СтрокаТЧ.ПоследствияРиска, 0);
СтрокаТЧ.ЗначимостьРиска = Вероятность * Последствия;
КонецЦикла;
КонецПроцедуры
Такой комбинированный подход гарантирует, что пользователь мгновенно увидит результат произведения на экране при вводе чисел (благодаря быстрому клиентскому коду), а база данных сохранит целостность и верные показатели даже в случае сложной автоматической загрузки данных в систему.