Леонид Вакуленко Webasyst


Леонид Вакуленко

  • Код шага confirm берёт все свои числа из того, что доступно в хуке before_confirm. Ничего нового там особо не появляется. В качестве примера посмотрите

    lib/classes/checkout2/shopCheckoutConfirmStep.class.php

    там прям сверху видно, откуда все эти суммы берутся.

    в ответ на Невозможно полностью использовать хуки checkout_*

  • Попробуйте в $params['data']['order'] вписать вашу финальную скидку. Это shopOrder. Что-то вроде

    $order = $params['data']['order'];
    $order['discount'] = 11.11;

    Если сделать это в хуке checkout_before_confirm, то должно быть хорошо. Оно по идее даже в итоговый заказ попадёт.

    Но вообще вы делаете что-то странное. Меня смущает, что в плагины доставки и оплаты уйдут странные числа - предполагаю неправильный расчёт стоимости доставки, например. Ещё меня смущает, что скидка не привязана к item'ам. Будут проблемы с выписыванием чеков. Но хозяин барин, конечно ¯\_(ツ)_/¯

    в ответ на Невозможно полностью использовать хуки checkout_*

  • Хуки `checkout_after_<step>` выполняются после рендера HTML соответствующего шага. Менять там переменные, конечно, уже поздно.

    Есть хук `checkout_render_confirm`. Он вызывается перед тем, как отрендерить шаблон шага `confirm`. В хук передаётся `$params['vars']` - это как раз переменные для шаблона.

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

    Расскажите поподробнее, что за функционал вам нужно реализовать. Я постараюсь предложить удачное место, чтобы вклиниться.

    в ответ на Невозможно полностью использовать хуки checkout_*

  • Леонид Вакуленко Леонид Вакуленко Webasyst 11 октября 2019 17:18 #

    В данном случае поможет вызов коллекции с параметром `'no_plugins_frontend_products' => 1,` но вообще говоря это заведомо плохая идея - использовать коллекцию в хуке, который вызывается в коллекции. Не надо так.

    Ещё один вариант. Блокировать рекурсию в коде вашего же обработчика. Если обработчик видит, что запущен второй раз рекурсивно, он больше не вызывает коллекцию.

    в ответ на Невозможность использовать кастомные коллекций в хуке frontend_products

  • Леонид Вакуленко Леонид Вакуленко Webasyst 11 октября 2019 12:47 #

    У меня вот так получилось этот индекс вставить.

    Подписался на хук

            'checkout_before_region' => 'zzzzMethod',
    

    Сделал вид, что от покупателя в форме пришёл индекс

        public function zzzzMethod(&$params)
        {
            // Если покупатель авторизован и у него в адресе уже есть индекс, то ничего делать не будем
            $customer_saved_addresses = wa()->getUser()->get('address');
            if (!empty($customer_saved_addresses[0]['data']['zip'])) {
                return;
            }
    
            // Волшебством и шаманством определим, откуда пришёл покупатель
            $customer_zip_code = '987654';
    
            $checkout_config = new shopCheckoutConfig(true);
            if ($checkout_config['shipping']['ask_zip']) {
                // Если включен режим "Запрашивать индекс вместе с названием населенного пункта",
                // то впишем индекс в блоке region (если не пришёл настоящий из браузера)
                if (empty($params['data']['input']['region']['zip'])) {
                    $params['data']['input']['region']['zip'] = $customer_zip_code;
                }
            } else {
                // Если режим "Запрашивать индекс вместе с названием населенного пункта" выключен,
                // то впишем индекс, как будто он пришёл в блоке details (если не пришёл настоящий из браузера)
                if (empty($params['data']['input']['details']['shipping_address']['zip'])) {
                    $params['data']['input']['details']['shipping_address']['zip'] = $customer_zip_code;
                }
            }
        }
    

    в ответ на Невозможно установить индекс для шага детальная информацимя в новом оформлении заказа

  • Леонид Вакуленко Леонид Вакуленко Webasyst 25 сентября 2019 10:23 #

    Баг! В этом месте должна быть проверка на существование ключа, что-то вроде

    foreach ($data['items'] as &$item) {
        $item['currency'] = ifempty($item, 'currency', $currency);     //    <<<< this
        if ($currency != $item['currency']) {

    Исправим следующим релизом.

    в ответ на new shopOrder

  • Похоже на правду. То есть происходит буквально вот это:

    shopRounding::roundCurrency(shop_currency('130', 'RUB', 'USD', false), 'USD')

    Но тогда 130 из рублей в $ должно быть 2. А у вас 130? О_о

    в ответ на Cоздание товара. 7 версия.

  • В плохом случае долларовой витрины при включённом округлении рублей: что выведет такая штука?

    wa_dump(
        $r['currency'],
        $product[$type_price],
        $product->currency,
        (new shopProduct($product->getId(), true))[$type_price],
        (new shopProduct($product->getId(), true))->currency // PHP5.4+
    );
    

    Это я хочу сравнить имеющийся $product с чистым и свежим объектом, чтобы удостовериться, что никто туда ничего не присваивал.

    в ответ на Cоздание товара. 7 версия.

  • Ничего не надо плагину знать про округление. А надо только не путаться в валютах. Что тоже может быть не просто :)

    Магазин всегда придерживается правил:

    1) product.price, .compare_price, .min_price, .max_price - в основной валюте магазина.

    2) sku.price, .purchase_price, .compare_price - в валюте товара (product.currency).

    3) sku.primary_price - в основной валюте магазина.

    Если происходит чёрная магия по переводу из валюты в валюту, округление и т.п., то product.currency и значения цен всегда меняются только вместе. Плагинам должно быть всё равно, произошло округление или нет: с точки зрения плагина ему просто пришёл товар в одной валюте или в другой; с одними ценами или с другими. И предоставляя товар на выход, плагин тоже должен выполнять эти условия. Тогда и другие плагины, и темы дизайна, которые ожидают выполнения этих условий, не сломаются.

    Если ваш $product[$type_price] - это product.price или product.compare_price, то переводить его нужно не из product.currencу, а из основной валюты магазина: wa('shop')->getConfig()->getCurrency(true).

    в ответ на Cоздание товара. 7 версия.

  • Проверьте, что настройки округления рублей у вас на обеих установках одинаковые.

    Хитрость в том, что $product->currency (вместе со всеми ценами) подменяется на валюту фронтэнда, но только когда для валюты фронтэнда включено округление. Кажется, я даже описывал механизм в дежавю по вашей ссылке :)

    в ответ на Cоздание товара. 7 версия.

  • Я не могу поверить, что приведённый кусок кода на одних и тех же входных данных выдаёт разный результат в SS6+старый фреймворк и SS7+новый фреймворк. По крайней мере у меня воспроизвести не получилось.

    Предполагаю, что параметры функции shop_currency() различаются. Скорее всего, одном случае $product->currency возвращает одно, а в другом другое. И смотреть надо туда, откуда берётся $product и в какой момент происходит расхождение.

    в ответ на Cоздание товара. 7 версия.

  • Если я правильно подставил икс и игрек в уравнение, то происходит вот это:

    shopRounding::roundCurrency(
        shop_currency('130.000', 'USD', 'RUB', false),
        'RUB'
    )

    Мы переводим $130 в рубли (по курсу 65) и округляем по правилам рублей. Должно получиться 8450. На моей машине одинаково в последней версии фреймворка и в 1.5.6...

    в ответ на Cоздание товара. 7 версия.

  • Где какая конвертация не работает-то? Мне никто не ответил на самый главный вопрос))

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

    в ответ на Cоздание товара. 7 версия.

  • > без этого метод работал безумно долго

    Кстати! Если у вас массовое обновление цен и остатков, целесообразно посмотреть в сторону shopProductMassUpdate вместо shopProduct. Массовое сохранение должно работать гораздо шустрее. Тыщи мильёнов строк за раз, конечно, не осилит, но пачками по 50 товаров вполне справится.

    в ответ на Cоздание товара. 7 версия.

  • Каким способом происходит редактирование цен артикулов, что цены товара не меняются?

    В 7.0.2 был баг в shopProductMassUpdate, что сохранение цены артикула не меняло цены товара. Воспроизводилось при редактировании в списке в бекенде или через мобильное приложение. Но это должно было исправиться в 7.0.3.

    Через shopProduct цены всегда сохранялись корректно, насколько я знаю.

    в ответ на Cоздание товара. 7 версия.

  • Вижу баг в классе shopProduct. Если не передать артикул, то новый товар сохраняется без артикулов. Так быть не должно, должен создаваться пустой артикул по умолчанию. Ну и, ессно, никаких нотисов быть не должно. Это я исправлю в очередном патче магазина.

    Пока могу предложить workaround.

    $prd = new shopProduct ();
    $prd->save ($data + array(
        'skus' => array(
            -1 => array('name' => ''), // или передать тут честные параметры SKU
        ),
    ));

    в ответ на Cоздание товара. 7 версия.

  • > Можно ли как-то оптимизировать добавление?

    Ух ты. 800 тысяч это круто!

    Если это разовая операция, я бы попробовал вот что. Удалить все индексы из таблиц shop_product* до импорта, провести импорт, а по окончании добавить индексы обратно. Индексы ускоряют поиск по таблицам, но замедляют вставку строк.

    в ответ на Импорт большого количества товара

  • > в перегенерации, видимо стоит использовать другое условие

    Если удастся обойтись без дополнительных запросов в БД, то, пожалуй, стоит. Надо посмотреть.

    в ответ на Баг или не баг

  • Попробовал, как вы сказали.

    1. Изначальное состояние: "Использовать исходные имена файлов" выключено. В БД колонка shop_product.image_filename для всех товаров пустая.
    2. Включил "Использовать исходные имена файлов". Выполнил "Удалить и заново создать эскизы изображений всех товаров". В БД колонка shop_product.image_filename стала содержать названия картинок.
    3. Выключил "Использовать исходные имена файлов". Выполнил "Удалить и заново создать эскизы изображений всех товаров". Колонка shop_product.image_filename, как и ожидалось, снова стала пустая.

    Вроде всё ок.

    Посмотрел логику перегенерации. Там идёт цикл по всем картинкам. Если видим, что $image['sort'] ноль (то есть это главная картинка товара), то обновляем значение image_filename соответствующего товара. Моё предположение, что в БД сломано условие, что product_image.sort==0 всегда соответствует главной картинке товара. Если в вашей базе это имеет место массово, большое подозрение на импорт или другой плагин массовой работы с товарами. Нужно больше информации. Есть мысли, на какой плагин смотреть?

    в ответ на Баг или не баг

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

    А нужно это потому что есть темы дизайна, которые работают в режиме одностраничного приложения. Лэйаут загружается один раз, а весь контент грузится XHR'ами. Вся обвязка вокруг содержимого $content им не нужна. А не присылается обвязка не потому что $content сложно распарсить в JS'е, а потому что её дорого каждый раз генерить на сервере. Мало ли какие блоки каких приложений там по менюшкам и сайдбарам распиханы.

    Во время лэйзи лоадинга, кстати, лэйаут тоже не грузится.

    в ответ на AJAX is deprecated?

  • Можно обмануть waRequest::isXMLHttpRequest(), если попросить jQuery не посылать заголовок X-Requested-With. Ничего менее костыльного придумать не могу. Но это лучше, чем скрытые спаны :)

    в ответ на AJAX is deprecated?

  • Фреймворк знает про URLы только когда запускается в контексте веб-сервера. В CLI веб-сервера нет. Можно вообще создать установку без веб-морды, только CLI.

    Но варианты есть.

    // Можно получить URL админки, используя сохранённый URL из настроек инсталлера.
    // Если юзер сам дурак и вписал туда какую-нибудь чушь, то это сломается.
    $root_url = trim(wa()->getSetting('url', '', 'webasyst'), '/').'/';
    $backend_url = $root_url.wa()->getConfig()->getBackendUrl().'/';
    
    // Можно взять URL из роутинга.
    // Если у установки нет фронтенда, это сломается.
    $domains = wa()->getRouting()->getDomains();
    $root_url = 'http://'.trim(reset($domains), '/').'/';
    $backend_url = $root_url.wa()->getConfig()->getBackendUrl().'/';
    
    // Можно получить URL админки, куда последний раз заходил конкретный юзер бекенда.
    // Удобно при отправке писем, когда известен контакт юзера.
    // Если никогда не заходил, это вернёт пустоту.
    $c = new waContact(100500);
    $var = $c->getSettings('webasyst', 'backend_url');

    в ответ на Как получить адрес хоста при запуске через крон

  • > суть полей [unconverted_*]

    В некоторых случаях unconverted_price не совпадает с price. Это глубочайшее тёмное шаманство, связанное с историческими сложностями введения округления.

    Чтобы показать цену в валюте фронтенда, тема дизайна конвертит её из основной валюты с помощью shop_currency(). Так всегда было исторически. Чтобы ввести округление без изменения кода тем, пришлось для каждого товара перевести цену из ОВ в ВФ, округлить, а потом перевести обратно из ВФ в ОВ. Так что при применении округления price перестаёт совпадать с unconverted_.

    в ответ на Цена товара в валюте витрины из бекэнда

  • Конвертация валюты, которую производит коллекция, нужна не для того, чтобы все цены перевести в валюту фронтенда. Она нужна для другого. Она модифицирует поля товара, чтобы сделать вид, что валюта товара изначально совпадала с валютой фронтенда. Эта операция не трогает цены, которые изначально по разным причинам должны храниться в валюте по умолчанию.

    в ответ на Цена товара в валюте витрины из бекэнда

  • > цифры одинаковые, а валюты разные

    Эти поля всегда в основной валюте магазина и не зависят от product.currency. До конвертации и после конвертации. Поэтому они и не поменялись. См. мой предыдущий пост. Так и должно быть.


    в ответ на Цена товара в валюте витрины из бекэнда

  • Не должно быть проблем.

    Цены товаров и артикулов могут храниться либо в валюте товара, либо в основной валюте магазина. Те, которые в основной валюте, коллекция никуда не конвертирует (но может немного подкорректировать, чтобы обеспечить округление). Коллекция конвертирует только цены, которые изначально указаны в валюте товара (product.currency). Но поскольку само поле product.currency тоже подменяется, код плагинов и тем не должен ничего заметить.

    Вот список полей с указанием, в какой валюте они хранятся. Я скопировал из комментов к моделям shopProductModel и shopProductSkusModel.

    product.price                      // primary shop currency
    product.min_price                  // primary shop currency
    product.max_price                  // primary shop currency
    product.compare_price              // primary shop currency
    product.total_sales                // primary shop currency
    sku.primary_price                  // primary shop currency
    
    product.compare_price_selectable   // product.currency
    product.base_price_selectable      // product.currency
    product.purchase_price_selectable  // product.currency
    skus.price                         // product.currency
    skus.purchase_price                // product.currency
    skus.compare_price                 // product.currency

    в ответ на Цена товара в валюте витрины из бекэнда

  • Как-то так?..

    $default_currency = wa('shop')->getConfig()->getCurrency(true);
    $frontend_currency = 'BDT'; // or whatever
    foreach($products as &$p) {
        foreach(array('price', 'min_price', 'max_price', 'compare_price') as $price_key) {
            $price = shop_currency($p[$price_key], $default_currency, $frontend_currency, false);
            $price = shopRounding::roundCurrency($price, $frontend_currency);
            $p[$price_key.'_'.$frontend_currency] = $price;
        }
    }
    unset($p);

    в ответ на Цена товара в валюте витрины из бекэнда

  • Ага. Я понял, что случилось.

    Находясь в бекенде, нельзя (без хаков) получить такую вещь как "текущая выбранная валюта фронтенда". wa('shop')->getConfig()->getCurrency(false) в бекенде всегда возвращает основную валюту. Коллекция думает, что выбрана основная валюта, и никаких округлений не применяет.

    Но это не мешает вам пробежаться по списку товаров и сконвертить цену в любую валюту, какую надо. shop_currency() никуда не делась.

    в ответ на Цена товара в валюте витрины из бекэнда

  • А как плагин доставки, интересно, создаст эту свою таблицу :) в системных плагинах ни db.php, ни install.php не поддерживаются. Опять-таки по причине нескольких копий плагина в системе.

    Если плагин таблицу как-то создал, значит, у него есть изобретённый велосипед, как определить, что это первый запуск плагина такого типа в системе. А если такой велосипед всё равно есть, он как-нибудь и версионность тоже отследит.

    Вопрос про старые файлы интересный. Хорошего решения, похоже, нет.

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

    в ответ на Как выполнить метаобновление плагина доставки

  • Нет такого механизма. Плагины доставки (и оплаты) ничего не знают про то, где хранятся их настройки. Это связано с тем, что бывает много "копий" одного плагина, причём иногда принадлежащих разным приложениям. Даже если бы для системных плагинов работал механизм метаобновлений, обращаться оттуда к shop_plugin_settings в любом случае было бы неправильно.

    Если метаобновление прям очень-очень нужно и без него никак, можно изобрести такой велосипед. Проверять наличие ключика (например, version) в момент получения настроек из адаптера. В зависимости от значения запускать или не запускать своё метаобновление и перезаписывать ключ.

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

    в ответ на Как выполнить метаобновление плагина доставки