При разработке отчетов в системе компоновки данных (СКД) часто возникает задача пересчета сумм из одной валюты в другую. Однако стандартные механизмы не всегда подходят, если бизнес-логика требует использовать не курс на дату документа, а средний курс за выбранный период. Сложность усугубляется тем, что курсы в справочник валют могут заноситься не ежедневно, а только по мере их изменения. В такой ситуации нам необходимо сначала «размножить» курсы на каждый календарный день периода, вычислить их среднее арифметическое и только затем использовать это число для пересчета.
В этой статье мы разберем, как реализовать подобную задачу, объединив данные о продажах, производственный календарь и регистр сведений курсов валют.
Если мы просто обратимся к регистру сведений КурсыВалют и применим функцию СРЕДНЕЕ, результат будет неверным. Например, если курс 28.0 держался 29 дней, а в последний день месяца стал 30.0, то среднее по записям регистра (где всего две записи) составит 29.0. Однако фактическое среднее по дням будет гораздо ближе к 28.0. Поэтому наша первоочередная задача — получить значение курса на каждую календарную дату периода.
Для решения этой задачи воспользуемся соединением регистра сведений с календарем. Мы проанализируем подход с использованием временных таблиц внутри одного набора данных запроса. Рассмотрим структуру запроса, который формирует таблицу курсов на все дни:
ВЫБРАТЬ
Календарь.ДатаКалендаря КАК Дата,
МАКСИМУМ(КурсыВалют.Период) КАК ПериодКурса
ПОМЕСТИТЬ ВТ_МаксимальныеДаты
ИЗ
РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК Календарь
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют
ПО КурсыВалют.Период <= Календарь.ДатаКалендаря
ГДЕ
Календарь.ДатаКалендаря МЕЖДУ &НачалоПериода И &КонецПериода
И КурсыВалют.Валюта = &Валюта
СГРУППИРОВАТЬ ПО
Календарь.ДатаКалендаря
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_МаксимальныеДаты.Дата КАК ДатаКалендаря,
КурсыВалют.Курс / КурсыВалют.Кратность КАК КурсЧислом
ПОМЕСТИТЬ ВТ_КурсыНаКаждыйДень
ИЗ
ВТ_МаксимальныеДаты КАК ВТ_МаксимальныеДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют
ПО ВТ_МаксимальныеДаты.ПериодКурса = КурсыВалют.Период
ГДЕ
КурсыВалют.Валюта = &Валюта
В этом пакете запросов мы сначала находим для каждого дня из РегламентированныйПроизводственныйКалендарь наиболее позднюю дату записи в регистре курсов (через условие <=). Затем во втором запросе получаем само значение курса на эту найденную дату.
Теперь, когда у нас есть курс на каждый день, нам нужно получить одно-единственное число — среднее значение. Мы можем сделать это в следующем временном пакете или в основном запросе. Проанализируем, как это выглядит:
ВЫБРАТЬ
СРЕДНЕЕ(ВТ_КурсыНаКаждыйДень.КурсЧислом) КАК СреднийКурсЗаПериод
ПОМЕСТИТЬ ВТ_ИтоговыйКурс
ИЗ
ВТ_КурсыНаКаждыйДень КАК ВТ_КурсыНаКаждыйДень
Важный нюанс: если вам нужно округление до двух знаков именно на этом этапе, используйте ВЫРАЗИТЬ(СРЕДНЕЕ(...) КАК ЧИСЛО(15, 2)). Однако помните, что преждевременное округление может привести к потере точности в итоговых суммах отчета.
Выясним, как применить полученный курс к данным о продажах. У нас есть два основных пути в СКД:
ВТ_ИтоговыйКурс к основной таблице продаж по условию ИСТИНА. Поскольку в таблице курса всего одна строка, данные продаж не задублируются, а в каждой строке появится поле со средним курсом.Рассмотрим пример основного запроса с использованием пересчета:
ВЫБРАТЬ
Продажи.Номенклатура,
Продажи.СуммаОборот КАК СуммаГРН,
Продажи.СуммаОборот / ВТ_ИтоговыйКурс.СреднийКурсЗаПериод КАК СуммаUSD
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода) КАК Продажи
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИтоговыйКурс КАК ВТ_ИтоговыйКурс
ПО (ИСТИНА)
Если логика пересчета сложная (например, часть документов уже в долларах, а часть нужно пересчитать), рекомендуется вынести формулу в Вычисляемые поля СКД. Пропишем выражение для поля СуммаВДолларах:
ВЫБОР КОГДА ВалютаДокумента = &ВалютаДол ТАКДА СуммаДокумента ИНАЧЕ СуммаДокумента / СреднийКурсЗаПериод КОНЕЦ
Для корректного расчета итогов по отчету, в закладке Ресурсы необходимо указать выражение суммы для этого поля: Сумма(СуммаВДолларах). Это обеспечит правильное суммирование уже пересчитанных значений по всем группировкам.
Если расчет среднего курса в запросе кажется слишком тяжелым для базы данных (например, при очень больших периодах), можно рассчитать значение программно в модуле отчета. В процедуре ПриКомпоновкеРезультата мы можем выполнить вспомогательный запрос, получить число и установить его в параметр СКД:
СреднийКурс = ПолучитьСреднийКурсЗаПериод(ПараметрыДанных.НачалоПериода.Значение, ПараметрыДанных.КонецПериода.Значение);
КомпоновщикНастроек.Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("СреднийКурс", СреднийКурс);
В этом случае в самом запросе СКД вам достаточно будет просто делить сумму на &СреднийКурс. Это наиболее производительный и стабильный метод, исключающий ошибки «размножения» строк при некорректных соединениях с календарем.
1. Ошибка пустого календаря. Если в вашей базе не заполнен РегламентированныйПроизводственныйКалендарь на текущий год, соединение в запросе выдаст пустой результат, и курс будет равен 0. Всегда проверяйте заполненность календаря или используйте генерацию последовательности дат в самом запросе.
2. Проблема точности. Как было замечено в обсуждении, разница в одну копейку может возникать из-за разного порядка операций: делить каждую продажу на средний курс и суммировать или суммировать продажи и один раз делить итог. Выясните у бухгалтерии, какой алгоритм считается эталонным.
3. Использование СрезаПоследних. Помните, что виртуальная таблица СрезПоследних в запросе принимает только одну дату. Для получения курсов на каждую дату периода она не подходит, поэтому использование соединения с условием КурсыВалют.Период <= Календарь.ДатаКалендаря является единственным верным способом внутри запроса.
Проанализировав эти подходы, мы видим, что задача пересчета сумм по среднему курсу требует внимательного отношения к агрегации данных. Использование временных таблиц для эмуляции среза последних на каждый день — это мощный инструмент, который позволяет сделать отчет гибким и точным.