From 41d48f36b48b9c5fd13d85ffcfebd71ed484b992 Mon Sep 17 00:00:00 2001 From: Aungkokolin1997 Date: Mon, 19 Sep 2022 23:11:50 +0630 Subject: [PATCH 01/85] [MIG] sale_commission: Migration to 15.0 --- commission/README.rst | 198 +++++++ commission/__init__.py | 2 + commission/__manifest__.py | 29 + commission/data/menuitem_data.xml | 19 + commission/demo/commission_and_agent_demo.xml | 36 ++ commission/models/__init__.py | 6 + commission/models/account_move.py | 41 ++ commission/models/commission.py | 54 ++ commission/models/commission_mixin.py | 153 +++++ commission/models/product_template.py | 9 + commission/models/res_partner.py | 56 ++ commission/models/settlement.py | 212 +++++++ commission/readme/CONFIGURE.rst | 38 ++ commission/readme/CONTRIBUTORS.rst | 19 + commission/readme/DESCRIPTION.rst | 10 + commission/readme/ROADMAP.rst | 5 + commission/readme/USAGE.rst | 35 ++ commission/security/commission_security.xml | 12 + commission/security/ir.model.access.csv | 12 + commission/static/description/icon.png | Bin 0 -> 19238 bytes commission/static/description/index.html | 547 ++++++++++++++++++ commission/tests/__init__.py | 1 + commission/tests/test_commission.py | 193 ++++++ commission/views/account_move_view.xml | 31 + commission/views/commission_mixin_views.xml | 18 + .../views/commission_settlement_report.xml | 12 + .../views/commission_settlement_views.xml | 174 ++++++ commission/views/commission_views.xml | 65 +++ commission/views/product_template_views.xml | 16 + .../views/report_settlement_templates.xml | 61 ++ commission/views/res_partner_views.xml | 81 +++ commission/wizards/__init__.py | 2 + commission/wizards/wizard_invoice.py | 68 +++ commission/wizards/wizard_invoice.xml | 52 ++ commission/wizards/wizard_settle.py | 148 +++++ commission/wizards/wizard_settle.xml | 54 ++ 36 files changed, 2469 insertions(+) create mode 100644 commission/README.rst create mode 100644 commission/__init__.py create mode 100644 commission/__manifest__.py create mode 100644 commission/data/menuitem_data.xml create mode 100644 commission/demo/commission_and_agent_demo.xml create mode 100644 commission/models/__init__.py create mode 100644 commission/models/account_move.py create mode 100644 commission/models/commission.py create mode 100644 commission/models/commission_mixin.py create mode 100644 commission/models/product_template.py create mode 100644 commission/models/res_partner.py create mode 100644 commission/models/settlement.py create mode 100644 commission/readme/CONFIGURE.rst create mode 100644 commission/readme/CONTRIBUTORS.rst create mode 100644 commission/readme/DESCRIPTION.rst create mode 100644 commission/readme/ROADMAP.rst create mode 100644 commission/readme/USAGE.rst create mode 100644 commission/security/commission_security.xml create mode 100644 commission/security/ir.model.access.csv create mode 100644 commission/static/description/icon.png create mode 100644 commission/static/description/index.html create mode 100644 commission/tests/__init__.py create mode 100644 commission/tests/test_commission.py create mode 100644 commission/views/account_move_view.xml create mode 100644 commission/views/commission_mixin_views.xml create mode 100644 commission/views/commission_settlement_report.xml create mode 100644 commission/views/commission_settlement_views.xml create mode 100644 commission/views/commission_views.xml create mode 100644 commission/views/product_template_views.xml create mode 100644 commission/views/report_settlement_templates.xml create mode 100644 commission/views/res_partner_views.xml create mode 100644 commission/wizards/__init__.py create mode 100644 commission/wizards/wizard_invoice.py create mode 100644 commission/wizards/wizard_invoice.xml create mode 100644 commission/wizards/wizard_settle.py create mode 100644 commission/wizards/wizard_settle.xml diff --git a/commission/README.rst b/commission/README.rst new file mode 100644 index 000000000..48a9a0409 --- /dev/null +++ b/commission/README.rst @@ -0,0 +1,198 @@ +=========== +Commissions +=========== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fcommission-lightgray.png?logo=github + :target: https://github.com/OCA/commission/tree/15.0/commission + :alt: OCA/commission +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/commission-15-0/commission-15-0-commission + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/165/15.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module provides the base functions for commission operations to enable the +following: + +- Define agents with their commissions +- Assign agents to partners +- Create settlements to summarize commissions for certain periods +- Create vendor bills from settlements + +You can define which base amount is going to be taken into account: net amount +(based on margin) or gross amount (line subtotal amount) + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +For adding commissions: + +#. Go to *Invoicing > Configuration > Commission Management > Commission types*. +#. Edit or create a new record. +#. Select a name for distinguishing that type. +#. Select the percentage type of the commission: + + * **Fixed percentage**: all commissions are computed with a fixed + percentage. You can fill the percentage in the field "Fixed percentage". + * **By sections**: percentage varies depending amount intervals. You can + fill intervals and percentages in the section "Rate definition". + +#. Select the base amount for computing the percentage: + + * **Gross Amount**: percentage is computed from the amount put on + sales order/invoice. + * **Net Amount**: percentage is computed from the profit only, taken the + cost from the product. + +For adding new agents: + +#. Go to *Invoicing > Vendors > Agents*. You can also access from + *Contacts > Contacts* or *Sales > Orders > Customers*. +#. Edit or create a new record. +#. On "Sales & Purchases" page, mark "Agent" check. It should be checked if + you have accessed from first menu option. +#. There's a new page called "Agent information". In it, you can set following + data: + + * The agent type, being in this base module "External agent" the only + existing configuration. It can be extended with `hr_commission` module + for setting an "Employee" agent type. + * The associated commission type. + * The settlement period, where you can select "Bi-weekly", "Monthly", "Quaterly", + "Semi-annual" or "Annual". + + You will also be able to see the settlements that have been made to this + agent from this page. + +Usage +===== + +For setting default agents in partners: + +#. Go to *Invoicing > Customers > Customers* or *Contacts > Contacts*. +#. Edit or create a new record. +#. On "Sales & Purchases" page, you will see a field called "Agents" where + they can be added. You can put the number of agents you want, but you can't + select specific commission for each partner in this base module. + +For settling the commissions to agents: + +#. Go to *Invoicing > Vendors > Commissions > Settle commissions*. +#. On the window that appears, you should select the date up to which you + want to create commissions. It should be at least one day after the last + period date. For example, if you settlements are monthly, you have to put + at least the first day of the following month. +#. You can settle only certain agents if you select them on the "Agents" + section. Leave it empty for settling all. +#. Click on "Make settlements" button. +#. If there are new settlements, they will be shown after this. + +For invoicing the settlements (only for external agents): + +#. Go to *Invoicing > Vendors > Create commission invoices*. +#. On the window that appears, you can select following data: + + * Product. It should be a service product for being coherent. + * Journal: To be selected between existing purchase journals. + * Date: If you want to choose a specific invoice date. You can leave it + blank if you prefer. + * Settlements: For selecting specific settlements to invoice. You can leave + it blank as well for invoicing all the pending settlements. + +#. If you want to invoice a specific settlement, you can navigate to it in + *Invoicing > Vendors > Settlements*, and click on "Make invoice" + button. + +Known issues / Roadmap +====================== + +* Make it totally multi-company aware. +* Be multi-currency aware for settlements. +* Allow to calculate and pay in other currency different from company one. +* Set agent popup window with a kanban view with richer information and + mobile friendly. + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* Pexego. +* Davide Corio +* Joao Alfredo Gama Batista +* Sandy Carter +* Giorgio Borelli +* Daniel Campos +* Oihane Crucelaegui +* Nicola Malcontenti +* Aitor Bouzas + +* `Tecnativa `__: + + * Pedro M. Baeza + * Manuel Calero + +* `Quartile `__: + + * Aung Ko Ko Lin + * Yoshi Tashiro + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-pedrobaeza| image:: https://github.com/pedrobaeza.png?size=40px + :target: https://github.com/pedrobaeza + :alt: pedrobaeza + +Current `maintainer `__: + +|maintainer-pedrobaeza| + +This module is part of the `OCA/commission `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/commission/__init__.py b/commission/__init__.py new file mode 100644 index 000000000..aee8895e7 --- /dev/null +++ b/commission/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/commission/__manifest__.py b/commission/__manifest__.py new file mode 100644 index 000000000..a2d9c57e6 --- /dev/null +++ b/commission/__manifest__.py @@ -0,0 +1,29 @@ +# Copyright 2014-2020 Tecnativa - Pedro M. Baeza +# Copyright 2020 Tecnativa - Manuel Calero +{ + "name": "Commissions", + "version": "15.0.1.0.0", + "author": "Tecnativa," "Odoo Community Association (OCA)", + "category": "Invoicing", + "license": "AGPL-3", + "depends": ["account"], + "website": "https://github.com/OCA/commission", + "maintainers": ["pedrobaeza"], + "data": [ + "security/ir.model.access.csv", + "data/menuitem_data.xml", + "views/account_move_view.xml", + "security/commission_security.xml", + "views/commission_views.xml", + "views/commission_settlement_views.xml", + "views/commission_mixin_views.xml", + "views/product_template_views.xml", + "views/res_partner_views.xml", + "views/commission_settlement_report.xml", + "views/report_settlement_templates.xml", + "wizards/wizard_invoice.xml", + "wizards/wizard_settle.xml", + ], + "demo": ["demo/commission_and_agent_demo.xml"], + "installable": True, +} diff --git a/commission/data/menuitem_data.xml b/commission/data/menuitem_data.xml new file mode 100644 index 000000000..316bec769 --- /dev/null +++ b/commission/data/menuitem_data.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/commission/demo/commission_and_agent_demo.xml b/commission/demo/commission_and_agent_demo.xml new file mode 100644 index 000000000..a158f9261 --- /dev/null +++ b/commission/demo/commission_and_agent_demo.xml @@ -0,0 +1,36 @@ + + + + + 10% fixed commission - Invoice Based + 10.0 + + + + Pritesh Sales Agent + Ahmedabad + 380007 + + 56 Beijing street + + + + + Eiffel pvt ltd + Ahmedabad + 380007 + + Wall Street 2 + + + + + Tiny Belgium + Belgium + 2457 + + Belgium Gao + + + + diff --git a/commission/models/__init__.py b/commission/models/__init__.py new file mode 100644 index 000000000..78325f66e --- /dev/null +++ b/commission/models/__init__.py @@ -0,0 +1,6 @@ +from . import commission +from . import commission_mixin +from . import product_template +from . import res_partner +from . import settlement +from . import account_move diff --git a/commission/models/account_move.py b/commission/models/account_move.py new file mode 100644 index 000000000..159c297cb --- /dev/null +++ b/commission/models/account_move.py @@ -0,0 +1,41 @@ +# Copyright 2014-2018 Tecnativa - Pedro M. Baeza +# Copyright 2020 Tecnativa - Manuel Calero +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class AccountMove(models.Model): + _inherit = "account.move" + + def button_cancel(self): + """Put settlements associated to the invoices in exception.""" + self.mapped("line_ids.settlement_id").write({"state": "except_invoice"}) + return super().button_cancel() + + def action_post(self): + """Put settlements associated to the invoices in invoiced state.""" + self.mapped("line_ids.settlement_id").write({"state": "invoiced"}) + return super().action_post() + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + settlement_id = fields.Many2one( + comodel_name="commission.settlement", + help="Settlement that generates this invoice line", + copy=False, + ) + + def _copy_data_extend_business_fields(self, values): + """ + We don't want to loose the settlement from the line when reversing the line if + it was a refund. + We need to include it, but as we don't want change it everytime, we will add + the data when a context key is passed + """ + super()._copy_data_extend_business_fields(values) + if self.settlement_id and self.env.context.get("include_settlement", False): + values["settlement_id"] = self.settlement_id.id + return diff --git a/commission/models/commission.py b/commission/models/commission.py new file mode 100644 index 000000000..658ac3ecd --- /dev/null +++ b/commission/models/commission.py @@ -0,0 +1,54 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, exceptions, fields, models + + +class Commission(models.Model): + _name = "commission" + _description = "Commission" + + name = fields.Char(required=True) + commission_type = fields.Selection( + selection=[("fixed", "Fixed percentage"), ("section", "By sections")], + string="Type", + required=True, + default="fixed", + ) + fix_qty = fields.Float(string="Fixed percentage") + section_ids = fields.One2many( + string="Sections", + comodel_name="commission.section", + inverse_name="commission_id", + ) + active = fields.Boolean(default=True) + amount_base_type = fields.Selection( + selection=[("gross_amount", "Gross Amount"), ("net_amount", "Net Amount")], + string="Base", + required=True, + default="gross_amount", + ) + + def calculate_section(self, base): + self.ensure_one() + for section in self.section_ids: + if section.amount_from <= base <= section.amount_to: + return base * section.percent / 100.0 + return 0.0 + + +class CommissionSection(models.Model): + _name = "commission.section" + _description = "Commission section" + + commission_id = fields.Many2one("commission", string="Commission") + amount_from = fields.Float(string="From") + amount_to = fields.Float(string="To") + percent = fields.Float(required=True) + + @api.constrains("amount_from", "amount_to") + def _check_amounts(self): + for section in self: + if section.amount_to < section.amount_from: + raise exceptions.ValidationError( + _("The lower limit cannot be greater than upper one.") + ) diff --git a/commission/models/commission_mixin.py b/commission/models/commission_mixin.py new file mode 100644 index 000000000..c925f2d31 --- /dev/null +++ b/commission/models/commission_mixin.py @@ -0,0 +1,153 @@ +# Copyright 2018-2020 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models + + +class CommissionMixin(models.AbstractModel): + _name = "commission.mixin" + _description = ( + "Mixin model for applying to any object that wants to handle commissions" + ) + + agent_ids = fields.One2many( + comodel_name="commission.line.mixin", + inverse_name="object_id", + string="Agents & commissions", + help="Agents/Commissions related to the invoice line.", + compute="_compute_agent_ids", + readonly=False, + store=True, + copy=True, + ) + product_id = fields.Many2one(comodel_name="product.product", string="Product") + commission_free = fields.Boolean( + string="Comm. free", + related="product_id.commission_free", + store=True, + readonly=True, + ) + commission_status = fields.Char( + compute="_compute_commission_status", + string="Commission", + ) + + def _prepare_agent_vals(self, agent): + return {"agent_id": agent.id, "commission_id": agent.commission_id.id} + + def _prepare_agents_vals_partner(self, partner): + """Utility method for getting agents creation dictionary of a partner.""" + return [(0, 0, self._prepare_agent_vals(agent)) for agent in partner.agent_ids] + + @api.depends("commission_free") + def _compute_agent_ids(self): + """Empty method that needs to be implemented in children models.""" + raise NotImplementedError() + + @api.depends("commission_free", "agent_ids") + def _compute_commission_status(self): + for line in self: + if line.commission_free: + line.commission_status = _("Comm. free") + elif len(line.agent_ids) == 0: + line.commission_status = _("No commission agents") + elif len(line.agent_ids) == 1: + line.commission_status = _("1 commission agent") + else: + line.commission_status = _("%s commission agents") % ( + len(line.agent_ids), + ) + + def recompute_agents(self): + self._compute_agent_ids() + + def button_edit_agents(self): + self.ensure_one() + view = self.env.ref("commission.view_commission_mixin_agent_only") + return { + "name": _("Agents"), + "type": "ir.actions.act_window", + "view_type": "form", + "view_mode": "form", + "res_model": self._name, + "views": [(view.id, "form")], + "view_id": view.id, + "target": "new", + "res_id": self.id, + "context": self.env.context, + } + + +class CommissionLineMixin(models.AbstractModel): + _name = "commission.line.mixin" + _description = ( + "Mixin model for having commission agent lines in " + "any object inheriting from this one" + ) + _rec_name = "agent_id" + + _sql_constraints = [ + ( + "unique_agent", + "UNIQUE(object_id, agent_id)", + "You can only add one time each agent.", + ) + ] + + object_id = fields.Many2one( + comodel_name="commission.mixin", + ondelete="cascade", + required=True, + copy=False, + string="Parent", + ) + agent_id = fields.Many2one( + comodel_name="res.partner", + domain="[('agent', '=', True)]", + ondelete="restrict", + required=True, + ) + commission_id = fields.Many2one( + comodel_name="commission", + ondelete="restrict", + required=True, + compute="_compute_commission_id", + store=True, + readonly=False, + copy=True, + ) + amount = fields.Monetary( + string="Commission Amount", + compute="_compute_amount", + store=True, + ) + # Fields to be overriden with proper source (via related or computed field) + currency_id = fields.Many2one(comodel_name="res.currency") + + def _compute_amount(self): + """Compute method to be implemented by inherited models.""" + raise NotImplementedError() + + def _get_commission_amount(self, commission, subtotal, product, quantity): + """Get the commission amount for the data given. It's called by + compute methods of children models. + + This means the inheritable method for modifying the amount of the commission. + """ + self.ensure_one() + if product.commission_free or not commission: + return 0.0 + if commission.amount_base_type == "net_amount": + # If subtotal (sale_price * quantity) is less than + # standard_price * quantity, it means that we are selling at + # lower price than we bought, so set amount_base to 0 + subtotal = max([0, subtotal - product.standard_price * quantity]) + if commission.commission_type == "fixed": + return subtotal * (commission.fix_qty / 100.0) + elif commission.commission_type == "section": + return commission.calculate_section(subtotal) + + @api.depends("agent_id") + def _compute_commission_id(self): + for record in self: + record.commission_id = record.agent_id.commission_id diff --git a/commission/models/product_template.py b/commission/models/product_template.py new file mode 100644 index 000000000..8b31bd282 --- /dev/null +++ b/commission/models/product_template.py @@ -0,0 +1,9 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + commission_free = fields.Boolean(string="Free of commission", default=False) diff --git a/commission/models/res_partner.py b/commission/models/res_partner.py new file mode 100644 index 000000000..c1049dc43 --- /dev/null +++ b/commission/models/res_partner.py @@ -0,0 +1,56 @@ +# Copyright 2016-2019 Tecnativa - Pedro M. Baeza +# Copyright 2018 Tecnativa - Ernesto Tejeda +# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, fields, models + + +class ResPartner(models.Model): + """Add some fields related to commissions""" + + _inherit = "res.partner" + + agent_ids = fields.Many2many( + comodel_name="res.partner", + relation="partner_agent_rel", + column1="partner_id", + column2="agent_id", + domain=[("agent", "=", True)], + readonly=False, + string="Agents", + ) + # Fields for the partner when it acts as an agent + agent = fields.Boolean( + string="Creditor/Agent", + help="Check this field if the partner is a creditor or an agent.", + ) + agent_type = fields.Selection( + selection=[("agent", "External agent")], + string="Type", + default="agent", + ) + commission_id = fields.Many2one( + string="Commission", + comodel_name="commission", + help="This is the default commission used in the sales where this " + "agent is assigned. It can be changed on each operation if " + "needed.", + ) + settlement = fields.Selection( + selection=[ + ("biweekly", "Bi-weekly"), + ("monthly", "Monthly"), + ("quaterly", "Quarterly"), + ("semi", "Semi-annual"), + ("annual", "Annual"), + ], + string="Settlement period", + default="monthly", + ) + + @api.model + def _commercial_fields(self): + """Add agents to commercial fields that are synced from parent to childs.""" + res = super()._commercial_fields() + res.append("agent_ids") + return res diff --git a/commission/models/settlement.py b/commission/models/settlement.py new file mode 100644 index 000000000..b0fe15d90 --- /dev/null +++ b/commission/models/settlement.py @@ -0,0 +1,212 @@ +# Copyright 2014-2020 Tecnativa - Pedro M. Baeza +# Copyright 2020 Tecnativa - Manuel Calero +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from itertools import groupby + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.tests.common import Form + + +class Settlement(models.Model): + _name = "commission.settlement" + _description = "Settlement" + + def _default_currency(self): + return self.env.user.company_id.currency_id.id + + name = fields.Char() + total = fields.Float(compute="_compute_total", readonly=True, store=True) + date_from = fields.Date(string="From") + date_to = fields.Date(string="To") + agent_id = fields.Many2one( + comodel_name="res.partner", domain="[('agent', '=', True)]" + ) + agent_type = fields.Selection(related="agent_id.agent_type") + settlement_type = fields.Char( + readonly=True, + help="e.g. 'invoice'. A technical field to control the view presentation.", + ) + line_ids = fields.One2many( + comodel_name="commission.settlement.line", + inverse_name="settlement_id", + string="Settlement lines", + readonly=True, + ) + state = fields.Selection( + selection=[ + ("settled", "Settled"), + ("invoiced", "Invoiced"), + ("cancel", "Canceled"), + ("except_invoice", "Invoice exception"), + ], + readonly=True, + default="settled", + ) + invoice_line_ids = fields.One2many( + comodel_name="account.move.line", + inverse_name="settlement_id", + string="Generated invoice", + readonly=True, + ) + # TODO: To be removed + invoice_id = fields.Many2one( + store=True, + comodel_name="account.move", + compute="_compute_invoice_id", + ) + currency_id = fields.Many2one( + comodel_name="res.currency", readonly=True, default=_default_currency + ) + company_id = fields.Many2one( + comodel_name="res.company", + default=lambda self: self.env.user.company_id, + required=True, + ) + + @api.depends("line_ids", "line_ids.settled_amount") + def _compute_total(self): + for record in self: + record.total = sum(record.mapped("line_ids.settled_amount")) + + @api.depends("invoice_line_ids") + def _compute_invoice_id(self): + for record in self: + record.invoice_id = record.invoice_line_ids[:1].move_id + + def action_cancel(self): + if any(x.state != "settled" for x in self): + raise UserError(_("Cannot cancel an invoiced settlement.")) + self.write({"state": "cancel"}) + + def unlink(self): + """Allow to delete only cancelled settlements""" + if any(x.state == "invoiced" for x in self): + raise UserError(_("You can't delete invoiced settlements.")) + return super().unlink() + + def action_invoice(self): + return { + "type": "ir.actions.act_window", + "name": _("Make invoice"), + "res_model": "commission.make.invoice", + "view_type": "form", + "target": "new", + "view_mode": "form", + "context": {"settlement_ids": self.ids}, + } + + def _get_invoice_partner(self): + return self[0].agent_id + + def _prepare_invoice(self, journal, product, date=False): + + move_form = Form( + self.env["account.move"].with_context(default_move_type="in_invoice") + ) + + if date: + move_form.invoice_date = date + partner = self._get_invoice_partner() + move_form.partner_id = partner + move_form.journal_id = journal + for settlement in self: + with move_form.invoice_line_ids.new() as line_form: + line_form.product_id = product + line_form.quantity = -1 if settlement.total < 0 else 1 + line_form.price_unit = abs(settlement.total) + # Put period string + partner = self.agent_id + lang = self.env["res.lang"].search( + [ + ( + "code", + "=", + partner.lang or self.env.context.get("lang", "en_US"), + ) + ] + ) + date_from = fields.Date.from_string(settlement.date_from) + date_to = fields.Date.from_string(settlement.date_to) + line_form.name += "\n" + _( + "Period: from %(date_from)s to %(date_to)s", + date_from=date_from.strftime(lang.date_format), + date_to=date_to.strftime(lang.date_format), + ) + line_form.currency_id = ( + settlement.currency_id + ) # todo or compute agent currency_id?\ + line_form.settlement_id = settlement + vals = move_form._values_to_save(all_fields=True) + return vals + + def _get_invoice_grouping_keys(self): + return ["company_id", "agent_id"] + + def make_invoices(self, journal, product, date=False, grouped=False): + invoice_vals_list = [] + settlement_obj = self.env[self._name] + if grouped: + invoice_grouping_keys = self._get_invoice_grouping_keys() + settlements = groupby( + self.sorted( + key=lambda x: [ + x._fields[grouping_key].convert_to_write(x[grouping_key], x) + for grouping_key in invoice_grouping_keys + ], + ), + key=lambda x: [ + x._fields[grouping_key].convert_to_write(x[grouping_key], x) + for grouping_key in invoice_grouping_keys + ], + ) + grouped_settlements = [ + settlement_obj.union(*list(sett)) + for _grouping_keys, sett in settlements + ] + else: + grouped_settlements = self + for settlement in grouped_settlements: + invoice_vals = settlement._prepare_invoice(journal, product, date) + invoice_vals_list.append(invoice_vals) + invoices = self.env["account.move"].create(invoice_vals_list) + invoices.sudo().filtered(lambda m: m.amount_total < 0).with_context( + include_settlement=True + ).action_switch_invoice_into_refund_credit_note() + self.write({"state": "invoiced"}) + return invoices + + +class SettlementLine(models.Model): + _name = "commission.settlement.line" + _description = "Line of a commission settlement" + + settlement_id = fields.Many2one( + "commission.settlement", + readonly=True, + ondelete="cascade", + required=True, + ) + + date = fields.Date() + agent_id = fields.Many2one( + comodel_name="res.partner", + readonly=True, + store=True, + ) + settled_amount = fields.Monetary(readonly=True, store=True) + currency_id = fields.Many2one( + comodel_name="res.currency", + store=True, + readonly=True, + ) + commission_id = fields.Many2one( + comodel_name="commission", + readonly=True, + store=True, + ) + company_id = fields.Many2one( + comodel_name="res.company", + related="settlement_id.company_id", + ) diff --git a/commission/readme/CONFIGURE.rst b/commission/readme/CONFIGURE.rst new file mode 100644 index 000000000..a3eec5c92 --- /dev/null +++ b/commission/readme/CONFIGURE.rst @@ -0,0 +1,38 @@ +For adding commissions: + +#. Go to *Invoicing > Configuration > Commission Management > Commission types*. +#. Edit or create a new record. +#. Select a name for distinguishing that type. +#. Select the percentage type of the commission: + + * **Fixed percentage**: all commissions are computed with a fixed + percentage. You can fill the percentage in the field "Fixed percentage". + * **By sections**: percentage varies depending amount intervals. You can + fill intervals and percentages in the section "Rate definition". + +#. Select the base amount for computing the percentage: + + * **Gross Amount**: percentage is computed from the amount put on + sales order/invoice. + * **Net Amount**: percentage is computed from the profit only, taken the + cost from the product. + +For adding new agents: + +#. Go to *Invoicing > Vendors > Agents*. You can also access from + *Contacts > Contacts* or *Sales > Orders > Customers*. +#. Edit or create a new record. +#. On "Sales & Purchases" page, mark "Agent" check. It should be checked if + you have accessed from first menu option. +#. There's a new page called "Agent information". In it, you can set following + data: + + * The agent type, being in this base module "External agent" the only + existing configuration. It can be extended with `hr_commission` module + for setting an "Employee" agent type. + * The associated commission type. + * The settlement period, where you can select "Bi-weekly", "Monthly", "Quaterly", + "Semi-annual" or "Annual". + + You will also be able to see the settlements that have been made to this + agent from this page. diff --git a/commission/readme/CONTRIBUTORS.rst b/commission/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..77b751822 --- /dev/null +++ b/commission/readme/CONTRIBUTORS.rst @@ -0,0 +1,19 @@ +* Pexego. +* Davide Corio +* Joao Alfredo Gama Batista +* Sandy Carter +* Giorgio Borelli +* Daniel Campos +* Oihane Crucelaegui +* Nicola Malcontenti +* Aitor Bouzas + +* `Tecnativa `__: + + * Pedro M. Baeza + * Manuel Calero + +* `Quartile `__: + + * Aung Ko Ko Lin + * Yoshi Tashiro diff --git a/commission/readme/DESCRIPTION.rst b/commission/readme/DESCRIPTION.rst new file mode 100644 index 000000000..5e5ab40bc --- /dev/null +++ b/commission/readme/DESCRIPTION.rst @@ -0,0 +1,10 @@ +This module provides the base functions for commission operations to enable the +following: + +- Define agents with their commissions +- Assign agents to partners +- Create settlements to summarize commissions for certain periods +- Create vendor bills from settlements + +You can define which base amount is going to be taken into account: net amount +(based on margin) or gross amount (line subtotal amount) diff --git a/commission/readme/ROADMAP.rst b/commission/readme/ROADMAP.rst new file mode 100644 index 000000000..64f69a38f --- /dev/null +++ b/commission/readme/ROADMAP.rst @@ -0,0 +1,5 @@ +* Make it totally multi-company aware. +* Be multi-currency aware for settlements. +* Allow to calculate and pay in other currency different from company one. +* Set agent popup window with a kanban view with richer information and + mobile friendly. diff --git a/commission/readme/USAGE.rst b/commission/readme/USAGE.rst new file mode 100644 index 000000000..95ea9fab7 --- /dev/null +++ b/commission/readme/USAGE.rst @@ -0,0 +1,35 @@ +For setting default agents in partners: + +#. Go to *Invoicing > Customers > Customers* or *Contacts > Contacts*. +#. Edit or create a new record. +#. On "Sales & Purchases" page, you will see a field called "Agents" where + they can be added. You can put the number of agents you want, but you can't + select specific commission for each partner in this base module. + +For settling the commissions to agents: + +#. Go to *Invoicing > Vendors > Commissions > Settle commissions*. +#. On the window that appears, you should select the date up to which you + want to create commissions. It should be at least one day after the last + period date. For example, if you settlements are monthly, you have to put + at least the first day of the following month. +#. You can settle only certain agents if you select them on the "Agents" + section. Leave it empty for settling all. +#. Click on "Make settlements" button. +#. If there are new settlements, they will be shown after this. + +For invoicing the settlements (only for external agents): + +#. Go to *Invoicing > Vendors > Create commission invoices*. +#. On the window that appears, you can select following data: + + * Product. It should be a service product for being coherent. + * Journal: To be selected between existing purchase journals. + * Date: If you want to choose a specific invoice date. You can leave it + blank if you prefer. + * Settlements: For selecting specific settlements to invoice. You can leave + it blank as well for invoicing all the pending settlements. + +#. If you want to invoice a specific settlement, you can navigate to it in + *Invoicing > Vendors > Settlements*, and click on "Make invoice" + button. diff --git a/commission/security/commission_security.xml b/commission/security/commission_security.xml new file mode 100644 index 000000000..9b8d52249 --- /dev/null +++ b/commission/security/commission_security.xml @@ -0,0 +1,12 @@ + + + + + Commission settlement multi company rule + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + diff --git a/commission/security/ir.model.access.csv b/commission/security/ir.model.access.csv new file mode 100644 index 000000000..e33099b90 --- /dev/null +++ b/commission/security/ir.model.access.csv @@ -0,0 +1,12 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_commission_manager,access_commission_manager,model_commission,account.group_account_manager,1,1,1,1 +access_commission,access_commission,model_commission,account.group_account_invoice,1,0,0,0 +access_commission_user,access_commission_user,model_commission,base.group_user,1,0,0,0 +access_commission_section_manager,access_commission_section_manager,model_commission_section,account.group_account_manager,1,1,1,1 +access_commission_section_user,access_commission_section_user,model_commission_section,base.group_user,1,0,0,0 +access_commission_make_settle,access_commission_make_settle,model_commission_make_settle,account.group_account_invoice,1,1,1,0 +access_commission_settlement_manager,access_commission_settlement_manager,model_commission_settlement,account.group_account_manager,1,1,1,1 +access_commission_settlement_user,access_commission_settlement_user,model_commission_settlement,account.group_account_invoice,1,0,0,0 +access_commission_settlement_line_manager,access_commission_settlement_line_manager,model_commission_settlement_line,account.group_account_manager,1,1,1,1 +access_commission_settlement_line_user,access_commission_settlement_line_user,model_commission_settlement_line,account.group_account_invoice,1,0,0,0 +access_commission_make_invoice,access_commission_make_invoice,model_commission_make_invoice,account.group_account_manager,1,1,1,0 diff --git a/commission/static/description/icon.png b/commission/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0539927894add59cc8db869b17fb5490c765a9b6 GIT binary patch literal 19238 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 lX7tu2x4gIydCwmk`d_A + + + + + +Commissions + + + +

