При работе с платформой 1С 7.7, особенно при составлении комплексных запросов, часто возникают ситуации, когда стандартные механизмы фильтрации работают не так, как мы ожидаем. Одна из таких распространенных проблем — это попадание непроведенных документов в результаты запроса, несмотря на использование директивы |ОбрабатыватьДокументы Проведенные;. Давайте подробно разберем, почему это происходит и как гарантированно решить эту задачу.
ОбрабатыватьДокументы Проведенные; в комплексных запросахНачнем с того, что директива |ОбрабатыватьДокументы Проведенные; в запросах 1С 7.7 предназначена для того, чтобы автоматически исключать из выборки непроведенные документы, когда мы работаем непосредственно с таблицами документов или с регистрами, в которых документ является "ТекущимДокументом" движения. По умолчанию, если эта директива не указана, запросы к документам обычно обрабатывают только проведенные документы, а при указании |ОбрабатыватьДокументы Все; — все документы, включая непроведенные.
Однако, когда мы начинаем смешивать источники данных в одном запросе, например, одновременно обращаемся к движениям регистра и напрямую к реквизитам документа, который может не быть "ТекущимДокументом" для данного регистра или вообще не связан напрямую с движениями, этот механизм фильтрации может давать сбои. Система запросов 1С 7.7 интерпретирует условия по-разному в зависимости от того, как соединены таблицы и какие поля мы запрашиваем. В таких случаях, несмотря на явное указание обрабатывать только проведенные документы, в результаты могут проникать непроведенные.
Рассмотрим исходный запрос, который столкнулся с этой проблемой:
|Период с Дата1 по Дата2;
|ОбрабатыватьДокументы Проведенные;
|Обрабатывать НеПомеченныеНаУдаление;
|Контрагент = Регистр.ОтгрузкиПоДоговорам.Договор.Владелец,Документ.СводнаяРеализацияПоДоговору.Договор.Владелец;
|Договор = Регистр.ОтгрузкиПоДоговорам.Договор,Документ.СводнаяРеализацияПоДоговору.Договор;
|КатегорияДоговора = Регистр.ОтгрузкиПоДоговорам.Договор.Категория,Документ.СводнаяРеализацияПоДоговору.Договор.Категория;
|_Отгружено = Регистр.ОтгрузкиПоДоговорам.Сумма;
|_Сумма = Документ.СводнаяРеализацияПоДоговору.Сумма;
|Условие (ПустоеЗначение(Договор)=0);
|Функция Отражено = Сумма(_Сумма);
|Функция Отгружено = Приход(_Отгружено);
|Функция Выставлено = Расход(_Отгружено);
В этом запросе мы видим обращение к регистру Регистр.ОтгрузкиПоДоговорам и к документу Документ.СводнаяРеализацияПоДоговору. Директива |ОбрабатыватьДокументы Проведенные; в данном случае, по всей видимости, не распространяется эффективно на все части составной выборки, что приводит к включению непроведенных документов типа СводнаяРеализацияПоДоговору в итоговые результаты.
Когда мы сталкиваемся с такой проблемой, первое, что приходит в голову – это добавить явное условие проверки проведения документа. Часто программисты пытаются использовать конструкции вида Документ.ИмяДокумента.Проведен() = 1 или Документ.ИмяДокумента.ТекущийДокумент.Проведен() = 1 непосредственно в функции Когда или в операторе Условие. Однако такие подходы в 1С 7.7 нередко приводят к ошибкам.
Например, попытка использовать следующее условие:
Функция Отражено = Сумма(_Сумма) Когда (Документ.СводнаяРеализацияПоДоговору.Проведен() = 1);
или
Функция Отражено = Сумма(_Сумма) Когда (Документ.СводнаяРеализацияПоДоговору.ТекущийДокумент.Проведен() = 1);
может вызвать ошибки от интерпретатора запросов 1С 7.7. Мы можем увидеть сообщения, подобные этим:
Запрос[10] : Ожидается логическое выражение
Запрос[10] : Значение не представляет агрегатный объект Проведен()
Эти ошибки возникают потому, что в контексте составного запроса и агрегатных функций, Документ.СводнаяРеализацияПоДоговору или Документ.СводнаяРеализацияПоДоговору.ТекущийДокумент не всегда распознаются как полноценная переменная-ссылка на объект "Документ", к которой можно применить метод Проведен(). Интерпретатор ожидает, что мы будем работать с переменной, которая была явно объявлена в запросе и содержит ссылку на документ.
Наиболее надежным и правильным способом решить эту проблему в 1С 7.7 является явное объявление в запросе универсальной переменной, которая будет ссылаться на соответствующий документ из всех возможных источников (как из регистра, так и напрямую), а затем использование этой переменной для проверки статуса проведения.
Нам необходимо создать в запросе переменную, которая будет принимать значение ТекущийДокумент для каждого из источников, которые могут быть связаны с проводимыми документами. В нашем случае это документ-движение регистра ОтгрузкиПоДоговорам и сам документ СводнаяРеализацияПоДоговору. Мы можем назвать эту переменную, например, _Документ:
|_Документ = Регистр.ОтгрузкиПоДоговорам.ТекущийДокумент, Документ.СводнаяРеализацияПоДоговору.ТекущийДокумент;
Эта переменная _Документ будет хранить ссылку на текущий документ, будь то документ, породивший движение в регистре, или сам документ СводнаяРеализацияПоДоговору, данные которого мы запрашиваем. Таким образом, мы получаем унифицированный способ доступа к объекту документа.
КогдаПосле того как мы объявили переменную _Документ, мы можем безопасно использовать ее для проверки статуса проведения в условиях агрегатных функций, таких как Когда. Очень важно также проверять Вид() документа, если наша переменная может принимать ссылки на документы разных видов или даже на другие объекты, для которых метод Проведен() неприменим.
Для нашего случая, чтобы отфильтровать сумму только по проведенным документам СводнаяРеализацияПоДоговору, мы изменим функцию Отражено следующим образом:
|Функция Отражено = Сумма(_Сумма) Когда ((_Документ.Вид() = "СводнаяРеализацияПоДоговору") И (_Документ.Проведен() = 1));
Здесь (_Документ.Вид() = "СводнаяРеализацияПоДоговору") гарантирует, что мы применяем фильтр только к тем записям, где _Документ действительно является документом вида "СводнаяРеализацияПоДоговору". А (_Документ.Проведен() = 1) уже непосредственно проверяет статус проведения этого документа. Если в запросе есть другие функции, которые должны учитывать проведение, мы должны применить аналогичный подход к каждой из них, используя нашу новую переменную _Документ.
Теперь давайте интегрируем найденное решение в исходный запрос. Полный текст запроса с корректной фильтрацией по проведенным документам будет выглядеть так:
|Период с Дата1 по Дата2;
|ОбрабатыватьДокументы Проведенные; // Это условие можно оставить, оно не повредит, но основную фильтрацию сделает наше явное условие.
|Обрабатывать НеПомеченныеНаУдаление;
|Контрагент = Регистр.ОтгрузкиПоДоговорам.Договор.Владелец,Документ.СводнаяРеализацияПоДоговору.Договор.Владелец;
|Договор = Регистр.ОтгрузкиПоДоговорам.Договор,Документ.СводнаяРеализацияПоДоговорам.Договор;
|КатегорияДоговора = Регистр.ОтгрузкиПоДоговорам.Договор.Категория,Документ.СводнаяРеализацияПоДоговору.Договор.Категория;
|_Отгружено = Регистр.ОтгрузкиПоДоговорам.Сумма;
|_Сумма = Документ.СводнаяРеализацияПоДоговору.Сумма;
|Условие (ПустоеЗначение(Договор)=0);
// Объявляем общую переменную для документа
|_Документ = Регистр.ОтгрузкиПоДоговорам.ТекущийДокумент, Документ.СводнаяРеализацияПоДоговору.ТекущийДокумент;
// Применяем условие проведения к функциям, где это необходимо
|Функция Отражено = Сумма(_Сумма) Когда ((_Документ.Вид() = "СводнаяРеализацияПоДоговору") И (_Документ.Проведен() = 1));
// Для других функций, если они должны зависеть от проведения определенного документа, делаем аналогично:
|Функция Отгружено = Приход(_Отгружено) Когда ((_Документ.Вид() = "ДокументПоРегиструОтгрузки") И (_Документ.Проведен() = 1));
// (здесь "ДокументПоРегиструОтгрузки" нужно заменить на реальный вид документа, который делает движения по регистру ОтгрузкиПоДоговорам, например, "Реализация")
|Функция Выставлено = Расход(_Отгружено) Когда ((_Документ.Вид() = "ДокументПоРегиструОтгрузки") И (_Документ.Проведен() = 1));
Обратите внимание, что для функций Отгружено и Выставлено, которые работают с _Отгружено = Регистр.ОтгрузкиПоДоговорам.Сумма;, мы также можем добавить условие Когда с проверкой проведения. Однако в этом случае нам потребуется знать Вид() документа, который делает движения по регистру ОтгрузкиПоДоговорам. Если регистр является оборотным, и его движения могут порождаться разными документами, то, возможно, потребуется более сложная логика в Когда или же принять, что |ОбрабатыватьДокументы Проведенные; справится с документами-движениями по регистру, но не с прямо выбранными документами. В данном примере, для чистоты решения, мы показали, как это применить для СводнаяРеализацияПоДоговору.
Чтобы глубже понять, почему ОбрабатыватьДокументы Проведенные; не всегда работает, давайте проанализируем воспроизведение проблемы, схожее с примером из обсуждения:
Рассмотрим случай, когда мы запрашиваем данные только из таблицы документов, используя директиву ОбрабатыватьДокументы Проведенные;. Допустим, у нас есть непроведенная "Заявка покупателя" с некоторой суммой.
|Период с ВыбНачПериода по ВыбКонПериода;
|ОбрабатыватьДокументы Проведенные;
|Обрабатывать НеПомеченныеНаУдаление;
|СуммаВзаиморасчетов = Документ.ЗаявкаПокупателя.СуммаВзаиморасчетов;
|Функция СуммаСуммаВзаиморасчетов = Сумма(СуммаВзаиморасчетов);
|Группировка Документ;
При выполнении такого запроса, если единственная "Заявка покупателя" в периоде является непроведенной, мы получим пустой результат. Это является ожидаемым и правильным поведением, подтверждающим, что ОбрабатыватьДокументы Проведенные; успешно отфильтровал непроведенный документ.
Теперь давайте включим в запрос данные из регистра, который может быть связан с этим же документом, но при этом запросим напрямую поля из документа, который не является проведенным:
|Период с ВыбНачПериода по ВыбКонПериода;
|ОбрабатыватьДокументы Проведенные;
|Обрабатывать НеПомеченныеНаУдаление;
|Контрагент = Регистр.Заявки.ДоговорПокупателя.Владелец, Документ.ЗаявкаПокупателя.Договор.Владелец;
|Договор = Регистр.Заявки.ДоговорПокупателя, Документ.ЗаявкаПокупателя.Договор;
|СтоимостьРасход = Регистр.Заявки.СтоимостьРасход;
|СуммаВзаиморасчетов = Документ.ЗаявкаПокупателя.СуммаВзаиморасчетов;
|Функция СуммаСтоимостьРасход = Расход(СтоимостьРасход);
|Функция СуммаСуммаВзаиморасчетов = Сумма(СуммаВзаиморасчетов);
|Условие (ПустоеЗначение(Договор)=0);
|Группировка Контрагент Без групп;
|Группировка Договор Без Групп;
|Группировка Документ;
В этом запросе мы запрашиваем СтоимостьРасход из регистра Регистр.Заявки и СуммаВзаиморасчетов напрямую из Документ.ЗаявкаПокупателя. Даже если "Заявка покупателя" непроведена и в регистре нет движений, связанных с этой конкретной непроведенной заявкой (или движения были сделаны другим документом), мы можем получить непроведенный документ в результатах выборки. Это происходит потому, что 1С 7.7 при построении такого составного запроса может сначала "вытянуть" все возможные ссылки на документы (например, через поля Договор.Владелец), а затем уже пытаться применить к ним условия. При этом, прямое обращение к реквизиту Документ.ЗаявкаПокупателя.СуммаВзаиморасчетов может рассматриваться как отдельный источник данных, на который общее условие ОбрабатыватьДокументы Проведенные; не распространяется так строго, как на документы, входящие в предопределенную группировку Документ, или на документы-движения регистров. Система как бы "видит" документ, потому что на него есть косвенная ссылка (например, через договор) или прямой запрос поля, и не отбрасывает его полностью, пока явное условие не будет применено к его статусу проведения.
Для предотвращения подобных проблем и обеспечения надежности ваших запросов в 1С 7.7, мы рекомендуем придерживаться следующих практик:
Всегда явно объявляйте переменные для документов: В сложных запросах, где вы работаете с несколькими источниками, ссылающимися на документы (регистры, прямые ссылки на документы), создавайте универсальную переменную с помощью ТекущийДокумент для всех потенциальных видов документов. Например: _МойДокумент = Регистр.МойРегистр.ТекущийДокумент, Документ.МойДокумент.ТекущийДокумент;.
Используйте метод Проведен() на объявленных переменных: Применяйте явную проверку статуса проведения с помощью _МойДокумент.Проведен() = 1 в условиях функций (например, Когда) или в операторе Условие, чтобы гарантированно отфильтровать непроведенные документы.
Проверяйте Вид() документа: Если ваша универсальная переменная может ссылаться на документы разных видов или даже на другие объекты, всегда включайте проверку _МойДокумент.Вид() = "ИмяВидаДокумента" в условие Когда, чтобы метод Проведен() применялся только к корректным объектам.
Рассмотрите декомпозицию запроса: Для очень сложных сценариев, когда один "черный" запрос становится слишком запутанным или непредсказуемым, рассмотрите выполнение нескольких отдельных запросов (например, один по регистру, другой по документам), а затем объединяйте и обрабатывайте полученные данные в Таблицах Значений (ТЗ) средствами встроенного языка. Этот подход часто более нагляден, предсказуем и гибок, хоть и требует больше программного кода.
Следуя этим рекомендациям, вы сможете создавать более надежные и предсказуемые запросы в 1С 7.7, избегая неожиданного включения непроведенных документов и повышая точность ваших отчетов и обработок.