new

msProductDiscounts

Создание скидок на товары с широкими возможностями настройки
Автор дополнения
Артур Шевченко
Пакетов
31
Закачек
3 539
Обычно отвечает в течение суток
Автор дополнения
Пакетов
31
Закачек
3 539
Обычно отвечает в течение суток
Версия 2.9.2-rc
Дата выпуска 30.09.2026
Загрузки 68
Просмотры 5 036
Внимание, этот компонент несовместим с MODX 3.
Будьте внимательны при обновлении с версий 1.х.х ОБЯЗАТЕЛЬНО что-то сломается.
Требует установки SendIt
Есть встроенная работа с промокодами.
НЕ РАБОТАЕТ С MODX 3

msProductDiscounts предназначен для управления скидками на товары интернет-магазина на базе miniShop2.
Основные возможности:
  1. Скидка в виде подарка;
  2. Скидки на каждый N-ый товар;
  3. Скидки на каждую N-ую позицию в корзине
  4. Скидки по дням недели
  5. Скидки по группам пользователей
  6. Скидки по определённым датам, на определенный период или бессрочно
  7. Скидки по категориям, с возможностью исключить дочерние
  8. Скидки на определённый товар, с возможностью задать исключения по отдельным модификациям (требуется дополнение msOprionPrice2) или по опциями и основным свойствам
  9. Скидки на товары с определенными опциями и свойствами
  10. Скидки на отдельные модификации
  11. Возможность ограничить применение скидок общей суммой товаров в корзине
  12. Автоматическая актуализация корзины
  13. Возможность определить приоритетность скидок
  14. Вывод информации о скидках на странице товара
  15. Возможность привязать ресурс с подробным описанием скидки
  16. Возможность вывести информацию о скидке на странице товара
  17. Возможность задать размер скидки как в относительных единицах измерения (%) так и в абсолютных

ВИДЕО ПРЕЗЕНТАЦИЯ

Для начала работы требуется просто установить компонент и задать параметры скидки. Если в параметрах скидки есть зависимость от опций, свойств или модификаций, эта информация должна передаваться на сервер стандартными средствами miniShop2, т.е. через массив options.

Для того, чтобы работала актуализация корзины, нужно в системных настройках указать ID страницы с корзиной (mspd_cart_page).
В чанке корзины должны присутствовать следующие элементы
<span data-ms-price>{$product.price}</span>
<span data-ms-old-price>{$product.old_price}</span>
<span data-ms-cost>{$product.cost}</span>
Ключевой момент здесь это data-атрибуты любого из них может не быть, но в этом случае, информация обновляться не будет, пока не перезагрузят страницу.

С версии 1.4.1 сниппет вывода информации о скидках на конкретный товар удалён. Вместо него добавлен процессор.
Таким образом показ цены со скидкой происходит автоматически, если в чанк товара добавлены соответствующие атрибуты.
Так вся информация о товаре должна быть внутри блока с атрибутом data-mspd-product="{$id}" где $id — идентификатор товара в админке.
Блок вывода цены должен содержать атрибут data-mspd-price в качестве значения нужно указать цену товара, для старой цены data-mspd-old-price — значение старая цена.
Свойства и опции влияющие на цену нужно обозначать соответственно data-mspd-data (свойство, например артикул или цвет или вес)
и data-mspd-option (любая кастомная опция). При этом если html элемент, которому вы добавляете эти атрибуты НЕ содержит атрибута name, необходимо в качестве значения указать ключ опции или свойства,
например так: data-mspd-data=«color» или data-mspd-option=«length».
Такие свойства как
  • Производитель,
  • Страна изготовитель,
  • Новый,
  • Особый,
  • Популярный
можно не обозначать и не передавать в корзину, они проверяются по умолчанию.
Чтобы нулевая старая цена скрывалась необходимо обернуть весь блок вывода старой цены в элемент с атрибутом data-mspd-old-price-wrap и убедиться, что в стилях описан класс d-none, либо, если за визуальное скрытие элементов у вас отвечает другой класс, указать свой класс в системных настройках компонента.

Там же, в системных настройках, можно изменить время принудительной перезагрузки страницы с корзиной. Принудительная перезагрузка происходит 1 раз в сутки. Это нужно на случай если пользователь оставил вкладку браузера на длительное время, а срок действия акции закончился. Тогда принудительная перезагрузка обновит стоимость корзины. Если оставить системную настройку mspd_reload_interval пустой принудительной перезагрузки не случится.

Стандартный скрипт показывает цену со скидкой и старую цену. Однако может возникнуть необходимость показывать ещё какую-то информацию о скидках на данный товар.
Для этого есть событие в JavaScript (с версии 1.4.1) mspd-get-discounts
document.addEventListener('mspd-get-discounts', e => {
    console.log(e.detail);
    // e.detail.response - ответ сервера с реальной ценой, ценой со скидкой/скидками и списком всех применённых скидок
    // e.detail.wrapper - блок-обёртка товара обозначенная атрибутом data-mspd-product
    // e.detail.price - цена от которой рассчитывалась скидка
    // e.detail.target - элемент вызвавший событие
    // e.detail.object - объект JS класса mspdGetDiscounts можно использовать его метод форматирования цены
    //e.detail.object.formatPrice(e.detail.response.discount_price, e.detail.object.config.price_format, e.detail.object.config.price_format_no_zeros)
    });

Для работы с msOprionsPrice2 необходимо внутри блока с data-mspd-product добавить
<input type="hidden" name="options[modification]">

Пример страницы товара
{'!msOptionsPrice.initialize' | snippet:[]}

<div class="text-center text-md-left mb-2 mb-md-0">
    {if $_modx->resource.new?}
        <span class="badge badge-secondary badge-pill col-auto">{'ms2_frontend_new' | lexicon}</span>
    {/if}
    {if $_modx->resource.popular?}
        <span class="badge badge-secondary badge-pill col-auto">{'ms2_frontend_popular' | lexicon}</span>
    {/if}
    {if $_modx->resource.favorite?}
        <span class="badge badge-secondary badge-pill col-auto">{'ms2_frontend_favorite' | lexicon}</span>
    {/if}
