При разработке отчетов и внешних обработок для экспорта проводок разработчики часто сталкиваются с ситуацией, когда по большинству счетов учета (10, 20, 26, 60) данные благополучно выгружаются, но движения по счетам 08 («Вложения во внеоборотные активы») и 71 («Расчеты с подотчетными лицами») упорно игнорируются либо вызывают фатальные ошибки выполнения. Разберем по шагам, почему так происходит, какие подводные камни скрываются в типовой структуре данных и как правильно построить алгоритм выгрузки.
Проанализируем типовое условие отбора документов в блоке ГДЕ текста запроса к регистру бухгалтерии:
ГДЕ
ХозрасчетныйДвиженияССубконто.Период МЕЖДУ &НачалоПериода И &КонецПериода
И (ХозрасчетныйДвиженияССубконто.Регистратор ССЫЛКА Документ.ОперацияБух
ИЛИ ХозрасчетныйДвиженияССубконто.Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг
ИЛИ ХозрасчетныйДвиженияССубконто.Регистратор ССЫЛКА Документ.ПоступлениеДопРасходов)
В типовой конфигурации 1C:Бухгалтерия действуют четкие правила методологии учета:
ПоступлениеТоваровУслуг). Основным первичным документом для таких операций выступает АвансовыйОтчет, а также платежные поручения и кассовые ордера. Если документа АвансовыйОтчет нет в фильтре по типам регистраторов, движения по счету 71 гарантированно не попадут в выборку.ПринятиеКУчетуОС) или ручные операции.Если нам необходимо видеть проводки подотчетных лиц, расширим список доступных типов регистраторов:
И (ХозрасчетныйДвиженияССубконто.Регистратор ССЫЛКА Документ.ОперацияБух
ИЛИ ХозрасчетныйДвиженияССубконто.Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг
ИЛИ ХозрасчетныйДвиженияССубконто.Регистратор ССЫЛКА Документ.ПоступлениеДопРасходов
ИЛИ ХозрасчетныйДвиженияССубконто.Регистратор ССЫЛКА Документ.АвансовыйОтчет)
При простом удалении ограничений на документы запрос начинает возвращать проводки, сформированные авансовыми отчетами или другими документами. Однако код немедленно завершается аварийно при попытке получить объект документа и перебрать строки его табличной части:
ТекДокумент = РезультатЗапроса.Регистратор.ПолучитьОбъект();
Для Каждого Стр Из ТекДокумент.Товары Цикл
Если ((Стр.Сумма - Стр.СуммаНДС) = РезультатЗапроса.Сумма) Тогда
// ...
КонецЕсли;
КонецЦикла;
Выясним причину этой ошибки: структура табличных частей у разных документов существенно различается:
АвансовыйОтчет табличные части называются иначе: Авансы, Товары, ОплатаПоставщикам, Прочее. При этом колонки могут отличаться по наименованию (например, не везде есть колонка Сумма или СуммаНДС).ПоступлениеТоваровУслуг для счета 08 позиции обычно заносятся не в табличную часть Товары и не в Услуги, а в табличные части Оборудование или ОбъектыСтроительства. Код, проверяющий исключительно таблицы Товары и Услуги, физически пропускает эти строки.Для корректной обработки таких документов необходимо анализировать состав метаданных конкретного типа регистратора перед чтением табличной части либо извлекать данные универсальным способом.
Посмотрим на условия фильтрации счетов в запросе и в коде на встроенном языке:
Если РезультатЗапроса.СчетДт.Родитель.Код = "08" ИЛИ РезультатЗапроса.СчетКт.Код = "71" Тогда
// Обработка
КонецЕсли;
Такой подход содержит грубые архитектурные ошибки:
71.01, 71.02). Сам счет 71 является группой. Проверка СчетКт.Код = "71" всегда возвращает Ложь, так как код проводки содержит значение "71.01".08.04.1 подчинен группе 08.04, которая подчинена счету 08). Конструкция СчетДт.Родитель.Код = "08" для субсчета 08.04.1 вернет Ложь, так как его непосредственным родителем является 08.04.В платформе 1С для работы с планом счетов нельзя проверять строковые коды родителей. Следует использовать оператор В ИЕРАРХИИ в параметрах виртуальной таблицы:
ИЗ
РегистрБухгалтерии.Хозрасчетный.ДвиженияССубконто(
&НачалоПериода,
&КонецПериода,
СчетДт В ИЕРАРХИИ (&Счет08)
ИЛИ СчетКт В ИЕРАРХИИ (&Счет71),
,
) КАК ХозрасчетныйДвиженияССубконто
В параметры запроса передаются ссылки на предопределенные счета:
Запрос.УстановитьПараметр("Счет08", ПланыСчетов.Хозрасчетный.ВложенияВоВнеоборотныеАктивы);
Запрос.УстановитьПараметр("Счет71", ПланыСчетов.Хозрасчетный.РасчетыСПодотчетнымиЛицами);
Вызов метода ПолучитьОбъект() на каждой итерации выборки запроса считывает весь документ целиком со всеми табличными частями из базы данных в оперативную память. Это создает критическую нагрузку на СУБД и приводит к резкому замедлению работы.
Кроме того, сопоставление по условию Стр.Сумма = РезультатЗапроса.Сумма порождает логическую ошибку: если в накладной присутствуют две разные позиции с одинаковой ценой и суммой, алгоритм всегда продублирует реквизиты первой найденной строки.
Рассмотрим, как правильно решить эту задачу: виртуальная таблица ДвиженияССубконто уже содержит в себе все необходимые аналитические разрезы проводки (номенклатуру, статьи затрат, контрагентов, основные средства). Достаточно обратиться к полям СубконтоДт1, СубконтоДт2, СубконтоКт1 и т.д. прямо в тексте запроса, без распаковки табличных частей регистратора.
Использование COM-объекта Excel.Application с поэлементной записью ячеек через Cells(a, b).Value на сотнях и тысячах строк выполняется крайне медленно. В платформе 1С предусмотрен встроенный и производительный способ выгрузки:
ТабличныйДокумент в памяти платформы.
ТабличныйДокумент.Записать("C:\Выгрузка.xlsx", ТипФайлаТабличногоДокумента.XLSX);
Этот способ работает в десятки раз быстрее, исключает зависание процессов EXCEL.EXE в диспетчере задач операционной системы и не требует наличия установленного пакета Microsoft Office на сервере или рабочем месте.