Циклические ссылки в отчёте: как заставить взаимозависимые показатели сойтись
Содержание
«Закрутить отчёт» в 1С — это заставить сойтись показатели, которые зависят друг от друга по кругу: сумма A опирается на сумму B, а B, в свою очередь, считается через A. Прямой расчёт в таком случае даёт неверные цифры или вовсе уходит в ошибку, поэтому отчёт приходится «прокручивать» несколько раз — до сходимости.
Короткий путь к решению: итерационный пересчёт (метод последовательных приближений) делают во встроенном языке, а не в вычисляемом поле схемы компоновки данных. СКД такую закольцовку не разрулит сама. Ниже разберём, откуда в 1С берётся циклическая ссылка в отчёте, как организовать многопроходный расчёт, где взять встречные обороты и как не уронить производительность на реальных объёмах.
Откуда в отчёте берётся циклическая ссылка
Взаимная зависимость появляется там, где объекты обслуживают друг друга. Классика — встречное распределение затрат вспомогательных подразделений:
- 🔥 котельная отпускает тепло ремонтному цеху;
- 🔧 ремонтный цех обслуживает оборудование котельной;
- полная себестоимость услуги котельной зависит от затрат ремонтного цеха — и наоборот.
Тот же узел встречается в бюджетировании (перекрёстные аллокации), в расчёте полной себестоимости по переделам с возвратами и в управленческих отчётах с каскадным распределением накладных.
Если считать «в лоб», порядок обработки решает всё: посчитаете котельную первой — в её сумму не попадут затраты ремонтного цеха, которые ещё не распределены. Результат зависит от очерёдности строк, а это и есть симптом закольцовки.
Циклическая ссылка — не ошибка данных, а свойство самой модели расчёта. Переставлять строки местами или менять сортировку бесполезно: пока зависимость взаимная, однопроходный расчёт корректного ответа не даст.
Что получится сделать средствами СКД, а что нет
Схема компоновки данных вычисляет поля и ресурсы в заранее определённом порядке. Внутри одной схемы поле не может ссылаться на само себя или на другое поле, которое в итоге ссылается обратно, — платформа либо выдаст ошибку компоновки, либо посчитает по незавершённым данным.
Поэтому разделение обязанностей такое:
- Расчёт взаимных сумм — во встроенном языке (в модуле отчёта или во внешней обработке).
- Представление готовых чисел — в СКД: группировки, итоги, оформление, отбор, расшифровки.
Готовый результат удобно передать в СКД через набор данных типа «Объект». Вы формируете таблицу значений в коде и подаёте её в компоновщик макета как внешний набор данных — тогда весь блок настроек, оформления и расшифровок остаётся штатным, а «кручение» остаётся за кадром.
// ВнешниеНаборыДанных — соответствие "имя набора" → таблица значений
ВнешниеНаборы = Новый Структура;
ВнешниеНаборы.Вставить("РаспределённыеЗатраты", ТаблицаРезультата);
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновки, Настройки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборы);
Имя набора данных «Объект» в схеме должно совпадать с ключом в структуре ВнешниеНаборы, а состав колонок таблицы — с полями набора. Несовпадение имени — самая частая причина пустого отчёта при таком подходе.
Итерационный пересчёт: метод последовательных приближений
Суть «кручения» — повторять расчёт до тех пор, пока цифры не перестанут меняться больше, чем на заданную точность. На первом проходе берём только прямые затраты, на каждом следующем добавляем встречное потребление по значениям предыдущего прохода.
Порядок действий:
- Собрать прямые затраты по подразделениям и доли встречного потребления в таблицы значений — одним запросом, до начала итераций.
- Задать точность (например, до копейки) и предел числа проходов, чтобы расчёт не завис.
- На каждом проходе пересчитать полные суммы по значениям прошлого прохода.
- Остановиться, когда максимальное изменение суммы за проход станет меньше точности.
Точность = 0.01; // добиваемся сходимости до копеек
МаксЧислоИтераций = 50; // жёсткий предел, чтобы не зациклиться
ПолныеСуммы = Новый Соответствие;
Для Каждого Строка Из ТаблицаЗатрат Цикл
ПолныеСуммы[Строка.Подразделение] = Строка.ПрямаяСумма;
КонецЦикла;
Для Итерация = 1 По МаксЧислоИтераций Цикл
МаксСдвиг = 0;
ПредыдущиеСуммы = Новый Соответствие;
Для Каждого Пара Из ПолныеСуммы Цикл
ПредыдущиеСуммы[Пара.Ключ] = Пара.Значение;
КонецЦикла;
Для Каждого Строка Из ТаблицаЗатрат Цикл
Подр = Строка.Подразделение;
Сумма = Строка.ПрямаяСумма;
Отбор = Новый Структура("Потребитель", Подр);
Для Каждого СтрокаП Из ТаблицаПотребления.НайтиСтроки(Отбор) Цикл
Сумма = Сумма + ПредыдущиеСуммы[СтрокаП.Поставщик] * СтрокаП.Доля;
КонецЦикла;
ПолныеСуммы[Подр] = Сумма;
МаксСдвиг = Макс(МаксСдвиг, АбсЗнач(Сумма - ПредыдущиеСуммы[Подр]));
КонецЦикла;
Если МаксСдвиг < Точность Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Обязательно ограничивайте число проходов — без МаксЧислоИтераций любая ошибка в долях превратит отчёт в бесконечный цикл, и сеанс придётся снимать принудительно.
Метод сходится, когда суммарная доля встречного потребления по каждому поставщику меньше единицы: на каждом проходе добавка уменьшается, ряд затухает. Обычно хватает 5–15 проходов. Если за 50 проходов сходимости нет — проблема в исходных долях, а не в алгоритме.
Пример на двух подразделениях
Возьмём котельную с прямыми затратами 100 000 ₽ и ремонтный цех с прямыми затратами 60 000 ₽. Котельная потребляет 20% услуг ремонтного цеха (доля 0,2), ремонтный цех — 10% услуг котельной (доля 0,1). На нулевом проходе берём только прямые затраты, дальше на каждом проходе добавляем встречную долю от сумм предыдущего прохода:
| Проход | Котельная, ₽ | Ремонтный цех, ₽ |
|---|---|---|
| 0 (прямые) | 100 000 | 60 000 |
| 1 | 112 000 | 70 000 |
| 2 | 114 000 | 71 200 |
| 3 | 114 240 | 71 400 |
| 4 | 114 280 | 71 424 |
| 5 | 114 285 | 71 428 |
Видно, как прибавка быстро тает: 12 000 → 2 000 → 240 → 40 → 5 ₽. К шестому проходу расхождение падает ниже рубля, и расчёт останавливается. Точное решение системы — 114 285,71 ₽ и 71 428,57 ₽, итерации выходят на те же цифры без составления и решения уравнений вручную.
Когда вместо итераций решать систему уравнений
Итерации — не единственный способ закрутить отчёт. Взаимное распределение — это система линейных уравнений, и при небольшом числе подразделений её можно решить точно методом Гаусса. Что выбрать:
| Метод | Когда применять | Ограничения |
|---|---|---|
| Итерации (приближения) | Много подразделений, встречные доли умеренные | Нужна проверка сходимости и предел проходов |
| Решение системы уравнений | Немного объектов (до нескольких десятков) | Больше кода; при вырожденной матрице решения нет |
| Ручное распределение по этапам | Односторонние услуги, цикла фактически нет | Неверно, если есть встречное потребление |
Для большинства учётных задач итераций достаточно: они проще в сопровождении и не ломаются на «почти вырожденных» данных. Точное решение системы оправдано там, где важна копеечная сходимость за один расчёт и подразделений немного.
Регистры накопления: откуда брать встречные обороты
Прямые затраты и объёмы встречных услуг не берутся из воздуха — это обороты регистров. Затраты обычно лежат в регистре накопления по статьям и подразделениям, объёмы услуг — в регистре по номенклатуре услуг и паре «поставщик — потребитель».
Чтобы расчёт был воспроизводимым:
- 📊 тяните обороты одним запросом за период отчёта, с отбором по организации и подразделениям;
- доли потребления считайте от натуральных объёмов (часы, кВт·ч, тонны), а не от сумм — суммы ещё не известны до пересчёта;
- результат «кручения» имеет смысл сохранять: записать полные суммы в отдельный регистр сведений или в промежуточный документ, чтобы следующий отчёт не пересчитывал всё заново.
Если распределение влияет на себестоимость и проводки, за фактические движения всё равно отвечает регламентная операция закрытия месяца. Отчёт-«раскрутка» тут — инструмент анализа и сверки, а не замена закрытию периода.
Производительность: как ускорить многопроходный расчёт
Главная ошибка — повторять запрос к базе на каждом проходе. База читается один раз, дальше крутятся только числа в памяти.
Что ускоряет расчёт:
- Один запрос на входе. Соберите прямые затраты и доли во временные таблицы через менеджер временных таблиц, выгрузите в таблицы значений — и дальше работайте с памятью.
- Соответствия вместо поиска по таблице. Доступ по ключу в
Соответствиебыстрее, чемНайтиСтрокив цикле по тысячам строк. Для больших моделей заранее переложите доли в соответствие вида «потребитель → массив поставщиков с долями». - Индексы у таблиц значений. Если без
НайтиСтрокине обойтись, проиндексируйте колонку отбора методомИндексы.Добавить. - Разумная точность. Точность до копейки сходится за единицы проходов; требование «до одной сотой копейки» добавит десятки лишних итераций без практической пользы.
Тяжёлый пересчёт на рабочей базе в разгар дня нагружает сервер и тормозит других пользователей. Объёмные расчёты запускайте в фоновом задании или в нерабочее время, а не в клиентском сеансе на весь период.
Диагностика: где искать причину неверных цифр
Если отчёт закрутился, но результат вызывает сомнения, разделяйте два класса проблем: логику расчёта и производительность.
- Логику проверяют отладчиком: точка останова в начале прохода, просмотр
ПолныеСуммыиМаксСдвигпо итерациям. Видно, сходится ли ряд и на каком проходе цифры застывают. - Скорость меряют замером производительности в Конфигураторе (меню
Отладка → Замер производительности): он покажет, какая строка съедает время — обычно это запрос внутри цикла или поиск по неиндексированной таблице. - Ошибки выполнения (деление на ноль, обращение по несуществующему ключу) попадают в журнал регистрации. Фильтр по уровню «Ошибка» и имени пользователя быстро выводит на проблемный прогон.
Когда выносить расчёт во внешнюю обработку
Встроить «кручение» можно прямо в модуль отчёта, но чаще удобнее внешняя обработка:
- её ставят на любую базу без изменения конфигурации и снятия с поддержки;
- логику легко дорабатывать и переносить между базами;
- тяжёлый расчёт просится в фоновое задание, а это удобнее запускать именно из обработки.
Внешнюю обработку
готовит специалист в Конфигураторе, а бухгалтер или экономист открывает её через Файл → Открыть в режиме 1С:Предприятие. Если расчёт нужен регулярно и многим — его стоит перенести в расширение конфигурации
или в доработку, а не держать как отдельный файл.
Внешнюю обработку с заполненными тестовыми данными удобно хранить рядом с описанием методики распределения — так следующий специалист поймёт модель расчёта без устных пояснений.
Частые вопросы
Чем «закрутить отчёт» отличается от обычного пересчёта?
Всегда ли метод последовательных приближений сходится?
Можно ли обойтись без итераций?
Нужен ли обычному пользователю режим Конфигуратора?
Файл → Открыть в режиме 1С:Предприятие и работает с привычными настройками.