При разработке отчетов в системе 1С:Предприятие 7.7 программисты часто сталкиваются с задачей вывода данных из заполненной ТаблицыЗначений. Если данные получены не прямым запросом, который имеет встроенные механизмы группировок, а собраны программно в таблицу, то вывод иерархии (например: Проект -> Статья затрат -> Подразделение) с подсчетом промежуточных итогов становится нетривиальной задачей.
Рассмотрим проблему, с которой столкнулся автор темы: есть плоская таблица с данными, и попытка вывести её с группировками через один общий цикл Для приводит к тому, что итоги "съезжают", дублируются или не рассчитываются вовсе. Это происходит из-за сложности отслеживания момента смены группировок (начала нового проекта или статьи) внутри одного линейного прохода.
Мы разберем, почему подход с одним циклом сложен в реализации, и детально рассмотрим два надежных метода решения этой задачи: метод "Свертки" таблиц и метод "Вложенных циклов".
В исходном коде используется попытка отследить изменение значений (например, Если текПодр <> прПодр) внутри одного цикла перебора строк. Такой алгоритм называется "Control Break" (разрыв по условию). Он вполне работоспособен, но требует соблюдения строжайших условий, которые часто упускаются:
<> для последней строки уже не сработает.Из-за сложности отладки такого "монстра" в 1С 7.7 рекомендуется использовать более структурированные подходы.
Этот метод, предложенный в обсуждении, является одним из самых надежных и простых для понимания. Идея заключается в том, чтобы не считать итоги "на лету" во время вывода, а заранее подготовить вспомогательные таблицы значений, где эти итоги уже посчитаны.
В платформе 1С 7.7 у объекта ТаблицаЗначений есть мощный метод Свернуть(). Он позволяет схлопнуть одинаковые строки, просуммировав числовые показатели.
Разберем алгоритм по шагам:
Шаг 1. Подготовка исходной таблицы
Предположим, у нас есть исходная таблица ТЗ_Исходная с колонками: Проект, Статья, Подразделение, Сумма, Количество.
Шаг 2. Создание таблиц для группировок
Нам нужно создать отдельные таблицы для каждого уровня группировки. Если у нас три уровня (Проект -> Статья -> Подразделение), нам понадобятся соответствующие таблицы.
// Создаем таблицу для верхнего уровня (Проекты)
ТЗ_Проекты = СоздатьОбъект("ТаблицаЗначений");
ТЗ_Исходная.Выгрузить(ТЗ_Проекты);
// Сворачиваем по Проекту, суммируем ресурсы
ТЗ_Проекты.Свернуть("Проект", "Сумма, Количество");
ТЗ_Проекты.Сортировать("Проект");
// Создаем таблицу для второго уровня (Проекты + Статьи)
ТЗ_Статьи = СоздатьОбъект("ТаблицаЗначений");
ТЗ_Исходная.Выгрузить(ТЗ_Статьи);
// Сворачиваем по паре Проект+Статья
ТЗ_Статьи.Свернуть("Проект, Статья", "Сумма, Количество");
ТЗ_Статьи.Сортировать("Проект, Статья");
// Исходная таблица будет служить детальными записями
ТЗ_Исходная.Сортировать("Проект, Статья, Подразделение");
Теперь у нас есть ТЗ_Проекты, где каждая строка — это уникальный проект с уже готовой общей суммой, и ТЗ_Статьи, где посчитаны суммы по статьям внутри проектов.
Шаг 3. Организация вложенных циклов вывода
Вместо одного цикла мы сделаем "матрешку". Это гарантирует, что структура отчета не нарушится.
// 1. Цикл по Проектам
ТЗ_Проекты.ВыбратьСтроки();
Пока ТЗ_Проекты.ПолучитьСтроку() = 1 Цикл
// Запоминаем текущий проект
ТекПроект = ТЗ_Проекты.Проект;
// Выводим секцию с заголовком и итогами Проекта
// Итоги берем прямо из ТЗ_Проекты - они там уже посчитаны!
ПечСуммаПроекта = ТЗ_Проекты.Сумма;
Таб.ВывестиСекцию("ЗаголовокПроекта");
// 2. Цикл по Статьям (фильтруем только статьи текущего проекта)
// В 7.7 нет простого отбора, поэтому перебираем и проверяем условие
// Или используем метод НайтиЗначение, если знаем как оптимизировать
ТЗ_Статьи.ВыбратьСтроки();
Пока ТЗ_Статьи.ПолучитьСтроку() = 1 Цикл
// Отсекаем чужие проекты
Если ТЗ_Статьи.Проект <> ТекПроект Тогда
Продолжить;
КонецЕсли;
ТекСтатья = ТЗ_Статьи.Статья;
// Выводим секцию Статьи с готовыми итогами
ПечСуммаСтатьи = ТЗ_Статьи.Сумма;
Таб.ВывестиСекцию("ЗаголовокСтатьи");
// 3. Цикл по детальным записям (Подразделениям)
ТЗ_Исходная.ВыбратьСтроки();
Пока ТЗ_Исходная.ПолучитьСтроку() = 1 Цикл
// Фильтр: должны совпасть и Проект, и Статья
Если (ТЗ_Исходная.Проект <> ТекПроект) ИЛИ (ТЗ_Исходная.Статья <> ТекСтатья) Тогда
Продолжить;
КонецЕсли;
// Вывод детальной строки
ПечПодразделение = ТЗ_Исходная.Подразделение;
ПечСумма = ТЗ_Исходная.Сумма;
Таб.ВывестиСекцию("Строка");
КонецЦикла; // Конец деталей
// Если нужен подвал статьи
Таб.ВывестиСекцию("ПодвалСтатьи");
КонецЦикла; // Конец статей
// Если нужен подвал проекта
Таб.ВывестиСекцию("ПодвалПроекта");
КонецЦикла; // Конец проектов
Преимущества метода:
Свернуть очень быстр).Если создание копий таблиц кажется избыточным (например, таблица очень большая и память ограничена), можно использовать метод одного прохода, но с правильной архитектурой. Главный секрет здесь — предварительная сортировка.
В 1С 7.7 этот метод часто реализуется через перебор строк и сравнение с предыдущим значением, но важно правильно расставить вывод секций.
// ОБЯЗАТЕЛЬНО сортируем таблицу
ТбЗн.Сортировать("Проект, ЭлементыЗатрат, Подразделения");
// Инициализируем переменные для хранения предыдущих значений
ПредПроект = "";
ПредСтатья = "";
ПредПодразделение = "";
// Переменные для итогов
ИтогПроект = 0;
ИтогСтатья = 0;
ТбЗн.ВыбратьСтроки();
Пока ТбЗн.ПолучитьСтроку() = 1 Цикл
// Проверяем смену старшей группировки (Проект)
Если (ТбЗн.Проект <> ПредПроект) И (ПустоеЗначение(ПредПроект) = 0) Тогда
// Проект изменился, значит старый закончился.
// Сначала закрываем младшие группы, если они открыты
Таб.ВывестиСекцию("ИтогСтатьи");
// Выводим итог старого проекта
Таб.ВывестиСекцию("ИтогПроекта");
// Сбрасываем итоги
ИтогПроект = 0;
ИтогСтатья = 0;
// Обновляем "предыдущие" значения, чтобы начать новые группы
ПредСтатья = ""; // Сброс статьи, т.к. новый проект
КонецЕсли;
// Проверяем смену средней группировки (Статья)
// Условие: Статья изменилась ИЛИ изменился Проект (выше)
Если (ТбЗн.ЭлементыЗатрат <> ПредСтатья) И (ПустоеЗначение(ПредСтатья) = 0) Тогда
Таб.ВывестиСекцию("ИтогСтатьи");
ИтогСтатья = 0;
КонецЕсли;
// Если это начало новой группы - выводим заголовки
Если ТбЗн.Проект <> ПредПроект Тогда
ПечПроект = ТбЗн.Проект;
Таб.ВывестиСекцию("ЗаголовокПроекта");
ПредПроект = ТбЗн.Проект;
КонецЕсли;
Если ТбЗн.ЭлементыЗатрат <> ПредСтатья Тогда
ПечСтатья = ТбЗн.ЭлементыЗатрат;
Таб.ВывестиСекцию("ЗаголовокСтатьи");
ПредСтатья = ТбЗн.ЭлементыЗатрат;
КонецЕсли;
// Выводим строку
Таб.ВывестиСекцию("Строка");
// Накапливаем итоги
ИтогПроект = ИтогПроект + ТбЗн.Сумма;
ИтогСтатья = ИтогСтатья + ТбЗн.Сумма;
КонецЦикла;
// ВАЖНО: Вывод "хвостов" после цикла
// После последней строки цикл закончился, но итоги последней группы не выведены
Если ТбЗн.КоличествоСтрок() > 0 Тогда
Таб.ВывестиСекцию("ИтогСтатьи");
Таб.ВывестиСекцию("ИтогПроекта");
КонецЕсли;
Этот код сложнее в написании, чем первый вариант, и требует внимательности к условиям сброса переменных. Любая ошибка в логике Если приведет к "съезжанию" данных.
В вопросе автора использовался код СокрЛП(Формат(Сумма, стФорматС)). Это правильный подход для 1С 7.7. Напомним, что:
Формат() преобразует число в строку с разделителями триад и фиксированным количеством знаков после запятой (например, "10 000.00").СокрЛП() удаляет лишние пробелы по краям, что полезно для центрирования в ячейках таблицы.Стоит также отметить, что если данные для таблицы берутся из регистров или документов, то самым профессиональным решением в 1С 7.7 является использование объекта Запрос.
Запрос автоматически умеет:
Группировка Проект;).Функция СуммаС = Сумма(Сумма);).Обход результатов запроса аналогичен методу вложенных циклов, но работает быстрее, чем перебор ТаблицыЗначений.
Пока Запрос.Группировка("Проект") = 1 Цикл
// Вывод шапки проекта
Пока Запрос.Группировка("ЭлементыЗатрат") = 1 Цикл
// Вывод шапки статьи
Пока Запрос.Группировка("Подразделения") = 1 Цикл
// Вывод строки
КонецЦикла;
// Вывод итога статьи (Запрос.СуммаС здесь содержит итог по текущей группировке)
КонецЦикла;
// Вывод итога проекта
КонецЦикла;
Для исправления проблемы с циклами в отчете 1С 7.7, показанной в теме форума, наиболее эффективным решением будет отказ от одного сложного цикла с проверкой условий в пользу:
Оба метода гарантируют корректный расчет и вывод итоговых сумм по проектам, статьям и подразделениям.