diff --git a/addons/sale/models/account_move.py b/addons/sale/models/account_move.py index ca040c8c92f..48faa9306b1 100644 --- a/addons/sale/models/account_move.py +++ b/addons/sale/models/account_move.py @@ -59,20 +59,21 @@ class AccountMove(models.Model): return super()._reverse_moves(default_values_list=default_values_list, cancel=cancel) def action_post(self): - #inherit of the function from account.move to validate a new tax and the priceunit of a downpayment + # inherit of the function from account.move to validate a new tax and the priceunit of a downpayment res = super(AccountMove, self).action_post() - down_payment_lines = self.line_ids.filtered('is_downpayment') - for line in down_payment_lines: - if not any(sol.display_type for sol in line.sale_line_ids): - line.sale_line_ids._compute_name() - if any(order.locked for order in line.sale_line_ids.order_id): - # We cannot change lines content on locked SO, changes on invoices are not - # forwarded to the SO if the SO is locked. - continue + # We cannot change lines content on locked SO, changes on invoices are not forwarded to the SO if the SO is locked + downpayment_lines = self.line_ids.sale_line_ids.filtered(lambda l: l.is_downpayment and not l.display_type and not l.order_id.locked) + other_so_lines = downpayment_lines.order_id.order_line - downpayment_lines + real_invoices = set(other_so_lines.invoice_lines.move_id) + for so_dpl in downpayment_lines: + so_dpl.price_unit = sum( + l.price_unit if l.move_id.move_type == 'out_invoice' else -l.price_unit + for l in so_dpl.invoice_lines + if l.move_id.state == 'posted' and l.move_id not in real_invoices # don't recompute with the final invoice + ) + so_dpl.tax_id = so_dpl.invoice_lines.tax_ids - line.sale_line_ids.tax_id = line.tax_ids - line.sale_line_ids.price_unit = line.price_unit return res def button_draft(self): diff --git a/addons/sale/tests/test_sale_to_invoice.py b/addons/sale/tests/test_sale_to_invoice.py index b1cf0b04ee2..bc881ad2da1 100644 --- a/addons/sale/tests/test_sale_to_invoice.py +++ b/addons/sale/tests/test_sale_to_invoice.py @@ -186,16 +186,17 @@ class TestSaleToInvoice(TestSaleCommon): 'deposit_account_id': self.company_data['default_account_revenue'].id }) downpayment.create_invoices() - # Let's do the invoice + # Let's do the invoice for the remaining amount payment = self.env['sale.advance.payment.inv'].with_context(context).create({ 'deposit_account_id': self.company_data['default_account_revenue'].id }) payment.create_invoices() - # Confirm all invoices - for invoice in sale_order.invoice_ids: - invoice.action_post() + downpayment_line = sale_order.order_line.filtered(lambda l: l.is_downpayment and not l.display_type) self.assertEqual(downpayment_line[0].price_unit, 50, 'The down payment unit price should not change on SO') + # Confirm all invoices + sale_order.invoice_ids.action_post() + self.assertEqual(downpayment_line[0].price_unit, 50, 'The down payment unit price should not change on SO') def test_downpayment_fixed_amount_with_zero_total_amount(self): # Create the SO with one line and amount total is zero @@ -261,6 +262,46 @@ class TestSaleToInvoice(TestSaleCommon): invoice.action_post() self.assertEqual(downpayment_line.price_unit, self.sale_order.amount_total/2, 'downpayment should have the correct amount') + def test_downpayment_invoice_and_partial_credit_note(self): + """This test check that the downpayment line amount on the sale order remains consistent""" + self.sale_order.action_confirm() + + # Create an invoice for a Down payment of 100 + payment = self.env['sale.advance.payment.inv'].with_context(self.context).create({ + 'advance_payment_method': 'fixed', + 'fixed_amount': 100, + 'deposit_account_id': self.company_data['default_account_revenue'].id, + }) + payment.create_invoices() + + # Ensure the downpayment line on the sale order is correctly set to 100 + downpayment_line = self.sale_order.order_line.filtered(lambda l: l.is_downpayment and not l.display_type) + self.assertEqual(downpayment_line.price_unit, 100) + + # post the downpayment invoice and ensure the downpayment_line amount is still 100 + downpayment_invoice = downpayment_line.order_id.order_line.invoice_lines.move_id + downpayment_invoice.action_post() + self.assertEqual(downpayment_line.price_unit, 100) + + # Create a credit note for a part of the downpayment invoice and post it + move_reversal = self.env['account.move.reversal'].with_context( + active_model="account.move", + active_ids=downpayment_invoice.ids, + ).create({ + 'date': '2020-02-01', + 'reason': 'no reason', + 'journal_id': downpayment_invoice.journal_id.id, + }) + reversal_action = move_reversal.reverse_moves() + reverse_move = self.env['account.move'].browse(reversal_action['res_id']) + with Form(reverse_move) as form_reverse: + with form_reverse.invoice_line_ids.edit(0) as line_form: + line_form.price_unit = 20.0 + reverse_move.action_post() + + self.assertEqual(downpayment_line.price_unit, 80, + "The downpayment line amount should be equal to the sum of the invoice and credit note amount") + def test_invoice_with_discount(self): """ Test invoice with a discount and check discount applied on both SO lines and an invoice lines """ # Update discount and delivered quantity on SO lines