Исходная ситуация: в базе настроено большое количество автоматических скидок, до определенного момента скидки применялись без проблем.
Описание проблемы: в один прекрасный день при расчете скидок 1С перестала применять некоторый список скидок. При просмотре списка назначаемых скидок видим, что скидки не применяются из-за того, что номенклатура не соответствует настроенному отбору.
Естественно, первое, что приходит в голову - это проверить настройки отбора и реквизиты номенклатуры.
Условия скидки просты:
- наличие карты лояльности
- определенный вид цены
- у номенклатуры задана определенная марка
- у номенклатуры задана категория с определенным доп.реквизитом
Увы, если бы было все так просто, то не было бы этой статьи) Карта действует, вид цены нужный, реквизиты номенклатуры соответствуют отбору
Ну теперь нам ничего не остается, как лезть в конфигуратор. Тут самое первое, что проверяем - это доработки.
Итак, после долгих прыганий по процедурам и функциям находим функцию ВыполнитьЗапросыУсловийПредоставления общего модуля "СкидкиНаценкиСервер": она возвращает таблицу выполненных условий - то, что нужно!
Для поиска причин проблемы создаем заказ с одной строкой. В заказе указываем все необходимые для предоставления скидки данные. В вышеуказанной функции находим цикл обхода всех скидок, в котором в общий пакет запросов добавляются запросы на проверку условий выполнения:
Для Каждого СтрокаДерева Из СкидкиНаценки Цикл
Если СтрокаДерева.ВариантОтбораНоменклатуры = Перечисления.ВариантыОтбораНоменклатурыДляРасчетаСкидокНаценок.БезОграничений
И Не СтрокаДерева.УстановленДополнительныйОтбор Тогда
Продолжить;
КонецЕсли;
Ключ = СтрокаДерева.Ссылка;
Настройки = СтрокаДерева.ХранилищеНастроекКомпоновкиДанных.Получить();
Если Настройки <> Неопределено И Настройки.Свойство("Запрос") Тогда
КоличествоЗапросовВПакете = Настройки.Запрос.КоличествоЗапросовВПакете;
ИндексЗапросаРезультата = Настройки.Запрос.ИндексЗапросаРезультата;
ТекстЗапроса = Настройки.Запрос.ТекстЗапроса;
Запрос = Новый Запрос(ТекстЗапроса);
Для Каждого Параметр Из Настройки.Запрос.Параметры Цикл
Запрос.УстановитьПараметр(Параметр.Ключ, Параметр.Значение);
КонецЦикла;
УдалятьВременныеТаблицы = Ложь;
ПреобразовыватьПараметры = Истина;
ЗаполнитьПараметрыЗапросОпределенияПозицийНоменклатурыНаКоторыеПредоставляетсяСкидка(Запрос, ПараметрыРасчета, "ПараметрыРасчета");
ПакетЗапросовВставитьЗапросВПакет(
Запрос, Пакет, Ключ,
УдалятьВременныеТаблицы, ПреобразовыватьПараметры,
КоличествоЗапросовВПакете,
ИндексЗапросаРезультата);
Иначе
УдалятьВременныеТаблицы = Истина;
ПреобразовыватьПараметры = Истина;
Запрос = ЗапросОпределенияПозицийНоменклатурыНаКоторыеПредоставляетсяСкидка(СтрокаДерева, ПараметрыРасчета, "Все");
ПакетЗапросовВставитьЗапросВПакет(
Запрос, Пакет, Ключ,
УдалятьВременныеТаблицы, ПреобразовыватьПараметры);
КонецЕсли;
КонецЦикла;
Но и тут оказалось все в порядке: запрос рабочий и параметры устанавливаются корректные.
После проверок запроса на выполнение условий по конкретной скидке с горьким вздохом возвращаемся в конфигуратор, но смотрим уже на результирующий запрос. Как уже писал, различных скидок настроено достаточно много и результирующий запрос состоит из 1336 пакетов. Но здесь сразу бросается в глаза тот факт, что большинство параметров стали с уникальным идентификатором, например, &Пe7b15fec_b10d_46b9_8e96_95e145435196. Цепляемся за эту ниточку и начинаем анализировать объединение запросов по каждой скидке в единый запрос.
Итак, пройдемся по процедуре СкидкиНаценкиСервер.ПакетЗапросовВставитьЗапросВПакет. Собственно, нас не интересует вся процедура, а только часть кода, которая преобразует локальные параметры в глобальные:
Если ПреобразовыватьПараметры Тогда
Для Каждого Параметр Из Запрос.Параметры Цикл
НовоеИмя = ПакетЗапросовПреобразоватьПараметр(Запрос.Текст, Параметр.Ключ, Параметр.Значение, ПакетЗапросов);
Если НовоеИмя <> Неопределено Тогда
ПакетЗапросов.Запрос.УстановитьПараметр(НовоеИмя, Параметр.Значение);
ПакетЗапросов.Параметры.Вставить(Параметр.Значение, НовоеИмя);
КонецЕсли;
КонецЦикла;
КонецЕсли;
Суть здесь простая: чтобы не плодить параметры с одинаковым значением, параметры из всех запросов надо объединить.
Что имеем на входе:
- Имя локального параметра
- Значение локального параметра
- Соответствия значений и имен глобальных параметров:
- Ключ - значение параметра
- Значение - имя глобального параметра
Непосредственно преобразование параметров происходит в функции СкидкиНаценкиСервер.ПакетЗапросовПреобразоватьПараметр. Итак, нас интересует случай, когда значение параметра - это ссылочный тип:
Если ПакетЗапросов <> Неопределено Тогда
Ключ = ПакетЗапросов.Параметры.Получить(ЗначениеПараметра);
КонецЕсли;
Если Ключ = Неопределено Тогда
Если ПакетЗапросов <> Неопределено И ПакетЗапросов.Запрос.Параметры.Свойство(ИсходноеИмя) Тогда
НовоеИмя = "П" + СтрЗаменить(Новый УникальныйИдентификатор,"-","_");
ЗаменитьИмяПараметраВЗапросе(ТекстЗапроса, ИсходноеИмя, НовоеИмя, Постфиксы);
Иначе
НовоеИмя = ИсходноеИмя;
КонецЕсли;
Иначе
ЗаменитьИмяПараметраВЗапросе(ТекстЗапроса, ИсходноеИмя, Ключ, Постфиксы);
КонецЕсли;
Первым делом проверяем, а не задан ли уже этот параметр в результирующем запросе:
Ключ = ПакетЗапросов.Параметры.Получить(ЗначениеПараметра);
В коде прописано несколько вариантов возможных ситуаций:
1) Ключ = Неопределено и имя локального параметра уже используется в результирующем запросе
2) Ключ = Неопределено и имя локального параметра не используется в результирующем запросе
3) Ключ <> Неопределено
И соответствующие обработчики:
1) Задается новое имя глобального параметра и в тексте запроса происходит замена локального параметра на новое глобальное
2) Локальное имя параметра переносится в список глобальных без изменений, текст запроса не изменяется
3) В тексте запроса происходит замена локального параметра на найденное глобальное
И вроде бы все ничего, но... Давайте посмотрим, что происходит с нашим запросом. Итак
ВЫБРАТЬ
ФильтрПоНоменклатуре.КлючСвязи КАК КлючСвязи
ПОМЕСТИТЬ ФильтрПоНоменклатуре
ИЗ
ВременнаяТаблицаТовары КАК ФильтрПоНоменклатуре
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ТоварныеКатегории.ДополнительныеРеквизиты КАК ТоварныеКатегорииДополнительныеРеквизиты
ПО (ТоварныеКатегорииДополнительныеРеквизиты.Ссылка = ФильтрПоНоменклатуре.Номенклатура.ТоварнаяКатегория
И ТоварныеКатегорииДополнительныеРеквизиты.Свойство = &Пe7b15fec_b10d_46b9_8e96_95e145435196)
ГДЕ
ТоварныеКатегорииДополнительныеРеквизиты.Значение = &П2_Кэшируется
И ФильтрПоНоменклатуре.Номенклатура.Марка = &П3_Кэшируется
И ФильтрПоНоменклатуре.ВидЦены = &П4_Кэшируется
;
////////////////////////////////////////////////////////////////////////////////
// #Результат#
ВЫБРАТЬ
Товары.КлючСвязи КАК КлючСвязи,
Неопределено КАК ЗначениеПоказателя,
-1 КАК КратностьВыполнения
ИЗ
ФильтрПоНоменклатуре КАК Товары
;
////////////////////////////////////////////////////////////////////////////////
УНИЧТОЖИТЬ ФильтрПоНоменклатуре
На определенном этапе локальный параметр &П2_Кэшируется (элемент справочника "ЗначенияСвойствОбъектов") подменяется на глобальный &П4_Кэшируется (найден по значению параметра) и запрос становится следующим:
ВЫБРАТЬ
ФильтрПоНоменклатуре.КлючСвязи КАК КлючСвязи
ПОМЕСТИТЬ ФильтрПоНоменклатуре
ИЗ
ВременнаяТаблицаТовары КАК ФильтрПоНоменклатуре
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ТоварныеКатегории.ДополнительныеРеквизиты КАК ТоварныеКатегорииДополнительныеРеквизиты
ПО (ТоварныеКатегорииДополнительныеРеквизиты.Ссылка = ФильтрПоНоменклатуре.Номенклатура.ТоварнаяКатегория
И ТоварныеКатегорииДополнительныеРеквизиты.Свойство = &Пe7b15fec_b10d_46b9_8e96_95e145435196)
ГДЕ
ТоварныеКатегорииДополнительныеРеквизиты.Значение = &П4_Кэшируется
И ФильтрПоНоменклатуре.Номенклатура.Марка = &П3_Кэшируется
И ФильтрПоНоменклатуре.ВидЦены = &П4_Кэшируется
;
////////////////////////////////////////////////////////////////////////////////
// #Результат#
ВЫБРАТЬ
Товары.КлючСвязи КАК КлючСвязи,
Неопределено КАК ЗначениеПоказателя,
-1 КАК КратностьВыполнения
ИЗ
ФильтрПоНоменклатуре КАК Товары
;
////////////////////////////////////////////////////////////////////////////////
УНИЧТОЖИТЬ ФильтрПоНоменклатуре
И вот он подвох: у условий по виду цены и по значению дополнительного реквизита теперь один параметр &П4_Кэшируется!
А все из-за того, что не была обработана ситуация:
4) Ключ <> Неопределено и найденное имя глобального параметра (Ключ) уже используется в локальном запросе
То есть следует внести примерно следующие изменения:
Если ПакетЗапросов <> Неопределено Тогда
Ключ = ПакетЗапросов.Параметры.Получить(ЗначениеПараметра);
КонецЕсли;
Если Ключ = Неопределено Тогда
Если ПакетЗапросов <> Неопределено И ПакетЗапросов.Запрос.Параметры.Свойство(ИсходноеИмя) Тогда
НовоеИмя = "П" + СтрЗаменить(Новый УникальныйИдентификатор,"-","_");
ЗаменитьИмяПараметраВЗапросе(ТекстЗапроса, ИсходноеИмя, НовоеИмя, Постфиксы);
Иначе
НовоеИмя = ИсходноеИмя;
КонецЕсли;
#Удаление
Иначе
#КонецУдаления
#Вставка
ИначеЕсли Ключ <> Неопределено И СтрНайти(ТекстЗапроса, Ключ) > 0 Тогда
НовоеИмя = "П" + СтрЗаменить(Новый УникальныйИдентификатор,"-","_");
ЗаменитьИмяПараметраВЗапросе(ТекстЗапроса, ИсходноеИмя, НовоеИмя, Постфиксы);
Иначе
#КонецВставки
ЗаменитьИмяПараметраВЗапросе(ТекстЗапроса, ИсходноеИмя, Ключ, Постфиксы);
КонецЕсли;
В итоге, в случае, если имя глобального параметра используется в локальном запросе, то локальный параметр все равно переносим в результирующий запрос с новым именем.
Ну и вариант для самых ленивых - это закомментить поиск по глобальным параметрам:
Если ПакетЗапросов <> Неопределено Тогда
Ключ = ПакетЗапросов.Параметры.Получить(ЗначениеПараметра);
КонецЕсли;
В этом случае в результирующий запрос все локальные параметры будут всегда добавляться с уникальным именем. Этот вариант тоже попробовал и замеры производительности не показали увеличение времени выполнения запроса) (0.93 с. после изменений против 1.01 до изменений)
Надеюсь, что данная статья поможет вам сэкономить значительное количество времени!