diff --git a/account_document_reversal/__manifest__.py b/account_document_reversal/__manifest__.py index 981034acb..df73249ac 100644 --- a/account_document_reversal/__manifest__.py +++ b/account_document_reversal/__manifest__.py @@ -2,22 +2,17 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) { - 'name': 'Account Document Reversal', - 'summary': 'Create reversed journal entries when cancel document', - 'version': '12.0.1.0.1', - 'author': 'Ecosoft,' - 'Eficent,' - 'Odoo Community Association (OCA)', - 'website': 'https://github.com/OCA/account-financial-tools', - 'category': 'Accounting & Finance', - 'depends': ['account_cancel'], - 'data': [ - 'wizard/reverse_account_document_wizard.xml', - 'views/account_view.xml', - ], - 'license': 'AGPL-3', - 'installable': True, - 'application': False, - 'development_status': 'Beta', - 'maintainers': ['kittiu'], + "name": "Account Document Reversal", + "summary": "Create reversed journal entries when cancel document", + "version": "12.0.1.0.1", + "author": "Ecosoft," "Eficent," "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/account-financial-tools", + "category": "Accounting & Finance", + "depends": ["account_cancel"], + "data": ["wizard/reverse_account_document_wizard.xml", "views/account_view.xml",], + "license": "AGPL-3", + "installable": True, + "application": False, + "development_status": "Beta", + "maintainers": ["kittiu"], } diff --git a/account_document_reversal/models/account.py b/account_document_reversal/models/account.py index 59c0b0a48..b7afc1094 100644 --- a/account_document_reversal/models/account.py +++ b/account_document_reversal/models/account.py @@ -1,35 +1,38 @@ # Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) -from odoo import models, fields, api +from odoo import api, fields, models class AccountJournal(models.Model): - _inherit = 'account.journal' + _inherit = "account.journal" cancel_method = fields.Selection( - [('normal', 'Normal (delete journal entries if exists)'), - ('reversal', 'Reversal (create reversed journal entries)')], - string='Cancel Method', - default='normal', + [ + ("normal", "Normal (delete journal entries if exists)"), + ("reversal", "Reversal (create reversed journal entries)"), + ], + string="Cancel Method", + default="normal", required=True, ) is_cancel_reversal = fields.Boolean( - string='Use Cancel Reversal', - compute='_compute_is_cancel_reversal', + string="Use Cancel Reversal", + compute="_compute_is_cancel_reversal", help="True, when journal allow cancel entries with method is reversal", ) use_different_journal = fields.Boolean( - string='Use different journal for reversal', + string="Use different journal for reversal", help="If checked, reversal wizard will show field Reversal Journal", ) reversal_journal_id = fields.Many2one( - 'account.journal', - string='Default Reversal Journal', + "account.journal", + string="Default Reversal Journal", help="Journal in this field will show in reversal wizard as default", ) @api.multi def _compute_is_cancel_reversal(self): for rec in self: - rec.is_cancel_reversal = \ - rec.update_posted and rec.cancel_method == 'reversal' + rec.is_cancel_reversal = ( + rec.update_posted and rec.cancel_method == "reversal" + ) diff --git a/account_document_reversal/models/account_bank_statement.py b/account_document_reversal/models/account_bank_statement.py index caaff3a95..07f2dafbe 100644 --- a/account_document_reversal/models/account_bank_statement.py +++ b/account_document_reversal/models/account_bank_statement.py @@ -1,23 +1,23 @@ # Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) -from odoo import api, models, _ +from odoo import _, api, models from odoo.exceptions import UserError class AccountPayment(models.Model): - _name = 'account.bank.statement.line' - _inherit = ['account.bank.statement.line', 'account.document.reversal'] + _name = "account.bank.statement.line" + _inherit = ["account.bank.statement.line", "account.document.reversal"] @api.multi def button_cancel_reconciliation(self): """ If cancel method is to reverse, use document reversal wizard """ - cancel_reversal = all(self.mapped('journal_entry_ids.move_id.' - 'journal_id.is_cancel_reversal')) - states = self.mapped('statement_id.state') + cancel_reversal = all( + self.mapped("journal_entry_ids.move_id." "journal_id.is_cancel_reversal") + ) + states = self.mapped("statement_id.state") if cancel_reversal: - if not all(st == 'open' for st in states): - raise UserError( - _('Only new bank statement can be cancelled')) + if not all(st == "open" for st in states): + raise UserError(_("Only new bank statement can be cancelled")) return self.reverse_document_wizard() return super().button_cancel_reconciliation() @@ -25,41 +25,42 @@ class AccountPayment(models.Model): def action_document_reversal(self, date=None, journal_id=None): """ Reverse all moves related to this statement + delete payment """ # This part is from button_cancel_reconciliation() - aml_to_unbind = self.env['account.move.line'] - aml_to_cancel = self.env['account.move.line'] - payment_to_unreconcile = self.env['account.payment'] - payment_to_cancel = self.env['account.payment'] + aml_to_unbind = self.env["account.move.line"] + aml_to_cancel = self.env["account.move.line"] + payment_to_unreconcile = self.env["account.payment"] + payment_to_cancel = self.env["account.payment"] for st_line in self: aml_to_unbind |= st_line.journal_entry_ids for line in st_line.journal_entry_ids: payment_to_unreconcile |= line.payment_id - if st_line.move_name and \ - line.payment_id.payment_reference == st_line.move_name: + if ( + st_line.move_name + and line.payment_id.payment_reference == st_line.move_name + ): # there can be several moves linked to a statement line but # maximum one created by the line itself aml_to_cancel |= line payment_to_cancel |= line.payment_id aml_to_unbind = aml_to_unbind - aml_to_cancel if aml_to_unbind: - aml_to_unbind.write({'statement_line_id': False}) + aml_to_unbind.write({"statement_line_id": False}) payment_to_unreconcile = payment_to_unreconcile - payment_to_cancel if payment_to_unreconcile: payment_to_unreconcile.unreconcile() # -- # Set all moves to unreconciled - aml_to_cancel.filtered(lambda x: - x.account_id.reconcile).remove_move_reconcile() - moves = aml_to_cancel.mapped('move_id') + aml_to_cancel.filtered(lambda x: x.account_id.reconcile).remove_move_reconcile() + moves = aml_to_cancel.mapped("move_id") # Important to remove relation with move.line before reverse - aml_to_cancel.write({'payment_id': False, - 'statement_id': False, - 'statement_line_id': False}) + aml_to_cancel.write( + {"payment_id": False, "statement_id": False, "statement_line_id": False} + ) # Create reverse entries moves.reverse_moves(date, journal_id) # Delete related payments if payment_to_cancel: payment_to_cancel.unlink() # Unlink from statement line - self.write({'move_name': False}) + self.write({"move_name": False}) return True diff --git a/account_document_reversal/models/account_document_reversal.py b/account_document_reversal/models/account_document_reversal.py index 4e80c7423..44507cb39 100644 --- a/account_document_reversal/models/account_document_reversal.py +++ b/account_document_reversal/models/account_document_reversal.py @@ -1,17 +1,18 @@ # Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) -from odoo import models, api +from odoo import api, models class AccountDocumentReversal(models.AbstractModel): - _name = 'account.document.reversal' - _description = 'Abstract Module for Document Reversal' + _name = "account.document.reversal" + _description = "Abstract Module for Document Reversal" @api.model def reverse_document_wizard(self): """ Return Wizard to Cancel Document """ - action = self.env.ref('account_document_reversal.' - 'action_view_reverse_account_document') + action = self.env.ref( + "account_document_reversal." "action_view_reverse_account_document" + ) vals = action.read()[0] return vals diff --git a/account_document_reversal/models/account_invoice.py b/account_document_reversal/models/account_invoice.py index 6e7e96341..36c1c2f62 100644 --- a/account_document_reversal/models/account_invoice.py +++ b/account_document_reversal/models/account_invoice.py @@ -1,12 +1,12 @@ # Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) -from odoo import api, models, _ -from odoo.exceptions import ValidationError, UserError +from odoo import _, api, models +from odoo.exceptions import UserError, ValidationError class AccountInvoice(models.Model): - _name = 'account.invoice' - _inherit = ['account.invoice', 'account.document.reversal'] + _name = "account.invoice" + _inherit = ["account.invoice", "account.document.reversal"] @api.multi def action_invoice_cancel(self): @@ -14,16 +14,20 @@ class AccountInvoice(models.Model): * Draft invoice, fall back to standard invoice cancel * Non draft, must be fully open (not even partial reconciled) to cancel """ - cancel_reversal = all(self.mapped('journal_id.is_cancel_reversal')) - states = self.mapped('state') - if cancel_reversal and 'draft' not in states: - if not all(st == 'open' for st in states) or \ - (self.mapped('move_id.line_ids.matched_debit_ids') | - self.mapped('move_id.line_ids.matched_credit_ids')): + cancel_reversal = all(self.mapped("journal_id.is_cancel_reversal")) + states = self.mapped("state") + if cancel_reversal and "draft" not in states: + if not all(st == "open" for st in states) or ( + self.mapped("move_id.line_ids.matched_debit_ids") + | self.mapped("move_id.line_ids.matched_credit_ids") + ): raise UserError( - _('Only fully unpaid invoice can be cancelled.\n' - 'To cancel this invoice, make sure all payment(s) ' - 'are also cancelled.')) + _( + "Only fully unpaid invoice can be cancelled.\n" + "To cancel this invoice, make sure all payment(s) " + "are also cancelled." + ) + ) return self.reverse_document_wizard() return super().action_invoice_cancel() @@ -31,22 +35,24 @@ class AccountInvoice(models.Model): def action_document_reversal(self, date=None, journal_id=None): """ Reverse all moves related to this invoice + set state to cancel """ # Check document state - if 'cancel' in self.mapped('state'): - raise ValidationError( - _('You are trying to cancel the cancelled document')) - MoveLine = self.env['account.move.line'] - move_lines = MoveLine.search([('invoice_id', 'in', self.ids)]) - moves = move_lines.mapped('move_id') + if "cancel" in self.mapped("state"): + raise ValidationError(_("You are trying to cancel the cancelled document")) + MoveLine = self.env["account.move.line"] + move_lines = MoveLine.search([("invoice_id", "in", self.ids)]) + moves = move_lines.mapped("move_id") # Set all moves to unreconciled - move_lines.filtered(lambda x: - x.account_id.reconcile).remove_move_reconcile() + move_lines.filtered(lambda x: x.account_id.reconcile).remove_move_reconcile() # Important to remove relation with move.line before reverse - move_lines.write({'invoice_id': False}) + move_lines.write({"invoice_id": False}) # Create reverse entries moves.reverse_moves(date, journal_id) # Set state cancelled and unlink with account.move - self.write({'move_id': False, - 'move_name': False, - 'reference': False, - 'state': 'cancel'}) + self.write( + { + "move_id": False, + "move_name": False, + "reference": False, + "state": "cancel", + } + ) return True diff --git a/account_document_reversal/models/account_move.py b/account_document_reversal/models/account_move.py index 29fee46e8..8eb1732df 100644 --- a/account_document_reversal/models/account_move.py +++ b/account_document_reversal/models/account_move.py @@ -1,24 +1,26 @@ # Copyright 2019 Eficent Business and IT Consulting Services, S.L. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) -from odoo import api, models, fields, _ +from odoo import _, api, fields, models from odoo.exceptions import ValidationError class AccountMove(models.Model): - _name = 'account.move' - _inherit = ['account.move', 'account.document.reversal'] + _name = "account.move" + _inherit = ["account.move", "account.document.reversal"] is_cancel_reversal = fields.Boolean( - string='Use Cancel Reversal', - related='journal_id.is_cancel_reversal', + string="Use Cancel Reversal", related="journal_id.is_cancel_reversal", ) @api.multi def button_cancel(self): """ Do not allow using this button for cancel with reversal """ - cancel_reversal = any(self.mapped('is_cancel_reversal')) + cancel_reversal = any(self.mapped("is_cancel_reversal")) if cancel_reversal: raise ValidationError( - _('This action is not allowed for cancel with reversal.\n' - 'Please use Reverse Entry.')) + _( + "This action is not allowed for cancel with reversal.\n" + "Please use Reverse Entry." + ) + ) return super().button_cancel() diff --git a/account_document_reversal/models/account_payment.py b/account_document_reversal/models/account_payment.py index bc8d86a45..c9adde6de 100644 --- a/account_document_reversal/models/account_payment.py +++ b/account_document_reversal/models/account_payment.py @@ -1,20 +1,21 @@ # Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) -from odoo import api, models, _ +from odoo import _, api, models from odoo.exceptions import ValidationError class AccountPayment(models.Model): - _name = 'account.payment' - _inherit = ['account.payment', 'account.document.reversal'] + _name = "account.payment" + _inherit = ["account.payment", "account.document.reversal"] @api.multi def cancel(self): """ If cancel method is to reverse, use document reversal wizard """ cancel_reversal = all( - self.mapped('move_line_ids.move_id.journal_id.is_cancel_reversal')) - states = self.mapped('state') - if cancel_reversal and 'draft' not in states: + self.mapped("move_line_ids.move_id.journal_id.is_cancel_reversal") + ) + states = self.mapped("state") + if cancel_reversal and "draft" not in states: return self.reverse_document_wizard() return super().cancel() @@ -22,19 +23,16 @@ class AccountPayment(models.Model): def action_document_reversal(self, date=None, journal_id=None): """ Reverse all moves related to this payment + set state to cancel """ # Check document state - if 'cancelled' in self.mapped('state'): - raise ValidationError( - _('You are trying to cancel the cancelled document')) - move_lines = self.mapped('move_line_ids') - moves = move_lines.mapped('move_id') + if "cancelled" in self.mapped("state"): + raise ValidationError(_("You are trying to cancel the cancelled document")) + move_lines = self.mapped("move_line_ids") + moves = move_lines.mapped("move_id") # Set all moves to unreconciled - move_lines.filtered(lambda x: - x.account_id.reconcile).remove_move_reconcile() + move_lines.filtered(lambda x: x.account_id.reconcile).remove_move_reconcile() # Important to remove relation with move.line before reverse - move_lines.write({'payment_id': False}) + move_lines.write({"payment_id": False}) # Create reverse entries moves.reverse_moves(date, journal_id) # Set state cancelled and unlink with account.move - self.write({'move_name': False, - 'state': 'cancelled'}) + self.write({"move_name": False, "state": "cancelled"}) return True diff --git a/account_document_reversal/tests/test_invoice_reversal.py b/account_document_reversal/tests/test_invoice_reversal.py index 16fd181d4..25ee36627 100644 --- a/account_document_reversal/tests/test_invoice_reversal.py +++ b/account_document_reversal/tests/test_invoice_reversal.py @@ -1,50 +1,54 @@ # Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) -from odoo.tests.common import SavepointCase, Form +from odoo.tests.common import Form, SavepointCase class TestInvoiceReversal(SavepointCase): - @classmethod def setUpClass(cls): super(TestInvoiceReversal, cls).setUpClass() - cls.partner = cls.env['res.partner'].create({'name': 'Test'}) - cls.account_type_receivable = cls.env['account.account.type'].create({ - 'name': 'Test Receivable', - 'type': 'receivable', - }) - cls.account_type_regular = cls.env['account.account.type'].create({ - 'name': 'Test Regular', - 'type': 'other', - }) - cls.account_receivable = cls.env['account.account'].create({ - 'name': 'Test Receivable', - 'code': 'TEST_AR', - 'user_type_id': cls.account_type_receivable.id, - 'reconcile': True, - }) - cls.account_income = cls.env['account.account'].create({ - 'name': 'Test Income', - 'code': 'TEST_IN', - 'user_type_id': cls.account_type_regular.id, - 'reconcile': False, - }) - cls.sale_journal = cls.env['account.journal'].\ - search([('type', '=', 'sale')])[0] - cls.invoice = cls.env['account.invoice'].create({ - 'name': "Test Customer Invoice", - 'journal_id': cls.sale_journal.id, - 'partner_id': cls.partner.id, - 'account_id': cls.account_receivable.id, - }) - cls.invoice_line = cls.env['account.invoice.line'] - cls.invoice_line1 = cls.invoice_line.create({ - 'invoice_id': cls.invoice.id, - 'name': 'Line 1', - 'price_unit': 200.0, - 'account_id': cls.account_income.id, - 'quantity': 1, - }) + cls.partner = cls.env["res.partner"].create({"name": "Test"}) + cls.account_type_receivable = cls.env["account.account.type"].create( + {"name": "Test Receivable", "type": "receivable",} + ) + cls.account_type_regular = cls.env["account.account.type"].create( + {"name": "Test Regular", "type": "other",} + ) + cls.account_receivable = cls.env["account.account"].create( + { + "name": "Test Receivable", + "code": "TEST_AR", + "user_type_id": cls.account_type_receivable.id, + "reconcile": True, + } + ) + cls.account_income = cls.env["account.account"].create( + { + "name": "Test Income", + "code": "TEST_IN", + "user_type_id": cls.account_type_regular.id, + "reconcile": False, + } + ) + cls.sale_journal = cls.env["account.journal"].search([("type", "=", "sale")])[0] + cls.invoice = cls.env["account.invoice"].create( + { + "name": "Test Customer Invoice", + "journal_id": cls.sale_journal.id, + "partner_id": cls.partner.id, + "account_id": cls.account_receivable.id, + } + ) + cls.invoice_line = cls.env["account.invoice.line"] + cls.invoice_line1 = cls.invoice_line.create( + { + "invoice_id": cls.invoice.id, + "name": "Line 1", + "price_unit": 200.0, + "account_id": cls.account_income.id, + "quantity": 1, + } + ) def test_journal_invoice_cancel_reversal(self): """ Tests cancel with reversal, end result must follow, @@ -52,30 +56,35 @@ class TestInvoiceReversal(SavepointCase): - Status is changed to cancel """ # Test journal - self.sale_journal.write({'update_posted': True, - 'cancel_method': 'normal', }) + self.sale_journal.write( + {"update_posted": True, "cancel_method": "normal",} + ) self.assertFalse(self.sale_journal.is_cancel_reversal) - self.sale_journal.write({'update_posted': True, - 'cancel_method': 'reversal', - 'use_different_journal': True, }) + self.sale_journal.write( + { + "update_posted": True, + "cancel_method": "reversal", + "use_different_journal": True, + } + ) # Open invoice self.invoice.action_invoice_open() move = self.invoice.move_id # Click Cancel will open reverse document wizard res = self.invoice.action_invoice_cancel() - self.assertEqual(res['res_model'], 'reverse.account.document') + self.assertEqual(res["res_model"], "reverse.account.document") # Cancel invoice - ctx = {'active_model': 'account.invoice', - 'active_ids': [self.invoice.id]} - f = Form(self.env[res['res_model']].with_context(ctx)) + ctx = {"active_model": "account.invoice", "active_ids": [self.invoice.id]} + f = Form(self.env[res["res_model"]].with_context(ctx)) cancel_wizard = f.save() cancel_wizard.action_cancel() reversed_move = move.reverse_entry_id - move_reconcile = move.mapped('line_ids').mapped('full_reconcile_id') - reversed_move_reconcile = \ - reversed_move.mapped('line_ids').mapped('full_reconcile_id') + move_reconcile = move.mapped("line_ids").mapped("full_reconcile_id") + reversed_move_reconcile = reversed_move.mapped("line_ids").mapped( + "full_reconcile_id" + ) # Check self.assertTrue(move_reconcile) self.assertTrue(reversed_move_reconcile) self.assertEqual(move_reconcile, reversed_move_reconcile) - self.assertEqual(self.invoice.state, 'cancel') + self.assertEqual(self.invoice.state, "cancel") diff --git a/account_document_reversal/tests/test_payment_reversal.py b/account_document_reversal/tests/test_payment_reversal.py index f37dd9bba..cc2d42edb 100644 --- a/account_document_reversal/tests/test_payment_reversal.py +++ b/account_document_reversal/tests/test_payment_reversal.py @@ -1,83 +1,89 @@ # Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) import time -from odoo.tests.common import SavepointCase, Form + from odoo.exceptions import UserError +from odoo.tests.common import Form, SavepointCase class TestPaymentReversal(SavepointCase): - @classmethod def setUpClass(cls): super(TestPaymentReversal, cls).setUpClass() # Models - cls.acc_bank_stmt_model = cls.env['account.bank.statement'] - cls.acc_bank_stmt_line_model = cls.env['account.bank.statement.line'] - cls.partner = cls.env['res.partner'].create({'name': 'Test'}) - cls.account_account_type_model = cls.env['account.account.type'] - cls.account_account_model = cls.env['account.account'] - cls.account_journal_model = cls.env['account.journal'] - cls.account_invoice_model = cls.env['account.invoice'] - cls.account_move_line_model = cls.env['account.move.line'] - cls.invoice_line_model = cls.env['account.invoice.line'] + cls.acc_bank_stmt_model = cls.env["account.bank.statement"] + cls.acc_bank_stmt_line_model = cls.env["account.bank.statement.line"] + cls.partner = cls.env["res.partner"].create({"name": "Test"}) + cls.account_account_type_model = cls.env["account.account.type"] + cls.account_account_model = cls.env["account.account"] + cls.account_journal_model = cls.env["account.journal"] + cls.account_invoice_model = cls.env["account.invoice"] + cls.account_move_line_model = cls.env["account.move.line"] + cls.invoice_line_model = cls.env["account.invoice.line"] # Records - cls.account_type_bank = cls.account_account_type_model.create({ - 'name': 'Test Bank', - 'type': 'liquidity', - }) - cls.account_type_receivable = cls.account_account_type_model.create({ - 'name': 'Test Receivable', - 'type': 'receivable', - }) - cls.account_type_regular = cls.account_account_type_model.create({ - 'name': 'Test Regular', - 'type': 'other', - }) - cls.account_bank = cls.account_account_model.create({ - 'name': 'Test Bank', - 'code': 'TEST_BANK', - 'user_type_id': cls.account_type_bank.id, - 'reconcile': False, - }) - cls.account_receivable = cls.account_account_model.create({ - 'name': 'Test Receivable', - 'code': 'TEST_AR', - 'user_type_id': cls.account_type_receivable.id, - 'reconcile': True, - }) - cls.account_income = cls.account_account_model.create({ - 'name': 'Test Income', - 'code': 'TEST_IN', - 'user_type_id': cls.account_type_regular.id, - 'reconcile': False, - }) - cls.account_expense = cls.account_account_model.create({ - 'name': 'Test Expense', - 'code': 'TEST_EX', - 'user_type_id': cls.account_type_regular.id, - 'reconcile': False, - }) - cls.bank_journal = cls.account_journal_model.create({ - 'name': 'Test Bank', - 'code': 'TBK', - 'type': 'bank' - }) - cls.sale_journal = cls.account_journal_model.\ - search([('type', '=', 'sale')])[0] - cls.invoice = cls.account_invoice_model.create({ - 'name': "Test Customer Invoice", - 'journal_id': cls.sale_journal.id, - 'partner_id': cls.partner.id, - 'account_id': cls.account_receivable.id, - }) + cls.account_type_bank = cls.account_account_type_model.create( + {"name": "Test Bank", "type": "liquidity",} + ) + cls.account_type_receivable = cls.account_account_type_model.create( + {"name": "Test Receivable", "type": "receivable",} + ) + cls.account_type_regular = cls.account_account_type_model.create( + {"name": "Test Regular", "type": "other",} + ) + cls.account_bank = cls.account_account_model.create( + { + "name": "Test Bank", + "code": "TEST_BANK", + "user_type_id": cls.account_type_bank.id, + "reconcile": False, + } + ) + cls.account_receivable = cls.account_account_model.create( + { + "name": "Test Receivable", + "code": "TEST_AR", + "user_type_id": cls.account_type_receivable.id, + "reconcile": True, + } + ) + cls.account_income = cls.account_account_model.create( + { + "name": "Test Income", + "code": "TEST_IN", + "user_type_id": cls.account_type_regular.id, + "reconcile": False, + } + ) + cls.account_expense = cls.account_account_model.create( + { + "name": "Test Expense", + "code": "TEST_EX", + "user_type_id": cls.account_type_regular.id, + "reconcile": False, + } + ) + cls.bank_journal = cls.account_journal_model.create( + {"name": "Test Bank", "code": "TBK", "type": "bank"} + ) + cls.sale_journal = cls.account_journal_model.search([("type", "=", "sale")])[0] + cls.invoice = cls.account_invoice_model.create( + { + "name": "Test Customer Invoice", + "journal_id": cls.sale_journal.id, + "partner_id": cls.partner.id, + "account_id": cls.account_receivable.id, + } + ) - cls.invoice_line1 = cls.invoice_line_model.create({ - 'invoice_id': cls.invoice.id, - 'name': 'Line 1', - 'price_unit': 200.0, - 'account_id': cls.account_income.id, - 'quantity': 1, - }) + cls.invoice_line1 = cls.invoice_line_model.create( + { + "invoice_id": cls.invoice.id, + "name": "Line 1", + "price_unit": 200.0, + "account_id": cls.account_income.id, + "quantity": 1, + } + ) def test_payment_cancel_normal(self): """ Tests that, if I don't use cancel reversal, @@ -85,16 +91,16 @@ class TestPaymentReversal(SavepointCase): - account move are removed completely """ # Test journal with normal cancel - self.bank_journal.write({'update_posted': True, - 'cancel_method': 'normal'}) + self.bank_journal.write({"update_posted": True, "cancel_method": "normal"}) # Open invoice self.invoice.action_invoice_open() # Pay invoice self.invoice.pay_and_reconcile(self.bank_journal, 200.0) payment = self.invoice.payment_ids[0] payment.cancel() - move_lines = self.env['account.move.line'].\ - search([('payment_id', '=', payment.id)]) + move_lines = self.env["account.move.line"].search( + [("payment_id", "=", payment.id)] + ) # All account moves are removed completely self.assertFalse(move_lines) @@ -106,36 +112,39 @@ class TestPaymentReversal(SavepointCase): - The invoice is not reconciled with the payment anymore """ # Test journal - self.bank_journal.write({'update_posted': True, - 'cancel_method': 'reversal'}) + self.bank_journal.write({"update_posted": True, "cancel_method": "reversal"}) # Open invoice self.invoice.action_invoice_open() # Pay invoice self.invoice.pay_and_reconcile(self.bank_journal, 200.0) payment = self.invoice.payment_ids[0] - move = self.env['account.move.line'].search( - [('payment_id', '=', payment.id)], limit=1).move_id + move = ( + self.env["account.move.line"] + .search([("payment_id", "=", payment.id)], limit=1) + .move_id + ) res = payment.cancel() # Cancel payment - ctx = {'active_model': 'account.payment', - 'active_ids': [payment.id]} - f = Form(self.env[res['res_model']].with_context(ctx)) - self.assertEqual(res['res_model'], 'reverse.account.document') + ctx = {"active_model": "account.payment", "active_ids": [payment.id]} + f = Form(self.env[res["res_model"]].with_context(ctx)) + self.assertEqual(res["res_model"], "reverse.account.document") cancel_wizard = f.save() cancel_wizard.action_cancel() - payment_moves = self.env['account.move.line'].search( - [('payment_id', '=', payment.id)]) + payment_moves = self.env["account.move.line"].search( + [("payment_id", "=", payment.id)] + ) self.assertFalse(payment_moves) reversed_move = move.reverse_entry_id - move_reconcile = move.mapped('line_ids').mapped('full_reconcile_id') - reversed_move_reconcile = \ - reversed_move.mapped('line_ids').mapped('full_reconcile_id') + move_reconcile = move.mapped("line_ids").mapped("full_reconcile_id") + reversed_move_reconcile = reversed_move.mapped("line_ids").mapped( + "full_reconcile_id" + ) # Check self.assertTrue(move_reconcile) self.assertTrue(reversed_move_reconcile) self.assertEqual(move_reconcile, reversed_move_reconcile) - self.assertEqual(payment.state, 'cancelled') - self.assertEqual(self.invoice.state, 'open') + self.assertEqual(payment.state, "cancelled") + self.assertEqual(self.invoice.state, "open") def test_bank_statement_cancel_normal(self): """ Tests that, if I don't use cancel reversal, @@ -144,41 +153,50 @@ class TestPaymentReversal(SavepointCase): - account move are removed completely """ # Test journal with normal cancel - self.bank_journal.write({'update_posted': True, - 'cancel_method': 'normal'}) + self.bank_journal.write({"update_posted": True, "cancel_method": "normal"}) # Open invoice self.invoice.action_invoice_open() - bank_stmt = self.acc_bank_stmt_model.create({ - 'journal_id': self.bank_journal.id, - 'date': time.strftime('%Y') + '-07-15', - 'name': 'payment' + self.invoice.name - }) + bank_stmt = self.acc_bank_stmt_model.create( + { + "journal_id": self.bank_journal.id, + "date": time.strftime("%Y") + "-07-15", + "name": "payment" + self.invoice.name, + } + ) bank_stmt_line = self.acc_bank_stmt_line_model.create( - {'name': 'payment', - 'statement_id': bank_stmt.id, - 'partner_id': self.partner.id, - 'amount': 200, - 'date': time.strftime('%Y') + '-07-15', }) + { + "name": "payment", + "statement_id": bank_stmt.id, + "partner_id": self.partner.id, + "amount": 200, + "date": time.strftime("%Y") + "-07-15", + } + ) line_id = self.account_move_line_model # reconcile the payment with the invoice for l in self.invoice.move_id.line_ids: if l.account_id.id == self.account_receivable.id: line_id = l break - bank_stmt_line.process_reconciliation(counterpart_aml_dicts=[{ - 'move_line': line_id, - 'account_id': self.account_income.id, - 'debit': 0.0, - 'credit': 200.0, - 'name': 'test_reconciliation', - }]) + bank_stmt_line.process_reconciliation( + counterpart_aml_dicts=[ + { + "move_line": line_id, + "account_id": self.account_income.id, + "debit": 0.0, + "credit": 200.0, + "name": "test_reconciliation", + } + ] + ) self.assertTrue(bank_stmt_line.journal_entry_ids) original_move_lines = bank_stmt_line.journal_entry_ids - self.assertTrue(original_move_lines.mapped('statement_id')) + self.assertTrue(original_move_lines.mapped("statement_id")) # Cancel the statement line bank_stmt_line.button_cancel_reconciliation() - move_lines = self.env['account.move.line'].\ - search([('statement_id', '=', bank_stmt.id)]) + move_lines = self.env["account.move.line"].search( + [("statement_id", "=", bank_stmt.id)] + ) # All account moves are removed completely self.assertFalse(move_lines) @@ -191,58 +209,69 @@ class TestPaymentReversal(SavepointCase): - The line in the statement is ready to reconcile again """ # Test journal - self.bank_journal.write({'update_posted': True, - 'cancel_method': 'reversal'}) + self.bank_journal.write({"update_posted": True, "cancel_method": "reversal"}) # Open invoice self.invoice.action_invoice_open() - bank_stmt = self.acc_bank_stmt_model.create({ - 'journal_id': self.bank_journal.id, - 'date': time.strftime('%Y') + '-07-15', - 'name': 'payment' + self.invoice.name - }) + bank_stmt = self.acc_bank_stmt_model.create( + { + "journal_id": self.bank_journal.id, + "date": time.strftime("%Y") + "-07-15", + "name": "payment" + self.invoice.name, + } + ) bank_stmt_line = self.acc_bank_stmt_line_model.create( - {'name': 'payment', - 'statement_id': bank_stmt.id, - 'partner_id': self.partner.id, - 'amount': 200, - 'date': time.strftime('%Y') + '-07-15', }) + { + "name": "payment", + "statement_id": bank_stmt.id, + "partner_id": self.partner.id, + "amount": 200, + "date": time.strftime("%Y") + "-07-15", + } + ) line_id = self.account_move_line_model # reconcile the payment with the invoice for l in self.invoice.move_id.line_ids: if l.account_id.id == self.account_receivable.id: line_id = l break - bank_stmt_line.process_reconciliation(counterpart_aml_dicts=[{ - 'move_line': line_id, - 'account_id': self.account_income.id, - 'debit': 0.0, - 'credit': 200.0, - 'name': 'test_reconciliation', - }]) + bank_stmt_line.process_reconciliation( + counterpart_aml_dicts=[ + { + "move_line": line_id, + "account_id": self.account_income.id, + "debit": 0.0, + "credit": 200.0, + "name": "test_reconciliation", + } + ] + ) self.assertTrue(bank_stmt_line.journal_entry_ids) original_move_lines = bank_stmt_line.journal_entry_ids - original_payment_id = original_move_lines.mapped('payment_id').id - self.assertTrue(original_move_lines.mapped('statement_id')) + original_payment_id = original_move_lines.mapped("payment_id").id + self.assertTrue(original_move_lines.mapped("statement_id")) # Cancel the statement line res = bank_stmt_line.button_cancel_reconciliation() - ctx = {'active_model': 'account.bank.statement.line', - 'active_ids': [bank_stmt_line.id]} - f = Form(self.env[res['res_model']].with_context(ctx)) - self.assertEqual(res['res_model'], 'reverse.account.document') + ctx = { + "active_model": "account.bank.statement.line", + "active_ids": [bank_stmt_line.id], + } + f = Form(self.env[res["res_model"]].with_context(ctx)) + self.assertEqual(res["res_model"], "reverse.account.document") cancel_wizard = f.save() cancel_wizard.action_cancel() self.assertFalse(bank_stmt_line.journal_entry_ids) - payment = self.env['account.payment'].search( - [('id', '=', original_payment_id)], - limit=1) + payment = self.env["account.payment"].search( + [("id", "=", original_payment_id)], limit=1 + ) self.assertFalse(payment) - self.assertFalse(original_move_lines.mapped('statement_id')) + self.assertFalse(original_move_lines.mapped("statement_id")) move = original_move_lines[0].move_id reversed_move = move.reverse_entry_id - move_reconcile = move.mapped('line_ids').mapped('full_reconcile_id') - reversed_move_reconcile = \ - reversed_move.mapped('line_ids').mapped('full_reconcile_id') + move_reconcile = move.mapped("line_ids").mapped("full_reconcile_id") + reversed_move_reconcile = reversed_move.mapped("line_ids").mapped( + "full_reconcile_id" + ) # Check self.assertTrue(move_reconcile) self.assertTrue(reversed_move_reconcile) @@ -257,51 +286,62 @@ class TestPaymentReversal(SavepointCase): - The line in the statement is ready to reconcile again """ # Test journal - self.bank_journal.write({'update_posted': True, - 'cancel_method': 'reversal'}) + self.bank_journal.write({"update_posted": True, "cancel_method": "reversal"}) # Create a bank statement - bank_stmt = self.acc_bank_stmt_model.create({ - 'journal_id': self.bank_journal.id, - 'date': time.strftime('%Y') + '-07-15', - 'name': 'payment' + self.invoice.name - }) + bank_stmt = self.acc_bank_stmt_model.create( + { + "journal_id": self.bank_journal.id, + "date": time.strftime("%Y") + "-07-15", + "name": "payment" + self.invoice.name, + } + ) bank_stmt_line = self.acc_bank_stmt_line_model.create( - {'name': 'payment', - 'statement_id': bank_stmt.id, - 'partner_id': self.partner.id, - 'amount': 200, - 'date': time.strftime('%Y') + '-07-15', }) + { + "name": "payment", + "statement_id": bank_stmt.id, + "partner_id": self.partner.id, + "amount": 200, + "date": time.strftime("%Y") + "-07-15", + } + ) line_id = self.account_move_line_model - bank_stmt_line.process_reconciliation(new_aml_dicts=[{ - 'move_line': line_id, - 'account_id': self.account_expense.id, - 'debit': 200.0, - 'name': 'test_expense_reconciliation', - }]) + bank_stmt_line.process_reconciliation( + new_aml_dicts=[ + { + "move_line": line_id, + "account_id": self.account_expense.id, + "debit": 200.0, + "name": "test_expense_reconciliation", + } + ] + ) self.assertTrue(bank_stmt_line.journal_entry_ids) original_move_lines = bank_stmt_line.journal_entry_ids - original_payment_id = original_move_lines.mapped('payment_id').id - self.assertTrue(original_move_lines.mapped('statement_id')) + original_payment_id = original_move_lines.mapped("payment_id").id + self.assertTrue(original_move_lines.mapped("statement_id")) # Cancel the statement line res = bank_stmt_line.button_cancel_reconciliation() - ctx = {'active_model': 'account.bank.statement.line', - 'active_ids': [bank_stmt_line.id]} - f = Form(self.env[res['res_model']].with_context(ctx)) - self.assertEqual(res['res_model'], 'reverse.account.document') + ctx = { + "active_model": "account.bank.statement.line", + "active_ids": [bank_stmt_line.id], + } + f = Form(self.env[res["res_model"]].with_context(ctx)) + self.assertEqual(res["res_model"], "reverse.account.document") cancel_wizard = f.save() cancel_wizard.action_cancel() self.assertFalse(bank_stmt_line.journal_entry_ids) - payment = self.env['account.payment'].search( - [('id', '=', original_payment_id)], - limit=1) + payment = self.env["account.payment"].search( + [("id", "=", original_payment_id)], limit=1 + ) self.assertFalse(payment) - self.assertFalse(original_move_lines.mapped('statement_id')) + self.assertFalse(original_move_lines.mapped("statement_id")) move = original_move_lines[0].move_id reversed_move = move.reverse_entry_id - move_reconcile = move.mapped('line_ids').mapped('full_reconcile_id') - reversed_move_reconcile = \ - reversed_move.mapped('line_ids').mapped('full_reconcile_id') + move_reconcile = move.mapped("line_ids").mapped("full_reconcile_id") + reversed_move_reconcile = reversed_move.mapped("line_ids").mapped( + "full_reconcile_id" + ) # Check self.assertTrue(move_reconcile) self.assertTrue(reversed_move_reconcile) @@ -313,31 +353,39 @@ class TestPaymentReversal(SavepointCase): - UserError will show """ # Test journal - self.bank_journal.write({'update_posted': True, - 'cancel_method': 'reversal'}) + self.bank_journal.write({"update_posted": True, "cancel_method": "reversal"}) # Create a bank statement - bank_stmt = self.acc_bank_stmt_model.create({ - 'journal_id': self.bank_journal.id, - 'date': time.strftime('%Y') + '-07-15', - 'name': 'payment' + self.invoice.name - }) + bank_stmt = self.acc_bank_stmt_model.create( + { + "journal_id": self.bank_journal.id, + "date": time.strftime("%Y") + "-07-15", + "name": "payment" + self.invoice.name, + } + ) bank_stmt_line = self.acc_bank_stmt_line_model.create( - {'name': 'payment', - 'statement_id': bank_stmt.id, - 'partner_id': self.partner.id, - 'amount': 200, - 'date': time.strftime('%Y') + '-07-15', }) + { + "name": "payment", + "statement_id": bank_stmt.id, + "partner_id": self.partner.id, + "amount": 200, + "date": time.strftime("%Y") + "-07-15", + } + ) line_id = self.account_move_line_model - bank_stmt_line.process_reconciliation(new_aml_dicts=[{ - 'move_line': line_id, - 'account_id': self.account_expense.id, - 'debit': 200.0, - 'name': 'test_expense_reconciliation', - }]) + bank_stmt_line.process_reconciliation( + new_aml_dicts=[ + { + "move_line": line_id, + "account_id": self.account_expense.id, + "debit": 200.0, + "name": "test_expense_reconciliation", + } + ] + ) bank_stmt.balance_end_real = 200.00 bank_stmt.check_confirm_bank() - self.assertEqual(bank_stmt.state, 'confirm') + self.assertEqual(bank_stmt.state, "confirm") with self.assertRaises(UserError): bank_stmt_line.button_cancel_reconciliation() diff --git a/account_document_reversal/views/account_view.xml b/account_document_reversal/views/account_view.xml index 9ef29e4db..6c7351638 100644 --- a/account_document_reversal/views/account_view.xml +++ b/account_document_reversal/views/account_view.xml @@ -1,17 +1,30 @@ - + account.journal.form account.journal - + - - - + + + @@ -19,14 +32,19 @@ account.move.form.inherit account.move - + - + diff --git a/account_document_reversal/wizard/reverse_account_document.py b/account_document_reversal/wizard/reverse_account_document.py index 2d46a106f..4e578ecc3 100644 --- a/account_document_reversal/wizard/reverse_account_document.py +++ b/account_document_reversal/wizard/reverse_account_document.py @@ -1,48 +1,47 @@ # Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) -from odoo import models, fields, api +from odoo import api, fields, models class ReverseAccountDocument(models.TransientModel): """ Document reversal wizard, it cancel by reverse document journal entries """ - _name = 'reverse.account.document' - _description = 'Account Document Reversal' + + _name = "reverse.account.document" + _description = "Account Document Reversal" date = fields.Date( - string='Reversal date', - default=fields.Date.context_today, - required=True, + string="Reversal date", default=fields.Date.context_today, required=True, ) use_different_journal = fields.Boolean( - string='Use different journal for reversal', - help="Checked, if the journal of underlineing document is checked." + string="Use different journal for reversal", + help="Checked, if the journal of underlineing document is checked.", ) journal_id = fields.Many2one( - 'account.journal', - string='Reversal Journal', - help='If empty, uses the journal of the journal entry to be reversed.', + "account.journal", + string="Reversal Journal", + help="If empty, uses the journal of the journal entry to be reversed.", ) @api.model def default_get(self, default_fields): - model = self._context.get('active_model') - active_ids = self._context.get('active_ids') + model = self._context.get("active_model") + active_ids = self._context.get("active_ids") documents = self.env[model].browse(active_ids) res = super().default_get(default_fields) if documents: - if 'journal_id' in documents[0]: + if "journal_id" in documents[0]: journal = documents[0].journal_id if journal.use_different_journal: - res['use_different_journal'] = True - res['journal_id'] = journal.reversal_journal_id.id + res["use_different_journal"] = True + res["journal_id"] = journal.reversal_journal_id.id return res @api.multi def action_cancel(self): - model = self._context.get('active_model') - active_ids = self._context.get('active_ids') + model = self._context.get("active_model") + active_ids = self._context.get("active_ids") documents = self.env[model].browse(active_ids) documents.action_document_reversal(self.date, self.journal_id) - return {'type': 'ir.actions.act_window_close'} + return {"type": "ir.actions.act_window_close"} diff --git a/account_document_reversal/wizard/reverse_account_document_wizard.xml b/account_document_reversal/wizard/reverse_account_document_wizard.xml index 7bb904063..32d324369 100644 --- a/account_document_reversal/wizard/reverse_account_document_wizard.xml +++ b/account_document_reversal/wizard/reverse_account_document_wizard.xml @@ -1,4 +1,4 @@ - + @@ -8,16 +8,28 @@
- + - - + +
-
@@ -28,7 +40,7 @@ reverse.account.document form tree,form - + new
diff --git a/setup/account_document_reversal/odoo/addons/account_document_reversal b/setup/account_document_reversal/odoo/addons/account_document_reversal new file mode 120000 index 000000000..319ce9b9e --- /dev/null +++ b/setup/account_document_reversal/odoo/addons/account_document_reversal @@ -0,0 +1 @@ +../../../../account_document_reversal \ No newline at end of file diff --git a/setup/account_document_reversal/setup.py b/setup/account_document_reversal/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/account_document_reversal/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)