В процессе разработки в среде 1С:Предприятие программисты часто сталкиваются с ситуацией, когда при программном обращении к документу СписаниеСРасчетногоСчета (или ПоступлениеНаРасчетныйСчет) реквизит СтатьяДвиженияДенежныхСредств (ДДС) возвращает пустое значение. При этом в режиме «1С:Предприятие» при просмотре того же самого документа в журнале или форме выписки статья наглядно заполнена. Выясним причину этой особенности, разберем архитектуру хранения данных в типовых конфигурациях и рассмотрим оптимальные способы решения этой задачи с точки зрения производительности системы.
Данная ситуация не является ошибкой платформы или конфигурации — это штатное поведение системы, обусловленное архитектурой хранения данных в банковских документах программных продуктов 1С (таких как «Бухгалтерия предприятия», «ERP Управление предприятием», «Комплексная автоматизация» и др.).
Дело в том, что банковские документы должны поддерживать как простые однострочные платежи (одна статья ДДС, один договор, один счет учета), так и сложные распределенные платежи (когда одна сумма оплаты разбивается на несколько договоров, разные ставки НДС или разные статьи ДДС). Для реализации этой возможности разработчики фирмы «1С» заложили в структуру документов табличную часть «РасшифровкаПлатежа».
Как распределяются данные в зависимости от режима работы:
РасшифровкаПлатежа, оставляя аналогичные реквизиты в шапке объекта пустыми.РасшифровкаПлатежа.РасшифровкаПлатежа, полностью игнорируя реквизиты шапки.Таким образом, попытка прочитать реквизит напрямую из шапки (например, через ДокСсылка.СтатьяДвиженияДенежныхСредств) почти всегда вернет пустое значение или пустую ссылку типа СправочникСсылка.СтатьиДвиженияДенежныхСредств, так как физически данные хранятся в РасшифровкаПлатежа[0].СтатьяДвиженияДенежныхСредств.
Проанализируем фрагмент кода, представленный в исходном обращении автора:
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() цикл
ДокСсылка = Выборка.Регистратор;
Если ДокСсылка.Метаданные().Имя = "СписаниеСРасчетногоСчета" Тогда
СписаниеСРСДОк = ДокСсылка.ПолучитьОбъект();
СтатьяДвиженияДенежныхСредств = СписаниеСРСДок.СтатьяДвиженияДенежныхСредств;
// ...
Помимо проблемы с чтением пустого реквизита шапки, данный код содержит критическую ошибку, существенно снижающую производительность всей системы — вызов метода ПолучитьОбъект() в цикле выборки запроса. Метод ПолучитьОбъект() считывает абсолютно все данные документа из базы данных в оперативную память, инициализирует модуль объекта, выполняет все обработчики событий при чтении и тратит колоссальное количество серверного времени и ресурсов СУБД. Если в выборке окажутся тысячи документов, система гарантированно зависнет или существенно замедлит работу пользователей.
Рассмотрим правильные и безопасные способы решения этой задачи, разделенные по уровням оптимизации.
Поскольку в исходной задаче выборка идет из виртуальной таблицы РегистрБухгалтерии.Хозрасчетный.Обороты, самым быстрым, элегантным и правильным решением будет получение статьи ДДС напрямую из самого регистра бухгалтерии без обращения к физическим таблицам документов.
На счетах учета денежных средств (таких как 51 «Расчетные счета», 50 «Касса», 52 «Валютные счета») практически всегда ведется субконто вида «Статьи движения денежных средств». Это означает, что при проведении документа статья ДДС записывается непосредственно в проводки. Мы можем извлечь ее в запросе. Выясним, каким по счету идет это субконто на нужном счете, и обратимся к нему в тексте запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХозрасчетныйОбороты.Регистратор КАК Регистратор,
| ХозрасчетныйОбороты.Счет КАК Счет,
| // Предполагаем, что Статьи ДДС — это Субконто1 на денежных счетах
| ВЫБОР
| КОГДА ХозрасчетныйОбороты.Субконто1 ССЫЛКА Справочник.СтатьиДвиженияДенежныхСредств
| ТОГДА ХозрасчетныйОбороты.Subkonto1
| КОГДА ХозрасчетныйОбороты.Субконто2 ССЫЛКА Справочник.СтатьиДвиженияДенежныхСредств
| ТОГДА ХозрасчетныйОбороты.Субконто2
| ИНАЧЕ ХозрасчетныйОбороты.Субконто3
| КОНЕЦ КАК СтатьяДДС
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Обороты(&ДатаНачала, &ДатаОкончания, Регистратор, , , , , ) КАК ХозрасчетныйОбороты
|ГДЕ
| ХозрасчетныйОбороты.Счет В ИЕРАРХИИ(&МассивСчетов)
| И ХозрасчетныйОбороты.Организация = &Организация";
Преимущества этого метода:
ПолучитьОбъект() в цикле.Если по каким-то причинам в регистре бухгалтерии субконто ДДС не заполнено, или вам нужно гарантированно получить значение статьи ДДС, которое фигурирует именно в первичном документе (даже если он не проведен), спроектируем запрос с левым соединением к табличной части документа.
Поскольку статья может находиться как в шапке (в редких конфигурациях или старых релизах), так и в табличной части РасшифровкаПлатежа, применим функцию ЕСТЬNULL для последовательного выбора значения. Для того чтобы запрос не задваивал строки при наличии нескольких записей в табличной части, свяжемся только с первой строкой (где НомерСтроки = 1):
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХозрасчетныйОбороты.Регистратор КАК Регистратор,
| ХозрасчетныйОбороты.Счет КАК Счет,
| ЕСТЬNULL(СписаниеТаблица.СтатьяДвиженияДенежныхСредств, СписаниеШапка.СтатьяДвиженияДенежныхСредств) КАК СтатьяДДС
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Обороты(
| &ДатаНачала,
| &ДатаОкончания,
| Регистратор,
| Счет В ИЕРАРХИИ (&МассивСчетов),
| ,
| Организация = &Организация,
| ,
| ) КАК ХозрасчетныйОбороты
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.СписаниеСРасчетногоСчета КАК СписаниеШапка
| ПО ХозрасчетныйОбороты.Регистратор = СписаниеШапка.Ссылка
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.СписаниеСРасчетногоСчета.РасшифровкаПлатежа КАК СписаниеТаблица
| ПО ХозрасчетныйОбороты.Регистратор = СписаниеТаблица.Ссылка
| И СписаниеТаблица.НомерСтроки = 1
|УПОРЯДОЧИТЬ ПО
| ХозрасчетныйОбороты.Регистратор.МоментВремени
|АВТОУПОРЯДОЧИВАНИЕ";
Обратите внимание на важные изменения:
ГДЕ в параметры виртуальной таблицы оборотов. Это значительно ускоряет выполнение запроса, так как СУБД будет накладывать фильтры по счетам и организациям на этапе построения временных таблиц, а не после полного сканирования всего регистра.УПОРЯДОЧИТЬ ПО ХозрасчетныйОбороты.Регистратор.МоментВремени вместо обращения через точку Регистратор.Ссылка.МоментВремени избавляет СУБД от лишней неявной операции соединения таблиц.Рассмотрим ситуацию, когда вам необходимо программно прочитать реквизит конкретного документа (например, при обработке интерактивного выбора на форме или при написании внешней обработки) без использования запроса. В этом случае мы должны написать универсальный «защищенный» алгоритм, который проверяет оба возможных места хранения реквизита, при этом избегая необоснованного получения тяжелого объекта.
Для чтения реквизитов без загрузки всего объекта в память используем обращение через точку от ссылки. Помните, что чтение табличной части через точку от ссылки (например, ДокСсылка.РасшифровкаПлатежа) возвращает коллекцию, содержащуюся в кэше ссылки, что работает быстрее, чем получение объекта через ПолучитьОбъект().
Проанализируем следующий пример кода:
// Функция возвращает статью ДДС из переданной ссылки на документ
Функция ПолучитьСтатьюДДСДокумента(ДокСсылка) Экспорт
СтатьяДДС = Справочники.СтатьиДвиженияДенежныхСредств.ПустаяСсылка();
// Проверим, заполнен ли реквизит непосредственно в шапке документа
Если ЗначениеЗаполнено(ДокСсылка.СтатьяДвиженияДенежныхСредств) Тогда
СтатьяДДС = ДокСсылка.СтатьяДвиженияДенежныхСредств;
Иначе
// Если в шапке пусто, обращаемся к первой строке табличной части расшифровки платежа
ТабличнаяЧасть = ДокСсылка.РасшифровкаПлатежа;
Если ТабличнаяЧасть.Количество() > 0 Тогда
СтатьяДДС = ТабличнаяЧасть[0].СтатьяДвиженияДенежныхСредств;
КонецЕсли;
КонецЕсли;
Возврат СтатьяДДС;
КонецФункции
Данный алгоритм полностью безопасен. Он гарантирует, что программа получит корректную статью движения денежных средств вне зависимости от того, как именно был сформирован документ: вручную пользователем в однострочном режиме, распределен по нескольким статьям или загружен программно через обработку интеграции с системой Клиент-Банк.
При работе с банковскими документами в 1С всегда помните о наличии табличной части РасшифровкаПлатежа. Старайтесь избегать прямого обращения к реквизитам шапки таких документов и всегда отдавайте предпочтение чтению данных через запросы с левыми соединениями или через обращение к субконто регистров учета.