From 8ec1687891c98aeacee27dbaf058f30ac85d8a76 Mon Sep 17 00:00:00 2001 From: nda Date: Thu, 7 Dec 2023 18:04:02 +0000 Subject: [PATCH] [FIX] point_of_sale: avoid to create new pickings after session closing steps to reproduce: - configure your POS with anglosaxon accounting and "Update quantities in stock" at session closing and enable "Use QR code on ticket" - open the POS, sell a storable product and close the session - scan the QR code on the ticket to create an invoice - check the pickings on the session form view before this commit: - 2 pickings are created, one at session closing and one from the invoice after this commit: - if the session is closed, do not create a new picking opw-3592418 closes odoo/odoo#146077 X-original-commit: 486fa23e586c1dae490ffe0a91138d6f6dc6d824 Signed-off-by: Joseph Caburnay (jcb) Signed-off-by: Nicolas Danhier (nda) --- addons/point_of_sale/models/pos_order.py | 2 +- .../tests/test_point_of_sale_flow.py | 121 +++++++++++------- 2 files changed, 73 insertions(+), 50 deletions(-) diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 1cbca0d47a1..ba771928d96 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -890,7 +890,7 @@ class PosOrder(models.Model): raise UserError(_("You cannot invoice orders belonging to different companies.")) self.write({'to_invoice': True}) res = self._generate_pos_order_invoice() - if self.company_id.anglo_saxon_accounting and self.session_id.update_stock_at_closing: + if self.company_id.anglo_saxon_accounting and self.session_id.update_stock_at_closing and self.session_id.state != 'closed': self._create_order_picking() return res 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 9b362de19b6..8e8f24be17a 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 @@ -23,6 +23,58 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): untax = res['total_excluded'] return untax, sum(tax.get('amount', 0.0) for tax in res['taxes']) + def _create_pos_order_for_postponed_invoicing(self): + # Create the order on the first of january. + with freeze_time('2020-01-01'): + product = self.env['product.product'].create({ + 'name': 'Dummy product', + 'type': 'product', + 'categ_id': self.env.ref('product.product_category_all').id, + 'taxes_id': self.tax_sale_a.ids, + }) + self.pos_config.open_ui() + pos_session = self.pos_config.current_session_id + untax, atax = self.compute_tax(product, 500, 1) + pos_order_data = { + 'data': { + 'amount_paid': untax + atax, + 'amount_return': 0, + 'amount_tax': atax, + 'amount_total': untax + atax, + 'date_order': fields.Datetime.to_string(fields.Datetime.now()), + 'fiscal_position_id': False, + 'lines': [(0, 0, { + 'discount': 0, + 'id': 42, + 'pack_lot_ids': [], + 'price_unit': 500.0, + 'product_id': product.id, + 'price_subtotal': 500.0, + 'price_subtotal_incl': 575.0, + 'qty': 1, + 'tax_ids': [(6, 0, product.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 + } + pos_order_id = self.PosOrder.create_from_ui([pos_order_data])[0]['id'] + pos_order = self.env['pos.order'].browse(pos_order_id) + # End the session. The order has been created without any invoice. + self.pos_config.current_session_id.action_pos_session_closing_control() + return pos_order + def test_order_refund(self): self.pos_config.open_ui() current_session = self.pos_config.current_session_id @@ -1175,55 +1227,9 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): - Reconcile the receivable lines from the created misc entry with the ones from the created payment(s) """ # Create the order on the first of january. - with freeze_time('2020-01-01'): - product = self.env['product.product'].create({ - 'name': 'Dummy product', - 'type': 'product', - 'categ_id': self.env.ref('product.product_category_all').id, - 'taxes_id': self.tax_sale_a.ids, - }) - self.pos_config.open_ui() - pos_session = self.pos_config.current_session_id - untax, atax = self.compute_tax(product, 500, 1) - pos_order_data = { - 'data': { - 'amount_paid': untax + atax, - 'amount_return': 0, - 'amount_tax': atax, - 'amount_total': untax + atax, - 'date_order': fields.Datetime.to_string(fields.Datetime.now()), - 'fiscal_position_id': False, - 'lines': [(0, 0, { - 'discount': 0, - 'id': 42, - 'pack_lot_ids': [], - 'price_unit': 500.0, - 'product_id': product.id, - 'price_subtotal': 500.0, - 'price_subtotal_incl': 575.0, - 'qty': 1, - 'tax_ids': [(6, 0, product.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 - } - pos_order_id = self.PosOrder.create_from_ui([pos_order_data])[0]['id'] - pos_order = self.env['pos.order'].browse(pos_order_id) - # End the session. The order has been created without any invoice. - self.pos_config.current_session_id.action_pos_session_closing_control() - self.assertFalse(pos_order.account_move.exists()) + pos_order = self._create_pos_order_for_postponed_invoicing() + self.assertFalse(pos_order.account_move.exists()) + # Client is back on the 3rd, asks for an invoice. with freeze_time('2020-01-03'): # We set the partner on the order @@ -1259,6 +1265,23 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): statement_receivable_line = statement.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'] and line.name == pos_order.session_id.name) # Because the payment method use the default receivable self.assertEqual(closing_entry_receivable_line.matching_number, statement_receivable_line.matching_number) + def test_sale_order_postponed_invoicing_anglosaxon(self): + """ Test the flow of creating an invoice later, after the POS session has been closed and everything has been processed + in the case of anglo-saxon accounting. + """ + self.env.company.anglo_saxon_accounting = True + self.env.company.point_of_sale_update_stock_quantities = 'closing' + pos_order = self._create_pos_order_for_postponed_invoicing() + + with freeze_time('2020-01-03'): + # We set the partner on the order + pos_order.partner_id = self.partner1.id + pos_order.action_pos_order_invoice() + + picking_ids = pos_order.session_id.picking_ids + # only one product is leaving stock + self.assertEqual(sum(picking_ids.move_line_ids.mapped('quantity')), 1) + def test_order_pos_tax_same_as_company(self): """Test that when the default_pos_receivable_account and the partner account_receivable are the same, payment are correctly reconciled and the invoice is correctly marked as paid.