При разработке отчетов или обработок в системе 1С программисты часто сталкиваются с задачей пересчета цен из одной валюты в другую прямо в тексте запроса. Одной из типичных проблем является получение курса валюты, выбранной пользователем на форме, и его применение к остаткам товаров или ценам номенклатуры. Ситуация осложняется, когда в справочнике Валюты для российского рубля нет записи в регистре сведений КурсыВалют, так как курс рубля к самому себе всегда равен единице и его редко хранят физически.
Рассмотрим подробно, как правильно связать таблицы в запросе, если у них нет общих полей, и как корректно обработать отсутствие курса для рубля.
Разберем ситуацию, когда нам нужно получить курс конкретной валюты, выбранной пользователем, и применить его ко всем строкам таблицы товаров. Основная сложность заключается в том, что в таблице ТоварыНаСкладахОстатки нет поля Валюта, по которому можно было бы связаться с регистром курсов. В этом случае мы используем механизм Cross Join (перекрестное соединение), который в языке запросов 1С реализуется через ЛЕВОЕ СОЕДИНЕНИЕ с условием ПО ИСТИНА.
Проанализируем пример запроса, дополненный правильной связью:
ВЫБРАТЬ
ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстатки.КоличествоОстаток КАК Количество,
ЕСТЬNULL(ЦеныНоменклатурыСрезПоследних.Цена, 0) / ЕСТЬNULL(КурсыВалютСрезПоследних.Курс, 1) КАК Цена,
ТоварыНаСкладахОстатки.КоличествоОстаток * ЕСТЬNULL(ЦеныНоменклатурыСрезПоследних.Цена, 0) / ЕСТЬNULL(КурсыВалютСрезПоследних.Курс, 1) КАК Стоимость
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, ВидЦены = &ВидЦены) КАК ЦеныНоменклатурыСрезПоследних
ПО ТоварыНаСкладахОстатки.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют.СрезПоследних(, Валюта = &Валюта) КАК КурсыВалютСрезПоследних
ПО ИСТИНА
ГДЕ
ТоварыНаСкладахОстатки.КоличествоОстаток > 0
Выясним причину, почему это работает. Когда мы пишем ПО ИСТИНА, система пытается сопоставить каждую строку из левой таблицы с каждой строкой из правой. Чтобы избежать "раздувания" результата (декартова произведения), крайне важно ограничить правую таблицу в параметрах виртуальной таблицы. В данном случае мы передаем параметр Валюта = &Валюта. Это гарантирует, что в виртуальной таблице КурсыВалют.СрезПоследних всегда будет только одна строка с курсом выбранной валюты. Таким образом, к каждой строке товаров просто "подклеится" это единственное число.
Как мы уже отметили, для валюты "Российский рубль" запись в регистре КурсыВалют может отсутствовать. В этом случае результат соединения вернет значение NULL в поле Курс. Если мы попытаемся разделить цену на NULL, результатом будет NULL, а при выполнении арифметических операций в коде 1С это может привести к ошибкам.
Для решения этой проблемы мы используем функцию ЕСТЬNULL(Поле, ЗаменяющееЗначение). Посмотрим на фрагмент кода:
ЕСТЬNULL(КурсыВалютСрезПоследних.Курс, 1)
Здесь мы говорим системе: "Если курса нет (значение NULL), считай, что он равен 1". Это идеальное решение для рубля, так как деление на 1 не меняет сумму, и расчет стоимости остается верным.
Если в вашей конфигурации валюта регламентированного учета (рубль) является предопределенным элементом справочника, можно сделать логику еще более надежной, используя конструкцию ВЫБОР. Это позволит не обращаться к регистру курсов вовсе, если выбрана национальная валюта.
Рассмотрим пример реализации в поле запроса:
ВЫБОР
КОГДА &Валюта = ЗНАЧЕНИЕ(Справочник.Валюты.Рубль)
ТОГДА 1
ИНАЧЕ ЕСТЬNULL(КурсыВалютСрезПоследних.Курс, 1)
КОНЕЦ КАК АктуальныйКурс
Такой подход защищает логику запроса от случайных пустых значений в регистре и делает код более понятным для других разработчиков. Мы явно декларируем, что для рубля курс всегда единица.
Проанализируем ситуацию, когда в системе используются валюты с большой разницей в номинале (например, вьетнамский донг или старые белорусские рубли). В профессиональных конфигурациях 1С курс всегда используется вместе с кратностью. Даже если в вашем текущем регистре ее нет, хорошей практикой считается учитывать этот параметр.
Формула расчета цены в валюте должна выглядеть так:
ЦенаВалютная = (ЦенаВРублях * Кратность) / Курс
Если вы планируете развивать систему, убедитесь, что в вашем соединении также участвует поле Кратность, и используйте ЕСТЬNULL(КурсыВалютСрезПоследних.Кратность, 1) для расчетов.
Если во всех строках отчета должен использоваться один и тот же курс, который не зависит от даты конкретной строки или номенклатуры, зачастую проще и быстрее получить курс до выполнения запроса на языке 1С и передать его как готовый параметр.
Рассмотрим, как это реализовать в коде обработки:
СтруктураКурса = РегистрыСведений.КурсыВалют.ПолучитьПоследнее(ТекущаяДата(), Новый Структура("Валюта", ВыбраннаяВалюта));
КурсЧислом = ?(СтруктураКурса.Курс = 0, 1, СтруктураКурса.Курс);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Цена / &ПереданныйКурс КАК ЦенаВВалюте
|ИЗ
| ... (ваша таблица)";
Запрос.УстановитьПараметр("ПереданныйКурс", КурсЧислом);
Этот метод имеет несколько преимуществ:
ВЫБОР и ЕСТЬNULL для каждой строки.Подведем итог: если вам необходимо получить курс в запросе, используйте ЛЕВОЕ СОЕДИНЕНИЕ ПО ИСТИНА с обязательным фильтром в параметрах виртуальной таблицы. Если же курс один для всего документа или отчета — передавайте его готовым параметром из встроенного языка.