[msDiscount] Система скидок для miniShop2

Специально для mamaboutique.ru написал новый компонент msDiscount. Учитывая требования заказчицы, компонент написан максимально универсально, и скорее всего появится в нашем магазине.

На данный момент возможности такие:
  • Скидки на группу товаров
  • Скидки для группы пользователей
  • Создание акций, ограниченных по времени, с указанием групп пользователей и товаров
  • Для групп пользователей можно указать сумму оплаченных покупок, при достижений которой, они автоматически в неё вступят.
  • Пересчет корзины при авторизации юзера
  • Автоматический вывод цены со скидкой в price, а старой цены в old_price.
    Пользователь после авторизации видит новые цены везде: и в каталоге, и на странице товара, и в корзине.
  • Величину скидки можно указывать относительную, в процентах, или абсолютную, в валюте магазина.
Самое главное отличие от других систем, что я видел: довольно хитрый алгоритм, который проверяет все возможности применения разных скидок к текущей комбинации покупатель-товар и выбирает максимальную.

При этом, требуемые группы могут как включать, так и исключать позиции из акции.

Интерфейс

При установке компонента, он появляется в меню miniShop2.


При переходе вы видите 4 вкладки: 3 для настройки и одна для проверки:

Группы пользователей и товаров

Это самые простые вкладки: вы можете переменовать группы и указать персональные скидки.

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

Акции и скидки

Здесь вы создаете акцию и назначаете ей группы, с которыми она работает. Эта вкладка нужна для того, чтобы создавать правила сложнее «дать группе юзеров скидку 10% на всё».

Сначала акцию нужно создать. Её можно связать со страницей сайта (вдруг вы хотите вывести какой-то сложный список действующих акций?) и указать свой логотип.

Затем сразу открывается окошко добавления групп и вы можете выбрать нужное из списка:
Поиск при наборе поддерживается (используются комбобоксы из MS2).

Обратите внимание: если акция не привязана ни к каким группам — она действует для всех товаров и пользователей.

Если мы хотим устроить, например, распродажу то нужно указать время проведения акции, группу акционных товаров (потом добавить товары в неё) и не привязывать её к пользователям.

Или указать группу юзеров (несколько групп), для которых эта акция.

При этом, группы могут служить исключением и если юзер или товар входит в эту группу — акция для него уже не действует. Конечно, они вполне могут подпасть под действие другой акции.

Как видите, в наличии крайне гибкая система указания правил, в которой очень легко потеряться. Поэтому был придуман отдельный раздел для проверки правил.

Алгоритм работы и проверка

Это специальный раздел, где можно указать товар, юзера и дату, для проверки действия акции:

Итак, самое интересное: как работает алгоритм выбора скидки?
  1. Выбираются все активные акции, то есть включенные и с нужной датой работы (если указана)
  2. Затем группы указанного пользователя
  3. И группы товаров
  4. Если активных акций нет — просто проверяем персональные скидки и групп и выбираем максимальную
  5. Если акции есть — проверяем их, в цикле
  6. Если акция исключает какую то группу — то она не работает для текущего юзера и товара, пропускается
  7. Если пользователь и товар входят в требуемые группы — назначаем скидку акции
  8. И сравниваем её с персональной скидкой групп товаров и юзеров. Если какая-то из них больше — она перекрывает акционную
  9. Если в акциях указана скидка и относительная в процентах, и абсолютная в валюте — приводим процент к числу от цены товара и сравниваем, что больше: относительная или абсолютная скидка.
  10. В итоге проходимся по всем скидкам и выбираем максимальную
Собственно, именно это вы и видите в логе проверки: какие акции, как применяются и почему назначается та или иная скидка.
Именно этим же алгоритмом будет определена скидка на товары сайта, при просмотре го покупателем.

Заключение

Компонент немного тормозит работу вывода каталога, так как скидки считаются для всех выводимых товаров, в зависимости от того, кто их видит.

Кэшировать это нельзя, так что замедление порядка 0.05 — 0.1 сек. на 20 товаров. На приличном хостинге, можно сказать, незаметно.

Это первая версия компонента, если он будет популярен — есть масса идей, как развивать. Коды со скидками, продвинутое управление группами товаров и пользователей (например добавление товаров целыми категориями) и т.д.