</div>
<div id="msProduct" class="row align-items-center" itemtype="http://schema.org/Product" itemscope>
    <meta itemprop="name" content="{$_modx->resource.pagetitle}">
    <meta itemprop="description" content="{$_modx->resource.description ?: $_modx->resource.pagetitle}">
    <div class="col-12 col-md-6">
        {'!msGallery' | snippet : []}
    </div>
    <div class="col-12 col-md-6" itemtype="http://schema.org/AggregateOffer" itemprop="offers" itemscope data-mspd-observe>
        <meta itemprop="category" content="{$_modx->resource.parent | resource: "pagetitle"}">
        <meta itemprop="offerCount" content="1">
        <meta itemprop="price" content="{$price | replace:" ":""}">
        <meta itemprop="lowPrice" content="{$price | replace:" ":""}">
        <meta itemprop="priceCurrency" content="RUR">
        

        <form class="form-horizontal ms2_form msoptionsprice-product" method="post" data-mspd-product="{$_modx->resource.id}">
            <input type="text" name="options[modification]" value="0" data-mspd-option>
            <h1 class="text-center text-md-left msoptionsprice-name msoptionsprice-[[*id]]">{$_modx->resource.pagetitle}</h1>
            <input type="hidden" name="id" value="{$_modx->resource.id}"/>

            <div class="form-group row align-items-center">
                <label class="col-6 col-md-3 text-right text-md-left col-form-label">{'ms2_product_article' | lexicon}:</label>
                <div class="col-6 col-md-9 msoptionsprice-article msoptionsprice-[[*id]]">
                    {$article ?: '-'}
                </div>
            </div>
            <div class="form-group row align-items-center">
                <label class="col-6 col-md-3 text-right text-md-left col-form-label">{'ms2_product_price' | lexicon}:</label>
                <div class="col-6 col-md-9">
                    <span class="msoptionsprice-cost msoptionsprice-[[*id]]" data-mspd-price="{$_modx->resource.price}">{$_modx->resource.price}</span> {'ms2_frontend_currency' | lexicon}
                    <span data-mspd-old-price-wrap class="old_price {$old_price == 0 ? 'd-none' : ''}">
                    <span class=" ml-2 msoptionsprice-old-cost msoptionsprice-[[*id]]" data-mspd-old-price="{$_modx->resource.old_price}">{$_modx->resource.old_price}</span>
                    {'ms2_frontend_currency' | lexicon}
                    </span>
                    
                </div>
            </div>
            <div class="form-group row align-items-center">
                <label class="col-6 col-md-3 text-right text-md-left col-form-label" for="product_price">{'ms2_cart_count' | lexicon}:</label>
                <div class="col-6 col-md-9">
                    <div class="input-group">
                        <input type="number" name="count" id="product_price" class="form-control col-md-6" value="1"/>
                        <div class="input-group-append">
                            <span class="input-group-text">{'ms2_frontend_count_unit' | lexicon}</span>
                        </div>
                    </div>
                </div>
            </div>
            <div class="form-group row align-items-center">
                <label class="col-6 col-md-3 text-right text-md-left col-form-label">{'ms2_product_weight' | lexicon}:</label>
                <div class="col-6 col-md-9">
                    <span class="msoptionsprice-mass msoptionsprice-[[*id]]" data-mspd-data="weight">{$weight}</span> {'ms2_frontend_weight_unit' | lexicon}
                </div>
            </div>

            <div class="form-group row align-items-center">
                <label class="col-6 col-md-3 text-right text-md-left col-form-label">{'ms2_product_made_in' | lexicon}:</label>
                <div class="col-6 col-md-9">
                    {$made_in ?: '-'}
                </div>
            </div>

            {'msOptions' | snippet : [
                'options' => 'color,size,test_5,test_6'
            ]}

            {'msProductOptions' | snippet : []}

            <div class="form-group row align-items-center">
                <div class="col-12 offset-md-3 col-md-9 text-center text-md-left">
                    <button type="submit" class="btn btn-primary" name="ms2_action" value="cart/add">
                        {'ms2_frontend_add_to_cart' | lexicon}
                    </button>
                </div>
            </div>
        </form>

    </div>
</div>
<div class="mt-3">
    {$_modx->resource.content}
</div>
!!! ВАЖНО При использовании msOptionsPrice2 атрибут data-mspd-product и класс msoptionsprice-product должны принадлежать одному HTML элементу. Атрибут data-mspd-old-price-wrap должен быть непосредственным родителем для элемента с атрибутом data-mspd-old-price.

[УСТАРЕЛО с версии 1.4.1]
Пример вызова сниппет для показа скидок на текущий товар (данный вызов следует размещать на странице товара). Вызов написан для Fenom
{set $discounts = '!getProductDiscounts' | snippet:[
            'id' => $_modx->resource.id,
            'tpl' => '',
            'tplEmpty' => '@INLINE Скидки для данного товара не найдены.',
            'price' => $_modx->resource.price,
            'weight' => $_modx->resource.weight,
            'options' => ['color'=>'','size'=>'','test_5'=>'','test_6' => '', 'modification' => 1],
            'data' => ['article' => ''],
            'total_cost' => 999999
 ]}
 {$discounts | print}

2.9.2-rc — 30.09.2026 Fixed

  • Исправление 2.9.1-rc было неполным: запрос активных скидок по-прежнему падал с ошибкой «Column 'createdby' in field list is ambiguous» — поля присоединённого ресурса тоже выбирались без имени таблицы. Скидки в корзине и каталоге снова работают.

2.9.1-rc — 30.09.2026 Fixed

  • Скидки перестали применяться в корзине и показываться в каталоге после обновления до 2.9.0-rc: запрос активных скидок падал с ошибкой «Column 'createdby' in field list is ambiguous» — новые поля скидки совпали по имени с полями присоединённого ресурса.
  • Отключение проверки общих условий из обработчика mspdOnBeforeCheckDiscount действует только на ту скидку, для которой его включили, а не на все следующие скидки и товары.
  • Расчёт скидок падал с ошибкой «Class 'MsProductDiscounts\msProductDiscountsManagerAccess' not found», как только у какой-нибудь скидки истекал период (с 2.7.9-rc).
  • Добавлено описание системной настройки mspd_check_common_conditions_on_show.

