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

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

Построение двумерных отчетов (кросс-таблиц или шахматок) в платформе 1С:Предприятие 7.7 — классическая задача, при решении которой разработчики часто сталкиваются со смещением колонок («расползанием» печатной формы вправо) или неверной группировкой показателей. Разберем типичные ошибки при реализации такого отчета и рассмотрим правильную методику вывода данных, где строками являются контрагенты и цены продажи, а столбцами — номенклатура (ТМЦ).

Анализ типичных ошибок в алгоритмах выборки и вывода

Проанализируем типовые архитектурные и логические ошибки, приводящие к некорректным результатам:

  1. Неверная обработка возвратов (отрицательная цена): Установка конструкции вида т.Цена = - д.ЦенаБезНДС; методически ошибочна. Отрицательными величинами при возврате должны становиться только количественные и суммовые показатели (Кво и Сумма). Цена является измерением (характеристикой группировки). Если сделать цену отрицательной, возврат не уменьшит оборот по исходной цене, а создаст отдельную некорректную строку.
  2. Наложение условий после добавления строк: Добавление строки в объект ТаблицаЗначений через т.НоваяСтрока() до проверки условий принадлежности контрагента и номенклатуры приводит к засорению таблицы пустыми или несоответствующими отбору записями. Фильтрацию обязательно выполняем до момента создания новой строки.
  3. Удаление строк внутри цикла обхода: Использование связки НайтиЗначение() и т.УдалитьСтроку() внутри циклов вывода сбивает внутреннюю индексацию строк. Из-за смещения индексов данные пропускаются, а ячейки макета начинают бесконтрольно дублироваться и сдвигаться вправо.
  4. Повторное сворачивание одной и той же таблицы: Вызов метода Свернуть() над уже свернутой таблицей без изменения состава колонок является избыточной операцией, не несущей практической пользы.

Правильная архитектура: разделение задачи на этапы

Для гарантированно точного формирования кросс-таблицы разделим процесс на три независимых этапа:

  1. Сбор сырых данных: выборка документов за период с проверкой проведенности и наложением фильтров.
  2. Подготовка матриц строк и колонок: формирование уникального списка колонок (ТМЦ), списка строк (Контрагент + Цена) и плоской таблицы итогов.
  3. Синхронный вывод в макет: обход строк и колонок с точечным позиционированием значений.

Реализация алгоритма через сопоставление ключей

Рассмотрим по шагам реализацию выборки данных и корректного формирования печатной формы.

Шаг 1. Получение и агрегация данных

Соберем данные документов в общую таблицу, предварительно отсекая ненужные позиции:


// Создаем структуру для сбора плоских данных
тзДанные = СоздатьОбъект("ТаблицаЗначений");
тзДанные.НоваяКолонка("Контрагент", "Справочник.Контрагенты");
тзДанные.НоваяКолонка("Цена", "Число", 16, 3);
тзДанные.НоваяКолонка("ТМЦ", "Справочник.ТМЦ");
тзДанные.НоваяКолонка("Кво", "Число", 16, 3);
тзДанные.НоваяКолонка("Сумма", "Число", 16, 2);

д = СоздатьОбъект("Документ");
д.ВыбратьДокументы(НачДата, КонДата);

