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