Как выбрать карты лояльности с оборотом и процентом действующей скидки в 1С

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

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

На первый взгляд задача кажется простой, но при попытке написать прямой запрос многие сталкиваются с тем, что система выдает либо все возможные пороги скидок (например, 3%, 5%, 7% одновременно), либо не связывает оборот с конкретным условием. В этой статье мы подробно разберем, почему возникает эта сложность, и рассмотрим два рабочих решения: чисто «запросный» вариант для простых скидочных систем и универсальный программный метод с использованием СКД.

В чем главная сложность связывания скидки и оборота в запросе?

Проанализируем структуру хранения данных в системе. В типовых конфигурациях скидки и условия их предоставления разделены:

  1. Карта лояльности (справочник КартыЛояльности) привязана к своему виду карты (справочник ВидыКартЛояльности).
  2. Действующие скидки регистрируются в регистре сведений ДействиеСкидокНаценок с привязкой к источнику (в нашем случае — к виду карты лояльности).
  3. Сами скидки хранятся в справочнике СкидкиНаценки.
  4. Условия, при которых эти скидки начинают действовать (например, «накопленный объем продаж не менее 50 000 руб.»), хранятся в справочнике УсловияПредоставленияСкидокНаценок.

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

---

Решение №1: Получение скидки и оборота одним запросом

Если в вашей базе настроена линейная дисконтная система (например, при накоплении 20 000 руб. — 3%, при 50 000 руб. — 5%, при 100 000 руб. — 10%), и не используются сложные правила совместного применения (вытеснение, сложение, группы «Умножение»), мы можем решить задачу одним запросом.

Разберем по шагам логику построения такого запроса:

  1. Выбираем карты лояльности и рассчитываем обороты: Соединяем справочник КартыЛояльности с регистром накопления ВыручкаИСебестоимостьПродаж.Обороты по партнеру карты.
  2. Получаем действующие скидки: Присоединяем срез последних регистра ДействиеСкидокНаценок, отбирая только действующие записи.
  3. Связываем скидки с условиями: Выполняем соединение с табличной частью УсловияПредоставления справочника СкидкиНаценки.
  4. Получаем параметры условий: Присоединяем справочник УсловияПредоставленияСкидокНаценок, где хранится пороговая сумма накопления (реквизит ЗначениеУсловияОграничения).
  5. Фильтруем по порогу: Накладываем условие связи: накопленный оборот по карте должен быть больше или равен порогу из условия предоставления скидки.
  6. Выбираем максимальную скидку: Поскольку клиент с оборотом 70 000 руб. формально подходит и под условия «от 20 000 руб. (3%)», и под «от 50 000 руб. (5%)», мы группируем результат по карте и выбираем МАКСИМУМ от значения процента скидки.

Рассмотрим пример реализации такого запроса в 1С:


ВЫБРАТЬ
    Карты.Ссылка КАК КартаЛояльности,
    Карты.Партнер КАК Партнер,
    ЕСТЬNULL(ПродажиОбороты.СуммаВыручкиОборот, 0) КАК НакопленныйОборот,
    МАКСИМУМ(ЕСТЬNULL(Скидки.ЗначениеСкидкиНаценки, 0)) КАК ПроцентСкидки
ПОМЕСТИТЬ ВТ_КартыИМаксимальнаяСкидка
ИЗ
    Справочник.КартыЛояльности КАК Карты
    
    // 1. Получаем накопленные продажи партнера по карте
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(,,, ) КАК ПродажиОбороты
        ПО Карты.Партнер = ПродажиОбороты.АналитикаУчетаПоПартнерам.Партнер
        
    // 2. Подтягиваем действующие скидки для вида карты (Владельца карты)
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДействиеСкидокНаценок.СрезПоследних КАК ДействиеСкидок
        ПО Карты.Владелец = ДействиеСкидок.Источник
        И ДействиеСкидок.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыДействияСкидок.Действует)
        
    // 3. Соединяем скидку с ее табличной частью условий
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.СкидкиНаценки.УсловияПредоставления КАК ТЧУсловия
        ПО ДействиеСкидок.СкидкаНаценка = ТЧУсловия.Ссылка
        
    // 4. Получаем настройки самого условия (пороговую сумму)
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.УсловияПредоставленияСкидокНаценок КАК Условия
        ПО ТЧУсловия.УсловиеПредоставления = Условия.Ссылка
        И Условия.УсловиеПредоставления = ЗНАЧЕНИЕ(Перечисление.УсловияПредоставленияСкидокНаценок.ЗаНакопленныйОбъемПродаж)
ГДЕ
    Карты.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыКартЛояльности.Действует)
    // 5. Отсекаем скидки, порог которых еще не достигнут по обороту
    И ЕСТЬNULL(ПродажиОбороты.СуммаВыручкиОборот, 0) >= ЕСТЬNULL(Условия.ЗначениеУсловияОграничения, 0)

СГРУППИРОВАТЬ ПО
    Карты.Ссылка,
    Карты.Partner,
    ЕСТЬNULL(ПродажиОбороты.СуммаВыручкиОборот, 0)
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ.КартаЛояльности КАК КартаЛояльности,
    ВТ.Партнер КАК Партнер,
    ВТ.НакопленныйОборот КАК НакопленныйОборот,
    ВТ.ПроцентСкидки КАК ПроцентСкидки
ИЗ
    ВТ_КартыИМаксимальнаяСкидка КАК ВТ

---

Почему чистый запрос может вернуть неверный результат в реальной базе?

Запросный метод идеален по быстродействию, но имеет серьезные функциональные ограничения. Выясним основные причины, почему в крупных базах со сложным маркетингом этот метод не сработает:

---

Решение №2: Программный расчет скидок через СКД и внешний набор данных

Для получения 100% достоверного результата лучше всего использовать штатный расчетный механизм конфигурации. Система сама умеет вычислять действующую скидку по карте лояльности, учитывая все сегменты, периоды накопления и вытеснения.

Для этого мы можем разработать отчет на СКД, где в качестве источника данных будет выступать внешняя Таблица Значений (ТЗ), заполняемая и рассчитываемая программно в модуле объекта отчета.

Разберем по шагам, как реализовать этот алгоритм:

  1. Создаем схему компоновки данных (СКД). Вместо стандартного запроса добавляем Набор данных - объект с именем ТаблицаКарт.
  2. В наборе данных вручную создаем поля: КартаЛояльности (тип СправочникСсылка.КартыЛояльности), Партнер (тип СправочникСсылка.Партнеры), НакопленныйОборот (тип Число), ПроцентСкидки (тип Число).
  3. Переходим в модуль объекта отчета и переопределяем процедуру ПриКомпоновкеРезультата.

Ниже представлен пример программного кода в модуле объекта отчета, который собирает данные, производит расчет скидок через встроенные механизмы и передает результат в СКД:


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

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

← На главную