msOptionsPrice2 и составной товар


Возникла необходимость при помощи msOptionsPrice2 реализовать составной товар. Оказалось, что компонент не умеет прибавлять к начальной цене товара несколько цен из модификаций одной опции.
Например, есть опция Наполнители, у нее около 20 значений. На фронте, при выборе каждого из значений, цена товара должна увеличиваться на N рублей. Проблема заключается в том, что имена тегам чекбоксов мы даем в виде массива:
<input type="checkbox" name="cb[]">
Компонент, в свою очередь, с таким работать не умеет. Предлагаю решение в четыре простых шага!

Решение проблемы


Предположим у нас есть опция Наполнители с ключом wok_fillers.

Шаг 1

При редактировании товара добавляем значения:


Шаг 2

Создаем модификацию для каждого значения, указывая:
Тип = +
Цена = Сумма, которую нужно прибавить к начальной стоимости товара
 


Шаг 3

На странице товара выводим список чекбоксов:
{'!msOptionsPrice.modification' | snippet : [
    'type' => 2,
    'where' => [
        'Option.key' => 'wok_fillers',
    ],
    'sortby' => '{"msopModification.id": "ASC"}',
    'limit' => 100,
    'tpl' => '@INLINE
        {foreach $options as $name => $value}
            <div>
                <label for="wok_fillers_{$idx}">
                    <input type="checkbox" name="options[{$name}][]" id="wok_fillers_{$idx}" value="{$value}"> {$value} <b style="color:red">(+{$modification.price} руб.)</b>
                </label>
            </div>
        {/foreach}
    ',
]}
Таким образом, мы выведем список модификаций для значений опции wok_fillers в виде чекбоксов, с прибавочной ценой рядом с названием значения.

Шаг 4

Создаем плагин msopModificationPrice на событие msOnGetProductPrice, указывая приоритет 999. Высокий приоритет необходим для того, чтобы наш плагин отрабатывал после плагина msOptionsPrice.


switch ($modx->event->name) {
    case "msOnGetProductPrice":
        $returned = (array)$modx->getPlaceholder('_returned_price');
        if (!isset($returned['price'])) {
            return;
        }
        $id = $returned['id'];
        $price = $returned['price'];
        $options = !empty($returned['msoptionsprice_options'])
            ? $returned['msoptionsprice_options']
            : (!empty($_REQUEST['options'])
                ? $_REQUEST['options']
                : array());
        $options = array_diff_key($options, array_flip(array('modifications', 'modification')));
        
        foreach (array_keys($options) as $k) {
            if (!empty($options[$k]) && is_array($options[$k])) {
                foreach ($options[$k] as $v) {
                    $q = $modx->newQuery('msopModification')
                        ->innerJoin('msopModificationOption', 'Option',
                            "Option.mid = msopModification.id AND Option.key = '{$k}' AND Option.value = '{$v}'")
                        ->select(array('price'))
                        ->where(array(
                            'msopModification.rid' => $id,
                            'msopModification.type' => 2,
                            'msopModification.active' => true,
                        ))
                        ;
                    if ($q->prepare() && $q->stmt->execute()) {
                        if ($price_add = $q->stmt->fetchColumn()) {
                            $price += $price_add;
                        }
                    }
                }
            }
        }
        $modx->event->returnedValues['price'] = $returned['price'] = $price;
        $modx->setPlaceholder('_returned_price', $returned);
        break;
}
 

Итого


На странице товара, при выборе чекбоксов опции Наполнители к начальной цене будет прибавляться дополнительная стоимость каждой модификации этой опции.
Павел Гвоздь
12 августа 2017, 06:56
modx.pro
33
7 625
+14
Поблагодарить автора Отправить деньги

Комментарии: 42

Воеводский Михаил
12 августа 2017, 10:02
+2
Если я правильно понял суть, то msAddLinked это умеет делать по умолчанию. Мб, управление в некоторых случаях менее удобно. С другой стороны, изменение добавочной цены централизованно (при редактировании товара-опции), а не в каждом товаре отдельно должно сокращать время на поддержку магазина менеджерами.

