From e75c5fc1afd7cfb7207a20f15af224be91b7e2ff Mon Sep 17 00:00:00 2001 From: rhe-odoo Date: Wed, 1 Jun 2022 13:44:01 +0000 Subject: [PATCH] [IMP] point_of_sale: warning with cash payment methods Currently, it is possible to assign the same "Cash" payment method to multiple PoS. At the moment, when creating a new PoS, Odoo tends to assign it by default. Although this is highly not recommended as it can only lead to incorrect cash control (two PoS devices rarely share the same till and therefore needs their own cash payment method). This commit makes sure payment methods of type Cash aren't set by default when creating new PoS. it also blocks the user from setting the same Cash payment method to multiple PoS configs. closes odoo/odoo#92664 Task-id: 2857417 Signed-off-by: Trinh Jacky (trj) --- addons/point_of_sale/models/pos_config.py | 20 +++++++++++++-- .../models/pos_payment_method.py | 2 +- addons/point_of_sale/tests/common.py | 5 ++++ .../point_of_sale/tests/test_anglo_saxon.py | 8 +++++- .../tests/test_pos_basic_config.py | 12 ++++++++- .../views/pos_payment_method_views.xml | 6 ++++- addons/pos_loyalty/tests/test_frontend.py | 25 +++++++++++++++++-- addons/pos_restaurant/tests/test_frontend.py | 4 +-- 8 files changed, 72 insertions(+), 10 deletions(-) diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index 367a1fa48ab..5dfc98a66c0 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -142,7 +142,7 @@ class PosConfig(models.Model): help="This field depicts the maximum difference allowed between the ending balance and the theoretical cash when " "closing a session, for non-POS managers. If this maximum is reached, the user will have an error message at " "the closing of his session saying that he needs to contact his manager.") - payment_method_ids = fields.Many2many('pos.payment.method', string='Payment Methods', default=lambda self: self._default_payment_methods()) + payment_method_ids = fields.Many2many('pos.payment.method', string='Payment Methods', default=lambda self: self._default_payment_methods(), copy=False) company_has_template = fields.Boolean(string="Company has chart of accounts", compute="_compute_company_has_template") current_user_id = fields.Many2one('res.users', string='Current Session Responsible', compute='_compute_current_session_user') other_devices = fields.Boolean(string="Other Devices", help="Connect devices to your PoS without an IoT Box.") @@ -352,6 +352,18 @@ class PosConfig(models.Model): raise ValidationError(_("No chart of account configured, go to the \"configuration / settings\" menu, and " "install one from the Invoicing tab.")) + @api.constrains('payment_method_ids') + def _check_payment_method_ids_journal(self): + cash_journal = self.env['account.journal'].search( + [('company_id', '=', self.env.company.id), ('type', '=', 'cash')]) + + for cash_method in self.payment_method_ids.filtered(lambda s: s.journal_id.id in cash_journal.ids): + if self.env['pos.config'].search([('id', '!=', self.id), ('payment_method_ids', 'in', cash_method.ids)]): + raise ValidationError(_("This cash payment method is already used in another Point of Sale.\n" + "A new cash payment method should be created for this Point of Sale.")) + if len(cash_method.journal_id.pos_payment_method_ids) > 1: + raise ValidationError(_("You cannot use the same journal on multiples cash payment methods.")) + def name_get(self): result = [] for config in self: @@ -586,7 +598,11 @@ class PosConfig(models.Model): if cash_journal: payment_methods |= payment_methods.create({ 'name': _('Cash'), - 'journal_id': cash_journal.id, + 'journal_id': self.env['account.journal'].create({ + 'name': "Cash", + 'code': "CSH %s" % pos_config.id, + 'type': 'cash', + }).id, 'company_id': company.id, }) if bank_journal: diff --git a/addons/point_of_sale/models/pos_payment_method.py b/addons/point_of_sale/models/pos_payment_method.py index ac4c589f91c..ce6b89bf363 100644 --- a/addons/point_of_sale/models/pos_payment_method.py +++ b/addons/point_of_sale/models/pos_payment_method.py @@ -25,7 +25,7 @@ class PosPaymentMethod(models.Model): is_cash_count = fields.Boolean(string='Cash', compute="_compute_is_cash_count", store=True) journal_id = fields.Many2one('account.journal', string='Journal', - domain=[('type', 'in', ('cash', 'bank'))], + domain=['|', '&', ('type', '=', 'cash'), ('pos_payment_method_ids', '=', False), ('type', '=', 'bank')], ondelete='restrict', help='Leave empty to use the receivable account of customer.\n' 'Defines the journal where to book the accumulated payments (or individual payment if Identify Customer is true) after closing the session.\n' diff --git a/addons/point_of_sale/tests/common.py b/addons/point_of_sale/tests/common.py index bfc831e4869..685913de994 100644 --- a/addons/point_of_sale/tests/common.py +++ b/addons/point_of_sale/tests/common.py @@ -263,6 +263,11 @@ class TestPoSCommon(ValuationReconciliationTestCommon): cls.cash_split_pm1 = cls.cash_pm1.copy(default={ 'name': 'Split (Cash) PM', 'split_transactions': True, + 'journal_id': cls.env['account.journal'].create({ + 'name': "Cash", + 'code': "CSH %s" % config.id, + 'type': 'cash', + }).id }) cls.bank_split_pm1 = cls.bank_pm1.copy(default={ 'name': 'Split (Bank) PM', diff --git a/addons/point_of_sale/tests/test_anglo_saxon.py b/addons/point_of_sale/tests/test_anglo_saxon.py index 41f131088b2..f63011e56e3 100644 --- a/addons/point_of_sale/tests/test_anglo_saxon.py +++ b/addons/point_of_sale/tests/test_anglo_saxon.py @@ -31,7 +31,13 @@ class TestAngloSaxonCommon(common.TransactionCase): self.category.property_stock_valuation_account_id = account_valuation self.category.property_stock_journal = self.env['account.journal'].create({'name': 'Stock journal', 'type': 'sale', 'code': 'STK00'}) self.pos_config = self.env.ref('point_of_sale.pos_config_main') - self.pos_config = self.pos_config.copy({'name': 'New POS config'}) + self.cash_journal = self.env['account.journal'].create( + {'name': 'CASH journal', 'type': 'cash', 'code': 'CSH02'}) + self.cash_payment_method = self.env['pos.payment.method'].create({ + 'name': 'Cash Test', + 'journal_id': self.cash_journal.id, + }) + self.pos_config = self.pos_config.copy({'name': 'New POS config', 'payment_method_ids': self.cash_payment_method}) self.product = self.env['product.product'].create({ 'name': 'New product', 'standard_price': 100, diff --git a/addons/point_of_sale/tests/test_pos_basic_config.py b/addons/point_of_sale/tests/test_pos_basic_config.py index fee1a5a0766..31b5078c2cf 100644 --- a/addons/point_of_sale/tests/test_pos_basic_config.py +++ b/addons/point_of_sale/tests/test_pos_basic_config.py @@ -845,7 +845,17 @@ class TestPoSBasicConfig(TestPoSCommon): self.assertEqual(session.cash_register_balance_start, pos_data['amount_paid']) pos01_config = self.config - pos02_config = pos01_config.copy() + self.cash_journal = self.env['account.journal'].create( + {'name': 'CASH journal', 'type': 'cash', 'code': 'CSH00'}) + self.cash_payment_method = self.env['pos.payment.method'].create({ + 'name': 'Cash Test', + 'journal_id': self.cash_journal.id, + 'receivable_account_id': pos01_config.payment_method_ids.filtered(lambda s: s.is_cash_count)[ + 1].receivable_account_id.id + }) + pos02_config = pos01_config.copy({ + 'payment_method_ids': self.cash_payment_method + }) pos01_data = {'config': pos01_config, 'p_qty': 1, 'amount_paid': 0} pos02_data = {'config': pos02_config, 'p_qty': 3, 'amount_paid': 0} diff --git a/addons/point_of_sale/views/pos_payment_method_views.xml b/addons/point_of_sale/views/pos_payment_method_views.xml index 743185b39a9..a94e2b48902 100644 --- a/addons/point_of_sale/views/pos_payment_method_views.xml +++ b/addons/point_of_sale/views/pos_payment_method_views.xml @@ -12,6 +12,7 @@
@@ -21,7 +22,6 @@ -
@@ -54,6 +54,7 @@ + @@ -67,6 +68,9 @@ + + + diff --git a/addons/pos_loyalty/tests/test_frontend.py b/addons/pos_loyalty/tests/test_frontend.py index 620ea810ce2..dd12eafb1b9 100644 --- a/addons/pos_loyalty/tests/test_frontend.py +++ b/addons/pos_loyalty/tests/test_frontend.py @@ -629,7 +629,17 @@ class TestUi(TestPointOfSaleHttpCommon): ] }) - self.main_pos_config2 = self.main_pos_config.copy() + self.cash_journal = self.env['account.journal'].create( + {'name': 'CASH journal', 'type': 'cash', 'code': 'CSH00'}) + self.cash_payment_method = self.env['pos.payment.method'].create({ + 'name': 'Cash Test', + 'journal_id': self.cash_journal.id, + 'receivable_account_id': self.main_pos_config.payment_method_ids.filtered(lambda s: s.is_cash_count).receivable_account_id.id + }) + + self.main_pos_config2 = self.main_pos_config.copy({ + 'payment_method_ids': self.cash_payment_method + }) loyalty_program = self.env['loyalty.program'].create({ 'name': 'Coupon Program - Pricelist', @@ -699,7 +709,18 @@ class TestUi(TestPointOfSaleHttpCommon): } ) - self.main_pos_config2 = self.main_pos_config.copy() + self.cash_journal = self.env['account.journal'].create( + {'name': 'CASH journal', 'type': 'cash', 'code': 'CSHDI'}) + self.cash_payment_method = self.env['pos.payment.method'].create({ + 'name': 'Cash Test', + 'journal_id': self.cash_journal.id, + 'receivable_account_id': self.main_pos_config.payment_method_ids.filtered( + lambda s: s.is_cash_count).receivable_account_id.id + }) + + self.main_pos_config2 = self.main_pos_config.copy({ + 'payment_method_ids': self.cash_payment_method + }) self.main_pos_config2.write({ 'module_pos_discount' : True, 'discount_product_id': self.discount_product.id, diff --git a/addons/pos_restaurant/tests/test_frontend.py b/addons/pos_restaurant/tests/test_frontend.py index 8cb6694a25d..39e3044631c 100644 --- a/addons/pos_restaurant/tests/test_frontend.py +++ b/addons/pos_restaurant/tests/test_frontend.py @@ -123,8 +123,8 @@ class TestFrontend(odoo.tests.HttpCase): 'journal_id': test_sale_journal.id, 'invoice_journal_id': test_sale_journal.id, 'payment_method_ids': [(0, 0, { - 'name': 'Cash restaurant', - 'split_transactions': True, + 'name': 'Cash', + 'split_transactions': False, 'receivable_account_id': account_receivable.id, 'journal_id': cash_journal.id, })],