I've created a custom module on my Magento 1 site. The purpose of this is to offer a buy one get one free deal, however all the products have custom options that cost more which aren't included in the discount. It also takes the cheapest product and removes the cost of that product from the total, rather than just a 50% discount.
The code I have to implement the discount is the following:
public function applyDiscount(Varien_Event_Observer $observer)
{
$quote=$observer->getEvent()->getQuote();
$quoteid=$quote->getId();
//check condition here if need to apply Discount
$numberOfQualifyingItems = 0;
$pricesArray = array();
$itemsDiscounted = array();
$minPrice = array();
foreach ($quote->getAllItems() as $item) {
$_productId = $item->getProduct()->getId();
$_product = Mage::getModel('catalog/product')->load($_productId);
if ($_product->getData("is_included_in_bogof") == 1) {
$itemQty = $item->getQty();
$numberOfQualifyingItems += $itemQty;
$price = $item->getProduct()->getPrice();
$today = date("Y-m-d H:i:s");
if( ($specialFrom = $item->getProduct()->getSpecialFromDate()) !== NULL ){
$specialFrom = date("Y-m-d H:i:s" , strtotime($item->getProduct()->getSpecialFromDate()));
}
if( ($specialTo = $item->getProduct()->getSpecialToDate()) !== NULL ){
$specialTo = date("Y-m-d H:i:s" , strtotime($item->getProduct()->getSpecialToDate()));
} else {
$specialTo = date("Y-m-d H:i:s" , strtotime(date("Y-m-d H:i:s", mktime()) . " + 365 day"));
}
if( $item->getProduct()->getSpecialPrice() && (($today > $specialFrom) && ($today < $specialTo)) ){
$price = $item->getProduct()->getSpecialPrice();
}
$minPrice[$item->getId()] = $item->getProduct()->getFinalPrice();
$pricesArray[$_productId] = array('productId' => $_productId, 'price' => $price, 'qty' => $itemQty);
}
}
$qtyToDiscount = floor($numberOfQualifyingItems / 2);
// sort pricesArray by price ASC
usort($pricesArray, function($a, $b) {
return $a['price'] - $b['price'];
});
asort($minPrice);
$numberOfDiscountsLeft = $qtyToDiscount;
$discountAmount = 0;
// If number of items to be discounted is more than one - calculate discount amount
if( $qtyToDiscount >= 1 ){
foreach ($pricesArray as $_priceItem) {
$discountAmount += min($_priceItem['qty'], $numberOfDiscountsLeft) * $_priceItem['price'];
// Make a note of which items have had a discount to use on the quote later on in the function
$itemsDiscounted[$_priceItem['productId']] = min($_priceItem['qty'], $numberOfDiscountsLeft) * $_priceItem['price'];
$numberOfDiscountsLeft -= min($_priceItem['qty'], $numberOfDiscountsLeft);
if( $numberOfDiscountsLeft === 0 ){
break;
}
}
}
if( $quoteid ){
if( $discountAmount > 0 ){
$total = $quote->getBaseSubtotal();
$quote->setSubtotal(0);
$quote->setBaseSubtotal(0);
$quote->setSubtotalWithDiscount(0);
$quote->setBaseSubtotalWithDiscount(0);
$quote->setGrandTotal(0);
$quote->setBaseGrandTotal(0);
$canAddItems = $quote->isVirtual()? ('billing') : ('shipping');
foreach ($quote->getAllAddresses() as $address) {
$address->setSubtotal(0);
$address->setBaseSubtotal(0);
$address->setGrandTotal(0);
$address->setBaseGrandTotal(0);
$address->collectTotals();
$quote->setSubtotal((float) $quote->getSubtotal() + $address->getSubtotal());
$quote->setBaseSubtotal((float) $quote->getBaseSubtotal() + $address->getBaseSubtotal());
$quote->setSubtotalWithDiscount(
(float) $quote->getSubtotalWithDiscount() + $address->getSubtotalWithDiscount()
);
$quote->setBaseSubtotalWithDiscount(
(float) $quote->getBaseSubtotalWithDiscount() + $address->getBaseSubtotalWithDiscount()
);
$quote->setGrandTotal((float) $quote->getGrandTotal() + $address->getGrandTotal());
$quote->setBaseGrandTotal((float) $quote->getBaseGrandTotal() + $address->getBaseGrandTotal());
$quote ->save();
$quote->setGrandTotal($quote->getBaseSubtotal()-$discountAmount)
->setBaseGrandTotal($quote->getBaseSubtotal()-$discountAmount)
->setSubtotalWithDiscount($quote->getBaseSubtotal()-$discountAmount)
->setBaseSubtotalWithDiscount($quote->getBaseSubtotal()-$discountAmount)
->save();
if( $address->getAddressType() == $canAddItems ){
$address->setSubtotalWithDiscount((float) $address->getSubtotalWithDiscount() - $discountAmount);
$address->setGrandTotal((float) $address->getGrandTotal() - $discountAmount);
$address->setBaseSubtotalWithDiscount((float) $address->getBaseSubtotalWithDiscount() - $discountAmount);
$address->setBaseGrandTotal((float) $address->getBaseGrandTotal() - $discountAmount);
if( $address->getDiscountDescription() ){
$address->setDiscountAmount(-($address->getDiscountAmount()-$discountAmount));
$address->setDiscountDescription($address->getDiscountDescription().', Buy One Get One Free Discount');
$address->setBaseDiscountAmount(-($address->getBaseDiscountAmount()-$discountAmount));
} else {
$address->setDiscountAmount(-($discountAmount));
$address->setDiscountDescription('Buy One Get One Free Discount');
$address->setBaseDiscountAmount(-($discountAmount));
}
$address->save();
}
}
foreach( $quote->getAllItems() as $item ){
//We apply discount amount based on the discount calculated and set on the product at the beginning of function
$_productId = $item->getProduct()->getId();
if( !in_array($_productId, array_keys($itemsDiscounted)) ){
continue;
}
$item->setDiscountAmount( ($item->getBaseDiscountAmount() + $itemsDiscounted[$_productId]) );
$item->setBaseDiscountAmount( $item->getBaseDiscountAmount() )->save();
}
// $quote->collectTotals()->save();
}
}
This function runs on the sales_quote_collect_totals_after
event. The issue is that this event occurs after the tax has been calculated, therefore the tax that is shown is pre-discount, which is incorrect, it has to be post-discount.
Is there either another event I can run this on to calculate the tax correctly? Or a way of manually re-calculating the tax in my function?