public function BuyXGetY::apply in Commerce Core 8.2
Applies the offer to the given entity.
Parameters
\Drupal\Core\Entity\EntityInterface $entity: The entity.
\Drupal\commerce_promotion\Entity\PromotionInterface $promotion: THe parent promotion.
Overrides PromotionOfferInterface::apply
File
- modules/
promotion/ src/ Plugin/ Commerce/ PromotionOffer/ BuyXGetY.php, line 341
Class
- BuyXGetY
- Provides the "Buy X Get Y" offer for orders.
Namespace
Drupal\commerce_promotion\Plugin\Commerce\PromotionOfferCode
public function apply(EntityInterface $entity, PromotionInterface $promotion) {
$this
->assertEntity($entity);
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $entity;
$order_items = $order
->getItems();
$buy_conditions = $this
->buildConditionGroup($this->configuration['buy_conditions']);
$buy_order_items = $this
->selectOrderItems($order_items, $buy_conditions, 'DESC');
$buy_quantities = array_map(function (OrderItemInterface $order_item) {
return $order_item
->getQuantity();
}, $buy_order_items);
if (array_sum($buy_quantities) < $this->configuration['buy_quantity']) {
return;
}
$get_conditions = $this
->buildConditionGroup($this->configuration['get_conditions']);
if ($this->configuration['get_auto_add'] && ($get_purchasable_entity = $this
->findSinglePurchasableEntity($get_conditions))) {
$order_item = $this
->findOrCreateOrderItem($get_purchasable_entity, $order_items);
$expected_get_quantity = $this
->calculateExpectedGetQuantity($buy_quantities, $order_item);
// If the expected get quantity is non-zero, we need to update the
// quantity of the 'get' order item accordingly.
if (Calculator::compare($expected_get_quantity, '0') !== 0) {
if (Calculator::compare($order_item
->getQuantity(), $expected_get_quantity) === -1) {
$order_item
->setQuantity($expected_get_quantity);
// Ensure that order items which are 'touched' by this promotion can
// not be edited by the customer, either by changing their quantity or
// removing them from the cart.
$order_item
->lock();
}
// Keep track of the quantity that was auto-added to this order item so
// we can subtract it (or remove the order item completely) if the buy
// conditions are no longer satisfied on the next order refresh.
$order_item
->setData("promotion:{$promotion->id()}:auto_add_quantity", $expected_get_quantity);
$time = $order
->getCalculationDate()
->format('U');
$context = new Context($order
->getCustomer(), $order
->getStore(), $time);
$unit_price = $this->chainPriceResolver
->resolve($get_purchasable_entity, $order_item
->getQuantity(), $context);
$order_item
->setUnitPrice($unit_price);
if ($order_item
->isNew()) {
$order_item
->set('order_id', $order
->id());
$order_item
->save();
$order
->addItem($order_item);
$order_items = $order
->getItems();
}
}
}
$get_order_items = $this
->selectOrderItems($order_items, $get_conditions, 'ASC');
$get_quantities = array_map(function (OrderItemInterface $order_item) {
return $order_item
->getQuantity();
}, $get_order_items);
if (empty($get_quantities)) {
return;
}
// It is possible for $buy_quantities and $get_quantities to overlap (have
// the same order item IDs). For example, in a "Buy 3 Get 1" scenario with
// a single T-shirt order item of quantity: 8, there are 6 bought and 2
// discounted products, in this order: 3, 1, 3, 1. To ensure the specified
// results, $buy_quantities must be processed group by group, with any
// overlaps immediately removed from the $get_quantities (and vice-versa).
// Additionally, ensure that any items from $buy_quantities that overlap
// with $get_quantities are processed last, in order to accommodate the case
// when $buy_conditions (or the lack thereof) are satisfied by the other
// (non-overlapping) $buy_quantity items.
foreach ($buy_quantities as $id => $quantity) {
if (isset($get_quantities[$id])) {
unset($buy_quantities[$id]);
$buy_quantities[$id] = $quantity;
}
}
$final_quantities = [];
$i = 0;
while (!empty($buy_quantities)) {
$selected_buy_quantities = $this
->sliceQuantities($buy_quantities, $this->configuration['buy_quantity']);
if (array_sum($selected_buy_quantities) < $this->configuration['buy_quantity']) {
break;
}
$get_quantities = $this
->removeQuantities($get_quantities, $selected_buy_quantities);
$selected_get_quantities = $this
->sliceQuantities($get_quantities, $this->configuration['get_quantity']);
$buy_quantities = $this
->removeQuantities($buy_quantities, $selected_get_quantities);
// Merge the selected get quantities into a final list, to ensure that
// each order item only gets a single adjustment.
$final_quantities = $this
->mergeQuantities($final_quantities, $selected_get_quantities);
// Determine whether the offer reached its limit.
if ($this->configuration['offer_limit'] == ++$i) {
break;
}
}
foreach ($final_quantities as $order_item_id => $quantity) {
$order_item = $get_order_items[$order_item_id];
$adjustment_amount = $this
->buildAdjustmentAmount($order_item, $quantity);
$order_item
->addAdjustment(new Adjustment([
'type' => 'promotion',
'label' => $promotion
->getDisplayName() ?: $this
->t('Discount'),
'amount' => $adjustment_amount
->multiply('-1'),
'source_id' => $promotion
->id(),
]));
}
}