[MIG] bemade_partner_email_domain to 18.0

This commit is contained in:
Marc Durepos 2025-09-10 09:30:02 -04:00
parent ba9f1e0c56
commit c78b926565
10 changed files with 360 additions and 0 deletions

View file

@ -0,0 +1,2 @@
from . import models
from . import controllers

View file

@ -0,0 +1,34 @@
{
"name": "Automated Partner Association by Email Domain",
"version": "18.0.0.0.1",
"category": "Extra Tools",
"summary": "Automatically associates partners with companies using matching email domains",
"description": """
This advanced utility module empowers Odoo users with the capacity to automate partner-company associations
based directly on email domains. It helps in eliminating manual effort by establishing a rule of correlation
between the email domain of a partner and the corresponding division or a company.
The automation proceeds as follows:
- On creation of partner records, the system will cross-verify the email domain against the companies
in the records.
- If a match is detected, the partner will automatically be linked with that company.
This module also takes care of instances where there are multiple companies with the same email domain.
In such cases, an email is dispatched to the partner with a selection interface to finalize the association.
The module is a significant productivity enhancer for businesses that deal with a high number of partners
and require efficient management.
""",
"depends": ["base", "mail", "website"],
"data": [
"views/res_partner_views.xml",
"data/mail_template_data.xml",
"data/template_data.xml",
],
"license": "AGPL-3",
"author": "Bemade",
"website": "https://bemade.org/",
"installable": True,
"auto_install": False,
}

View file

@ -0,0 +1 @@
from . import main

View file

@ -0,0 +1,40 @@
# Import necessary libraries
from odoo import http, _
#from odoo.http import request
# Define a controller class that inherits from http.Controller
class DivisionCompanyController(http.Controller):
# Decorator to set the routing rules for the method 'select_division_company'.
# The URL for this method will be '/select_division_company'
# It has public access and should be used for HTTP requests of type GET.
@http.route('/select_division_company', website=True, type='http', auth='public')
def select_division_company(self, partner_id, access_token, division_id, **kwargs):
# Search the 'res.partner' model for a record with the given partner_id using Super User access rights
partner = http.request.env['res.partner'].sudo().search([
('id', '=', int(partner_id))
])
# Check if a partner was found with the given ID
if not partner:
# If no partner found, render an error page with a translatable error message
return http.request.render('bemade_partner_email_domain.error_page',
{'error_message': _('The requested partner is not found.')})
else:
# If the partner exists and the access token matches, proceed with updating the partner's division
if access_token and access_token == partner.access_token:
# Update the partner's parent with the division_id
partner.write({'parent_id': int(division_id)})
# Set a confirmation message signaling that the partner has been associated with the division
confirmation_message = _('Partner %s is associated with the division %s.' % (partner.name, division_id))
# Render the error page (which in this case acts as a status page) with the confirmation message.
return http.request.render('bemade_partner_email_domain.error_page',
{'confirmation_message': confirmation_message})
else:
# If the access token was not provided or does not match, render an error page with an error message
return http.request.render('bemade_partner_email_domain.error_page',
{'error_message': _('No Access Token found or not matching for this partner.')})