2.9.0-rc — 29.09.2026 Added

  • Форма скидки: основные поля на виду, настройки и условия — во вкладках. Видимые вкладки и их порядок задают новые системные настройки mspd_discount_tabs (страница скидки) и mspd_discount_modal_tabs (скидка в модалке на странице промокода).
  • Условия новой скидки задаются до первого сохранения и сохраняются вместе с ней.
  • Выбор товара-подарка через модалки товара и модификации; поиск товаров, модификаций и подарка срабатывает при вводе; списки в модалках с пагинацией; автосохранение в модалках категорий, товаров и модификаций.
  • Промокод: создание, редактирование, удаление и закрепление скидок прямо из формы промокода; новый промокод создаётся вводом кода, сохранённый сохраняется автоматически; по умолчанию безлимитный и бессрочный.
  • Поиск скидок промокода: фильтры-плашки, отдельный поиск по исключениям, фильтр по создателю. У скидок и промокодов хранятся создатель и дата создания.
  • Списки скидок и промокодов: поиск и фильтры по контексту, создателю, дате создания, у промокодов — по коду, скидкам и состоянию.
  • Промокод без скидок остаётся черновиком (отметка и фильтр «Без скидок»), а не удаляется вместе с последней скидкой.
  • Меню: пункты собраны в группы «Скидки» и «Промокоды», добавлены «Создать скидку» и «Создать промокод». При обновлении группа встаёт туда, где на сайте лежал прежний пункт.
  • Создание из меню без mspd_base_ctx при нескольких контекстах открывает окно выбора контекста.
  • Модалка «Подробнее» о скидке переоформлена.
  • Системная настройка mspd_gift_price (цена товара-подарка, по умолчанию 0) видна в админке.

Changed

  • mspd_debug при новой установке выключен.

Fixed

  • Период скидки и промокода не меньше часа, даты формы без сдвига на час.
  • Промокод и его скидки — в одном контексте; пустой контекст не сохраняется.
  • Занятое название скидки показывается ошибкой у поля.
  • Версии скриптов менеджера в URL: браузер не берёт старый JS из кэша.

2.8.0-rc — 21.09.2026 Added

  • Поиск скидок при редактировании промокода по названию, категориям, товарам, опциям и модификациям. Результаты показывают совпавшие условия и исключения.
  • Просмотр параметров и условий скидки кнопкой «Подробнее» без ухода из промокода.
  • Выбранные скидки отображаются отдельно и сохраняются при смене запроса и страницы.

2.7.10-rc — 17.09.2026 Fixed

  • ИСПРАВЛЕНО: скидка считалась не для выбранного варианта товара. getProductOptions собирал все поля options[...] подряд и перезаписывал значение по имени, поэтому из группы переключателей в запрос уходил последний в разметке, а не отмеченный покупателем. Невыбранные radio/checkbox и отключённые поля теперь в опции не попадают.

Added

  • msProductDiscounts.refreshDiscounts() — публичный вызов принудительного пересчёта скидок всех товаров страницы, и атрибут формы data-mspd-manual, при котором пакет не шлёт запрос скидок сам по change опций и ждёт этого вызова. Нужно сайтам, где цена зависит от модификации, получаемой отдельным ajax-запросом: запрос по change уходил бы с прежним options[modification], и цена отставала бы на один выбор. Поля формы, кроме options[...] (количество и прочее), обрабатываются как раньше.