На данный момент мы тестируем функционал. Отпишитесь, пожалуйста в комментариях, интересно ли вам купить это дополнение, и какую сумму вы готовы заплатить?

Следующая заметка
[pdoTools] Версия 1.8.0-rc: Сниппет pdoPage
Предыдущая заметка
[pdoTools] Версия 1.8.0-beta: Условия выборки


Комментарии ()

  1. Вячеслав Сергееевич 10 октября 2013, 10:00 # 0
    Дополнение безусловно интересно!
    Думаю, адекватный ценник 500 — 1000 рублей (в таком виде) и 1000 — 1500 рублей, когда будут коды со скидками (это наиболее частый запрос от заказчиков).
    1. Олег Карягин 10 октября 2013, 10:03 # 0
      Да, безусловно!
      По цене согласен с Вячеславом.
      1. Yana V 10 октября 2013, 10:46 # 0
        добавила в корзину товар
        prntscr.com/1wdvbc
        пишет цена 0 рублей.
        1. Василий Наумкин 10 октября 2013, 10:52 # 0
          Какой-то баг, разберусь.
          1. Yana V 10 октября 2013, 11:32 # 0
            специально и написала ;)
            1. Василий Наумкин 10 октября 2013, 17:13 # 0
              Спасибо! Была глупая ошибка:
              if (empty($user_id)) {
              	return false;
              }

              Забыл, что пользователи могут быть и не авторизованы =)
        2. Clean 10 октября 2013, 12:52 # 0
          Василий, это очень популярная штука. притом ее можно использовать не только для MS2 а независимо…
          Однозначно есть смысл поместить store.simpledream… На сейчас единственное решение которое я видел — это под шопкипер купоны со скидками и скидки по сумме.
          1. Перетягин Илья 10 октября 2013, 20:23 # 0
            Система скидок не может быть не интересной, я бы даже сказал, что это очень важная часть любого магазина, всегда приятнее покупать со скидкой чем без нее, что заставляет покупателей возвращаться именно в ваш магазин.
            На счет цены — если развить функционал до все возможных моделей скидок, то такой компонент может стоить достаточно дорого.
            п.с Такой функционал нужен так же как и фильтр, что не скажешь о всяких интеграциях складов, переоценках и похожих дополнениях которые нужны далеко не всегда.
            1. Марк Львов 14 октября 2013, 19:16 # 0
              Делал такую же штуку для ИМ на modx evolution 3 года назад, функционал соответствовал представленному примерно на 90%, не просто с этими скидками, те еще заморочки с расчетами… Еще там была такая фишка, чтобы получать скидки и участвовать в акциях нужно обязательно быть подписанным на рассылки этих акций.
              Алгоритм работы был таким:
              1)Админ создавал акцию
              2)Выбирал группу пользователей-участников
              3)Выбирался ресурс, в котором было указано предложение
              4)Выбирался тип скидки (скидка кстати бывает не только процентная, но и фиксированная, 1000 руб. допустим на все)
              5)Нажималась кнопка, сохранить
              6)Далее у каждого товара можно было выбрать в каких акциях он участвует, без привязки к группе
              7)Далее админ нажимал кнопку «активировать» и всем кто подписан рассылалось предложение — оно автоматом бралось из указанного привязанного ресурса, а ресурс публиковался :)
              и еще скидка по акции на товар действовала для тех кто был подписан на момент рассылки (была таблица связи участников и акций). Еще у акции был период действия, по истечении которого скидка переставала работать.
              Это так, вдруг, что-то из этого захочется реализовать:)
              1. Василий Наумкин 14 октября 2013, 20:38 # 0
                Дествительно, функционал очень похож, но есть несколько отличий:

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

                4) Зачем усложнять? У нас можно вводить или проценты, или точное число — система сама разберется.

                6) Думал про такое, но выходит не очень удобно бегать по товарам и уточнять, в каких акциях он участвует. Опять же, система нескольких товарных групп с исключениями гораздо гибче.

                Есть мысль просто сделать удобную работу с группами прямо из компонента, но это потом.

                7) Чем организована рассылка — самописный скрипт? Насколько я знаю, ничего приличного по рассылкам у нас пока нет.

                Это можно сделать, да — событием при активации акции. Спасибо за идеи!
                1. Марк Львов 16 октября 2013, 17:02 # 0
                  2)Да с исключениями удобнее, но там проще было с группами, там было всего три группы (все зарегистрированные, привилегированные и vip)
                  3)администратору магазина так понятнее было :)
                  6)Можно было всегда просмотреть в акции какие товары участвуют, и если нужно исключить или добавить
                  7)Там использовался обычный phpMailer, далее все было на совести хостинга, проблем ни когда не было, хостинг рассылал по 50 писем раз в 10 минут.
                  вообще когда это делал смотрел на то, как это сделано в FastSales Pro
              2. Виталий Батушев 15 октября 2013, 09:10 # 0
                Мне всегда интересно купить. 1000-2000.
                1. Алексей Карташов 15 октября 2013, 23:29 # 0
                  Коды со скидками — наше всё)
                  Честно говоря, не знаю какую цену ты ожидаешь услышать, но мне кажется 1500-2000 — в самый раз для рядового магазина.
                  1. Clean 15 октября 2013, 23:41 # 0
                    Это компонент по важности на уровне офиса или фильтрации.Цена должна быть тоже на уровне, поэтому думаю 1500 в самый раз… Ну все от сложности еще конечно зависит.
                    1. Воеводский Михаил 16 октября 2013, 11:17 # 0
                      Согласен.
                  2. Clean 16 октября 2013, 12:00 # 0
                    Смотрю уже в репозитории, но никто не качает почему-то =)

                    Как по мне получилось круто исходя из описания, но осталось на мой взгляд еще добавить туда купонатор, чтобы генерировать купоны на скидку
                    1. Воеводский Михаил 16 октября 2013, 12:32 # 0
                      Не у всех, видимо, есть необходимость применения компонента прямо сейчас :)
                      Я планирую его приобретение через 1-2 месяца.
                      1. Алексей 17 октября 2013, 20:00 # 0
                        Можно предварительно «пощупать» админку компонента?
                        Так то очень нужная вещь.
                        1. Василий Наумкин 17 октября 2013, 20:03 # 0
                          Неужто баннеры modx-test.com не видны ни здесь, ни в магазине?
                          1. Алексей 18 октября 2013, 00:22 # 0
                            блин, а я тут смотрел minishop2.com/
                            1. Василий Наумкин 18 октября 2013, 06:53 # 0
                              А я что-то не добавил там.

                              Сейчас сделаю.
                      2. Dennis Nichi 30 октября 2013, 17:04 # 0
                        Дополнение очень полезное, но не хватает скидки по промо-коду.
                        1. Denis Ok 30 октября 2013, 21:55 # 0
                          подскажите пожалуйста на пару вопросов

                          1. если ли возможность установить несколько скидок на товар?
                          например при заказе:
                          от 0 до 50 шт. скидка 0%
                          от 51 до 100шт. скидка 5%
                          от 101 до 200 скидка 10%

                          2. можно ли будет вывести эти скидки на странице товара?
                          например:
                          стоимость 100руб
                          при заказе 51-100 — 95 руб
                          при заказе 101-200 — 90 руб
                          1. Василий Наумкин 30 октября 2013, 22:40 # 0
                            Нет.
                          2. Юрий Эффа 07 ноября 2013, 23:22 # 0
                            1. При смене Группы ресурсов у товара скидка меняется только на странице товара, а на странице каталога (списка товаров) остаётся прежней — лечится через чистку кеша сайта (Обновить сайт)
                            я так понимаю, что при сохранении товара, чистится только кеш ресурса?

                            2. Вот к этому:
                            довольно хитрый алгоритм, который проверяет все возможности применения разных скидок к текущей комбинации покупатель-товар и выбирает максимальную.
                            При включении товара в несколько Групп ресурсов, у которых разная скидка, например:
                            Скидка 5% - со скидкой 5%
                            Скидка 10% - со скидкой 10%
                            Скидка 35% - со скидкой 35%
                            приоритет отдаётся скидке в 5%. Однако если сделать так, что у группы «Скидка 5%» будет значение скидки 25% — всё считается правильно, и к цене товара применяется скидка 35%
                            1. Василий Наумкин 08 ноября 2013, 06:57 # 0
                              1. Нужно вызывать msProducts некэшированным.

                              2. Или ты ошибаешься, или я. Специально для этого в админке есть проверка скидки с подробным логом.
                              1. Юрий Эффа 08 ноября 2013, 09:45 # 0
                                1. Так и пришлось.

                                2. Сначала у группы скидок id=16 скидка 5%
                                Начальная цена товара (id=35): 1900.
                                Получены группы пользователя (id=1): 1 шт.
                                Получены группы товара (id=35): 3 шт.
                                Получены активные акции: 0 шт.
                                
                                Скидка на все товары из группы (id = 19): 20%.
                                Скидка на все товары из группы (id = 23): 40%.
                                Скидка на все товары из группы (id = 16): 5%.
                                Считаем процент 5% от цены товара 1900 - выходит 95.
                                Итоговая скидка 95
                                Итоговая цена товара 1805.
                                 
                                а затем назначаем этой же группе id=16 скидку 23%
                                Начальная цена товара (id=35): 1900.
                                Получены группы пользователя (id=1): 1 шт.
                                Получены группы товара (id=35): 3 шт.
                                Получены активные акции: 0 шт.
                                
                                Скидка на все товары из группы (id = 19): 20%.
                                Скидка на все товары из группы (id = 16): 22%.
                                Скидка на все товары из группы (id = 23): 40%.
                                Считаем процент 40% от цены товара 1900 - выходит 760.
                                Итоговая скидка 760
                                Итоговая цена товара 1140.
                                1. Василий Наумкин 08 ноября 2013, 09:59 # 0
                                  Похоже тут ошибка сортировки. Нужно сверять проценты как числа, а сверяются, как строки.

                                  Создай, пожалуйста, тикет в магазине и напиши доступы к сайту, посмотрю на месте и поправлю.
                                  1. Василий Наумкин 08 ноября 2013, 13:27 # 0
                                    Поправил, обновляйся.
                              2. Юрий Эффа 07 ноября 2013, 23:25 # 0
                                Василий, подскажи, пожалуйста, как получить значение текущей скидки для конкретного товара?
                                Нужно в каталоге на сайте вывести на товаре ярлык со значением этой скидки…
                                1. Василий Наумкин 08 ноября 2013, 07:01 # 0
                                  Попробуй вызвать такой мини-сниппет на странице товара:
                                  return $modx->resource->get('price') - $modx->resource->getPrice();
                                  1. Юрий Эффа 08 ноября 2013, 09:50 # 0
                                    Спасибо, работает. А нет ли возможности получить сразу рассчитанную максимальную скидку в %%-ах, не делая обратных расчётов от цены и скидки?
                                    1. Василий Наумкин 08 ноября 2013, 09:55 # 0
                                      Нет, нету.

                                      Постараюсь добавить в следующих версиях.
                                      1. Юрий Эффа 08 ноября 2013, 09:56 # 0
                                        Может пригодится кому
                                        return ($modx->resource->get('price') - $modx->resource->getPrice())/$modx->resource->get('price')*100;
                                    2. Юрий Эффа 08 ноября 2013, 19:00 # 0
                                      Я правильно понимаю, что в чанке элемента списка товаров (страница каталога), эта конструкция работать не будет?
                                      500ая ошибка (
                                      1. Василий Наумкин 08 ноября 2013, 19:10 # 0
                                        В чанках любой php код работать не будет.
                                        1. Юрий Эффа 08 ноября 2013, 19:11 # 0
                                          Я имею ввиду вызов сниппета
                                          1. Юрий Эффа 08 ноября 2013, 19:14 # 0
                                            Сделал вот так:
                                            $page = $modx->getObject('msProduct', $id);
                                            $fullprice = $page->get('price');
                                            $saleprice = $page->getPrice();
                                            $sale = ( $fullprice - $saleprice ) / $fullprice * 100;
                                            
                                            return $sale;
                                            где id передаётся при вызове сниппета в чанке: [[getSaleSignCat? &id=`[[+id]]`]]
                                            1. Василий Наумкин 08 ноября 2013, 19:16 # 0
                                              Молодец.

                                              А если сразу подумать, то можно и не задавать лишних вопросов.

                                              И кстати, лучше не получать каждый раз товар заново, а просто вычислять разницу между [[+price]] и [[+old_price]]. То есть, между текущей ценой (скидочной) и старой ценой (оригинальной).
                                              1. Юрий Эффа 08 ноября 2013, 23:15 # 0
                                                Спасибо за совет, так и сделал. Разделительные пробелы только удалять пришлось из цены
                                    3. Юрий Эффа 10 ноября 2013, 21:15 # 0
                                      Василий, подскажи, пожалуйста, как получить список всех товаров, к которым для конкретного пользователя применяются скидки?
                                      Мозгов хватает только на то, чтобы вывести все товары, и в чанке делать проверку на величину скидки… Может быть существует более изящное решение? типа сниппета, который возвращает id товаров со скидками, чтобы потом их можно бы было запихнуть в resources
                                      1. Василий Наумкин 11 ноября 2013, 06:22 # 0
                                        Вот я тут даже не знаю, что посоветовать. Зависит от используемых правил, наверное.

                                        Самый простой способ — да, выбрать все товары и проверить на скидку.
                                        $pdo = $modx->getService('pdoFetch');
                                        $msd = $modx->getService('msDiscount');
                                        
                                        $resources = $pdo->getCollection('msProduct', array(
                                        		'class_key' => 'msProduct',
                                        		'published' => 1,
                                        		'deleted' => 0
                                        	),
                                        	array(
                                        		'parents' => '2'
                                        	)
                                        );
                                        $ids = array();
                                        foreach ($resources as $resource) {
                                        	$new_price = $msd->getNewPrice($resource['id'], $resources['price']);
                                        	if ($new_price != $resource['price']) {
                                        		// У товара скидка
                                        		$ids[] = $resources['id'];
                                        	}
                                        }
                                        if (!empty($ids)) {
                                        	return $modx->runSnippet('msProducts', array('resources' => implode(',', $ids)));
                                        }
                                        else {
                                        	return 'Скидок нет';
                                        }
                                        

                                        Но если товаров много, это может быть не быстро. Код не проверял, возможны ошибки.
                                        1. Юрий Эффа 14 декабря 2013, 19:23 # 0
                                          Код не проверял, возможны ошибки.
                                          Угу… выдал «Скидок нет»
                                           
                                          При наличии у товара скидки, вот это:
                                          $new_price = $msd->getNewPrice($resource['id'], $resources['price']);
                                          выдаёт ноль, при отсутствии — пустоту
                                          1. Юрий Эффа 14 декабря 2013, 19:47 # 0
                                            а при print_r($resources); — [price] не обнаруживается, там только поля обычного ресурса, от продукта ничего нет
                                            1. Юрий Эффа 16 декабря 2013, 19:56 # 0
                                              А как было бы замечательно, пользуясь приобретённым компонентом, создать на сайте раздел Распродажа и вывести туда все товары со скидкой =)
                                              1. Василий Наумкин 16 декабря 2013, 20:51 # 0
                                                Компонент работает сложно, но зато и позволяет многое.
                                                При этом, он не отменяет классические способы группировки товаров: по шаблону, по ТВ, по другому параметру.

                                                Для создания скидки нужна группа ресурсов. Если ты делаешь так, что скидка не зависит от юзера, а доступна всем, то можно просто вывести эту группу:
                                                [[!msProducts?
                                                	&parents=`0`
                                                	&innerJoin=`{
                                                	"modResourceGroupResource": {"on":"modResourceGroupResource.document = msProduct.id"},
                                                	"modResourceGroup": {"on":"modResourceGroupResource.document_group = modResourceGroup.id"}
                                                	}`
                                                	&where=`{"modResourceGroup.name":"Test"}`
                                                ]]
                                                В примере я вывожу группу ресурсов Test. От одного джоина можно отказаться, если указывать группу по id, а не имени.
                                          2. Mekhron Khusainov 11 апреля 2014, 19:00 # 0
                                            Функционал промокодов появился в нем?
                                            1. Василий Наумкин 13 апреля 2014, 08:02 # 0
                                              Пока нет.
                                              1. Игорь Ткачук 24 сентября 2014, 18:26 # 0
                                                А когда планируете реализовать? Очень нужная вещь!
                                            Добавление новых комментариев отключено.