[13.0][MIG] account_document_reversal

This commit is contained in:
kittiu
2020-03-27 21:23:59 +07:00
committed by OCA Transbot
parent 0cbb026770
commit 351071ae4d
79 changed files with 219 additions and 181 deletions

View File

@@ -4,12 +4,16 @@
{
"name": "Account Document Reversal",
"summary": "Create reversed journal entries when cancel document",
"version": "12.0.1.0.0",
"version": "13.0.1.0.0",
"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",],
"depends": ["account"],
"data": ["wizard/reverse_account_document_wizard.xml",
"views/account_view.xml",
"views/account_move_view.xml",
"views/account_payment_view.xml",
],
"license": "AGPL-3",
"installable": True,
"application": False,

View File

@@ -2,7 +2,6 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import account
from . import account_document_reversal
from . import account_invoice
from . import account_payment
from . import account_bank_statement
from . import account_move

View File

@@ -8,7 +8,7 @@ class AccountJournal(models.Model):
cancel_method = fields.Selection(
[
("normal", "Normal (delete journal entries if exists)"),
("normal", "Normal (remove journal entries)"),
("reversal", "Reversal (create reversed journal entries)"),
],
string="Cancel Method",
@@ -30,9 +30,6 @@ class AccountJournal(models.Model):
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.cancel_method == "reversal"

View File

@@ -1,6 +1,6 @@
# 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 _, models
from odoo.exceptions import UserError
@@ -8,11 +8,10 @@ class AccountPayment(models.Model):
_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")
self.mapped("journal_entry_ids.move_id.journal_id.is_cancel_reversal")
)
states = self.mapped("statement_id.state")
if cancel_reversal:
@@ -21,46 +20,35 @@ class AccountPayment(models.Model):
return self.reverse_document_wizard()
return super().button_cancel_reconciliation()
@api.multi
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
):
# there can be several moves linked to a statement line but
# maximum one created by the line itself
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()
# Find account moves to cancel reversal
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}
)
# 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})
moves._cancel_reversal(journal_id)
# Set cancel related payments
payment_to_cancel.write({"state": "cancelled"})
return True

View File

@@ -1,12 +1,28 @@
# 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, fields
class AccountDocumentReversal(models.AbstractModel):
_name = "account.document.reversal"
_description = "Abstract Module for Document Reversal"
is_cancel_reversal = fields.Boolean(
string="Use Cancel Reversal",
compute="_compute_is_cancel_reversal",
)
# reversal_move_ids = fields.Many2many(
# comodel_name="account.move",
# help="Cancelled journal entries",
# )
def _compute_is_cancel_reversal(self):
for rec in self:
if "journal_id" in rec:
rec.is_cancel_reversal = rec.journal_id.is_cancel_reversal
else:
rec.is_cancel_reversal = False
@api.model
def reverse_document_wizard(self):
""" Return Wizard to Cancel Document """
@@ -16,7 +32,6 @@ class AccountDocumentReversal(models.AbstractModel):
vals = action.read()[0]
return vals
@api.multi
def action_document_reversal(self, date=None, journal_id=None):
""" Reverse with following guildeline,
- Check existing document state / raise warning

View File

@@ -1,58 +0,0 @@
# 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 UserError, ValidationError
class AccountInvoice(models.Model):
_name = "account.invoice"
_inherit = ["account.invoice", "account.document.reversal"]
@api.multi
def action_invoice_cancel(self):
""" If cancel method is to reverse, use document reversal wizard
* 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")
):
raise UserError(
_(
"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()
@api.multi
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")
# Set all moves to unreconciled
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})
# 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",
}
)
return True

View File

