Как объединить строки по контрагенту в отчете 1С, если у него несколько договоров или валют?

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В практике разработки на платформе 1С:Предприятие 8.3 (особенно в конфигурациях на обычных формах, таких как УТ 10.3 или УПП) программисты часто сталкиваются с ситуацией, когда отчет выводит одну и ту же организацию-дебитора несколькими строками. Чаще всего это происходит из-за того, что данные в запросе детализируются по договорам, валютам или сделкам, в то время как пользователю нужен агрегированный показатель по контрагенту в целом. Разберем подробно, как решить эту задачу на разных уровнях: от текста запроса до обработки табличного документа.

Анализируем причину возникновения проблемы

Прежде чем приступать к исправлению кода, проанализируем ситуацию. Если отчет строится на базе регистра накопления (например, ВзаиморасчетыСКонтрагентамиПоДокументамРасчетов), система хранит остатки в разрезе всех измерений регистра. Если в запросе выбраны такие поля, как ДоговорКонтрагента, Сделка или ВалютаВзаиморасчетов, то при группировке СГРУППИРОВАТЬ ПО платформа будет обязана вывести отдельную строку для каждой уникальной комбинации этих полей.

В рассматриваемом нами случае по одному контрагенту установили лимит в 2 млн. рублей. Однако в отчете сумма долга разбивается на две строки (курсивом), потому что у контрагента есть задолженность по двум разным договорам. Наша задача — схлопнуть эти строки, просуммировать задолженность и сравнить её с общим лимитом.

Решение 1: Корректировка текста запроса (лучшая практика)

Самый правильный способ объединить строки — сделать это на этапе выполнения запроса. Для этого нам нужно убрать лишние разрезы из группировки и привести все суммы к единой валюте (регламентированного учета, обычно это рубли). Выясним, как это сделать с помощью соединения с курсами валют.

Рассмотрим пример исправленного запроса, где мы пересчитываем валютную сумму в рубли прямо "на лету" и группируем результат строго по контрагенту:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	Остатки.Контрагент КАК Контрагент,
|	Остатки.Контрагент.ИНН КАК ИНН,
|	ВЫБОР
|		КОГДА МАКСИМУМ(Остатки.ДоговорКонтрагента.ВалютаВзаиморасчетов.Код) = ""978""
|			ТОГДА СУММА(ЕСТЬNULL(Остатки.СуммаВзаиморасчетовОстаток, 0)) * МАКСИМУМ(КурсыВалют.Курс)
|		ИНАЧЕ СУММА(ЕСТЬNULL(Остатки.СуммаВзаиморасчетовОстаток, 0))
|	КОНЕЦ КАК СуммаВРублях,
|	Остатки.Контрагент.Наименование КАК КонтрагентНаименование
|ИЗ
|	РегистрНакопления.ВзаиморасчетыСКонтрагентамиПоДокументамРасчетов.Остатки(&ДатаСреза, ) КАК Остатки
|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют.СрезПоследних(&ДатаСреза, Валюта = &ВалютаЕвро) КАК КурсыВалют
|		ПО (ИСТИНА)
|ГДЕ
|	Остатки.ДоговорКонтрагента.ВидДоговора = ЗНАЧЕНИЕ(Перечисление.ВидыДоговоровКонтрагентов.СПокупателем)
|
|СГРУППИРОВАТЬ ПО
|	Остатки.Контрагент,
|	Остатки.Контрагент.ИНН,
|	Остатки.Контрагент.Наименование";

Запрос.УстановитьПараметр("ДатаСреза", КонецДня(ДатаСреза));
Запрос.УстановитьПараметр("ВалютаЕвро", Справочники.Валюты.НайтиПоКоду("978"));

Важный нюанс: В этом примере мы использовали ЛЕВОЕ СОЕДИНЕНИЕ с регистром курсов. Мы сознательно убрали группировку по договорам. Теперь, сколько бы договоров ни было у клиента, запрос вернет одну строку с общей суммой, пересчитанной в рубли.

Решение 2: Агрегация данных с помощью метода Свернуть()

Если по каким-то причинам переписать запрос сложно (например, он формируется динамически), мы можем воспользоваться программной обработкой таблицы значений. Метод Свернуть() позволяет объединить строки с одинаковыми ключевыми полями и просуммировать числовые ресурсы.

Рассмотрим алгоритм действий:

  1. Выгрузим результат запроса в таблицу значений.
  2. Если в таблице есть колонки, которые мешают сворачиванию (например, уникальный номер договора или сделки), их нужно либо удалить, либо очистить перед сворачиванием.
  3. Вызовем метод Свернуть().