+

Commissions

+ + +

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

+

This module provides the base functions for commission operations to enable the +following:

+
    +
  • Define agents with their commissions
  • +
  • Assign agents to partners
  • +
  • Create settlements to summarize commissions for certain periods
  • +
  • Create vendor bills from settlements
  • +
+

You can define which base amount is going to be taken into account: net amount +(based on margin) or gross amount (line subtotal amount)

+

Table of contents

+ +
+

Configuration

+

For adding commissions:

+
    +
  1. Go to Invoicing > Configuration > Commission Management > Commission types.
  2. +
  3. Edit or create a new record.
  4. +
  5. Select a name for distinguishing that type.
  6. +
  7. Select the percentage type of the commission:
      +
    • Fixed percentage: all commissions are computed with a fixed +percentage. You can fill the percentage in the field ???Fixed percentage???.
    • +
    • By sections: percentage varies depending amount intervals. You can +fill intervals and percentages in the section ???Rate definition???.
    • +
    +
  8. +
  9. Select the base amount for computing the percentage:
      +
    • Gross Amount: percentage is computed from the amount put on +sales order/invoice.
    • +
    • Net Amount: percentage is computed from the profit only, taken the +cost from the product.
    • +
    +
  10. +
+

For adding new agents:

+
    +
  1. Go to Invoicing > Vendors > Agents. You can also access from +Contacts > Contacts or Sales > Orders > Customers.

    +
  2. +
  3. Edit or create a new record.

    +
  4. +
  5. On ???Sales & Purchases??? page, mark ???Agent??? check. It should be checked if +you have accessed from first menu option.

    +
  6. +
  7. There???s a new page called ???Agent information???. In it, you can set following +data:

    +
      +
    • The agent type, being in this base module ???External agent??? the only +existing configuration. It can be extended with hr_commission module +for setting an ???Employee??? agent type.
    • +
    • The associated commission type.
    • +
    • The settlement period, where you can select ???Bi-weekly???, ???Monthly???, ???Quaterly???, +???Semi-annual??? or ???Annual???.
    • +
    +

    You will also be able to see the settlements that have been made to this +agent from this page.

    +
  8. +
