При разработке и доработке отчетов в среде 1С:Предприятие 7.7 часто возникает задача вывести актуальные остатки (конечное сальдо) по конкретному счету в разрезе аналитики — субконто «Контрагенты» и «Договоры». Бухгалтеры привыкли видеть эти цифры в стандартной Оборотно-сальдовой ведомости (ОСВ) по счету, но при формировании реестров договоров или управленческих отчетов гораздо удобнее выводить эти данные в виде отдельной колонки прямо в строках отчета.
В этой статье мы подробно разберем, как решить эту задачу с помощью встроенного языка 1С 7.7. Мы рассмотрим различные подходы: от простых точечных запросов до оптимизированных пакетных выборок, которые не будут перегружать систему и обеспечат высокую скорость работы отчета.
Представим ситуацию: у нас есть отчёт «Реестр договоров». В каждой строке этого отчета фигурирует контрагент и документ договора. Нам необходимо добавить колонку «Остаток» (конечное сальдо). Учет взаиморасчетов ведется, например, на активно-пассивных субсчетах счета 76 (например, 76.К.1 «Личные сбережения» или 76.К.4 «Займы»).
Для решения этой задачи в платформе 1С 7.7 используется специализированный объект БухгалтерскиеИтоги. Однако здесь разработчики часто сталкиваются с неочевидной проблемой сопоставления типов данных аналитики (субконто) и производительности базы данных при расчете остатков в цикле.
Самый простой и очевидный способ — создать функцию, которая будет вызываться для каждой строки отчета, инициализировать объект бухгалтерских итогов, позиционироваться на нужной дате и счете, а затем возвращать остаток. Посмотрим на пример реализации такой функции:
Функция ПолучитьОстатокПоДоговору(ТекущийДоговор, КонДата)
// Определяем счет в зависимости от свойств договора
Счет = ?(ТекущийДоговор.Вид = Перечисление.ПС_ВидыДоговоров.ДоговорВзноса, СчетПоКоду("76.К.1"), СчетПоКоду("76.К.4"));
// Определяем контрагента (первое субконто)
Субконто1 = ТекущийДоговор.Контрагент.ТекущийЭлемент();
Субконто2 = ТекущийДоговор;
БухИт = СоздатьОбъект("БухгалтерскиеИтоги");
БухИт.Актуальность(1);
БухИт.ПериодМ(КонДата);
// Получаем сальдо конечное дебетовое и кредитовое
ОстатокДт = БухИт.СКД(Счет, , , Субконто1, Субконто2);
ОстатокКт = БухИт.СКК(Счет, , , Субконто1, Субконто2);
// Возвращаем разницу (или конкретное сальдо в зависимости от логики счета)
Возврат ОстатокКт - ОстатокДт;
КонецФункции
В данном коде мы используем методы СКД() (Сальдо Конечное Дебетовое) и СКК() (Сальдо Конечное Кредитовое) объекта БухгалтерскиеИтоги. В качестве параметров мы передаем счет учета, тип суммы, валюту (если учет валютный) и конкретные значения субконто.
Иногда при использовании вышеописанного кода функция возвращает нулевой результат, хотя в стандартной ОСВ остаток по контрагенту и договору присутствует. Выясним причину этой проблемы.
Очень часто в конфигурациях 1С 7.7 в качестве реквизита в отчетах или документах выступает ссылка на документ договора (например, документ «Договор займа»), в то время как на счетах бухгалтерского учета в качестве второго субконто установлен справочник «Договоры». Прямая передача документа в качестве параметра Субконто2 метода СКД() или СКК() вернет пустой результат, так как типы данных различаются.
Чтобы решить эту проблему, нам нужно найти элемент справочника «Договоры», который связан с этим документом. Разберем, как сделать это эффективно.
Неэффективный способ (полный перебор в цикле):
// Такой код будет сильно замедлять работу отчета при большом количестве договоров!
Спр = СоздатьОбъект("Справочник.Договоры");
Спр.ИспользоватьВладельца(Договор.Контрагент);
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
Если Спр.ТекущийЭлемент().ПС_Документ = Договор Тогда
Субконто2 = Спр.ТекущийЭлемент();
Прервать;
КонецЕсли;
КонецЦикла;
Эффективный способ (поиск по реквизиту):
Для того чтобы избежать медленного перебора справочника вручную, воспользуемся методом НайтиПоРеквизиту(). Предварительно убедитесь, что в конфигураторе у реквизита ПС_Документ справочника «Договоры» установлено свойство «Сортировка».
Спр = СоздатьОбъект("Справочник.Договоры");
Спр.ИспользоватьВладельца(Договор.Контрагент);
// Третий параметр "0" означает поиск в пределах подчинения владельцу
Если Спр.НайтиПоРеквизиту("ПС_Документ", Договор, 0) = 1 Тогда
Субконто2 = Спр.ТекущийЭлемент();
Иначе
Субконто2 = ПолучитьПустоеЗначение("Справочник.Договоры");
КонецЕсли;
Использование метода НайтиПоРеквизиту() работает мгновенно за счет индексации таблиц базы данных СУБД, что кардинально повышает скорость открытия отчета.
Несмотря на то, что точечное обращение к итогам в функции (Способ 1) работает просто, у него есть огромный минус. Если в вашем отчете будет 500 строк, система выполнит 500 отдельных обращений к таблицам бухгалтерских итогов на диске. В файловом варианте базы данных (DBF) это вызовет заметные задержки, а на крупных базах отчет может просто «зависнуть».
Проанализируем правильный профессиональный подход: мы делаем один общий запрос ко всем итогам перед началом вывода строк отчета, а затем внутри цикла быстро обращаемся к полученным данным в оперативной памяти.
Разберем по шагам реализацию этого механизма:
Шаг 1. Инициализация и выполнение запроса перед циклом вывода отчета:
// Создаем список счетов, по которым нам нужны остатки
СписокСчетов = СоздатьОбъект("СписокЗначений");
СписокСчетов.ДобавитьЗначение(СчетПоКоду("76.К.1"));
СписокСчетов.ДобавитьЗначение(СчетПоКоду("76.К.4"));
// Создаем и настраиваем объект бухгалтерских итогов
БухИт = СоздатьОбъект("БухгалтерскиеИтоги");
БухИт.ИспользоватьСубконто(ВидыСубконто.Контрагенты, , 1); // 1 - выборка по всем элементам
БухИт.ИспользоватьСубконто(ВидыСубконто.Договоры, , 1);
// Выполняем один запрос на конечную дату
БухИт.ВыполнитьЗапрос( , КонДата, СписокСчетов);
Шаг 2. Выборка остатков внутри цикла вывода строк отчета:
В процессе обхода строк нашего отчета (например, при чтении строк справочника или документа) мы не создаем новые объекты бухгалтерских итогов. Вместо этого мы используем позиционирование по результатам уже выполненного запроса БухИт с помощью третьего параметра методов ПолучитьСчет() и ПолучитьСубконто():
// Предположим, у нас идет цикл по строкам отчета
Пока ТаблицаДоговоровОтчета.ПолучитьСтроку() = 1 Цикл
ТекущийКонтрагент = ТаблицаДоговоровОтчета.Контрагент;
ТекущийДоговорСправочник = ТаблицаДоговоровОтчета.ДоговорСсылка; // Ссылка на справочник!
НужныйСчет = ТаблицаДоговоровОтчета.СчетУчета;
ОстатокКт = 0;
ОстатокДт = 0;
КонечныйОстаток = 0;
// Позиционируемся на нужном счете
Если БухИт.ПолучитьСчет( , НужныйСчет) = 1 Тогда
// Позиционируемся на Контрагенте (1-е субконто)
Если БухИт.ПолучитьСубконто(1, , ТекущийКонтрагент) = 1 Тогда
// Позиционируемся на Договоре (2-е субконто)
Если БухИт.ПолучитьСубконто(2, , ТекущийДоговорСправочник) = 1 Тогда
// Извлекаем остатки прямо из памяти без обращений к диску!
ОстатокКт = БухИт.СКК("С");
ОстатокДт = БухИт.СКД("С");
КонечныйОстаток = ОстатокКт - ОстатокДт;
КонецЕсли;
КонецЕсли;
КонецЕсли;
// Выводим полученный КонечныйОстаток в строку отчета
// ... Код вывода секции таблицы ...
КонецЦикла;
Использование третьего параметра в методе ПолучитьСубконто(НомерСубконто, , Значение) позволяет мгновенно переместить указатель выборки на нужного контрагента и договор. Это увеличивает скорость работы отчета в десятки и сотни раз по сравнению с индивидуальными запросами в цикле.
Еще один удобный и гибкий метод, который часто используют разработчики 1С 7.7, — это выгрузка результатов запроса бухгалтерских итогов во временную таблицу значений (ТЗ) с последующим поиском в ней.
Рассмотрим, как это реализовать:
ВыгрузитьИтоги() для создания таблицы значений с готовыми остатками.Посмотрим на пример кода:
// Выполняем запрос
БухИт = СоздатьОбъект("БухгалтерскиеИтоги");
БухИт.ИспользоватьСубконто(ВидыСубконто.Контрагенты, , 1);
БухИт.ИспользоватьСубконто(ВидыСубконто.Договоры, , 1);
БухИт.ВыполнитьЗапрос( , КонДата, СписокСчетов);
// Выгружаем итоги в Таблицу Значений
ТЗ_Остатки = СоздатьОбъект("ТаблицаЗначений");
// Второй параметр "1" означает выгрузку детальных строк по субконто
БухИт.ВыгрузитьИтоги(ТЗ_Остатки, 1, 1);
// Для ускорения поиска можно проиндексировать или свернуть таблицу значений
// ТЗ_Остатки будет содержать колонки: Субконто1, Субконто2, Счет, СКД, СКК и др.
Теперь в цикле вывода отчета нам достаточно выполнить быстрый поиск по нашей таблице значений ТЗ_Остатки:
Пока ТаблицаДоговоровОтчета.ПолучитьСтроку() = 1 Цикл
КонтрагентПоиск = ТаблицаДоговоровОтчета.Контрагент;
ДоговорПоиск = ТаблицаДоговоровОтчета.ДоговорСсылка;
СтрокаТЗ = 0;
Остаток = 0;
// Ищем строку с нужным контрагентом в колонке "Субконто1"
Если ТЗ_Остатки.НайтиЗначение(КонтрагентПоиск, СтрокаТЗ, "Субконто1") = 1 Тогда
// ТЗ_Остатки.ПолучитьСтрокуПоНомеру(СтрокаТЗ);
// Проверяем дополнительно совпадение договора во второй колонке "Субконто2"
// ... (дополнительная фильтрация или позиционирование) ...
Остаток = ТЗ_Остатки.ПолучитьЗначение(СтрокаТЗ, "СКК") - ТЗ_Остатки.ПолучитьЗначение(СтрокаТЗ, "СКД");
КонецЕсли;
// Выводим остаток в макет
КонецЦикла;
Метод с выгрузкой в ТЗ особенно удобен тем, что вы можете производить с полученными данными любые математические и сортировочные операции стандартными средствами таблиц значений 1С 7.7.
Подводя итог, выделим ключевые правила работы с остатками по счетам и аналитике в 1С 7.7:
НайтиПоРеквизиту()).БухгалтерскиеИтоги внутри циклов: это существенно замедляет систему.ВыполнитьЗапрос() до начала цикла, а внутри цикла позиционируйтесь по результатам через ПолучитьСубконто(номер, , значение) или выгружайте результаты в таблицу значений методом ВыгрузитьИтоги().Применение этих простых правил гарантирует, что ваши отчеты будут работать быстро и безошибочно даже на больших объемах данных в крупных информационных базах 1С 7.7.