2.7.9-rc — 06.09.2026 Security

  • ИСПРАВЛЕНО: точка входа assets/components/msproductdiscounts/action.php пускала к процессорам менеджера кого угодно, без входа в админку. Списки скидок и промокодов (включая сами коды) отдавались анонимному посетителю по прямой ссылке. Проверка прав стояла только в connector.php, а action.php поднимал MODX в режиме API и звал процессор как есть. Теперь оба входа спрашивают одно и то же право: mgr/promocode/* — mspd_promocodes, прочие mgr/* — mspd_discounts; действия витрины (не mgr/) прав по-прежнему не требуют.
  • ИСПРАВЛЕНО: право теперь проверяет сам процессор, а не только точка входа. У каждого процессора каталога processors/mgr/ объявлено требуемое право, поэтому новая точка входа или прямой вызов процессора больше не обходят разграничение «скидки отдельно от промокодов». Внутренний вызов пакета (уборка истёкших периодов при расчёте скидок на витрине) идёт через msProductDiscountsManagerAccess::runProcessor() и прав не требует. Fixed
  • ИСПРАВЛЕНО: установка и обновление пакета больше не разрегистрируют права mspd_discounts и mspd_promocodes в общем шаблоне AdministratorTemplate и не вычищают их из политики Administrator. Разовый перенос прав из версий 2.1.9-2.6.0 выполнялся при каждом апгрейде, и на проектах, где эти права заведены в общем шаблоне сознательно, доступ тихо ломался: право пропадало из шаблона, а первое же сохранение политики через админку стирало ключ из её данных — процессор ядра пишет политику полным снимком формы. Свежая установка прав в общем шаблоне не создаёт, переносить нечего.

2.7.8-rc — 18.08.2026 Fixed

  • ИСПРАВЛЕНО: подарок появлялся и пропадал в корзине только после перезагрузки страницы. Признак «состав корзины изменился» сбрасывался в начале каждого расчёта скидок, а читает его ответ фронту — то есть уже следующий запрос. Изменение количества товара подарок добавляло или снимало на сервере, но список строк на странице оставался прошлым до F5; итоги при этом были верными. Теперь признак живёт до ближайшего ответа корзины и сбрасывается только после того, как отдан фронту.

2.7.7-rc — 18.08.2026 Fixed

  • ИСПРАВЛЕНО: строка корзины в ответе сервера показывала прошлое состояние цен. Разметка строки собирается один раз, при чтении корзины, — то есть до применения скидок и до добавления подарка. Дальше пересчёт менял цены и суммы, а разметка оставалась прежней: только что добавленный подарок печатался каталожной ценой вместо нулевой, и это держалось до перезагрузки страницы. Тот же дефект был у обычного товара, впервые получившего скидку.

Changed

  • ТРЕБУЕТСЯ msAltCart 1.2.4-pl и выше (метод renderProductRow()). Со старой корзиной пакет работает по-прежнему, разметка строк просто не обновляется. Порядок выкладки: сначала msAltCart, потом msProductDiscounts.

2.7.6-rc — 18.08.2026 Fixed

  • ИСПРАВЛЕНО: сумма позиций корзины расходилась с итогом, когда товаров больше одного. Точная сумма позиции сверялась с состоянием строки только по количеству, а пересчёт скидок меняет разбивку внутри позиции: «две единицы со скидкой из трёх» становятся тремя, когда рядом появляется второй товар. Количество прежнее, цена и сумма другие — строка печатала новую сумму, а итог считался по снимку прошлого расчёта.
  • ИСПРАВЛЕНО: снимок точной суммы не доживал до серверного рендера. Пара полей снималась, когда сумма сходилась с ценой × количеством, а хранилище db при этом оставляло в properties строки прежние значения — в базе не было ни одного exact_cost, и до ответа getcart страница печатала цену × количество с потерянной копейкой (636.99 вместо 637.00).

Changed

  • ИЗМЕНЕНО: контракт точной суммы с корзиной расширен ценой. Строка метится тройкой exact_cost + exact_count + exact_price, потребитель принимает снимок только при совпадении и количества, и цены единицы. Снимок без exact_price (формат до 2.7.6-rc) игнорируется.
  • ИЗМЕНЕНО: отпечаток пишется всегда — и при сбросе цен к исходным, и для подарка. «Пары нет» перестало быть состоянием, в котором строка остаётся с чужим снимком.
  • ТРЕБУЕТСЯ msAltCart 1.2.2-pl и выше. Порядок выкладки: сначала msAltCart, потом msProductDiscounts.

2.7.5-rc — 14.08.2026 Changed

  • ИЗМЕНЕНО: русское предупреждение для принятого промокода без эффекта сокращено до «Промокод принят, но не суммируется с другими скидками.» — без предположения, что причина обязательно в товарах, уже имеющих скидку.

2.7.4-rc — 14.08.2026 Fixed

  • ИСПРАВЛЕНО: очистка поля не отменяла действующий промокод — фронт прекращал запрос при пустом значении. Теперь очистка сразу отправляет отмену и возвращает корзину к состоянию без промокода.
  • ИСПРАВЛЕНО: если поверх действующего промокода ввести другой, форма пыталась отменить новый код, оставляя неверные лимиты, и не применяла его. Теперь новый код сначала полностью проверяется, затем за один запрос возвращается лимит старого и списывается лимит нового. Неверный новый код не снимает уже действующий.
  • ИСПРАВЛЕНО: отмена больше не доверяет значению поля формы — активный промокод берётся из сессии. Повторная отправка уже применённого кода не списывает его лимит второй раз.

2.7.3-rc — 14.08.2026 Fixed

  • ИСПРАВЛЕНО: промокод, не давший скидки, сообщал «Промокод применён» и оставлял корзину без изменений — клиент видел успех и прежнюю сумму. Так бывает, когда на товары уже действует другая скидка, а промокодная «Все кроме этой» с ней не суммируется. Теперь пакет проверяет, сработала ли хоть одна привязанная к промокоду скидка, и отвечает честно: «Промокод принят, но на товары в корзине уже действует скидка — промокод с ней не суммируется». Промокод при этом остаётся применённым: доложите в корзину товар без скидки — он сработает.

Added

  • НОВОЕ: Discounts::$appliedDiscountIds — id скидок, реально изменивших корзину в этом пересчёте (скидка начислена хотя бы одному товару либо подарок оказался в корзине), и метод Discounts::hasAppliedPromocodeDiscounts() поверх него. Отбор скидки ещё не означает, что она сработала, — теперь это различимо.
  • НОВОЕ: ответ действия applypromocode содержит поле data.effect — дал ли промокод эффект. Фронт пакета по нему показывает уведомление жёлтым (предупреждение) вместо зелёного, подменяя тип в штатном событии SendIt si:notify:before.

Changed

  • ИЗМЕНЕНО: текст ответа на применение промокода без эффекта берётся из нового ключа лексикона mspd_promocode_applied_no_effect.

2.7.2-rc — 14.08.2026 Fixed

  • ИСПРАВЛЕНО: точная сумма позиции терялась после пересчёта, и корзина снова показывала копейку меньше. Корзина перечитывается после операций с подарками, а обработчик корзины собирает cost заново как цена × количество — восстановленная сумма затиралась ещё до сохранения. Теперь пакет помнит сумму вместе с количеством, для которого она посчитана, и возвращает её в перечитанную корзину.
  • ИСПРАВЛЕНО: итог корзины брал сумму позиции, не проверяя, тому ли количеству она соответствует. Теперь сначала берётся помеченная пара «сумма + количество», и только при несовпадении — обычный расчёт.

Added

  • НОВОЕ: точная сумма позиции передаётся корзине парой полей exact_cost и exact_count (сумма и количество, для которого она посчитана). Поля выставляются только там, где цена × количество даёт другую сумму. Их понимает msAltCart с 1.2.1-pl — msAltCartHandler::getItemCost(); порядок выкладки: сначала msAltCart, потом этот пакет.

2.7.1-rc — 14.08.2026 Fixed

  • ИСПРАВЛЕНО: сумма позиции со скидкой теряла копейки, когда не делилась нацело на количество. Скидка на часть единиц размазывается в среднюю цену за штуку, а та округляется до mspd_round_precision: три товара по 245 со скидкой 20 % на две единицы давали цену 212.33 и сумму 636.99 вместо 637.00. Теперь пакет вычитает скидку из суммы позиции, а цена за штуку остаётся производной для показа.
  • ИСПРАВЛЕНО: выгода по позиции (discount_cost) считалась как усреднённая разница цен, умноженная на число единиц со скидкой, — величина не сходилась ни с суммой позиции, ни с ценой до скидки. Теперь это разность сумм: old_cost − cost.

Added

  • НОВОЕ: пакет подписан на событие msOnGetStatusCart и пересчитывает итог корзины по суммам позиций. miniShop2 складывает итог как «цена за штуку × количество», из-за чего та же копейка терялась и в итоге корзины, и в сумме заказа. Обработчик продолжает с уже накопленного статуса, поэтому не зависит от порядка плагинов на событии.

2.7.0-rc — 13.08.2026 Added

  • НОВОЕ: установщик создаёт две отдельные политики доступа: msproductdiscountsFull для управления скидками и промокодами и msproductdiscountsPromocodes для управления только промокодами. Политики назначаются группе пользователей в дополнение к обычной manager-политике.

Security

  • БЕЗОПАСНОСТЬ: права mspd_discounts и mspd_promocodes теперь проверяются не только у пунктов меню и страниц списков, но также у прямых URL страниц редактирования и у всех запросов manager-коннектора. Пользователь без права больше не может обойти меню прямой ссылкой или вызовом процессора.
  • БЕЗОПАСНОСТЬ: права пакета перенесены из общей политики Administrator в собственный шаблон политик. Раньше Administrator выдавал оба права всем своим получателям, поэтому политика «только промокоды» не могла ограничить доступ: MODX объединяет разрешения.

2.6.0-rc — 12.08.2026 Fixed

  • ИСПРАВЛЕНО: строковые значения в итогах корзины превращались в 0. Коннектор округлял всё подряд, кроме ключей с _str, а сайт через событие msOnGetStatusCart кладёт в статус и текст — подпись единиц измерения, готовую разметку блока о доставке. round() от такой строки даёт ноль, и корзина после любого AJAX-обновления показывала 0 вместо текста. Теперь округляются только числовые значения.

Changed

  • ИЗМЕНЕНО: пакет больше не рисует корзину. Раньше при добавлении или снятии товара-подарка он сам обновлял разметку чужой корзины и знал про конкретный компонент корзины. Теперь он бросает отменяемое событие mspd:cart:changed с составом и итогами, а рисует себя сама корзина и отменяет событие. Событие никто не отменил — пакет, как и прежде, перезагружает страницу. Имя события настраивается параметром cartChangedEvent модуля Cart.
  • ИЗМЕНЕНО: событие mspd:gift:manage стало отменяемым — теперь preventDefault() в подписчике действительно отключает штатную реакцию пакета на изменение состава корзины.
  • ИЗМЕНЕНО: запрос скидок после действий с корзиной откладывается на 50 мс (getCartDelay). Несколько колбэков miniShop2 подряд схлопываются в один запрос, а корзина успевает отрисовать изменённую строку до того, как пакет проставит в неё свои значения.
  • ВНИМАНИЕ: для работы подарков без перезагрузки страницы msAltCart нужен версии 1.2.0 и выше; обновлять его следует до обновления этого пакета.

2.5.0-rc — 12.08.2026 Added

  • НОВОЕ: значение * системной настройки mspd_tpls_with_cart включает пересчёт скидок корзины при загрузке ресурса с любым шаблоном. Прежний список ID шаблонов через запятую работает без изменений. Это позволяет корректно актуализировать боковую корзину на сайтах, где она входит в общий wrapper всех страниц.

2.4.1-rc — 31.07.2026

  • ИСПРАВЛЕНО: в фоновом режиме (cron сайта, консольные скрипты) скидки не видели родительские категории товара, а в лог ошибок на каждый товар падало «PHP warning: Invalid argument supplied for foreach()». Пакет брал предков ядровым modX::getParentIds(), который обходит карту дерева контекста, а она в API-режиме не построена: список родителей возвращался пустым, и скидка, назначенная на родительскую категорию, молча не применялась. Теперь предки считаются по собственной карте «ресурс → родитель»: один запрос на контекст, дальше подъём в памяти — в вебе это ещё и быстрее ядрового обхода (поиск по ключу вместо перебора всей карты).

2.4.0-rc — 23.07.2026

  • НОВОЕ: журнал действий менеджера со скидками и промокодами. Пакет записывает в mxLogger, кто и когда создал, изменил, удалил или продублировал скидку, а также кто создал, изменил или удалил промокод. В записи попадают пользователь (id и логин), действие, объект с его id и названием (кодом) и то, что именно изменилось: при создании и дублировании — все значения объекта, при изменении — только изменённые поля парами «было / стало», при удалении — полное состояние удалённого объекта. Для промокода так же фиксируются привязанные к нему скидки, в том числе их изменение. Значения полей сохраняются целиком, без сокращений, поэтому по журналу можно восстановить прежние настройки скидки. Время проставляет сам mxLogger. Найти записи можно по тэгам msproductdiscounts и audit, а историю конкретного объекта — по процессу mspd_discount_ID или mspd_promocode_ID. Журнал ведётся всегда и не требует включённой отладки (mspd_debug). Если mxLogger на сайте не установлен, скидки и промокоды сохраняются как обычно — записи просто не ведутся.
  • ИСПРАВЛЕНО: при отборе скидок учитывались только первая категория товара и его первая модификация. Товар, лежащий в нескольких категориях каталога, мог не получить скидку, назначенную на «неглавную» категорию; то же касалось товаров с несколькими модификациями. Теперь берутся все категории товара вместе с их родительскими и все модификации.

2.3.0-rc — 23.07.2026

  • НОВОЕ: системное событие mspdOnGetShowPrices — точка расширения, через которую сайт может подать пакету собственную цену показа и старую цену конкретного товара. Событие возникает при подготовке ответа для каталога и карточки товара: после того как пакет применил свои скидки, но до того как посчитал строковые значения цены, поэтому перечёркнутая цена и цена со скидкой остаются единым механизмом пакета, а не рисуются сайтом отдельно. Параметры события: productData — данные товара, discounts — скидки, попавшие в показ (может быть пустым), scope — область показа (catalog или product), prices — текущая пара цен, object — экземпляр класса Discounts. Подписчик подменяет цены через $object->showPrices['price'] и $object->showPrices['old_price']; цену, которую он не тронул, пакет считает как раньше. Кейс: товар-комплект, выгода которого не выражается ни одной записью скидки — сайт считает пару цен сам и отдаёт её пакету, а показ остаётся единым.
  • НОВОЕ: свойство $object->externalPrices. Сайт, который подаёт свои цены показа, выставляет его в подписчике события mspdOnFilterIdsByDiscount. Флаг снимает ранний выход по корзине — без него товар, у которого скидок нет вовсе, до расчёта цены показа не доходит. Внимание: со снятым ранним выходом в ответ попадают и товары, о которых сервер раньше молчал, поэтому старая цена таких товаров берётся из карточки товара — если она там заполнена, а показывать её не нужно, подписчику следует обнулить её самому. Пока подписчика нет, флаг остаётся выключенным и ответ сервера не меняется.
  • ИЗМЕНЕНО: пункт меню компонента больше не возвращается в свою категорию при обновлении. Раньше обновление пакета перезаписывало пункт меню целиком, и он «уезжал» обратно из той категории, куда его перенёс администратор сайта. Теперь существующий пункт меню при обновлении не трогается совсем — сохраняются и его расположение, и порядок в списке.

2.2.0-rc — 21.07.2026

  • НОВОЕ: у каждой скидки появились настройки показа — «Показ в каталоге», «Показ в карточке» и «Описание акции». Каждая принимает три значения: «По умолчанию» (как раньше — по системным настройкам), «Показывать» и «Не показывать». Раньше показом скидок в списках товаров управляли только глобальные mspd_show_for_all и mspd_check_common_conditions_on_show: включив показ ради одной акции, приходилось включать его для всех скидок сразу. «Показывать» означает, что скидка участвует в расчёте цены для показа независимо от корзины: цена со скидкой и зачёркнутая старая цена видны даже при пустой корзине и невыполненных общих условиях (сумма, количество). «Не показывать» убирает скидку из показа совсем, в том числе при mspd_show_for_all=1. Настройки влияют только на показ — корзина продолжает считать фактические скидки по прежним правилам, поэтому у скрытой в каталоге скидки цена в корзине может отличаться от показанной. Кейс: «Товары недели» показывают старую и новую цену в каталоге, а «−20% на вторую единицу» в списке товаров не показывается.
  • НОВОЕ: каталог и карточка товара различаются атрибутом data-mspd-scope на обёртке товара или любом его предке: значение product — карточка товара, всё остальное (и отсутствие атрибута) — каталог. Шаблоны без атрибута работают как раньше.
  • НОВОЕ: системная настройка mspd_show_discount_info (по умолчанию «Да») — дефолт показа описания акции для скидок, у которых эта настройка оставлена в значении «По умолчанию». Раньше инфоблок по mspd_info_tpl выводился всегда и отключить его было нечем.
  • Обновление БД: у скидок появились поля show_in_catalog, show_in_product, show_description (по умолчанию 0 — «По умолчанию»), поэтому на уже работающих сайтах поведение не меняется. Затрагивает: модель mspdDiscount, Discounts::filterIdsByDiscount, Discounts::getProductDiscounts, Base::initialize, шаблон управления скидкой, js-модуль Discounts.

2.1.22-rc — 21.07.2026

  • НОВОЕ: тип применения «Суммируется с другими» (apply_type=3). Такая скидка не участвует в эксклюзивном отсечении: если у товара есть скидка «Только эту скидку», суммируемая всё равно применяется поверх неё. Работает только при mspd_many_behaviour=3 — при других значениях пакет применяет одну скидку-победителя, и суммируемая в расчёт не попадает. Порядок применения в корзине: обычные скидки → «Все кроме этой» → «Суммируется с другими». Благодаря такому порядку суммируемая скидка не отбирает у «Все кроме этой» её товары: для отбора товар по-прежнему считается «без скидки», если получил только суммируемую. Математика — каскад от текущей цены, как и у остальных скидок: 1000 ₽ при основной 10% и суммируемой 5% даёт 855 ₽, а не 850 ₽. Превью карточек товара показывает ту же цену, что даст корзина: суммируемая скидка добавляется в стек последней и не влияет на показ «Все кроме этой». Кейс: «coming_soon 5%» поверх основной скидки и поверх эксклюзивных «Товаров недели». Затрагивает: Discounts::filterDiscountsByApplyType, Discounts::applyDiscounts, Discounts::filterIdsByDiscount, шаблон управления скидкой.

2.1.21-rc — 17.07.2026

  • FIX: промокод больше не «слетает» при применении/отмене через браузер. Причина — гонка сессии: фронт после applypromocode параллельно слал getDiscounts + getCart, и их запись в незалоченную сессию MODX затирала только что записанный промокод ($_SESSION['mspd']). Теперь applypromocode/cancelpromocode сами возвращают пересчитанную корзину (products/totals/giftsChanged) в своём ответе, а фронт рендерит из него без отдельных follow-up-запросов — гоняющегося запроса больше нет. Затрагивает: сниппет mspdConnector, JS-модуль promocodes.js (successHandler).

2.1.20-rc — 16.07.2026

  • ИЗМЕНЕНИЕ ПОВЕДЕНИЯ: тип применения «Все кроме этой» (apply_type=2) теперь учитывает ФАКТИЧЕСКИ применённые скидки, а не применимые по условиям. Такая скидка применяется после остальных и только к товарам, не получившим скидку в текущем пересчёте корзины. Раньше она отбрасывалась, если у товара были другие подходящие по условиям скидки — даже когда те не дали товару никакой выгоды (например, из-за отступа). Кейс: промокод со скидкой «Все кроме этой» теперь даёт выгоду товарам, оставшимся без основной акции. В превью карточек такая скидка не стекуется с другими. Работает при mspd_many_behaviour=3.

2.1.19-rc — 16.07.2026

  • Включающие «Произвольные условия» скидки (Discounts::checkProductOptions) теперь проверяются все, а не только первое: условия группируются по ключу, внутри одного ключа — ИЛИ, между ключами — И. Раньше цикл завершался return на первой строке, и несколько include-условий не работали (в т.ч. нельзя было ограничить скидку несколькими контекстами через context_key). Exclude-условия — без изменений. Тест: dev/tests/check-product-options.php (13 кейсов).

2.1.18-rc — 16.06.2026

  • Исправлена ошибка mixed content в админке: из URL подключаемых mgr-скриптов в контроллерах (getdiscounts, getpromocodes, discount/manage, promocode/manage) убран лишний слеш после {$assetsUrl}. Переменная уже оканчивается на js/, поэтому в import ... from "{$assetsUrl}/mgr/..." получался двойной слеш js//mgr/.... Теперь путь корректный — js/mgr/....

2.1.17-rc — 16.06.2026

  • Логирование переведено на mxLogger. Отладочная трассировка скидок ($this->modx->log под mspd_debug) теперь делегируется сервису mxLogger через новый класс MsProductDiscounts\Logger и фасад Base::log(). Если mxLogger не установлен — записи молча пропускаются.
  • Новая настройка mspd_log_level (минимальный уровень debug|info|warning|error, по умолчанию error; единый приёмник mxLogger, включено из коробки), консистентно с mxlogger.min_level. Переключатель mspd_debug сохранён.
  • Тэги: базовые msproductdiscounts + cart, этап discount. process_uid воронки — по сессии. Ранее негейтнутые вызовы $this->modx->log (например дамп SQL) теперь тоже под гейтом mspd_debug.
  • ВНИМАНИЕ: перед сборкой настройку mspd_log_level создать в менеджере (namespace msproductdiscounts) — сборка тянет настройки из БД стенда.

2.1.16-rc — 23.04.2026

  • Улучшил совместимость с msAltCart: корзина теперь обновляется по событиям msac:row:add/replace/remove:after, когда msAltCart активен, стандартный ms2-колбэк пропускается во избежание двойного запроса и гонок.
  • Сделал свойство $apply в Discounts nullable (?bool), чтобы плагины на событиях mspdOnBeforeCheckDiscount/mspdOnCheckDiscount могли безопасно сбрасывать флаг применения скидки.

2.1.15-rc — 14.04.2026

  • Исправлена потеря скидок по промокодам: дисконтированные цены теперь сохраняются в сессию перед обработкой подарков, что предотвращает перезатирание цен при перечитывании корзины.

2.1.14-rc — 16.03.2026

  • Исправление ошибок

2.1.13-rc — 14.03.2026

  • Добавил поддержку мультиконтекстных подарков: товар-подарок автоматически резолвится в текущий контекст по связующему полю (настройка mspd_gift_link_field).
  • Добавил поддержку резолва модификаций подарков в другой контекст по набору опций.
  • Добавил проверку соответствия товара-подарка текущему контексту перед добавлением в корзину.

2.1.12-rc — 14.03.2026

  • Перевёл добавление и удаление подарков на стандартные методы miniShop2 (cart->add/cart->remove) с защитой от рекурсии через статический флаг.
  • Добавил флаг giftsChanged в ответ коннектора getcart для отслеживания операций с подарками.
  • Добавил вызов window.msAltCart.updateCarts() при изменении подарков в корзине.
  • Добавил событие mspd:gift:manage для кастомной обработки изменений подарков на фронтенде.
  • Добавил версионирование подключаемых JS-модулей (?v=) на основе времени последнего изменения файлов.

2.1.11-rc — 14.03.2026

  • Убрал округление строк при получении статуса корзины в коннекторе.

2.1.10-rc — 13.03.2026

  • Добавил автоматическое удаление промокодов при удалении скидки, если она была единственной привязанной к ним.
  • Добавил предупреждение в окне подтверждения удаления скидки о промокодах, которые будут удалены вместе с ней.

2.1.9-rc — 12.03.2026

  • Добавил раздельные permissions для скидок (mspd_discounts) и промокодов (mspd_promocodes).
  • Добавил проверку permissions в контроллерах скидок и промокодов.
  • Добавил резольвер для регистрации permissions в AdministratorTemplate и политике Administrator.
  • Добавил установку permissions на пунктах меню.

2.1.8-rc — 10.03.2026

  • Добавил приведение к float в Discounts::applyDiscount()

2.1.7-rc — 2025-12-26 - 06.03.2026

  • Уменьшил размер ключа для промокода.
  • Добавил новый тип скидки "Подарок" (unit "g") — автоматическое добавление товара-подарка в корзину.
  • Добавил поддержку модификаций msOptionPrice в качестве подарка (чекбокс gift_class_key).
  • Добавил системную настройку mspd_gift_price для указания цены подарка в корзине.
  • Добавил учёт manyBehaviour и apply_type для подарочных скидок.
  • Добавил исключение подарков из подсчёта позиций, количества и стоимости корзины.
  • Добавил автоматическое удаление подарка из корзины при несоответствии условиям скидки.
  • Добавил панель "Произвольные условия" (ключ-значение) в настройках скидки.
  • Добавил поддержку кастомных значений для чекбоксов в админке (data-checked-value, data-unchecked-value).

2.1.6-rc — 2025-12-06 - 2025-12-24

  • Исправил чанк информации о скидке.
  • Исправил применение скидок при указании отношения к количеству "кратно"
  • Добавил округление.

2.1.5-rc — 2025-10-20 - 2025-12-06

  • Добавил задержку после применения или отмены промокода перед получением данных корзины.
  • Перенёс переопределение корзины в метод applyDiscounts.
  • Добавил прерывание проверки соответствия условий скидки конкретному товару при нахождении первого несоответствия
  • Исправил ошибку связанную с удалением неподходящих скидок.
  • Изменил значение defaultEngine в файле msproductdiscounts.mysql.schema.xml на InnoDB

2.1.4-rc — 2025-10-14

  • Исправил ошибку в методах cancelPromocode и applyPromocode

2.1.3-rc — 2025-09-30 - 2025-10-01

  • Разделил загрузку вебконфига и файла js для возможности раздельного подключения.
  • Добавил явное преобразование типов при передачи значений метод formatPrice

2.1.2-rc — 2025-09-23

  • Добавил возможность отключения проверки общих условий при показе скидок без применения при помощи системной настройки mspd_check_common_conditions_on_show.

2.1.1-rc — 2025-09-19

  • Добавил проверку общих условий при показе скидок без применения.

2.1.0-rc12 — 2025-08-17

  • Добавил явное приведение типов для статусов.

2.1.0-rc11 — 2025-08-03

  • Мелкие правки.

2.1.0-rc10 — 2025-08-02

  • Добавил в системное событие mspdOnBeforeApplyDiscount.

2.1.0-rc9 — 2025-08-02

  • Добавил в JS событие mspd:on:get:productdata.

2.1.0-rc8 — 2025-08-02

  • Установил глобальную переменную msProductDiscounts в JS.

2.1.0-rc7 — 2025-08-02

  • Добавил удаление активных скидок для которых нет товаров.
  • Сделал наличие JS miniShop2 необязательным.

2.1.0-rc6 — 2025-05-13 - 2025-05-17

  • Добавил проверку на наличие старой цены перед расчётом скидки.
  • Добавил приведение типов в методе checkCommonConditions.
  • Поменял discount_total на discount_cost.
  • Добавил возможность делать скидки с учётом наличия акционных товаров в корзине.

2.1.0-rc5 — 2025-05-05

  • Добавил проверку $order_id при удалении привязки промокода к заказу.
  • Добавил обработку колбэков минишопа для получения скидок в каталоге.

2.1.0-rc4 — 2025-05-02 - 2025-05-04

  • Добавил фильтр скидок на странице редактирования промокода.
  • Добавил возможность переходить к редактированию скидки со страницы редактирования промокода.
  • Добавил системное событие mspdOnBeforeGetPromocode.
  • Добавил системное событие mspdOnGetWebConfig.
  • Сделал рефакторинг JavaScript.
  • Добавил зависимость от компонента SendIt.
  • Вынес шаблон информации о скидках в чанк.
  • Добавил сниппет mspdConnector.
  • Переписал сниппет msCart так чтобы он корректно отображал данные товара со скидкой в корзине.

2.1.0-rc3 — 2025-05-02

  • Добавил фильтр промокодов по названию скидки

2.1.0-rc2 — 2025-05-02

  • Доработал совместную работу обычных скидок и скидок по промокодам.

2.1.0-rc1 — 2025-04-29

  • Добавил возможность создавать промокоды.

2.0.0-rc5 — 2025-04-11

  • Добавил подключение пакета в процессор поиска совпадений.
  • Улучшил совместимость с msAltCart.
  • Добавил системные события mspdOnGetProductDiscounts и mspdOnBeforeCheckDiscount
  • Добавил куку mspd_context для сохранения текущего контекста
  • Добавил событие mspdOnBeforeGetDiscounts

2.0.0-rc4 — 2025-04-08

  • Исправил метод сброса корзины при отсутствии скидок

2.0.0-rc3 — 2025-02-15

  • Исправил условие в методе checkCommonConditions()

2.0.0-rc2 — 2025-02-08

  • Добавил проверку общих параметров скидок таких как отношение к количеству и минимальная сумма

2.0.0-rc1 — 2024-09-15

  • Added parameter showHidden in category tree.
  • Fix some bugs in JS
  • Add check in get conditions method.
  • Fixed the error of passing parameters from the mspdOnCheckDiscount plugin.
  • Fixed error on check allowed resources.
  • Fixed an error where the processor returns an empty result if no discounts are found
  • Add for product in cart information about value and unit of discount
  • Change way to get original price
  • Exclude class mspdPeriod from the list of checked classes

2.0.0-rc — 2024-09-14

  • Added limit, offset and context parameters to discounts.
  • Added composer autoloader.
  • Rewrite base class for discounts.
  • Exclude using Session for keeping discounts.
  • No compatibility with previous versions.
  • No compatibility with other discount components

1.4.7 — 2023-12-25

  • Added the data-mspd-cart-row="${key}" attribute to indicate a separate item in the cart
  • Аdded support for multiple shopping carts
  • The mspd_cart_page system setting has been replaced with mspd_tpls_with_cart

1.4.6 — 2023-03-25

  • Fixed bug with rename manager path
  • Fixed bug with week day check

1.4.5 — 2023-03-17

  • Fixed bug on remove additional objects

1.4.4 — 2023-02-25

  • Аdded output of information about the discount
  • Fixed bug with clearing the resource field
  • Fixed the error of multiple application of discounts
  • Added automatic activation and deactivation of discounts
  • Changed the minimum validity period to 1 hour
  • Fixed bug Clean button

Added

  • session cleanup

1.4.3 — 2022-12-26

Fixed some bugs

1.4.2 — 2022-12-25

Fixed some bugs

Added

  • Support for ajax pagination in pdoPage

1.4.1 — 2022-12-24

Added

  • JS Event mspd-get-discounts
  • Processor GetProductDiscountsProcessor
  • Reload cart interval

Removed

  • Snippet getProductDiscounts

1.3.1 — 2022-12-04

Added

  • Mass deletion of conditions

Changed

  • Fixed a bug with updating the cart

1.2.3 — 2022-12-04

Added

  • "discount_price" parameter to the getProductDiscounts snippet
  • Bread crumbs for categories

Changed

  • Product search
  • Build package

1.2.0 — 2022-12-03

Added

  • Search for products with msearch2 if it is installed
  • Category tree
  • Processors: /mgr/products/, /mgr/exproducts/, /mgr/categories/ and /mgr/excategories/

Changed

  • Selecting a resource with a description of the discount

Removed

  • Some modal windows on discount manage page
  • Processors: /mgr/product/, /mgr/exproduct/, /mgr/category/ and /mgr/excategory/

1.0.1 — 2022-12-01

  • Initial release.

Последние обсуждения в сообществе MODX.pro