+
+
+

Usage

+

For setting default agents in partners:

+
    +
  1. Go to Invoicing > Customers > Customers or Contacts > Contacts.
  2. +
  3. Edit or create a new record.
  4. +
  5. On ???Sales & Purchases??? page, you will see a field called ???Agents??? where +they can be added. You can put the number of agents you want, but you can???t +select specific commission for each partner in this base module.
  6. +
+

For settling the commissions to agents:

+
    +
  1. Go to Invoicing > Vendors > Commissions > Settle commissions.
  2. +
  3. On the window that appears, you should select the date up to which you +want to create commissions. It should be at least one day after the last +period date. For example, if you settlements are monthly, you have to put +at least the first day of the following month.
  4. +
  5. You can settle only certain agents if you select them on the ???Agents??? +section. Leave it empty for settling all.
  6. +
  7. Click on ???Make settlements??? button.
  8. +
  9. If there are new settlements, they will be shown after this.
  10. +
+

For invoicing the settlements (only for external agents):

+
    +
  1. Go to Invoicing > Vendors > Create commission invoices.
  2. +
  3. On the window that appears, you can select following data:
      +
    • Product. It should be a service product for being coherent.
    • +
    • Journal: To be selected between existing purchase journals.
    • +
    • Date: If you want to choose a specific invoice date. You can leave it +blank if you prefer.
    • +
    • Settlements: For selecting specific settlements to invoice. You can leave +it blank as well for invoicing all the pending settlements.
    • +
    +
  4. +
  5. If you want to invoice a specific settlement, you can navigate to it in +Invoicing > Vendors > Settlements, and click on ???Make invoice??? +button.
  6. +
+
+
+

Known issues / Roadmap

+
    +
  • Make it totally multi-company aware.
  • +
  • Be multi-currency aware for settlements.
  • +
  • Allow to calculate and pay in other currency different from company one.
  • +
  • Set agent popup window with a kanban view with richer information and +mobile friendly.
  • +
+
+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

pedrobaeza

+

