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