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