|
| 1 | +# Demonstrate ability to customize the shopping cart |
| 2 | + |
| 3 | +## Describe how to implement shopping cart rules. |
| 4 | +DB tables: |
| 5 | +- `salesrule` |
| 6 | + * name, description, is_active, is_rss |
| 7 | + * from_date, to_date, conditions_serialized, is_advanced, sort_order, product_ids |
| 8 | + * actions_serialized, simple_action, discount_amount, discount_qty, discount_step, apply_to_shipping |
| 9 | + * stop_rules_processing, uses_per_customer, times_used |
| 10 | + * coupon_type, use_auto_generation |
| 11 | +- `salesrule_label` - label translation |
| 12 | + * rule_id, store_id, label |
| 13 | +- `salesrule_product_attribute` |
| 14 | + * rule_id, website_id, customer_group_id, attribute_id |
| 15 | + |
| 16 | +Multiple selection: |
| 17 | +- `salesrule_website` |
| 18 | +- `salesrule_customer_group` |
| 19 | + |
| 20 | +- `salesrule_coupon` |
| 21 | + * rule_id, code, usage_limit, usage_per_customer, times_used, expiration_date, is_primary, created_at, type |
| 22 | + |
| 23 | +Usage tracking: |
| 24 | +- `salesrule_coupon_usage` |
| 25 | + * coupon_id, customer_id, times_used |
| 26 | +- `salesrule_customer` |
| 27 | + * rule_id, customer_id, times_used |
| 28 | +- `salesrule_coupon_aggregated`, `salesrule_coupon_aggregated_updated`, `salesrule_coupon_aggregated_order` - reporting stuff |
| 29 | + |
| 30 | +- ui component `sales_rule_form` |
| 31 | +- conditions - html content block - *generic form* Edit\Tab\Conditions |
| 32 | +- conditions fieldset renderer \Magento\Rule\Block\Conditions.render |
| 33 | +- `$element->getRule()->getConditions()->asHtmlRecursive();` |
| 34 | +- Magento\SalesRule\Model\Rule extends Magento\Rule\Model\AbstractModel |
| 35 | + + store labels |
| 36 | + + coupon generation |
| 37 | +- Conditions model instance - \Magento\SalesRule\Model\Rule\Condition\Combine, extends getNewChildSelectOptions: |
| 38 | + + \Magento\SalesRule\Model\Rule\Condition\Product\Found - Product attribute combination |
| 39 | + + \Magento\SalesRule\Model\Rule\Condition\Product\Subselect - Products subselection |
| 40 | + + Conditions combination - self, recursion |
| 41 | + + \Magento\SalesRule\Model\Rule\Condition\Address - Cart Attribute: |
| 42 | + base_subtotal, total_qty, weight, shipping_method, postcode, region, region_id, country_id |
| 43 | + |
| 44 | +what is: |
| 45 | +- `is_advanced` column - not used |
| 46 | +- `product_ids` column - not used |
| 47 | +- `salesrule_product_attribute` table - attributes currently used in conditions and actions of rule. |
| 48 | + |
| 49 | + Used in plugin to \Magento\Quote\Model\Quote\Config::`getProductAttributes` to ensure all needed |
| 50 | + attributes are loaded with quote. |
| 51 | + * quote.load() |
| 52 | + * quoteResource.`_assignProducts` |
| 53 | + * product collection `->addAttributeToSelect($this->_quoteConfig->getProductAttributes())`. |
| 54 | + |
| 55 | + |
| 56 | +## What is the difference between sales rules and catalog rules? |
| 57 | +- Catalog rules update product final price, they are visible in all catalog - category listing, product page. |
| 58 | +- Sales rule is visible only in checkout and can require a coupon. |
| 59 | +- Sales rules can affect shipping price |
| 60 | +- Sales rules can trigger on products combination |
| 61 | + |
| 62 | + |
| 63 | +## How do sales rules affect performance? |
| 64 | +Performance is not affected, all calculation happens in totals collectors. |
| 65 | + |
| 66 | +## What are the limitations of the native sales rules engine? |
| 67 | +Only one coupon can be applied. |
| 68 | + |
| 69 | + |
| 70 | +Implementing rules: |
| 71 | +- Rule model |
| 72 | + * getConditionsInstance - will be used in form as `$model->getConditions()->asHtmlRecursive()`. |
| 73 | + Usually starting conditions are composite - selector [all/any] and sub-conditions selection. |
| 74 | +- Composite conditions model |
| 75 | + * getNewChildSelectOptions - used by Consition\Combine.getNewChildElement |
| 76 | + |
| 77 | + |
| 78 | +## Describe add-to-cart logic in different scenarios. |
| 79 | + |
| 80 | +### Add to cart |
| 81 | +cart model - used only on frontend, marked deprecated. |
| 82 | + |
| 83 | +- checkout/cart/add [product, qty, related_product] |
| 84 | + * cart model.addProduct |
| 85 | + + filter request here, can register own via argument for \Magento\Checkout\Model\Cart\RequestInfoFilter |
| 86 | + + by default filtered out `form_key` and `custom_price` |
| 87 | + * quote.addProduct |
| 88 | + * product type instance.`prepareForCartAdvanced` |
| 89 | + * get same quote item or create new |
| 90 | + * \Magento\Quote\Model\Quote\Item\Processor::prepare |
| 91 | + + quote item.addQty |
| 92 | + + support request.customPrice -> quote item.customPrice |
| 93 | + * event `checkout_cart_product_add_after` |
| 94 | + * checkout session.setLastAddedProductId |
| 95 | + * cart.save |
| 96 | + + quote collect totals |
| 97 | + + event `checkout_cart_save_after` |
| 98 | + + reinitialize state - remove addresses and payments |
| 99 | + * event `checkout_cart_add_product_complete` |
| 100 | + |
| 101 | +- checkout/cart/updatePost |
| 102 | + * cart.suggestItemsQty => \Magento\CatalogInventory\Model\StockStateProvider::suggestQty - qty increments, min/max qty |
| 103 | + * cart.updateItems |
| 104 | + + events `checkout_cart_update_items_before`, `checkout_cart_update_items_after` |
| 105 | + + quote item.setQty -- triggers stock validation |
| 106 | + * cart.save |
| 107 | + |
| 108 | +### *controller checkout/cart/add*: |
| 109 | +- cart.addProduct: |
| 110 | + * quote.addProduct: type.`prepareForCartAdvanced`, event `sales_quote_product_add_after` |
| 111 | + * event `checkout_cart_product_add_after` |
| 112 | +- event `checkout_cart_add_product_complete` -- this event only when normal add to cart |
| 113 | + * withlist observer, marked to be deleted |
| 114 | + |
| 115 | + |
| 116 | +### *add to withlist* `wishlist/index/add`: |
| 117 | +- same buy request |
| 118 | +- cart candidates = type instance.processConfiguration -> `_prepareProduct`. Same as with add to cart. |
| 119 | +- create/update qty withlist item, just like quote item |
| 120 | +- event `wishlist_product_add_after` |
| 121 | +- event `wishlist_add_product` |
| 122 | + |
| 123 | +### *add to cart from wishlist* `withlist/index/cart`: |
| 124 | + |
| 125 | +Can override original buyRequest when adding to cart. |
| 126 | +NO event _checkout_cart_add_product_complete_. |
| 127 | + |
| 128 | +- load withlist item and options |
| 129 | +- merge buy request - saved and current. |
| 130 | +- wishlist item.addToCart |
| 131 | +- `cart.addProduct` with merged buyRequest: |
| 132 | + * events `sales_quote_product_add_after`, `checkout_cart_product_add_after` |
| 133 | +- cart.save |
| 134 | + * event `checkout_cart_save_before` |
| 135 | + * event `checkout_cart_save_after` |
| 136 | +- quote.collectTotals |
| 137 | +- \Magento\Wishlist\Helper\Data::calculate |
| 138 | + * write to customer session |
| 139 | + * event `wishlist_items_renewed` |
| 140 | + |
| 141 | +### *merge quotes* controller `customer/account/loginPost`: |
| 142 | + |
| 143 | +Cart model is not used at all. |
| 144 | + |
| 145 | +- \Magento\Customer\Model\Session::setCustomerDataAsLoggedIn |
| 146 | +- event `customer_login` |
| 147 | +- \Magento\Checkout\Observer\LoadCustomerQuoteObserver |
| 148 | +- checkout session.`loadCustomerQuote`: |
| 149 | + * event `load_customer_quote_before` |
| 150 | + * finds existing customer quote |
| 151 | + * detects that guest quote <> customer quote |
| 152 | + * customer quote.`merge`(guest quote): |
| 153 | + + event `sales_quote_merge_before` |
| 154 | + + for each quote item.`compare` - product id, options same |
| 155 | + + if items same, qty++ |
| 156 | + + if guest item is new, clone quote item, quote.addItem, event `sales_quote_add_item` |
| 157 | + + event `sales_quote_merge_after` |
| 158 | + * *delete guest quote* |
| 159 | + |
| 160 | +### *reorder* controller `sales/order/reorder`: |
| 161 | + |
| 162 | +Like normal add to cart, but NO event _checkout_cart_add_product_complete_. |
| 163 | + |
| 164 | +- load order, ensure can view |
| 165 | +- for each order item, `cart.addOrderItem` - converts order item to quote item: |
| 166 | + * load product |
| 167 | + * get item option buyRequest |
| 168 | + * `cart.addProduct` with old buy request |
| 169 | + + events `sales_quote_product_add_after`, `checkout_cart_product_add_after` |
| 170 | + |
| 171 | + |
| 172 | +## Describe how to customize the process of adding a product to the cart. |
| 173 | +- plugin over product type `prepareForCartAdvanced` |
| 174 | +- event `catalog_product_type_prepare_full_options` - custom options in `_prepareOptions` |
| 175 | +- plugin over \Magento\Quote\Model\Quote\Item\Processor::prepare - qty, custom price |
| 176 | +- event `checkout_cart_product_add_after` |
| 177 | + |
| 178 | +## Which different scenarios should you take into account? |
| 179 | +- add to cart from catalog |
| 180 | +- add to cart from wishlist |
| 181 | +- move all wishlist to cart |
| 182 | +- merge quote when existing customer has quote, then shops as a guest and logs in |
| 183 | +- admin create order |
| 184 | +- admin reorder |
| 185 | +- configure added product - change custom options |
| 186 | + |
| 187 | + |
| 188 | +## Render product types |
| 189 | +checkout cart index: |
| 190 | +- `<update handle="checkout_cart_item_renderers"/>` |
| 191 | +- `<update handle="checkout_item_price_renderers"/>` |
| 192 | + |
| 193 | +- block \Magento\Checkout\Block\Cart\AbstractCart::getItems - quote visible items |
| 194 | + * can limit with *pagination* |
| 195 | + * *custom_items* can override |
| 196 | +- abstract cart.`getItemHtml`(quote item) |
| 197 | +- render via abstract cart.`getItemRenderer` by product type |
| 198 | + * renderer child block `renderer.list` \Magento\Framework\View\Element\RendererList |
| 199 | + * renderer list block.getRenderer(type, template) |
| 200 | + * finds child block by alias = product type |
| 201 | + * \Magento\Checkout\Block\Cart\Item\Renderer |
| 202 | + * |
| 203 | + |
| 204 | +Customizations: |
| 205 | +- provide cart block data arguments `<referenceBlock name="checkout.cart.form">`: |
| 206 | + * `renderer_list_name` - use custom renderer list |
| 207 | + * `overriden_templates` - array by product type |
| 208 | + * `renderer_template` - regardless of product type |
| 209 | +- in `checkout_cart_item_renderers`, `<referenceBlock name='renderer.list'>`, add child blocks by alias = product type. E.g. |
| 210 | + |
| 211 | +```xml |
| 212 | +<referenceBlock name="checkout.cart.item.renderers"> |
| 213 | + <block class="Magento\Checkout\Block\Cart\Item\Renderer" name="checkout.cart.item.renderers.default" as="default" template="Magento_Checkout::cart/item/default.phtml"> |
| 214 | + <block class="Magento\Checkout\Block\Cart\Item\Renderer\Actions" name="checkout.cart.item.renderers.default.actions" as="actions"> |
| 215 | + <block class="Magento\Checkout\Block\Cart\Item\Renderer\Actions\Edit" name="checkout.cart.item.renderers.default.actions.edit" template="Magento_Checkout::cart/item/renderer/actions/edit.phtml"/> |
| 216 | + <block class="Magento\Checkout\Block\Cart\Item\Renderer\Actions\Remove" name="checkout.cart.item.renderers.default.actions.remove" template="Magento_Checkout::cart/item/renderer/actions/remove.phtml"/> |
| 217 | + </block> |
| 218 | + </block> |
| 219 | +</referenceBlock> |
| 220 | +``` |
| 221 | + |
| 222 | +## Describe the available shopping cart operations. |
| 223 | + |
| 224 | +### Configure product in cart - controller `checkout/cart/configure` |
| 225 | +Product view and product configure are rendered via same helper |
| 226 | +\Magento\Catalog\Helper\Product\View.prepareAndRender. |
| 227 | + |
| 228 | +The only difference is params: |
| 229 | +- category_id - can be `false` |
| 230 | +- configure_mode - optional |
| 231 | +- buy_request - optional |
| 232 | +- specify_options - error message if missing options |
| 233 | + |
| 234 | +*Normal product view*: |
| 235 | +- params[category_id] = current |
| 236 | +- params[specify_options] - from product type |
| 237 | +- helper product view.prepareAndRender |
| 238 | + * \Magento\Catalog\Helper\Product::initProduct - load product, check visible/enabled |
| 239 | + + event `catalog_controller_product_init_before` |
| 240 | + + event `catalog_controller_product_init_after` |
| 241 | + * event `catalog_controller_product_view` |
| 242 | + |
| 243 | +*Configure product*: |
| 244 | +- params[`buyRequest`] = quote item.buyRequest |
| 245 | +- helper product view.prepareAndRender |
| 246 | + * product helper.initProduct |
| 247 | + * product helper.`prepareProductOptions` - set default selections from buy request, e.g. selected size=XXL |
| 248 | + + product.processBuyRequest, `product type.processBuyRequest`, `product type.checkProductConfiguration` |
| 249 | + + product.`setPreconfiguredValues` |
| 250 | + * product.setConfigureMode -- used to show all hidden customization options instantly in edit mode |
| 251 | + * event `catalog_controller_product_view` |
| 252 | + |
| 253 | +product.getPreconfiguredValues is later used: |
| 254 | +- default product qty = product.preconfigured values.qty |
| 255 | +- custom option values = product.preconfigured values.option_{$id} |
| 256 | + |
| 257 | +### How do you add a field to the shipping address? |
| 258 | + |
| 259 | +Quote address extends AbstractExtensibleModel, and so should implement `getCustomAttributesCodes()` |
| 260 | +to add custom attributes. Custom attributes should load and save automatically, assuming you added |
| 261 | +a column in migration and registered custom attribute in plugin (see below). |
| 262 | + |
| 263 | +Quote address custom attributes: |
| 264 | +* community \Magento\Quote\Model\Quote\Address\CustomAttributeList - empty, use plugin to add |
| 265 | +* EE \Magento\CustomerCustomAttributes\Model\Quote\Address\CustomAttributeList::getAttributes |
| 266 | + + customer address attributes + customer attributes |
| 267 | + |
| 268 | +Alternatively, you can always register extension attribute and load/save it manually. |
0 commit comments