Как в макете 1С посчитать итог по группе идентичных документов?

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

При разработке отчетов в 1С без использования Системы компоновки данных (СКД) программисты часто сталкиваются с необходимостью вывода итогов по группам строк. Например, когда в одном отчете выводится список товаров по нескольким накладным, и после каждой накладной нужно вывести строку «Итого по документу». Рассмотрим подробно, как реализовать этот функционал программно, избежав типичных ошибок вроде дублирования строк или неверного расчета сложных показателей (таких как ROI).

Метод 1: Использование переменной «Предыдущее значение»

Это самый эффективный метод с точки зрения производительности, так как он требует всего одного прохода по таблице значений. Основная идея заключается в том, что мы сравниваем текущий документ в цикле с документом из предыдущей итерации.

Разберем по шагам алгоритм реализации:

  1. Перед началом цикла инициализируем переменную ПредыдущийДок значением Неопределено и обнуляем счетчики итогов.
  2. Внутри цикла проверяем: если ПредыдущийДок не равен текущему и не является пустым, значит, группа строк по предыдущему документу закончилась.
  3. Выводим область макета с итогами, обнуляем счетчики для новой группы.
  4. В конце итерации цикла присваиваем переменной ПредыдущийДок значение текущего документа.

Проанализируем пример кода для этого метода:


ПредыдущийДок = Неопределено;
ИтогПоДокументу = 0;

Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
    
    // Проверяем, сменился ли документ
    Если ПредыдущийДок <> Неопределено И ПредыдущийДок <> СтрокаТЗ.Регистратор Тогда
        // Выводим секцию итога для ПРЕДЫДУЩЕГО документа
        ОбластьИтог = Макет.ПолучитьОбласть("ИтогПоДокументу");
        ОбластьИтог.Параметры.ТекстИтога = "Итого по " + СокрЛП(ПредыдущийДок);
        ОбластьИтог.Параметры.Количество = ИтогПоДокументу;
        ТабДок.Вывести(ОбластьИтог);
        
        // Сбрасываем счетчик для следующей группы
        ИтогПоДокументу = 0;
    КонецЕсли;

    // Выводим основную строку данных
    ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
    // ... заполнение параметров строки ...
    ТабДок.Вывести(ОбластьСтрока);

    // Накапливаем итоги и фиксируем текущий документ
    ИтогПоДокументу = ИтогПоДокументу + СтрокаТЗ.Количество;
    ПредыдущийДок = СтрокаТЗ.Регистратор;
    
КонецЦикла;

// Важно: выводим итог по самому последнему документу после завершения цикла
Если ПредыдущийДок <> Неопределено Тогда
    ОбластьИтог = Макет.ПолучитьОбласть("ИтогПоДокументу");
    // ... заполнение параметров ...
    ТабДок.Вывести(ОбластьИтог);
КонецЕсли;

Метод 2: Двухуровневый обход с группировкой таблицы значений

Если логика отчета сложная (например, нужно считать количество уникальных позиций внутри документа или сложные коэффициенты), удобнее использовать вложенные циклы. Выясним причину, почему это иногда приводит к дубликатам: чаще всего разработчики забывают убрать внешний обход всей таблицы или некорректно фильтруют данные.

Рассмотрим правильную структуру вложенных циклов:

  1. Создаем копию основной таблицы значений и сворачиваем ее по колонке документа, чтобы получить список уникальных регистраторов.
  2. Запускаем внешний цикл по этой свернутой таблице.
  3. Внутри внешнего цикла получаем массив строк из основной таблицы, относящихся только к текущему документу, используя метод НайтиСтроки().
  4. Запускаем внутренний цикл по найденным строкам для вывода детальных данных.
  5. После внутреннего цикла выводим область итога.

Посмотрим на пример реализации:


// Получаем список уникальных документов
ТЗ_Документы = ТЗ_Данные.Скопировать();
ТЗ_Документы.Свернуть("РегистраторДокументПродажи");

Для Каждого СтрокаДок Из ТЗ_Документы Цикл
    
    // Фильтруем детальные записи по текущему документу
    ПараметрыОтбора = Новый Структура("РегистраторДокументПродажи", СтрокаДок.РегистраторДокументПродажи);
    ДетальныеСтроки = ТЗ_Данные.НайтиСтроки(ПараметрыОтбора);
    
    СуммаПоДоку = 0;
    
    Для Каждого СтрДанные Из ДетальныеСтроки Цикл
        // Выводим детальную информацию
        ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
        ЗаполнитьЗначенияСвойств(ОбластьСтрока.Параметры, СтрДанные);
        ТабДок.Вывести(ОбластьСтрока);
        
        СуммаПоДоку = СуммаПоДоку + СтрДанные.Сумма;
    КонецЦикла;
    
    // Выводим итог по группе
    ОбластьИтог = Макет.ПолучитьОбласть("ИтогПоГруппе");
    ОбластьИтог.Параметры.ИтогСумма = СуммаПоДоку;
    ТабДок.Вывести(ОбластьИтог);
    
КонецЦикла;

Особенности расчета ROI и других расчетных показателей

Проанализируем ситуацию, когда в итогах нужно вывести расчетный показатель, например ROI (Return on Investment). Распространенная ошибка — суммировать значения ROI из каждой строки. Математически это неверно.

Чтобы посчитать ROI по группе документов, мы должны сначала просуммировать все составляющие формулы по этой группе, и только потом производить деление. Например, если ROI = (Выручка - Себестоимость) / Себестоимость * 100, то для итога по документу формула будет выглядеть так:

ИтогROI = (Сумма(Выручка) - Сумма(Себестоимость)) / Сумма(Себестоимость) * 100.

Для реализации этого в коде 1С удобно использовать метод Итог() у таблицы значений. Если мы используем Метод 2 (с вложенными циклами), то расчет будет выглядеть так:


// Создаем временную таблицу для строк текущего документа
ТЗ_ТекущийДок = ТЗ_Данные.Скопировать(ДетальныеСтроки);

СуммаЗаказа = ТЗ_ТекущийДок.Итог("СуммаЗаказа");
СуммаСкидки = ТЗ_ТекущийДок.Итог("СуммаСкидки");

Если СуммаСкидки <> 0 Тогда
    ИтогROI = (СуммаЗаказа - СуммаСкидки) / СуммаСкидки * 100;
Иначе
    ИтогROI = 0;
КонецЕсли;

Визуальное оформление: группировка строк в Табличном Документе

Чтобы отчет был удобным для пользователя, мы можем добавить возможность сворачивать и разворачивать итоги. Для этого используются встроенные методы 1С НачатьГруппуСтрок() и ЗакончитьГруппуСтрок().

Разберем, как это добавить в код:


Для Каждого СтрокаДок Из ТЗ_Документы Цикл
    
    // Перед выводом детальных строк начинаем группу
    ТабДок.НачатьГруппуСтрок("ГруппаДокумента", Ложь); // Ложь - группа по умолчанию развернута
    
    Для Каждого СтрДанные Из ДетальныеСтроки Цикл
        // Вывод строк...
    КонецЦикла;
    
    ТабДок.ЗакончитьГруппуСтрок();
    
    // Вывод итога после завершения группы
    ТабДок.Вывести(ОбластьИтог);
    
КонецЦикла;

Важные рекомендации:

Таким образом, для подсчета итогов по идентичным документам в макете лучше всего использовать либо метод сравнения с предыдущим значением (для простых итогов), либо метод предварительной группировки таблицы (для сложных расчетов). Это обеспечит чистоту кода и корректность математических вычислений в отчете.

← На главную