diff --git a/addons/sale_payment/models/__init__.py b/addons/sale_payment/models/__init__.py index 51af63e66bb..ec29df54d63 100644 --- a/addons/sale_payment/models/__init__.py +++ b/addons/sale_payment/models/__init__.py @@ -2,4 +2,5 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import payment -from . import sale_order \ No newline at end of file +from . import sale_order +from . import account_invoice diff --git a/addons/sale_payment/models/account_invoice.py b/addons/sale_payment/models/account_invoice.py new file mode 100644 index 00000000000..2cbd4757024 --- /dev/null +++ b/addons/sale_payment/models/account_invoice.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from odoo import api, models + + +class AccountInvoice(models.Model): + _inherit = 'account.invoice' + + @api.multi + def action_invoice_open(self): + # OVERRIDE + # Auto-reconcile the invoice with payments coming from transactions. + # It's useful when you have a "paid" sale order (using a payment transaction) and you invoice it later. + res = super(AccountInvoice, self).action_invoice_open() + + if not self: + return res + + for invoice in self: + payments = invoice.mapped('transaction_ids.payment_id') + move_lines = payments.mapped('move_line_ids').filtered(lambda line: not line.reconciled and line.credit > 0.0) + for line in move_lines: + invoice.assign_outstanding_credit(line.id) + return res diff --git a/addons/sale_payment/tests/__init__.py b/addons/sale_payment/tests/__init__.py new file mode 100644 index 00000000000..2b187fe50a8 --- /dev/null +++ b/addons/sale_payment/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_sale_transaction diff --git a/addons/sale_payment/tests/test_sale_transaction.py b/addons/sale_payment/tests/test_sale_transaction.py new file mode 100644 index 00000000000..61184815168 --- /dev/null +++ b/addons/sale_payment/tests/test_sale_transaction.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from odoo import tests +from odoo.addons.account.tests.account_test_classes import AccountingTestCase + + +@tests.tagged('post_install', '-at_install') +class TestSaleTransaction(AccountingTestCase): + def test_sale_invoicing_from_transaction(self): + ''' Test the following scenario: + - Create a sale order + - Create a transaction for the sale order. + - Confirm the transaction but no invoice generated automatically. + - Create manually an invoice for this sale order. + => The invoice must be paid. + ''' + product = self.env['product.product'].create({ + 'name': 'Product A', + }) + + order = self.env['sale.order'].create({ + 'partner_id': self.env.ref('base.res_partner_1').id, + 'order_line': [ + (0, False, { + 'product_id': product.id, + 'name': '1 Product', + 'price_unit': 100.0, + }), + ], + }) + + transaction = order._create_payment_transaction({ + 'acquirer_id': self.env.ref('payment.payment_acquirer_transfer').id, + }) + transaction._set_transaction_done() + + # Assert a posted payment has been generated at this point. + self.assertTrue(transaction.payment_id) + self.assertEqual(transaction.payment_id.state, 'posted') + + invoice_ids = order.action_invoice_create() + invoice = self.env['account.invoice'].browse(invoice_ids) + invoice.action_invoice_open() + + self.assertEqual(invoice.state, 'paid')