Каталог решений - Улучшение регистра курсов валют в v8

Улучшение регистра курсов валют в v8

Улучшение регистра курсов валют в v8

В наличии

Небольшая доработка регистра сведений "Курсы валют" для более удобного использования его в запросах.

Категория:

Описание

Регистр курсов валют в стандартных конфигурациях 1С прост и понятен любому программисту. Но он имеет одно существенное неудобство — его сложно использовать в запросах в соединении с другими таблицами из-за того, что в общем случае курс валюты устанавливается не на каждый календарный день. Если на какую-то дату курс не установлен, должен браться курс на предыдущую дату, что на языке запросов громоздко выглядит и долго исполняется.

Для примера возьмем таблицу документов «Заявка» с реквизитами Дата, Сумма, Валюта, и представим, что нам нужно вывести список заявок с суммами, пересчитанными в рубли по курсу на дату каждой заявки.

У проблемы есть четыре решения:

1. Предварительный расчет
———————————

В документ «Заявка» добавляется реквизит «СуммаРуб», которая расчитывается и заполняется при записи заявки.
Такой вариант вполне применим и даже предпочтителен для ряда частных случаев, но универсальным не является. Кроме того, если курс по каким-либо причинам менялся после записи документа (например, документ имеет плановый характер и введен будущей датой), необходимо будет перезаписать документ для обновления рублевой суммы.

2. Административные методы
———————————

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

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

Однако, как и все административные методы, этот чувствителен к человеческому фактору и соблюдению регламента. Также, он не решает вопрос, если сушествуют документы, введенные будущими датами (например, плановые заявки).

3. Хитрый запрос
———————————

Используем двойное соединение с регистром курсов для получения действующего курса на каждую дату:

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

Этот теоретически работающий запрос может выполняться очень долго.

4. Доработка регистра курсов
———————————

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

ВЫБРАТЬ
  Дата, Сумма, Заявки.Валюта, Сумма*Курс КАК СуммаРуб
ИЗ
  Документ.Заявка КАК Заявки
  ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курсы
  ПО (Заявки.Валюта = Курсы.Валюта) И
     (Заявки.Дата >= Курсы.Период) И
     (Заявки.Дата < Курсы.СледующийПериод)

Наибольшие сложности этого метода связаны с доработкой модуля регистра курсов валют для автоматической поддержки актуальности служебного реквизита «СледующийПериод». Когда в регистр добавляется новая запись следует:
1. Определить СледующийПериод для добавляемой записи.
2. Скорректировать СледующийПериод для уже существующей записи, стоящей по хронологии перед добавляемой записью.

Cледующий код решает эту задачу (его необходимо добавить в процедуру «ПриЗаписи» модуля регистра курсов):

  // Подготовимся к заполнению поля "Следующий период": определим область пространства измерений,
  //которые подвергаются изменениям
  ОтборДатаМин = Неопределено; ОтборДатаМакс = Неопределено; ОтборВалюта = Неопределено;
  Если не ОбменДанными.Загрузка Тогда
    Если Замещение Тогда
      ОтборДатаМин = ?(Отбор.Период.Использование, Отбор.Период.Значение, '00010101');
      ОтборДатаМакс = ?(Отбор.Период.Использование, Отбор.Период.Значение, '39991231');
      ОтборВалюта = ?(Отбор.Валюта.Использование, Отбор.Валюта.Значение, Неопределено);
    ИначеЕсли Количество() > 0 Тогда
      ТЗ = Выгрузить();
      ТЗ.Сортировать("Период");
      ОтборДатаМин = ТЗ[0].Период;
      ОтборДатаМакс = ТЗ[ТЗ.Количество()-1].Период;
      ТЗ.Свернуть("Валюта");
      ОтборВалюта = ТЗ.ВыгрузитьКолонку("Валюта");
    КонецЕсли;
  КонецЕсли;
  // Заполнение поля "Следующий период": находим все записи, попавшие в изменяемый период,
  // и для каждой определяем дату следующего курса
  Если ОтборДатаМин <> Неопределено Тогда
    Записи = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
    Записи.ОбменДанными.Загрузка = Истина;
    РЗ = оВыполнитьЗапрос("ВЫБРАТЬ
         |  Период, Валюта, Курс, Кратность, СледующийПериод
         |ИЗ
         |  РегистрСведений.КурсыВалют КАК КурсыВалют
         |ГДЕ
         |  СледующийПериод = &ПустаяДата
         |  ИЛИ (Период <= &ДатаМакс И (Период >= &ДатаМин ИЛИ СледующийПериод >= &ДатаМин)
         |  " + ?(ОтборВалюта = Неопределено, "", ?(ТипЗнч(ОтборВалюта) = Тип("Массив"),
            "И Валюта В (&Валюта)", "И Валюта = &Валюта")) + ")",
         Новый Структура("ДатаМин, ДатаМакс, Валюта, ПустаяДата",
                         ОтборДатаМин, ОтборДатаМакс, ОтборВалюта, '00010101'));
    Выборка = РЗ.Выбрать();
    Пока Выборка.Следующий() Цикл
      Зн = оВыполнитьЗапросВСкаляр("ВЫБРАТЬ ПЕРВЫЕ 1
                     |  Период КАК Период
                     |ИЗ
                     |  РегистрСведений.КурсыВалют КАК КурсыВалют
                     |ГДЕ
                     |  Валюта = &Валюта И Период > &Период
                     |УПОРЯДОЧИТЬ ПО
                     |  Период",
                     Новый Структура("Валюта, Период", Выборка.Валюта, Выборка.Период));
      Зн = ?(Зн = NULL, '39991231', Зн);
      Если Зн <> Выборка.СледующийПериод Тогда
        Записи.Очистить();
        Записи.Отбор.Период.Установить(Выборка.Период);
        Записи.Отбор.Валюта.Установить(Выборка.Валюта);
        Запись = Записи.Добавить();
        Запись.Период = Выборка.Период;
        Запись.Валюта = Выборка.Валюта;
        Запись.Курс = Выборка.Курс;
        Запись.Кратность = Выборка.Кратность;
        Запись.СледующийПериод = Зн;
        Записи.Записать(Истина);
      КонецЕсли;
    КонецЦикла;
  КонецЕсли;

