Как конвертировать сумму документа в рубли в СКД?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Торговля и дистрибуция
← На главную

При разработке отчетов в системе компоновки данных (СКД) программисты часто сталкиваются с необходимостью пересчета сумм документов, проведенных в иностранной валюте (USD, EUR и др.), в рубли. Казалось бы, задача простая: нужно получить курс валюты на дату документа и умножить на него сумму. Однако стандартное левое соединение с виртуальной таблицей среза последних часто работает некорректно или сильно замедляет отчет. В этой статье мы подробно разберем, почему возникает эта проблема и какими способами ее можно решить.

Выясняем причину: почему не работает простое левое соединение

Рассмотрим типичную ошибку, которую совершают начинающие разработчики при попытке связать документы с курсами валют. Запрос выглядит примерно так:


ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют.СрезПоследних(, ) КАК КурсыВалютСрезПоследних
    ПО ЗаказКлиента.Дата >= КурсыВалютСрезПоследних.Период

При таком подходе расчет рублевой суммы часто выдает некорректные данные (сумма в рублях оказывается равной сумме в валюте, либо курс берется не на дату документа). Выясним причину этой проблемы на уровне работы СУБД.

Виртуальная таблица СрезПоследних без указания конкретной даты в параметрах рассчитывается системой ровно один раз — на текущий момент времени. Она возвращает плоскую таблицу, в которой содержится ровно по одной строке для каждой валюты с ее самым последним актуальным курсом. Когда СУБД выполняет последующее левое соединение по условию Дата >= Период, она сопоставляет дату документа со статичным значением периода последней записи из регистра. Пересчета среза на каждую дату документа при этом не происходит. Именно поэтому расчет возвращает неверные результаты.

Давайте разберем три надежных и эффективных способа решения этой задачи в СКД.

---

Способ 1. Связи наборов данных в СКД (Рекомендуемый способ)

Самый элегантный, производительный и простой в реализации способ — переложить расчет среза последних на механизмы самой СКД. Вместо написания громоздких SQL-запросов мы можем использовать возможности связывания наборов данных.

Разберем этот способ по шагам:

  1. Создадим Набор данных - запрос с именем НаборДанныхЗаказы. В нем мы выберем данные документов без всяких соединений с курсами валют:
    
    ВЫБРАТЬ
        ЗаказКлиента.Ссылка КАК Документ,
        ЗаказКлиента.Дата КАК Дата,
        ЗаказКлиента.Валюта КАК Валюта,
        ЗаказКлиента.СуммаДокумента КАК СуммаДокумента
    ИЗ
        Документ.ЗаказКлиента КАК ЗаказКлиента
    ГДЕ
        ЗаказКлиента.Проведен
    
  2. Создадим второй Набор данных - запрос с именем НаборДанныхКурсы. Этот запрос будет выбирать курс на определенную дату:
    
    ВЫБРАТЬ
        КурсыВалютСрезПоследних.Валюта КАК Валюта,
        КурсыВалютСрезПоследних.Курс КАК Курс,
        КурсыВалютСрезПоследних.Кратность КАК Кратность
    ИЗ
        РегистрСведений.КурсыВалют.СрезПоследних(&ПериодКурса, Валюта = &ВалютаКурса) КАК КурсыВалютСрезПоследних
    
  3. Перейдем на вкладку Связи наборов данных и добавим новую строку:
    • Источник связи: НаборДанныхЗаказы
    • Приемник связи: НаборДанныхКурсы
    • Условие связи: Валюта = Валюта
    • Параметры:
      • ПериодКурса = Дата (передаем дату документа в параметр виртуальной таблицы)
      • ВалютаКурса = Валюта (передаем валюту документа в параметр)
  4. В поле «Вычисляемые поля» добавим строку СуммаВРублях с выражением:
    СуммаДокумента * ЕСТЬNULL(Курс, 1) / ЕСТЬNULL(Кратность, 1)

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

---

Способ 2. Классический срез последних на каждую дату в одном запросе

Если вам необходимо получить данные строго в рамках одного запроса (например, для использования в динамическом списке или простом отчете), то классический алгоритм «среза последних на каждую дату» реализуется через пакетный запрос.