@@ -1,26 +1,79 @@
# 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, fields, models
from odoo.exceptions import ValidationError
from odoo import _, fields, models
from odoo.exceptions import UserError
class AccountMove(models.Model):
_name = "account.move"
_inherit = ["account.move", "account.document.reversal"]
is_cancel_reversal = fields.Boolean(
string="Use Cancel Reversal", related="journal_id.is_cancel_reversal",
cancel_reversal = fields.Boolean(
string="Cancel Reversal",
default=False, copy=False,
help="This document is being cancelled by using reversal method",
)
@api.multi
def button_cancel(self):
""" Do not allow using this button for cancel with 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."
)
)
return super().button_cancel()
def button_cancel_reversal(self):
return self.reverse_document_wizard()
def button_draft(self):
for rec in self:
if rec.is_cancel_reversal and rec.state != "cancel":
raise UserError(_("Cannot set to draft!"))
return super().button_draft()
def action_document_reversal(self, date=None, journal_id=None):
# Check document readiness
valid_state = len(self.mapped("state")) == 1 and \
list(set(self.mapped("state")))[0] == "posted"
if not valid_state:
raise UserError(
_("Only posted document can be cancelled (reversal)"))
if self.mapped("line_ids.matched_debit_ids") | self.mapped("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."))
# Create reverse entries
self._cancel_reversal(journal_id)
return True
def _cancel_reversal(self, journal_id):
self.mapped("line_ids").filtered(
lambda x: x.account_id.reconcile).remove_move_reconcile()
Reversal = self.env["account.move.reversal"]
ctx = {"active_ids": self.ids, "active_model": "account.move"}
res = Reversal.with_context(ctx).default_get([])
res.update({"journal_id": journal_id,
"refund_method": "cancel",
"move_type": "entry", })
reversal = Reversal.create(res)
reversal.with_context(cancel_reversal=True).reverse_moves()
def _reverse_moves(self, default_values_list=None, cancel=False):
""" Set flag on the moves and the reversal moves being reversed """
if self._context.get("cancel_reversal"):
self.write({"cancel_reversal": True})
reverse_moves = super()._reverse_moves(default_values_list, cancel)
if self._context.get("cancel_reversal"):
reverse_moves.write({"cancel_reversal": True})
return reverse_moves
def _reverse_move_vals(self, default_values, cancel=True):
""" Reverse with cancel reversal, always use move_type = entry """
if self._context.get("cancel_reversal"):
default_values.update({"type": "entry"})
return super()._reverse_move_vals(default_values, cancel)
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
def remove_move_reconcile(self):
""" For move with cancel_reversal = True, freeze it """
if not self._context.get("cancel_reversal") and \
any(self.mapped("move_id").mapped("cancel_reversal")):
raise UserError(_("This document was cancelled and freezed,\n"
"unreconcilation not allowed."))
return super().remove_move_reconcile()

View File

@@ -1,38 +1,37 @@
# 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
from odoo import _, api, models, fields
from odoo.exceptions import UserError
class AccountPayment(models.Model):
_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:
return self.reverse_document_wizard()
return super().cancel()
def cancel_reversal(self):
return self.reverse_document_wizard()
def action_draft(self):
""" Case cancel reversal, set to draft allowed only when no moves """
for rec in self:
if rec.is_cancel_reversal and rec.move_line_ids:
raise UserError(_("Cannot set to draft!"))
return super().action_draft()
@api.multi
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")
# Check document readiness
valid_state = len(self.mapped("state")) == 1 and \
list(set(self.mapped("state")))[0] == "posted"
if not valid_state:
raise UserError(
_("Only validated document can be cancelled (reversal)"))
# Find moves to get reversed
move_lines = self.mapped("move_line_ids").filtered(
lambda x: x.journal_id == self.mapped("journal_id")[0])
moves = move_lines.mapped("move_id")
# Set all moves to unreconciled
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})
# Create reverse entries
moves.reverse_moves(date, journal_id)
moves._cancel_reversal(journal_id)
# Set state cancelled and unlink with account.move
self.write({"move_name": False, "state": "cancelled"})
self.write({"state": "cancelled"})
return True

View File

@@ -1,4 +1,4 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import test_invoice_reversal
from . import test_payment_reversal
# from . import test_invoice_reversal
# from . import test_payment_reversal

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_move_form" model="ir.ui.view">
<field name="name">account.move.form.inherit</field>
<field name="model">account.move</field>
<field
name="inherit_id"
ref="account.view_move_form"
/>
<field name="arch" type="xml">
<field name="state" position="before">
<field name="is_cancel_reversal" invisible="1" />
<field name="cancel_reversal" invisible="1"/>
</field>
<button name="button_cancel" position="after">
<button name="button_cancel_reversal" string="Cancel Entry" type="object" groups="account.group_account_invoice"
attrs="{'invisible': ['|', '|', ('state', '!=', 'posted'), ('is_cancel_reversal', '=', False), ('cancel_reversal', '=', True)]}"/>
</button>
<field name="id" position="before">
<widget name="web_ribbon" title="Cancelled" attrs="{'invisible': ['|', ('cancel_reversal', '=', False), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"/>
</field>
</field>
</record>
<record id="view_account_invoice_filter" model="ir.ui.view">
<field name="name">view.account.invoice.filter</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_account_invoice_filter"/>
<field name="arch" type="xml">
<filter name="unpaid" position="after">
<filter name="cancelled" string="Cancelled" domain="['|', ('state', '=', 'cancel'), ('cancel_reversal', '=', True)]"/>
</filter>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_account_payment_form" model="ir.ui.view">
<field name="name">view.account.payment.form</field>
<field name="model">account.payment</field>
<field name="inherit_id" ref="account.view_account_payment_form"/>
<field name="arch" type="xml">
<field name="state" position="before">
<field name="is_cancel_reversal" invisible="1" />
</field>
<button name="cancel" position="after">
<button name="cancel_reversal" attrs="{'invisible': ['|', ('state', '!=', 'posted'), ('is_cancel_reversal', '=', False)]}" string="Cancel" type="object"/>
</button>
</field>
</record>
</odoo>

View File

@@ -1,49 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_account_journal_form_inherit" model="ir.ui.view">
<record id="view_account_journal_form" model="ir.ui.view">
<field name="name">account.journal.form</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.view_account_journal_form"/>
<field name="arch" type="xml">
<field name="update_posted" position="after">
<field
name="cancel_method"
groups="base.group_no_one"
widget="radio"
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>
</record>
<record id="view_move_form_inherit_account_cancel" model="ir.ui.view">
<field name="name">account.move.form.inherit</field>
<field name="model">account.move</field>
<field
name="inherit_id"
ref="account_cancel.view_move_form_inherit_account_cancel"
/>
<field name="arch" type="xml">
<field name="state" position="before">
<field name="is_cancel_reversal" invisible="1" />
</field>
<button name="button_cancel" position="attributes">
<attribute
name="attrs"
>{'invisible': ['|', ('state', '!=', 'posted'), ('is_cancel_reversal', '=', True)]}</attribute>
<attribute name="states" />
</button>
<xpath expr="//page[@name='advanced_settings']/group" position="inside">
<group name="cancel_option" string="Cancel Option">
<field
name="cancel_method"
groups="base.group_no_one"
widget="radio"
/>
<field
name="use_different_journal"
attrs="{'invisible': [('cancel_method', '!=', 'reversal')]}"
/>
<field
name="reversal_journal_id"
widget="selection"
attrs="{'invisible': [('cancel_method', '!=', 'reversal'), ('use_different_journal', '=', False)]}"
/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -38,10 +38,9 @@ class ReverseAccountDocument(models.TransientModel):
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")
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.id)
return {"type": "ir.actions.act_window_close"}

View File

@@ -37,7 +37,6 @@
<record id="action_view_reverse_account_document" model="ir.actions.act_window">
<field name="name">Document Cancel</field>
<field name="res_model">reverse.account.document</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_reverse_account_document" />
<field name="target">new</field>

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1 @@
../../../../account_document_reversal

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)