Разработчики в среде 1С:Предприятие часто сталкиваются со следующей задачей: в табличной части формы есть колонка Сумма, которая рассчитывается автоматически на основании введенных пользователем данных в колонки Цена и Количество. Возникает необходимость выполнять дополнительные действия (например, рассчитывать сумму НДС, скидку или изменять значение другой зависимой колонки) в тот момент, когда сумма была автоматически пересчитана.
Первая интуитивная реакция разработчика — создать обработчик события ПриИзменении для элемента формы Сумма. Однако при тестировании выясняется, что данный обработчик срабатывает исключительно тогда, когда пользователь редактирует ячейку вручную. При автоматическом программном расчете суммы это событие игнорируется платформой. Ситуация усложняется, если разработка ведется в расширении конфигурации, а сам алгоритм расчета находится в основной базе.
В этой статье мы подробно разберем, почему платформа 1С ведет себя именно так, и рассмотрим три надежных практических способа решения этой задачи при проектировании расширений.
Рассмотрим внутренние механизмы работы платформы 1С:Предприятие. Событие ПриИзменении (OnChange) для элементов формы (полей ввода, флажков и т.д.) разработано исключительно для интерактивного взаимодействия. Оно сигнализирует системе, что данные были изменены пользователем через интерфейс ввода.
Когда значение в ячейке меняется с помощью встроенного языка, например:
ТекущаяСтрока.Сумма = ТекущаяСтрока.Цена * ТекущаяСтрока.Количество;
платформа обновляет данные в информационной базе и на клиенте, но не генерирует интерактивные события для измененного элемента формы. Это сделано для предотвращения бесконечных рекурсивных вызовов (зацикливания) кода при программном обновлении реквизитов.
Следовательно, пытаться отслеживать программное изменение суммы через обработчик самой суммы бесполезно. Нам нужно использовать другие архитектурные подходы.
В типовых и хорошо спроектированных нетиповых конфигурациях 1С расчет сумм, налогов и скидок редко пишется непосредственно внутри обработчиков событий элементов формы. Как правило, в модуле формы или в общем модуле существует единая экспортная процедура пересчета строки табличной части.
Проанализируем типовой вызов. При изменении цены или количества вызывается процедура, которая может называться, например, РассчитатьСуммуТабличнойЧасти() или ПересчитатьСтроку(). Наша задача — встроить логику нашей новой колонки в этот общий алгоритм пересчета.
Разберем этот процесс по шагам:
Количество.&После для выполнения своего кода сразу после завершения стандартных расчетов.Посмотрим на пример реализации такого кода в модуле расширения:
&После("РассчитатьСтрокуТабличнойЧасти")
Процедура Расш1_РассчитатьСтрокуТабличнойЧасти(СтрокаТабличнойЧасти, ПараметрыПересчета = Неопределено)
// К этому моменту стандартный код уже рассчитал сумму
// Нам остается только рассчитать значение нашей зависимой колонки
Если СтрокаТабличнойЧасти.Сумма > 0 Тогда
СтрокаТабличнойЧасти.ДополнительнаяКолонка = СтрокаТабличнойЧасти.Сумма * 0.1; // например, расчет 10% бонуса
Иначе
СтрокаТабличнойЧасти.ДополнительнаяКолонка = 0;
EndIf;
КонецПроцедуры
Преимущества этого метода: Нам не нужно контролировать, какое именно поле изменил пользователь (Цена, Количество или Скидка). Любое изменение, приводящее к вызову общего расчета суммы, автоматически обновит и наши зависимые данные. Код получается чистым, компактным и простым в обслуживании.
Что делать, если в основной конфигурации расчет суммы написан непосредственно внутри событий элементов формы (например, прямо в процедуре ТоварыКоличествоПриИзменении), и единой процедуры расчета нет? В этом случае нам придется перехватить события изменения всех «влияющих» колонок (в нашем случае это Цена и Количество).
Иногда при попытке автоматически перенести событие из основной формы в расширение через конфигуратор возникает сбой: открывается пустая форма расширения, а обработчик не создается. Выясним причину: это может быть связано с режимом совместимости платформы или тем, что событие привязано программно в процедуре ПриСозданииНаСервере с помощью метода УстановитьДействие.
В такой ситуации мы можем написать перехват вручную непосредственно в модуле формы расширения, точно повторив сигнатуру оригинального обработчика события.
Рассмотрим пример реализации для колонок Цена и Количество:
&НаКлиенте
&После("ТоварыКоличествоПриИзменении")
Процедура Расш1_ТоварыКоличествоПриИзменении(Элемент)
// Получаем текущую строку табличной части
ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
Если ТекущаяСтрока <> Неопределено Тогда
// Стандартный обработчик уже рассчитал Сумму.
// Выполняем расчет нашей зависимой колонки:
ТекущаяСтрока.НашаКолонка = ТекущаяСтрока.Сумма * 0.20; // Например, расчет НДС 20%
КонецЕсли;
КонецПроцедуры
&НаКлиенте
&После("ТоварыЦенаПриИзменении")
Процедура Расш1_ТоварыЦенаПриИзменении(Элемент)
// Повторяем ту же логику для изменения цены
ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
Если ТекущаяСтрока <> Неопределено Тогда
ТекущаяСтрока.НашаКолонка = ТекущаяСтрока.Сумма * 0.20;
КонецЕсли;
КонецПроцедуры
Преимущества этого метода: Позволяет решить задачу точечно на уровне интерфейса формы, не затрагивая серверные процедуры проведения или записи объекта, если изменения нужны только для интерактивного отображения пользователю.
Если для бизнес-процесса мгновенное интерактивное отображение значения в новой колонке на клиенте не критично, а данные необходимы для корректного проведения документа, формирования отчетов или выгрузки во внешние системы, то идеальным решением будет перенос расчета на серверный уровень объекта.
Мы можем перехватить событие объекта ПередЗаписью в расширении модуля менеджера или модуля объекта документа. Это гарантирует, что расчет выполнится всегда, вне зависимости от того, как был изменен документ — интерактивно пользователем через форму, программно через групповую обработку или при обмене данными через API.
Проанализируем пример кода в модуле объекта расширения:
&Перед("ПередЗаписью")
Процедура Расш1_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, не выполняется ли обмен данными, при котором расчеты могут быть нежелательны
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Обходим строки табличной части и производим расчет
Для Каждого СтрокаТЧ Из Товары Цикл
// Сумма в базе гарантированно заполнена
СтрокаТЧ.НашаКолонка = СтрокаТЧ.Сумма * 0.20;
КонецЦикла;
КонецПроцедуры
Преимущества этого метода: Абсолютная надежность данных. Этот код сработает даже при загрузке документов из внешних файлов (Excel, XML, JSON), когда визуальная форма документа вообще не открывается на экране пользователя.
Для качественного решения задачи отслеживания программных изменений в ячейках таблиц при разработке расширений важно помнить:
ПриИзменении предназначено только для отслеживания непосредственных физических действий пользователя (ввода с клавиатуры или выбора из списка).&После.ПередЗаписью модуля объекта.