Работа с ценами в Commerce ведётся с помощью пяти модулей:
Price — реализует поле типа Price.
Product Pricing + Product Pricing UI — позволяют динамически менять цену товаров.
Tax + Tax UI — рассчитывают различные виды налогов.
Поле Price по умолчанию имеют следующие сущности: Product (Price), Line item (Unit price, Total), Order (Order total). Схематично здесь.
Каждое значение поля Price состоит из:
amount — цена в копейках или сумма цен всех компонентов, если они есть.
currency_code — код валюты.
data — массив с компонентами цены (price components).
Вся работа с ценами ведётся в копейках. Для правильного перерасчёта в у.е. и обратно есть функции commerce_currency_amount_to_decimal()
и commerce_currency_decimal_to_amount()
.
Модули и администраторы сайта могут динамически изменять цены товаров и заказов. Каждое изменение цены записывается в так называемый price component (компонент цены) — data['components']
из которых и высчитывается главный amount (итоговая цена):
Каждый price component имеет свой тип (поле name в массиве). По умолчанию в Commerce три типа компонентов цены:
base_price — базовая цена
discount — скидка
fee — наценка
Модули Tax + Tax UI позволяют из админки добавлять свои типы компонентов цены. Другие модули так же могут создавать типы price components с помощью хука hook_commerce_price_component_type_info()
.
Price components добавляются к цене с помощью commerce_price_component_add()
. Изменять цену в обход этой функции не рекомендуется.
Цена товара рассчитывается в момент вывода (см. commerce_product_pricing_commerce_price_field_formatter_prepare_view()
). В базе хранится только цена, указанная при создании товара. Отсюда сложности с сортировкой и фильтром по выводимой цене, а не по реальной.
Цена показываемого товара рассчитывается на основе "виртуального" line item-а. "Виртуального" — потому что созданный line item не сохраняется в базе и у него нет line_item_id
(см. commerce_product_calculate_sell_price()
).
Цена товара в корзине (а точнее цена line item-а) высчитывается при добавлении его в корзину (см. commerce_cart_add_to_cart_form_submit()
). Следует помнить, что цена высчитывается перед сохранением line item-а в базу. В дальнейшем цена line item-ов в корзине перерассчитывается каждые 30 секунд (по умолчанию) и уже на основе "реальных" line item-ов, которые есть в базе и имеют line_item_id
.
Цена корзины рассчитывается после добавления товара (line item-а) в корзину, после сохранения корзины и каждые 30 секунд.
На страницах /cart
и /checkout*
цены перерассчитываются каждое открытие.
Для гурманов — как работает вывод цены:
Вывод цены товара
commerce_product_reference_field_extra_fields()
commerce_product_reference_entity_view()
└ field_view_field('commerce_product', $product, 'имя_поля_цены', ...)
├ commerce_price_field_formatter_prepare_view()
│ └ hook_commerce_price_field_formatter_prepare_view()
│ └ commerce_product_pricing_commerce_price_field_formatter_prepare_view()
│ └ commerce_product_calculate_sell_price($product)
│ ├ commerce_product_line_item_new($product)
│ │ ├ entity_create('commerce_line_item', ...)
│ │ └ commerce_product_line_item_populate($line_item, $product)
│ │ └ commerce_price_component_add(..., 'base_price', ...)
│ ├ drupal_alter('commerce_product_calculate_sell_price_line_item', $line_item)
│ └ rules_invoke_event('commerce_product_calculate_sell_price', $line_item);
│ ├ пользовательский rules + commerce_price_component_add()
│ ├ пользовательский rules + commerce_price_component_add()
│ └ ...
└ commerce_price_field_formatter_view()
└ commerce_currency_format()
или
├ commerce_price_component_total()
├ drupal_alter('commerce_price_formatted_components', $components, $item, $entity);
└ theme('commerce_price_formatted_components', array('components' => $components, 'price' => $item))
Форма добавление товара в корзину
commerce_cart_field_formatter_view()
├ commerce_product_reference_default_product($products)
└ commerce_product_line_item_new($default_product, ...)
├ entity_create('commerce_line_item', ...)
└ commerce_product_line_item_populate($line_item, $product)
└ commerce_price_component_add(..., 'base_price', ...)
commerce_cart_field_attach_view_alter()
└ drupal_get_form('commerce_cart_add_to_cart_form_...', $line_item, ...)
└ commerce_cart_add_to_cart_form($form, &$form_state, $line_item, ...)
├ field_attach_form('commerce_line_item', $form_state['line_item'], $form['line_item_fields'], ...)
└ commerce_product_calculate_sell_price($form_state['default_product'])
├ commerce_product_line_item_new($product)
│ ├ entity_create('commerce_line_item', ...)
│ └ commerce_product_line_item_populate($line_item, $product)
│ └ commerce_price_component_add(..., 'base_price', ...)
├ drupal_alter('commerce_product_calculate_sell_price_line_item', $line_item)
└ rules_invoke_event('commerce_product_calculate_sell_price', $line_item);
├ пользовательский rules + commerce_price_component_add()
├ пользовательский rules + commerce_price_component_add()
└ ...
Добавление товара в корзину
commerce_cart_add_to_cart_form($form, &$form_state, $line_item, ...)
├ field_attach_form('commerce_line_item', $form_state['line_item'], $form['line_item_fields'], ...)
└ commerce_product_calculate_sell_price($form_state['default_product'])
├ commerce_product_line_item_new($product)
│ ├ entity_create('commerce_line_item', ...)
│ └ commerce_product_line_item_populate($line_item, $product)
│ └ commerce_price_component_add(..., 'base_price', ...)
├ drupal_alter('commerce_product_calculate_sell_price_line_item', $line_item)
└ rules_invoke_event('commerce_product_calculate_sell_price', $line_item);
├ пользовательский rules + commerce_price_component_add()
├ пользовательский rules + commerce_price_component_add()
└ ...
commerce_cart_add_to_cart_form_submit()
├ commerce_product_line_item_new($product, ...)
│ ├ entity_create('commerce_line_item', ...)
│ └ commerce_product_line_item_populate($line_item, $product)
│ └ commerce_price_component_add(..., 'base_price', ...)
├ drupal_alter('commerce_product_calculate_sell_price_line_item', $line_item);
├ field_attach_submit('commerce_line_item', $line_item, $form['line_item_fields'], $form_state)
├ rules_invoke_event('commerce_product_calculate_sell_price', $line_item);
│ ├ пользовательский rules + commerce_price_component_add()
│ ├ пользовательский rules + commerce_price_component_add()
│ └ ...
└ commerce_cart_product_add($form_state['values']['uid'], $line_item, ...)
├ commerce_cart_order_new($uid)
├ rules_invoke_all('commerce_cart_product_prepare', $order, $product, ...);
├ commerce_line_item_save($line_item)
├ commerce_order_save($order)
│ └ commerce_order_calculate_total($order)
└ rules_invoke_all('commerce_cart_product_add', $order, $product, $quantity, $line_item);
Обновление цены корзины и её содержимого
commerce_order_load()
└ hook_commerce_order_load()
└ commerce_cart_commerce_order_load($orders)
└ commerce_cart_order_refresh($order)
├ commerce_product_line_item_populate()
├ rules_invoke_event('commerce_product_calculate_sell_price', $cloned_line_item);
│ ├ пользовательский rules + commerce_price_component_add()
│ ├ пользовательский rules + commerce_price_component_add()
│ └ ...
├ hook_commerce_cart_line_item_refresh()
├ commerce_line_item_save()
├ hook_commerce_cart_order_refresh()
└ commerce_order_save()
└ commerce_order_calculate_total($order)
Бросается в глаза, что при выводе цены товара вместе с формой добавления товара в корзину, виртуальных line item-ов создаётся аж три штуки, а цена высчитывается два раза. Помните это, создавая правила для расчёта цены — не забывайте про кеширование и всё такое.
Для дальнейшего изучения будет полезен доклад о скидках и материалы по тегу работа с ценой.
Комментарии
Хорошую работу проделал, спасибо
Xandeadx, скажи, а зачем так углублялся в дебри комерца? Спортивный интерес или реально нужда по проекту?
нужда
Xandeadx, спасибо огромное. Понятно и очень наглядно. В свое время пришлось разбираться с формированием цен в Commerce практически методом тыка - на beta-версиях, почти без документации и с кучей глюков. Такая полезная статья тогда сэкономила бы несколько дней...
Подскажи, каким софтом делал деревья вызовов функции "Вывод цены товара" и все остальные - такие схемы реально помогают лучше понять как это всё работает изнутри...
деревья делал ручками
На одном из буржуйских форумов нашел решение для скидки на конретный товар. Делается просто, например так:
Работает!
Как по вашему, какие подводные камни в таком решении?
https://www.drupal.org/project/commerce_saleprice
цену надо менять с помощью commerce_price_component_add()
Интересно, а можно как-то завести еще один тип цены - себестоимость, чтобы вести складской учет? Или вообще делать "покупки" в минус - оприходование (в том числе с разными ценами закупа для одной позиции в зависимости от поставки), а потом уже реализацию со склада оприходованных товаров (по новой общей цене)? Мне кажется это то, чего не хватает инет-магазинам для полного контроля над торговлей.
заведите второе поле цены
К предыдущему комменту: оприходование - это прям и есть заказ наоборот, только вместо скидок - разные закупочные цены, и товары не уменьшаются, а увеличиваются, также заказчик и заказ становятся поставщиком и поставкой. Может уже есть что-то подобное?
Пожалуйста, подскажите, каким образом можно изменить (программно) порядок вывода Product variation во view Display Products. По умолчанию отображается product variation с min весом, нужно - с минимальной ценой.
@suwsv http://xandeadx.ru/blog/drupal/685
Добавить комментарий