diff --git a/modules/account_invoice/invoice.py b/modules/account_invoice/invoice.py index 8d4488452c..7741e9a7ca 100644 --- a/modules/account_invoice/invoice.py +++ b/modules/account_invoice/invoice.py @@ -425,6 +425,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin): ('validated', 'cancelled'), ('posted', 'cancelled'), ('cancelled', 'draft'), + ('cancelled', 'posted'), )) cls._buttons.update({ 'cancel': { @@ -1285,9 +1286,9 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin): grouped_invoices = sorted(grouped_invoices, key=invoice_date) for invoice in grouped_invoices: - # Posted and paid invoices are tested by check_modify so we can - # not modify tax_identifier nor number - if invoice.state in {'posted', 'paid'}: + # Posted, paid and cancelled invoices are tested by + # check_modify so we can not modify tax_identifier nor number + if invoice.state in {'posted', 'paid', 'cancelled'}: continue if not invoice.tax_identifier: invoice.tax_identifier = invoice.get_tax_identifier() @@ -2011,15 +2012,23 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin): @classmethod @ModelView.button def process(cls, invoices): + to_save = [] paid = [] posted = [] for invoice in invoices: - if invoice.state not in ('posted', 'paid'): - continue - if invoice.reconciled: - paid.append(invoice) - else: - posted.append(invoice) + if invoice.state in {'posted', 'paid'}: + if invoice.reconciled: + paid.append(invoice) + else: + posted.append(invoice) + elif invoice.state == 'cancelled' and invoice.move: + if not invoice.reconciled: + if invoice.cancel_move: + invoice.cancel_move = None + invoice.save() + to_save.append(invoice) + posted.append(invoice) + cls.save(to_save) cls.paid(paid) cls._post(posted) diff --git a/modules/account_invoice/tests/scenario_invoice.rst b/modules/account_invoice/tests/scenario_invoice.rst index dd880c5208..795c56d051 100644 --- a/modules/account_invoice/tests/scenario_invoice.rst +++ b/modules/account_invoice/tests/scenario_invoice.rst @@ -243,6 +243,8 @@ Credit invoice with refund:: >>> invoice.reload() >>> invoice.state 'cancelled' + >>> bool(invoice.cancel_move) + True >>> bool(invoice.reconciled) True >>> credit_note, = Invoice.find([ @@ -283,6 +285,15 @@ Credit invoice with refund:: ... credit_note_tax_code.amount Decimal('20.00') +Unreconcile cancelled invoice:: + + >>> unreconcile_lines = Wizard( + ... 'account.move.unreconcile_lines', invoice.move.lines) + >>> invoice.reload() + >>> invoice.state + 'posted' + >>> invoice.cancel_move + Pay invoice:: >>> invoice, = invoice.duplicate()