[FIX] point_of_sale: balance pos session move when closing
Instances like deleting a tax used in a pos.order before closing a session is allowed but it renders error when closing the session because of imbalance move lines. In this commit, we are adding a 'balancing' line in the move before it is created. This ensures the closing of pos session. The introduced move line is named 'Difference at closing PoS session' and is a receivable making it easy to find and can be reconciled. closes odoo/odoo#62308 X-original-commit: 7b9545387962981b5729318e2bbc5714dcbac68c Signed-off-by: pimodoo <pimodoo@users.noreply.github.com> Signed-off-by: Joseph Caburnay (jcb) <caburj@users.noreply.github.com>
This commit is contained in:
@@ -352,6 +352,38 @@ class PosSession(models.Model):
|
||||
pickings = self.env['stock.picking']._create_picking_from_pos_order_lines(location_dest_id, lines, picking_type)
|
||||
pickings.write({'pos_session_id': self.id, 'origin': self.name})
|
||||
|
||||
def _create_balancing_line(self, data):
|
||||
imbalance_amount = 0
|
||||
for line in self.move_id.line_ids:
|
||||
# it is an excess debit so it should be credited
|
||||
imbalance_amount += line.debit - line.credit
|
||||
|
||||
if (not float_is_zero(imbalance_amount, precision_rounding=self.currency_id.rounding)):
|
||||
balancing_vals = self._prepare_balancing_line_vals(imbalance_amount, self.move_id)
|
||||
MoveLine = data.get('MoveLine')
|
||||
MoveLine.create(balancing_vals)
|
||||
|
||||
return data
|
||||
|
||||
def _prepare_balancing_line_vals(self, imbalance_amount, move):
|
||||
account = self._get_balancing_account()
|
||||
partial_vals = {
|
||||
'name': _('Difference at closing PoS session'),
|
||||
'account_id': account.id,
|
||||
'move_id': move.id,
|
||||
'partner_id': False,
|
||||
}
|
||||
# `imbalance_amount` is already in terms of company currency so it is the amount_converted
|
||||
# param when calling `_credit_amounts`. amount param will be the converted value of
|
||||
# `imbalance_amount` from company currency to the session currency.
|
||||
imbalance_amount_session = 0
|
||||
if (not self.is_in_company_currency):
|
||||
imbalance_amount_session = self.company_id.currency_id._convert(imbalance_amount, self.currency_id, self.company_id, fields.Date.context_today(self))
|
||||
return self._credit_amounts(partial_vals, imbalance_amount_session, imbalance_amount)
|
||||
|
||||
def _get_balancing_account(self):
|
||||
return self.company_id.account_default_pos_receivable_account_id or self.env['ir.property'].get('property_account_receivable_id', 'res.partner')
|
||||
|
||||
def _create_account_move(self):
|
||||
""" Create account.move and account.move.line records for this session.
|
||||
|
||||
@@ -376,6 +408,7 @@ class PosSession(models.Model):
|
||||
data = self._create_cash_statement_lines_and_cash_move_lines(data)
|
||||
data = self._create_invoice_receivable_lines(data)
|
||||
data = self._create_stock_output_lines(data)
|
||||
data = self._create_balancing_line(data)
|
||||
|
||||
if account_move.line_ids:
|
||||
account_move._post()
|
||||
|
||||
@@ -13,6 +13,14 @@ from odoo.addons.point_of_sale.tests.common import TestPointOfSaleCommon
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
|
||||
def compute_tax(self, product, price, qty=1, taxes=None):
|
||||
if not taxes:
|
||||
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id)
|
||||
currency = self.pos_config.pricelist_id.currency_id
|
||||
res = taxes.compute_all(price, currency, qty, product=product)
|
||||
untax = res['total_excluded']
|
||||
return untax, sum(tax.get('amount', 0.0) for tax in res['taxes'])
|
||||
|
||||
def test_order_refund(self):
|
||||
self.pos_config.open_session_cb(check_coa=False)
|
||||
current_session = self.pos_config.current_session_id
|
||||
@@ -196,22 +204,14 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
check the quantity, the locations and return picking logic
|
||||
"""
|
||||
|
||||
def compute_tax(product, price, qty=1, taxes=None):
|
||||
if taxes is None:
|
||||
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id)
|
||||
currency = self.pos_config.pricelist_id.currency_id
|
||||
res = taxes.compute_all(price, currency, qty, product=product)
|
||||
untax = res['total_excluded']
|
||||
return untax, sum(tax.get('amount', 0.0) for tax in res['taxes'])
|
||||
|
||||
# I click on create a new session button
|
||||
self.pos_config.open_session_cb(check_coa=False)
|
||||
current_session = self.pos_config.current_session_id
|
||||
|
||||
# I create a PoS order with 2 units of PCSC234 at 450 EUR
|
||||
# and 3 units of PCSC349 at 300 EUR.
|
||||
untax1, atax1 = compute_tax(self.product3, 450, 2)
|
||||
untax2, atax2 = compute_tax(self.product4, 300, 3)
|
||||
untax1, atax1 = self.compute_tax(self.product3, 450, 2)
|
||||
untax2, atax2 = self.compute_tax(self.product4, 300, 3)
|
||||
self.pos_order_pos1 = self.PosOrder.create({
|
||||
'company_id': self.env.company.id,
|
||||
'session_id': current_session.id,
|
||||
@@ -275,8 +275,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
)
|
||||
|
||||
# I create a second order
|
||||
untax1, atax1 = compute_tax(self.product3, 450, -2)
|
||||
untax2, atax2 = compute_tax(self.product4, 300, -3)
|
||||
untax1, atax1 = self.compute_tax(self.product3, 450, -2)
|
||||
untax2, atax2 = self.compute_tax(self.product4, 300, -3)
|
||||
self.pos_order_pos2 = self.PosOrder.create({
|
||||
'company_id': self.env.company.id,
|
||||
'session_id': current_session.id,
|
||||
@@ -339,8 +339,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
'Move Lines should be in done state.'
|
||||
)
|
||||
|
||||
untax1, atax1 = compute_tax(self.product3, 450, -2)
|
||||
untax2, atax2 = compute_tax(self.product4, 300, 3)
|
||||
untax1, atax1 = self.compute_tax(self.product3, 450, -2)
|
||||
untax2, atax2 = self.compute_tax(self.product4, 300, 3)
|
||||
self.pos_order_pos3 = self.PosOrder.create({
|
||||
'company_id': self.env.company.id,
|
||||
'session_id': current_session.id,
|
||||
@@ -407,19 +407,11 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
|
||||
def test_order_to_invoice(self):
|
||||
|
||||
def compute_tax(product, price, qty=1, taxes=None):
|
||||
if taxes is None:
|
||||
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id)
|
||||
currency = self.pos_config.pricelist_id.currency_id
|
||||
res = taxes.compute_all(price, currency, qty, product=product)
|
||||
untax = res['total_excluded']
|
||||
return untax, sum(tax.get('amount', 0.0) for tax in res['taxes'])
|
||||
|
||||
self.pos_config.open_session_cb(check_coa=False)
|
||||
current_session = self.pos_config.current_session_id
|
||||
|
||||
untax1, atax1 = compute_tax(self.product3, 450*0.95, 2)
|
||||
untax2, atax2 = compute_tax(self.product4, 300*0.95, 3)
|
||||
untax1, atax1 = self.compute_tax(self.product3, 450*0.95, 2)
|
||||
untax2, atax2 = self.compute_tax(self.product4, 300*0.95, 3)
|
||||
# I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
|
||||
self.pos_order_pos1 = self.PosOrder.create({
|
||||
'company_id': self.env.company.id,
|
||||
@@ -529,21 +521,13 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
Simulation of sales coming from the interface, even after closing the session
|
||||
"""
|
||||
|
||||
def compute_tax(product, price, qty=1, taxes=None):
|
||||
if not taxes:
|
||||
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id)
|
||||
currency = self.pos_config.pricelist_id.currency_id
|
||||
res = taxes.compute_all(price, currency, qty, product=product)
|
||||
untax = res['total_excluded']
|
||||
return untax, sum(tax.get('amount', 0.0) for tax in res['taxes'])
|
||||
|
||||
# I click on create a new session button
|
||||
self.pos_config.open_session_cb(check_coa=False)
|
||||
|
||||
current_session = self.pos_config.current_session_id
|
||||
num_starting_orders = len(current_session.order_ids)
|
||||
|
||||
untax, atax = compute_tax(self.led_lamp, 0.9)
|
||||
untax, atax = self.compute_tax(self.led_lamp, 0.9)
|
||||
carrot_order = {'data':
|
||||
{'amount_paid': untax + atax,
|
||||
'amount_return': 0,
|
||||
@@ -577,7 +561,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
'id': '00042-003-0014',
|
||||
'to_invoice': False}
|
||||
|
||||
untax, atax = compute_tax(self.whiteboard_pen, 1.2)
|
||||
untax, atax = self.compute_tax(self.whiteboard_pen, 1.2)
|
||||
zucchini_order = {'data':
|
||||
{'amount_paid': untax + atax,
|
||||
'amount_return': 0,
|
||||
@@ -611,7 +595,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
'id': '00043-003-0014',
|
||||
'to_invoice': False}
|
||||
|
||||
untax, atax = compute_tax(self.newspaper_rack, 1.28)
|
||||
untax, atax = self.compute_tax(self.newspaper_rack, 1.28)
|
||||
newspaper_rack_order = {'data':
|
||||
{'amount_paid': untax + atax,
|
||||
'amount_return': 0,
|
||||
@@ -689,14 +673,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
})
|
||||
|
||||
def compute_tax(product, price, qty=1, taxes=None):
|
||||
if not taxes:
|
||||
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id)
|
||||
currency = self.pos_config.pricelist_id.currency_id
|
||||
res = taxes.compute_all(price, currency, qty, product=product)
|
||||
untax = res['total_excluded']
|
||||
return untax, sum(tax.get('amount', 0.0) for tax in res['taxes'])
|
||||
|
||||
# make a config that has currency different from the company
|
||||
eur_pricelist = self.partner1.property_product_pricelist.copy(default={'currency_id': self.env.ref('base.EUR').id})
|
||||
sale_journal = self.env['account.journal'].create({
|
||||
@@ -724,8 +700,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
# I create a PoS order with 2 units of PCSC234 at 450 EUR (Tax Incl)
|
||||
# and 3 units of PCSC349 at 300 EUR. (Tax Excl)
|
||||
|
||||
untax1, atax1 = compute_tax(self.product3, 450, 2)
|
||||
untax2, atax2 = compute_tax(self.product4, 300, 3)
|
||||
untax1, atax1 = self.compute_tax(self.product3, 450, 2)
|
||||
untax2, atax2 = self.compute_tax(self.product4, 300, 3)
|
||||
self.pos_order_pos0 = self.PosOrder.create({
|
||||
'company_id': self.env.company.id,
|
||||
'session_id': current_session.id,
|
||||
@@ -877,3 +853,68 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
self.assertFalse(iline.tax_ids)
|
||||
|
||||
self.pos_config.current_session_id.action_pos_session_closing_control()
|
||||
|
||||
def test_order_with_deleted_tax(self):
|
||||
# create tax
|
||||
dummy_50_perc_tax = self.env['account.tax'].create({
|
||||
'name': 'Tax 50%',
|
||||
'amount_type': 'percent',
|
||||
'amount': 50.0,
|
||||
'price_include': 0
|
||||
})
|
||||
|
||||
# set tax to product
|
||||
product5 = self.env['product.product'].create({
|
||||
'name': 'product5',
|
||||
'type': 'product',
|
||||
'categ_id': self.env.ref('product.product_category_all').id,
|
||||
'taxes_id': dummy_50_perc_tax.ids
|
||||
})
|
||||
|
||||
# sell product thru pos
|
||||
self.pos_config.open_session_cb(check_coa=False)
|
||||
pos_session = self.pos_config.current_session_id
|
||||
untax, atax = self.compute_tax(product5, 10.0)
|
||||
product5_order = {'data':
|
||||
{'amount_paid': untax + atax,
|
||||
'amount_return': 0,
|
||||
'amount_tax': atax,
|
||||
'amount_total': untax + atax,
|
||||
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
|
||||
'fiscal_position_id': False,
|
||||
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
|
||||
'lines': [[0,
|
||||
0,
|
||||
{'discount': 0,
|
||||
'id': 42,
|
||||
'pack_lot_ids': [],
|
||||
'price_unit': 10.0,
|
||||
'product_id': product5.id,
|
||||
'price_subtotal': 10.0,
|
||||
'price_subtotal_incl': 15.0,
|
||||
'qty': 1,
|
||||
'tax_ids': [(6, 0, product5.taxes_id.ids)]}]],
|
||||
'name': 'Order 12345-123-1234',
|
||||
'partner_id': False,
|
||||
'pos_session_id': pos_session.id,
|
||||
'sequence_number': 2,
|
||||
'statement_ids': [[0,
|
||||
0,
|
||||
{'amount': untax + atax,
|
||||
'name': fields.Datetime.now(),
|
||||
'payment_method_id': self.cash_payment_method.id}]],
|
||||
'uid': '12345-123-1234',
|
||||
'user_id': self.env.uid},
|
||||
'id': '12345-123-1234',
|
||||
'to_invoice': False}
|
||||
self.PosOrder.create_from_ui([product5_order])
|
||||
|
||||
# delete tax
|
||||
dummy_50_perc_tax.unlink()
|
||||
|
||||
# close session (should not fail here)
|
||||
pos_session.action_pos_session_closing_control()
|
||||
|
||||
# check the difference line
|
||||
diff_line = pos_session.move_id.line_ids.filtered(lambda line: line.name == 'Difference at closing PoS session')
|
||||
self.assertAlmostEqual(diff_line.credit, 5.0, msg="Missing amount of 5.0")
|
||||
|
||||
Reference in New Issue
Block a user