summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorJoseph Caburnay <jcb@odoo.com>2020-11-24 07:26:50 +0000
committerJoseph Caburnay <jcb@odoo.com>2020-11-24 07:26:50 +0000
commit7b9545387962981b5729318e2bbc5714dcbac68c (patch)
tree5b1468058fba00766f5f49429ca43fb4cbae73c1 /addons
parentbea4222875b82b96069a20fc68eb15ab4e7a8797 (diff)
[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#62194 X-original-commit: 5c6b045 Signed-off-by: pimodoo <pimodoo@users.noreply.github.com>
Diffstat (limited to 'addons')
-rw-r--r--addons/point_of_sale/models/pos_session.py33
-rw-r--r--addons/point_of_sale/tests/test_point_of_sale_flow.py131
2 files changed, 119 insertions, 45 deletions
diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py
index 9e992d20083..34f14a7cc0f 100644
--- a/addons/point_of_sale/models/pos_session.py
+++ b/addons/point_of_sale/models/pos_session.py
@@ -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()
diff --git a/addons/point_of_sale/tests/test_point_of_sale_flow.py b/addons/point_of_sale/tests/test_point_of_sale_flow.py
index c2b1696b39e..04b9b952725 100644
--- a/addons/point_of_sale/tests/test_point_of_sale_flow.py
+++ b/addons/point_of_sale/tests/test_point_of_sale_flow.py
@@ -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")