diff --git a/sale_commission_partial_settlement/README.rst b/sale_commission_partial_settlement/README.rst index 3b2a25fcd..405bb8ecf 100644 --- a/sale_commission_partial_settlement/README.rst +++ b/sale_commission_partial_settlement/README.rst @@ -7,7 +7,7 @@ Sales commissions based on paid amount !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:9596c13efc487356fc34bac45ca9c0138c0ec5127e6e12f5427aea0a1630db80 + !! source digest: sha256:a9e800ab8099964a8184f688597f54dba7b6f40f0110cac40e8d1e60576186e0 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -44,6 +44,18 @@ Usage Behavior for commission settlements will be as per module description. +Known issues / Roadmap +====================== + +Due to the data structure of the module before version 14.0.2.0.0, +there is no way to link partial settlements created before this version +of the module to a specific payment or account move, and therefore to the +new data structure existing after the changes. + +A "best effort" migration of previous data could be developed to +ensure a smoother transition between versions. + + Bug Tracker =========== diff --git a/sale_commission_partial_settlement/__manifest__.py b/sale_commission_partial_settlement/__manifest__.py index 0f6d85fda..2ba7d9cc1 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.2.1", + "version": "14.0.2.0.0", "author": "Nextev Srl," "Ooops," "Odoo Community Association (OCA)", "maintainers": ["aleuffre", "renda-dev", "PicchiSeba"], "category": "Sales Management", @@ -10,7 +10,10 @@ "website": "https://github.com/OCA/commission", "data": [ "security/ir.model.access.csv", + "views/account_invoice_line_agent_views.xml", + "views/account_invoice_line_agent_partial_views.xml", "views/res_config_settings_view.xml", + "views/sale_commission_settlement_line_partial_views.xml", "views/sale_commission_settlement_view.xml", "views/sale_commission_view.xml", ], diff --git a/sale_commission_partial_settlement/migrations/14.0.1.2.1/post-migrate.py b/sale_commission_partial_settlement/migrations/14.0.1.2.1/post-migrate.py index ed23e8407..6bcd625a4 100644 --- a/sale_commission_partial_settlement/migrations/14.0.1.2.1/post-migrate.py +++ b/sale_commission_partial_settlement/migrations/14.0.1.2.1/post-migrate.py @@ -5,8 +5,11 @@ def recompute_partial_commission_settled(env): """ Recompute field "partial_commission_settled" of model "account.partial.reconcile" + Removed in future versions of the module """ - env["account.partial.reconcile"].search([])._compute_partial_commission_settled() + partial_reconcile = env["account.partial.reconcile"] + if getattr(partial_reconcile, "_compute_partial_commission_settled", False): + partial_reconcile.search([])._compute_partial_commission_settled() @openupgrade.migrate() diff --git a/sale_commission_partial_settlement/migrations/14.0.2.0.0/post-migrate.py b/sale_commission_partial_settlement/migrations/14.0.2.0.0/post-migrate.py new file mode 100644 index 000000000..ceef43bda --- /dev/null +++ b/sale_commission_partial_settlement/migrations/14.0.2.0.0/post-migrate.py @@ -0,0 +1,35 @@ +import logging + +from openupgradelib import openupgrade + +_logger = logging.getLogger(__name__) + + +def recompute_partial_commission_forecast(env): + """ + Recompute forecast for all partial commissions + """ + _logger.info( + "Computing partial commission forecast for all invoices. " + "This may take some time..." + ) + limit = 5000 + offset = 0 + while True: + aila = env["account.invoice.line.agent"].search([], limit=limit, offset=offset) + if not aila: + break + offset += limit + aila._compute_invoice_line_agent_partial_ids() + while env.all.tocompute: + aila.flush() + aila.invalidate_cache() # avoid MemoryError + _logger.info( + "%(offset)s invoice commission lines computed!" % {"offset": str(offset)} + ) + _logger.info("Computation of partial commission forecast completed!") + + +@openupgrade.migrate() +def migrate(env, version): + recompute_partial_commission_forecast(env) diff --git a/sale_commission_partial_settlement/models/__init__.py b/sale_commission_partial_settlement/models/__init__.py index da2661b51..562d39d4b 100644 --- a/sale_commission_partial_settlement/models/__init__.py +++ b/sale_commission_partial_settlement/models/__init__.py @@ -6,3 +6,4 @@ from . import sale_commission from . import sale_commission_settlement from . import sale_commission_settlement_line +from . import sale_commission_settlement_line_partial diff --git a/sale_commission_partial_settlement/models/account_invoice_line_agent.py b/sale_commission_partial_settlement/models/account_invoice_line_agent.py index c30b7ab62..62a1705b9 100644 --- a/sale_commission_partial_settlement/models/account_invoice_line_agent.py +++ b/sale_commission_partial_settlement/models/account_invoice_line_agent.py @@ -1,13 +1,14 @@ # Copyright 2023 Nextev # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import api, 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" + payment_amount_type = fields.Selection(related="commission_id.payment_amount_type") partial_settled = fields.Monetary( string="Partial Commission Amount Settled", compute="_compute_partial_settled", @@ -15,26 +16,30 @@ class AccountInvoiceLineAgent(models.Model): ) 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" + "account.invoice.line.agent.partial", + "invoice_line_agent_id", + compute="_compute_invoice_line_agent_partial_ids", + store=True, + ) + commission_settlement_line_partial_ids = fields.One2many( + "sale.commission.settlement.line.partial", + compute="_compute_commission_settlement_line_partial_ids", ) @api.depends( - "invoice_line_agent_partial_ids.amount", - "invoice_line_agent_partial_ids.agent_line.settlement_id.state", + "invoice_line_agent_partial_ids.settled_amount", ) 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 any( - settlement.state != "cancel" - for settlement in ailap.mapped("agent_line.settlement_id") - ) + ailap.settled_amount for ailap in rec.invoice_line_agent_partial_ids ) @api.depends( - "commission_id.payment_amount_type", "amount", "settled", "partial_settled" + "commission_id.payment_amount_type", + "amount", + "settled", + "partial_settled", ) def _compute_is_fully_settled(self): for rec in self: @@ -50,62 +55,51 @@ def _compute_is_fully_settled(self): == 0 ) - def _partial_commissions(self, date_payment_to): + @api.depends( + "amount", + "commission_id.payment_amount_type", + "object_id.move_id.move_type", + "object_id.move_id.line_ids.amount_residual", + ) + def _compute_invoice_line_agent_partial_ids(self): """ - This method iterates through agent invoice lines and calculates - partial commissions based on the payment amount. - If the partial payment amount is greater than the invoice line - amount, it fully settles the corresponding agent line. - Otherwise, it calculates the partial commission proportionally to - the amount paid, invoice amount and total commissions. + Create an account.invoice.line.agent.partial for each + payment term move line """ - partial_lines_to_settle = [] - partial_payment_remaining = {} - for line in self: - line_total_amount = line.amount - for ( - partial, - amount, - counterpart_line, - ) in line.invoice_id._get_reconciled_invoices_partials(): - if partial.partial_commission_settled: - continue - elif date_payment_to and date_payment_to < counterpart_line.date: - break - if partial.id in partial_payment_remaining: - payment_amount = partial_payment_remaining[partial.id][ - "remaining_amount" - ] - else: - payment_amount = amount - partial_payment_remaining[partial.id] = {"remaining_amount": amount} - if line.object_id.price_total <= payment_amount: - partial_lines_to_settle.append( - { - "invoice_line_agent_id": line.id, - "currency_id": line.currency_id.id, - "amount": line_total_amount, - "account_partial_reconcile_id": partial.id, - } + for rec in self: + # Prevent compute from running too early + if not rec.id: + continue + ailap_model = rec.invoice_line_agent_partial_ids.browse() + if rec.commission_id.payment_amount_type != "paid" or rec.amount == 0: + rec.invoice_line_agent_partial_ids = False + continue + pay_term_lines = rec.object_id.move_id.line_ids.filtered( + lambda line: line.account_internal_type in ("receivable", "payable") + ) + forecast_lines = rec.invoice_line_agent_partial_ids.mapped("move_line_id") + for move_line in pay_term_lines: + if move_line not in forecast_lines: + ailap_model.create( + {"move_line_id": move_line.id, "invoice_line_agent_id": rec.id} ) - partial_payment_remaining[partial.id] = { - "remaining_amount": amount - line.object_id.price_total - } - break - paid_in_proportion = payment_amount / line.invoice_id.amount_total - partial_commission = ( - line.invoice_id.commission_total * paid_in_proportion - ) - partial_lines_to_settle.append( - { - "invoice_line_agent_id": line.id, - "currency_id": line.currency_id.id, - "amount": partial_commission, - "account_partial_reconcile_id": partial.id, - } - ) - partial_agent_lines = self.env["account.invoice.line.agent.partial"].create( - partial_lines_to_settle + def _compute_commission_settlement_line_partial_ids(self): + for rec in self: + rec.commission_settlement_line_partial_ids = ( + rec.invoice_line_agent_partial_ids.settlement_line_partial_ids + ) + + def action_see_partial_commissions(self): + view = self.env.ref( + "sale_commission_partial_settlement.account_invoice_line_agent_form_partial_only" ) - return partial_agent_lines + return { + "name": _("Partial Commissions"), + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": self._name, + "views": [(view.id, "form")], + "target": "new", + "res_id": self.id, + } 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 index 181a7f8c9..dc4433d7c 100644 --- a/sale_commission_partial_settlement/models/account_invoice_line_agent_partial.py +++ b/sale_commission_partial_settlement/models/account_invoice_line_agent_partial.py @@ -1,28 +1,124 @@ # 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 class AccountInvoiceLineAgentPartial(models.Model): _name = "account.invoice.line.agent.partial" - _description = "Partial agent commissions" + _description = "Partial agent commissions. " + "Tracks the expected commissions." + move_line_id = fields.Many2one( + "account.move.line", + required=True, # TODO: migration? Probably cannot enforce + ondelete="cascade", + ) 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, + settlement_line_partial_ids = fields.One2many( + "sale.commission.settlement.line.partial", + "invoice_agent_partial_id", + compute="_compute_settlement_line_partial_ids", + store=True, ) - account_partial_reconcile_id = fields.Many2one("account.partial.reconcile") + account_partial_reconcile_id = fields.Many2one( + "account.partial.reconcile" + ) # TODO: Remove amount = fields.Monetary( + compute="_compute_amount", + store=True, string="Commission Amount", ) currency_id = fields.Many2one( related="invoice_line_agent_id.currency_id", ) + settled_amount = fields.Monetary( + compute="_compute_settled_amount", + store=True, + ) + is_settled = fields.Boolean( + compute="_compute_settled_amount", store=True, string="Fully settled" + ) + + move_id = fields.Many2one(related="move_line_id.move_id", string="Invoice") + date_maturity = fields.Date( + related="move_line_id.date_maturity", + store=True, + ) + invoice_line_id = fields.Many2one( + related="invoice_line_agent_id.object_id", string="Invoice Line" + ) + agent_id = fields.Many2one( + related="invoice_line_agent_id.agent_id", + store=True, + ) + invoice_date = fields.Date( + related="invoice_line_agent_id.invoice_date", + store=True, + ) + + @api.depends( + "settlement_line_partial_ids.amount", + "settlement_line_partial_ids.is_settled", + ) + def _compute_settled_amount(self): + for rec in self: + # TODO: handle different currencies + rec.settled_amount = sum( + x.amount for x in rec.settlement_line_partial_ids if x.is_settled + ) + rec.is_settled = rec.currency_id.is_zero(rec.settled_amount - rec.amount) + + @api.depends( + "move_line_id.balance", + "move_line_id.move_id.amount_total", + "invoice_line_agent_id.amount", + ) + def _compute_amount(self): + for rec in self: + # move_line_id.balance + # invoice_line_agent_id.amount + # move_line_id.move_id.amount_total_signed + # all 3 terms are signed + rec.amount = ( + rec.move_line_id.balance + * rec.invoice_line_agent_id.amount + / rec.move_line_id.move_id.amount_total_signed + ) + + @api.depends( + "invoice_line_agent_id.amount", + "move_line_id.matched_debit_ids", + "move_line_id.matched_credit_ids", + ) + def _compute_settlement_line_partial_ids(self): + """ + Cf. method _get_reconciled_invoices_partials + in odoo.addons.account.models.account_move.AccountMove. + """ + for rec in self: + if not rec.invoice_line_agent_id.amount: + rec.settlement_line_partial_ids = False + continue + pay_term_line = rec.move_line_id + matched_partials = ( + pay_term_line.matched_debit_ids + pay_term_line.matched_credit_ids + ) + if not matched_partials: + continue + existing_partial_settlements = rec.settlement_line_partial_ids + existing_partials = existing_partial_settlements.mapped( + "partial_reconcile_id" + ) + + for partial in matched_partials: + if partial not in existing_partials: + existing_partial_settlements.create( + { + "partial_reconcile_id": partial.id, + "invoice_agent_partial_id": rec.id, + "agent_line": [(4, rec.invoice_line_agent_id.id)], + } + ) diff --git a/sale_commission_partial_settlement/models/account_partial_reconcile.py b/sale_commission_partial_settlement/models/account_partial_reconcile.py index e603c6baf..5e87129d2 100644 --- a/sale_commission_partial_settlement/models/account_partial_reconcile.py +++ b/sale_commission_partial_settlement/models/account_partial_reconcile.py @@ -1,26 +1,9 @@ -from odoo import api, fields, models +from odoo import fields, models class AccountPartialReconcile(models.Model): _inherit = "account.partial.reconcile" - # 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 = any( - settlement.state != "cancel" - for settlement in rec.mapped( - "account_invoice_line_agent_partial_ids.agent_line.settlement_id" - ) - ) + ) # TODO: Remove? diff --git a/sale_commission_partial_settlement/models/sale_commission_settlement.py b/sale_commission_partial_settlement/models/sale_commission_settlement.py index 0f5225704..05e748047 100644 --- a/sale_commission_partial_settlement/models/sale_commission_settlement.py +++ b/sale_commission_partial_settlement/models/sale_commission_settlement.py @@ -18,6 +18,6 @@ class SaleCommissionSettlement(models.Model): help="The payment date used to create the settlement", ) - def unlink(self): - self.mapped("line_ids.agent_line_partial_ids").unlink() - return super().unlink() + # def unlink(self): + # self.mapped("line_ids.agent_line_partial_ids").unlink() + # return super().unlink() diff --git a/sale_commission_partial_settlement/models/sale_commission_settlement_line.py b/sale_commission_partial_settlement/models/sale_commission_settlement_line.py index a5ebda029..fee5bee06 100644 --- a/sale_commission_partial_settlement/models/sale_commission_settlement_line.py +++ b/sale_commission_partial_settlement/models/sale_commission_settlement_line.py @@ -4,12 +4,16 @@ class SettlementLine(models.Model): _inherit = "sale.commission.settlement.line" - agent_line_partial_ids = fields.Many2many( + agent_line_partial_ids = fields.Many2many( # TODO: Remove? comodel_name="account.invoice.line.agent.partial", relation="settlement_agent_line_partial_rel", column1="settlement_id", column2="agent_line_partial_id", ) + settlement_line_partial_ids = fields.Many2many( + comodel_name="sale.commission.settlement.line.partial", + relation="settlement_line_line_partial_rel", + ) settled_amount = fields.Monetary( compute="_compute_settled_amount", related=False, @@ -21,10 +25,11 @@ class SettlementLine(models.Model): def _compute_settled_amount(self): for rec in self: if rec.commission_id.payment_amount_type == "paid": - rec.settled_amount = rec.agent_line_partial_ids[:1].amount + if rec.settlement_line_partial_ids: + rec.settled_amount = sum( + x.amount for x in rec.settlement_line_partial_ids + ) + else: # TODO: Remove? + 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/models/sale_commission_settlement_line_partial.py b/sale_commission_partial_settlement/models/sale_commission_settlement_line_partial.py new file mode 100644 index 000000000..6c9221245 --- /dev/null +++ b/sale_commission_partial_settlement/models/sale_commission_settlement_line_partial.py @@ -0,0 +1,122 @@ +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class SettlementLinePartial(models.Model): + _name = "sale.commission.settlement.line.partial" + _description = "Partial settlements. " + "Tracks the effective settled amounts relative to the expected." + + settlement_line_ids = fields.Many2many( + comodel_name="sale.commission.settlement.line", + relation="settlement_line_line_partial_rel", + ) + invoice_agent_partial_id = fields.Many2one( + comodel_name="account.invoice.line.agent.partial", + required=True, + ondelete="cascade", + ) + partial_reconcile_id = fields.Many2one( + comodel_name="account.partial.reconcile", + required=True, + ondelete="cascade", + ) + amount = fields.Monetary( + compute="_compute_amount", + store=True, + ) + move_id = fields.Many2one(related="invoice_agent_partial_id.move_id") + company_id = fields.Many2one(related="move_id.company_id") + invoice_line_id = fields.Many2one( + related="invoice_agent_partial_id.invoice_line_id" + ) + invoice_date = fields.Date( + related="invoice_agent_partial_id.invoice_date", store=True + ) + invoice_line_agent_id = fields.Many2one( + related="invoice_agent_partial_id.invoice_line_agent_id" + ) + agent_id = fields.Many2one( + related="invoice_agent_partial_id.agent_id", store=True, index=True + ) + currency_id = fields.Many2one(related="invoice_agent_partial_id.currency_id") + reconcile_amount = fields.Monetary( + related="partial_reconcile_id.amount", string="Payment amount" + ) + date_maturity = fields.Date( + related="partial_reconcile_id.max_date", store=True, index=True + ) + reconcile_debit_move_id = fields.Many2one( + "account.move.line", + related="partial_reconcile_id.debit_move_id", + string="Debit move line", + ) + reconcile_credit_move_id = fields.Many2one( + "account.move.line", + related="partial_reconcile_id.credit_move_id", + string="Credit move line", + ) + # Mostly to ease user navigation + settlement_id = fields.Many2one( + "sale.commission.settlement", + compute="_compute_settlement_id", + store=True, + ) + is_settled = fields.Boolean( + compute="_compute_settlement_id", store=True, index=True + ) + + def name_get(self): + return [ + ( + rec.id, + "%(invoice_line)s - %(amount)s" + % { + "invoice_line": rec.invoice_line_id.display_name, + "amount": rec.amount, + }, + ) + for rec in self + ] + + @api.depends( + "partial_reconcile_id.amount", + "invoice_agent_partial_id.invoice_line_agent_id.amount", + "invoice_agent_partial_id.move_id.amount_total", + ) + def _compute_amount(self): + for rec in self: + # partial_reconcile_id.amount is unsigned + # invoice_agent_partial_id.move_id.amount_total is unsigned + # -> Sign depends only on + # invoice_agent_partial_id.invoice_line_agent_id.amount + rec.amount = ( + rec.partial_reconcile_id.amount + * rec.invoice_agent_partial_id.invoice_line_agent_id.amount + / rec.invoice_agent_partial_id.move_id.amount_total + ) + + @api.depends("settlement_line_ids.settlement_id.state") + def _compute_settlement_id(self): + for rec in self: + settlements = rec.settlement_line_ids.mapped("settlement_id").filtered( + lambda x: x.state != "cancel" + ) + rec.settlement_id = settlements[:1] + rec.is_settled = bool(settlements) + + def unlink(self): + for rec in self: + if rec.is_settled: + raise UserError( + _( + "Cannot delete Partial Settlement Line " + "for agent %(agent_name)s, amount %(amount)s, " + "date maturity %(date_maturity)s because it is " + "already part of a settlement", + agent_name=rec.agent_id.display_name, + amount=rec.amount, + date_maturity=rec.date_maturity, + ) + ) + super().unlink() diff --git a/sale_commission_partial_settlement/readme/ROADMAP.rst b/sale_commission_partial_settlement/readme/ROADMAP.rst new file mode 100644 index 000000000..0c9e55a2c --- /dev/null +++ b/sale_commission_partial_settlement/readme/ROADMAP.rst @@ -0,0 +1,8 @@ +Due to the data structure of the module before version 14.0.2.0.0, +there is no way to link partial settlements created before this version +of the module to a specific payment or account move, and therefore to the +new data structure existing after the changes. + +A "best effort" migration of previous data could be developed to +ensure a smoother transition between versions. + diff --git a/sale_commission_partial_settlement/security/ir.model.access.csv b/sale_commission_partial_settlement/security/ir.model.access.csv index 4c54861bb..81a6173bf 100644 --- a/sale_commission_partial_settlement/security/ir.model.access.csv +++ b/sale_commission_partial_settlement/security/ir.model.access.csv @@ -1,3 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_account_invoice_line_agent_partial,access_account_invoice_line_agent_partial,model_account_invoice_line_agent_partial,sales_team.group_sale_salesman,1,1,1,1 access_account_invoice_line_agent_partial_user,access_account_invoice_line_agent_partial_user,model_account_invoice_line_agent_partial,base.group_user,1,0,0,0 +access_sale_commission_settlement_line_partial,access_sale_commission_settlement_line_partial,model_sale_commission_settlement_line_partial,sales_team.group_sale_salesman,1,1,1,1 +access_sale_commission_settlement_line_partial_user,access_sale_commission_settlement_line_partial_user,model_sale_commission_settlement_line_partial,base.group_user,1,0,0,0 diff --git a/sale_commission_partial_settlement/static/description/index.html b/sale_commission_partial_settlement/static/description/index.html index 9cca39c7a..67ea1becc 100644 --- a/sale_commission_partial_settlement/static/description/index.html +++ b/sale_commission_partial_settlement/static/description/index.html @@ -367,7 +367,7 @@

