From c5f5f317ed8fa07e972cebc497815d2ab8028a3c Mon Sep 17 00:00:00 2001 From: Christopher Ormaza Date: Mon, 6 Feb 2023 14:26:44 -0500 Subject: [PATCH] imp: added feature of lines con account.payment.register --- account_payment_line/__init__.py | 1 + account_payment_line/__manifest__.py | 1 + account_payment_line/models/__init__.py | 1 + .../models/account_payment.py | 145 ++--------------- .../models/counterpart_line.py | 154 ++++++++++++++++++ .../security/ir.model.access.csv | 4 + account_payment_line/wizard/__init__.py | 1 + .../wizard/account_payment_register.py | 131 +++++++++++++++ .../wizard/account_payment_register_view.xml | 93 +++++++++++ 9 files changed, 400 insertions(+), 131 deletions(-) create mode 100644 account_payment_line/models/counterpart_line.py create mode 100644 account_payment_line/wizard/__init__.py create mode 100644 account_payment_line/wizard/account_payment_register.py create mode 100644 account_payment_line/wizard/account_payment_register_view.xml diff --git a/account_payment_line/__init__.py b/account_payment_line/__init__.py index 10e16283e0d3..1e6ded7127e1 100644 --- a/account_payment_line/__init__.py +++ b/account_payment_line/__init__.py @@ -2,4 +2,5 @@ # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). from . import models +from . import wizard from .hooks import post_load_hook diff --git a/account_payment_line/__manifest__.py b/account_payment_line/__manifest__.py index 8219f93b9542..e0298ef9bc01 100644 --- a/account_payment_line/__manifest__.py +++ b/account_payment_line/__manifest__.py @@ -13,6 +13,7 @@ "data": [ "security/ir.model.access.csv", "views/account_payment_views.xml", + "wizard/account_payment_register_view.xml", ], "maintainers": ["ChrisOForgeFlow"], "installable": True, diff --git a/account_payment_line/models/__init__.py b/account_payment_line/models/__init__.py index 2c5f5867feed..a41b4214589a 100644 --- a/account_payment_line/models/__init__.py +++ b/account_payment_line/models/__init__.py @@ -1,2 +1,3 @@ +from . import counterpart_line from . import account_payment from . import account_move diff --git a/account_payment_line/models/account_payment.py b/account_payment_line/models/account_payment.py index 8df3e95c9684..4ef16dc1c172 100644 --- a/account_payment_line/models/account_payment.py +++ b/account_payment_line/models/account_payment.py @@ -1,15 +1,9 @@ # Copyright 2022 ForgeFlow, S.L. # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). -from odoo import _, api, fields, models -from odoo.exceptions import ValidationError +from odoo import fields, models from odoo.tools import float_is_zero -dict_payment_type = dict( - inbound=["out_invoice", "out_refund", "out_receipt"], - outbound=["in_invoice", "in_refund", "in_receipt"], -) - class AccountPayment(models.Model): _inherit = "account.payment" @@ -180,139 +174,28 @@ def action_draft(self): return res -class AccountPaymentCounterLines(models.Model): +class AccountPaymentCounterLine(models.Model): _name = "account.payment.counterpart.line" + _inherit = "account.payment.counterpart.line.abstract" _description = "Counterpart line payment" payment_id = fields.Many2one( "account.payment", string="Payment", required=False, ondelete="cascade" ) - company_id = fields.Many2one(related="payment_id.company_id") - name = fields.Char(string="Description", required=True, default="/") - account_id = fields.Many2one( - "account.account", - string="Account", - required=True, - ondelete="restrict", - check_company=True, - ) - analytic_account_id = fields.Many2one( - comodel_name="account.analytic.account", - string="Analytic Account", - ondelete="restrict", - check_company=True, - ) analytic_tag_ids = fields.Many2many( - "account.analytic.tag", + comodel_name="account.analytic.tag", + relation="counterpart_line_analytic_tag_rel", + column1="line_id", + column2="tag_id", string="Analytic Tags", domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", check_company=True, ) - currency_id = fields.Many2one( - comodel_name="res.currency", string="Currency", related="payment_id.currency_id" - ) - amount = fields.Monetary(string="Amount", required=True) - amount_currency = fields.Monetary( - string="Amount in Company Currency", compute="_compute_amounts" - ) - aml_amount_residual = fields.Monetary( - string="Amount Residual", - compute="_compute_amounts", - ) - residual_after_payment = fields.Monetary( - compute="_compute_amounts", - ) - aml_amount_residual_currency = fields.Monetary( - string="Amount Residual Currency", - compute="_compute_amounts", - ) - residual_after_payment_currency = fields.Monetary( - compute="_compute_amounts", - ) - - @api.depends( - "aml_id.amount_residual", "amount", "payment_id.currency_id", "payment_id.date" - ) - def _compute_amounts(self): - for rec in self: - rec.amount_currency = rec.payment_id.currency_id._convert( - rec.amount, - rec.payment_id.company_id.currency_id, - rec.payment_id.company_id, - date=rec.payment_id.date, - ) - rec.aml_amount_residual = rec.aml_id.amount_residual - rec.residual_after_payment = max( - abs(rec.aml_id.amount_residual) - rec.amount_currency, 0 - ) - rec.aml_amount_residual_currency = rec.aml_id.amount_residual_currency - rec.residual_after_payment_currency = max( - abs(rec.aml_id.amount_residual_currency) - rec.amount_currency, 0 - ) - partner_id = fields.Many2one("res.partner", string="Partner", ondelete="restrict") - commercial_partner_id = fields.Many2one(related="partner_id.commercial_partner_id") - move_id = fields.Many2one( - "account.move", string="Journal Entry", ondelete="set null" - ) - move_ids = fields.One2many( - "account.move.line", - "payment_line_id", - string="Journal Entries Created", - ) - aml_id = fields.Many2one( - "account.move.line", string="Journal Item to Reconcile", ondelete="set null" - ) - aml_date_maturity = fields.Date( - string="Date Maturity", required=False, related="aml_id.date_maturity" - ) - - @api.onchange("move_id", "aml_id") - def _onchange_move_id(self): - aml_model = self.env["account.move.line"] - for rec in self: - type_move = dict_payment_type.get(rec.payment_id.payment_type, []) - if rec.move_id and not rec.aml_id: - domain = [ - ("move_id", "=", rec.move_id.id), - ("amount_residual", "!=", 0.0), - ] - lines_ordered = aml_model.search( - domain, order="date_maturity ASC", limit=1 - ) - if lines_ordered: - rec.aml_id = lines_ordered.id - if rec.aml_id: - rec.move_id = rec.aml_id.move_id.id - rec.account_id = rec.aml_id.account_id.id - rec.amount = abs(rec.aml_id.amount_residual) - rec.partner_id = rec.aml_id.partner_id.id - if rec.move_id.move_type == "entry": - if rec.payment_id.partner_type == "supplier": - if rec.payment_id.payment_type == "outbound": - rec.amount = -rec.aml_id.amount_residual - else: - rec.amount = rec.aml_id.amount_residual - else: - if rec.payment_id.payment_type == "outbound": - rec.amount = -rec.aml_id.amount_residual - else: - rec.amount = rec.aml_id.amount_residual - else: - if ( - type_move - and rec.move_id.move_type not in type_move - and rec.amount - ): - rec.amount *= -1 - - @api.constrains("amount", "aml_amount_residual") - def constrains_amount_residual(self): - for rec in self: - if rec.aml_id and 0 < rec.aml_amount_residual < rec.amount: - raise ValidationError( - _( - "the amount exceeds the residual amount, please check the invoice %s" - ), - (rec.aml_id.move_id.name or rec.aml_id.name), - ) + def _get_onchange_fields(self): + return ( + "aml_id.amount_residual", + "amount", + "payment_id.currency_id", + "payment_id.date", + ) diff --git a/account_payment_line/models/counterpart_line.py b/account_payment_line/models/counterpart_line.py new file mode 100644 index 000000000000..ae2285853e89 --- /dev/null +++ b/account_payment_line/models/counterpart_line.py @@ -0,0 +1,154 @@ +# Copyright 2022 ForgeFlow, S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +dict_payment_type = dict( + inbound=["out_invoice", "out_refund", "out_receipt"], + outbound=["in_invoice", "in_refund", "in_receipt"], +) + + +class AccountPaymentCounterLinesAbstract(models.AbstractModel): + _name = "account.payment.counterpart.line.abstract" + _description = "Counterpart line payment Abstract" + + company_id = fields.Many2one( + comodel_name="res.company", compute="_compute_company_fields" + ) + name = fields.Char(string="Description", required=True, default="/") + account_id = fields.Many2one( + "account.account", + string="Account", + required=True, + ondelete="restrict", + check_company=True, + ) + analytic_account_id = fields.Many2one( + comodel_name="account.analytic.account", + string="Analytic Account", + ondelete="restrict", + check_company=True, + ) + currency_id = fields.Many2one( + comodel_name="res.currency", + string="Currency", + compute="_compute_company_fields", + ) + + def _compute_company_fields(self): + for rec in self: + rec.company_id = self.env.company.id + rec.currency_id = self.env.company.currency_id.id + + amount = fields.Monetary(string="Amount", required=True) + amount_currency = fields.Monetary( + string="Amount in Company Currency", compute="_compute_amounts" + ) + aml_amount_residual = fields.Monetary( + string="Amount Residual", + compute="_compute_amounts", + ) + residual_after_payment = fields.Monetary( + compute="_compute_amounts", + ) + aml_amount_residual_currency = fields.Monetary( + string="Amount Residual Currency", + compute="_compute_amounts", + ) + residual_after_payment_currency = fields.Monetary( + compute="_compute_amounts", + ) + + def _get_onchange_fields(self): + return "aml_id.amount_residual", "amount" + + @api.depends(lambda x: x._get_onchange_fields()) + def _compute_amounts(self): + for rec in self: + payment_date = ( + hasattr(rec.payment_id, "payment_date") + and rec.payment_id.payment_date + or rec.payment_id.date + ) + rec.amount_currency = rec.payment_id.currency_id._convert( + rec.amount, + rec.payment_id.company_id.currency_id, + rec.payment_id.company_id, + date=payment_date, + ) + rec.aml_amount_residual = rec.aml_id.amount_residual + rec.residual_after_payment = max( + abs(rec.aml_id.amount_residual) - rec.amount_currency, 0 + ) + rec.aml_amount_residual_currency = rec.aml_id.amount_residual_currency + rec.residual_after_payment_currency = max( + abs(rec.aml_id.amount_residual_currency) - rec.amount_currency, 0 + ) + + partner_id = fields.Many2one("res.partner", string="Partner", ondelete="restrict") + commercial_partner_id = fields.Many2one(related="partner_id.commercial_partner_id") + move_id = fields.Many2one( + "account.move", string="Journal Entry", ondelete="set null" + ) + move_ids = fields.One2many( + "account.move.line", + "payment_line_id", + string="Journal Entries Created", + ) + aml_id = fields.Many2one( + "account.move.line", string="Journal Item to Reconcile", ondelete="set null" + ) + aml_date_maturity = fields.Date( + string="Date Maturity", required=False, related="aml_id.date_maturity" + ) + + @api.onchange("move_id", "aml_id") + def _onchange_move_id(self): + aml_model = self.env["account.move.line"] + for rec in self: + type_move = dict_payment_type.get(rec.payment_id.payment_type, []) + if rec.move_id and not rec.aml_id: + domain = [ + ("move_id", "=", rec.move_id.id), + ("amount_residual", "!=", 0.0), + ] + lines_ordered = aml_model.search( + domain, order="date_maturity ASC", limit=1 + ) + if lines_ordered: + rec.aml_id = lines_ordered.id + if rec.aml_id: + rec.move_id = rec.aml_id.move_id.id + rec.account_id = rec.aml_id.account_id.id + rec.amount = abs(rec.aml_id.amount_residual) + rec.partner_id = rec.aml_id.partner_id.id + if rec.move_id.move_type == "entry": + if rec.payment_id.partner_type == "supplier": + if rec.payment_id.payment_type == "outbound": + rec.amount = -rec.aml_id.amount_residual + else: + rec.amount = rec.aml_id.amount_residual + else: + if rec.payment_id.payment_type == "outbound": + rec.amount = -rec.aml_id.amount_residual + else: + rec.amount = rec.aml_id.amount_residual + else: + if ( + type_move + and rec.move_id.move_type not in type_move + and rec.amount + ): + rec.amount *= -1 + + @api.constrains("amount", "aml_amount_residual") + def constrains_amount_residual(self): + for rec in self: + if rec.aml_id and 0 < rec.aml_amount_residual < rec.amount: + raise ValidationError( + _( + "the amount exceeds the residual amount, please check the invoice %s" + ), + (rec.aml_id.move_id.name or rec.aml_id.name), + ) diff --git a/account_payment_line/security/ir.model.access.csv b/account_payment_line/security/ir.model.access.csv index 1d8229944fe4..124172c59c1e 100644 --- a/account_payment_line/security/ir.model.access.csv +++ b/account_payment_line/security/ir.model.access.csv @@ -1,4 +1,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_account_payment_register_counterpart_line_all,access_account_payment_register_counterpart_line_all,model_account_payment_register_counterpart_line,,1,0,0,0 +access_account_payment_register_counterpart_line_group_account_invoice,access_account_payment_register_counterpart_line_group_account_invoice,model_account_payment_register_counterpart_line,account.group_account_invoice,1,1,1,1 +access_account_payment_register_counterpart_line_group_account_user,access_account_payment_register_counterpart_line_group_account_user,model_account_payment_register_counterpart_line,account.group_account_user,1,1,1,1 +access_account_payment_register_counterpart_line_group_account_manager,access_account_payment_register_counterpart_line_group_account_manager,model_account_payment_register_counterpart_line,account.group_account_manager,1,1,1,1 access_account_payment_counterpart_line_all,access_account_payment_counterpart_line_all,model_account_payment_counterpart_line,,1,0,0,0 access_account_payment_counterpart_line_group_account_invoice,access_account_payment_counterpart_line_group_account_invoice,model_account_payment_counterpart_line,account.group_account_invoice,1,1,1,1 access_account_payment_counterpart_line_group_account_user,access_account_payment_counterpart_line_group_account_user,model_account_payment_counterpart_line,account.group_account_user,1,1,1,1 diff --git a/account_payment_line/wizard/__init__.py b/account_payment_line/wizard/__init__.py new file mode 100644 index 000000000000..019698a882d2 --- /dev/null +++ b/account_payment_line/wizard/__init__.py @@ -0,0 +1 @@ +from . import account_payment_register diff --git a/account_payment_line/wizard/account_payment_register.py b/account_payment_line/wizard/account_payment_register.py new file mode 100644 index 000000000000..35b1f3e4f07c --- /dev/null +++ b/account_payment_line/wizard/account_payment_register.py @@ -0,0 +1,131 @@ +# Copyright 2022 ForgeFlow, S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo import api, fields, models +from odoo.models import MAGIC_COLUMNS + + +class AccountPaymentRegister(models.TransientModel): + + _inherit = "account.payment.register" + + line_payment_counterpart_ids = fields.One2many( + "account.payment.register.counterpart.line", + "payment_id", + string="Counterpart Lines", + help="Use these lines to add matching lines, for example in a credit" + "card payment, financing interest or commission is added", + ) + + def _get_moves_domain(self): + current_invoices = ( + self.env.context.get("active_model") == "account.move" + and self.env.context.get("active_ids", []) + or [] + ) + if current_invoices: + domain = [("id", "in", current_invoices)] + return domain + + def _filter_amls(self, amls): + return amls.filtered( + lambda x: x.partner_id.commercial_partner_id.id + == self.partner_id.commercial_partner_id.id + and x.amount_residual != 0 + and x.account_id.internal_type in ("receivable", "payable") + ) + + def _hook_preprocess_lines(self, lines): + return lines + + @api.onchange( + "amount", + "currency_id", + "payment_date", + ) + def onchange_payment_value(self): + move_model = self.env["account.move"] + domain = self._get_moves_domain() + pending_invoices = move_model.search(domain, order="invoice_date_due ASC") + pending_amount = self.amount + self.update({"line_payment_counterpart_ids": [(5, 0, 0)]}) + lines = [] + for invoice in pending_invoices: + for aml in self._filter_amls(invoice.line_ids): + amount_to_apply = 0 + amount_residual = self.company_id.currency_id._convert( + aml.amount_residual, + self.currency_id, + self.company_id, + date=self.payment_date, + ) + if pending_amount >= 0: + amount_to_apply = min(abs(amount_residual), pending_amount) + pending_amount -= abs(amount_residual) + lines.append( + { + "payment_id": self.id, + "name": "/", + "move_id": invoice.id, + "aml_id": aml.id, + "account_id": aml.account_id.id, + "partner_id": self.partner_id.commercial_partner_id.id, + "amount": amount_to_apply, + } + ) + lines = self._hook_preprocess_lines(lines) + self.update( + { + "line_payment_counterpart_ids": lines + and [(0, 0, line) for line in lines] + or [] + } + ) + + def _create_payment_vals_from_wizard(self): + res = super()._create_payment_vals_from_wizard() + if self.payment_difference_handling != "reconcile": + lines_data = self.line_payment_counterpart_ids.read(load="_classic_write") + for line_data in lines_data: + for magic_field in MAGIC_COLUMNS: + if magic_field in lines_data: + line_data.pop(magic_field) + if "payment_id" in line_data: + line_data.pop("payment_id") + if lines_data: + res.update( + { + "line_payment_counterpart_ids": [ + (0, 0, line) for line in lines_data + ], + } + ) + return res + + +class AccountPaymentRegisterCounterpartLine(models.TransientModel): + + _name = "account.payment.register.counterpart.line" + _inherit = "account.payment.counterpart.line.abstract" + _description = "Counterpart Lines on Invoice Wizard" + + payment_id = fields.Many2one( + "account.payment.register", string="Payment", required=False, ondelete="cascade" + ) + + analytic_tag_ids = fields.Many2many( + comodel_name="account.analytic.tag", + relation="counterpart_line_wizard_analytic_tag_rel", + column1="line_id", + column2="tag_id", + string="Analytic Tags", + domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", + check_company=True, + ) + + def _get_onchange_fields(self): + return ( + "aml_id.amount_residual", + "amount", + "payment_id.currency_id", + "payment_id.payment_date", + ) diff --git a/account_payment_line/wizard/account_payment_register_view.xml b/account_payment_line/wizard/account_payment_register_view.xml new file mode 100644 index 000000000000..080e14f7cfd3 --- /dev/null +++ b/account_payment_line/wizard/account_payment_register_view.xml @@ -0,0 +1,93 @@ + + + + + + view.account.payment.register.line.tree + account.payment.register.counterpart.line + + + + + + + + + + + + + + + + + + + + + + + + + account.payment.register.form + account.payment.register + + + + + + + + + + + + +