diff --git a/crm_commission/README.rst b/crm_commission/README.rst new file mode 100644 index 000000000..6efe73826 --- /dev/null +++ b/crm_commission/README.rst @@ -0,0 +1,113 @@ +============== +CRM Commission +============== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:efc3406872e760bc5bfa8cc2168260090fe47794bf91a1c8ec0d6797e93167da + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/18.0/crm_commission + :alt: OCA/commission +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/commission-18-0/commission-18-0-crm_commission + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/commission&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module bridges the CRM and commission modules by allowing +commission agents to be assigned directly on leads and opportunities. + +When a lead is converted into an opportunity and a customer is created +or linked, the agents defined on the lead are automatically propagated +to the partner's ``agent_ids`` field. This ensures that sale orders +generated from that opportunity will inherit the correct commission +agents through the standard ``sale_commission_oca`` computation. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +No special configuration is needed. Just install the module and the +``Agents`` field will appear on the lead/opportunity form, next to the +tags field. + +Make sure your agents are properly configured in the ``commission_oca`` +module (partners with the *Creditor/Agent* flag enabled and a commission +assigned). + +Usage +===== + +1. Open a lead or opportunity in the CRM. + +2. In the **Agents** field, select one or more commission agents. + +3. When converting the lead to an opportunity: + + - **Create a new customer**: the new partner will be created with the + agents already assigned. + - **Link to an existing customer**: the agents will be added to the + existing partner (without duplicating agents already present). + +4. Any sale order created for that partner will automatically inherit + the commission agents on its lines (via ``sale_commission_oca``). + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* KMEE + +Contributors +------------ + +- `KMEE `__: + + - Luis Felipe Mileo + +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. + +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/crm_commission/__init__.py b/crm_commission/__init__.py new file mode 100644 index 000000000..aee8895e7 --- /dev/null +++ b/crm_commission/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/crm_commission/__manifest__.py b/crm_commission/__manifest__.py new file mode 100644 index 000000000..ee5ea5e9f --- /dev/null +++ b/crm_commission/__manifest__.py @@ -0,0 +1,17 @@ +{ + "name": "CRM Commission", + "version": "18.0.1.0.0", + "category": "Sales/CRM", + "summary": "Propagate commission agents from CRM leads to partners", + "author": "KMEE, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/commission", + "license": "AGPL-3", + "depends": [ + "crm", + "commission_oca", + ], + "data": [ + "views/crm_lead_views.xml", + ], + "installable": True, +} diff --git a/crm_commission/models/__init__.py b/crm_commission/models/__init__.py new file mode 100644 index 000000000..e66f0d6cf --- /dev/null +++ b/crm_commission/models/__init__.py @@ -0,0 +1 @@ +from . import crm_lead diff --git a/crm_commission/models/crm_lead.py b/crm_commission/models/crm_lead.py new file mode 100644 index 000000000..d37096195 --- /dev/null +++ b/crm_commission/models/crm_lead.py @@ -0,0 +1,24 @@ +from odoo import fields, models + + +class CrmLead(models.Model): + _inherit = "crm.lead" + + lead_agent_ids = fields.Many2many( + comodel_name="res.partner", + relation="crm_lead_agent_rel", + column1="lead_id", + column2="agent_id", + string="Agentes", + domain=[("agent", "=", True)], + help="Agentes comissionados para este lead. " + "Serão propagados ao cliente na conversão.", + ) + + def _prepare_customer_values(self, partner_name, is_company=False, parent_id=False): + vals = super()._prepare_customer_values( + partner_name, is_company=is_company, parent_id=parent_id + ) + if self.lead_agent_ids: + vals["agent_ids"] = [(6, 0, self.lead_agent_ids.ids)] + return vals diff --git a/crm_commission/pyproject.toml b/crm_commission/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/crm_commission/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/crm_commission/readme/CONFIGURE.md b/crm_commission/readme/CONFIGURE.md new file mode 100644 index 000000000..49e5be485 --- /dev/null +++ b/crm_commission/readme/CONFIGURE.md @@ -0,0 +1,7 @@ +No special configuration is needed. Just install the module and the +``Agents`` field will appear on the lead/opportunity form, next to the +tags field. + +Make sure your agents are properly configured in the ``commission_oca`` +module (partners with the *Creditor/Agent* flag enabled and a commission +assigned). diff --git a/crm_commission/readme/CONTRIBUTORS.md b/crm_commission/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..b58485180 --- /dev/null +++ b/crm_commission/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [KMEE](https://www.kmee.com.br): + - Luis Felipe Mileo diff --git a/crm_commission/readme/DESCRIPTION.md b/crm_commission/readme/DESCRIPTION.md new file mode 100644 index 000000000..c465c0c03 --- /dev/null +++ b/crm_commission/readme/DESCRIPTION.md @@ -0,0 +1,8 @@ +This module bridges the CRM and commission modules by allowing commission +agents to be assigned directly on leads and opportunities. + +When a lead is converted into an opportunity and a customer is created or +linked, the agents defined on the lead are automatically propagated to the +partner's ``agent_ids`` field. This ensures that sale orders generated from +that opportunity will inherit the correct commission agents through the +standard ``sale_commission_oca`` computation. diff --git a/crm_commission/readme/USAGE.md b/crm_commission/readme/USAGE.md new file mode 100644 index 000000000..992dd27a6 --- /dev/null +++ b/crm_commission/readme/USAGE.md @@ -0,0 +1,11 @@ +1. Open a lead or opportunity in the CRM. +2. In the **Agents** field, select one or more commission agents. +3. When converting the lead to an opportunity: + + - **Create a new customer**: the new partner will be created with the + agents already assigned. + - **Link to an existing customer**: the agents will be added to the + existing partner (without duplicating agents already present). + +4. Any sale order created for that partner will automatically inherit the + commission agents on its lines (via ``sale_commission_oca``). diff --git a/crm_commission/static/description/index.html b/crm_commission/static/description/index.html new file mode 100644 index 000000000..fae8e708d --- /dev/null +++ b/crm_commission/static/description/index.html @@ -0,0 +1,459 @@ + + + + + +CRM Commission + + + +
+