This module is part of the OCA/commission project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/commission/tests/__init__.py b/commission/tests/__init__.py new file mode 100644 index 000000000..a6e0c2e87 --- /dev/null +++ b/commission/tests/__init__.py @@ -0,0 +1 @@ +from . import test_commission diff --git a/commission/tests/test_commission.py b/commission/tests/test_commission.py new file mode 100644 index 000000000..d5f773cde --- /dev/null +++ b/commission/tests/test_commission.py @@ -0,0 +1,193 @@ +# Copyright 2016-2019 Tecnativa - Pedro M. Baeza +# Copyright 2020 Tecnativa - Manuel Calero +# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0.html + +from dateutil.relativedelta import relativedelta + +from odoo import fields +from odoo.exceptions import UserError, ValidationError +from odoo.tests import tagged +from odoo.tests.common import TransactionCase + + +@tagged("post_install", "-at_install") +class TestCommission(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.commission_model = cls.env["commission"] + cls.commission_net_paid = cls.commission_model.create( + { + "name": "20% fixed commission (Net amount) - Payment Based", + "fix_qty": 20.0, + "amount_base_type": "net_amount", + } + ) + cls.commission_section_paid = cls.commission_model.create( + { + "name": "Section commission - Payment Based", + "commission_type": "section", + "section_ids": [ + (0, 0, {"amount_from": 1.0, "amount_to": 100.0, "percent": 10.0}) + ], + "amount_base_type": "net_amount", + } + ) + cls.commission_section_invoice = cls.commission_model.create( + { + "name": "Section commission - Invoice Based", + "commission_type": "section", + "section_ids": [ + ( + 0, + 0, + { + "amount_from": 15000.0, + "amount_to": 16000.0, + "percent": 20.0, + }, + ) + ], + } + ) + cls.company = cls.env.ref("base.main_company") + cls.res_partner_model = cls.env["res.partner"] + cls.partner = cls.env.ref("base.res_partner_2") + cls.partner.write({"agent": False}) + cls.settle_model = cls.env["commission.settlement"] + cls.make_settle_model = cls.env["commission.make.settle"] + cls.commission_product = cls.env["product.product"].create( + {"name": "Commission test product", "type": "service"} + ) + cls.journal = cls.env["account.journal"].search( + [("type", "=", "purchase")], limit=1 + ) + cls.agent_monthly = cls.res_partner_model.create( + { + "name": "Test Agent - Monthly", + "agent": True, + "settlement": "monthly", + "lang": "en_US", + "commission_id": cls.commission_net_paid.id, + } + ) + cls.agent_quaterly = cls.res_partner_model.create( + { + "name": "Test Agent - Quaterly", + "agent": True, + "settlement": "quaterly", + "lang": "en_US", + "commission_id": cls.commission_section_invoice.id, + } + ) + cls.agent_semi = cls.res_partner_model.create( + { + "name": "Test Agent - Semi-annual", + "agent": True, + "settlement": "semi", + "lang": "en_US", + } + ) + cls.agent_annual = cls.res_partner_model.create( + { + "name": "Test Agent - Annual", + "agent": True, + "settlement": "annual", + "lang": "en_US", + } + ) + + # Expected to be used in inheriting modules. + def _get_make_settle_vals(self, agent=None, period=None, date=None): + vals = { + "date_to": ( + fields.Datetime.from_string(fields.Datetime.now()) + + relativedelta(months=period) + ) + if period + else date, + } + if agent: + vals["agent_ids"] = [(4, agent.id)] + return vals + + # Expected to be used in inheriting modules. + def _check_propagation(self, agent, commission_type, agent_partner): + self.assertTrue(agent) + self.assertTrue(agent.commission_id, commission_type) + self.assertTrue(agent.agent_id, agent_partner) + + def _create_settlement(self, agent, commission): + sett_from = self.make_settle_model._get_period_start(agent, fields.Date.today()) + sett_to = self.make_settle_model._get_next_period_date(agent, sett_from) + return self.settle_model.create( + { + "agent_id": agent.id, + "date_from": sett_from, + "date_to": sett_to, + "company_id": self.company.id, + "line_ids": [ + ( + 0, + 0, + { + "date": fields.Date.today(), + "agent_id": agent.id, + "commission_id": commission.id, + "settled_amount": 100.0, + "currency_id": self.company.currency_id.id, + }, + ) + ], + } + ) + + def _check_settlements(self, agent, commission, settlements=None): + if not settlements: + settlements = self._create_settlement(agent, commission) + settlements.make_invoices(self.journal, self.commission_product) + for settlement in settlements: + self.assertEqual(settlement.state, "invoiced") + with self.assertRaises(UserError): + settlements.action_cancel() + with self.assertRaises(UserError): + settlements.unlink() + return settlements + + def test_commission_gross_amount(self): + settlements = self._check_settlements( + self.env.ref("commission.res_partner_pritesh_sale_agent"), + self.commission_section_paid, + ) + # Check report print - It shouldn't fail + self.env.ref("commission.action_report_settlement")._render_qweb_html( + settlements[0].ids + ) + + def test_wrong_section(self): + with self.assertRaises(ValidationError): + self.commission_model.create( + { + "name": "Section commission - Invoice Based", + "commission_type": "section", + "section_ids": [ + (0, 0, {"amount_from": 5, "amount_to": 1, "percent": 20.0}) + ], + } + ) + + def test_res_partner_agent_propagation(self): + partner = self.env["res.partner"].create( + { + "name": "Test partner", + "agent_ids": [(4, self.agent_monthly.id), (4, self.agent_quaterly.id)], + } + ) + # Create + child = self.env["res.partner"].create( + {"name": "Test child", "parent_id": partner.id} + ) + self.assertEqual(set(child.agent_ids.ids), set(partner.agent_ids.ids)) + # Write + partner.agent_ids = [(4, self.agent_annual.id)] + self.assertEqual(set(child.agent_ids.ids), set(partner.agent_ids.ids)) diff --git a/commission/views/account_move_view.xml b/commission/views/account_move_view.xml new file mode 100644 index 000000000..c948a6109 --- /dev/null +++ b/commission/views/account_move_view.xml @@ -0,0 +1,31 @@ + + + + account.move.line + + + + + + + + + account.invoice.form.agent + account.move + + + + + + + + + + + diff --git a/commission/views/commission_mixin_views.xml b/commission/views/commission_mixin_views.xml new file mode 100644 index 000000000..219376405 --- /dev/null +++ b/commission/views/commission_mixin_views.xml @@ -0,0 +1,18 @@ + + + commission.mixin + +
+ + +
+
+ +
+
+
diff --git a/commission/views/commission_settlement_report.xml b/commission/views/commission_settlement_report.xml new file mode 100644 index 000000000..b0be5e747 --- /dev/null +++ b/commission/views/commission_settlement_report.xml @@ -0,0 +1,12 @@ + + + + Settlement report + commission.settlement + qweb-pdf + commission.report_settlement + commission.report_settlement + + report + + diff --git a/commission/views/commission_settlement_views.xml b/commission/views/commission_settlement_views.xml new file mode 100644 index 000000000..1ac43d282 --- /dev/null +++ b/commission/views/commission_settlement_views.xml @@ -0,0 +1,174 @@ + + + + Settlements tree + commission.settlement + + + + + + + + + + + + + + commission.settlement.search + commission.settlement + + + + + + + + + + + + + + + + + Settlements + commission.settlement + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Settlement lines + commission.settlement.line + + + + + + + + + + + commission.settlement.line.search + commission.settlement.line + + + + + + + + + + + + + + + + commission.settlement.line.graph + commission.settlement.line + + + + + + + + + + Settlements + ir.actions.act_window + commission.settlement + form,tree + + +
diff --git a/commission/views/commission_views.xml b/commission/views/commission_views.xml new file mode 100644 index 000000000..d34b70d19 --- /dev/null +++ b/commission/views/commission_views.xml @@ -0,0 +1,65 @@ + + + + commissions tree + commission + + + + + + + + + + commissions form + commission + +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Commission types + ir.actions.act_window + commission + form,tree + + +
diff --git a/commission/views/product_template_views.xml b/commission/views/product_template_views.xml new file mode 100644 index 000000000..53665ffa4 --- /dev/null +++ b/commission/views/product_template_views.xml @@ -0,0 +1,16 @@ + + + + product.template.common.form.commission + product.template + + + + + + diff --git a/commission/views/report_settlement_templates.xml b/commission/views/report_settlement_templates.xml new file mode 100644 index 000000000..e23983b0d --- /dev/null +++ b/commission/views/report_settlement_templates.xml @@ -0,0 +1,61 @@ + + + + diff --git a/commission/views/res_partner_views.xml b/commission/views/res_partner_views.xml new file mode 100644 index 000000000..e4352de24 --- /dev/null +++ b/commission/views/res_partner_views.xml @@ -0,0 +1,81 @@ + + + + res.partner.form.agent + res.partner + + + + + + + + + + + + + + + + + + + + + + + + + res.partner.select + res.partner + + + + + + + + + Agents + ir.actions.act_window + res.partner + form,kanban,tree + {"search_default_agent": 1, 'default_agent': 1, 'default_customer': 0, 'default_supplier': 1} + + + + diff --git a/commission/wizards/__init__.py b/commission/wizards/__init__.py new file mode 100644 index 000000000..c2151d762 --- /dev/null +++ b/commission/wizards/__init__.py @@ -0,0 +1,2 @@ +from . import wizard_invoice +from . import wizard_settle diff --git a/commission/wizards/wizard_invoice.py b/commission/wizards/wizard_invoice.py new file mode 100644 index 000000000..75c997cfc --- /dev/null +++ b/commission/wizards/wizard_invoice.py @@ -0,0 +1,68 @@ +from odoo import _, fields, models + + +class CommissionMakeInvoice(models.TransientModel): + _name = "commission.make.invoice" + _description = "Wizard for making an invoice from a settlement" + + def _default_journal_id(self): + return self.env["account.journal"].search([("type", "=", "purchase")])[:1] + + def _default_settlement_ids(self): + return self.env.context.get("settlement_ids", []) + + def _default_from_settlement(self): + return bool(self.env.context.get("settlement_ids")) + + journal_id = fields.Many2one( + comodel_name="account.journal", + required=True, + domain="[('type', '=', 'purchase')]", + default=_default_journal_id, + ) + company_id = fields.Many2one( + comodel_name="res.company", related="journal_id.company_id", readonly=True + ) + product_id = fields.Many2one( + string="Product for invoicing", comodel_name="product.product", required=True + ) + settlement_ids = fields.Many2many( + comodel_name="commission.settlement", + relation="commission_make_invoice_settlement_rel", + column1="wizard_id", + column2="settlement_id", + domain="[('state', '=', 'settled'),('agent_type', '=', 'agent')," + "('company_id', '=', company_id)]", + default=_default_settlement_ids, + ) + from_settlement = fields.Boolean(default=_default_from_settlement) + date = fields.Date(default=fields.Date.context_today) + grouped = fields.Boolean(string="Group invoices") + + def button_create(self): + self.ensure_one() + if self.settlement_ids: + settlements = self.settlement_ids + else: + settlements = self.env["commission.settlement"].search( + [ + ("state", "=", "settled"), + ("agent_type", "=", "agent"), + ("company_id", "=", self.journal_id.company_id.id), + ] + ) + invoices = settlements.make_invoices( + self.journal_id, + self.product_id, + date=self.date, + grouped=self.grouped, + ) + # go to results + if len(settlements): + return { + "name": _("Created Invoices"), + "type": "ir.actions.act_window", + "views": [[False, "list"], [False, "form"]], + "res_model": "account.move", + "domain": [["id", "in", invoices.ids]], + } diff --git a/commission/wizards/wizard_invoice.xml b/commission/wizards/wizard_invoice.xml new file mode 100644 index 000000000..95b36ca9d --- /dev/null +++ b/commission/wizards/wizard_invoice.xml @@ -0,0 +1,52 @@ + + + + Make invoices + commission.make.invoice + +
+ + + + + + + +

(keep empty for invoicing all the settlements)

+ + + + + + + +
+
+
+
+
+
+ + Create Commission Invoices + commission.make.invoice + form + new + + + +
diff --git a/commission/wizards/wizard_settle.py b/commission/wizards/wizard_settle.py new file mode 100644 index 000000000..605e56667 --- /dev/null +++ b/commission/wizards/wizard_settle.py @@ -0,0 +1,148 @@ +# Copyright 2014-2020 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from datetime import date, timedelta + +from dateutil.relativedelta import relativedelta + +from odoo import _, fields, models + + +class CommissionMakeSettle(models.TransientModel): + _name = "commission.make.settle" + _description = "Wizard for settling commissions" + + date_to = fields.Date("Up to", required=True, default=fields.Date.today()) + agent_ids = fields.Many2many( + comodel_name="res.partner", domain="[('agent', '=', True)]" + ) + settlement_type = fields.Selection(selection=[], required=True) + + def _get_period_start(self, agent, date_to): + if agent.settlement == "monthly": + return date(month=date_to.month, year=date_to.year, day=1) + elif agent.settlement == "biweekly": + if date_to.day >= 16: + return date(month=date_to.month, year=date_to.year, day=16) + else: + return date(month=date_to.month, year=date_to.year, day=1) + elif agent.settlement == "quaterly": + # Get first month of the date quarter + month = (date_to.month - 1) // 3 * 3 + 1 + return date(month=month, year=date_to.year, day=1) + elif agent.settlement == "semi": + if date_to.month > 6: + return date(month=7, year=date_to.year, day=1) + else: + return date(month=1, year=date_to.year, day=1) + elif agent.settlement == "annual": + return date(month=1, year=date_to.year, day=1) + + def _get_next_period_date(self, agent, current_date): + if agent.settlement == "monthly": + return current_date + relativedelta(months=1) + elif agent.settlement == "biweekly": + if current_date.day == 1: + return current_date + relativedelta(days=15) + else: + return date( + month=current_date.month, year=current_date.year, day=1 + ) + relativedelta(months=1, days=-1) + elif agent.settlement == "quaterly": + return current_date + relativedelta(months=3) + elif agent.settlement == "semi": + return current_date + relativedelta(months=6) + elif agent.settlement == "annual": + return current_date + relativedelta(years=1) + + def _get_settlement(self, agent, company, sett_from, sett_to): + return self.env["commission.settlement"].search( + [ + ("agent_id", "=", agent.id), + ("date_from", "=", sett_from), + ("date_to", "=", sett_to), + ("company_id", "=", company.id), + ("state", "=", "settled"), + ], + limit=1, + ) + + def _prepare_settlement_vals(self, agent, company, sett_from, sett_to): + return { + "agent_id": agent.id, + "date_from": sett_from, + "date_to": sett_to, + "company_id": company.id, + "settlement_type": self.settlement_type, + } + + def _get_settlement_line_date(self, line): + """Need to be extended according to settlement_type.""" + raise NotImplementedError() + + def _prepare_settlement_line_vals(self, settlement, line): + return { + "settlement_id": settlement.id, + "agent_line": [(6, 0, [line.id])], + "date": self._get_settlement_line_date(line), + "agent_id": line.agent_id.id, + "commission_id": line.commission_id.id, + "settled_amount": line.amount, + "currency_id": line.currency_id.id, + } + + def _get_agent_lines(self, date_to_agent): + """Need to be extended according to settlement_type.""" + raise NotImplementedError() + + def action_settle(self): + self.ensure_one() + settlement_obj = self.env["commission.settlement"] + settlement_line_obj = self.env["commission.settlement.line"] + settlement_ids = [] + + if self.agent_ids: + agents = self.agent_ids + else: + agents = self.env["res.partner"].search([("agent", "=", True)]) + date_to = self.date_to + for agent in agents: + date_to_agent = self._get_period_start(agent, date_to) + # Get non settled invoices + agent_lines = self._get_agent_lines(agent, date_to_agent) + for company in agent_lines.mapped("company_id"): + agent_lines_company = agent_lines.filtered( + lambda r: r.object_id.company_id == company + ) + pos = 0 + sett_to = date(year=1900, month=1, day=1) + while pos < len(agent_lines_company): + line = agent_lines_company[pos] + pos += 1 + if line._skip_settlement(): + continue + if line.invoice_date > sett_to: + sett_from = self._get_period_start(agent, line.invoice_date) + sett_to = self._get_next_period_date(agent, sett_from) + sett_to -= timedelta(days=1) + settlement = self._get_settlement( + agent, company, sett_from, sett_to + ) + if not settlement: + settlement = settlement_obj.create( + self._prepare_settlement_vals( + agent, company, sett_from, sett_to + ) + ) + settlement_ids.append(settlement.id) + settlement_line_obj.create( + self._prepare_settlement_line_vals(settlement, line) + ) + # go to results + if len(settlement_ids): + return { + "name": _("Created Settlements"), + "type": "ir.actions.act_window", + "views": [[False, "list"], [False, "form"]], + "res_model": "commission.settlement", + "domain": [["id", "in", settlement_ids]], + } diff --git a/commission/wizards/wizard_settle.xml b/commission/wizards/wizard_settle.xml new file mode 100644 index 000000000..dc0983f7f --- /dev/null +++ b/commission/wizards/wizard_settle.xml @@ -0,0 +1,54 @@ + + + + Select period to settle + commission.make.settle + +
+ +

Select the date up to which you want to make the settlements: +

+
+ + + + + +

(keep empty for making the settlement of all agents) +