Sales commissions based on paid amount

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:9596c13efc487356fc34bac45ca9c0138c0ec5127e6e12f5427aea0a1630db80 +!! source digest: sha256:a9e800ab8099964a8184f688597f54dba7b6f40f0110cac40e8d1e60576186e0 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/commission Translate me on Weblate Try me on Runboat

This module allows to settle commissions based on paid amount.

@@ -375,11 +375,12 @@

Sales commissions based on paid amount

Behavior for commission settlements will be as per module description.

+
+

Known issues / Roadmap

+

Due to the data structure of the module before version 14.0.2.0.0, +there is no way to link partial settlements created before this version +of the module to a specific payment or account move, and therefore to the +new data structure existing after the changes.

+

A “best effort” migration of previous data could be developed to +ensure a smoother transition between versions.

+
-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -402,16 +412,16 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association diff --git a/sale_commission_partial_settlement/tests/test_partial_settlement.py b/sale_commission_partial_settlement/tests/test_partial_settlement.py index 66ba38ea4..e1ad29bf1 100644 --- a/sale_commission_partial_settlement/tests/test_partial_settlement.py +++ b/sale_commission_partial_settlement/tests/test_partial_settlement.py @@ -19,7 +19,7 @@ def setUpClass(cls): "name": "20% fixed commission (Net amount) - Payment Based - Partial", "fix_qty": 20.0, "invoice_state": "paid", - "amount_base_type": "net_amount", + "amount_base_type": "gross_amount", "payment_amount_type": "paid", } ) @@ -61,7 +61,7 @@ def setUpClass(cls): { "name": "10% fixed commission (Net amount) - Invoice Based", "fix_qty": 10.0, - "amount_base_type": "net_amount", + "amount_base_type": "gross_amount", } ) cls.agent_biweekly = cls.res_partner_model.create( @@ -142,11 +142,10 @@ def _invoice_sale_order(self, sale_order, date=None): def _settle_agent(self, agent=None, period=None, date=None, date_payment_to=None): vals = { "date_to": ( - fields.Datetime.from_string(fields.Datetime.now()) - + relativedelta(months=period) - ) - if period - else date, + (fields.Datetime.now() + relativedelta(months=period)) + if period + else date + ), "date_payment_to": date_payment_to, } if agent: @@ -202,7 +201,11 @@ def test_partial_payment_amount_settlement(self): payment_difference_handling="open", ) self.assertTrue(invoice._get_reconciled_invoices_partials()) - self._settle_agent(self.agent_monthly, 1, date_payment_to=datetime.now()) + self._settle_agent( + self.agent_monthly, + 2, + date_payment_to=datetime.today() + relativedelta(months=2), + ) settlements = self.env["sale.commission.settlement"].search( [ ( diff --git a/sale_commission_partial_settlement/views/account_invoice_line_agent_partial_views.xml b/sale_commission_partial_settlement/views/account_invoice_line_agent_partial_views.xml new file mode 100644 index 000000000..5cbab28a6 --- /dev/null +++ b/sale_commission_partial_settlement/views/account_invoice_line_agent_partial_views.xml @@ -0,0 +1,56 @@ + + + + invoice.line.agent.partial.tree.embedded + account.invoice.line.agent.partial + 999 + + + + + + + + + + + + + invoice.line.agent.partial.tree + account.invoice.line.agent.partial + + primary + + + 0 + + + + + + + + + invoice.line.agent.partial.search + account.invoice.line.agent.partial + + + + + + + + + + Partial Commission Forecast + account.invoice.line.agent.partial + tree + + + diff --git a/sale_commission_partial_settlement/views/account_invoice_line_agent_views.xml b/sale_commission_partial_settlement/views/account_invoice_line_agent_views.xml new file mode 100644 index 000000000..62d1e2c0c --- /dev/null +++ b/sale_commission_partial_settlement/views/account_invoice_line_agent_views.xml @@ -0,0 +1,52 @@ + + + + account.invoice.line.agent + + + + +