Проанализируем ситуацию по шагам:

  1. Сначала мы выбираем документы во временную таблицу ВТ_Документы.
  2. Затем соединяем полученную таблицу с регистром сведений КурсыВалют по условию КурсыВалют.Период <= ВТ_Документы.Дата и группируем результат, чтобы найти максимальную дату курса (период) для каждого документа на момент его проведения.
  3. Полученную таблицу максимальных дат соединяем обратно с регистром сведений, чтобы получить физические значения ресурсов Курс и Кратность.

Посмотрим на пример реализации такого запроса:


ВЫБРАТЬ
    ЗаказКлиента.Ссылка КАК Документ,
    ЗаказКлиента.Дата КАК Дата,
    ЗаказКлиента.Валюта КАК Валюта,
    ЗаказКлиента.СуммаДокумента КАК СуммаДокумента
ПОМЕСТИТЬ ВТ_Документы
ИЗ
    Документ.ЗаказКлиента КАК ЗаказКлиента
ГДЕ
    ЗаказКлиента.Проведен
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Документы.Документ КАК Документ,
    ВТ_Документы.Валюта КАК Валюта,
    МАКСИМУМ(КурсыВалют.Период) КАК ПериодКурса
ПОМЕСТИТЬ ВТ_МаксимальныеДатыКурсов
ИЗ
    ВТ_Документы КАК ВТ_Документы
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют
        ПО ВТ_Документы.Валюта = КурсыВалют.Валюта
        И ВТ_Документы.Дата >= КурсыВалют.Период
СГРУППИРОВАТЬ ПО
    ВТ_Документы.Документ,
    ВТ_Документы.Валюта
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Документы.Документ КАК Документ,
    ВТ_Документы.Дата КАК Дата,
    ВТ_Документы.Валюта КАК Валюта,
    ВТ_Документы.СуммаДокумента КАК СуммаДокумента,
    ЕСТЬNULL(КурсыВалют.Курс, 1) КАК Курс,
    ЕСТЬNULL(КурсыВалют.Кратность, 1) КАК Кратность,
    ВТ_Документы.СуммаДокумента * ЕСТЬNULL(КурсыВалют.Курс, 1) / ЕСТЬNULL(КурсыВалют.Кратность, 1) КАК СуммаВРублях
ИЗ
    ВТ_Документы КАК ВТ_Документы
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_МаксимальныеДатыКурсов КАК ВТ_МаксимальныеДатыКурсов
        ПО ВТ_Документы.Документ = ВТ_МаксимальныеДатыКурсов.Документ
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют
        ПО ВТ_МаксимальныеДатыКурсов.Валюта = КурсыВалют.Валюта
        И ВТ_МаксимальныеДатыКурсов.PeriodКурса = КурсыВалют.Период

Важное примечание: обязательно учитывайте поле Кратность. Игнорирование кратности при расчете — частая причина искажения сумм для редких валют (например, японская иена или казахский тенге), курс которых в системе регистрируется за 10, 100 или 1000 единиц.

---

Способ 3. Особенности архитектуры современных конфигураций (на примере УТ 11)

Если вы разрабатываете отчет для современных ERP-систем фирмы 1С (таких как «Управление торговлей 11», «Комплексная автоматизация 2» или «ERP Управление предприятием»), то стандартные подходы можно существенно оптимизировать, зная архитектурные нюансы этих конфигураций.

Нюанс 1. Курс уже сохранен в самом документе
В конфигурации УТ 11 документы, создаваемые в валюте, отличной от валюты регламентированного учета (рублей), при проведении записывают курс и кратность непосредственно в свои реквизиты шапки (или табличных частей). Это сделано для того, чтобы изменения курсов в регистре задним числом не ломали уже проведенные документы. Обратившись напрямую к реквизитам документа (например, ЗаказКлиента.Курс и ЗаказКлиента.Кратность), вы получите точные данные без единого соединения с регистрами сведений.

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

При проведении документов система автоматически пересчитывает все суммы в валюту регламентированного (рубли) и управленческого учета. В регистр пишутся готовые показатели:

Используя виртуальные таблицы оборотов этих регистров, мы сразу получаем точные, готовые рублевые суммы, рассчитанные системой штатно в момент проведения документов. Наш запрос становится максимально простым, быстрым и надежным.

← На главную