+ +
+
+
+
+
+
+ + Settle Commissions + commission.make.settle + form + new + + + +
From 77520ac3fe8493ce88ac4a9557bc5498e7f2249f Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Sat, 19 Nov 2022 14:10:03 +0100 Subject: [PATCH 02/85] [REF] *commission*: Abstract even more the base module - Make specific top-level menu and permissions for commissions. - Don't depend on account. - Reorganize elements. - Change some parts of the code to fit better ORM tools. - Adjust permissions in children modules. - Add to some form views to get modern view. --- commission/README.rst | 31 +-- commission/__manifest__.py | 17 +- commission/data/menuitem_data.xml | 20 +- commission/models/__init__.py | 3 +- commission/models/account_move.py | 41 ---- commission/models/commission_settlement.py | 139 ++++++++++++ commission/models/res_partner.py | 7 +- commission/models/settlement.py | 212 ------------------ commission/readme/CONFIGURE.rst | 4 +- commission/readme/DESCRIPTION.rst | 3 +- commission/readme/USAGE.rst | 18 +- .../commission_settlement_report.xml | 0 .../report_settlement_templates.xml | 0 commission/security/commission_security.xml | 51 ++++- commission/security/ir.model.access.csv | 20 +- commission/static/description/icon.png | Bin 19238 -> 6692 bytes commission/static/description/icon.svg | 100 +++++++++ commission/static/description/index.html | 27 +-- commission/tests/test_commission.py | 34 +-- commission/views/account_move_view.xml | 31 --- .../views/commission_settlement_views.xml | 97 ++++---- commission/views/commission_views.xml | 62 ++--- commission/views/res_partner_views.xml | 35 ++- commission/wizards/__init__.py | 3 +- ...rd_settle.py => commission_make_settle.py} | 35 +-- ...e.xml => commission_make_settle_views.xml} | 16 +- commission/wizards/wizard_invoice.py | 68 ------ commission/wizards/wizard_invoice.xml | 52 ----- 28 files changed, 475 insertions(+), 651 deletions(-) delete mode 100644 commission/models/account_move.py create mode 100644 commission/models/commission_settlement.py delete mode 100644 commission/models/settlement.py rename commission/{views => reports}/commission_settlement_report.xml (100%) rename commission/{views => reports}/report_settlement_templates.xml (100%) create mode 100644 commission/static/description/icon.svg delete mode 100644 commission/views/account_move_view.xml rename commission/wizards/{wizard_settle.py => commission_make_settle.py} (86%) rename commission/wizards/{wizard_settle.xml => commission_make_settle_views.xml} (76%) delete mode 100644 commission/wizards/wizard_invoice.py delete mode 100644 commission/wizards/wizard_invoice.xml diff --git a/commission/README.rst b/commission/README.rst index 48a9a0409..3ff8f641d 100644 --- a/commission/README.rst +++ b/commission/README.rst @@ -19,9 +19,9 @@ Commissions .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png :target: https://translation.odoo-community.org/projects/commission-15-0/commission-15-0-commission :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/165/15.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/commission&target_branch=15.0 + :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -31,10 +31,9 @@ following: - Define agents with their commissions - Assign agents to partners - Create settlements to summarize commissions for certain periods -- Create vendor bills from settlements You can define which base amount is going to be taken into account: net amount -(based on margin) or gross amount (line subtotal amount) +(based on margin) or gross amount (line subtotal amount). **Table of contents** @@ -46,7 +45,7 @@ Configuration For adding commissions: -#. Go to *Invoicing > Configuration > Commission Management > Commission types*. +#. Go to *Commissions > Configuration > Commission types*. #. Edit or create a new record. #. Select a name for distinguishing that type. #. Select the percentage type of the commission: @@ -65,7 +64,7 @@ For adding commissions: For adding new agents: -#. Go to *Invoicing > Vendors > Agents*. You can also access from +#. Go to *Commissions > Agents*. You can also access from *Contacts > Contacts* or *Sales > Orders > Customers*. #. Edit or create a new record. #. On "Sales & Purchases" page, mark "Agent" check. It should be checked if @@ -96,7 +95,7 @@ For setting default agents in partners: For settling the commissions to agents: -#. Go to *Invoicing > Vendors > Commissions > Settle commissions*. +#. Go to *Commissions > Settlements > Settle Commissions*. #. On the window that appears, you should select the date up to which you want to create commissions. It should be at least one day after the last period date. For example, if you settlements are monthly, you have to put @@ -106,22 +105,6 @@ For settling the commissions to agents: #. Click on "Make settlements" button. #. If there are new settlements, they will be shown after this. -For invoicing the settlements (only for external agents): - -#. Go to *Invoicing > Vendors > Create commission invoices*. -#. On the window that appears, you can select following data: - - * Product. It should be a service product for being coherent. - * Journal: To be selected between existing purchase journals. - * Date: If you want to choose a specific invoice date. You can leave it - blank if you prefer. - * Settlements: For selecting specific settlements to invoice. You can leave - it blank as well for invoicing all the pending settlements. - -#. If you want to invoice a specific settlement, you can navigate to it in - *Invoicing > Vendors > Settlements*, and click on "Make invoice" - button. - Known issues / Roadmap ====================== diff --git a/commission/__manifest__.py b/commission/__manifest__.py index a2d9c57e6..ffd4e4714 100644 --- a/commission/__manifest__.py +++ b/commission/__manifest__.py @@ -1,28 +1,27 @@ -# Copyright 2014-2020 Tecnativa - Pedro M. Baeza # Copyright 2020 Tecnativa - Manuel Calero +# Copyright 2022 Quartile +# Copyright 2014-2022 Tecnativa - Pedro M. Baeza { "name": "Commissions", "version": "15.0.1.0.0", - "author": "Tecnativa," "Odoo Community Association (OCA)", + "author": "Tecnativa, Odoo Community Association (OCA)", "category": "Invoicing", "license": "AGPL-3", - "depends": ["account"], + "depends": ["product"], "website": "https://github.com/OCA/commission", "maintainers": ["pedrobaeza"], "data": [ + "security/commission_security.xml", "security/ir.model.access.csv", "data/menuitem_data.xml", - "views/account_move_view.xml", - "security/commission_security.xml", "views/commission_views.xml", "views/commission_settlement_views.xml", "views/commission_mixin_views.xml", "views/product_template_views.xml", "views/res_partner_views.xml", - "views/commission_settlement_report.xml", - "views/report_settlement_templates.xml", - "wizards/wizard_invoice.xml", - "wizards/wizard_settle.xml", + "reports/commission_settlement_report.xml", + "reports/report_settlement_templates.xml", + "wizards/commission_make_settle_views.xml", ], "demo": ["demo/commission_and_agent_demo.xml"], "installable": True, diff --git a/commission/data/menuitem_data.xml b/commission/data/menuitem_data.xml index 316bec769..0271a5150 100644 --- a/commission/data/menuitem_data.xml +++ b/commission/data/menuitem_data.xml @@ -1,19 +1,17 @@ - - diff --git a/commission/models/__init__.py b/commission/models/__init__.py index 78325f66e..890d9a2c6 100644 --- a/commission/models/__init__.py +++ b/commission/models/__init__.py @@ -1,6 +1,5 @@ from . import commission from . import commission_mixin +from . import commission_settlement from . import product_template from . import res_partner -from . import settlement -from . import account_move diff --git a/commission/models/account_move.py b/commission/models/account_move.py deleted file mode 100644 index 159c297cb..000000000 --- a/commission/models/account_move.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2014-2018 Tecnativa - Pedro M. Baeza -# Copyright 2020 Tecnativa - Manuel Calero -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from odoo import fields, models - - -class AccountMove(models.Model): - _inherit = "account.move" - - def button_cancel(self): - """Put settlements associated to the invoices in exception.""" - self.mapped("line_ids.settlement_id").write({"state": "except_invoice"}) - return super().button_cancel() - - def action_post(self): - """Put settlements associated to the invoices in invoiced state.""" - self.mapped("line_ids.settlement_id").write({"state": "invoiced"}) - return super().action_post() - - -class AccountMoveLine(models.Model): - _inherit = "account.move.line" - - settlement_id = fields.Many2one( - comodel_name="commission.settlement", - help="Settlement that generates this invoice line", - copy=False, - ) - - def _copy_data_extend_business_fields(self, values): - """ - We don't want to loose the settlement from the line when reversing the line if - it was a refund. - We need to include it, but as we don't want change it everytime, we will add - the data when a context key is passed - """ - super()._copy_data_extend_business_fields(values) - if self.settlement_id and self.env.context.get("include_settlement", False): - values["settlement_id"] = self.settlement_id.id - return diff --git a/commission/models/commission_settlement.py b/commission/models/commission_settlement.py new file mode 100644 index 000000000..d9355165a --- /dev/null +++ b/commission/models/commission_settlement.py @@ -0,0 +1,139 @@ +# Copyright 2020 Tecnativa - Manuel Calero +# Copyright 2022 Quartile +# Copyright 2014-2022 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +from odoo import api, fields, models + + +class CommissionSettlement(models.Model): + _name = "commission.settlement" + _description = "Settlement" + + name = fields.Char() + total = fields.Float(compute="_compute_total", readonly=True, store=True) + date_from = fields.Date(string="From", required=True) + date_to = fields.Date(string="To", required=True) + agent_id = fields.Many2one( + comodel_name="res.partner", + domain="[('agent', '=', True)]", + required=True, + ) + agent_type = fields.Selection(related="agent_id.agent_type") + settlement_type = fields.Selection( + selection=[("manual", "Manual")], + default="manual", + readonly=True, + required=True, + help="The source of the settlement, e.g. 'Sales invoice', 'Sales order', " + "'Purchase order'...", + ) + can_edit = fields.Boolean( + compute="_compute_can_edit", + help="Technical field for determining if user can edit settlements", + store=True, + ) + line_ids = fields.One2many( + comodel_name="commission.settlement.line", + inverse_name="settlement_id", + string="Settlement lines", + ) + state = fields.Selection( + selection=[ + ("settled", "Settled"), + ("cancel", "Canceled"), + ], + readonly=True, + required=True, + default="settled", + ) + currency_id = fields.Many2one( + comodel_name="res.currency", + readonly=True, + default=lambda self: self._default_currency_id(), + required=True, + ) + company_id = fields.Many2one( + comodel_name="res.company", + default=lambda self: self._default_company_id(), + required=True, + ) + + def _default_currency_id(self): + return self.env.company.currency_id.id + + def _default_company_id(self): + return self.env.company.id + + @api.depends("line_ids", "line_ids.settled_amount") + def _compute_total(self): + for record in self: + record.total = sum(record.mapped("line_ids.settled_amount")) + + @api.depends("settlement_type") + def _compute_can_edit(self): + for record in self: + record.can_edit = record.settlement_type == "manual" + + def action_cancel(self): + self.write({"state": "cancel"}) + + +class SettlementLine(models.Model): + _name = "commission.settlement.line" + _description = "Line of a commission settlement" + + settlement_id = fields.Many2one( + "commission.settlement", + readonly=True, + ondelete="cascade", + required=True, + ) + date = fields.Date( + compute="_compute_date", + readonly=False, + store=True, + required=True, + ) + agent_id = fields.Many2one( + comodel_name="res.partner", + related="settlement_id.agent_id", + store=True, + ) + settled_amount = fields.Monetary( + compute="_compute_settled_amount", readonly=False, store=True + ) + currency_id = fields.Many2one( + related="settlement_id.currency_id", + comodel_name="res.currency", + store=True, + readonly=True, + ) + commission_id = fields.Many2one( + comodel_name="commission", + compute="_compute_commission_id", + readonly=False, + store=True, + required=True, + ) + company_id = fields.Many2one( + comodel_name="res.company", + related="settlement_id.company_id", + store=True, + ) + + def _compute_date(self): + """Empty hook for allowing in children modules to auto-compute this field + depending on the settlement line source. + """ + + def _compute_commission_id(self): + """Empty hook for allowing in children modules to auto-compute this field + depending on the settlement line source. + """ + + def _compute_settled_amount(self): + """Empty container for allowing in children modules to auto-compute this + amount depending on the settlement line source. + """ diff --git a/commission/models/res_partner.py b/commission/models/res_partner.py index c1049dc43..605ba0151 100644 --- a/commission/models/res_partner.py +++ b/commission/models/res_partner.py @@ -1,5 +1,5 @@ -# Copyright 2016-2019 Tecnativa - Pedro M. Baeza # Copyright 2018 Tecnativa - Ernesto Tejeda +# Copyright 2016-2022 Tecnativa - Pedro M. Baeza # License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0.html from odoo import api, fields, models @@ -47,6 +47,11 @@ class ResPartner(models.Model): string="Settlement period", default="monthly", ) + settlement_ids = fields.One2many( + comodel_name="commission.settlement", + inverse_name="agent_id", + readonly=True, + ) @api.model def _commercial_fields(self): diff --git a/commission/models/settlement.py b/commission/models/settlement.py deleted file mode 100644 index b0fe15d90..000000000 --- a/commission/models/settlement.py +++ /dev/null @@ -1,212 +0,0 @@ -# Copyright 2014-2020 Tecnativa - Pedro M. Baeza -# Copyright 2020 Tecnativa - Manuel Calero -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from itertools import groupby - -from odoo import _, api, fields, models -from odoo.exceptions import UserError -from odoo.tests.common import Form - - -class Settlement(models.Model): - _name = "commission.settlement" - _description = "Settlement" - - def _default_currency(self): - return self.env.user.company_id.currency_id.id - - name = fields.Char() - total = fields.Float(compute="_compute_total", readonly=True, store=True) - date_from = fields.Date(string="From") - date_to = fields.Date(string="To") - agent_id = fields.Many2one( - comodel_name="res.partner", domain="[('agent', '=', True)]" - ) - agent_type = fields.Selection(related="agent_id.agent_type") - settlement_type = fields.Char( - readonly=True, - help="e.g. 'invoice'. A technical field to control the view presentation.", - ) - line_ids = fields.One2many( - comodel_name="commission.settlement.line", - inverse_name="settlement_id", - string="Settlement lines", - readonly=True, - ) - state = fields.Selection( - selection=[ - ("settled", "Settled"), - ("invoiced", "Invoiced"), - ("cancel", "Canceled"), - ("except_invoice", "Invoice exception"), - ], - readonly=True, - default="settled", - ) - invoice_line_ids = fields.One2many( - comodel_name="account.move.line", - inverse_name="settlement_id", - string="Generated invoice", - readonly=True, - ) - # TODO: To be removed - invoice_id = fields.Many2one( - store=True, - comodel_name="account.move", - compute="_compute_invoice_id", - ) - currency_id = fields.Many2one( - comodel_name="res.currency", readonly=True, default=_default_currency - ) - company_id = fields.Many2one( - comodel_name="res.company", - default=lambda self: self.env.user.company_id, - required=True, - ) - - @api.depends("line_ids", "line_ids.settled_amount") - def _compute_total(self): - for record in self: - record.total = sum(record.mapped("line_ids.settled_amount")) - - @api.depends("invoice_line_ids") - def _compute_invoice_id(self): - for record in self: - record.invoice_id = record.invoice_line_ids[:1].move_id - - def action_cancel(self): - if any(x.state != "settled" for x in self): - raise UserError(_("Cannot cancel an invoiced settlement.")) - self.write({"state": "cancel"}) - - def unlink(self): - """Allow to delete only cancelled settlements""" - if any(x.state == "invoiced" for x in self): - raise UserError(_("You can't delete invoiced settlements.")) - return super().unlink() - - def action_invoice(self): - return { - "type": "ir.actions.act_window", - "name": _("Make invoice"), - "res_model": "commission.make.invoice", - "view_type": "form", - "target": "new", - "view_mode": "form", - "context": {"settlement_ids": self.ids}, - } - - def _get_invoice_partner(self): - return self[0].agent_id - - def _prepare_invoice(self, journal, product, date=False): - - move_form = Form( - self.env["account.move"].with_context(default_move_type="in_invoice") - ) - - if date: - move_form.invoice_date = date - partner = self._get_invoice_partner() - move_form.partner_id = partner - move_form.journal_id = journal - for settlement in self: - with move_form.invoice_line_ids.new() as line_form: - line_form.product_id = product - line_form.quantity = -1 if settlement.total < 0 else 1 - line_form.price_unit = abs(settlement.total) - # Put period string - partner = self.agent_id - lang = self.env["res.lang"].search( - [ - ( - "code", - "=", - partner.lang or self.env.context.get("lang", "en_US"), - ) - ] - ) - date_from = fields.Date.from_string(settlement.date_from) - date_to = fields.Date.from_string(settlement.date_to) - line_form.name += "\n" + _( - "Period: from %(date_from)s to %(date_to)s", - date_from=date_from.strftime(lang.date_format), - date_to=date_to.strftime(lang.date_format), - ) - line_form.currency_id = ( - settlement.currency_id - ) # todo or compute agent currency_id?\ - line_form.settlement_id = settlement - vals = move_form._values_to_save(all_fields=True) - return vals - - def _get_invoice_grouping_keys(self): - return ["company_id", "agent_id"] - - def make_invoices(self, journal, product, date=False, grouped=False): - invoice_vals_list = [] - settlement_obj = self.env[self._name] - if grouped: - invoice_grouping_keys = self._get_invoice_grouping_keys() - settlements = groupby( - self.sorted( - key=lambda x: [ - x._fields[grouping_key].convert_to_write(x[grouping_key], x) - for grouping_key in invoice_grouping_keys - ], - ), - key=lambda x: [ - x._fields[grouping_key].convert_to_write(x[grouping_key], x) - for grouping_key in invoice_grouping_keys - ], - ) - grouped_settlements = [ - settlement_obj.union(*list(sett)) - for _grouping_keys, sett in settlements - ] - else: - grouped_settlements = self - for settlement in grouped_settlements: - invoice_vals = settlement._prepare_invoice(journal, product, date) - invoice_vals_list.append(invoice_vals) - invoices = self.env["account.move"].create(invoice_vals_list) - invoices.sudo().filtered(lambda m: m.amount_total < 0).with_context( - include_settlement=True - ).action_switch_invoice_into_refund_credit_note() - self.write({"state": "invoiced"}) - return invoices - - -class SettlementLine(models.Model): - _name = "commission.settlement.line" - _description = "Line of a commission settlement" - - settlement_id = fields.Many2one( - "commission.settlement", - readonly=True, - ondelete="cascade", - required=True, - ) - - date = fields.Date() - agent_id = fields.Many2one( - comodel_name="res.partner", - readonly=True, - store=True, - ) - settled_amount = fields.Monetary(readonly=True, store=True) - currency_id = fields.Many2one( - comodel_name="res.currency", - store=True, - readonly=True, - ) - commission_id = fields.Many2one( - comodel_name="commission", - readonly=True, - store=True, - ) - company_id = fields.Many2one( - comodel_name="res.company", - related="settlement_id.company_id", - ) diff --git a/commission/readme/CONFIGURE.rst b/commission/readme/CONFIGURE.rst index a3eec5c92..61abded58 100644 --- a/commission/readme/CONFIGURE.rst +++ b/commission/readme/CONFIGURE.rst @@ -1,6 +1,6 @@ For adding commissions: -#. Go to *Invoicing > Configuration > Commission Management > Commission types*. +#. Go to *Commissions > Configuration > Commission types*. #. Edit or create a new record. #. Select a name for distinguishing that type. #. Select the percentage type of the commission: @@ -19,7 +19,7 @@ For adding commissions: For adding new agents: -#. Go to *Invoicing > Vendors > Agents*. You can also access from +#. Go to *Commissions > Agents*. You can also access from *Contacts > Contacts* or *Sales > Orders > Customers*. #. Edit or create a new record. #. On "Sales & Purchases" page, mark "Agent" check. It should be checked if diff --git a/commission/readme/DESCRIPTION.rst b/commission/readme/DESCRIPTION.rst index 5e5ab40bc..69c079481 100644 --- a/commission/readme/DESCRIPTION.rst +++ b/commission/readme/DESCRIPTION.rst @@ -4,7 +4,6 @@ following: - Define agents with their commissions - Assign agents to partners - Create settlements to summarize commissions for certain periods -- Create vendor bills from settlements You can define which base amount is going to be taken into account: net amount -(based on margin) or gross amount (line subtotal amount) +(based on margin) or gross amount (line subtotal amount). diff --git a/commission/readme/USAGE.rst b/commission/readme/USAGE.rst index 95ea9fab7..0311233ec 100644 --- a/commission/readme/USAGE.rst +++ b/commission/readme/USAGE.rst @@ -8,7 +8,7 @@ For setting default agents in partners: For settling the commissions to agents: -#. Go to *Invoicing > Vendors > Commissions > Settle commissions*. +#. Go to *Commissions > Settlements > Settle Commissions*. #. On the window that appears, you should select the date up to which you want to create commissions. It should be at least one day after the last period date. For example, if you settlements are monthly, you have to put @@ -17,19 +17,3 @@ For settling the commissions to agents: section. Leave it empty for settling all. #. Click on "Make settlements" button. #. If there are new settlements, they will be shown after this. - -For invoicing the settlements (only for external agents): - -#. Go to *Invoicing > Vendors > Create commission invoices*. -#. On the window that appears, you can select following data: - - * Product. It should be a service product for being coherent. - * Journal: To be selected between existing purchase journals. - * Date: If you want to choose a specific invoice date. You can leave it - blank if you prefer. - * Settlements: For selecting specific settlements to invoice. You can leave - it blank as well for invoicing all the pending settlements. - -#. If you want to invoice a specific settlement, you can navigate to it in - *Invoicing > Vendors > Settlements*, and click on "Make invoice" - button. diff --git a/commission/views/commission_settlement_report.xml b/commission/reports/commission_settlement_report.xml similarity index 100% rename from commission/views/commission_settlement_report.xml rename to commission/reports/commission_settlement_report.xml diff --git a/commission/views/report_settlement_templates.xml b/commission/reports/report_settlement_templates.xml similarity index 100% rename from commission/views/report_settlement_templates.xml rename to commission/reports/report_settlement_templates.xml diff --git a/commission/security/commission_security.xml b/commission/security/commission_security.xml index 9b8d52249..314b992a3 100644 --- a/commission/security/commission_security.xml +++ b/commission/security/commission_security.xml @@ -1,12 +1,43 @@ - - - - Commission settlement multi company rule - - ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] - - + + + + Commissions + Allows to handle commission related stuff. + 8 + + + User + + + + + Manager + + + + + + + + + + + + + Commission settlement multi company rule + + ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] + + + Commission settlements - See all + + + [(1, '=', 1)] + + + diff --git a/commission/security/ir.model.access.csv b/commission/security/ir.model.access.csv index e33099b90..575cbe649 100644 --- a/commission/security/ir.model.access.csv +++ b/commission/security/ir.model.access.csv @@ -1,12 +1,10 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_commission_manager,access_commission_manager,model_commission,account.group_account_manager,1,1,1,1 -access_commission,access_commission,model_commission,account.group_account_invoice,1,0,0,0 -access_commission_user,access_commission_user,model_commission,base.group_user,1,0,0,0 -access_commission_section_manager,access_commission_section_manager,model_commission_section,account.group_account_manager,1,1,1,1 -access_commission_section_user,access_commission_section_user,model_commission_section,base.group_user,1,0,0,0 -access_commission_make_settle,access_commission_make_settle,model_commission_make_settle,account.group_account_invoice,1,1,1,0 -access_commission_settlement_manager,access_commission_settlement_manager,model_commission_settlement,account.group_account_manager,1,1,1,1 -access_commission_settlement_user,access_commission_settlement_user,model_commission_settlement,account.group_account_invoice,1,0,0,0 -access_commission_settlement_line_manager,access_commission_settlement_line_manager,model_commission_settlement_line,account.group_account_manager,1,1,1,1 -access_commission_settlement_line_user,access_commission_settlement_line_user,model_commission_settlement_line,account.group_account_invoice,1,0,0,0 -access_commission_make_invoice,access_commission_make_invoice,model_commission_make_invoice,account.group_account_manager,1,1,1,0 +access_commission_manager,access_commission_manager,model_commission,group_commission_manager,1,1,1,1 +access_commission,access_commission,model_commission,group_commission_user,1,0,0,0 +access_commission_section_manager,access_commission_section_manager,model_commission_section,group_commission_manager,1,1,1,1 +access_commission_section_user,access_commission_section_user,model_commission_section,group_commission_user,1,0,0,0 +access_commission_settlement_manager,access_commission_settlement_manager,model_commission_settlement,group_commission_manager,1,1,1,1 +access_commission_settlement_user,access_commission_settlement_user,model_commission_settlement,group_commission_user,1,0,0,0 +access_commission_settlement_line_manager,access_commission_settlement_line_manager,model_commission_settlement_line,group_commission_manager,1,1,1,1 +access_commission_settlement_line_user,access_commission_settlement_line_user,model_commission_settlement_line,group_commission_user,1,0,0,0 +access_commission_make_settle,access_commission_make_settle,model_commission_make_settle,group_commission_manager,1,1,1,0 diff --git a/commission/static/description/icon.png b/commission/static/description/icon.png index 0539927894add59cc8db869b17fb5490c765a9b6..6958c6b4f6c1365a536e3c25b818d71c4f86f0e9 100644 GIT binary patch literal 6692 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 literal 19238 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 lX7tu2x4gIydCwmk`d_A + + + + + + + + + + + + + + + + + + diff --git a/commission/static/description/index.html b/commission/static/description/index.html index 85eeb6b24..5efa46416 100644 --- a/commission/static/description/index.html +++ b/commission/static/description/index.html @@ -367,17 +367,16 @@

