Merge pull request #13 from expsa/os_whatsapp

os_whatsapp
This commit is contained in:
esam-sermah 2025-09-22 13:34:32 +03:00 committed by GitHub
commit a22bb3522e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 597 additions and 0 deletions

View File

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

View File

@ -0,0 +1,32 @@
{
'name': 'Odoo Whatsapp Integration',
'version': '18.0.1.0',
'summary': 'Odoo Whatsapp Integration',
'author': 'Odosquare',
'company': 'Odosquare',
'maintainer': 'Odosquare',
'sequence': 4,
'images': ['static/description/Banner.png'],
'license': 'LGPL-3',
'description': """Odoo Whatsapp Integration""",
'category': 'Connector',
'depends': [
'base', 'contacts', 'sale', 'crm', 'stock', 'sale_management', 'account', 'purchase'
],
'data': [
'security/ir.model.access.csv',
'data/whatsapp_template.xml',
'views/sale_wa.xml',
'views/crm_wa.xml',
'views/purchase_wa.xml',
'views/stock_wa.xml',
'views/invoice_wa.xml',
'views/contact_wa.xml',
'wizard/wizard.xml',
],
'demo': [],
'qweb': [],
'installable': True,
'application': False,
'auto_install': False,
}

View File

@ -0,0 +1,109 @@
<?xml version="1.0"?>
<odoo noupdate="1">
<record id="sales_whatsapp_template" model="mail.template">
<field name="name">Sales: Confirm Order</field>
<field name="subject">Sales Confirm</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="body_html"><![CDATA[<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px;">
<p> Hello *${object.partner_id.name or ''}*,</p>
Your order ${object.name} amount *${format_amount(object.amount_total, object.currency_id)}* is Confirmed.
<br>
Kindly refer below link for order receipt.
<br>
]]>
</field>
</record>
<!-- <record id="sales_whatsapp_template" model="mail.template">-->
<!-- <field name="name">Sales: Test Order</field>-->
<!-- <field name="subject">Sales Confirm</field>-->
<!-- <field name="model_id" ref="sale.model_sale_order"/>-->
<!-- <field name="body_html"><![CDATA[<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px;">-->
<!-- <p> Hello *${object.partner_id.name or ''}*,</p>-->
<!-- Your order ${object.name} amount *${format_amount(object.amount_total, object.currency_id)}* is Confirmed.-->
<!-- <br>-->
<!-- Kindly refer below link for order receipt.-->
<!-- <br>-->
<!-- ]]>-->
<!-- </field>-->
<!-- </record>-->
<record id="purchase_whatsapp_template" model="mail.template">
<field name="name">Purchase: Confirm Order</field>
<field name="subject">Purchase Order</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
<field name="body_html"><![CDATA[<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;">
Dear ${object.partner_id.name}
% if object.partner_id.parent_id:
(${object.partner_id.parent_id.name})
% endif
<br/><br/>
Here is in attachment a purchase order <strong>${object.name}</strong>
% if object.partner_ref:
with reference: ${object.partner_ref}
% endif
amounting in <strong>${format_amount(object.amount_total, object.currency_id)}</strong>
from ${object.company_id.name}.
<br/><br/>
If you have any questions, please do not hesitate to contact us.
<br/><br/>
Best regards,
</p>
]]>
</field>
</record>
<record id="invoice_whatsapp_template" model="mail.template">
<field name="name">Invoice: Send by WhatsApp</field>
<field name="model_id" ref="account.model_account_move"/>
<field name="subject">Purchase Order</field>
<field name="body_html"><![CDATA[<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;">
Dear
% if object.partner_id.parent_id:
${object.partner_id.name} (${object.partner_id.parent_id.name}),
% else:
${object.partner_id.name},
% endif
<br /><br />
Here is your
% if object.name:
invoice <strong>${object.name}</strong>
% else:
invoice
%endif
% if object.invoice_origin:
(with reference: ${object.invoice_origin})
% endif
amounting in <strong>${format_amount(object.amount_total, object.currency_id)}</strong>
from ${object.company_id.name}.
% if object.payment_state == 'paid':
This invoice is already paid.
% else:
Please remit payment at your earliest convenience.
% if object.payment_reference:
<br /><br />
Please use the following communication for your payment: <strong>${object.payment_reference}</strong>.
% endif
% endif
<br /><br />
Do not hesitate to contact us if you have any questions.
% if object.invoice_user_id.signature:
<br />
${object.invoice_user_id.signature | safe}
% endif
</p>
]]>
</field>
<!-- <field name="report_template" ref="account.account_invoices"/>-->
<!-- <field name="report_name">-->
<!-- Invoice_${(object.name or '').replace('/','_')}${object.state == 'draft' and '_draft' or ''}-->
<!-- </field>-->
<!-- <field name="lang">${object.partner_id.lang}</field>-->
<!-- <field name="user_signature" eval="False"/>-->
<!-- <field name="auto_delete" eval="True"/>-->
</record>
</odoo>

