При разработке отчетов в среде 1С:Предприятие часто возникает задача вывода данных в виде "шахматки", где по горизонтали располагаются даты, а в конце определенного периода (например, недели) необходимо вывести промежуточный итог. Рассмотрим типичные ошибки, которые возникают при ручном формировании таких отчетов в коде, и разберем наиболее современные и эффективные способы решения этой задачи с использованием Системы компоновки данных (СКД).
Разберем ситуацию, когда программист пытается накопить сумму за неделю внутри цикла обхода дат. Основная проблема часто заключается в неверном управлении переменной-счетчиком. Проанализируем представленный фрагмент кода:
Пока ТекДата <= НачалоДня(КонецПериода) Цикл
Если ТекДата = СтрНоменклатура.ДатаОтгрузки Тогда
СтрокаПериода.Параметры.Количество = СтрНоменклатура.Количество;
Иначе
СтрокаПериода.Параметры.Количество = 0;
КонецЕсли;
ТабДок.Присоединить(СтрокаПериода);
СуммаНедели = 0; // ОШИБКА: Обнуление происходит на каждом шаге цикла (каждый день)
СуммаНедели = СуммаНедели + СтрНоменклатура.Количество;
Если ДеньНедели(ТекДата) = 7 Тогда
ОбластьИтог.Параметры.ИтогЗаНеделю = СуммаНедели;
ТабДок.Присоединить(ОбластьИтог);
СуммаНедели = 0;
КонецЕсли;
ТекДата = ТекДата + 60*60*24;
КонецЦикла;
Выясним причину, по которой данный код работает некорректно. На каждой итерации цикла (каждый день) программа заново устанавливает СуммаНедели в значение 0, а затем прибавляет к нему количество текущего дня. В результате, когда наступает воскресенье (ДеньНедели(ТекДата) = 7), в переменной СуммаНедели оказывается не сумма за 7 дней, а только значение за последний день недели. Важно помнить: инициализация переменной итога должна происходить за пределами цикла накопления или строго после вывода результата за предыдущий период.
Для того чтобы правильно посчитать итог, нам необходимо изменить логику обнуления переменной. Рассмотрим исправленный вариант алгоритма по шагам:
СуммаНедели перед началом цикла по датам.Посмотрим на пример правильной реализации:
СуммаНедели = 0; // Инициализация перед циклом
ТекДата = НачалоДня(НачалоПериода);
Пока ТекДата <= НачалоДня(КонецПериода) Цикл
// Логика определения количества за день
КоличествоЗаДень = 0;
Если ТекДата = СтрНоменклатура.ДатаОтгрузки Тогда
КоличествоЗаДень = СтрНоменклатура.Количество;
КонецЕсли;
СтрокаПериода.Параметры.Количество = КоличествоЗаДень;
ТабДок.Присоединить(СтрокаПериода);
// Накопление итога
СуммаНедели = СуммаНедели + КоличествоЗаДень;
// Проверка на конец недели или конец всего периода отчета
Если ДеньНедели(ТекДата) = 7 Или ТекДата = НачалоДня(КонецПериода) Тогда
ОбластьИтог.Параметры.ИтогЗаНеделю = СуммаНедели;
ТабДок.Присоединить(ОбластьИтог);
// Обнуление ТОЛЬКО после вывода итога
СуммаНедели = 0;
КонецЕсли;
ТекДата = ТекДата + 86400; // Добавляем сутки
КонецЦикла;
Хотя ручной вывод через ТабДок.Присоединить() понятен начинающим разработчикам, он крайне неэффективен при работе с большими объемами данных и сложными группировками. В современной практике 1С для таких задач следует использовать СКД. Она автоматически берет на себя расчет итогов, дополнение периодов (вывод нулей в пустые дни) и формирование структуры колонок.
Рассмотрим, как реализовать подобный отчет в СКД без написания сложного кода в модуле:
НАЧАЛОПЕРИОДА(ДатаОтгрузки, НЕДЕЛЯ).Количество в список ресурсов с функцией СУММА(Количество).Грузополучатель, Заказ, Номенклатура.ДатаОтгрузки (с типом дополнения "День", чтобы видеть пустые даты).НеделяОтгрузки (созданному в запросе).Проанализируем преимущества этого метода: СКД сама создаст вложенные таблицы. Группировка "Неделя" будет содержать внутри себя группировку "День". При выводе отчета система автоматически сформирует колонки с днями, а рядом — итоговую колонку за неделю, просуммировав все входящие в нее ресурсы.
В исходном коде автора часто встречается метод ТабДанных.Скопировать() и Свернуть() внутри вложенных циклов. Это является "антипаттерном" программирования в 1С. Каждый раз, когда вы вызываете Свернуть() для большой таблицы в цикле, вы заставляете сервер выполнять колоссальный объем лишней работы.
Проанализируем ситуацию: если у вас 100 грузополучателей, по каждому 10 заказов, а в каждом заказе 10 товаров — внутренний код выполнится 10 000 раз. Копирование таблицы из 1000 строк 10 000 раз приведет к зависанию отчета.
Рекомендация: Используйте возможности языка запросов. Группируйте данные прямо в запросе или используйте обход результата запроса по группировкам:
ВыборкаГрузополучатель = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаГрузополучатель.Следующий() Цикл
// Вывод строки контрагента
ВыборкаЗаказ = ВыборкаГрузополучатель.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаЗаказ.Следующий() Цикл
// Вывод строки заказа
// ... и так далее
КонецЦикла;
КонецЦикла;
Этот метод работает в десятки раз быстрее, чем постоянная манипуляция с ТаблицаЗначений в памяти.
Часто заказчики просят выводить в отчете все дни месяца, даже если в эти дни не было отгрузок. Если просто делать запрос к документам, пустые дни не попадут в выборку. Чтобы решить эту проблему в ручном коде, мы используем цикл Пока ТекДата <= КонецПериода, как это сделал автор. Однако, чтобы найти данные для конкретной даты, лучше заранее подготовить Соответствие или Индексированную таблицу значений, где ключом будет дата, а значением — структура с данными.
В СКД эта задача решается установкой свойства "Тип дополнения" в значение "День" для группировки периода. Система сама "дорисует" недостающие даты в табличном документе и проставит в них нулевые значения ресурсов.
Мы рассмотрели два подхода к расчету еженедельных итогов. Программный метод требует строгой дисциплины в управлении переменными (своевременное обнуление накопленных сумм). Метод с использованием СКД является более предпочтительным, так как минимизирует количество ошибок и позволяет легко изменять структуру отчета (например, добавить итоги за месяц или квартал) простым изменением настроек, не затрагивая программный код.