View file

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="1">
<record id="email_template_select_parent" model="mail.template">
<field name="name">Division Selection: Choose Your Division</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="email_from">{{object.company_id.email_formatted or user.email_formatted}}</field>
<field name="partner_to">{{object.id}}</field>
<field name="subject">Select Your Division at {{object.company_id.name}}</field>
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0"
style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial, sans-serif; color: #454748; width: 100%; border-collapse:separate;">
<tbody>
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
<tbody>
<!-- HEADER -->
<tr>
<td align="center" style="min-width: 590px;">
<!-- Content here -->
</td>
</tr>
<!-- CONTENT -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tbody>
<tr>
<td valign="top" style="font-size: 13px;">
<p>Hello <t t-out="object.name or ''">Brandon Freeman</t>,</p>
<p>We noticed that you have registered with our company by sending an email or using our website. To provide you with the best possible service, we need you to confirm the division of your company that you are working for.</p>
<p>Please click on the link below to select your division:</p>
<ul>
<t t-foreach="ctx.get('links', {}).items()" t-as="company_name">
<li>
<a t-attf-href="{{ company_name[1] }}" style="font-size:12px">
<t t-esc="company_name[0]"/>
</a>
</li>
</t>
</ul>
<p>If you have any questions or did not register with us, please disregard this email or contact us directly.</p>
<p>Best regards,</p>
<p>The Durpro team.</p>
</td>
</tr>
<!-- More content can be added here -->
</tbody>
</table>
</td>
</tr>
<!-- FOOTER -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tbody>
<tr>
<td valign="middle" align="left">
<t t-out="object.company_id.name or ''"/>
</td>
</tr>
<tr>
<td valign="middle" align="left" style="opacity: 0.7;">
<t t-out="object.company_id.phone or ''"/>
<t t-if="object.company_id.email">
| <a t-att-href="'mailto:%s' % object.company_id.email" style="text-decoration:none; color: #454748;" t-out="object.company_id.email or ''"></a>
</t>
<t t-if="object.company_id.website">
| <a t-att-href="'%s' % object.company_id.website" style="text-decoration:none; color: #454748;" t-out="object.company_id.website or ''"></a>
</t>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<!-- POWERED BY -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: #F1F1F1; color: #454748; padding: 8px; border-collapse:separate;">
<tbody>
<tr>
<td style="text-align: center; font-size: 13px;">
Powered by <a target="_blank" href="https://www.odoo.com?utm_source=db&amp;utm_medium=auth" style="color: #875A7B;">Odoo</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</field>
<field name="lang">{{ object.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
</data>
</odoo>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="error_page">
<base href="/" />
<t t-call="website.layout">
<div id="wrap">
<t t-if="error_message">
<div class="alert alert-danger"><t t-esc="error_message"/></div>
</t>
<t t-if="confirmation_message">
<div class="alert alert-success"><t t-esc="confirmation_message"/></div>
</t>
</div>
</t>
</template>
</data>
</odoo>

View file

@ -0,0 +1,41 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * bemade_partner_email_domain
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: it@bemade.org\n"
"Last-Translator: chatgpt\n"
"Language-Team: French (http://www.transifex.com/odoo/odoo-14-0/language/fr_CA/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#: model:ir.model.fields,field_description:bemade_partner_email_domain.field_res_partner_access_token
msgid "Access Token"
msgstr "Jeton d'accès"
#: model:ir.model.fields,field_description:bemade_partner_email_domain.field_res_partner_email_domain
msgid "Email Domain"
msgstr "Domaine de courriel"
#: model:ir.model.fields,field_description:bemade_partner_email_domain.field_res_partner_is_subdivision
msgid "Subdivision"
msgstr "Subdivision"
#: code:addons/bemade_partner_email_domain/controllers/main.py:0
#, python-format
msgid "The requested partner is not found."
msgstr "Le partenaire demandé n'est pas trouvé."
#: code:addons/bemade_partner_email_domain/controllers/main.py:0
#, python-format
msgid "No Access Token found or not matching for this partner."
msgstr "Aucun jeton d'accès trouvé ou ne correspondant pas à ce partenaire."
#: code:addons/bemade_partner_email_domain/controllers/main.py:0
#, python-format
msgid "Partner %s is associated with the division %s."
msgstr "Le partenaire %s est associé à la division %s."

View file

@ -0,0 +1 @@
from . import res_partner

View file

@ -0,0 +1,97 @@
from odoo import api, fields, models, Command
from odoo.tools.sql import SQL
import uuid
from odoo.addons.website.controllers.main import Website
class Partner(models.Model):
_inherit = "res.partner"
email_domain = fields.Char(string="Email Domain")
is_subdivision = fields.Boolean(string="Subdivision", default=False)
access_token = fields.Char(string="Access Token")
def _generate_access_token(self):
return uuid.uuid4().hex
def _send_selection_email(self, division_companies):
self.ensure_one()
# Generate a token and save it to the partner
access_token = self._generate_access_token()
print(f"partner id: {self.id}")
self.write({"access_token": access_token})
# Now include the token in the links
base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
links = {
company.name: (
f"{base_url}/select_division_company?partner_id={self.id}&access_token={access_token}&division_id={company.id}"
)
for company in division_companies
}
print(f"links: {links}")
# Render the email template and send the email
template = self.env.ref(
"bemade_partner_email_domain.email_template_select_parent"
)
template.with_context(links=links).send_mail(self.id, force_send=True)
def _check_parent_from_email_domain(self):
for rec in self:
if rec.parent_id:
continue
try:
# Split the email address on '@' and get the domain part
if rec.email:
email_domain = rec.email.split("@")[1].strip()
else:
continue
except IndexError:
# If there's no '@' symbol, the email address is invalid
return False
sql = """
SELECT id
FROM res_partner
WHERE email_domain IS NOT NULL
AND email_domain = RIGHT(%s, LENGTH(email_domain))
AND company_id = %s
"""
self.env.cr.execute(
SQL(sql, email_domain, self.env.company and self.env.company.id or None)
)
res = self.env.cr.fetchall()
if len(res) > 1:
rec._send_selection_email(
self.env["res.partner"].search(
[("id", "in", [result[0] for result in res])]
)
)
elif len(res) == 1:
rec.write({"parent_id": res[0][0]})
else:
continue
@api.model_create_multi
def create(self, vals_list):
res = super(Partner, self).create(vals_list)
res._check_parent_from_email_domain()
return res
def write(self, vals):
res = super(Partner, self).write(vals)
if "email" in vals:
self._check_parent_from_email_domain()
return res
@api.onchange("is_subdivision")
def _onchange_is_subdivision(self):
"""Change the contact type since partners of type "contact" get their address
overwritten when the parent_id is changed."""
for record in self:
partner = record._origin
if record.is_subdivision and record.type == "contact":
record.type = "other"
partner.type = "other"

View file

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<odoo>
<record id="partner_email_domain_form" model="ir.ui.view">
<field name="name">bemade_partner_email_domain.partner.email.domain.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<field name="company_type" position="after">
<label for="is_subdivision" string="Division" invisible="is_company == False"/>
<field name="is_subdivision" invisible="is_company == False"/>
</field>
<field name="website" position="before">
<field name="email_domain" invisible="is_company == False"/>
</field>
<field name="parent_id" position="attributes">
<attribute name="invisible">is_company == True and is_subdivision == False</attribute>
</field>
</field>
</record>
</odoo>