Пока д.ПолучитьДокумент() = 1 Цикл
    Если (д.Вид() <> "РасходнаяНакладная") И (д.Вид() <> "ВозвратнаяНакладная") Тогда
        Продолжить;
    КонецЕсли;
    
    Если д.Проведен() = 0 Тогда
        Продолжить;
    КонецЕсли;
    
    // Проверка фильтра по контрагенту ДО обработки строк
    Если ВыбКонтрагент.Выбран() = 1 Тогда
        Если ВыбКонтрагент.ЭтоГруппа() = 1 Тогда
            Если д.Контрагент.ПринадлежитГруппе(ВыбКонтрагент) = 0 Тогда
                Продолжить;
            КонецЕсли;
        ИначеЕсли д.Контрагент <> ВыбКонтрагент Тогда
            Продолжить;
        КонецЕсли;
    КонецЕсли;
    
    Коэф = 1;
    Если д.Вид() = "ВозвратнаяНакладная" Тогда
        Коэф = -1;
    КонецЕсли;
    
    д.ВыбратьСтроки();
    Пока д.ПолучитьСтроку() = 1 Цикл
        Если д.ТМЦ.ВидТМЦ <> Перечисление.ВидыТМЦ.Продукция Тогда
            Продолжить;
        КонецЕсли;
        
        // Проверка фильтра по номенклатуре
        Если ВыбТМЦ.Выбран() = 1 Тогда
            Если ВыбТМЦ.ЭтоГруппа() = 1 Тогда
                Если д.ТМЦ.ПринадлежитГруппе(ВыбТМЦ) = 0 Тогда
                    Продолжить;
                КонецЕсли;
            ИначеЕсли д.ТМЦ <> ВыбТМЦ Тогда
                Продолжить;
            КонецЕсли;
        КонецЕсли;
        
        тзДанные.НоваяСтрока();
        тзДанные.Контрагент = д.Контрагент;
        тзДанные.Цена       = д.ЦенаБезНДС; // Цена всегда положительная!
        тзДанные.ТМЦ        = д.ТМЦ;
        тзДанные.Кво        = д.Кво * Коэф;
        тзДанные.Сумма      = д.СуммаСНДС * Коэф;
    КонецЦикла;
КонецЦикла;

// Сворачиваем итоги по трем измерениям
тзДанные.Свернуть("Контрагент, Цена, ТМЦ", "Кво, Сумма");

Шаг 2. Подготовка измерений кросс-таблицы

Сформируем список горизонтальных колонок и вертикальных строк отчета:


// 1. Формируем список колонок (уникальные ТМЦ)
тзКолонки = СоздатьОбъект("ТаблицаЗначений");
тзДанные.Выгрузить(тзКолонки);
тзКолонки.Свернуть("ТМЦ", "");
тзКолонки.Сортировать("ТМЦ+");

// 2. Формируем список строк (Контрагент + Цена)
тзСтроки = СоздатьОбъект("ТаблицаЗначений");
тзДанные.Выгрузить(тзСтроки);
тзСтроки.Свернуть("Контрагент, Цена", "");
тзСтроки.Сортировать("Контрагент+, Цена+");

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

Шаг 3. Вывод таблицы на печать

Выведем данные в макет, используя сочетание методов Таб.ВывестиСекцию() (для начала строки) и Таб.ПрисоединитьСекцию() (для колонок):


Таб = СоздатьОбъект("Таблица");
Таб.ИсходнаяТаблица("Сформировать");

// Вывод шапки таблицы
Таб.ВывестиСекцию("Шапка|Вертикаль_1");
тзКолонки.ВыбратьСтроки();
Пока тзКолонки.ПолучитьСтроку() = 1 Цикл
    ПечТМЦ = тзКолонки.ТМЦ;
    Таб.ПрисоединитьСекцию("Шапка|Вертикаль_2");
КонецЦикла;

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

Таб.ТолькоПросмотр(1);
Таб.Показать("Отчет по продажам");

Альтернативные способы оптимизации

Для ускорения формирования отчетов в высоконагруженных базах 1С 7.7 можно применить следующие подходы:

  1. Переход на выборку через объект «Запрос»: Использование встроенного языка запросов 1С 7.7 с секциями Группировка Контрагент;, Группировка Цена;, Группировка ТМЦ; вместо прямого перебора документов выборкой через ВыбратьДокументы() значительно снижает нагрузку на дисковую подсистему и сокращает время выполнения.
  2. Чтение регистров оперативного учета: Если в конфигурации ведется учет продаж через регистры (например, оборотный регистр «Продажи»), чтение агрегированных итогов регистра происходит в разы быстрее прямого сканирования документов.
  3. Использование внешних компонент (1С++): Применение класса ИндексированнаяТаблица позволяет организовать индексацию колонок по составному ключу, что обеспечивает практически мгновенный поиск O(log N) значений ячеек в момент вывода матрицы на печать.
← На главную