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