[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:
Joseph Caburnay
2020-11-25 10:26:59 +00:00
parent 1d7fc44b48
commit 07aa3a77ab
2 changed files with 119 additions and 45 deletions
@@ -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")