From 9b2ece14980ce96717142efadff31288a61d7fca Mon Sep 17 00:00:00 2001 From: Alessandro Uffreduzzi Date: Tue, 12 Nov 2024 12:54:30 +0100 Subject: [PATCH] [FIX] sale_commission_partial_settlement: handle settlement deletion This commit fixes an issue where, if a settlement was deleted, any partial commission would not be recreated because it was still considered paid. Includes a small refactor of the files and deletion of some unused fields. --- .../__manifest__.py | 2 +- .../migrations/14.0.1.1.0/post-migrate.py | 54 ++++++++++++ .../models/__init__.py | 8 +- ..._move.py => account_invoice_line_agent.py} | 87 +++++++++---------- .../account_invoice_line_agent_partial.py | 28 ++++++ .../models/account_partial_reconcile.py | 22 ++++- .../models/sale_commission_settlement.py | 9 ++ ....py => sale_commission_settlement_line.py} | 4 + .../wizard/wizard_settle.py | 11 +-- 9 files changed, 165 insertions(+), 60 deletions(-) create mode 100644 sale_commission_partial_settlement/migrations/14.0.1.1.0/post-migrate.py rename sale_commission_partial_settlement/models/{account_move.py => account_invoice_line_agent.py} (59%) create mode 100644 sale_commission_partial_settlement/models/account_invoice_line_agent_partial.py create mode 100644 sale_commission_partial_settlement/models/sale_commission_settlement.py rename sale_commission_partial_settlement/models/{settlement.py => sale_commission_settlement_line.py} (88%) diff --git a/sale_commission_partial_settlement/__manifest__.py b/sale_commission_partial_settlement/__manifest__.py index 1b98f3e68..eb5e8a989 100644 --- a/sale_commission_partial_settlement/__manifest__.py +++ b/sale_commission_partial_settlement/__manifest__.py @@ -1,7 +1,7 @@ # Copyright 2023 Nextev { "name": "Sales commissions based on paid amount", - "version": "14.0.1.0.1", + "version": "14.0.1.1.0", "author": "Nextev Srl," "Ooops," "Odoo Community Association (OCA)", "maintainers": ["aleuffre", "renda-dev", "PicchiSeba"], "category": "Sales Management", diff --git a/sale_commission_partial_settlement/migrations/14.0.1.1.0/post-migrate.py b/sale_commission_partial_settlement/migrations/14.0.1.1.0/post-migrate.py new file mode 100644 index 000000000..7f62b44d9 --- /dev/null +++ b/sale_commission_partial_settlement/migrations/14.0.1.1.0/post-migrate.py @@ -0,0 +1,54 @@ +from openupgradelib import openupgrade + + +def unlink_orphan_agent_partials(env): + """ + Delete agent partials that are not linked + to a settlement line + e.g. because the settlement was deleted + but the ailap was left behind + """ + openupgrade.logged_query( + env.cr, + """ + SELECT id + FROM account_invoice_line_agent_partial + WHERE id not in + (SELECT agent_line_partial_id + FROM settlement_agent_line_partial_rel) + """, + ) + ids = [row[0] for row in env.cr.fetchall()] + env["account.invoice.line.agent.partial"].browse(ids).unlink() + + +def recompute_partial_settled(env): + """ + "partial_settled" was not recomputed properly if a + settlement was canceled or deleted + """ + env["account.invoice.line.agent"].search( + [ + ("commission_id.payment_amount_type", "=", "paid"), + ("partial_settled", "!=", 0), + ] + )._compute_partial_settled() + + +def recompute_settled(env): + """ + Sometimes AILA were not marked as "settled" properly + for commissions of type "paid" + """ + env["account.invoice.line.agent"].search( + [ + ("commission_id.payment_amount_type", "=", "paid"), + ] + )._compute_settled() + + +@openupgrade.migrate() +def migrate(env, version): + unlink_orphan_agent_partials(env) + recompute_partial_settled(env) + recompute_settled(env) diff --git a/sale_commission_partial_settlement/models/__init__.py b/sale_commission_partial_settlement/models/__init__.py index 4e17db438..61873e96a 100644 --- a/sale_commission_partial_settlement/models/__init__.py +++ b/sale_commission_partial_settlement/models/__init__.py @@ -1,4 +1,6 @@ -from . import sale_commission -from . import account_move +from . import account_invoice_line_agent +from . import account_invoice_line_agent_partial from . import account_partial_reconcile -from . import settlement +from . import sale_commission +from . import sale_commission_settlement +from . import sale_commission_settlement_line diff --git a/sale_commission_partial_settlement/models/account_move.py b/sale_commission_partial_settlement/models/account_invoice_line_agent.py similarity index 59% rename from sale_commission_partial_settlement/models/account_move.py rename to sale_commission_partial_settlement/models/account_invoice_line_agent.py index a402c5ed3..c2689010e 100644 --- a/sale_commission_partial_settlement/models/account_move.py +++ b/sale_commission_partial_settlement/models/account_invoice_line_agent.py @@ -1,23 +1,51 @@ # Copyright 2023 Nextev # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import api, fields, models +from odoo.tools.float_utils import float_compare class AccountInvoiceLineAgent(models.Model): _inherit = "account.invoice.line.agent" - partial_settled = fields.Monetary(string="Partial Commission Amount Settled") + partial_settled = fields.Monetary( + string="Partial Commission Amount Settled", + compute="_compute_partial_settled", + store=True, + ) + is_fully_settled = fields.Boolean(compute="_compute_is_fully_settled", store=True) + invoice_line_agent_partial_ids = fields.One2many( + "account.invoice.line.agent.partial", "invoice_line_agent_id" + ) - def _compute_settled(self): - filtered_lines = self.filtered( - lambda x: x.commission_id.payment_amount_type != "paid" - ) - for line in self - filtered_lines: - if not line.mapped("agent_line.settlement_id"): - line.settled = False + @api.depends( + "invoice_line_agent_partial_ids.amount", + "invoice_line_agent_partial_ids.agent_line.settlement_id.state", + ) + def _compute_partial_settled(self): + for rec in self: + rec.partial_settled = sum( + ailap.amount + for ailap in rec.invoice_line_agent_partial_ids + if ailap.mapped("agent_line.settlement_id")[:1].state != "cancel" + ) - super(AccountInvoiceLineAgent, filtered_lines)._compute_settled() + @api.depends( + "commission_id.payment_amount_type", "amount", "settled", "partial_settled" + ) + def _compute_is_fully_settled(self): + for rec in self: + if rec.commission_id.payment_amount_type != "paid": + rec.is_fully_settled = rec.settled + else: + rec.is_fully_settled = rec.settled and ( + float_compare( + rec.partial_settled, + rec.amount, + precision_rounding=rec.currency_id.rounding, + ) + == 0 + ) def _partial_commissions(self, date_payment_to): """ @@ -30,7 +58,6 @@ def _partial_commissions(self, date_payment_to): """ partial_lines_to_settle = [] partial_payment_remaining = {} - lines_to_update = {} for line in self: line_total_amount = line.amount for ( @@ -55,12 +82,9 @@ def _partial_commissions(self, date_payment_to): "invoice_line_agent_id": line.id, "currency_id": line.currency_id.id, "amount": line_total_amount, + "account_partial_reconcile_id": partial.id, } ) - lines_to_update[line.id] = { - "partial_settled": line_total_amount, - "settled": True, - } partial_payment_remaining[partial.id] = { "remaining_amount": amount - line.object_id.price_total } @@ -75,39 +99,10 @@ def _partial_commissions(self, date_payment_to): "invoice_line_agent_id": line.id, "currency_id": line.currency_id.id, "amount": partial_commission, + "account_partial_reconcile_id": partial.id, } ) - if line.id in lines_to_update: - lines_to_update[line.id]["partial_settled"] += partial_commission - else: - lines_to_update[line.id] = {"partial_settled": partial_commission} - - if lines_to_update[line.id]["partial_settled"] >= line_total_amount: - lines_to_update[line.id].update({"settled": True}) - break - partial.partial_commission_settled = True partial_agent_lines = self.env["account.invoice.line.agent.partial"].create( partial_lines_to_settle ) - return partial_agent_lines, lines_to_update - - -class AccountInvoiceLineAgentPartial(models.Model): - _name = "account.invoice.line.agent.partial" - _description = "Partial agent commissions" - - invoice_line_agent_id = fields.Many2one("account.invoice.line.agent", required=True) - agent_line = fields.Many2many( - comodel_name="sale.commission.settlement.line", - relation="settlement_agent_line_partial_rel", - column1="agent_line_partial_id", - column2="settlement_id", - copy=False, - ) - amount = fields.Monetary( - string="Commission Amount", - ) - currency_id = fields.Many2one( - related="invoice_line_agent_id.currency_id", - ) - settled = fields.Boolean() + return partial_agent_lines diff --git a/sale_commission_partial_settlement/models/account_invoice_line_agent_partial.py b/sale_commission_partial_settlement/models/account_invoice_line_agent_partial.py new file mode 100644 index 000000000..181a7f8c9 --- /dev/null +++ b/sale_commission_partial_settlement/models/account_invoice_line_agent_partial.py @@ -0,0 +1,28 @@ +# Copyright 2023 Nextev +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class AccountInvoiceLineAgentPartial(models.Model): + _name = "account.invoice.line.agent.partial" + _description = "Partial agent commissions" + + invoice_line_agent_id = fields.Many2one( + "account.invoice.line.agent", required=True, ondelete="cascade" + ) + # logically a One2one + agent_line = fields.Many2many( + comodel_name="sale.commission.settlement.line", + relation="settlement_agent_line_partial_rel", + column1="agent_line_partial_id", + column2="settlement_id", + copy=False, + ) + account_partial_reconcile_id = fields.Many2one("account.partial.reconcile") + amount = fields.Monetary( + string="Commission Amount", + ) + currency_id = fields.Many2one( + related="invoice_line_agent_id.currency_id", + ) diff --git a/sale_commission_partial_settlement/models/account_partial_reconcile.py b/sale_commission_partial_settlement/models/account_partial_reconcile.py index 7c1714d4d..e4ecabc5b 100644 --- a/sale_commission_partial_settlement/models/account_partial_reconcile.py +++ b/sale_commission_partial_settlement/models/account_partial_reconcile.py @@ -1,7 +1,25 @@ -from odoo import fields, models +from odoo import api, fields, models class AccountPartialReconcile(models.Model): _inherit = "account.partial.reconcile" - partial_commission_settled = fields.Boolean() + # Logically a One2one + account_invoice_line_agent_partial_ids = fields.One2many( + "account.invoice.line.agent.partial", "account_partial_reconcile_id" + ) + partial_commission_settled = fields.Boolean( + compute="_compute_partial_commission_settled", store=True + ) + + @api.depends( + "account_invoice_line_agent_partial_ids", + "account_invoice_line_agent_partial_ids.agent_line.settlement_id.state", + ) + def _compute_partial_commission_settled(self): + for rec in self: + rec.partial_commission_settled = bool( + rec.account_invoice_line_agent_partial_ids.filtered( + lambda x: x.mapped("agent_line.settlement_id")[:1].state != "cancel" + ) + ) diff --git a/sale_commission_partial_settlement/models/sale_commission_settlement.py b/sale_commission_partial_settlement/models/sale_commission_settlement.py new file mode 100644 index 000000000..1aa58e699 --- /dev/null +++ b/sale_commission_partial_settlement/models/sale_commission_settlement.py @@ -0,0 +1,9 @@ +from odoo import models + + +class Settlement(models.Model): + _inherit = "sale.commission.settlement" + + def unlink(self): + self.mapped("line_ids.agent_line_partial_ids").unlink() + return super().unlink() diff --git a/sale_commission_partial_settlement/models/settlement.py b/sale_commission_partial_settlement/models/sale_commission_settlement_line.py similarity index 88% rename from sale_commission_partial_settlement/models/settlement.py rename to sale_commission_partial_settlement/models/sale_commission_settlement_line.py index f25ba11e0..a5ebda029 100644 --- a/sale_commission_partial_settlement/models/settlement.py +++ b/sale_commission_partial_settlement/models/sale_commission_settlement_line.py @@ -24,3 +24,7 @@ def _compute_settled_amount(self): rec.settled_amount = rec.agent_line_partial_ids[:1].amount else: rec.settled_amount = rec.agent_line[:1].amount + + def unlink(self): + self.mapped("agent_line_partial_ids").unlink() + return super().unlink() diff --git a/sale_commission_partial_settlement/wizard/wizard_settle.py b/sale_commission_partial_settlement/wizard/wizard_settle.py index 5c271e5e1..aad31f6ad 100644 --- a/sale_commission_partial_settlement/wizard/wizard_settle.py +++ b/sale_commission_partial_settlement/wizard/wizard_settle.py @@ -32,14 +32,9 @@ def action_settle_partial(self): for agent in agents: date_to_agent = self._get_period_start(agent, date_to) main_agent_line = self.get_partial_agent_lines(agent, date_to_agent) - ( - partial_agent_lines, - agent_lines_to_update, - ) = main_agent_line._partial_commissions(self.date_payment_to) - for line_id in agent_lines_to_update: - self.env["account.invoice.line.agent"].browse(line_id).update( - agent_lines_to_update[line_id] - ) + partial_agent_lines = main_agent_line._partial_commissions( + self.date_payment_to + ) for company in partial_agent_lines.mapped( "invoice_line_agent_id.company_id" ):