В современных конфигурациях 1С (таких как «Управление торговлей 11», «ERP Управление предприятием» или «Комплексная автоматизация 2») маркетинговый блок обладает колоссальной гибкостью. Однако оборотной стороной этой гибкости является сложная архитектура данных. Очень часто перед разработчиком встает задача: вывести список карт лояльности, рассчитать по каждой из них текущий оборот (накопленную сумму продаж) и сопоставить с действующим на данный момент процентом скидки.
На первый взгляд задача кажется простой, но при попытке написать прямой запрос многие сталкиваются с тем, что система выдает либо все возможные пороги скидок (например, 3%, 5%, 7% одновременно), либо не связывает оборот с конкретным условием. В этой статье мы подробно разберем, почему возникает эта сложность, и рассмотрим два рабочих решения: чисто «запросный» вариант для простых скидочных систем и универсальный программный метод с использованием СКД.
Проанализируем структуру хранения данных в системе. В типовых конфигурациях скидки и условия их предоставления разделены:
КартыЛояльности) привязана к своему виду карты (справочник ВидыКартЛояльности).ДействиеСкидокНаценок с привязкой к источнику (в нашем случае — к виду карты лояльности).СкидкиНаценки.УсловияПредоставленияСкидокНаценок.Связующим звеном между скидкой и условием является табличная часть УсловияПредоставления внутри справочника СкидкиНаценки. Если в запросе не связать скидку с ее табличной частью условий, а условия — с их конкретными пороговыми значениями, запрос вернет избыточные данные (все доступные проценты скидок без привязки к реальным продажам клиента).
---
Если в вашей базе настроена линейная дисконтная система (например, при накоплении 20 000 руб. — 3%, при 50 000 руб. — 5%, при 100 000 руб. — 10%), и не используются сложные правила совместного применения (вытеснение, сложение, группы «Умножение»), мы можем решить задачу одним запросом.
Разберем по шагам логику построения такого запроса:
КартыЛояльности с регистром накопления ВыручкаИСебестоимостьПродаж.Обороты по партнеру карты.ДействиеСкидокНаценок, отбирая только действующие записи.УсловияПредоставления справочника СкидкиНаценки.УсловияПредоставленияСкидокНаценок, где хранится пороговая сумма накопления (реквизит ЗначениеУсловияОграничения).МАКСИМУМ от значения процента скидки.Рассмотрим пример реализации такого запроса в 1С:
ВЫБРАТЬ
Карты.Ссылка КАК КартаЛояльности,
Карты.Партнер КАК Партнер,
ЕСТЬNULL(ПродажиОбороты.СуммаВыручкиОборот, 0) КАК НакопленныйОборот,
МАКСИМУМ(ЕСТЬNULL(Скидки.ЗначениеСкидкиНаценки, 0)) КАК ПроцентСкидки
ПОМЕСТИТЬ ВТ_КартыИМаксимальнаяСкидка
ИЗ
Справочник.КартыЛояльности КАК Карты
// 1. Получаем накопленные продажи партнера по карте
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(,,, ) КАК ПродажиОбороты
ПО Карты.Партнер = ПродажиОбороты.АналитикаУчетаПоПартнерам.Партнер
// 2. Подтягиваем действующие скидки для вида карты (Владельца карты)
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДействиеСкидокНаценок.СрезПоследних КАК ДействиеСкидок
ПО Карты.Владелец = ДействиеСкидок.Источник
И ДействиеСкидок.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыДействияСкидок.Действует)
// 3. Соединяем скидку с ее табличной частью условий
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.СкидкиНаценки.УсловияПредоставления КАК ТЧУсловия
ПО ДействиеСкидок.СкидкаНаценка = ТЧУсловия.Ссылка
// 4. Получаем настройки самого условия (пороговую сумму)
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.УсловияПредоставленияСкидокНаценок КАК Условия
ПО ТЧУсловия.УсловиеПредоставления = Условия.Ссылка
И Условия.УсловиеПредоставления = ЗНАЧЕНИЕ(Перечисление.УсловияПредоставленияСкидокНаценок.ЗаНакопленныйОбъемПродаж)
ГДЕ
Карты.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыКартЛояльности.Действует)
// 5. Отсекаем скидки, порог которых еще не достигнут по обороту
И ЕСТЬNULL(ПродажиОбороты.СуммаВыручкиОборот, 0) >= ЕСТЬNULL(Условия.ЗначениеУсловияОграничения, 0)
СГРУППИРОВАТЬ ПО
Карты.Ссылка,
Карты.Partner,
ЕСТЬNULL(ПродажиОбороты.СуммаВыручкиОборот, 0)
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.КартаЛояльности КАК КартаЛояльности,
ВТ.Партнер КАК Партнер,
ВТ.НакопленныйОборот КАК НакопленныйОборот,
ВТ.ПроцентСкидки КАК ПроцентСкидки
ИЗ
ВТ_КартыИМаксимальнаяСкидка КАК ВТ
---
Запросный метод идеален по быстродействию, но имеет серьезные функциональные ограничения. Выясним основные причины, почему в крупных базах со сложным маркетингом этот метод не сработает:
---
Для получения 100% достоверного результата лучше всего использовать штатный расчетный механизм конфигурации. Система сама умеет вычислять действующую скидку по карте лояльности, учитывая все сегменты, периоды накопления и вытеснения.
Для этого мы можем разработать отчет на СКД, где в качестве источника данных будет выступать внешняя Таблица Значений (ТЗ), заполняемая и рассчитываемая программно в модуле объекта отчета.
Разберем по шагам, как реализовать этот алгоритм:
ТаблицаКарт.КартаЛояльности (тип СправочникСсылка.КартыЛояльности), Партнер (тип СправочникСсылка.Партнеры), НакопленныйОборот (тип Число), ПроцентСкидки (тип Число).ПриКомпоновкеРезультата.Ниже представлен пример программного кода в модуле объекта отчета, который собирает данные, производит расчет скидок через встроенные механизмы и передает результат в СКД:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// 1. Создаем таблицу значений для СКД
ТаблицаКарт = Новый ТаблицаЗначений;
ТаблицаКарт.Колонки.Добавить("КартаЛояльности", Новый ОписаниеТипов("СправочникСсылка.КартыЛояльности"));
ТаблицаКарт.Колонки.Добавить("Партнер", Новый ОписаниеТипов("СправочникСсылка.Партнеры"));
ТаблицаКарт.Колонки.Добавить("НакопленныйОборот", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
ТаблицаКарт.Колонки.Добавить("ПроцентСкидки", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(5, 2)));
// 2. Выбираем базовые данные по картам и их оборотам простым запросом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Карты.Ссылка КАК КартаЛояльности,
| Карты.Партнер КАК Партнер,
| ЕСТЬNULL(ПродажиОбороты.СуммаВыручкиОборот, 0) КАК НакопленныйОборот
|ИЗ
| Справочник.КартыЛояльности КАК Карты
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты КАК ПродажиОбороты
| ПО Карты.Партнер = ПродажиОбороты.AналитикаУчетаПоПартнерам.Партнер
|ГДЕ
| Карты.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыКартЛояльности.Действует)";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = ТаблицаКарт.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
// 3. Используем штатный механизм расчета скидок.
// Для этого имитируем расчет в памяти (без записи в БД).
// Создаем временную структуру параметров или используем общий модуль:
Попытка
// В зависимости от версии ERP/УТ 11 вызов может немного отличаться.
// Ниже приведен пример получения скидки через штатный механизм расчета по карте:
ПараметрыСкидок = СкидкиНаценкиСервер.ПолучитьПараметрыРасчетаСкидок();
ПараметрыСкидок.Вставить("Партнер", Выборка.Партнер);
ПараметрыСкидок.Вставить("КартаЛояльности", Выборка.КартаЛояльности);
ПараметрыСкидок.Вставить("Дата", ТекущаяДата());
// Получаем таблицу применимых скидок по условиям карты
ТаблицаСкидок = СкидкиНаценкиСервер.РассчитатьАвтоматическиеСкидки(ПараметрыСкидок);
Если ТаблицаСкидок.Количество() > 0 Тогда
// Находим максимальную действующую скидку из рассчитанных системой
НоваяСтрока.ПроцентСкидки = ТаблицаСкидок.Итог("Процент");
Иначе
НоваяСтрока.ПроцентСкидки = 0;
КонецЕсли;
Исключение
НоваяСтрока.ПроцентСкидки = 0; // На случай отсутствия настроек или исключения
КонецПопытки;
КонецЦикла;
// 4. Передаем заполненную таблицу во внешние наборы данных СКД
ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ТаблицаКарт", ТаблицаКарт);
// Инициализируем компоновку и выводим результат
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки);
ДокументРезультат.Очистить();
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
Преимущества программного подхода: этот метод полностью избавляет разработчика от необходимости дублировать сложную логику расчета скидок в коде своего запроса. Любые изменения маркетинговой политики компании (введение новых порогов, изменение правил сложения скидок, перенос условий на другие сегменты номенклатуры) автоматически учтутся в отчете без вмешательства программиста.