Почему реквизит СтатьяДвиженияДенежныхСредств в документе Списание с расчетного счета пустой и как это исправить?

Программист 1С v8.3 (Обычные формы) 1C:Бухгалтерия Бухгалтерский учет IT и автоматизация бизнеса
← На главную

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

В чем кроется причина «непонятного глюка»?

Данная ситуация не является ошибкой платформы или конфигурации — это штатное поведение системы, обусловленное архитектурой хранения данных в банковских документах программных продуктов 1С (таких как «Бухгалтерия предприятия», «ERP Управление предприятием», «Комплексная автоматизация» и др.).

Дело в том, что банковские документы должны поддерживать как простые однострочные платежи (одна статья ДДС, один договор, один счет учета), так и сложные распределенные платежи (когда одна сумма оплаты разбивается на несколько договоров, разные ставки НДС или разные статьи ДДС). Для реализации этой возможности разработчики фирмы «1С» заложили в структуру документов табличную часть «РасшифровкаПлатежа».

Как распределяются данные в зависимости от режима работы:

  1. Режим «Без разбивки платежа» (однострочный режим): В пользовательском интерфейсе документ выглядит просто: все ключевые реквизиты, включая статью ДДС, договор и счет расчетов, отображаются в шапке формы. Пользователь заполняет их как обычные реквизиты. Однако при записи и проведении документа система автоматически переносит эти данные в первую строку табличной части РасшифровкаПлатежа, оставляя аналогичные реквизиты в шапке объекта пустыми.
  2. Режим «Разбить платеж» (многострочный режим): На форме документа появляется полноценная табличная часть, где пользователь может указать несколько строк, распределив общую сумму по разным договорам или статьям ДДС. В этом случае данные также физически записываются только в табличную часть РасшифровкаПлатежа.
  3. Загрузка через Клиент-Банк: При автоматическом импорте банковских выписок сторонние обработки и типовые механизмы обмена чаще всего заполняют данные напрямую в табличную часть РасшифровкаПлатежа, полностью игнорируя реквизиты шапки.

Таким образом, попытка прочитать реквизит напрямую из шапки (например, через ДокСсылка.СтатьяДвиженияДенежныхСредств) почти всегда вернет пустое значение или пустую ссылку типа СправочникСсылка.СтатьиДвиженияДенежныхСредств, так как физически данные хранятся в РасшифровкаПлатежа[0].СтатьяДвиженияДенежныхСредств.

Анализ исходного кода и проблем производительности

Проанализируем фрагмент кода, представленный в исходном обращении автора:


Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() цикл
    ДокСсылка = Выборка.Регистратор;
    Если ДокСсылка.Метаданные().Имя = "СписаниеСРасчетногоСчета" Тогда
        СписаниеСРСДОк = ДокСсылка.ПолучитьОбъект();
        СтатьяДвиженияДенежныхСредств = СписаниеСРСДок.СтатьяДвиженияДенежныхСредств;
        // ...

Помимо проблемы с чтением пустого реквизита шапки, данный код содержит критическую ошибку, существенно снижающую производительность всей системы — вызов метода ПолучитьОбъект() в цикле выборки запроса. Метод ПолучитьОбъект() считывает абсолютно все данные документа из базы данных в оперативную память, инициализирует модуль объекта, выполняет все обработчики событий при чтении и тратит колоссальное количество серверного времени и ресурсов СУБД. Если в выборке окажутся тысячи документов, система гарантированно зависнет или существенно замедлит работу пользователей.

Рассмотрим правильные и безопасные способы решения этой задачи, разделенные по уровням оптимизации.

Решение №1. Получение статьи ДДС через аналитику (Субконто) регистра в запросе

Поскольку в исходной задаче выборка идет из виртуальной таблицы РегистрБухгалтерии.Хозрасчетный.Обороты, самым быстрым, элегантным и правильным решением будет получение статьи ДДС напрямую из самого регистра бухгалтерии без обращения к физическим таблицам документов.

На счетах учета денежных средств (таких как 51 «Расчетные счета», 50 «Касса», 52 «Валютные счета») практически всегда ведется субконто вида «Статьи движения денежных средств». Это означает, что при проведении документа статья ДДС записывается непосредственно в проводки. Мы можем извлечь ее в запросе. Выясним, каким по счету идет это субконто на нужном счете, и обратимся к нему в тексте запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ХозрасчетныйОбороты.Регистратор КАК Регистратор,
    |	ХозрасчетныйОбороты.Счет КАК Счет,
    |	// Предполагаем, что Статьи ДДС — это Субконто1 на денежных счетах
    |	ВЫБОР
    |		КОГДА ХозрасчетныйОбороты.Субконто1 ССЫЛКА Справочник.СтатьиДвиженияДенежныхСредств
    |			ТОГДА ХозрасчетныйОбороты.Subkonto1
    |		КОГДА ХозрасчетныйОбороты.Субконто2 ССЫЛКА Справочник.СтатьиДвиженияДенежныхСредств
    |			ТОГДА ХозрасчетныйОбороты.Субконто2
    |		ИНАЧЕ ХозрасчетныйОбороты.Субконто3
    |	КОНЕЦ КАК СтатьяДДС
    |ИЗ
    |	РегистрБухгалтерии.Хозрасчетный.Обороты(&ДатаНачала, &ДатаОкончания, Регистратор, , , , , ) КАК ХозрасчетныйОбороты
    |ГДЕ
    |	ХозрасчетныйОбороты.Счет В ИЕРАРХИИ(&МассивСчетов)
    |	И ХозрасчетныйОбороты.Организация = &Организация";

