Разберем классическую задачу, с которой сталкивается каждый разработчик 1С: в документе есть одна или несколько табличных частей (например, товары, услуги, лимиты), и нам необходимо вывести и сохранить общую сумму всех строк в специальном реквизите шапки документа. На первый взгляд, задача тривиальная, но новички часто допускают серьезные архитектурные ошибки, которые ведут к избыточным вызовам сервера, зависаниям интерфейса и нарушению целостности данных.
Проанализируем ситуацию по шагам и выясним, в каких процедурах и модулях правильно выполнять суммирование, стоит ли вообще хранить эту сумму в базе данных и как избежать распространенных ошибок.
Если наша единственная цель — показать пользователю общую сумму документа на экране для удобства, в современных управляемых формах 1С для этого можно вообще не писать программный код. Платформа берет эту задачу на себя.
Рассмотрим, как это сделать:
ПутьКДанным.Объект.ИмяТабличнойЧасти.ИтогИмяКолонки (например, Объект.Лимиты.ИтогСуммаСчета).Платформа 1С будет «на лету» пересчитывать это значение при любых изменениях (вводе новых сумм, удалении или добавлении строк) полностью на клиенте. Никаких дополнительных обращений к серверу не произойдет.
Часто разработчики задают вопрос: зачем плодить сущности и заводить отдельный реквизит в шапке, если сумму всегда можно получить динамически из табличных частей? Если сумма нужна только на форме — это справедливое замечание. Однако в типовых конфигурациях 1С (ERP, УТ, БП) хранение итоговой суммы в шапке документа — это строгий стандарт.
Разберем причину такого подхода. Речь идет о намеренной денормализации данных ради оптимизации производительности СУБД. При построении сложных отчетов, журналов документов или динамических списков системе потребуется вывести общую сумму документа. Если реквизита в шапке нет, платформе придется каждый раз выполнять тяжелое соединение таблиц с группировкой и агрегатной функцией расчета сумм по всем строкам табличной части. Это существенно замедлит работу системы на больших объемах данных. Чтение готового числа из основной таблицы документа работает в десятки раз быстрее.
Если бизнес-логика требует хранения суммы в реквизите объекта, мы должны обеспечить ее синхронный пересчет при редактировании документа. В обсуждении мы видели пример кода, где разработчик реализовал расчет следующим образом:
&НаКлиенте
Процедура ЛимитыСуммаСчетаПриИзменении(Элемент)
ИтогПоСумме();
КонецПроцедуры
&НаСервере
Процедура ИтогПоСумме()
Объект.Сумма = Объект.Лимиты.Итог("СуммаСчета") + Объект.ЗаявкиНаРасходование.Итог("Сумма");
КонецПроцедуры
Проанализируем этот код. Использование директивы &НаСервере для такого простого расчета — это грубая ошибка производительности. Тип ДанныеФормыКоллекция, которым представлена табличная часть на управляемой форме, поддерживает метод Итог() непосредственно на клиенте. Вызов сервера при каждом изменении цифры в ячейке заставит систему гонять контекст формы по сети туда-обратно, вызывая микро-фризы (зависания интерфейса).
Код должен быть полностью клиентским:
&НаКлиенте
Процедура ИтогПоСумме()
Объект.Сумма = Объект.Лимиты.Итог("СуммаСчета") + Объект.ЗаявкиНаРасходование.Итог("Сумма");
КонецПроцедуры
Эту процедуру необходимо вызывать в событиях ПриИзменении для колонок сумм, а также при добавлении и удалении строк табличных частей, чтобы пользователь всегда видел перед глазами актуальную цифру.
Ограничиваться расчетом только в модуле формы категорически нельзя. Пользовательский интерфейс — не единственный способ создания и изменения данных. Документы могут генерироваться программно: загружаться через обмены данными (XML, JSON, OData), создаваться внешними обработками, перепроводиться регламентными заданиями. Во всех этих случаях форма документа вообще не открывается.
Если пересчет будет реализован только в форме, программно созданный документ запишется с пустой (нулевой) суммой в шапке, что приведет к ошибкам в учете и отчетах. Использовать событие ПриЗаписи для таких расчетов бесполезно, так как объект к этому моменту уже записан в базу данных, и менять его свойства поздно.
Посмотрим на идеальный паттерн гарантированного расчета — использование события ПередЗаписью в Модуле объекта документа:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Окончательный пересчет суммы перед физической записью в СУБД
Сумма = Лимиты.Итог("СуммаСчета") + ЗаявкиНаРасходование.Итог("Сумма");
КонецПроцедуры
Подведем итог оптимальной архитектуры суммирования:
&НаКлиенте на форме документа без обращений к серверу.ПередЗаписью, чтобы защитить систему от записи некорректных данных при программных интеграциях.