P.S. В коде используются две вспомогательные функции (оВыполнитьЗапрос и оВыполнитьЗапросВСкаляр):

// Функция выполняет произвольный запрос
//
// Параметры
//  ТекстЗапроса – Строка
//  Параметры    – Структура – параметры запроса
//
// Возвращаемое значение:
//  РезультатЗапроса
//
Функция оВыполнитьЗапрос(ТекстЗапроса, Параметры = Неопределено) Экспорт
  Перем Запрос, Зн;
  Запрос = Новый Запрос(ТекстЗапроса);
  Если Параметры <> Неопределено Тогда
    Для каждого Зн из Параметры Цикл
      Запрос.УстановитьПараметр(Зн.Ключ, Зн.Значение)
    КонецЦикла;
  КонецЕсли;
  Возврат Запрос.Выполнить()
КонецФункции

// Функция выполняет произвольный запрос и возвращает значение из 
// первой строки и первой колонки результата.
// Имеет смысл, применяя эту функцию, использовать в тексте запроса конструкцию
// "ВЫБРАТЬ ПЕРВЫЕ 1"
//
// Параметры
//  ТекстЗапроса – Строка
//  Параметры    – Структура – параметры запроса
//
// Возвращаемое значение:
//  Значение из первой строки и первой колонки результата запроса.
//  Если результат запроса пустой, возвращается NULL
//
Функция оВыполнитьЗапросВСкаляр(ТекстЗапроса, Параметры = Неопределено) Экспорт
  Перем Выборка;
  Выборка = оВыполнитьЗапрос(ТекстЗапроса, Параметры).Выбрать(ОбходРезультатаЗапроса.Прямой);
  Возврат ?(Выборка.Следующий(), Выборка[0], NULL);
КонецФункции

// Функция выполняет произвольный запрос и выгружает его результат в таблицу значений
//
// Параметры
//  ТекстЗапроса – Строка
//  Параметры    – Структура – параметры запроса
//
// Возвращаемое значение:
//  ТаблицаЗначений
//
Функция оВыполнитьЗапросВТаблицу(ТекстЗапроса, Параметры = Неопределено) Экспорт
  Возврат оВыполнитьЗапрос(ТекстЗапроса, Параметры).Выгрузить(ОбходРезультатаЗапроса.Прямой)
КонецФункции

// Функция выполняет произвольный запрос и выгружает первую колонку его результата
// в массив
//
// Параметры
//  ТекстЗапроса – Строка
//  Параметры    – Структура – параметры запроса
//
// Возвращаемое значение:
//  Массив
//
Функция оВыполнитьЗапросВМассив(ТекстЗапроса, Параметры = Неопределено) Экспорт
  Возврат оВыполнитьЗапросВТаблицу(ТекстЗапроса, Параметры).ВыгрузитьКолонку(0)
КонецФункции

// Функция выполняет произвольный запрос и выгружает первую колонку его результата
// в список значений
//
// Параметры
//  ТекстЗапроса – Строка
//  Параметры    – Структура – параметры запроса
//
// Возвращаемое значение:
//  Список значений
//
Функция оВыполнитьЗапросВСписок(ТекстЗапроса, Параметры = Неопределено) Экспорт
  Перем Рез;
  Рез = Новый СписокЗначений;
  Рез.ЗагрузитьЗначения(оВыполнитьЗапросВМассив(ТекстЗапроса, Параметры));
  Возврат Рез
КонецФункции

has been added to your cart:
Оформление заказа