При разработке учетных документов в 1С часто возникает задача автоматического расчета итоговых показателей шапки на основании данных табличной части и дополнительных реквизитов. Начинающие разработчики нередко сталкиваются с ситуацией, когда итоговый реквизит либо не пересчитывается при редактировании строк, либо берет не те слагаемые, либо падает с ошибками контекста выполнения («Клиент/Сервер»).
Разберем по шагам, как правильно организовать подсчет сумм документа на управляемой форме, выясним, почему не срабатывал пересчет, и рассмотрим способы отображения итоговых данных в форме списка документов.
Рассмотрим типовой сценарий: в документе есть числовой реквизит шапки ЦенаРаботыБезУчетаМатериалов, табличная часть ПереченьМатериалов с колонкой ЦенаЗаЕдиницу и результирующий реквизит шапки ОбщаяСтоимостьРаботы.
Проанализируем исходный код, с которым возникли сложности:
&НаКлиенте
Процедура ЦенаРаботыБезУчетаМатериаловПриИзменении(Элемент)
РасчетСуммы();
КонецПроцедуры
&НаКлиенте
Процедура ПереченьМатериаловИтогЦенаЗаЕдиницуПриИзменении(Элемент)
РасчетСуммы();
КонецПроцедуры
&НаКлиенте
Процедура РасчетСуммы()
Объект.ОбщаяСтоимостьРаботы = Объект.СтоимостьМатериалов + Объект.ПереченьМатериалов.Итог("ЦенаЗаЕдиницу");
КонецПроцедуры
В данном фрагменте допущено сразу несколько критических ошибок:
Объект.СтоимостьМатериалов вместо реквизита шапки Объект.ЦенаРаботыБезУчетаМатериалов.ПереченьМатериаловИтогЦенаЗаЕдиницуПриИзменении был назначен не на колонку редактируемой строки, а на элемент подвала (итога) таблицы. Поля подвала в 1С служат для отображения и не генерируют событие ПриИзменении при пользовательском вводе в строках.Итог(): Существует заблуждение, что метод Итог() работает исключительно на сервере. Разберем этот нюанс подробнее.В платформе «1С:Предприятие 8.3» поведение метода зависит от типа объекта, к которому идет обращение:
ДокументОбъект): Табличная часть имеет тип ТабличнаяЧасть. Для нее метод Итог() доступен только на сервере, в толстом клиенте или внешнем соединении.Объект): Табличная часть представлена типом ДанныеФормыКоллекция. Для этого типа метод Итог("ИмяКолонки") полноценно доступен на тонком клиенте (&НаКлиенте).Таким образом, для пересчета итога в форме документа нет необходимости делать серверный вызов. Расчет прямо на клиенте работает мгновенно и не создает лишней сетевой нагрузки.
Разберем реализацию правильного алгоритма.
Шаг 1. Создаем единую клиентскую процедуру расчета:
&НаКлиенте
Процедура РасчетСуммы()
// Подсчитываем итог по табличной части ПереченьМатериалов
СтоимостьМатериалов = Объект.ПереченьМатериалов.Итог("ЦенаЗаЕдиницу");
// Если в шапке есть отдельное поле для стоимости материалов, заполняем его
Объект.СтоимостьМатериалов = СтоимостьМатериалов;
// Рассчитываем общую стоимость с учетом работы
Объект.ОбщаяСтоимостьРаботы = Объект.ЦенаРаботыБезУчетаМатериалов + СтоимостьМатериалов;
КонецПроцедуры
Шаг 2. Назначаем обработчики событий на элементы формы:
Чтобы сумма всегда оставалась актуальной, вызов процедуры РасчетСуммы() необходимо привязать ко всем элементам, влияющим на результат:
ЦенаРаботыБезУчетаМатериалов в палитре свойств находим событие ПриИзменении и привязываем процедуру:
&НаКлиенте
Процедура ЦенаРаботыБезУчетаМатериаловПриИзменении(Элемент)
РасчетСуммы();
КонецПроцедуры
ПереченьМатериалов, находим колонку ЦенаЗаЕдиницу (и Количество, если оно участвует в сумме), переходим в ее свойства и в событии ПриИзменении указываем:
&НаКлиенте
Процедура ПереченьМатериаловЦенаЗаЕдиницуПриИзменении(Элемент)
РасчетСуммы();
КонецПроцедуры
ПереченьМатериалов (не колонок, а корневого элемента таблицы формы) обязательно назначаем обработчик на событие ПослеУдаления. Если этого не сделать, при удалении пользователем строки сумма документа не пересчитается:
&НаКлиенте
Процедура ПереченьМатериаловПослеУдаления(Элемент)
РасчетСуммы();
КонецПроцедуры
Выясним, как вывести рассчитанную стоимость в динамический список документов.
Вариант 1. Использование реквизита шапки (рекомендуемый):
Если в объекте метаданных создан реквизит документа СтоимостьМатериалов или ОбщаяСтоимостьРаботы, и он сохраняется при записи документа, достаточно открыть форму списка, найти в правом окне «Реквизиты» поле Список.СтоимостьМатериалов и перетащить его в элементы формы списка.
Вариант 2. Использование произвольного запроса в динамическом списке:
Если итоговая стоимость не хранится в шапке документа, а существует только в табличной части, отобразим ее динамически:
Список (тип ДинамическийСписок).
ВЫБРАТЬ
ДокументНаборкаРабот.Ссылка КАК Ссылка,
ДокументНаборкаРабот.ПометкаУдаления КАК ПометкаУдаления,
ДокументНаборкаРабот.Номер КАК Номер,
ДокументНаборкаРабот.Дата КАК Дата,
ДокументНаборкаРабот.Проведен КАК Проведен,
ДокументНаборкаРабот.ЦенаРаботыБезУчетаМатериалов КАК ЦенаРаботыБезУчетаМатериалов,
ЕСТЬNULL(ТаблицаИтогов.СуммаМатериалов, 0) КАК СтоимостьМатериалов,
ДокументНаборкаРабот.ЦенаРаботыБезУчетаМатериалов + ЕСТЬNULL(ТаблицаИтогов.СуммаМатериалов, 0) КАК ОбщаяСтоимость
ИЗ
Документ.НаборкаРабот КАК ДокументНаборкаРабот
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
ВложеннаяТЧ.Ссылка КАК Ссылка,
СУММА(ВложеннаяТЧ.ЦенаЗаЕдиницу) КАК СуммаМатериалов
ИЗ
Документ.НаборкаРабот.ПереченьМатериалов КАК ВложеннаяТЧ
СГРУППИРОВАТЬ ПО
ВложеннаяТЧ.Ссылка
) КАК ТаблицаИтогов
ПО ДокументНаборкаРабот.Ссылка = ТаблицаИтогов.Ссылка
СтоимостьМатериалов, ОбщаяСтоимость) станут доступны для добавления в элементы формы списка.Код управляемой формы обеспечивает удобство работы пользователя в диалоговом режиме. Однако, если документ будет создаваться или перезаполняться программно (например, при загрузке или создании на основании), клиентские обработчики не сработают.
Для надежности продублируем расчет итогов в модуле объекта в предопределенном событии ПередЗаписью:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Здесь мы работаем на сервере с типом ТабличнаяЧасть
СтоимостьМатериалов = ПереченьМатериалов.Итог("ЦенаЗаЕдиницу");
ОбщаяСтоимостьРаботы = ЦенаРаботыБезУчетаМатериалов + СтоимостьМатериалов;
КонецПроцедуры
Такой подход гарантирует корректность сумм в базе данных при любых сценариях записи и проведения документов.