Commissions

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

This module provides the base functions for commission operations to enable the following:

  • Define agents with their commissions
  • Assign agents to partners
  • Create settlements to summarize commissions for certain periods
  • -
  • Create vendor bills from settlements

You can define which base amount is going to be taken into account: net amount -(based on margin) or gross amount (line subtotal amount)

+(based on margin) or gross amount (line subtotal amount).

Table of contents

    @@ -397,7 +396,7 @@

    Commissions

    Configuration

    For adding commissions:

      -
    1. Go to Invoicing > Configuration > Commission Management > Commission types.
    2. +
    3. Go to Commissions > Configuration > Commission types.
    4. Edit or create a new record.
    5. Select a name for distinguishing that type.
    6. Select the percentage type of the commission:

    For adding new agents:

      -
    1. Go to Invoicing > Vendors > Agents. You can also access from +

    2. Go to Commissions > Agents. You can also access from Contacts > Contacts or Sales > Orders > Customers.

    3. Edit or create a new record.

      @@ -452,7 +451,7 @@

      Usage

    For settling the commissions to agents:

      -
    1. Go to Invoicing > Vendors > Commissions > Settle commissions.
    2. +
    3. Go to Commissions > Settlements > Settle Commissions.
    4. On the window that appears, you should select the date up to which you want to create commissions. It should be at least one day after the last period date. For example, if you settlements are monthly, you have to put @@ -462,22 +461,6 @@

      Usage

    5. Click on ???Make settlements??? button.
    6. If there are new settlements, they will be shown after this.
    -

    For invoicing the settlements (only for external agents):

    -
      -
    1. Go to Invoicing > Vendors > Create commission invoices.
    2. -
    3. On the window that appears, you can select following data:
        -
      • Product. It should be a service product for being coherent.
      • -
      • Journal: To be selected between existing purchase journals.
      • -
      • Date: If you want to choose a specific invoice date. You can leave it -blank if you prefer.
      • -
      • Settlements: For selecting specific settlements to invoice. You can leave -it blank as well for invoicing all the pending settlements.
      • -
      -
    4. -
    5. If you want to invoice a specific settlement, you can navigate to it in -Invoicing > Vendors > Settlements, and click on ???Make invoice??? -button.
    6. -

Known issues / Roadmap