Но, повторюсь, мог не уловить всей глубины задумки.
    Павел Гвоздь
    12 августа 2017, 10:09
    +1
    Да, демо версия говорит, что умеет. Только значения Наполнителей будут товарами.
    Однако не стал экспериментировать, т.к. клиент купил msOptionsPrice2 и msExtraFields. Второй — для решения задачи, описанной в данном посте (до сих пор не знаю, умеет ли он такое). После установки обоих, функция изменения стоимости у первого резко отвалилась. После изучения кода было ясно, что компоненты в одном проекте вместе не уживаются. Удалил второй (т.к. он не используется на проекте, в отличие от первого), еще немного поковырял первый и родилось вот такое решение.
      Воеводский Михаил
      12 августа 2017, 10:44
      1
      +1
      Только значения Наполнителей будут товарами.
      В этом свои преимущества, как я написал — изменение цены такого наполнителя в одном месте, а не в каждом товаре по отдельности.

      родилось вот такое решение
      Как ни крути, любые варианты решения задач полезны. Не исключаю. что в некоторых случаях такое решение будет рациональнее, чем приплетать msAddLinked.
    Володя
    12 августа 2017, 11:04
    +1
    Вот что значит свежая голова и умелые руки…
    Оказалось, что компонент не умеет прибавлять к начальной цене товара несколько цен из модификаций одной опции.
    наверно неверно сформулировал. Компонент просто не работает с опциями массивами…
    $modx->newQuery('msopModification')
    для этого можно задействовать метод getModificationByOptions
    ps. И да, для таких вопросов есть ТП в которой они успешно решаются…
      Павел Гвоздь
      12 августа 2017, 11:09
      0
      наверно неверно сформулировал. Компонент просто не работает с опциями массивами…
      Да нет, вроде верно. Если опция не массив, то он тоже не прибавит к начальной цене все выбранные значения, а только последнее выбранное.

      Я бы отписал в ТП, только не я покупал компонент, а заказчик. Задачу было необходимо решить скорее, поэтому решил сам. Естественно, когда если ты внедришь поддержку работы с массивами опций, то данное решение утратит актуальность и я его удалю. Только просьба сообщить об этом тут. :)
        Володя
        12 августа 2017, 11:13
        +3
        Задачу было необходимо решить скорее, поэтому решил сам
        отлично! Могу только сказать спасибо!)
        Естественно, когда если ты внедришь поддержку работы с массивами опций, то данное решение утратит актуальность и я его удалю. Только просьба сообщить об этом тут. :)
        Не собираюсь в ближайшее время это делать. Это повлияет на логику компонента…
        Да и зачем удалять. Хороший пример как можно модифицировать стандартную логику расчета.
      Виктор Долгий
      19 августа 2017, 19:27
      0
      Для этой фичи я готов купить компонент на еще один проект, может, два…

      Павел, несколько вопросов:
      1.
      <input type="checkbox" name="options[{$name}][]"

      Может,
      <input type="checkbox" name="options[{$name}]"
      ..., а то не понимает скрипт, или у меня не работает…

      2. У меня приходит не массив опций, а только одна, последняя опция. Не проходит проверку
      if (!empty($options[$k]) && is_array($options[$k])) {...

      3. Что за плейсхолдер _returned_price?
        Виктор Долгий
        19 августа 2017, 19:29
        0
        При этом, если снять все чекбоксы, приходит первое значение, то есть, первая опция.
          Виктор Долгий
          19 августа 2017, 22:07
          0
          Это был очередной глюк компонента. пересоздал опции, если сняты все чекбоксы, приходит массив с пустым msoptionsprice_options.
          Виктор Долгий
          19 августа 2017, 22:14
          0
          На счет _returned_price — посмотрел его в компоненте msOptionsPrice.
            Павел Гвоздь
            19 августа 2017, 22:16
            0
            Если вы сделаете все так, как описано в статье, то работать будет. Я уже на двух проектах такое повторил.
              Виктор Долгий
              19 августа 2017, 22:18
              0
              Не работает. Сижу, проверяю. Ну никак.
                Павел Гвоздь
                19 августа 2017, 22:23
                0
                Приоритет плагину указали?
                  Виктор Долгий
                  19 августа 2017, 22:25
                  0
                  Все, как тут, только имя опции изменил. Если указываю name=«options[{$name}][]», то цена меняется только по первой опции, если name=«options[{$name}]», то цена меняется в зависимости от опции, но не суммируется.
                    Павел Гвоздь
                    19 августа 2017, 22:29
                    0
                    К сожалению, так не смогу выявить проблему. Если готовы оплатить час работы (1.5кр) — пишите доступы на почту или в скайп.
                      Виктор Долгий
                      19 августа 2017, 22:35
                      0
                      Ок, еще поковыряюсь, коль начал, если не разберусь — отпишу.
                        Виктор Долгий
                        19 августа 2017, 22:42
                        0
                        А пример работающего сайта есть?
                          Павел Гвоздь
                          19 августа 2017, 22:48
                          0
                          Есть. Не думаю, что заказчик будет рад от того, что я ссылками на его сайты разбрасываюсь…
                    Виктор Долгий
                    19 августа 2017, 22:28
                    0
                    Вот тут у меня приходит только последняя опция (не массив опций из чекбоксов): $returned['msoptionsprice_options']
                      Володя
                      20 августа 2017, 07:41
                      0
                      Все, как тут, только имя опции изменил. Если указываю name=«options[{$name}][]», то цена меняется только по первой опции, если name=«options[{$name}]», то цена меняется в зависимости от опции, но не суммируется.
                      если вы задали опцию как
                      options[{$name}]
                      то естественно на сервер придет не массив, а только последнее значение…
                        Виктор Долгий
                        22 августа 2017, 14:31
                        0
                        Володя, еще вопрос, думаю, не только для меня актуален. На странице товара (не в списке), при штатном использовании компонента (без составного товара), если у контейнера выставлен id=«msProduct», то взаимосвязь чекбоксов и картинок теряется, а если айди убрать, то связь восстанавливается, но всегда выставляется чекнутым первый чекбокс. Пробовал и по-разному, с выставлением массивов опций, может, подскажете, как восстановить взимосвязь галереи и опций, чтобы первый чекбокс не проставлялся. Думаю, вам, как разработчику будет проще дать ответ, нежели мне снова перебирать компонент. Или, такой глюк только у меня?
                          Виктор Долгий
                          22 августа 2017, 16:07
                          0
                          При выборе картинки, к которой не привязана ни одна модификация, также выставляется первая в списке опция.
                            Виктор Долгий
                            22 августа 2017, 16:31
                            0
                            А если не указывать в форме скрытый инпут options (с любыми вариациями определения, масивом и нет), то и с id=«msProduct» все работает.
                              Виктор Долгий
                              24 августа 2017, 19:54
                              0
                              Из-за особенностей процессора, нулевая опция (из-за скрытого инпута) ломает логику. Можно легко полечить, например, этим:
                              unset($options['0']);
                              $modx->event->returnedValues['options'] = $options;
                              , в плагине, на событие msopOnBeforeGetModification.
                          Виктор Долгий
                          19 августа 2017, 22:30
                          0
                          MODx Revo 2.5.7-pl
                          pdoTools 2.9.1-pl
                          msOptionsPrice2 2.3.33-beta
                          miniShop 2.4.11-pl
                        Виктор Долгий
                        22 августа 2017, 14:31
                        0
                        А вы с галереей подружили опции?
                          Павел Гвоздь
                          22 августа 2017, 14:35
                          -2
                          Не пробовал даже. В задаче этого не требовалось.
                      Виктор Долгий
                      20 августа 2017, 14:16
                      0
                      Со свежей головой разобрался. Если кто-то еще будет ломать голову, то:

                      1. Чекбоксы указываются таки с параметром
                      name="options[{$name}][]"
                      , тогда в массиве опций передается массив зачений.

                      2. У формы нужно указать (определить) скрытый
                      <input type="hidden" name="options[]" value="[]"> или <input type="hidden" name="options[]" value="">
                      , из-за этого, по сути, ничего и не выходило, проморгал.
                        Виктор Долгий
                        20 августа 2017, 16:37
                        +1
                        Еще, если кому-то нужно, чтобы все это работало еще и в списке товаров и выставлялись чекбоксы с mSearch2, нужно поправить default.js у msOptionsPrice2 (/assets/components/msoptionsprice/js/web/default.js), строки 722-731:

                        с
                        if (rid) {
                            inputs = $(msOptionsPrice.Product.cost + msOptionsPrice.Product.prefix + rid)
                                .closest(msOptionsPrice.Product.form)
                                .find('[name="' + name + '"]');
                            }
                            else {
                                inputs = $(msOptionsPrice.Product.cost)
                                .closest(msOptionsPrice.Product.form)
                                .find('[name="' + name + '"]');
                            }
                        на
                        if (rid) {
                            inputs = $(msOptionsPrice.Product.cost + msOptionsPrice.Product.prefix + rid)
                                .closest(msOptionsPrice.Product.form)
                                .find('[name*="' + name + '"]');
                            }
                            else {
                                inputs = $(msOptionsPrice.Product.cost)
                                .closest(msOptionsPrice.Product.form)
                                .find('[name*="' + name + '"]');
                            }
                        , то есть, добавить к селектору name звездочку — *.
                          Денис
                          13 ноября 2017, 12:58
                          0
                          Всем привет! Сдеал все по инструкции. Без скрытого input не работает. Добавляю, тогда в корзине появляются такие записи. Может кто то сталкивался с таким?
                          Дарья Сизова
                          14 апреля 2020, 21:45
                          0
                          Здравствуйте! Подскажите, применима ли статья к последней версии [msOptionsPrice2]?
                          Сергей
                          04 октября 2020, 14:28
                          0
                          Если у товара нет зависимых опций, размер цвет, то все работает корректно, но если у товара уже есть зависимость, то есть проблема. Он все эти опции добавляет ко все вариантам + при установки чекбокса прибавляется стоимость еще раз. Кто то решал эту задачу?
                            Яна Митрофанова
                            26 февраля 2021, 08:14
                            0
                            Сергей, здравствуйте! Вы не нашли в чем проблема?
                            Возникла такая же, сразу добавляет цену к товару, а при отметке флажка добавляет еще раз…
                              Александр
                              16 июня 2021, 17:09
                              0
                              Здравствуйте! Нашли решение проблемы? такая же ситуация.
                            Денис Усманов
                            02 августа 2023, 23:20
                            0
                            Вопрос возможно давно не актуальный, но вот решение:

                            <?php
                            switch ($modx->event->name) {
                                case "msOnGetProductPrice":
                                    $returned = (array)$modx->getPlaceholder('_returned_price');
                                    if (!isset($returned['price'])) {
                                        return;
                                    }
                                    $id = $returned['id'];
                                    foreach (array_keys($options) as $k) {
                                        if (!empty($options[$k]) && is_array($options[$k])) {
                                            foreach ($options[$k] as $v) {
                                                $q = $modx->newQuery('msopModification')
                                                    ->innerJoin('msopModificationOption', 'Option',
                                                        "Option.mid = msopModification.id AND Option.key = '{$k}' AND Option.value = '{$v}'")
                                                    ->select(array('price'))
                                                    ->where(array(
                                                        'msopModification.rid' => $id,
                                                        'msopModification.type' => 2,
                                                        'msopModification.active' => true,
                                                    ))
                                                    ;
                                                if ($q->prepare() && $q->stmt->execute()) {
                                                    $price = $price - $q->stmt->fetchColumn();
                                                }
                                            }
                                        }
                                    }
                                    $options = !empty($returned['msoptionsprice_options'])
                                        ? $returned['msoptionsprice_options']
                                        : (!empty($_REQUEST['options'])
                                            ? $_REQUEST['options']
                                            : array());
                                    $options = array_diff_key($options, array_flip(array('modifications', 'modification')));
                                    
                                    foreach (array_keys($options) as $k) {
                                        if (!empty($options[$k]) && is_array($options[$k])) {
                                            foreach ($options[$k] as $v) {
                                                $q = $modx->newQuery('msopModification')
                                                    ->innerJoin('msopModificationOption', 'Option',
                                                        "Option.mid = msopModification.id AND Option.key = '{$k}' AND Option.value = '{$v}'")
                                                    ->select(array('price'))
                                                    ->where(array(
                                                        'msopModification.rid' => $id,
                                                        'msopModification.type' => 2,
                                                        'msopModification.active' => true,
                                                    ))
                                                    ;
                                                if ($q->prepare() && $q->stmt->execute()) {
                                                    // $price = $price - $q->stmt->fetchColumn();
                                                    if ($price_add = $q->stmt->fetchColumn()) {
                                                        $price += $price_add;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    $modx->event->returnedValues['price'] = $returned['price'] = $price;
                                    $modx->setPlaceholder('_returned_price', $returned);
                                    break;
                            }
                              Sergey Korn
                              19 февраля 2024, 13:00
                              0
                              Спасибо огромное! А как можно его допилить, чтобы и вес тоже менялся?
                                Денис Усманов
                                19 февраля 2024, 23:42
                                0
                                Я уже не имею доступа к проекту, где это делал, пиши мне в Телеграм, помогу, контакты у меня в профиле или на странице О нас.
                            Михаил
                            21 августа 2021, 03:51
                            0
                            Здравствуйте! У меня заработало только так:

                            В шаблоне карточки товара:
                            [[!msOptionsPrice.option?
                            &product=`[[*id]]`
                            &name=`fillers`
                            &type=`2`
                            &where=`{"Option.key": "fillers"}`
                            &sortby=`{"msopModification.id": "ASC"}`
                            &limit=`100`
                            &tpl=`tpl.msOptionsFillers`
                            ]]

                            Чанк tpl.msOptionsFillers:
                            {foreach $options as $name => $values}
                                   {foreach $values as $value} <label for="fillers_{$idx}">
                                        <input type="checkbox" name="options[{$name}][]" id="fillers_{$idx}" value="{$value}"><input type="hidden" name="options[]" value="[]"> {$value} <b style="color:red">(+{$modification.price} руб.)</b>
                                    </label> {/foreach}
                            {/foreach}

                            Но почему то не выводится добавочная цена {$modification.price}
                              Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                              42