diff --git a/srm/README.rst b/srm/README.rst new file mode 100644 index 00000000000..a98bbd56707 --- /dev/null +++ b/srm/README.rst @@ -0,0 +1,82 @@ +============ +SRM +============ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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%2Fcrm-lightgray.png?logo=github + :target: https://github.com/OCA/crm/tree/14.0/crm_location + :alt: OCA/crm +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/crm-14-0/crm-14-0-srm + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/111/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows the usage of crm module to manage leads coming from suppliers. +The flow is similar to CRM. The main change is that leads be generated from customer or supplier request type. +For suplier requests leads can be converted in purchases. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +To install this module, you need: + +* crm + +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 +~~~~~~~ + +* Camptocmap + +Contributors +~~~~~~~~~~~~ + +* Telmo Santos + +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/crm `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/srm/__init__.py b/srm/__init__.py new file mode 100644 index 00000000000..9b4296142f4 --- /dev/null +++ b/srm/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/srm/__manifest__.py b/srm/__manifest__.py new file mode 100644 index 00000000000..d629606b28a --- /dev/null +++ b/srm/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright 2022 Telmo Santos +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + + +{ + "name": "SRM", + "version": "18.0.1.0.0", + "author": "Camptocamp, Odoo Community Association (OCA)", + "summary": "Use CRM model for suppliers", + "license": "AGPL-3", + "category": "CRM", + "depends": [ + "account", + "crm", + "sale_crm", + "purchase", + ], + "website": "https://github.com/OCA/crm", + "data": [ + "security/ir.model.access.csv", + "views/srm_menu_views.xml", + "views/srm_lead_views.xml", + "views/crm_lead_views.xml", + "views/purchase_views.xml", + "wizard/srm_opportunity_to_rfq_views.xml", + ], + "installable": True, +} diff --git a/srm/models/__init__.py b/srm/models/__init__.py new file mode 100644 index 00000000000..ca4bdecbe19 --- /dev/null +++ b/srm/models/__init__.py @@ -0,0 +1,3 @@ +from . import crm_lead +from . import crm_team +from . import purchase diff --git a/srm/models/crm_lead.py b/srm/models/crm_lead.py new file mode 100644 index 00000000000..cc72d00c1e9 --- /dev/null +++ b/srm/models/crm_lead.py @@ -0,0 +1,81 @@ +# Copyright 2022 Telmo Santos +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class CrmLead(models.Model): + _inherit = "crm.lead" + + request_type = fields.Selection( + [ + ("customer", "Customer Lead"), + ("supplier", "Supplier Lead"), + ], + ) + purchase_amount_total = fields.Monetary( + compute="_compute_purchase_data", + string="Sum of Purchase Orders", + help="Untaxed Total of Confirmed Orders", + currency_field="company_currency", + ) + request_for_quotation_count = fields.Integer( + compute="_compute_purchase_data", string="Number of Request for Quotations" + ) + purchase_order_count = fields.Integer( + compute="_compute_purchase_data", string="Number of Purchase Orders" + ) + purchase_order_ids = fields.One2many( + "purchase.order", "opportunity_id", string="Purchase Orders" + ) + + @api.depends( + "order_ids.state", + "order_ids.currency_id", + "order_ids.amount_untaxed", + "order_ids.date_order", + "order_ids.company_id", + ) + def _compute_purchase_data(self): + for lead in self: + total = 0.0 + rfq_cnt = 0 + purchase_order_cnt = 0 + company_currency = lead.company_currency or self.env.company.currency_id + for order in lead.purchase_order_ids: + if order.state in ("draft", "sent"): + rfq_cnt += 1 + if order.state not in ("draft", "sent", "cancel"): + purchase_order_cnt += 1 + total += order.currency_id._convert( + order.amount_untaxed, + company_currency, + order.company_id, + order.date_order or fields.Date.today(), + ) + lead.purchase_amount_total = total + lead.request_for_quotation_count = rfq_cnt + lead.purchase_order_count = purchase_order_cnt + + def _create_customer(self): + """It can be a customer or supplier depending on lead request type""" + self = self.with_context(res_partner_search_mode=self.request_type) + return super()._create_customer() + + def action_lead_rfq_new(self): + if not self.partner_id: + return self.env["ir.actions.actions"]._for_xml_id( + "srm.srm_rfq_partner_action" + ) + else: + return self.action_rfq_new() + + def action_rfq_new(self): + action = self.env["ir.actions.actions"]._for_xml_id("srm.action_lead_rfq_new") + action["context"] = { + "default_partner_id": self.partner_id.id, + "default_opportunity_id": self.id, + } + if self.user_id: + action["context"]["default_user_id"] = self.user_id.id + return action diff --git a/srm/models/crm_team.py b/srm/models/crm_team.py new file mode 100644 index 00000000000..ff0b2eaa389 --- /dev/null +++ b/srm/models/crm_team.py @@ -0,0 +1,19 @@ +# Copyright 2022 Telmo Santos +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class Team(models.Model): + _inherit = "crm.team" + + @api.model + def action_your_pipeline(self): + action = super().action_your_pipeline() + request_type = self.env.context.get("request_type") + if request_type: + action["domain"] = ( + f"[('type','=','opportunity'), ('request_type', '=', '{request_type}')]" + ) + action["context"]["default_request_type"] = request_type + return action diff --git a/srm/models/purchase.py b/srm/models/purchase.py new file mode 100644 index 00000000000..ac97f456cde --- /dev/null +++ b/srm/models/purchase.py @@ -0,0 +1,15 @@ +# Copyright 2022 Telmo Santos +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class PurchaseOrder(models.Model): + _inherit = "purchase.order" + + opportunity_id = fields.Many2one( + "crm.lead", + string="Opportunity", + check_company=True, + domain="[('type', '=', 'opportunity'), ('request_type', '=', 'supplier'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", # noqa + ) diff --git a/srm/pyproject.toml b/srm/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/srm/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/srm/security/ir.model.access.csv b/srm/security/ir.model.access.csv new file mode 100644 index 00000000000..f7e0abb7e62 --- /dev/null +++ b/srm/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_srm_rfq_partner,access.srm.rfq.partner,model_srm_rfq_partner,purchase.group_purchase_user,1,1,1,0 diff --git a/srm/views/crm_lead_views.xml b/srm/views/crm_lead_views.xml new file mode 100644 index 00000000000..081ce2bd8ac --- /dev/null +++ b/srm/views/crm_lead_views.xml @@ -0,0 +1,19 @@ + + + + action = model.with_context(request_type='customer').action_your_pipeline() + + + + [('type','=','opportunity'),('request_type','in',(False,'customer'))] + { + 'default_type': 'opportunity', + 'default_request_type': 'customer', + 'search_default_assigned_to_me': 1, + } + + diff --git a/srm/views/purchase_views.xml b/srm/views/purchase_views.xml new file mode 100644 index 00000000000..e901a498139 --- /dev/null +++ b/srm/views/purchase_views.xml @@ -0,0 +1,13 @@ + + + + purchase.order.form + purchase.order + + + + + + + + diff --git a/srm/views/srm_lead_views.xml b/srm/views/srm_lead_views.xml new file mode 100644 index 00000000000..b82bdeb4e29 --- /dev/null +++ b/srm/views/srm_lead_views.xml @@ -0,0 +1,246 @@ + + + + crm.lead.form + crm.lead + + + + 1 + + + + + + + + + + + + Responsible + + + Team + + + + + + Lead RFQ new + purchase.order + form + {'search_default_partner_id': id, 'default_partner_id': id} + + + + crm.lead.oppor.inherited.crm + crm.lead + + + + + + + + + + + + + + srm.lead.form.quick_create + crm.lead + + + + + + + + + + + Srm: Pipeline + crm.lead + kanban,list,graph,pivot,form,calendar,activity + [('type','=','opportunity'), ('request_type','=', 'supplier')] + { + 'default_type': 'opportunity', + 'search_default_assigned_to_me': 1, + 'default_request_type': 'supplier', + } + + + + + Srm: My Pipeline + + code + action = model.with_context(request_type='supplier').action_your_pipeline() + + + + + Leads + crm.lead + list,kanban,graph,pivot,calendar,form,activity + ['&','|', ('type','=','lead'), ('type','=',False), ('request_type', '=', 'supplier')] + + { + 'default_type':'lead', + 'default_request_type': 'supplier', + 'search_default_type': 'lead', + 'search_default_to_process':1, + } + + + + + + Srm Leads Analysis + crm.lead + pivot,graph,list + ['&', ('request_type','=', 'supplier'), '|', ('active','=',True), ('active','=',False)] + + + + + + Pipeline Analysis + crm.lead + pivot,graph,list,form,cohort + [('request_type','=', 'supplier')] + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srm/views/srm_menu_views.xml b/srm/views/srm_menu_views.xml new file mode 100644 index 00000000000..860a6aeb3f2 --- /dev/null +++ b/srm/views/srm_menu_views.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + diff --git a/srm/wizard/__init__.py b/srm/wizard/__init__.py new file mode 100644 index 00000000000..14fe9893e82 --- /dev/null +++ b/srm/wizard/__init__.py @@ -0,0 +1 @@ +from . import srm_opportunity_to_rfq diff --git a/srm/wizard/srm_opportunity_to_rfq.py b/srm/wizard/srm_opportunity_to_rfq.py new file mode 100644 index 00000000000..6fa783202a8 --- /dev/null +++ b/srm/wizard/srm_opportunity_to_rfq.py @@ -0,0 +1,58 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from odoo import api, fields, models +from odoo.exceptions import UserError + + +class Opportunity2Rfq(models.TransientModel): + _name = "srm.rfq.partner" + _description = "Create new or use existing Supplier on new RFQ" + + @api.model + def default_get(self, fields): + result = super().default_get(fields) + + active_model = self._context.get("active_model") + if active_model != "crm.lead": + raise UserError(self.env._("You can only apply this action from a lead.")) + + lead = False + if result.get("lead_id"): + lead = self.env["crm.lead"].browse(result["lead_id"]) + elif "lead_id" in fields and self._context.get("active_id"): + lead = self.env["crm.lead"].browse(self._context["active_id"]) + if lead: + result["lead_id"] = lead.id + partner_id = result.get("partner_id") or lead._find_matching_partner().id + if "action" in fields and not result.get("action"): + result["action"] = "exist" if partner_id else "create" + if "partner_id" in fields and not result.get("partner_id"): + result["partner_id"] = partner_id + + return result + + action = fields.Selection( + [ + ("create", "Create a new vendor"), + ("exist", "Link to an existing vendor"), + ("nothing", "Do not link to a vendor"), + ], + string="RFQ Vendor", + required=True, + ) + lead_id = fields.Many2one("crm.lead", "Associated Lead", required=True) + partner_id = fields.Many2one("res.partner", "Vendor") + + def action_apply(self): + """Convert lead to opportunity or merge lead and opportunity and open + the freshly created opportunity view. + """ + self.ensure_one() + if self.action == "create": + self.lead_id.handle_partner_assignment(create_missing=True) + elif self.action == "exist": + self.lead_id.handle_partner_assignment( + force_partner_id=self.partner_id.id, create_missing=False + ) + return self.lead_id.action_rfq_new() diff --git a/srm/wizard/srm_opportunity_to_rfq_views.xml b/srm/wizard/srm_opportunity_to_rfq_views.xml new file mode 100644 index 00000000000..41196dc3ac4 --- /dev/null +++ b/srm/wizard/srm_opportunity_to_rfq_views.xml @@ -0,0 +1,45 @@ + + + + srm.rfq.partner.view.form + srm.rfq.partner + +
+ + + + + + + + + + + +
+
+
+
+
+ + + New RFQ + ir.actions.act_window + srm.rfq.partner + form + + new + +