В практике разработки на платформе 1С:Предприятие 8.3 (особенно в конфигурациях на обычных формах, таких как УТ 10.3 или УПП) программисты часто сталкиваются с ситуацией, когда отчет выводит одну и ту же организацию-дебитора несколькими строками. Чаще всего это происходит из-за того, что данные в запросе детализируются по договорам, валютам или сделкам, в то время как пользователю нужен агрегированный показатель по контрагенту в целом. Разберем подробно, как решить эту задачу на разных уровнях: от текста запроса до обработки табличного документа.
Прежде чем приступать к исправлению кода, проанализируем ситуацию. Если отчет строится на базе регистра накопления (например, ВзаиморасчетыСКонтрагентамиПоДокументамРасчетов), система хранит остатки в разрезе всех измерений регистра. Если в запросе выбраны такие поля, как ДоговорКонтрагента, Сделка или ВалютаВзаиморасчетов, то при группировке СГРУППИРОВАТЬ ПО платформа будет обязана вывести отдельную строку для каждой уникальной комбинации этих полей.
В рассматриваемом нами случае по одному контрагенту установили лимит в 2 млн. рублей. Однако в отчете сумма долга разбивается на две строки (курсивом), потому что у контрагента есть задолженность по двум разным договорам. Наша задача — схлопнуть эти строки, просуммировать задолженность и сравнить её с общим лимитом.
Самый правильный способ объединить строки — сделать это на этапе выполнения запроса. Для этого нам нужно убрать лишние разрезы из группировки и привести все суммы к единой валюте (регламентированного учета, обычно это рубли). Выясним, как это сделать с помощью соединения с курсами валют.
Рассмотрим пример исправленного запроса, где мы пересчитываем валютную сумму в рубли прямо "на лету" и группируем результат строго по контрагенту:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Контрагент КАК Контрагент,
| Остатки.Контрагент.ИНН КАК ИНН,
| ВЫБОР
| КОГДА МАКСИМУМ(Остатки.ДоговорКонтрагента.ВалютаВзаиморасчетов.Код) = ""978""
| ТОГДА СУММА(ЕСТЬNULL(Остатки.СуммаВзаиморасчетовОстаток, 0)) * МАКСИМУМ(КурсыВалют.Курс)
| ИНАЧЕ СУММА(ЕСТЬNULL(Остатки.СуммаВзаиморасчетовОстаток, 0))
| КОНЕЦ КАК СуммаВРублях,
| Остатки.Контрагент.Наименование КАК КонтрагентНаименование
|ИЗ
| РегистрНакопления.ВзаиморасчетыСКонтрагентамиПоДокументамРасчетов.Остатки(&ДатаСреза, ) КАК Остатки
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют.СрезПоследних(&ДатаСреза, Валюта = &ВалютаЕвро) КАК КурсыВалют
| ПО (ИСТИНА)
|ГДЕ
| Остатки.ДоговорКонтрагента.ВидДоговора = ЗНАЧЕНИЕ(Перечисление.ВидыДоговоровКонтрагентов.СПокупателем)
|
|СГРУППИРОВАТЬ ПО
| Остатки.Контрагент,
| Остатки.Контрагент.ИНН,
| Остатки.Контрагент.Наименование";
Запрос.УстановитьПараметр("ДатаСреза", КонецДня(ДатаСреза));
Запрос.УстановитьПараметр("ВалютаЕвро", Справочники.Валюты.НайтиПоКоду("978"));
Важный нюанс: В этом примере мы использовали ЛЕВОЕ СОЕДИНЕНИЕ с регистром курсов. Мы сознательно убрали группировку по договорам. Теперь, сколько бы договоров ни было у клиента, запрос вернет одну строку с общей суммой, пересчитанной в рубли.
Если по каким-то причинам переписать запрос сложно (например, он формируется динамически), мы можем воспользоваться программной обработкой таблицы значений. Метод Свернуть() позволяет объединить строки с одинаковыми ключевыми полями и просуммировать числовые ресурсы.
Рассмотрим алгоритм действий:
Свернуть().Посмотрим на пример кода:
Остатки = Запрос.Выполнить.Выгрузить();
// Удаляем измерения, которые вызывают дублирование строк
// Оставляем только те, по которым нужно объединить (Контрагент, ИНН)
Остатки.Свернуть("Контрагент, ИНН, КонтрагентНаименование", "Сумма1, Сумма2");
Обратите внимание: Если значения в колонках группировки хотя бы на один символ отличаются (например, у одного контрагента заполнен ИНН, а у другого — нет, или есть лишний пробел), метод Свернуть() воспримет их как разные записи и не объединит. Поэтому перед использованием метода рекомендуется пройтись циклом и нормализовать данные (например, использовать СокрЛП() для строковых полей).
В исходном вопросе автор столкнулся с проблемой вывода в "Секцию 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). Если завтра код евро изменится или добавится доллар, отчет придется переписывать. Выясним, как сделать лучше: лучше создать параметр в отчете или настройку, где пользователь сам выберет валюту, к которой нужно приводить данные, либо использовать предопределенные элементы (если они есть).
Подводя итог: для объединения строк необходимо либо упростить группировки в запросе, предварительно приведя все суммы к одной валюте, либо воспользоваться методом Свернуть() для таблицы значений перед выводом данных в макет. Выбор метода зависит от сложности существующей архитектуры отчета.