Преимущества этого метода:

Решение №2. Получение данных через левое соединение в запросе

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

Поскольку статья может находиться как в шапке (в редких конфигурациях или старых релизах), так и в табличной части РасшифровкаПлатежа, применим функцию ЕСТЬNULL для последовательного выбора значения. Для того чтобы запрос не задваивал строки при наличии нескольких записей в табличной части, свяжемся только с первой строкой (где НомерСтроки = 1):


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ХозрасчетныйОбороты.Регистратор КАК Регистратор,
    |	ХозрасчетныйОбороты.Счет КАК Счет,
    |	ЕСТЬNULL(СписаниеТаблица.СтатьяДвиженияДенежныхСредств, СписаниеШапка.СтатьяДвиженияДенежныхСредств) КАК СтатьяДДС
    |ИЗ
    |	РегистрБухгалтерии.Хозрасчетный.Обороты(
    |			&ДатаНачала,
    |			&ДатаОкончания,
    |			Регистратор,
    |			Счет В ИЕРАРХИИ (&МассивСчетов),
    |			,
    |			Организация = &Организация,
    |			,
    |			) КАК ХозрасчетныйОбороты
    |	ЛЕВОЕ СОЕДИНЕНИЕ Документ.СписаниеСРасчетногоСчета КАК СписаниеШапка
    |		ПО ХозрасчетныйОбороты.Регистратор = СписаниеШапка.Ссылка
    |	ЛЕВОЕ СОЕДИНЕНИЕ Документ.СписаниеСРасчетногоСчета.РасшифровкаПлатежа КАК СписаниеТаблица
    |		ПО ХозрасчетныйОбороты.Регистратор = СписаниеТаблица.Ссылка
    |			И СписаниеТаблица.НомерСтроки = 1
    |УПОРЯДОЧИТЬ ПО
    |	ХозрасчетныйОбороты.Регистратор.МоментВремени
    |АВТОУПОРЯДОЧИВАНИЕ";

Обратите внимание на важные изменения:

Решение №3. Безопасное чтение статьи ДДС на встроенном языке (BSL)

Рассмотрим ситуацию, когда вам необходимо программно прочитать реквизит конкретного документа (например, при обработке интерактивного выбора на форме или при написании внешней обработки) без использования запроса. В этом случае мы должны написать универсальный «защищенный» алгоритм, который проверяет оба возможных места хранения реквизита, при этом избегая необоснованного получения тяжелого объекта.

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

Проанализируем следующий пример кода:


// Функция возвращает статью ДДС из переданной ссылки на документ
Функция ПолучитьСтатьюДДСДокумента(ДокСсылка) Экспорт
    
    СтатьяДДС = Справочники.СтатьиДвиженияДенежныхСредств.ПустаяСсылка();
    
    // Проверим, заполнен ли реквизит непосредственно в шапке документа
    Если ЗначениеЗаполнено(ДокСсылка.СтатьяДвиженияДенежныхСредств) Тогда
        СтатьяДДС = ДокСсылка.СтатьяДвиженияДенежныхСредств;
    Иначе
        // Если в шапке пусто, обращаемся к первой строке табличной части расшифровки платежа
        ТабличнаяЧасть = ДокСсылка.РасшифровкаПлатежа;
        Если ТабличнаяЧасть.Количество() > 0 Тогда
            СтатьяДДС = ТабличнаяЧасть[0].СтатьяДвиженияДенежныхСредств;
        КонецЕсли;
    КонецЕсли;
    
    Возврат СтатьяДДС;
    
КонецФункции

Данный алгоритм полностью безопасен. Он гарантирует, что программа получит корректную статью движения денежных средств вне зависимости от того, как именно был сформирован документ: вручную пользователем в однострочном режиме, распределен по нескольким статьям или загружен программно через обработку интеграции с системой Клиент-Банк.

Резюме

При работе с банковскими документами в 1С всегда помните о наличии табличной части РасшифровкаПлатежа. Старайтесь избегать прямого обращения к реквизитам шапки таких документов и всегда отдавайте предпочтение чтению данных через запросы с левыми соединениями или через обращение к субконто регистров учета.

← На главную