CRM Commission

+ + +

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

+

This module bridges the CRM and commission modules by allowing +commission agents to be assigned directly on leads and opportunities.

+

When a lead is converted into an opportunity and a customer is created +or linked, the agents defined on the lead are automatically propagated +to the partner’s agent_ids field. This ensures that sale orders +generated from that opportunity will inherit the correct commission +agents through the standard sale_commission_oca computation.

+

Table of contents

+ +
+

Configuration

+

No special configuration is needed. Just install the module and the +Agents field will appear on the lead/opportunity form, next to the +tags field.

+

Make sure your agents are properly configured in the commission_oca +module (partners with the Creditor/Agent flag enabled and a commission +assigned).

+
+
+

Usage

+
    +
  1. Open a lead or opportunity in the CRM.
  2. +
  3. In the Agents field, select one or more commission agents.
  4. +
  5. When converting the lead to an opportunity:
      +
    • Create a new customer: the new partner will be created with the +agents already assigned.
    • +
    • Link to an existing customer: the agents will be added to the +existing partner (without duplicating agents already present).
    • +
    +
  6. +
  7. Any sale order created for that partner will automatically inherit +the commission agents on its lines (via sale_commission_oca).
  8. +
+
+
+

Bug Tracker

+

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

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • KMEE
  • +
+
+
+

Contributors

+
    +
  • KMEE:
      +
    • Luis Felipe Mileo
    • +
    +
  • +
+
+
+

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.

