diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 7071d9f1b00..21238bfe978 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -1018,7 +1018,7 @@ class PurchaseOrderLine(models.Model): # compute qty_invoiced qty = 0.0 for inv_line in line._get_invoice_lines(): - if inv_line.move_id.state not in ['cancel']: + if inv_line.move_id.state not in ['cancel'] or inv_line.move_id.payment_state == 'invoicing_legacy': if inv_line.move_id.move_type == 'in_invoice': qty += inv_line.product_uom_id._compute_quantity(inv_line.quantity, line.product_uom) elif inv_line.move_id.move_type == 'in_refund': diff --git a/addons/purchase/tests/test_purchase_invoice.py b/addons/purchase/tests/test_purchase_invoice.py index f9ddac9b0b5..c28901d111a 100644 --- a/addons/purchase/tests/test_purchase_invoice.py +++ b/addons/purchase/tests/test_purchase_invoice.py @@ -3,6 +3,7 @@ from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.tests import tagged from odoo.tests.common import Form +from odoo import Command, fields @tagged('post_install', '-at_install') @@ -413,3 +414,46 @@ class TestPurchaseToInvoice(AccountTestInvoicingCommon): ] for line in invoice.invoice_line_ids.sorted('sequence'): self.assertEqual(line.purchase_order_id, expected_purchase.pop(0)) + + def test_partial_billing_interaction_with_invoicing_switch_threshold(self): + """ Let's say you create a partial bill 'B' for a given PO. Now if you change the + 'Invoicing Switch Threshold' such that the bill date of 'B' is before the new threshold, + the PO should still take bill 'B' into account. + """ + if not self.env['ir.module.module'].search([('name', '=', 'account_accountant'), ('state', '=', 'installed')]): + self.skipTest("This test requires the installation of the account_account module") + + purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({ + 'partner_id': self.partner_a.id, + 'order_line': [ + Command.create({ + 'name': self.product_deliver.name, + 'product_id': self.product_deliver.id, + 'product_qty': 20.0, + 'product_uom': self.product_deliver.uom_id.id, + 'price_unit': self.product_deliver.list_price, + 'taxes_id': False, + }), + ], + }) + line = purchase_order.order_line[0] + + purchase_order.button_confirm() + line.qty_received = 10 + purchase_order.action_create_invoice() + + invoice = purchase_order.invoice_ids + invoice.invoice_date = invoice.date + invoice.action_post() + + self.assertEqual(line.qty_invoiced, 10) + + self.env['res.config.settings'].create({ + 'invoicing_switch_threshold': fields.Date.add(invoice.invoice_date, days=30), + }).execute() + + invoice.invalidate_model(fnames=['payment_state']) + + self.assertEqual(line.qty_invoiced, 10) + line.qty_received = 15 + self.assertEqual(line.qty_invoiced, 10) diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py index 7098a532b00..48f7c46640e 100644 --- a/addons/sale/models/sale_order_line.py +++ b/addons/sale/models/sale_order_line.py @@ -704,7 +704,7 @@ class SaleOrderLine(models.Model): for line in self: qty_invoiced = 0.0 for invoice_line in line._get_invoice_lines(): - if invoice_line.move_id.state != 'cancel': + if invoice_line.move_id.state != 'cancel' or invoice_line.move_id.payment_state == 'invoicing_legacy': if invoice_line.move_id.move_type == 'out_invoice': qty_invoiced += invoice_line.product_uom_id._compute_quantity(invoice_line.quantity, line.product_uom) elif invoice_line.move_id.move_type == 'out_refund': diff --git a/addons/sale/tests/test_sale_to_invoice.py b/addons/sale/tests/test_sale_to_invoice.py index 0c3caaa4085..7072ef85587 100644 --- a/addons/sale/tests/test_sale_to_invoice.py +++ b/addons/sale/tests/test_sale_to_invoice.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import fields from odoo.fields import Command from odoo.tests import Form, tagged from odoo.tools import float_is_zero @@ -706,3 +707,41 @@ class TestSaleToInvoice(TestSaleCommon): inv.company_id, self.company_data['company'], 'invoices should be created in the company of the SO, not the main company of the context') + + def test_partial_invoicing_interaction_with_invoicing_switch_threshold(self): + """ Let's say you partially invoice a SO, let's call the resuling invoice 'A'. Now if you change the + 'Invoicing Switch Threshold' such that the invoice date of 'A' is before the new threshold, + the SO should still take invoice 'A' into account. + """ + if not self.env['ir.module.module'].search([('name', '=', 'account_accountant'), ('state', '=', 'installed')]): + self.skipTest("This test requires the installation of the account_account module") + + sale_order = self.env['sale.order'].create({ + 'partner_id': self.partner_a.id, + 'order_line': [ + Command.create({ + 'product_id': self.company_data['product_delivery_no'].id, + 'product_uom_qty': 20, + }), + ], + }) + line = sale_order.order_line[0] + + sale_order.action_confirm() + + line.qty_delivered = 10 + + invoice = sale_order._create_invoices() + invoice.action_post() + + self.assertEqual(line.qty_invoiced, 10) + + self.env['res.config.settings'].create({ + 'invoicing_switch_threshold': fields.Date.add(invoice.invoice_date, days=30), + }).execute() + + invoice.invalidate_model(fnames=['payment_state']) + + self.assertEqual(line.qty_invoiced, 10) + line.qty_delivered = 15 + self.assertEqual(line.qty_invoiced, 10)