Посмотрим на пример кода:


Остатки = Запрос.Выполнить.Выгрузить();
// Удаляем измерения, которые вызывают дублирование строк
// Оставляем только те, по которым нужно объединить (Контрагент, ИНН)
Остатки.Свернуть("Контрагент, ИНН, КонтрагентНаименование", "Сумма1, Сумма2");

Обратите внимание: Если значения в колонках группировки хотя бы на один символ отличаются (например, у одного контрагента заполнен ИНН, а у другого — нет, или есть лишний пробел), метод Свернуть() воспримет их как разные записи и не объединит. Поэтому перед использованием метода рекомендуется пройтись циклом и нормализовать данные (например, использовать СокрЛП() для строковых полей).

Решение 3: Работа с макетом и секциями (уровень вывода)

В исходном вопросе автор столкнулся с проблемой вывода в "Секцию 1" и "Секцию 2". Это часто происходит, когда в коде реализован двухуровневый обход: сначала по ИНН, а внутри него — по конкретным записям остатков. Чтобы объединить данные, нам нужно изменить логику вывода.

Проанализируем ситуацию: если мы хотим видеть только одну строку, нам не нужно выводить детализацию (курсив в Секция2). Нам нужно рассчитать общий итог по группе и вывести его в Секция1. Программный обход будет выглядеть так:


Для Каждого СтрокаОстатков Из Остатки Цикл
    // Если мы уже обработали этот ИНН в рамках группировки, пропускаем
    Если Не СтрокаОстатков.ВыводитьСтроку Тогда 
        Продолжить; 
    КонецЕсли;

    // Находим все строки с таким же ИНН, чтобы сложить их
    СтрокиСИНН = Остатки.НайтиСтроки(Новый Структура("ИНН", СтрокаОстатков.ИНН));
    
    ОбщаяСумма1 = 0;
    ОбщаяСумма2 = 0;
    Для Каждого Стр Из СтрокиСИНН Цикл
        ОбщаяСумма1 = ОбщаяСумма1 + Стр.Сумма1;
        ОбщаяСумма2 = ОбщаяСумма2 + Стр.Сумма2;
        // Помечаем строку как обработанную, чтобы не выводить её повторно
        Стр.ВыводитьСтроку = Ложь;
    КонецЦикла;

    // Заполняем параметры только одной секции
    Секция1.Параметры.Контрагент = СтрокаОстатков.Контрагент;
    Секция1.Параметры.Сумма1 = ОбщаяСумма1;
    Секция1.Параметры.Сумма2 = ОбщаяСумма2;
    
    // Рассчитываем лимит и превышение
    Лимит = ОпределитьЛимит(СтрокаОстатков.Контрагент);
    Секция1.Параметры.Лимит = Лимит;
    Секция1.Параметры.Превышение = ?(ОбщаяСумма2 > Лимит, ОбщаяСумма2 - Лимит, 0);
    
    ТабличныйДокумент.Вывести(Секция1);
КонецЦикла;

Полезные советы по оптимизации отчета

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

1. Используйте ресурс СуммаРеглОстаток. В типовой конфигурации УТ 10.3 в регистрах взаиморасчетов есть ресурс СуммаРеглОстаток. Это сумма в валюте бухгалтерского (регламентированного) учета. Если вам нужно просто получить долг в рублях, не обязательно соединяться с регистром курсов валют — достаточно просуммировать этот ресурс. Система сама пересчитает его при проведении документов.

2. Учет "Головного контрагента". Если вы работаете с крупными холдингами, где у одной компании много филиалов с разными карточками в 1С, но общим лимитом, группируйте данные по полю Контрагент.ГоловнойКонтрагент. Это позволит объединить задолженность всей группы компаний в одну строку отчета.

3. Переход на СКД. Хотя старые отчеты в УТ 10.3 часто написаны на обычном макете, мы настоятельно рекомендуем использовать Систему компоновки данных (СКД). В СКД проблема объединения строк решается парой кликов мыши: вы просто добавляете группировку по полю Контрагент, а ресурсы (суммы) система складывает автоматически. Это избавляет от необходимости писать сложный код обхода таблицы значений и управления секциями макета.

4. Проблема "978" (Евро). В коде автора встречается жесткая привязка к коду валюты "978". Это плохая практика (magic strings). Если завтра код евро изменится или добавится доллар, отчет придется переписывать. Выясним, как сделать лучше: лучше создать параметр в отчете или настройку, где пользователь сам выберет валюту, к которой нужно приводить данные, либо использовать предопределенные элементы (если они есть).

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

← На главную