From 5783e0573d1964693e48b601b81edd8228ee6ef7 Mon Sep 17 00:00:00 2001 From: roen-odoo Date: Mon, 11 Dec 2023 14:25:59 +0000 Subject: [PATCH] [FIX] pos_loyalty: prevent using expired eWallets Current behavior: It was possible to use an expired eWallet to pay for an order. It was also possible to put money on an expired eWallet. Steps to reproduce: - Create an eWallet with an expiration date in the past - Open PoS and add products to the order - Click on the eWallet button - You can still use the eWallet to pay for the order opw-3568270 closes odoo/odoo#149622 X-original-commit: e3492b9192d9ca56104e0ce92371fffc1731b406 Signed-off-by: Joseph Caburnay (jcb) Signed-off-by: Robin Engels (roen) --- addons/pos_loyalty/i18n/pos_loyalty.pot | 22 ++++++++++++++ .../e_wallet_button/e_wallet_button.js | 15 +++++++--- .../static/src/overrides/models/loyalty.js | 11 +++++-- .../static/src/overrides/models/pos_store.js | 5 ++-- .../static/tests/tours/EWalletProgramTours.js | 19 ++++++++++++ addons/pos_loyalty/tests/test_frontend.py | 29 +++++++++++++++++++ 6 files changed, 93 insertions(+), 8 deletions(-) diff --git a/addons/pos_loyalty/i18n/pos_loyalty.pot b/addons/pos_loyalty/i18n/pos_loyalty.pot index 7793d148d6d..36663be208a 100644 --- a/addons/pos_loyalty/i18n/pos_loyalty.pot +++ b/addons/pos_loyalty/i18n/pos_loyalty.pot @@ -331,6 +331,20 @@ msgstr "" #. odoo-javascript #: code:addons/pos_loyalty/static/src/app/control_buttons/reward_button/reward_button.js:0 #, python-format +msgid "No rewards available." +msgstr "" + +#. module: pos_loyalty +#. odoo-javascript +#: code:addons/pos_loyalty/static/src/js/ControlButtons/eWalletButton.js:0 +#, python-format +msgid "No valid eWallet found" +msgstr "" + +#. module: pos_loyalty +#. odoo-javascript +#: code:addons/pos_loyalty/static/src/js/ControlButtons/RewardButton.js:0 +#, python-format msgid "Please select a product for this reward" msgstr "" @@ -754,6 +768,14 @@ msgstr "" msgid "You cannot set negative quantity or price to gift card or ewallet." msgstr "" +#. module: pos_loyalty +#. odoo-javascript +#: code:addons/pos_loyalty/static/src/js/ControlButtons/eWalletButton.js:0 +#, python-format +msgid "" +"You either have not created an eWallet or all your eWallets have expired." +msgstr "" + #. module: pos_loyalty #. odoo-python #: code:addons/pos_loyalty/models/loyalty_program.py:0 diff --git a/addons/pos_loyalty/static/src/app/control_buttons/e_wallet_button/e_wallet_button.js b/addons/pos_loyalty/static/src/app/control_buttons/e_wallet_button/e_wallet_button.js index 333ff641e02..13720f57496 100644 --- a/addons/pos_loyalty/static/src/app/control_buttons/e_wallet_button/e_wallet_button.js +++ b/addons/pos_loyalty/static/src/app/control_buttons/e_wallet_button/e_wallet_button.js @@ -18,10 +18,10 @@ export class eWalletButton extends Component { _getEWalletRewards(order) { const claimableRewards = order.getClaimableRewards(); - const eWalletRewards = claimableRewards.filter( - ({ reward }) => reward.program_id.program_type == "ewallet" - ); - return eWalletRewards; + return claimableRewards.filter((reward_line) => { + const coupon = this.pos.couponCache[reward_line.coupon_id]; + return coupon && reward_line.reward.program_id.program_type == 'ewallet' && !coupon.isExpired(); + }); } _getEWalletPrograms() { return this.pos.programs.filter((p) => p.program_type == "ewallet"); @@ -31,6 +31,13 @@ export class eWalletButton extends Component { const eWalletPrograms = this.pos.programs.filter((p) => p.program_type == "ewallet"); const orderTotal = order.get_total_with_tax(); const eWalletRewards = this._getEWalletRewards(order); + if (eWalletRewards.length === 0 && orderTotal >= 0) { + this.popup.add(ErrorPopup, { + title: _t('No valid eWallet found'), + body: _t('You either have not created an eWallet or all your eWallets have expired.'), + }); + return; + } if (orderTotal < 0 && eWalletPrograms.length >= 1) { let selectedProgram = null; if (eWalletPrograms.length == 1) { diff --git a/addons/pos_loyalty/static/src/overrides/models/loyalty.js b/addons/pos_loyalty/static/src/overrides/models/loyalty.js index d22dd0f1d3c..ba209f60406 100644 --- a/addons/pos_loyalty/static/src/overrides/models/loyalty.js +++ b/addons/pos_loyalty/static/src/overrides/models/loyalty.js @@ -25,13 +25,19 @@ export class PosLoyaltyCard { * @param {number} program_id id of loyalty.program * @param {number} partner_id id of res.partner * @param {number} balance points on the coupon, not counting the order's changes + * @param {string} expiration_date */ - constructor(code, id, program_id, partner_id, balance) { + constructor(code, id, program_id, partner_id, balance, expiration_date = false) { this.code = code; this.id = id || nextId--; this.program_id = program_id; this.partner_id = partner_id; this.balance = balance; + this.expiration_date = expiration_date && new Date(expiration_date); + } + + isExpired() { + return this.expiration_date && this.expiration_date < new Date(); } } @@ -1547,7 +1553,8 @@ patch(Order.prototype, { payload.coupon_id, payload.program_id, payload.partner_id, - payload.points + payload.points, + payload.expiration_date, ); this.pos.couponCache[coupon.id] = coupon; this.codeActivatedCoupons.push(coupon); diff --git a/addons/pos_loyalty/static/src/overrides/models/pos_store.js b/addons/pos_loyalty/static/src/overrides/models/pos_store.js index 481734d9947..e31aadba1a3 100644 --- a/addons/pos_loyalty/static/src/overrides/models/pos_store.js +++ b/addons/pos_loyalty/static/src/overrides/models/pos_store.js @@ -309,7 +309,7 @@ patch(PosStore.prototype, { const result = await this.env.services.orm.searchRead( "loyalty.card", domain, - ["id", "points", "code", "partner_id", "program_id"], + ["id", "points", "code", "partner_id", "program_id", "expiration_date"], { limit } ); if (Object.keys(this.couponCache).length + result.length > COUPON_CACHE_MAX_SIZE) { @@ -327,7 +327,8 @@ patch(PosStore.prototype, { dbCoupon.id, dbCoupon.program_id[0], dbCoupon.partner_id[0], - dbCoupon.points + dbCoupon.points, + dbCoupon.expiration_date ); this.couponCache[coupon.id] = coupon; this.partnerId2CouponIds[coupon.partner_id] = diff --git a/addons/pos_loyalty/static/tests/tours/EWalletProgramTours.js b/addons/pos_loyalty/static/tests/tours/EWalletProgramTours.js index f7f541c0f42..65fb5fb2fe1 100644 --- a/addons/pos_loyalty/static/tests/tours/EWalletProgramTours.js +++ b/addons/pos_loyalty/static/tests/tours/EWalletProgramTours.js @@ -5,6 +5,7 @@ import * as ProductScreen from "@point_of_sale/../tests/tours/helpers/ProductScr import * as TicketScreen from "@point_of_sale/../tests/tours/helpers/TicketScreenTourMethods"; import * as Chrome from "@point_of_sale/../tests/tours/helpers/ChromeTourMethods"; import * as PartnerListScreen from "@point_of_sale/../tests/tours/helpers/PartnerListScreenTourMethods"; +import * as ErrorPopup from "@point_of_sale/../tests/tours/helpers/ErrorPopupTourMethods"; import { registry } from "@web/core/registry"; //#region EWalletProgramTour1 @@ -105,3 +106,21 @@ registry.category("web_tour.tours").add("EWalletProgramTour2", { }); //#endregion + +registry + .category("web_tour.tours") + .add('ExpiredEWalletProgramTour', { + test: true, + url: '/pos/ui', + steps: () => [ + ProductScreen.confirmOpeningPopup(), + ProductScreen.clickHomeCategory(), + ProductScreen.clickPartnerButton(), + ProductScreen.clickCustomer('AAAA'), + ProductScreen.addOrderline('Whiteboard Pen', '2', '6', '12.00'), + PosLoyalty.eWalletButtonState({ highlighted: false }), + PosLoyalty.clickEWalletButton(), + ErrorPopup.isShown(), + ErrorPopup.clickConfirm() + ].flat(), +}); diff --git a/addons/pos_loyalty/tests/test_frontend.py b/addons/pos_loyalty/tests/test_frontend.py index 2b920a81924..3f6bb241e04 100644 --- a/addons/pos_loyalty/tests/test_frontend.py +++ b/addons/pos_loyalty/tests/test_frontend.py @@ -1428,3 +1428,32 @@ class TestUi(TestPointOfSaleHttpCommon): "PosLoyaltyTour9", login="pos_user", ) + + def test_ewallet_expiration_date(self): + """ + Test for ewallet program. + - Collect points in EWalletProgramTour1. + - Use points in EWalletProgramTour2. + """ + LoyaltyProgram = self.env['loyalty.program'] + # Deactivate all other programs to avoid interference + (LoyaltyProgram.search([])).write({'pos_ok': False}) + # But activate the ewallet_product_50 because it's shared among new ewallet programs. + self.env.ref('loyalty.ewallet_product_50').write({'active': True}) + # Create ewallet program + ewallet_program = self.create_programs([('arbitrary_name', 'ewallet')])['arbitrary_name'] + # Create test partners + partner_aaa = self.env['res.partner'].create({'name': 'AAAA'}) + #Create an eWallet for partner_aaa + self.env['loyalty.card'].create({ + 'partner_id': partner_aaa.id, + 'program_id': ewallet_program.id, + 'points': 50, + 'expiration_date': '2020-01-01', + }) + self.main_pos_config.open_ui() + self.start_tour( + "/pos/web?config_id=%d" % self.main_pos_config.id, + "ExpiredEWalletProgramTour", + login="pos_user", + )