diff --git a/commission/tests/test_commission.py b/commission/tests/test_commission.py index d5f773cde..d7f145f2b 100644 --- a/commission/tests/test_commission.py +++ b/commission/tests/test_commission.py @@ -1,17 +1,16 @@ -# Copyright 2016-2019 Tecnativa - Pedro M. Baeza # Copyright 2020 Tecnativa - Manuel Calero +# Copyright 2022 Quartile +# Copyright 2016-2022 Tecnativa - Pedro M. Baeza # License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0.html from dateutil.relativedelta import relativedelta from odoo import fields -from odoo.exceptions import UserError, ValidationError -from odoo.tests import tagged +from odoo.exceptions import ValidationError from odoo.tests.common import TransactionCase -@tagged("post_install", "-at_install") -class TestCommission(TransactionCase): +class TestCommissionBase(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -59,9 +58,6 @@ def setUpClass(cls): cls.commission_product = cls.env["product.product"].create( {"name": "Commission test product", "type": "service"} ) - cls.journal = cls.env["account.journal"].search( - [("type", "=", "purchase")], limit=1 - ) cls.agent_monthly = cls.res_partner_model.create( { "name": "Test Agent - Monthly", @@ -142,28 +138,8 @@ def _create_settlement(self, agent, commission): } ) - def _check_settlements(self, agent, commission, settlements=None): - if not settlements: - settlements = self._create_settlement(agent, commission) - settlements.make_invoices(self.journal, self.commission_product) - for settlement in settlements: - self.assertEqual(settlement.state, "invoiced") - with self.assertRaises(UserError): - settlements.action_cancel() - with self.assertRaises(UserError): - settlements.unlink() - return settlements - - def test_commission_gross_amount(self): - settlements = self._check_settlements( - self.env.ref("commission.res_partner_pritesh_sale_agent"), - self.commission_section_paid, - ) - # Check report print - It shouldn't fail - self.env.ref("commission.action_report_settlement")._render_qweb_html( - settlements[0].ids - ) +class TestCommission(TestCommissionBase): def test_wrong_section(self): with self.assertRaises(ValidationError): self.commission_model.create( diff --git a/commission/views/account_move_view.xml b/commission/views/account_move_view.xml deleted file mode 100644 index c948a6109..000000000 --- a/commission/views/account_move_view.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - account.move.line - - - - - - - - - account.invoice.form.agent - account.move - - - - - - - - - - - diff --git a/commission/views/commission_settlement_views.xml b/commission/views/commission_settlement_views.xml index 1ac43d282..2141ff589 100644 --- a/commission/views/commission_settlement_views.xml +++ b/commission/views/commission_settlement_views.xml @@ -4,11 +4,7 @@ Settlements tree commission.settlement - + @@ -55,13 +51,6 @@
-
- - - - - + + + + + + + + + + + + + + + + + name="line_ids" + nolabel="1" + attrs="{'readonly': [('can_edit', '=', False)]}" + > + + + + + + - - - - - + + - - - - - - - - - - - - - + @@ -159,16 +154,22 @@ - + Settlements ir.actions.act_window commission.settlement - form,tree + tree,form + diff --git a/commission/views/commission_views.xml b/commission/views/commission_views.xml index d34b70d19..8d3a68699 100644 --- a/commission/views/commission_views.xml +++ b/commission/views/commission_views.xml @@ -16,37 +16,39 @@ commission
- - - - + + + + + + + + + + - - - - - - - + + + + + name="section_ids" + widget="one2many_list" + colspan="4" + nolabel="1" + attrs="{'invisible': [('commission_type', '!=', 'section')]}" + > + + + + + + - - - - - - - - +
@@ -58,8 +60,8 @@ diff --git a/commission/views/res_partner_views.xml b/commission/views/res_partner_views.xml index e4352de24..8e3d0dec2 100644 --- a/commission/views/res_partner_views.xml +++ b/commission/views/res_partner_views.xml @@ -5,7 +5,6 @@ res.partner - @@ -24,7 +23,7 @@ @@ -42,6 +41,23 @@ attrs="{'required': [('agent', '=', True)]}" /> + + + + + + + + + +
@@ -50,15 +66,16 @@ res.partner.select res.partner - + - + + @@ -66,16 +83,14 @@ Agents ir.actions.act_window res.partner - form,kanban,tree - {"search_default_agent": 1, 'default_agent': 1, 'default_customer': 0, 'default_supplier': 1} + kanban,form,tree + {"search_default_agent": 1, 'default_agent': 1} diff --git a/commission/wizards/__init__.py b/commission/wizards/__init__.py index c2151d762..aca351357 100644 --- a/commission/wizards/__init__.py +++ b/commission/wizards/__init__.py @@ -1,2 +1 @@ -from . import wizard_invoice -from . import wizard_settle +from . import commission_make_settle diff --git a/commission/wizards/wizard_settle.py b/commission/wizards/commission_make_settle.py similarity index 86% rename from commission/wizards/wizard_settle.py rename to commission/wizards/commission_make_settle.py index 605e56667..c42d92c56 100644 --- a/commission/wizards/wizard_settle.py +++ b/commission/wizards/commission_make_settle.py @@ -1,21 +1,33 @@ -# Copyright 2014-2020 Tecnativa - Pedro M. Baeza +# Copyright 2022 Quartile +# Copyright 2014-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from datetime import date, timedelta from dateutil.relativedelta import relativedelta -from odoo import _, fields, models +from odoo import _, api, fields, models class CommissionMakeSettle(models.TransientModel): _name = "commission.make.settle" _description = "Wizard for settling commissions" - date_to = fields.Date("Up to", required=True, default=fields.Date.today()) + date_to = fields.Date("Up to", required=True, default=fields.Date.today) agent_ids = fields.Many2many( comodel_name="res.partner", domain="[('agent', '=', True)]" ) settlement_type = fields.Selection(selection=[], required=True) + can_settle = fields.Boolean( + compute="_compute_can_settle", + help="Technical field for improving UX when no extra *commission is installed.", + ) + + @api.depends("date_to") # use this unrelated field to trigger the computation + def _compute_can_settle(self): + """Check if there's any settlement type for making the settlements.""" + self.can_settle = bool( + self.env[self._name]._fields["settlement_type"].selection + ) def _get_period_start(self, agent, date_to): if agent.settlement == "monthly": @@ -55,6 +67,7 @@ def _get_next_period_date(self, agent, current_date): return current_date + relativedelta(years=1) def _get_settlement(self, agent, company, sett_from, sett_to): + self.ensure_one() return self.env["commission.settlement"].search( [ ("agent_id", "=", agent.id), @@ -62,6 +75,7 @@ def _get_settlement(self, agent, company, sett_from, sett_to): ("date_to", "=", sett_to), ("company_id", "=", company.id), ("state", "=", "settled"), + ("settlement_type", "=", self.settlement_type), ], limit=1, ) @@ -75,19 +89,10 @@ def _prepare_settlement_vals(self, agent, company, sett_from, sett_to): "settlement_type": self.settlement_type, } - def _get_settlement_line_date(self, line): - """Need to be extended according to settlement_type.""" - raise NotImplementedError() - def _prepare_settlement_line_vals(self, settlement, line): + """Hook for returning the settlement line dictionary vals.""" return { "settlement_id": settlement.id, - "agent_line": [(6, 0, [line.id])], - "date": self._get_settlement_line_date(line), - "agent_id": line.agent_id.id, - "commission_id": line.commission_id.id, - "settled_amount": line.amount, - "currency_id": line.currency_id.id, } def _get_agent_lines(self, date_to_agent): @@ -99,7 +104,6 @@ def action_settle(self): settlement_obj = self.env["commission.settlement"] settlement_line_obj = self.env["commission.settlement.line"] settlement_ids = [] - if self.agent_ids: agents = self.agent_ids else: @@ -107,7 +111,7 @@ def action_settle(self): date_to = self.date_to for agent in agents: date_to_agent = self._get_period_start(agent, date_to) - # Get non settled invoices + # Get non settled elements agent_lines = self._get_agent_lines(agent, date_to_agent) for company in agent_lines.mapped("company_id"): agent_lines_company = agent_lines.filtered( @@ -134,6 +138,7 @@ def action_settle(self): ) ) settlement_ids.append(settlement.id) + # TODO: Do creates in batch settlement_line_obj.create( self._prepare_settlement_line_vals(settlement, line) ) diff --git a/commission/wizards/wizard_settle.xml b/commission/wizards/commission_make_settle_views.xml similarity index 76% rename from commission/wizards/wizard_settle.xml rename to commission/wizards/commission_make_settle_views.xml index dc0983f7f..c08c24df9 100644 --- a/commission/wizards/wizard_settle.xml +++ b/commission/wizards/commission_make_settle_views.xml @@ -5,6 +5,17 @@ commission.make.settle
+ +

diff --git a/commission/wizards/wizard_invoice.py b/commission/wizards/wizard_invoice.py deleted file mode 100644 index 75c997cfc..000000000 --- a/commission/wizards/wizard_invoice.py +++ /dev/null @@ -1,68 +0,0 @@ -from odoo import _, fields, models - - -class CommissionMakeInvoice(models.TransientModel): - _name = "commission.make.invoice" - _description = "Wizard for making an invoice from a settlement" - - def _default_journal_id(self): - return self.env["account.journal"].search([("type", "=", "purchase")])[:1] - - def _default_settlement_ids(self): - return self.env.context.get("settlement_ids", []) - - def _default_from_settlement(self): - return bool(self.env.context.get("settlement_ids")) - - journal_id = fields.Many2one( - comodel_name="account.journal", - required=True, - domain="[('type', '=', 'purchase')]", - default=_default_journal_id, - ) - company_id = fields.Many2one( - comodel_name="res.company", related="journal_id.company_id", readonly=True - ) - product_id = fields.Many2one( - string="Product for invoicing", comodel_name="product.product", required=True - ) - settlement_ids = fields.Many2many( - comodel_name="commission.settlement", - relation="commission_make_invoice_settlement_rel", - column1="wizard_id", - column2="settlement_id", - domain="[('state', '=', 'settled'),('agent_type', '=', 'agent')," - "('company_id', '=', company_id)]", - default=_default_settlement_ids, - ) - from_settlement = fields.Boolean(default=_default_from_settlement) - date = fields.Date(default=fields.Date.context_today) - grouped = fields.Boolean(string="Group invoices") - - def button_create(self): - self.ensure_one() - if self.settlement_ids: - settlements = self.settlement_ids - else: - settlements = self.env["commission.settlement"].search( - [ - ("state", "=", "settled"), - ("agent_type", "=", "agent"), - ("company_id", "=", self.journal_id.company_id.id), - ] - ) - invoices = settlements.make_invoices( - self.journal_id, - self.product_id, - date=self.date, - grouped=self.grouped, - ) - # go to results - if len(settlements): - return { - "name": _("Created Invoices"), - "type": "ir.actions.act_window", - "views": [[False, "list"], [False, "form"]], - "res_model": "account.move", - "domain": [["id", "in", invoices.ids]], - } diff --git a/commission/wizards/wizard_invoice.xml b/commission/wizards/wizard_invoice.xml deleted file mode 100644 index 95b36ca9d..000000000 --- a/commission/wizards/wizard_invoice.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - Make invoices - commission.make.invoice - - - - - - - - - -

(keep empty for invoicing all the settlements)

