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, 09:56    Павел Гвоздь   
17    609 +14

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

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

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

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

        Я бы отписал в ТП, только не я покупал компонент, а заказчик. Задачу было необходимо решить скорее, поэтому решил сам. Естественно, когда если ты внедришь поддержку работы с массивами опций, то данное решение утратит актуальность и я его удалю. Только просьба сообщить об этом тут. :)
        1. Володя 12 августа 2017, 11:13 # +3
          Задачу было необходимо решить скорее, поэтому решил сам
          отлично! Могу только сказать спасибо!)
          Естественно, когда если ты внедришь поддержку работы с массивами опций, то данное решение утратит актуальность и я его удалю. Только просьба сообщить об этом тут. :)
          Не собираюсь в ближайшее время это делать. Это повлияет на логику компонента…
          Да и зачем удалять. Хороший пример как можно модифицировать стандартную логику расчета.
      2. Виктор Долгий 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?
        1. Виктор Долгий 19 августа 2017, 19:29 # 0
          При этом, если снять все чекбоксы, приходит первое значение, то есть, первая опция.
          1. Виктор Долгий 19 августа 2017, 22:07 # 0
            Это был очередной глюк компонента. пересоздал опции, если сняты все чекбоксы, приходит массив с пустым msoptionsprice_options.
          2. Виктор Долгий 19 августа 2017, 22:14 # 0
            На счет _returned_price — посмотрел его в компоненте msOptionsPrice.
            1. Павел Гвоздь 19 августа 2017, 22:16 # 0
              Если вы сделаете все так, как описано в статье, то работать будет. Я уже на двух проектах такое повторил.
              1. Виктор Долгий 19 августа 2017, 22:18 # 0
                Не работает. Сижу, проверяю. Ну никак.
                1. Павел Гвоздь 19 августа 2017, 22:23 # 0
                  Приоритет плагину указали?
                  1. Виктор Долгий 19 августа 2017, 22:25 # 0
                    Все, как тут, только имя опции изменил. Если указываю name=«options[{$name}][]», то цена меняется только по первой опции, если name=«options[{$name}]», то цена меняется в зависимости от опции, но не суммируется.
                    1. Павел Гвоздь 19 августа 2017, 22:29 # 0
                      К сожалению, так не смогу выявить проблему. Если готовы оплатить час работы (1.5кр) — пишите доступы на почту или в скайп.
                      1. Виктор Долгий 19 августа 2017, 22:35 # 0
                        Ок, еще поковыряюсь, коль начал, если не разберусь — отпишу.
                        1. Виктор Долгий 19 августа 2017, 22:42 # 0
                          А пример работающего сайта есть?
                          1. Павел Гвоздь 19 августа 2017, 22:48 # 0
                            Есть. Не думаю, что заказчик будет рад от того, что я ссылками на его сайты разбрасываюсь…
                            1. Виктор Долгий 19 августа 2017, 22:52 # 0
                              А в личку, для личного анализа? Если нет — пойму.
                              1. Павел Гвоздь 19 августа 2017, 22:53 # 0
                                Нет.
                      2. Виктор Долгий 19 августа 2017, 22:28 # 0
                        Вот тут у меня приходит только последняя опция (не массив опций из чекбоксов): $returned['msoptionsprice_options']
                        1. Володя 20 августа 2017, 07:41 # 0
                          Все, как тут, только имя опции изменил. Если указываю name=«options[{$name}][]», то цена меняется только по первой опции, если name=«options[{$name}]», то цена меняется в зависимости от опции, но не суммируется.
                          если вы задали опцию как
                          options[{$name}]
                          
                          то естественно на сервер придет не массив, а только последнее значение…
                          1. Виктор Долгий 22 августа 2017, 14:31 # 0
                            Володя, еще вопрос, думаю, не только для меня актуален. На странице товара (не в списке), при штатном использовании компонента (без составного товара), если у контейнера выставлен id=«msProduct», то взаимосвязь чекбоксов и картинок теряется, а если айди убрать, то связь восстанавливается, но всегда выставляется чекнутым первый чекбокс. Пробовал и по-разному, с выставлением массивов опций, может, подскажете, как восстановить взимосвязь галереи и опций, чтобы первый чекбокс не проставлялся. Думаю, вам, как разработчику будет проще дать ответ, нежели мне снова перебирать компонент. Или, такой глюк только у меня?
                            1. Виктор Долгий 22 августа 2017, 16:07 # 0
                              При выборе картинки, к которой не привязана ни одна модификация, также выставляется первая в списке опция.
                              1. Виктор Долгий 22 августа 2017, 16:31 # 0
                                А если не указывать в форме скрытый инпут options (с любыми вариациями определения, масивом и нет), то и с id=«msProduct» все работает.
                                1. Виктор Долгий 24 августа 2017, 19:54 # 0
                                  Из-за особенностей процессора, нулевая опция (из-за скрытого инпута) ломает логику. Можно легко полечить, например, этим:
                                  unset($options['0']);
                                  $modx->event->returnedValues['options'] = $options;
                                  
                                  , в плагине, на событие msopOnBeforeGetModification.
                            2. Виктор Долгий 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
                          2. Виктор Долгий 22 августа 2017, 14:31 # 0
                            А вы с галереей подружили опции?
                            1. Павел Гвоздь 22 августа 2017, 14:35 # -2
                              Не пробовал даже. В задаче этого не требовалось.
                        2. Виктор Долгий 20 августа 2017, 14:16 # 0
                          Со свежей головой разобрался. Если кто-то еще будет ломать голову, то:

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

                          2. У формы нужно указать (определить) скрытый
                          <input type="hidden" name="options[]" value="[]"> или <input type="hidden" name="options[]" value="">
                          , из-за этого, по сути, ничего и не выходило, проморгал.
                          1. Виктор Долгий 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 звездочку — *.
                            1. Денис 13 ноября 2017, 12:58 # 0
                              Всем привет! Сдеал все по инструкции. Без скрытого input не работает. Добавляю, тогда в корзине появляются такие записи. Может кто то сталкивался с таким?
                            Вы должны авторизоваться, чтобы оставлять комментарии.