# HG changeset patch
Related to issue 34856 : Refactored duplicate code for fetching discount

* Added New Calculation if Same Payment Method is present in Multiple Discount

diff -r e5321e14545b -r 34f5a6c33f25 web/org.openbravo.retail.discounts.bypaymentmethod/js/paymentmethod-discount.js
--- a/web/org.openbravo.retail.discounts.bypaymentmethod/js/paymentmethod-discount.js	Tue Apr 04 17:36:59 2017 +0200
+++ b/web/org.openbravo.retail.discounts.bypaymentmethod/js/paymentmethod-discount.js	Tue May 09 12:46:38 2017 +0530
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2012-2016 Openbravo S.L.U.
+ * Copyright (C) 2012-2017 Openbravo S.L.U.
  * Licensed under the Openbravo Commercial License version 1.0
  * You may obtain a copy of the License at http://www.openbravo.com/legal/obcl.html
  * or in the legal folder of this module distribution.
@@ -9,229 +9,291 @@
 
 /*global OB, Backbone, enyo, _ */
 OB.DISXPM = OB.DISXPM || {};
+OB.DISXPM.discountType = '3E48978BE0844186A5DCC433F823DA56';
+
+OB.Model.Discounts.discountRules[OB.DISXPM.discountType] = {
+  async: false,
+  implementation: function (discountRule, receipt, line) {}
+};
+
+OB.DISXPM.applyDiscount = function (receipt) {
+  if (!receipt.get('multiOrdersList') && !receipt.get('belongsToMultiOrder')) {
+    return true;
+  }
+  return false;
+};
+
 OB.DISXPM.setDiscountInfo = function (receipt, selectedPayment) {
   function resetInfo() {
     receipt.trigger('extrainfo', '');
     receipt.set('exactpayment', {});
   }
-  if (!receipt.get('multiOrdersList') && !receipt.get('belongsToMultiOrder') && receipt.getTotal() > 0 && selectedPayment) {
-    OB.Dal.find(OB.Model.OBDISX_offer_paymentmethod, {
-      type: '3E48978BE0844186A5DCC433F823DA56',
-      payment: selectedPayment.paymentMethod.paymentMethod,
-      _orderByClause: 'discount asc',
-      _limit: 1
-    }, function (discount) {
-      if (discount && discount.models && discount.length > 0) {
-        OB.Dal.find(OB.Model.Discount, {
-          id: discount.at(0).get('offer')
-        }, function (discountRules) {
-          if (discountRules && discountRules.models && discountRules.models.length === 1) {
-            var exactpayment = {},
-                pendingAmt = receipt.getPending(),
-                discountedTotal = OB.DEC.sub(pendingAmt, OB.DEC.mul(pendingAmt, (discount.at(0).get('discount') / 100), 6), 6);
-            if (discountedTotal > 0) {
-              receipt.trigger('extrainfo', OB.I18N.getLabel('OBDISX_WithPaymentMethodDiscount', [OB.I18N.formatCurrencyWithSymbol(OB.DEC.mul(discountedTotal, selectedPayment.mulrate), selectedPayment.symbol, selectedPayment.currencySymbolAtTheRight), discount.at(0).get('discount'), selectedPayment.payment._identifier]));
-              exactpayment[selectedPayment.payment.searchKey] = discountedTotal;
-              receipt.set('exactpayment', exactpayment);
-            } else {
-              resetInfo();
-            }
-          } else {
-            resetInfo();
-          }
-        });
+  if (selectedPayment === undefined && receipt.get('selectedPayment')) {
+    selectedPayment = OB.MobileApp.model.paymentnames[receipt.get('selectedPayment')];
+  }
+  if (!OB.DISXPM.applyDiscount(receipt) || selectedPayment === undefined || receipt.getTotal() < 0) {
+    resetInfo();
+    return false;
+  }
+  OB.DISXPM.getDiscountRuleByPaymentMethod([selectedPayment.paymentMethod.paymentMethod], function (discountPayments) {
+    if (discountPayments && discountPayments.length === 1) {
+      var exactpayment = {},
+          pendingAmt = receipt.getPending(),
+          discountedTotal = OB.DEC.sub(pendingAmt, OB.DEC.div(OB.DEC.mul(pendingAmt, discountPayments[0].discount), 100));
+      if (discountedTotal > 0) {
+        receipt.trigger('extrainfo', OB.I18N.getLabel('OBDISX_WithPaymentMethodDiscount', [OB.I18N.formatCurrencyWithSymbol(OB.DEC.mul(discountedTotal, selectedPayment.mulrate), selectedPayment.symbol, selectedPayment.currencySymbolAtTheRight), discountPayments[0].discount, selectedPayment.payment._identifier]));
+        exactpayment[selectedPayment.payment.searchKey] = discountedTotal;
+        receipt.set('exactpayment', exactpayment);
       } else {
         resetInfo();
       }
-    });
-  } else {
-    resetInfo();
-  }
-};
-OB.DISXPM.calculateDiscount = function (discountRule, receipt, paymentAmount) {
-  var line, x, y, grossTotal, totalDiscount, grossAmtToDiscount, otherPromotionsAmt, lineDiscount;
-  //Remove discount if already applied
-  grossTotal = receipt.getGross();
-  for (x = 0; x < receipt.get('lines').models.length; x++) {
-    line = receipt.get('lines').models[x];
-    if (line.get('promotions')) {
-      for (y = 0; y < line.get('promotions').length; y++) {
-        if (line.get('promotions')[y].amt && line.get('promotions')[y].ruleId === discountRule.get('id')) {
-          grossTotal += Number(line.get('promotions')[y].amt);
-        }
-      }
-    }
-    if (line.get('gross') < 0) {
-      grossTotal += Math.abs(line.get('gross'));
-    }
-    receipt.removePromotion(line, discountRule);
-  }
-  // Calculate Total discount
-  grossAmtToDiscount = (paymentAmount > grossTotal) ? grossTotal : OB.DEC.div(paymentAmount, (1 - (discountRule.get('obdisxPercentage') / 100)));
-  totalDiscount = OB.DEC.mul(grossAmtToDiscount, (discountRule.get('obdisxPercentage') / 100), 6);
-  // Add line discount
-  for (x = 0; x < receipt.get('lines').models.length; x++) {
-    line = receipt.get('lines').models[x];
-    if (line.get('gross') < 0) {
-      continue;
-    }
-    otherPromotionsAmt = 0;
-    if (line.get('promotions')) {
-      for (y = 0; y < line.get('promotions').length; y++) {
-        if (line.get('promotions')[y].amt) {
-          otherPromotionsAmt += Number(line.get('promotions')[y].amt);
-        }
-      }
-    }
-    // Calculate line discount
-    lineDiscount = OB.DEC.div(OB.DEC.mul(OB.DEC.sub(line.getGross(), otherPromotionsAmt), totalDiscount), grossTotal, 6);
-    if (lineDiscount > 0) {
-      var promotions = line.get('promotions') || [];
-      promotions.push({
-        identifier: discountRule.get('_identifier') || discountRule.get('name'),
-        name: discountRule.get('printName') || discountRule.get('name'),
-        ruleId: discountRule.id || discountRule.get('ruleId'),
-        discountType: discountRule.get('discountType'),
-        priority: discountRule.get('priority'),
-        amt: lineDiscount,
-        obdiscQtyoffer: discountRule.get('qtyOffer') ? OB.DEC.toNumber(discountRule.get('qtyOffer')) : line.get('qty'),
-        applyNext: discountRule.get('applyNext'),
-        chunks: undefined,
-        hidden: false,
-        preserve: false,
-        displayedTotalAmount: lineDiscount,
-        _idx: discountRule.get('_idx')
-      });
-      line.set('promotions', promotions);
-    }
-  }
-  receipt.calculateGross();
-};
-OB.DISXPM.getPaymentAmountByDiscountRule = function (discountRule, receipt, callback) {
-  var paymentAmount = 0;
-  OB.Dal.find(OB.Model.OBDISX_offer_paymentmethod, {
-    type: '3E48978BE0844186A5DCC433F823DA56',
-    offer: discountRule.get('id'),
-    _orderByClause: 'discount asc'
-  }, function (discounts) {
-    if (discounts && discounts.models && discounts.models.length >= 0) {
-      _.each(discounts.models, function (discount) {
-        _.each(receipt.get('payments').models, function (payment) {
-          if (OB.MobileApp.model.paymentnames[payment.get('kind')].paymentMethod.paymentMethod === discount.get('payment')) {
-            paymentAmount += Number(payment.get('origAmount'));
-          }
-        }, this);
-      }, this);
-      callback(paymentAmount);
     } else {
-      callback(paymentAmount);
+      resetInfo();
     }
   });
 };
-// Payment Method Discount
-OB.Model.Discounts.discountRules['3E48978BE0844186A5DCC433F823DA56'] = {
-  async: false,
-  implementation: function (discountRule, receipt, line) {}
+
+OB.DISXPM.removeDiscount = function (receipt, discountRule) {
+  _.each(receipt.get('lines').models, function (line) {
+    receipt.removePromotion(line, discountRule);
+  }, this);
 };
 
-OB.MobileApp.model.hookManager.registerHook('OBPOS_preAddPayment', function (args, callbacks) {
-  var receipt = args.receipt,
-      paymentToAdd = args.paymentToAdd,
-      selectedPayment = OB.MobileApp.model.paymentnames[paymentToAdd.get('kind')];
+OB.DISXPM.calculateDiscount = function (receipt, callback) {
+  var x, paymentList = [],
+      receiptLines, finalCallback, discountCalculation, receiptGrossAmt, paymentAmount, receiptAccumPaymentAmt;
 
-  function callback() {
-    OB.MobileApp.model.hookManager.callbackExecutor(args, callbacks);
-  }
-  if (!receipt.get('multiOrdersList') && !receipt.get('belongsToMultiOrder') && receipt.getTotal() > 0 && paymentToAdd.get('amount') >= 0) {
-    OB.Dal.find(OB.Model.OBDISX_offer_paymentmethod, {
-      type: '3E48978BE0844186A5DCC433F823DA56',
-      payment: selectedPayment.paymentMethod.paymentMethod,
-      _orderByClause: 'discount asc',
-      _limit: 1
-    }, function (discount) {
-      if (discount && discount.models && discount.models.length === 1) {
-        OB.Dal.find(OB.Model.Discount, {
-          id: discount.at(0).get('offer')
-        }, function (discountRules) {
-          if (discountRules && discountRules.models && discountRules.models.length === 1) {
-            // Get Payment Amount By Discount Rule
-            OB.DISXPM.getPaymentAmountByDiscountRule(discountRules.at(0), receipt, function (paymentAmount) {
-              // Get Current Payment based on Pending Amount
-              paymentToAdd = OB.DEC.mul(paymentToAdd.get('amount'), selectedPayment.rate, 6);
-              if (paymentToAdd < receipt.getPending()) {
-                paymentAmount += paymentToAdd;
-              } else {
-                paymentAmount += OB.DEC.sub(receipt.getPending(), OB.DEC.mul(receipt.getPending(), (discountRules.at(0).get('obdisxPercentage') / 100), 6), 6);
-              }
-              OB.DISXPM.calculateDiscount(discountRules.at(0), receipt, paymentAmount);
-              callback();
-            });
-          } else {
-            callback();
-          }
-        });
-      } else {
+  finalCallback = function () {
+    receipt.calculateReceipt(function () {
+      receipt.unset('skipApplyPromotions');
+      if (callback) {
         callback();
       }
     });
+  };
+
+  receipt.set('skipApplyPromotions', true);
+  receiptLines = _.filter(receipt.get('lines').models, function (line) {
+    return line.getQty() >= 0;
+  });
+  receiptGrossAmt = receipt.getGross();
+
+  if (receipt.get('payments').length > 0) {
+    _.each(receipt.get('payments').models, function (payment) {
+      paymentList.push(OB.MobileApp.model.paymentnames[payment.get('kind')].paymentMethod.paymentMethod);
+    });
+    // Get Unique Discounts based on Payment method
+    OB.DISXPM.getDiscountRuleByPaymentMethod(paymentList, function (discountPayments) {
+      if (discountPayments && discountPayments.length > 0) {
+        discountCalculation = _.after(discountPayments.length, function () {
+          finalCallback();
+        });
+
+        // Remove Promotions
+        _.each(discountPayments, function (discountPayment) {
+          _.each(receipt.get('lines').models, function (line, index) {
+            _.each(line.get('promotions'), function (promotion, promotionIndex) {
+              if (promotion.ruleId === discountPayment.discountRuleId) {
+                receiptGrossAmt = OB.DEC.add(receiptGrossAmt, Number(promotion.amt));
+                line.get('promotions').splice(promotionIndex, 1);
+              }
+            });
+          });
+        });
+        receiptAccumPaymentAmt = receiptGrossAmt;
+
+        _.each(discountPayments, function (discountPayment) {
+          paymentAmount = _.reduce(receipt.get('payments').models, function (memo, payment) {
+            if (discountPayment.paymentMethod.indexOf(OB.MobileApp.model.paymentnames[payment.get('kind')].paymentMethod.paymentMethod) > -1) {
+              return memo + payment.get('origAmount');
+            } else {
+              return memo;
+            }
+          }, 0);
+
+          OB.Dal.findUsingCache('DiscountPaymentMethod', OB.Model.Discount, {
+            id: discountPayment.discountRuleId,
+            _limit: 1
+          }, function (discountRules) {
+            if (discountRules && discountRules.length === 1) {
+              var lineTotalDiscount, lineGrossAmt, lineDiscount, lineAccumDiscount, discountRule;
+
+              discountRule = discountRules.at(0);
+
+              // If payment amount exceeds total pending, discount is calculated for pending amount
+              var paymentAmtToCalculate = paymentAmount;
+              var maxPaymentAmtToCalculate = OB.DEC.sub(receiptAccumPaymentAmt, OB.DEC.div(OB.DEC.mul(receiptAccumPaymentAmt, discountPayment.discount), 100));
+              if (paymentAmount > maxPaymentAmtToCalculate) {
+                paymentAmtToCalculate = maxPaymentAmtToCalculate;
+              }
+
+              // Calculate Discount
+              lineTotalDiscount = OB.DEC.div(paymentAmtToCalculate, OB.DEC.sub(1, OB.DEC.div(discountPayment.discount, 100)));
+              lineTotalDiscount = (lineTotalDiscount > receiptAccumPaymentAmt) ? receiptAccumPaymentAmt : lineTotalDiscount;
+              lineTotalDiscount = OB.DEC.div(OB.DEC.mul(lineTotalDiscount, discountPayment.discount), 100);
+
+              if (paymentAmount > maxPaymentAmtToCalculate) {
+                receiptAccumPaymentAmt = OB.DEC.sub(receiptAccumPaymentAmt, receiptGrossAmt);
+              } else {
+                receiptAccumPaymentAmt = OB.DEC.sub(receiptAccumPaymentAmt, OB.DEC.add(paymentAmount, lineTotalDiscount));
+              }
+
+              lineAccumDiscount = 0;
+              _.each(receiptLines, function (line, lineIndex) {
+                lineGrossAmt = line.getGross();
+
+                _.each(line.get('promotions'), function (promotion) {
+                  if (promotion.amt && promotion.isPaymentMethodDiscount) {
+                    lineGrossAmt = OB.DEC.sub(lineGrossAmt, Number(promotion.amt));
+                  }
+                });
+
+                if (lineIndex !== receiptLines.length - 1) {
+                  lineDiscount = OB.DEC.toNumber(OB.DEC.toBigDecimal(lineGrossAmt).multiply(OB.DEC.toBigDecimal(lineTotalDiscount).divide(OB.DEC.toBigDecimal(receiptAccumPaymentAmt), 20, OB.DEC.getRoundingMode())));
+                } else {
+                  lineDiscount = OB.DEC.sub(lineTotalDiscount, lineAccumDiscount);
+                }
+                lineAccumDiscount = OB.DEC.add(lineAccumDiscount, lineDiscount);
+
+                if (lineDiscount > 0) {
+                  var promo = _.find(line.get('promotions'), function (promotion) {
+                    return promotion.ruleId === discountRule.get('id');
+                  });
+                  if (promo) {
+                    promo.amt = OB.DEC.add(promo.amt, lineDiscount);
+                    promo.displayedTotalAmount = promo.amt;
+                    promo.fullAmt = promo.amt;
+                  } else {
+                    receipt.addPromotion(line, discountRule, {
+                      amt: lineDiscount,
+                      _idx: -1
+                    });
+                    _.each(line.get('promotions'), function (promotion) {
+                      if (promotion.ruleId === discountRule.get('id')) {
+                        promotion.isPaymentMethodDiscount = true;
+                      }
+                    });
+                  }
+                }
+              });
+            }
+            discountCalculation();
+          }, function (tx, error) {
+            OB.UTIL.showError("OBDAL error: " + error);
+            discountCalculation();
+          }, {
+            modelsAffectedByCache: ['Discount']
+          });
+        });
+      } else {
+        finalCallback();
+      }
+    });
   } else {
-    callback();
+    finalCallback();
   }
-});
+};
+
+OB.DISXPM.getDiscountRuleByPaymentMethod = function (selectedPayments, callback) {
+  var sql, discPayments;
+  // query will filter payment method based on lower priority and lower discount
+  sql = 'select op.id, op.offer, op.discount, op.payment as payment from obdisx_offer_paymentmethod op join m_offer o on o.m_offer_id = offer where 1 = 1 ';
+  if (!OB.UTIL.isNullOrUndefined(selectedPayments) && _.isArray(selectedPayments) && selectedPayments.length > 0) {
+    sql += "and op.payment in ('" + selectedPayments.join("','") + "') ";
+  }
+  sql += 'order by o.priority is null, o.priority, op.discount, op.offer, op.payment';
+  OB.Dal.queryUsingCache(OB.Model.OBDISX_offer_paymentmethod, sql, [], function (modelsList) {
+    if (modelsList && modelsList.length > 0) {
+      // Remove Duplicate Payment Methods
+      discPayments = _.uniq(modelsList.models, false, function (item) {
+        return item.get('payment');
+      });
+      // Group by discount
+      var discountPayments = [];
+      _.each(discPayments, function (discPayment) {
+        var index = _.pluck(discountPayments, 'discountRuleId').indexOf(discPayment.get('offer'));
+        if (index < 0) {
+          discountPayments.push({
+            discountRuleId: discPayment.get('offer'),
+            discount: discPayment.get('discount'),
+            paymentMethod: [discPayment.get('payment')]
+          });
+        } else {
+          discountPayments[index].paymentMethod.push(discPayment.get('payment'));
+        }
+      });
+      callback(discountPayments);
+    } else {
+      callback(null);
+    }
+  }, function (tx, error) {
+    OB.UTIL.showError("OBDAL error: " + error);
+  }, {
+    modelsAffectedByCache: ['Discount', 'OBDISX_offer_paymentmethod']
+  });
+};
+
 OB.MobileApp.model.hookManager.registerHook('OBPOS_preRemovePayment', function (args, callbacks) {
   var receipt = args.receipt,
       paymentToRemove = args.paymentToRem,
       selectedPayment = OB.MobileApp.model.paymentnames[paymentToRemove.get('kind')];
 
-  function callback() {
+  function callbackHook() {
     OB.MobileApp.model.hookManager.callbackExecutor(args, callbacks);
   }
-  if (!receipt.get('multiOrdersList') && !receipt.get('belongsToMultiOrder') && receipt.getTotal() > 0) {
-    OB.Dal.find(OB.Model.OBDISX_offer_paymentmethod, {
-      type: '3E48978BE0844186A5DCC433F823DA56',
-      payment: selectedPayment.paymentMethod.paymentMethod,
-      _orderByClause: 'discount asc',
-      _limit: 1
-    }, function (discount) {
-      if (discount && discount.models && discount.models.length === 1) {
-        OB.Dal.find(OB.Model.Discount, {
-          id: discount.at(0).get('offer')
-        }, function (discountRules) {
-          if (discountRules && discountRules.models && discountRules.models.length === 1) {
-            // Get Payment Amount By Discount Rule
-            OB.DISXPM.getPaymentAmountByDiscountRule(discountRules.at(0), receipt, function (paymentAmount) {
-              // Get Current Payment based on Removed Payment Amount
-              paymentAmount -= paymentToRemove.get('origAmount');
-              OB.DISXPM.calculateDiscount(discountRules.at(0), receipt, paymentAmount);
-              callback();
-            });
-          } else {
-            callback();
-          }
-        });
-      } else {
-        callback();
-      }
-    });
-  } else {
-    callback();
+  if (!OB.DISXPM.applyDiscount(receipt)) {
+    callbackHook();
+    return false;
   }
+  OB.DISXPM.getDiscountRuleByPaymentMethod([selectedPayment.paymentMethod.paymentMethod], function (discountPayments) {
+    if (discountPayments && discountPayments.length === 1) {
+      OB.Dal.findUsingCache('DiscountPaymentMethod', OB.Model.Discount, {
+        id: discountPayments[0].discountRuleId,
+        _limit: 1
+      }, function (discountRules) {
+        if (discountRules && discountRules.length === 1) {
+          OB.DISXPM.removeDiscount(receipt, discountRules.at(0));
+        }
+        callbackHook();
+      });
+    } else {
+      callbackHook();
+    }
+  });
 });
+
 OB.MobileApp.model.hookManager.registerHook('OBPOS_PaymentSelected', function (args, callbacks) {
   var receipt = args.order,
       selectedPayment = args.paymentSelected;
-  OB.DISXPM.setDiscountInfo(receipt, selectedPayment);
+  if (OB.MobileApp.model.get('lastPaneShown') === 'payment') {
+    OB.DISXPM.setDiscountInfo(receipt, selectedPayment);
+  }
   OB.MobileApp.model.hookManager.callbackExecutor(args, callbacks);
 });
 
 OB.MobileApp.model.hookManager.registerHook('OBPOS_PrePaymentHook', function (args, callbacks) {
-  var receipt = args.context.get('order'),
-      selectedPayment = OB.MobileApp.model.paymentnames[receipt.get('selectedPayment')];
-  receipt.on('change:gross', function () {
-    OB.DISXPM.setDiscountInfo(receipt, OB.MobileApp.model.paymentnames[receipt.get('selectedPayment')]);
+  var receipt = args.context.get('order');
+  // Calculate Discount on first time if payments presents
+  OB.DISXPM.calculateDiscount(receipt, function () {
+    OB.DISXPM.setDiscountInfo(receipt);
   });
-  OB.DISXPM.setDiscountInfo(receipt, selectedPayment);
+
+  // Calculate Discount if any payment changed
+  if (!receipt.get('hasPaymentMethodDiscount') || !OB.DISXPM.applyDiscountInfo) {
+    receipt.on('change:payment', function () {
+      setTimeout(function () {
+        if (OB.MobileApp.model.get('lastPaneShown') === 'payment') {
+          OB.DISXPM.calculateDiscount(receipt, function () {
+            OB.DISXPM.setDiscountInfo(receipt);
+          });
+        }
+      }, 200);
+    });
+    receipt.set('hasPaymentMethodDiscount', true);
+    OB.DISXPM.applyDiscountInfo = true;
+  }
+
   OB.MobileApp.model.hookManager.callbackExecutor(args, callbacks);
 });
 
-OB.Model.Discounts.standardFilter = OB.Model.Discounts.standardFilter + " AND M_OFFER_TYPE_ID NOT IN ('3E48978BE0844186A5DCC433F823DA56') ";
\ No newline at end of file
+OB.Model.Discounts.standardFilter = OB.Model.Discounts.standardFilter + " AND M_OFFER_TYPE_ID NOT IN ('" + OB.DISXPM.discountType + "') ";
\ No newline at end of file
