commit 515c5b8ee71a6719f81cb6fee4984617df8ab862
Author: Aaron Calero <aaron.calero@openbravo.com>
Date:   Wed May 24 09:46:13 2023 +0200

    Fixed ISSUE-52399: Backport of 49994 and 48273. Fixed payment and discounts rounding issues

diff --git a/web-test/model/business-object/ticket/CalculateTotalsModelHook.test.js b/web-test/model/business-object/ticket/CalculateTotalsModelHook.test.js
index a305f6e00..95af798d4 100644
--- a/web-test/model/business-object/ticket/CalculateTotalsModelHook.test.js
+++ b/web-test/model/business-object/ticket/CalculateTotalsModelHook.test.js
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2020-2021 Openbravo S.L.U.
+ * Copyright (C) 2020-2022 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.
@@ -407,9 +407,11 @@ describe('Apply Discounts and Taxes Model Hook', () => {
   });
 
   test.each`
-    payloadTicket                                                                                                                                                                                                                                  | discountsEngineResult | taxEngineResult                                                                         | resultTicket
-    ${{ id: '0', priceIncludesTax: true, lines: [{ id: '1', grossUnitPrice: 0, baseGrossUnitPrice: 100, qty: -1, quantity: 1, skipApplyPromotions: true, promotions: [{ amt: 100, actualAmt: 100, displayedTotalAmount: 100 }] }], payments: [] }} | ${{ lines: [] }}      | ${{ grossAmount: 0, lines: [{ id: '1', grossUnitAmount: 0, grossUnitPrice: 0 }] }}      | ${{ id: '0', grossAmount: 0, lines: [{ id: '1', grossUnitAmount: 0, grossUnitPrice: 0, promotions: [{ amt: 100, actualAmt: 100, displayedTotalAmount: 100 }] }] }}
-    ${{ id: '0', priceIncludesTax: true, lines: [{ id: '1', grossUnitPrice: 90, baseGrossUnitPrice: 100, qty: -1, quantity: 1, skipApplyPromotions: true, promotions: [{ amt: 10, actualAmt: 10, displayedTotalAmount: 10 }] }], payments: [] }}   | ${{ lines: [] }}      | ${{ grossAmount: -90, lines: [{ id: '1', grossUnitAmount: -90, grossUnitPrice: 90 }] }} | ${{ id: '0', grossAmount: -90, lines: [{ id: '1', grossUnitAmount: -90, grossUnitPrice: 90, promotions: [{ amt: 10, actualAmt: 10, displayedTotalAmount: 10 }] }] }}
+    payloadTicket                                                                                                                                                                                             | discountsEngineResult | taxEngineResult                                                         | resultTicket
+    ${{ id: '0', priceIncludesTax: true, lines: [{ id: '1', grossUnitPrice: 0, baseGrossUnitPrice: 100, qty: -1, quantity: 1, skipApplyPromotions: true, promotions: [{ amt: -100 }] }], payments: [] }}      | ${{ lines: [] }}      | ${{ grossAmount: 0, lines: [{ id: '1', grossUnitAmount: 0 }] }}         | ${{ id: '0', grossAmount: 0, lines: [{ id: '1', qty: -1, grossUnitAmount: 0, promotions: [{ amt: -100 }] }] }}
+    ${{ id: '0', priceIncludesTax: true, lines: [{ id: '1', grossUnitPrice: 90, baseGrossUnitPrice: 100, qty: -1, quantity: 1, skipApplyPromotions: true, promotions: [{ amt: -10 }] }], payments: [] }}      | ${{ lines: [] }}      | ${{ grossAmount: -90, lines: [{ id: '1', grossUnitAmount: -90 }] }}     | ${{ id: '0', grossAmount: -90, lines: [{ id: '1', qty: -1, grossUnitAmount: -90, promotions: [{ amt: -10 }] }] }}
+    ${{ id: '0', priceIncludesTax: true, lines: [{ id: '1', grossUnitPrice: 3.56, baseGrossUnitPrice: 3.95, qty: -2, quantity: 2, skipApplyPromotions: true, promotions: [{ amt: -0.79 }] }], payments: [] }} | ${{ lines: [] }}      | ${{ grossAmount: -7.11, lines: [{ id: '1', grossUnitAmount: -7.11 }] }} | ${{ id: '0', grossAmount: -7.11, lines: [{ id: '1', qty: -2, grossUnitAmount: -7.11, promotions: [{ amt: -0.79 }] }] }}
+    ${{ id: '0', priceIncludesTax: false, lines: [{ id: '1', netUnitPrice: 3.56, baseNetUnitPrice: 3.95, qty: -2, quantity: 2, skipApplyPromotions: true, promotions: [{ amt: -0.79 }] }], payments: [] }}    | ${{ lines: [] }}      | ${{ grossAmount: -7.11, lines: [{ id: '1', netUnitAmount: -7.11 }] }}   | ${{ id: '0', grossAmount: -7.11, lines: [{ id: '1', qty: -2, netUnitAmount: -7.11, promotions: [{ amt: -0.79 }] }] }}
   `(
     'Skip calculate line gross amount if line skipApplyPromotions is true',
     ({
@@ -419,7 +421,12 @@ describe('Apply Discounts and Taxes Model Hook', () => {
       resultTicket
     }) => {
       setDiscountsEngineResultAs(discountsEngineResult);
-      setTaxesEngineResultAs(taxEngineResult);
+      OB.Taxes.Pos.translateTaxes = jest.fn().mockReturnValue(taxEngineResult);
+      OB.Taxes.Pos.applyTaxes = jest.fn().mockImplementation(ticket => {
+        expect(ticket.lines).toMatchObject(resultTicket.lines);
+        return taxEngineResult;
+      });
+
       const result = hook(deepfreeze(payloadTicket), payload());
       expect(result).toMatchObject(resultTicket);
     }
diff --git a/web/org.openbravo.retail.posterminal/app/model/business-object/ticket/TicketUtils.js b/web/org.openbravo.retail.posterminal/app/model/business-object/ticket/TicketUtils.js
index a6b3a767c..e3259b786 100644
--- a/web/org.openbravo.retail.posterminal/app/model/business-object/ticket/TicketUtils.js
+++ b/web/org.openbravo.retail.posterminal/app/model/business-object/ticket/TicketUtils.js
@@ -1,6 +1,6 @@
 /*
  ************************************************************************************
- * Copyright (C) 2020-2021 Openbravo S.L.U.
+ * Copyright (C) 2020-2022 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.
@@ -444,77 +444,59 @@
         return quantity === 0 ? 0 : OB.DEC.div(unitAmount, quantity);
       };
 
+      const isBooked = OB.App.State.Ticket.Utils.isBooked(this.ticket);
+
       // sets line amounts and prices and applies the discount calculation result into the ticket
       this.ticket.lines = this.ticket.lines.map(line => {
         const hasDiscounts = line.promotions && line.promotions.length > 0;
         const {
           baseGrossUnitPrice,
           grossUnitPrice,
-          grossUnitAmount,
           baseNetUnitPrice,
           netUnitPrice,
-          netUnitAmount,
           qty
         } = line;
-        if (line.skipApplyPromotions && hasDiscounts) {
-          const newLine = { ...line };
-
-          if (priceIncludesTax) {
-            newLine.grossUnitAmount =
-              grossUnitAmount || OB.DEC.mul(grossUnitPrice, qty);
-            newLine.netUnitAmount = undefined;
-            // This part is only used for visualization in WebPOS 2.0
-            newLine.grossUnitAmountWithoutTicketDiscounts = calculateUnitAmountWithoutTicketDiscounts(
-              OB.DEC.mul(baseGrossUnitPrice, qty),
-              newLine.promotions
-            );
-            newLine.grossUnitPriceWithoutTicketDiscounts = calculateUnitPriceWithoutTicketDiscounts(
-              newLine.grossUnitAmountWithoutTicketDiscounts,
-              qty
-            );
-          } else {
-            newLine.netUnitAmount =
-              netUnitAmount || OB.DEC.mul(baseNetUnitPrice, qty);
-            newLine.grossUnitAmount = undefined;
-            // This part is only used for visualization in WebPOS 2.0
-            newLine.netUnitAmountWithoutTicketDiscounts = calculateUnitAmountWithoutTicketDiscounts(
-              OB.DEC.mul(baseNetUnitPrice, qty),
-              newLine.promotions
-            );
-            newLine.netUnitPriceWithoutTicketDiscounts = calculateUnitPriceWithoutTicketDiscounts(
-              newLine.netUnitAmountWithoutTicketDiscounts,
-              qty
-            );
-          }
-
-          return newLine;
-        }
-        const isBooked = OB.App.State.Ticket.Utils.isBooked(this.ticket);
-        const discounts = line.skipApplyPromotions
-          ? undefined
-          : discountsResult.lines.find(l => l.id === line.id);
+        const calculatePrice = !(
+          isBooked ||
+          (line.skipApplyPromotions && hasDiscounts)
+        );
+        let discounts;
+        let discountAmt;
         const newLine = {
-          ...line,
-          // eslint-disable-next-line no-nested-ternary
-          promotions: discounts
-            ? discounts.discounts
-            : isBooked
-            ? line.promotions
-            : []
+          ...line
         };
+        if (calculatePrice) {
+          discounts = line.skipApplyPromotions
+            ? undefined
+            : discountsResult.lines.find(l => l.id === line.id);
+          newLine.promotions = discounts ? discounts.discounts : [];
+        } else {
+          newLine.promotions = line.promotions || [];
+          discountAmt = newLine.promotions.reduce(
+            (t, p) => OB.DEC.add(t, p.amt),
+            OB.DEC.Zero
+          );
+        }
 
         if (priceIncludesTax) {
           newLine.baseGrossUnitAmount = OB.DEC.mul(baseGrossUnitPrice, qty);
+          if (calculatePrice) {
+            newLine.grossUnitPrice = discounts
+              ? discounts.grossUnitPrice
+              : baseGrossUnitPrice;
+            newLine.grossUnitAmount = discounts
+              ? discounts.grossUnitAmount
+              : newLine.baseGrossUnitAmount;
+          } else {
+            newLine.grossUnitPrice = grossUnitPrice || line.grossListPrice;
+            newLine.grossUnitAmount = OB.DEC.sub(
+              newLine.baseGrossUnitAmount,
+              discountAmt
+            );
+          }
           newLine.baseNetUnitAmount = OB.DEC.Zero;
-          // eslint-disable-next-line no-nested-ternary
-          newLine.grossUnitPrice = discounts
-            ? discounts.grossUnitPrice
-            : isBooked
-            ? grossUnitPrice
-            : baseGrossUnitPrice;
-          newLine.grossUnitAmount = discounts
-            ? discounts.grossUnitAmount
-            : OB.DEC.mul(isBooked ? grossUnitPrice : baseGrossUnitPrice, qty);
+          newLine.netUnitPrice = OB.DEC.Zero;
+          newLine.netUnitAmount = OB.DEC.Zero;
           // This part is only used for visualization in WebPOS 2.0
           newLine.grossUnitAmountWithoutTicketDiscounts = calculateUnitAmountWithoutTicketDiscounts(
             newLine.baseGrossUnitAmount,
@@ -525,17 +507,24 @@
             qty
           );
         } else {
-          newLine.baseGrossUnitAmount = OB.DEC.Zero;
           newLine.baseNetUnitAmount = OB.DEC.mul(baseNetUnitPrice, qty);
-          // eslint-disable-next-line no-nested-ternary
-          newLine.netUnitPrice = discounts
-            ? discounts.netUnitPrice
-            : isBooked
-            ? netUnitPrice
-            : baseNetUnitPrice;
-          newLine.netUnitAmount = discounts
-            ? discounts.netUnitAmount
-            : OB.DEC.mul(isBooked ? netUnitPrice : baseNetUnitPrice, qty);
+          if (calculatePrice) {
+            newLine.netUnitPrice = discounts
+              ? discounts.netUnitPrice
+              : baseNetUnitPrice;
+            newLine.netUnitAmount = discounts
+              ? discounts.netUnitAmount
+              : newLine.baseNetUnitAmount;
+          } else {
+            newLine.netUnitPrice = netUnitPrice;
+            newLine.netUnitAmount = OB.DEC.sub(
+              newLine.baseNetUnitAmount,
+              discountAmt
+            );
+          }
+          newLine.baseGrossUnitAmount = OB.DEC.Zero;
+          newLine.grossUnitPrice = OB.DEC.Zero;
+          newLine.grossUnitAmount = OB.DEC.Zero;
           // This part is only used for visualization in WebPOS 2.0
           newLine.netUnitAmountWithoutTicketDiscounts = calculateUnitAmountWithoutTicketDiscounts(
             newLine.baseNetUnitAmount,
@@ -546,13 +535,13 @@
             qty
           );
         }
+
         return newLine;
       });
 
       let ticketTaxRules;
       // Applied Taxes should be used to calculate if ticket is booked
-      const isTicketBooked = OB.App.State.Ticket.Utils.isBooked(this.ticket);
-      if (isTicketBooked) {
+      if (isBooked) {
         ticketTaxRules = this.ticket.receiptTaxes.map(receiptTax =>
           taxRules.find(taxRule => taxRule.id === receiptTax.taxid)
         );
@@ -562,7 +551,7 @@
       try {
         taxesResult = OB.Taxes.Pos.applyTaxes(
           this.ticket,
-          isTicketBooked ? ticketTaxRules : taxRules
+          isBooked ? ticketTaxRules : taxRules
         );
       } catch (error) {
         if (error instanceof OB.App.Class.TaxEngineError) {