+

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/crm_commission/tests/__init__.py b/crm_commission/tests/__init__.py new file mode 100644 index 000000000..9df6d8f3f --- /dev/null +++ b/crm_commission/tests/__init__.py @@ -0,0 +1 @@ +from . import test_crm_commission diff --git a/crm_commission/tests/test_crm_commission.py b/crm_commission/tests/test_crm_commission.py new file mode 100644 index 000000000..f52b82435 --- /dev/null +++ b/crm_commission/tests/test_crm_commission.py @@ -0,0 +1,144 @@ +from odoo.fields import Datetime +from odoo.tests import tagged, users + +from odoo.addons.crm.tests.common import TestLeadConvertCommon + + +@tagged("crm_commission") +class TestCrmCommission(TestLeadConvertCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.agent = cls.env["res.partner"].create( + { + "name": "Agent Smith", + "agent": True, + "commission_id": cls.env["commission"] + .create( + { + "name": "Test Commission 10%", + "fix_qty": 10.0, + } + ) + .id, + } + ) + cls.agent_2 = cls.env["res.partner"].create( + { + "name": "Agent Jones", + "agent": True, + "commission_id": cls.env["commission"] + .create( + { + "name": "Test Commission 5%", + "fix_qty": 5.0, + } + ) + .id, + } + ) + date = Datetime.from_string("2020-01-20 16:00:00") + cls.crm_lead_dt_mock.now.return_value = date + + def _convert_lead(self, lead, action="create", partner_id=False): + """Helper to run the lead2opportunity wizard.""" + ctx = { + "active_model": "crm.lead", + "active_id": lead.id, + "active_ids": lead.ids, + } + vals = {"action": action} + if partner_id: + vals["partner_id"] = partner_id + convert = ( + self.env["crm.lead2opportunity.partner"].with_context(**ctx).create(vals) + ) + convert.action_apply() + return convert + + @users("user_sales_manager") + def test_lead_agent_propagation_create(self): + """Agent from lead is propagated to newly created partner.""" + lead = self.lead_1.with_user(self.env.user) + lead.write({"lead_agent_ids": [(6, 0, self.agent.ids)]}) + + self._convert_lead(lead, action="create") + + self.assertTrue(lead.partner_id, "Partner should have been created") + self.assertIn( + self.agent, + lead.partner_id.agent_ids, + "Agent should be propagated to the created partner", + ) + + @users("user_sales_manager") + def test_lead_agent_propagation_exist(self): + """Agent from lead is added to existing partner on conversion.""" + lead = self.lead_1.with_user(self.env.user) + lead.write( + { + "lead_agent_ids": [(6, 0, self.agent.ids)], + "partner_id": self.contact_1.id, + } + ) + + self._convert_lead(lead, action="exist", partner_id=self.contact_1.id) + + self.assertEqual(lead.partner_id, self.contact_1) + self.assertIn( + self.agent, + self.contact_1.agent_ids, + "Agent should be added to the existing partner", + ) + + @users("user_sales_manager") + def test_lead_agent_no_duplicate(self): + """If partner already has the agent, it is not duplicated.""" + self.contact_1.write({"agent_ids": [(4, self.agent.id)]}) + lead = self.lead_1.with_user(self.env.user) + lead.write( + { + "lead_agent_ids": [(6, 0, self.agent.ids)], + "partner_id": self.contact_1.id, + } + ) + + self._convert_lead(lead, action="exist", partner_id=self.contact_1.id) + + agent_count = len(self.contact_1.agent_ids.filtered(lambda a: a == self.agent)) + self.assertEqual(agent_count, 1, "Agent should not be duplicated") + + @users("user_sales_manager") + def test_lead_no_agents(self): + """Lead without agents does not alter partner agent_ids.""" + self.contact_1.write({"agent_ids": [(5, 0, 0)]}) + lead = self.lead_1.with_user(self.env.user) + lead.write( + { + "lead_agent_ids": [(5, 0, 0)], + "partner_id": self.contact_1.id, + } + ) + + self._convert_lead(lead, action="exist", partner_id=self.contact_1.id) + + self.assertFalse( + self.contact_1.agent_ids, + "Partner should have no agents since lead had none", + ) + + @users("user_sales_manager") + def test_lead_multiple_agents_create(self): + """Multiple agents from lead are propagated to newly created partner.""" + lead = self.lead_1.with_user(self.env.user) + lead.write( + { + "lead_agent_ids": [(6, 0, (self.agent | self.agent_2).ids)], + } + ) + + self._convert_lead(lead, action="create") + + self.assertTrue(lead.partner_id) + self.assertIn(self.agent, lead.partner_id.agent_ids) + self.assertIn(self.agent_2, lead.partner_id.agent_ids) diff --git a/crm_commission/views/crm_lead_views.xml b/crm_commission/views/crm_lead_views.xml new file mode 100644 index 000000000..f92775bd0 --- /dev/null +++ b/crm_commission/views/crm_lead_views.xml @@ -0,0 +1,33 @@ + + + + + crm.lead.form.commission + crm.lead + + + + + + + + + + + diff --git a/crm_commission/wizards/__init__.py b/crm_commission/wizards/__init__.py new file mode 100644 index 000000000..f34b21a0e --- /dev/null +++ b/crm_commission/wizards/__init__.py @@ -0,0 +1 @@ +from . import crm_lead_to_opportunity diff --git a/crm_commission/wizards/crm_lead_to_opportunity.py b/crm_commission/wizards/crm_lead_to_opportunity.py new file mode 100644 index 000000000..c7106174b --- /dev/null +++ b/crm_commission/wizards/crm_lead_to_opportunity.py @@ -0,0 +1,16 @@ +from odoo import models + + +class Lead2OpportunityPartner(models.TransientModel): + _inherit = "crm.lead2opportunity.partner" + + def _convert_handle_partner(self, lead, action, partner_id): + res = super()._convert_handle_partner(lead, action, partner_id) + if lead.lead_agent_ids and lead.partner_id: + existing = lead.partner_id.agent_ids + new_agents = lead.lead_agent_ids - existing + if new_agents: + lead.partner_id.write( + {"agent_ids": [(4, agent.id) for agent in new_agents]} + ) + return res