- - - - - - - -
-
-
- -
- - - Create Commission Invoices - commission.make.invoice - form - new - - - - From 6491a8a4192265343f95c8452b415f396ff574c9 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 1 Dec 2022 16:48:17 +0100 Subject: [PATCH 03/85] [IMP] commission*: Add settlement_type to commissions This way, we can restrict which commissions we can select or be automatically populate in the different documents. --- commission/models/commission.py | 9 +++++++++ commission/models/commission_mixin.py | 12 +++++++++--- commission/views/commission_views.xml | 2 ++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/commission/models/commission.py b/commission/models/commission.py index 658ac3ecd..3f2ac1a2a 100644 --- a/commission/models/commission.py +++ b/commission/models/commission.py @@ -1,3 +1,4 @@ +# Copyright 2016-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import _, api, exceptions, fields, models @@ -27,6 +28,14 @@ class Commission(models.Model): required=True, default="gross_amount", ) + settlement_type = fields.Selection(selection="_selection_settlement_type") + + @api.model + def _selection_settlement_type(self): + """Return the same types as the settlements.""" + return self.env["commission.settlement"].fields_get( + allfields=["settlement_type"] + )["settlement_type"]["selection"] def calculate_section(self, base): self.ensure_one() diff --git a/commission/models/commission_mixin.py b/commission/models/commission_mixin.py index c925f2d31..31268ab68 100644 --- a/commission/models/commission_mixin.py +++ b/commission/models/commission_mixin.py @@ -1,4 +1,4 @@ -# Copyright 2018-2020 Tecnativa - Pedro M. Baeza +# Copyright 2018-2022 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import _, api, fields, models @@ -35,9 +35,15 @@ class CommissionMixin(models.AbstractModel): def _prepare_agent_vals(self, agent): return {"agent_id": agent.id, "commission_id": agent.commission_id.id} - def _prepare_agents_vals_partner(self, partner): + def _prepare_agents_vals_partner(self, partner, settlement_type=None): """Utility method for getting agents creation dictionary of a partner.""" - return [(0, 0, self._prepare_agent_vals(agent)) for agent in partner.agent_ids] + agents = partner.agent_ids + if settlement_type: + agents = agents.filtered( + lambda x: not x.commission_id.settlement_type + or x.commission_id.settlement_type == settlement_type + ) + return [(0, 0, self._prepare_agent_vals(agent)) for agent in agents] @api.depends("commission_free") def _compute_agent_ids(self): diff --git a/commission/views/commission_views.xml b/commission/views/commission_views.xml index 8d3a68699..d92aef139 100644 --- a/commission/views/commission_views.xml +++ b/commission/views/commission_views.xml @@ -7,6 +7,7 @@ +
@@ -24,6 +25,7 @@
+ From 6e395390130de1fecbdb100e850e1eb3b2bba92f Mon Sep 17 00:00:00 2001 From: oca-ci Date: Mon, 5 Dec 2022 13:08:24 +0000 Subject: [PATCH 04/85] [UPD] Update commission.pot --- commission/i18n/commission.pot | 646 +++++++++++++++++++++++++++++++++ 1 file changed, 646 insertions(+) create mode 100644 commission/i18n/commission.pot diff --git a/commission/i18n/commission.pot b/commission/i18n/commission.pot new file mode 100644 index 000000000..f8f87b131 --- /dev/null +++ b/commission/i18n/commission.pot @@ -0,0 +1,646 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * commission +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: commission +#: code:addons/commission/models/commission_mixin.py:0 +#, python-format +msgid "%s commission agents" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settled_wizard +msgid "(keep empty for making the settlement of all agents)" +msgstr "" + +#. module: commission +#: code:addons/commission/models/commission_mixin.py:0 +#, python-format +msgid "1 commission agent" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settled_wizard +msgid "" +"\n" +" No automatic settlements can be made. Install any extra\n" +" commission module for having this feature enabled.\n" +" " +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.report_settlement +msgid "Agent:" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.report_settlement +msgid "From:" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.report_settlement +msgid "To:" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.report_settlement +msgid "Total" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__active +msgid "Active" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_line_mixin__agent_id +#: model:ir.model.fields,field_description:commission.field_commission_make_settle__agent_ids +#: model:ir.model.fields,field_description:commission.field_commission_settlement__agent_id +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__agent_id +#: model_terms:ir.ui.view,arch_db:commission.view_partner_form_agent +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_line_search +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_search +msgid "Agent" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.res_partner_view_search +msgid "Agent Partners" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_partner_form_agent +msgid "Agent information" +msgstr "" + +#. module: commission +#: code:addons/commission/models/commission_mixin.py:0 +#: model:ir.actions.act_window,name:commission.action_agent_form +#: model:ir.model.fields,field_description:commission.field_res_partner__agent_ids +#: model:ir.model.fields,field_description:commission.field_res_users__agent_ids +#: model:ir.ui.menu,name:commission.menu_agent_form +#: model_terms:ir.ui.view,arch_db:commission.res_partner_view_search +#: model_terms:ir.ui.view,arch_db:commission.view_settled_wizard +#, python-format +msgid "Agents" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_mixin__agent_ids +msgid "Agents & commissions" +msgstr "" + +#. module: commission +#: model:ir.model.fields,help:commission.field_commission_mixin__agent_ids +msgid "Agents/Commissions related to the invoice line." +msgstr "" + +#. module: commission +#: model:ir.module.category,description:commission.module_category_commission +msgid "Allows to handle commission related stuff." +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.report_settlement +msgid "Amount settled" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__res_partner__settlement__annual +msgid "Annual" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__amount_base_type +msgid "Base" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__res_partner__settlement__biweekly +msgid "Bi-weekly" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__commission__commission_type__section +msgid "By sections" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_settlement__can_edit +msgid "Can Edit" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_make_settle__can_settle +msgid "Can Settle" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settled_wizard +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_form +msgid "Cancel" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__commission_settlement__state__cancel +msgid "Canceled" +msgstr "" + +#. module: commission +#: model:ir.model.fields,help:commission.field_res_partner__agent +#: model:ir.model.fields,help:commission.field_res_users__agent +msgid "Check this field if the partner is a creditor or an agent." +msgstr "" + +#. module: commission +#: code:addons/commission/models/commission_mixin.py:0 +#: model:ir.model.fields,field_description:commission.field_commission_mixin__commission_free +#, python-format +msgid "Comm. free" +msgstr "" + +#. module: commission +#: model:ir.model,name:commission.model_commission +#: model:ir.model.fields,field_description:commission.field_commission_line_mixin__commission_id +#: model:ir.model.fields,field_description:commission.field_commission_mixin__commission_status +#: model:ir.model.fields,field_description:commission.field_commission_section__commission_id +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__commission_id +#: model:ir.model.fields,field_description:commission.field_res_partner__commission_id +#: model:ir.model.fields,field_description:commission.field_res_users__commission_id +#: model_terms:ir.ui.view,arch_db:commission.commission_form +#: model_terms:ir.ui.view,arch_db:commission.report_settlement +msgid "Commission" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_line_mixin__amount +msgid "Commission Amount" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_form +msgid "Commission lines" +msgstr "" + +#. module: commission +#: model:ir.model,name:commission.model_commission_section +msgid "Commission section" +msgstr "" + +#. module: commission +#: model:ir.actions.act_window,name:commission.action_commission +#: model:ir.ui.menu,name:commission.menu_commission_type +msgid "Commission types" +msgstr "" + +#. module: commission +#: model:ir.module.category,name:commission.module_category_commission +#: model:ir.ui.menu,name:commission.menu_commission +msgid "Commissions" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_settlement__company_id +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__company_id +msgid "Company" +msgstr "" + +#. module: commission +#: model:ir.ui.menu,name:commission.menu_commission_management +msgid "Configuration" +msgstr "" + +#. module: commission +#: model:ir.model,name:commission.model_res_partner +msgid "Contact" +msgstr "" + +#. module: commission +#: code:addons/commission/wizards/commission_make_settle.py:0 +#, python-format +msgid "Created Settlements" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__create_uid +#: model:ir.model.fields,field_description:commission.field_commission_make_settle__create_uid +#: model:ir.model.fields,field_description:commission.field_commission_section__create_uid +#: model:ir.model.fields,field_description:commission.field_commission_settlement__create_uid +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__create_uid +msgid "Created by" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__create_date +#: model:ir.model.fields,field_description:commission.field_commission_make_settle__create_date +#: model:ir.model.fields,field_description:commission.field_commission_section__create_date +#: model:ir.model.fields,field_description:commission.field_commission_settlement__create_date +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__create_date +msgid "Created on" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_res_partner__agent +#: model:ir.model.fields,field_description:commission.field_res_users__agent +msgid "Creditor/Agent" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_line_mixin__currency_id +#: model:ir.model.fields,field_description:commission.field_commission_settlement__currency_id +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__currency_id +msgid "Currency" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__date +msgid "Date" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_search +msgid "Date from month" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_line_search +msgid "Date month" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_commission_mixin_agent_only +msgid "Discard" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__display_name +#: model:ir.model.fields,field_description:commission.field_commission_make_settle__display_name +#: model:ir.model.fields,field_description:commission.field_commission_section__display_name +#: model:ir.model.fields,field_description:commission.field_commission_settlement__display_name +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__display_name +msgid "Display Name" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__res_partner__agent_type__agent +msgid "External agent" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__fix_qty +#: model:ir.model.fields.selection,name:commission.selection__commission__commission_type__fixed +msgid "Fixed percentage" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_product_product__commission_free +#: model:ir.model.fields,field_description:commission.field_product_template__commission_free +msgid "Free of commission" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_section__amount_from +#: model:ir.model.fields,field_description:commission.field_commission_settlement__date_from +msgid "From" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.commission_form +msgid "General information" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__commission__amount_base_type__gross_amount +msgid "Gross Amount" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_line_search +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_search +msgid "Group By" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__id +#: model:ir.model.fields,field_description:commission.field_commission_make_settle__id +#: model:ir.model.fields,field_description:commission.field_commission_section__id +#: model:ir.model.fields,field_description:commission.field_commission_settlement__id +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__id +msgid "ID" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission____last_update +#: model:ir.model.fields,field_description:commission.field_commission_make_settle____last_update +#: model:ir.model.fields,field_description:commission.field_commission_section____last_update +#: model:ir.model.fields,field_description:commission.field_commission_settlement____last_update +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__write_uid +#: model:ir.model.fields,field_description:commission.field_commission_make_settle__write_uid +#: model:ir.model.fields,field_description:commission.field_commission_section__write_uid +#: model:ir.model.fields,field_description:commission.field_commission_settlement__write_uid +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__write_date +#: model:ir.model.fields,field_description:commission.field_commission_make_settle__write_date +#: model:ir.model.fields,field_description:commission.field_commission_section__write_date +#: model:ir.model.fields,field_description:commission.field_commission_settlement__write_date +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__write_date +msgid "Last Updated on" +msgstr "" + +#. module: commission +#: model:ir.model,name:commission.model_commission_settlement_line +msgid "Line of a commission settlement" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settled_wizard +msgid "Make settlements" +msgstr "" + +#. module: commission +#: model:res.groups,name:commission.group_commission_manager +msgid "Manager" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__commission_settlement__settlement_type__manual +msgid "Manual" +msgstr "" + +#. module: commission +#: model:ir.model,name:commission.model_commission_mixin +msgid "" +"Mixin model for applying to any object that wants to handle commissions" +msgstr "" + +#. module: commission +#: model:ir.model,name:commission.model_commission_line_mixin +msgid "" +"Mixin model for having commission agent lines in any object inheriting from " +"this one" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__res_partner__settlement__monthly +msgid "Monthly" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__name +#: model:ir.model.fields,field_description:commission.field_commission_settlement__name +msgid "Name" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__commission__amount_base_type__net_amount +msgid "Net Amount" +msgstr "" + +#. module: commission +#: code:addons/commission/models/commission_mixin.py:0 +#, python-format +msgid "No commission agents" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_line_mixin__object_id +msgid "Parent" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_section__percent +msgid "Percent" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_mixin__product_id +msgid "Product" +msgstr "" + +#. module: commission +#: model:ir.model,name:commission.model_product_template +msgid "Product Template" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__res_partner__settlement__quaterly +msgid "Quarterly" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.commission_form +msgid "Rates definition" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_commission_mixin_agent_only +msgid "Save" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__section_ids +msgid "Sections" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settled_wizard +msgid "Select the date up to which you want to make the settlements:" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__res_partner__settlement__semi +msgid "Semi-annual" +msgstr "" + +#. module: commission +#: model:ir.actions.act_window,name:commission.action_agents_settlement +#: model:ir.ui.menu,name:commission.menu_commission_make_settle +msgid "Settle Commissions" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settled_wizard +msgid "Settle commissions" +msgstr "" + +#. module: commission +#: model:ir.model.fields.selection,name:commission.selection__commission_settlement__state__settled +msgid "Settled" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__settled_amount +msgid "Settled Amount" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_partner_form_agent +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_line_tree +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_tree +msgid "Settled total" +msgstr "" + +#. module: commission +#: model:ir.model,name:commission.model_commission_settlement +#: model:ir.model.fields,field_description:commission.field_commission_settlement_line__settlement_id +#: model:ir.model.fields,field_description:commission.field_res_partner__settlement_ids +#: model:ir.model.fields,field_description:commission.field_res_users__settlement_ids +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_form +msgid "Settlement" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__settlement_type +#: model:ir.model.fields,field_description:commission.field_commission_make_settle__settlement_type +#: model:ir.model.fields,field_description:commission.field_commission_settlement__settlement_type +msgid "Settlement Type" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_line_graph +msgid "Settlement analysis" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_settlement__line_ids +msgid "Settlement lines" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_line_search +msgid "Settlement lines search" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_res_partner__settlement +#: model:ir.model.fields,field_description:commission.field_res_users__settlement +msgid "Settlement period" +msgstr "" + +#. module: commission +#: model:ir.actions.report,name:commission.action_report_settlement +msgid "Settlement report" +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_search +msgid "Settlement search" +msgstr "" + +#. module: commission +#: model:ir.actions.act_window,name:commission.action_commission_settlement +#: model:ir.ui.menu,name:commission.menu_settlement +#: model:ir.ui.menu,name:commission.menu_settlement_parent +#: model_terms:ir.ui.view,arch_db:commission.view_partner_form_agent +msgid "Settlements" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_settlement__state +msgid "State" +msgstr "" + +#. module: commission +#: model:ir.model.fields,help:commission.field_commission_settlement__can_edit +msgid "Technical field for determining if user can edit settlements" +msgstr "" + +#. module: commission +#: model:ir.model.fields,help:commission.field_commission_make_settle__can_settle +msgid "" +"Technical field for improving UX when no extra *commission is installed." +msgstr "" + +#. module: commission +#: code:addons/commission/models/commission.py:0 +#, python-format +msgid "The lower limit cannot be greater than upper one." +msgstr "" + +#. module: commission +#: model:ir.model.fields,help:commission.field_commission_settlement__settlement_type +msgid "" +"The source of the settlement, e.g. 'Sales invoice', 'Sales order', 'Purchase" +" order'..." +msgstr "" + +#. module: commission +#: model:ir.model.fields,help:commission.field_res_partner__commission_id +#: model:ir.model.fields,help:commission.field_res_users__commission_id +msgid "" +"This is the default commission used in the sales where this agent is " +"assigned. It can be changed on each operation if needed." +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_line_search +#: model_terms:ir.ui.view,arch_db:commission.view_settlement_search +msgid "This year" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_section__amount_to +#: model:ir.model.fields,field_description:commission.field_commission_settlement__date_to +msgid "To" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_settlement__total +msgid "Total" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission__commission_type +#: model:ir.model.fields,field_description:commission.field_commission_settlement__agent_type +#: model:ir.model.fields,field_description:commission.field_res_partner__agent_type +#: model:ir.model.fields,field_description:commission.field_res_users__agent_type +msgid "Type" +msgstr "" + +#. module: commission +#: model:ir.model.fields,field_description:commission.field_commission_make_settle__date_to +msgid "Up to" +msgstr "" + +#. module: commission +#: model:res.groups,name:commission.group_commission_user +msgid "User" +msgstr "" + +#. module: commission +#: model:ir.model,name:commission.model_commission_make_settle +msgid "Wizard for settling commissions" +msgstr "" + +#. module: commission +#: model:ir.model.constraint,message:commission.constraint_account_invoice_line_agent_unique_agent +#: model:ir.model.constraint,message:commission.constraint_commission_line_mixin_unique_agent +#: model:ir.model.constraint,message:commission.constraint_sale_order_line_agent_unique_agent +msgid "You can only add one time each agent." +msgstr "" + +#. module: commission +#: model_terms:ir.ui.view,arch_db:commission.view_settled_wizard +msgid "or" +msgstr "" From 0ce64dd3e7f91d5ec734ea43bb9e7927c0e6e8f4 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 5 Dec 2022 13:12:26 +0000 Subject: [PATCH 05/85] [UPD] README.rst --- commission/README.rst | 6 +++--- commission/static/description/index.html | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/commission/README.rst b/commission/README.rst index 3ff8f641d..1ea503bcc 100644 --- a/commission/README.rst +++ b/commission/README.rst @@ -19,9 +19,9 @@ Commissions .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png :target: https://translation.odoo-community.org/projects/commission-15-0/commission-15-0-commission :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/commission&target_branch=15.0 - :alt: Try me on Runboat +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/165/15.0 + :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| diff --git a/commission/static/description/index.html b/commission/static/description/index.html index 5efa46416..374229512 100644 --- a/commission/static/description/index.html +++ b/commission/static/description/index.html @@ -3,7 +3,7 @@ - + Commissions