View File

@ -0,0 +1,6 @@
from . import contacts_wa
from . import crm_wa
from . import invoice_wa
from . import purchase_wa
from . import sale_wa
from . import stock_wa

View File

@ -0,0 +1,15 @@
from odoo import _, models
class ResPartner(models.Model):
_inherit = 'res.partner'
def contacts_whatsapp(self):
return {'type': 'ir.actions.act_window',
'name': _('Whatsapp Message'),
'res_model': 'whatsapp.message.wizard',
'target': 'new',
'view_mode': 'form',
'view_type': 'form',
'context': {'default_user_id': self.id},
}

View File

@ -0,0 +1,15 @@
from odoo import _, models
class WhatsappCrm(models.Model):
_inherit = 'crm.lead'
def crm_whatsapp(self):
return {'type': 'ir.actions.act_window',
'name': _('Whatsapp Message'),
'res_model': 'whatsapp.message.wizard',
'target': 'new',
'view_mode': 'form',
'view_type': 'form',
'context': {'default_user_id': self.partner_id.id},
}

View File

@ -0,0 +1,15 @@
from odoo import _, models
class WhatsappInvoice(models.Model):
_inherit = 'account.move'
def invoice_whatsapp(self):
return {'type': 'ir.actions.act_window',
'name': _('Whatsapp Message'),
'res_model': 'whatsapp.message.wizard',
'target': 'new',
'view_mode': 'form',
'view_type': 'form',
'context': {'default_template_id': self.env.ref('os_whatsapp_integration.invoice_whatsapp_template').id},
}

View File

@ -0,0 +1,15 @@
from odoo import _, models
class WhatsappPurchase(models.Model):
_inherit = 'purchase.order'
def purchase_whatsapp(self):
return {'type': 'ir.actions.act_window',
'name': _('Whatsapp Message'),
'res_model': 'whatsapp.message.wizard',
'target': 'new',
'view_mode': 'form',
'view_type': 'form',
'context': {'default_template_id': self.env.ref('os_whatsapp_integration.purchase_whatsapp_template').id},
}

View File

@ -0,0 +1,15 @@
from odoo import _, models
class WhatsappSale(models.Model):
_inherit = 'sale.order'
def sale_whatsapp(self):
return {'type': 'ir.actions.act_window',
'name': _('Whatsapp Message'),
'res_model': 'whatsapp.message.wizard',
'target': 'new',
'view_mode': 'form',
'view_type': 'form',
'context': {'default_template_id': self.env.ref('os_whatsapp_integration.sales_whatsapp_template').id},
}

View File

@ -0,0 +1,15 @@
from odoo import _, models
class WhatsappPurchase(models.Model):
_inherit = 'stock.picking'
def stock_whatsapp(self):
return {'type': 'ir.actions.act_window',
'name': _('Whatsapp Message'),
'res_model': 'whatsapp.message.wizard',
'target': 'new',
'view_mode': 'form',
'view_type': 'form',
'context': {'default_user_id': self.partner_id.id},
}

