From 09b2dab543abd445f0a890d2700cead943facfe0 Mon Sep 17 00:00:00 2001 From: pedrambiria Date: Fri, 14 Apr 2023 09:14:56 +0000 Subject: [PATCH] [FIX] pos_loyalty: zero division when reward product has zero price The issue occurred when a loyalty program's rule was set to be based on money spent, and the reward was a free product with a sale price of zero. This caused a zero division error in the code, resulting in the remaining points becoming NaN after the reward was obtained in the point of sale. opw-3253366 closes odoo/odoo#123123 X-original-commit: 1eaca074741157e42c12da22e945a1817e7ceec3 Signed-off-by: Joseph Caburnay (jcb) Signed-off-by: Pedram Bi Ria (pebr) --- addons/pos_loyalty/static/src/js/Loyalty.js | 22 ++++++---- .../src/tours/PosLoyaltyLoyaltyProgramTour.js | 21 +++++++++ addons/pos_loyalty/tests/test_frontend.py | 44 +++++++++++++++++++ 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/addons/pos_loyalty/static/src/js/Loyalty.js b/addons/pos_loyalty/static/src/js/Loyalty.js index 0f01c13b2dc..a98c5d073d4 100644 --- a/addons/pos_loyalty/static/src/js/Loyalty.js +++ b/addons/pos_loyalty/static/src/js/Loyalty.js @@ -1516,14 +1516,20 @@ patch(Order.prototype, "pos_loyalty.Order", { } return result; }, 0); - const correction = shouldCorrectRemainingPoints - ? this._getPointsCorrection(reward.program_id) - : 0; - freeQty = computeFreeQuantity( - (remainingPoints - correction) / factor, - reward.required_points / factor, - reward.reward_product_qty - ); + if (factor === 0) { + freeQty = Math.floor( + (remainingPoints / reward.required_points) * reward.reward_product_qty + ); + } else { + const correction = shouldCorrectRemainingPoints + ? this._getPointsCorrection(reward.program_id) + : 0; + freeQty = computeFreeQuantity( + (remainingPoints - correction) / factor, + reward.required_points / factor, + reward.reward_product_qty + ); + } } else { freeQty = Math.floor( (remainingPoints / reward.required_points) * reward.reward_product_qty diff --git a/addons/pos_loyalty/static/src/tours/PosLoyaltyLoyaltyProgramTour.js b/addons/pos_loyalty/static/src/tours/PosLoyaltyLoyaltyProgramTour.js index 4535a8e35e5..7dfe88b8db0 100644 --- a/addons/pos_loyalty/static/src/tours/PosLoyaltyLoyaltyProgramTour.js +++ b/addons/pos_loyalty/static/src/tours/PosLoyaltyLoyaltyProgramTour.js @@ -140,3 +140,24 @@ PosLoyalty.check.orderTotalIs("6.40"); PosLoyalty.exec.finalizeOrder("Cash", "10"); registry.category("web_tour.tours").add("PosLoyaltyLoyaltyProgram2", { test: true, url: "/pos/web", steps: getSteps() }); + +startSteps(); + +ProductScreen.do.confirmOpeningPopup(); +ProductScreen.do.clickHomeCategory(); + +// Generates 10.2 points and use points to get the reward product with zero sale price +ProductScreen.exec.addOrderline('Desk Organizer', '2'); +ProductScreen.do.clickPartnerButton(); +ProductScreen.do.clickCustomer('Test Partner AAA'); + +// At this point, the free_product program is triggered. +// The reward button should be highlighted. +PosLoyalty.check.isRewardButtonHighlighted(true); +PosLoyalty.do.claimReward("Free Product - Whiteboard Pen"); +PosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '0.0', '1.00'); + +PosLoyalty.check.orderTotalIs('10.2'); +PosLoyalty.exec.finalizeOrder('Cash', '10.2'); + +registry.category("web_tour.tours").add("PosLoyaltyLoyaltyProgram3", { test: true, url: "/pos/web", steps: getSteps() }); diff --git a/addons/pos_loyalty/tests/test_frontend.py b/addons/pos_loyalty/tests/test_frontend.py index 9a8fe59ad80..34b4da46f8b 100644 --- a/addons/pos_loyalty/tests/test_frontend.py +++ b/addons/pos_loyalty/tests/test_frontend.py @@ -377,6 +377,50 @@ class TestUi(TestPointOfSaleHttpCommon): reward_orderline = self.main_pos_config.current_session_id.order_ids[-1].lines.filtered(lambda line: line.is_reward_line) self.assertEqual(len(reward_orderline.ids), 0, msg='Reference: Order4_no_reward. Last order should have no reward line.') + def test_loyalty_free_product_zero_sale_price_loyalty_program(self): + # In this program, each $ spent gives 1 point. + # 5 points can be used to get a free whiteboard pen. + # and the whiteboard pen sale price is zero + self.pos_user.write({ + 'groups_id': [ + (4, self.env.ref('stock.group_stock_user').id), + ] + }) + self.whiteboard_pen.write({'lst_price': 0}) + + loyalty_program = self.env['loyalty.program'].create({ + 'name': 'Loyalty Program', + 'program_type': 'loyalty', + 'trigger': 'auto', + 'applies_on': 'both', + 'rule_ids': [(0, 0, { + 'reward_point_amount': 1, + 'reward_point_mode': 'money', + 'minimum_qty': 1, + })], + 'reward_ids': [(0, 0, { + 'reward_type': 'product', + 'reward_product_id': self.whiteboard_pen.id, + 'reward_product_qty': 1, + 'required_points': 5, + })], + }) + + (self.promo_programs | self.coupon_program).write({'active': False}) + + partner_aaa = self.env['res.partner'].create({'name': 'Test Partner AAA'}) + + self.start_tour( + "/pos/web?config_id=%d" % self.main_pos_config.id, + "PosLoyaltyLoyaltyProgram3", + login="pos_user", + ) + + aaa_loyalty_card = loyalty_program.coupon_ids.filtered(lambda coupon: coupon.partner_id.id == partner_aaa.id) + + self.assertEqual(loyalty_program.pos_order_count, 1) + self.assertAlmostEqual(aaa_loyalty_card.points, 5.2) + def test_pos_loyalty_tour_max_amount(self): """Test the loyalty program with a maximum amount and product with different taxe."""