Mygento_Payture_Helper_Discount::checkSpread()   C
last analyzed

Complexity

Conditions 7
Paths 12

Size

Total Lines 49
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 49
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 27
nc 12
nop 0
1
<?php
2
3
/**
4
 *
5
 *
6
 * @category Mygento
7
 * @package Mygento_Payture
8
 * @copyright 2017 NKS LLC. (https://www.mygento.ru)
9
 */
10
class Mygento_Payture_Helper_Discount extends Mage_Core_Helper_Abstract
11
{
12
    protected $_code = 'payture';
13
14
    const VERSION = '1.0.9';
15
16
    protected $generalHelper = null;
17
18
    protected $_entity           = null;
19
    protected $_taxValue         = null;
20
    protected $_taxAttributeCode = null;
21
    protected $_shippingTaxValue = null;
22
23
    protected $_discountlessSum = 0.00;
24
25
    protected $spreadDiscOnAllUnits = null;
26
27
    const NAME_UNIT_PRICE      = 'disc_hlpr_price';
28
    const NAME_SHIPPING_AMOUNT = 'disc_hlpr_shipping_amount';
29
30
    /** Returns all items of the entity (order|invoice|creditmemo) with properly calculated discount and properly calculated Sum
31
     * @param $entity Mage_Sales_Model_Order | Mage_Sales_Model_Order_Invoice | Mage_Sales_Model_Order_Creditmemo
32
     * @param string $taxValue
33
     * @param string $taxAttributeCode Set it if info about tax is stored in product in certain attr
34
     * @param string $shippingTaxValue
35
     * @return array with calculated items and sum
36
     */
37
    public function getRecalculated($entity, $taxValue = '', $taxAttributeCode = '', $shippingTaxValue = '', $spreadDiscOnAllUnits = false)
38
    {
39
        if (!$entity) {
40
            return;
41
        }
42
43
        $this->_entity              = $entity;
44
        $this->_taxValue            = $taxValue;
45
        $this->_taxAttributeCode    = $taxAttributeCode;
46
        $this->_shippingTaxValue    = $shippingTaxValue;
47
        $this->generalHelper        = Mage::helper($this->_code);
48
        $this->spreadDiscOnAllUnits = $spreadDiscOnAllUnits;
49
50
        $generalHelper = $this->generalHelper;
51
        $generalHelper->addLog("== START == Recalculation of entity prices. Helper Version: " . self::VERSION . ".  Entity class: " . get_class($entity) . ". Entity id: {$entity->getId()}");
52
53
        //If there is no discounts - DO NOTHING
54
        if ($this->checkSpread()) {
55
            $this->applyDiscount();
56
        }
57
58
        $this->generalHelper->addLog("== STOP == Recalculation. Entity class: " . get_class($entity) . ". Entity id: {$entity->getId()}");
59
60
        return $this->buildFinalArray();
61
    }
62
63
    public function applyDiscount()
64
    {
65
        $subTotal       = $this->_entity->getData('subtotal_incl_tax');
66
        $shippingAmount = $this->_entity->getData('shipping_incl_tax');
67
        $grandTotal     = $this->_entity->getData('grand_total');
68
        $grandDiscount  = $grandTotal - $subTotal - $shippingAmount;
69
70
        $percentageSum = 0;
71
72
        $items      = $this->getAllItems();
73
        $itemsSum   = 0.00;
74
        foreach ($items as $item) {
75
            if (!$this->isValidItem($item)) {
76
                continue;
77
            }
78
79
            $price    = $item->getData('price_incl_tax');
80
            $qty      = $item->getQty() ?: $item->getQtyOrdered();
81
            $rowTotal = $item->getData('row_total_incl_tax');
82
83
            //Calculate Percentage. The heart of logic.
84
            $denominator   = ($this->spreadDiscOnAllUnits || ($subTotal == $this->_discountlessSum)) ? $subTotal : ($subTotal - $this->_discountlessSum);
85
            $rowPercentage = $rowTotal / $denominator;
86
87
            if (!$this->spreadDiscOnAllUnits && (floatval($item->getDiscountAmount()) === 0.00)) {
88
                $rowPercentage = 0;
89
            }
90
            $percentageSum += $rowPercentage;
91
92
            $discountPerUnit   = $rowPercentage * $grandDiscount / $qty;
93
            $priceWithDiscount = bcadd($price, $discountPerUnit, 2);
94
95
            //Set Recalculated unit price for the item
96
            $item->setData(self::NAME_UNIT_PRICE, $priceWithDiscount);
97
98
            $itemsSum += round($priceWithDiscount * $qty, 2);
99
        }
100
101
        $this->generalHelper->addLog("Sum of all percentages: {$percentageSum}");
102
103
        //Calculate DIFF!
104
        $itemsSumDiff = round($this->slyFloor($grandTotal - $itemsSum - $shippingAmount, 3), 2);
105
106
        $this->generalHelper->addLog("Items sum: {$itemsSum}. All Discounts: {$grandDiscount} Diff value: {$itemsSumDiff}");
107
        if (bccomp($itemsSumDiff, 0.00, 2) < 0) {
108
            //if: $itemsSumDiff < 0
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
109
            $this->generalHelper->addLog("Notice: Sum of all items is greater than sumWithAllDiscount of entity. ItemsSumDiff: {$itemsSumDiff}");
110
            $itemsSumDiff = 0.0;
111
        }
112
113
        //Set Recalculated Shipping Amount
114
        $this->_entity->setData(self::NAME_SHIPPING_AMOUNT, $this->_entity->getData('shipping_incl_tax') + $itemsSumDiff);
115
    }
116
117
    public function buildFinalArray()
118
    {
119
        $grandTotal = $this->_entity->getData('grand_total');
120
121
        $items      = $this->getAllItems();
122
        $itemsFinal = [];
123
        $itemsSum   = 0.00;
124
        foreach ($items as $item) {
125
            if (!$this->isValidItem($item)) {
126
                continue;
127
            }
128
129
            $taxValue   = $this->_taxAttributeCode ? $this->addTaxValue($this->_taxAttributeCode, $this->_entity, $item) : $this->_taxValue;
130
            $price      = $item->getData(self::NAME_UNIT_PRICE) ?: $item->getData('price_incl_tax');
131
            $entityItem = $this->_buildItem($item, $price, $taxValue);
132
133
            $itemsFinal[$item->getId()] = $entityItem;
134
135
            $itemsSum += $entityItem['sum'];
136
        }
137
138
        $receipt = [
139
            'sum'            => $itemsSum,
140
            'origGrandTotal' => floatval($grandTotal)
141
        ];
142
143
        $shippingAmount = $this->_entity->getData(self::NAME_SHIPPING_AMOUNT) ?: $this->_entity->getData('shipping_incl_tax') + 0.00;
144
145
        $shippingItem = [
146
            'name'     => $this->getShippingName($this->_entity),
147
            'price'    => $shippingAmount,
148
            'quantity' => 1.0,
149
            'sum'      => $shippingAmount,
150
            'tax'      => $this->_shippingTaxValue,
151
        ];
152
153
        $itemsFinal['shipping'] = $shippingItem;
154
        $receipt['items']       = $itemsFinal;
155
156
        if (!$this->_checkReceipt($receipt)) {
157
            $this->generalHelper->addLog("WARNING: Calculation error! Sum of items is not equal to grandTotal!");
158
        }
159
160
        $this->generalHelper->addLog("Final array:");
161
        $this->generalHelper->addLog($receipt);
162
163
        $receiptObj = (object) $receipt;
164
165
        Mage::dispatchEvent('mygento_discount_recalculation_after', array('modulecode' => $this->_code, 'receipt' => $receiptObj));
166
167
        return (array)$receiptObj;
168
    }
169
170
    protected function _buildItem($item, $price, $taxValue = '')
171
    {
172
        $generalHelper = Mage::helper($this->_code);
173
174
        $qty = $item->getQty() ?: $item->getQtyOrdered();
175
        if (!$qty) {
176
            throw new Exception('Divide by zero. Qty of the item is equal to zero! Item: ' . $item->getId());
177
        }
178
179
        $entityItem = [
180
            'price' => round($price, 2),
181
            'name' => $item->getName(),
182
            'quantity' => round($qty, 2),
183
            'sum' => round($price * $qty, 2),
184
            'tax' => $taxValue,
185
        ];
186
187
        $generalHelper->addLog("Item calculation details:");
188
        $generalHelper->addLog("Item id: {$item->getId()}. Orig price: {$price} Item rowTotalInclTax: {$item->getData('row_total_incl_tax')} PriceInclTax of 1 piece: {$price}. Result of calc:");
189
        $generalHelper->addLog($entityItem);
190
191
        return $entityItem;
192
    }
193
194
    public function getShippingName($entity)
195
    {
196
        return $entity->getShippingDescription()
197
            ?: ($entity->getOrder() ? $entity->getOrder()->getShippingDescription() : '');
198
    }
199
200
    /**Validation method. It sums up all items and compares it to grandTotal.
201
     * @param array $receipt
202
     * @return bool True if all items price equal to grandTotal. False - if not.
203
     */
204
    protected function _checkReceipt(array $receipt)
205
    {
206
        $sum = array_reduce($receipt['items'], function ($carry, $item) {
207
            $carry += $item['sum'];
208
            return $carry;
209
        });
210
211
        return bcsub($sum, $receipt['origGrandTotal'], 2) === '0.00';
212
    }
213
214
    public function isValidItem($item)
215
    {
216
        return $item->getData('row_total_incl_tax') !== null;
217
    }
218
219
    public function slyFloor($val, $precision = 2)
220
    {
221
        $factor  = 1.00;
222
        $divider = pow(10, $precision);
223
224
        if ($val < 0) {
225
            $factor = -1.00;
226
        }
227
228
        return (floor(abs($val) * $divider) / $divider) * $factor;
229
    }
230
231
    protected function addTaxValue($taxAttributeCode, $entity, $item)
232
    {
233
        if (!$taxAttributeCode) {
234
            return '';
235
        }
236
        $storeId  = $entity->getStoreId();
237
        $store    = $storeId ? Mage::app()->getStore($storeId) : Mage::app()->getStore();
238
239
        $taxValue = Mage::getResourceModel('catalog/product')->getAttributeRawValue(
240
            $item->getProductId(),
241
            $taxAttributeCode,
242
            $store
243
        );
244
245
        $attributeModel = Mage::getModel('eav/entity_attribute')->loadByCode('catalog_product', $taxAttributeCode);
246
        if ($attributeModel->getData('frontend_input') == 'select') {
247
            $taxValue = $attributeModel->getSource()->getOptionText($taxValue);
248
        }
249
250
        return $taxValue;
251
    }
252
253
    /** It checks do we need to spread dicount on all units and sets flag $this->spreadDiscOnAllUnits
254
     * @return nothing
255
     */
256
    public function checkSpread()
257
    {
258
        $items = $this->getAllItems();
259
260
        $sum                    = 0.00;
261
        $sumDiscountAmount      = 0.00;
262
        $discountless           = false;
263
        $this->_discountlessSum = 0.00;
264
        foreach ($items as $item) {
265
            $rowPrice = $item->getData('row_total_incl_tax') - $item->getData('discount_amount');
266
267
            if (floatval($item->getData('discount_amount')) === 0.00) {
268
                $discountless           = true;
269
                $this->_discountlessSum += $item->getData('row_total_incl_tax');
270
            }
271
272
            $sum               += $rowPrice;
273
            $sumDiscountAmount += $item->getData('discount_amount');
274
        }
275
276
        $grandTotal     = $this->_entity->getData('grand_total');
277
        $shippingAmount = $this->_entity->getData('shipping_incl_tax');
278
279
        //Есть ли общая скидка на Чек. bccomp returns 0 if operands are equal
280
        if (bccomp($grandTotal - $shippingAmount - $sum, 0.00, 2) !== 0) {
281
            $this->generalHelper->addLog("1. Global discount on whole cheque.");
282
283
            $this->spreadDiscOnAllUnits = true;
284
            return true;
285
        }
286
287
        //ок, нет скидки на заказ
288
        // Есть товар без скидок
289
        if ($discountless && ($sumDiscountAmount !== 0.00)) {
290
            $this->generalHelper->addLog("2. Item without discount.");
291
292
            return true;
293
        }
294
295
        // Все товары со скидками
296
        if ($sumDiscountAmount != 0.00) {
297
            $this->generalHelper->addLog("3. All items with discounts.");
298
299
            $this->spreadDiscOnAllUnits = true;
300
            return true;
301
        }
302
303
        return false;
304
    }
305
306
    public function getDecimalsCountAfterDiv($x, $y)
307
    {
308
        $divRes   = strval(round($x / $y, 3));
309
        $decimals = strrchr($divRes, ".") ? strlen(strrchr($divRes, ".")) - 1 : 0;
310
311
        return $decimals;
312
    }
313
314
    public function getAllItems()
315
    {
316
        return $this->_entity->getAllVisibleItems() ? $this->_entity->getAllVisibleItems() : $this->_entity->getAllItems();
317
    }
318
}
319