View File

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_group_user,Access User,model_whatsapp_message_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_group_user Access User model_whatsapp_message_wizard base.group_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,172 @@
<section class="oe_container oe_dark">
<div class="col-md-12">
<h2 class="oe_slogan" style="font-size: 35px;color:#2C0091"><b>Odoo WhatsApp Integration</b></h2>
</div>
</section>
<section class="oe_container">
<div class="col-md-12">
<h2 class="oe_slogan" style="font-size: 25px;color:#000000"><b>Now Send Messages to the customers and partners
through WhatsApp. The WhatsApp feature is available in the modules Sales, CRM, Purchase, Invoice, Inventory and
Contacts. With this feature, the communication between the partners become more easier as the new technologies.
</b></h2>
</div>
</section>
<section class="oe_container apps">
<div>
<div>
<h3 class="oe_slogan"
style="opacity:1;background: linear-gradient(to right, yellow 0%, violet 100%);padding: 10px 0px;color: black;letter-spacing: 0.5em;text-transform: uppercase;text-shadow: 0.1em .04em 0.1em #7d8c88;">
<b>Available in</b>
</h3>
</div>
<div class="row">
<div class="col" style="margin: 10px;">
<div class="row">
<div class="col-md-3 text-center">
<img class="img img-responsive" style="max-width:100%;" src="feature/contacts.png"/>
</div>
<div class="col-md-9">
<h4><b>Using WhatsApp For Customer Communication</b></h4>
<span class="oe_slogan">Direct chat with the contacts in the contacts module through WhatsApp</span>
</div>
</div>
</div>
<div class="col" style="margin: 10px;">
<div class="row">
<div class="col-md-3 text-center">
<img class="img img-responsive" style="max-width:100%;" src="feature/sale.png"/>
</div>
<div class="col-md-9">
<h4><b>Creative Usage Of WhatsApp For Business</b></h4>
<span class="oe_slogan">Grow your business profitably and creatively. Use WhatsApp as an extra sales channel. Easy to send the Quotation, Sale Order, etc... related files to the corresponding partners</span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col" style="margin: 10px;">
<div class="row">
<div class="col-md-3 text-center">
<img class="img img-responsive" style="max-width:100%;" src="feature/crm.png"/>
</div>
<div class="col-md-9">
<h4><b>Using WhatsApp For Marketing & Promotion</b></h4>
<span class="oe_slogan">WhatsApp can be a very solid marketing tool. It is a good individual platform for direct communication. You can use WhatsApp to send images, audio files, short videos of your products and more. </span>
</div>
</div>
</div>
<div class="col" style="margin: 10px;">
<div class="row">
<div class="col-md-3 text-center">
<img class="img img-responsive" style="max-width:100%;" src="feature/account.png"/>
</div>
<div class="col-md-9">
<h4><b>Using WhatsApp For payment reminder </b></h4>
<span class="oe_slogan">Whatsapp will help you bill more, faster and control and speed up your collection process.</span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col" style="margin: 10px;">
<div class="row">
<div class="col-md-3 text-center">
<img class="img img-responsive" style="max-width:100%;" src="feature/purchase.png"/>
</div>
<div class="col-md-9">
<h4><b>Quick collection of accounts receivable, payment reminder</b></h4>
<span class="oe_slogan">Your collection team will have an additional communication channel to streamline your accounts receivable process.</span>
</div>
</div>
</div>
<div class="col" style="margin: 10px;">
<div class="row">
<div class="col-md-3 text-center">
<img class="img img-responsive" style="max-width:100%;" src="feature/stock.png"/>
</div>
<div class="col-md-9">
<h4><b> Using WhatsApp For Customer Support </b></h4>
<span class="oe_slogan">Our customers would always prefer to send you a message over WhatsApp rather than calling a helpdesk number or raising a ticket. </span>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="oe_container exp">
<div>
<div>
<p class="oe_slogan" style="color: black;font-size: 35px; font-style: italic;font-weight:bolder;"><u>Contacts</u></p>
<br/>
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/contact1.jpg">
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/contact2.jpg">
</div>
</div>
</section>
<section class="oe_container exp">
<div>
<div>
<p class="oe_slogan" style="color: black;font-size: 35px; font-style: italic;font-weight:bolder;"><u>Sales</u></p>
<br/>
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/sale1.jpg">
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/sale2.jpg">
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/sale3.jpg">
</div>
</div>
</section>
<section class="oe_container exp">
<div>
<div>
<p class="oe_slogan" style="color: black;font-size: 35px; font-style: italic;font-weight:bolder;"><u>Invoice</u></p>
<br/>
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/invoice1.jpg">
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/invoice2.jpg">
</div>
</div>
</section>
<section class="oe_container exp">
<div>
<div>
<p class="oe_slogan" style="color: black;font-size: 35px; font-style: italic;font-weight:bolder;"><u>Purchase</u></p>
<br/>
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/purchase1.jpg">
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/purchase2.jpg">
</div>
</div>
</section>
<section class="oe_container exp">
<div>
<div>
<p class="oe_slogan" style="color: black;font-size: 35px; font-style: italic;font-weight:bolder;"><u>Inventory</u></p>
<br/>
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/stock1.jpg">
</div>
</div>
</section>
<section class="oe_container exp">
<div>
<div>
<p class="oe_slogan" style="color: black;font-size: 35px; font-style: italic;font-weight:bolder;"><u>CRM</u></p>
<br/>
<img class="img-border img-responsive thumbnail mb16" style="border: 1px solid black;" width="1050px" src="what_index/crm1.jpg">
</div>
</div>
</section>
<hr style="width: 100%;height: 4px;background: #2C0091;margin: 0px 0px;">
<hr style="width: 100%;height: 4px;background: #148963;margin: 0px 0px;">
<section class="oe_container oe_dark">
<div class="oe_row ">
<div class="oe_slogan text-center">
<div style="color:#269900;">
<h3 style="color:#2C0091;font-size: 25px;">If you have any queries or doubt just contact us:</h3><br>
<h3 style="color:#2C0091;font-size: 20px;">Email: <a href="mailto:support@odosquare.com" target="_top">support@odosquare.com</a> <br></h3>
<h3 style="color:#2C0091;font-size: 20px;">Website: <a href="http://www.odosquare.com" target="new">www.odosquare.com</a> <br></h3>
</div>
</div>
</div>
</section>
<hr style="width: 100%;height: 4px;background: #148963;margin: 0px 0px;">
<hr style="width: 100%;height: 4px;background: #2C0091;margin: 0px 0px;">

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="whatsapp_res_view_form" model="ir.ui.view">
<field name="name">whatsapp.view.form.inherit.res</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='image_1920']" position="before">
<div class="oe_button_box" name="button_box">
<button name="contacts_whatsapp" string="Whatsapp Message" type="object" class="oe_stat_button"
icon="fa-whatsapp"/>
</div>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="whatsapp_crm_view_form" model="ir.ui.view">
<field name="name">whatsapp.view.form.inherit.crm</field>
<field name="model">crm.lead</field>
<field name="inherit_id" ref="crm.crm_lead_view_form"/>
<field name="arch" type="xml">
<header>
<button name="crm_whatsapp" string="Send Whatsapp" type="object" class="btn-secondary"
icon="fa-whatsapp"/>
</header>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="whatsapp_account_view_form" model="ir.ui.view">
<field name="name">whatsapp.view.form.inherit.account</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<header>
<button name="invoice_whatsapp" string="Send Whatsapp" type="object" class="btn-secondary"
icon="fa-whatsapp" context="{'default_user_id' : partner_id}"
invisible="state != 'posted'"/>
</header>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="whatsapp_purchase_view_form" model="ir.ui.view">
<field name="name">whatsapp.view.form.inherit.purchase</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<header>
<button name="purchase_whatsapp" string="Send Whatsapp" type="object" class="btn-secondary"
icon="fa-whatsapp" context="{'default_user_id' : partner_id}"/>
</header>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="whatsapp_sale_view_form" model="ir.ui.view">
<field name="name">whatsapp.view.form.inherit.sale</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<header>
<button name="sale_whatsapp" string="Send Whatsapp" type="object" class="btn-secondary"
icon="fa-whatsapp" context="{'default_user_id' : partner_id}"
invisible="state not in ['sale', 'done']"/>
</header>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="whatsapp_stock_view_form" model="ir.ui.view">
<field name="name">whatsapp.view.form.inherit.stock</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<header>
<button name="stock_whatsapp" string="Send Whatsapp" type="object" class="btn-secondary"
icon="fa-whatsapp"/>
</header>
</field>
</record>
</data>
</odoo>

