В legacy-системах на платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с необходимостью доработки типовых или самописных отчетов, построенных на механизме бухгалтерских итогов. Типичная задача — рассчитать процент использования материальных запасов за период, вывести этот показатель в печатную форму и предоставить пользователю возможность сортировки по нему.
В этой статье мы подробно разберем, как найти формулу расчета процента использования материалов в исходном коде, как обойти ограничение платформы 1С 7.7, не позволяющее сортировать строки непосредственно в печатной таблице, и как оптимизировать алгоритм, чтобы отчет не зависал на больших объемах данных.
Проанализируем исходный код процедуры, предоставленной автором темы. Логика отбора материалов, у которых процент использования за период составил менее 30% от остатка, скрывается в следующей строке кода:
Если (Ит.КО(3)) < ((Ит.СКД(3) - Ит.СКК(3)) * 0.3) Тогда
Рассмотрим детально, что означают эти функции объекта БухгалтерскиеИтоги:
Ит.КО(3) — Кредитовый Оборот по счету (в количественном выражении). Индекс 3 указывает платформе, что нас интересует именно количество, а не сумма (индекс 1 используется для рублевого эквивалента). В контексте учета материалов кредитовый оборот — это объем списания или расхода материала за выбранный период.Ит.СКД(3) - Ит.СКК(3) — расчет конечного остатка на складе. СКД(3) (Сальдо Конечное Дебетовое в количестве) минус СКК(3) (Сальдо Конечное Кредитовое в количестве) дает чистый количественный остаток материала на конец периода.0.3 — это математическое представление 30% лимита использования.Таким образом, условие отбирает те материалы, расход которых (кредитовый оборот) оказался строго меньше, чем 30% от их конечного остатка. Если мы хотим вывести точный процент использования материала в отдельную колонку отчета, нам потребуется следующая математическая формула:
Процент использования = (Расход за период / Конечный остаток) * 100%
В коде 1С 7.7 расчет этой величины с защитой от деления на ноль будет выглядеть так:
КонечныйОстаток = Ит.СКД(3) - Ит.СКК(3);
Расход = Ит.КО(3);
Если КонечныйОстаток = 0 Тогда
ПроцентИспользования = 0;
Иначе
ПроцентИспользования = Окр((Расход / КонечныйОстаток) * 100, 2);
КонецЕсли;
В платформе 1С 7.7 объект Таблица (выходной табличный документ) является исключительно средством отображения информации. В отличие от современных версий 1С (8.x), где у табличного документа есть встроенные методы сортировки, в «семерке» отсортировать уже выведенные в макет секции невозможно.
Единственный надежный и правильный способ решить эту задачу — применить промежуточную буферизацию данных в объект ТаблицаЗначений (ТЗ). Рассмотрим этот алгоритм по шагам:
ТаблицаЗначений и описываем ее структуру (колонки).Сортировать().ТаблицуЗначений в цикле и выводим строки в отчет.Посмотрим на практическую реализацию этого метода. Создадим процедуру подготовки данных и заполнения промежуточной таблицы значений:
// Создаем таблицу значений для накопления данных перед выводом
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("Склад", "Справочник.МестаХранения");
ТЗ.НоваяКолонка("МОЛ", "Справочник.Сотрудники");
ТЗ.НоваяКолонка("Материал", "Справочник.Материалы");
ТЗ.НоваяКолонка("НачальныйОстаток", "Число", 15, 3);
ТЗ.НоваяКолонка("Приход", "Число", 15, 3);
ТЗ.НоваяКолонка("Расход", "Число", 15, 3);
ТЗ.НоваяКолонка("КонечныйОстаток", "Число", 15, 3);
ТЗ.НоваяКолонка("Процент", "Число", 6, 2);
Теперь наполним созданную таблицу данных в цикле обхода выборки бухгалтерских итогов:
Ит.ВыбратьСубконто(ВидыСубконто.МестаХранения);
Пока Ит.ПолучитьСубконто(ВидыСубконто.МестаХранения) = 1 Цикл
ТекущийСклад = Ит.Субконто(ВидыСубконто.МестаХранения);
Ит.ВыбратьСубконто(ВидыСубконто.Сотрудники);
Пока Ит.ПолучитьСубконто(ВидыСубконто.Сотрудники) = 1 Цикл
ТекущийМОЛ = Ит.Субконто(ВидыСубконто.Сотрудники);
Ит.ВыбратьСубконто(ВидыСубконто.Материалы);
Пока Ит.ПолучитьСубконто(ВидыСубконто.Материалы) = 1 Цикл
ТекущийМатериал = Ит.Субконто(ВидыСубконто.Материалы);
// Исключаем группы материалов из детальных строк
Если ТекущийМатериал.ЭтоГруппа() = 1 Тогда
Продолжить;
КонецЕсли;
// Получаем количественные показатели
НачОст = Ит.СНД(3) - Ит.СНК(3);
Прих = Ит.ДО(3);
Раскх = Ит.КО(3);
КонОст = Ит.СКД(3) - Ит.СКК(3);
// Вычисляем процент использования
Если КонОст = 0 Тогда
Процент = 0;
Иначе
Процент = Окр((Раскх / КонОст) * 100, 2);
КонецЕсли;
// Добавляем запись в таблицу значений
ТЗ.НоваяСтрока();
ТЗ.Склад = ТекущийСклад;
ТЗ.МОЛ = ТекущийМОЛ;
ТЗ.Материал = ТекущийМатериал;
ТЗ.НачальныйОстаток = НачОст;
ТЗ.Приход = Прих;
ТЗ.Расход = Раскх;
ТЗ.КонечныйОстаток = КонОст;
ТЗ.Процент = Процент;
КонецЦикла;
КонецЦикла;
КонецЦикла;
После того как вся таблица заполнена, мы можем легко выполнить сортировку по возрастанию или убыванию процента с помощью одной команды. Символ «-» в параметре метода сортировки указывает системе на необходимость сортировать по убыванию:
// Сортируем таблицу по убыванию процента использования
ТЗ.Сортировать("Процент-");
Теперь нам остается только вывести накопленные и отсортированные данные в макет отчета:
Таб = СоздатьОбъект("Таблица");
Таб.ИсходнаяТаблица("Сформировать");
Таб.ВывестиСекцию("Заголовок");
Таб.ВывестиСекцию("Шапка");
нпп = 0;
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
нпп = нпп + 1;
// Передаем значения из ТЗ в переменные макета
ВыбСклад = ТЗ.Склад;
ВыбМОЛ = ТЗ.МОЛ;
ВыбМатериал = ТЗ.Материал;
НачальныйОстаток = ТЗ.НачальныйОстаток;
Приход = ТЗ.Приход;
Расход = ТЗ.Расход;
КонечныйОстаток = ТЗ.КонечныйОстаток;
ПроцентИспользования = ТЗ.Процент;
// Выводим строку в печатную форму
Таб.ВывестиСекцию("Строка");
КонецЦикла;
Таб.ВывестиСекцию("Подвал");
Таб.ТолькоПросмотр(1);
Таб.Показать("Отчет по использованию материалов");
Такой подход полностью решает задачу сортировки и гарантирует корректный вывод данных пользователю.
Если запустить данный отчет на реальной базе данных с большим объемом операций, система может «зависнуть» на несколько минут или даже часов. Выясним причину такого поведения платформы 1С 7.7.
Проблема кроется в конструкции вложенных циклов выборки субконто:
Пока Ит.ПолучитьСубконто(ВидыСубконто.МестаХранения) = 1 Цикл
Пока Ит.ПолучитьСубконто(ВидыСубконто.Сотрудники) = 1 Цикл
Пока Ит.ПолучитьСубконто(ВидыСубконто.Материалы) = 1 Цикл
Каждый раз при выполнении метода ПолучитьСубконто() на внутренних уровнях вложенности платформа 1С 7.7 совершает одиночные SQL-запросы (или обращения к DBF-файлам таблиц итогов) к базе данных. Если у вас в базе 10 складов, 50 материально ответственных лиц (МОЛ) и 1000 наименований материалов, общее количество потенциальных обращений к базе данных составит 10 * 50 * 1000 = 500 000 микрозапросов. Это неизбежно парализует работу системы.
Чтобы избежать этой проблемы, применим высокоэффективный прием — выгрузку всех бухгалтерских итогов в одну плоскую таблицу значений с помощью метода ВыгрузитьТаблицу(). Платформа выполнит один оптимизированный запрос к базе данных и мгновенно вернет нам заполненную таблицу значений.
Модернизируем наш алгоритм:
// Создаем объект БухгалтерскиеИтоги и выполняем запрос
Ит = СоздатьОбъект("БухгалтерскиеИтоги");
Ит.ИспользоватьСубконто(ВидыСубконто.МестаХранения,, 1);
Ит.ИспользоватьСубконто(ВидыСубконто.Сотрудники,, 1);
Ит.ИспользоватьСубконто(ВидыСубконто.Материалы, сзМатериалы, 1);
Если Ит.ВыполнитьЗапрос(выбНачПериода, ВыбКонПериода, списокСчетов) = 0 Тогда
Предупреждение("Не удалось рассчитать бухгалтерские итоги!");
Возврат;
КонецЕсли;
// Создаем пустую ТЗ и выгружаем в нее все итоги одним методом!
ТЗ_Итоги = СоздатьОбъект("ТаблицаЗначений");
// Параметры метода: ВыгрузитьТаблицу(ТЗ, ФлагГруппировок, ФлагПоказателей)
// ФлагГруппировок = 1 (выгружать детальные строки субконто)
// ФлагПоказателей = 1 (выгружать количественные и суммовые показатели)
Ит.ВыгрузитьТаблицу(ТЗ_Итоги, 1, 1);
Метод ВыгрузитьТаблицу() автоматически создаст ТЗ со структурой колонок, соответствующей субконто и показателям. Имена колонок для субконто будут иметь вид Субконто1, Субконто2, Субконто3, а показатели — СНД3 (Сальдо Начальное Дебет Количество), КО3 (Кредитовый оборот количество) и т.д.
Теперь нам достаточно пройтись по полученной плоской таблице в один быстрый проход в памяти компьютера, рассчитать процент использования и отсортировать ее:
// Добавим в выгруженную ТЗ колонку для расчета процента
ТЗ_Итоги.НоваяКолонка("Процент", "Число", 6, 2);
// Переименуем колонки для удобства обращения (необязательно, но полезно)
ТЗ_Итоги.УстановитьПараметрыКолонки("Субконто1", , , , "Склад");
ТЗ_Итоги.УстановитьПараметрыКолонки("Субконто2", , , , "МОЛ");
ТЗ_Итоги.УстановитьПараметрыКолонки("Субконто3", , , , "Материал");
// Рассчитываем проценты прямо в строках таблицы значений
ТЗ_Итоги.ВыбратьСтроки();
Пока ТЗ_Итоги.ПолучитьСтроку() = 1 Цикл
// Исключаем строки-группировки, если они выгрузились
Если ПустоеЗначение(ТЗ_Итоги.Материал) = 1 Тогда
Продолжить;
КонецЕсли;
Если ТЗ_Итоги.Материал.ЭтоГруппа() = 1 Тогда
Продолжить;
КонецЕсли;
// Вычисляем остаток: Дебетовое Сальдо Конечное - Кредитовое Сальдо Конечное (количественные колонки СКД3 и СКК3)
КонОст = ТЗ_Итоги.СКД3 - ТЗ_Итоги.СКК3;
Расход = ТЗ_Итоги.КО3; // Кредитовый оборот (количество)
Если КонОст <= 0 Тогда
ТЗ_Итоги.Процент = 0;
Иначе
ТЗ_Итоги.Процент = Окр((Расход / КонОст) * 100, 2);
КонецЕсли;
КонецЦикла;
// Фильтруем ТЗ, оставляя только нужные строки (например, где процент < 30)
// Или просто сортируем для вывода
ТЗ_Итоги.Сортировать("Процент-");
Использование метода ВыгрузитьТаблицу() вместо вложенных циклов выборки субконто сокращает время формирования отчета на крупных базах данных с нескольких десятков минут до нескольких секунд. Это связано с тем, что вся тяжелая аналитическая работа по объединению таблиц перекладывается на плечи СУБД (SQL Server) или оптимизированного внутреннего движка 1С DBF, а последующая обработка данных происходит в оперативной памяти компьютера без повторных обращений к диску.
Мы успешно решили задачу доработки отчета по использованию материалов в 1С 7.7. В ходе совместной работы мы:
ТаблицаЗначений.ВыгрузитьТаблицу(), которая ускоряет работу отчета в десятки раз.