Построение двумерных отчетов (кросс-таблиц или шахматок) в платформе 1С:Предприятие 7.7 — классическая задача, при решении которой разработчики часто сталкиваются со смещением колонок («расползанием» печатной формы вправо) или неверной группировкой показателей. Разберем типичные ошибки при реализации такого отчета и рассмотрим правильную методику вывода данных, где строками являются контрагенты и цены продажи, а столбцами — номенклатура (ТМЦ).
Проанализируем типовые архитектурные и логические ошибки, приводящие к некорректным результатам:
т.Цена = - д.ЦенаБезНДС; методически ошибочна. Отрицательными величинами при возврате должны становиться только количественные и суммовые показатели (Кво и Сумма). Цена является измерением (характеристикой группировки). Если сделать цену отрицательной, возврат не уменьшит оборот по исходной цене, а создаст отдельную некорректную строку.ТаблицаЗначений через т.НоваяСтрока() до проверки условий принадлежности контрагента и номенклатуры приводит к засорению таблицы пустыми или несоответствующими отбору записями. Фильтрацию обязательно выполняем до момента создания новой строки.НайтиЗначение() и т.УдалитьСтроку() внутри циклов вывода сбивает внутреннюю индексацию строк. Из-за смещения индексов данные пропускаются, а ячейки макета начинают бесконтрольно дублироваться и сдвигаться вправо.Свернуть() над уже свернутой таблицей без изменения состава колонок является избыточной операцией, не несущей практической пользы.Для гарантированно точного формирования кросс-таблицы разделим процесс на три независимых этапа:
Рассмотрим по шагам реализацию выборки данных и корректного формирования печатной формы.
Шаг 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 можно применить следующие подходы:
Группировка Контрагент;, Группировка Цена;, Группировка ТМЦ; вместо прямого перебора документов выборкой через ВыбратьДокументы() значительно снижает нагрузку на дисковую подсистему и сокращает время выполнения.ИндексированнаяТаблица позволяет организовать индексацию колонок по составному ключу, что обеспечивает практически мгновенный поиск O(log N) значений ячеек в момент вывода матрицы на печать.