[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) <trj@odoo.com>
This commit is contained in:
rhe-odoo
2023-02-13 12:36:15 +01:00
parent 621c8cf5fe
commit e75c5fc1af
8 changed files with 72 additions and 10 deletions
+18 -2
View File
@@ -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:
@@ -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'
+5
View File
@@ -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',
@@ -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,
@@ -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}
@@ -12,6 +12,7 @@
<div class="oe_title">
<label for="name"/>
<h1><field name="name" placeholder="e.g. Cash" class="oe_inline"/></h1>
<field name="image" class="oe_avatar" widget='image'/>
</div>
<group name="Payment methods">
<field name="hide_use_payment_terminal" invisible="1"/>
@@ -21,7 +22,6 @@
<field name="outstanding_account_id" groups="account.group_account_readonly" attrs="{'invisible': [('type', '!=', 'bank')]}" placeholder="Leave empty to use the default account from the company setting" />
<field name="receivable_account_id" groups="account.group_account_readonly" attrs="{'invisible': [('split_transactions', '=', True)]}" placeholder="Leave empty to use the default account from the company setting" />
<field name="company_id" readonly="1" groups="base.group_multi_company" />
<field name="image"/>
</group>
<group attrs="{'invisible': ['|', ('hide_use_payment_terminal', '=', False), ('type', 'in', ['cash', 'pay_later'])]}">
<div colspan="2">
@@ -54,6 +54,7 @@
<field name="outstanding_account_id" groups="account.group_account_readonly" optional="hide" attrs="{'invisible': [('type', '!=', 'bank')]}" />
<field name="receivable_account_id" groups="account.group_account_readonly" optional="hide" attrs="{'invisible': [('split_transactions', '=', True)]}" />
<field name="company_id" groups="base.group_multi_company" />
<field name="config_ids" widget="many2many_tags"/>
</tree>
</field>
</record>
@@ -67,6 +68,9 @@
<field name="receivable_account_id" groups="account.group_account_readonly" />
<group expand="1" string="Group By">
<filter name="group_by_receivable_account" string="Account" domain="[]" context="{'group_by':'receivable_account_id'}"/>
<filter name="group_by_pos_config" string="Point of Sale" domain="[]" context="{'group_by':'config_ids'}"/>
<filter name="group_by_method_name" string="Method Name" domain="[]" context="{'group_by':'name'}"/>
<filter name="group_by_journal_id" string="Journal" domain="[]" context="{'group_by':'journal_id'}"/>
</group>
<filter string="Archived" name="active" domain="[('active', '=', False)]"/>
</search>
+23 -2
View File
@@ -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,
+2 -2
View File
@@ -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,
})],