View File

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

View File

@ -0,0 +1,51 @@
from odoo import api, fields, models
import html2text
class WhatsappSendMessage(models.TransientModel):
_name = 'whatsapp.message.wizard'
_description = 'Whatsapp Message Wizard'
user_id = fields.Many2one('res.partner', string="Recipient")
mobile = fields.Char(related='user_id.mobile', required=True, readonly=False)
message = fields.Text(string="Message")
model = fields.Char('Related Document Model')
template_id = fields.Many2one(
'mail.template', 'Use template', index=True,
domain="[('model', '=', model)]"
)
@api.onchange('template_id')
def _onchange_template_id(self):
"""
Renders the selected template and populates the message field.
This is the correct method for Odoo 17/18.
"""
if self.template_id:
res_id = self._context.get('active_id')
# Render the template's body_html field for the active record
rendered_html = self.template_id._render_field('body_html', [res_id], compute_lang=True)[res_id]
# Convert the rendered HTML to plain text suitable for WhatsApp
self.message = html2text.html2text(rendered_html)
def send_message(self):
"""
Generates the WhatsApp Web URL and opens it in a new tab.
"""
self.ensure_one()
if self.message and self.mobile:
# Properly encode the message for a URL, handling spaces and new lines
message_string = self.message.replace(' ', '%20').replace('\n', '%0A')
# Sanitize the mobile number for the URL
number = self.mobile.replace('+', '').replace(' ', '')
link = f"https://web.whatsapp.com/send?phone={number}&text={message_string}"
return {
'type': 'ir.actions.act_url',
'url': link,
'target': 'new',
}
return True

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="whatsapp_message_wizard_form" model="ir.ui.view">
<field name="name">whatsapp.message.wizard.form</field>
<field name="model">whatsapp.message.wizard</field>
<field name="priority" eval="8"/>
<field name="arch" type="xml">
<form string="Whatsapp Message">
<group>
<group>
<field name="user_id"/>
<field name="mobile"/>
<field name="message"/>
</group>
<field name="model" invisible="1"/>
<group>
<field name="template_id"
options="{'no_create': True, 'no_edit': True}"
/>
</group>
</group>
<footer>
<button name="send_message" string="Send" type="object"/>
<button name="cancel" string="Cancel" special="cancel"/>
</footer>
</form>
</field>
</record>
</data>
</odoo>