upload whatsapp module edition

This commit is contained in:
eman 2024-08-21 03:24:59 +03:00
parent d59d4a4352
commit 3dfcbd00aa
22 changed files with 406 additions and 113 deletions

View File

@ -17,6 +17,7 @@
'security/ir.model.access.csv',
'wizard/whatsapp_preview_views.xml',
'wizard/whatsapp_composer_views.xml',
'wizard/reply_button_template.xml',
'views/discuss_channel_views.xml',
'views/whatsapp_account_views.xml',
'views/whatsapp_message_views.xml',

View File

@ -20,7 +20,6 @@ class Webhook(http.Controller):
@http.route('/whatsapp/webhook/', methods=['POST'], type="json", auth="public")
def webhookpost(self):
data = json.loads(request.httprequest.data)
print('ddddddddddddd', data)
for entry in data['entry']:
account_id = entry['id']
account = request.env['whatsapp.account'].sudo().search(
@ -65,14 +64,25 @@ class Webhook(http.Controller):
if changes['field'] == 'template_category_update':
template.write({'template_type': value['new_category'].lower()})
continue
if changes['field'] == 'messages':
print('messagesmessages')
template_id = self.env['whatsapp.template'].browse(4)
partner_id = self.env['res.partner'].browse(7577)
self.wa_create_composer(template_id, partner_id)
_logger.warning("Unknown Template webhook : %s ", value)
else:
_logger.warning("No Template found for this webhook : %s ", value)
elif changes['field'] == 'messages' and 'messages' in value:
for message in value['messages']:
if message['type'] == 'button':
partner_response = message['button']['payload']
send_message = request.env['whatsapp.message'].sudo().with_context(
active_test=False).search(
[('msg_uid', '=', message['context']['id'])])
message_response = request.env['whatsapp.message'].sudo().with_context(
active_test=False).search(
[('msg_uid', '=', message['id'])])
init_template = send_message.wa_template_id
reply_template_id = init_template.button_ids.filtered(lambda
b: b.button_type == 'quick_reply' and b.name == partner_response).reply_template_id
if reply_template_id:
partner_id = message_response.mail_message_id.author_id
self.wa_create_composer(reply_template_id, partner_id)
@http.route('/whatsapp/webhook/', methods=['GET'], type="http", auth="public", csrf=False)
def webhookget(self, **kwargs):
@ -115,24 +125,10 @@ class Webhook(http.Controller):
return consteq(signature[7:], expected)
def wa_create_composer(self, template_id, records):
values = {
'res_model': records._name,
'res_ids': records.ids,
'wa_template_id': template_id.id,
}
composer_id = self.env['whatsapp.composer'].create(values)
print('composer_idcomposer_id', composer_id)
composer_id.action_send_whatsapp_template()
ddddddddddddd = {'object': 'whatsapp_business_account', 'entry': [{'id': '407473312443001', 'changes': [{'value': {
'messaging_product': 'whatsapp',
'metadata': {'display_phone_number': '15556257124', 'phone_number_id': '329683520237319'},
'contacts': [{'profile': {'name': 'Ahmed Hannachi'}, 'wa_id': '21697527420'}], 'messages': [
{'context': {'from': '15556257124', 'id': 'wamid.HBgLMjE2OTc1Mjc0MjAVAgARGBJDNUQ0MEQyRDQ2N0M5RTNENEQA'},
'from': '21697527420',
'id': 'wamid.HBgLMjE2OTc1Mjc0MjAVAgASGCA4QTJERkI4REY2OTcwM0RCQUVCNjI0QjgyRTlENTg0MgA=',
'timestamp': '1723043846', 'type': 'button',
'button': {'payload': 'Rep 1 pour ce message', 'text': 'Rep 1 pour ce message'}}]},
'field': 'messages'}]}]}
'res_model': records._name,
'res_ids': records.ids,
'wa_template_id': template_id.id,
}
composer_id = request.env['whatsapp.composer'].sudo().create(values)
composer_id.sudo().action_send_whatsapp_template()

View File

@ -14,4 +14,5 @@ from . import whatsapp_message
from . import whatsapp_template
from . import whatsapp_template_button
from . import whatsapp_template_variable
from . import phone_blacklist

View File

@ -46,8 +46,12 @@ def json_dump(v):
return json.dumps(v, separators=(',', ':'), default=date_utils.json_default)
class ImBus(models.Model):
_inherit = 'bus.bus'
@api.model
def _sendone(self, channel, notification_type, message):
self._sendmany([[channel, notification_type, message]])
@api.model
def _sendmany(self, notifications):
channels = set()

View File

@ -190,7 +190,6 @@ class Channel(models.Model):
@api.depends('channel_member_ids')
def _compute_member_count(self):
read_group_res = self.env['discuss.channel.member'].read_group(domain=[('channel_id', 'in', self.ids)],fields=[], groupby=['channel_id'])
print('read_group_resread_group_res', read_group_res)
member_count_by_channel_id = {}
for item in read_group_res:
key = item['channel_id'][0]
@ -629,7 +628,6 @@ class Channel(models.Model):
# The current client code might be setting the key to True on sending
# message but it is only useful when targeting customers in chatter.
# This value should simply be set to False in channels no matter what.
print('kwargskwargs', kwargs)
return super(Channel, self.with_context(mail_create_nosubscribe=True, mail_post_autofollow=False)).message_post(message_type=message_type, **kwargs)
def _message_post_after_hook(self, message, msg_vals):
@ -703,7 +701,7 @@ class Channel(models.Model):
])
if not message_to_update:
return
message_to_update.flush_recordset(['pinned_at'])
message_to_update.flush(['pinned_at'])
# Use SQL because by calling write method, write_date is going to be updated, but we don't want pin/unpin
# a message changes the write_date
self.env.cr.execute("UPDATE mail_message SET pinned_at=%s WHERE id=%s",

View File

@ -81,8 +81,8 @@ class ChannelMember(models.Model):
@api.depends("channel_id.message_ids", "seen_message_id")
def _compute_message_unread(self):
if self.ids:
self.env['mail.message'].flush_model()
self.flush_recordset(['channel_id', 'seen_message_id'])
self.env['mail.message'].flush()
self.flush(['channel_id', 'seen_message_id'])
self.env.cr.execute("""
SELECT count(mail_message.id) AS count,
discuss_channel_member.id
@ -197,7 +197,6 @@ class ChannelMember(models.Model):
if member.partner_id:
# sudo: res.partner - reading _get_partner_data related to a member is considered acceptable
persona = member.sudo()._get_partner_data(fields=fields.get('persona', {}).get('partner'))
print('personapersona', persona)
persona['type'] = "partner"
if member.guest_id:
# sudo: mail.guest - reading _guest_format related to a member is considered acceptable
@ -214,7 +213,7 @@ class ChannelMember(models.Model):
def _get_partner_data(self, fields=None):
self.ensure_one()
return self.partner_id.mail_partner_format().get(self.partner_id)
return self.partner_id.mail_partner_format_with_field(fields=fields).get(self.partner_id)
# --------------------------------------------------------------------------
# RTC (voice/video)

View File

@ -71,7 +71,7 @@ class DiscussChannel(models.Model):
""" Return the last message of the whatsapp_partner_id given whatsapp channels."""
if not self:
return []
self.env['mail.message'].flush_model()
self.env['mail.message'].flush()
self.env.cr.execute("""
SELECT res_id AS id, MAX(mm.id) AS message_id
FROM mail_message AS mm
@ -205,7 +205,6 @@ class DiscussChannel(models.Model):
if related_message:
# Add message in channel about the related document
info = _("Related %(model_name)s: ", model_name=self.env['ir.model']._get(related_message.model).display_name)
print('selfffffff', self)
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
url = Markup('{base_url}/web#model={model}&id={res_id}').format(
base_url=base_url, model=related_message.model, res_id=related_message.res_id)

View File

@ -1,6 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo import models, tools
class MailThread(models.AbstractModel):
_inherit = 'mail.thread'
@ -10,6 +10,29 @@ class MailThread(models.AbstractModel):
res['canSendWhatsapp'] = self.env['whatsapp.template']._can_use_whatsapp(self._name)
return res
def _track_set_log_message(self, message):
""" Link tracking to a message logged as body, in addition to subtype
description (if set) and tracking values that make the core content of
tracking message. """
if not self._track_get_fields():
return
body_values = self.env.cr.precommit.data.setdefault(f'mail.tracking.message.{self._name}', {})
for id_ in self.ids:
body_values[id_] = message
@tools.ormcache('self.env.uid', 'self.env.su')
def _track_get_fields(self):
""" Return the set of tracked fields names for the current model. """
model_fields = {
name
for name, field in self._fields.items()
if getattr(field, 'tracking', None) or getattr(field, 'track_visibility', None)
}
return model_fields and set(self.fields_get(model_fields, attributes=()))
class PhoneMixin(models.AbstractModel):

View File

@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import exceptions, models, _
from odoo import exceptions, models, api, _
from odoo.addons.phone_validation.tools import phone_validation
class BaseModel(models.AbstractModel):
@ -100,3 +101,76 @@ class BaseModel(models.AbstractModel):
responsible_users = whatsapp_account.notify_user_ids
return responsible_users
def _phone_format(self, fname=False, number=False, country=False, force_format='E164', raise_exception=False):
""" Format and return number. This number can be found using a field
(in which case self should be a singleton recordet), or directly given
if the formatting itself is what matter. Field name can be found
automatically using '_phone_get_number_fields'
:param str fname: if number is not given, fname indicates the field to
use to find the number; otherwise use '_phone_get_number_fields';
:param str number: number to format (in which case fields-based computation
is skipped);
:param <res.country> country: country used for formatting number; otherwise
it is fetched based on record, using '_phone_get_country_field';
:param str force_format: stringified version of format globals; should be
one of 'E164', 'INTERNATIONAL', 'NATIONAL' or 'RFC3966';
:param bool raise_exception: raise if formatting is not possible (notably
wrong formatting, invalid country information, ...). Otherwise False
is returned;
:return str: formatted number. If formatting is not possible False is
returned.
"""
if not number:
# if no number is given, having a singletong recordset is mandatory to
# always have a number as input
self.ensure_one()
fnames = self._phone_get_number_fields() if not fname else [fname]
number = next((self[fname] for fname in fnames if fname in self and self[fname]), False)
if not number:
return False
# fetch country info only if self is a singleton recordset allowing to
# effectively try to find a country
if not country and self:
self.ensure_one()
country_fname = self._phone_get_country_field()
country = self[country_fname] if country_fname and country_fname in self else self.env['res.country']
if not country:
country = self.env.company.country_id
return self._phone_format_number(
number,
country=country, force_format=force_format,
raise_exception=raise_exception,
)
@api.model
def _phone_get_country_field(self):
if 'country_id' in self:
return 'country_id'
return False
def _phone_format_number(self, number, country, force_format='E164', raise_exception=False):
""" Format and return number according to the asked format. This is
mainly a small helper around 'phone_validation.phone_format'."""
if not number or not country:
return False
try:
number = phone_validation.phone_format(
number,
country.code,
country.phone_code,
force_format=force_format,
raise_exception=True, # do not get original number returned
)
except exceptions.UserError:
if raise_exception:
raise
number = False
return number

View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
from odoo import models
class PhoneBlackList(models.Model):
_inherit = 'phone.blacklist'
def add(self, number, message=None):
sanitized = self.env.user._phone_format(number=number)
return self._add([sanitized], message=message)
def _add(self, numbers, message=None):
""" Add or re activate a phone blacklist entry.
:param numbers: list of sanitized numbers """
records = self.env["phone.blacklist"].with_context(active_test=False).search([('number', 'in', numbers)])
todo = [n for n in numbers if n not in records.mapped('number')]
if records:
if message:
records._track_set_log_message(message)
records.action_unarchive()
if todo:
new_records = self.create([{'number': n} for n in todo])
if message:
for record in new_records:
record.with_context(mail_create_nosubscribe=True).message_post(
body=message,
subtype_xmlid='mail.mt_note',
)
records += new_records
return records
def remove(self, number, message=None):
sanitized = self.env.user._phone_format(number=number)
return self._remove([sanitized], message=message)
def _remove(self, numbers, message=None):
""" Add de-activated or de-activate a phone blacklist entry.
:param numbers: list of sanitized numbers """
records = self.env["phone.blacklist"].with_context(active_test=False).search([('number', 'in', numbers)])
todo = [n for n in numbers if n not in records.mapped('number')]
if records:
if message:
records._track_set_log_message(message)
records.action_archive()
if todo:
new_records = self.create([{'number': n, 'active': False} for n in todo])
if message:
for record in new_records:
record.with_context(mail_create_nosubscribe=True).message_post(
body=message,
subtype_xmlid='mail.mt_note',
)
records += new_records
return records

View File

@ -2,6 +2,7 @@
import logging
import re
import odoo
from odoo import exceptions, models, _
from odoo.addons.phone_validation.tools import phone_validation
@ -105,3 +106,38 @@ class ResPartner(models.Model):
self._cr.execute(query, (pattern, term) * len(phone_fields))
res = self._cr.fetchall()
return self.browse([r[0] for r in res])
def mail_partner_format_with_field(self, fields=None):
partners_format = dict()
if not fields:
fields = {'id': True, 'name': True, 'email': True, 'active': True, 'im_status': True, 'is_company': True, 'user': {}, "write_date": True}
for partner in self:
data = {}
if 'id' in fields:
data['id'] = partner.id
if 'name' in fields:
data['name'] = partner.name
if 'email' in fields:
data['email'] = partner.email
if 'active' in fields:
data['active'] = partner.active
if 'im_status' in fields:
data['im_status'] = partner.im_status
if 'is_company' in fields:
data['is_company'] = partner.is_company
if "write_date" in fields:
data["write_date"] = odoo.fields.Datetime.to_string(partner.write_date)
if 'user' in fields:
users = partner.with_context(active_test=False).user_ids
internal_users = users - users.filtered('share')
main_user = internal_users[0] if len(internal_users) > 0 else users[0] if len(users) > 0 else self.env['res.users']
data['user'] = {
"id": main_user.id,
"isInternalUser": not main_user.share,
} if main_user else False
if not self.env.user._is_internal():
data.pop('email', None)
data['type'] = "partner"
partners_format[partner] = data
return partners_format

View File

@ -115,7 +115,6 @@ class WhatsAppAccount(models.Model):
try:
wa_api._test_connection()
except WhatsAppError as e:
print('eeeeeee', e)
raise UserError(str(e))
return {
'type': 'ir.actions.client',

View File

@ -251,7 +251,7 @@ class WhatsAppMessage(models.Model):
for whatsapp_message in self:
wa_api = message_to_api[whatsapp_message]
whatsapp_message = whatsapp_message.with_user(whatsapp_message.create_uid)
whatsapp_message = whatsapp_message.with_user(self.env.user)
if whatsapp_message.state != 'outgoing':
_logger.info("Message state in %s state so it will not sent.", whatsapp_message.state)
continue

View File

@ -27,6 +27,7 @@ class WhatsAppTemplateButton(models.Model):
call_number = fields.Char(string="Call Number")
variable_ids = fields.One2many('whatsapp.template.variable', 'button_id',
compute='_compute_variable_ids', precompute=True, store=True)
reply_template_id = fields.Many2one(string='Reply Template', comodel_name='whatsapp.template')
_sql_constraints = [
(
@ -79,3 +80,16 @@ class WhatsAppTemplateButton(models.Model):
for button in self:
if button.button_type == 'phone_number':
phone_validation.phone_format(button.call_number, False, False)
def action_open_edit_reply_template(self):
return {
'type': 'ir.actions.act_window',
'target': 'new',
'name': _('Reply Template'),
'view_mode': 'form',
'res_model': 'reply.button.template',
'context': {'default_button_line_id': self.id},
}

View File

@ -13,3 +13,12 @@ access_whatsapp_template_button_administrator,access.whatsapp.template.button,mo
access_whatsapp_template_button_user,access.whatsapp.template.button,model_whatsapp_template_button,base.group_user,1,0,0,0
access_whatsapp_template_variable_administrator,access.whatsapp.template.variable,model_whatsapp_template_variable,group_whatsapp_admin,1,1,1,1
access_whatsapp_template_variable_user,access.whatsapp.template.variable,model_whatsapp_template_variable,base.group_user,1,0,0,0
access_discuss_channel_public,discuss.channel.public,model_discuss_channel,base.group_public,1,0,0,0
access_discuss_channel_portal,discuss.channel.portal,model_discuss_channel,base.group_portal,1,0,0,0
access_discuss_channel_user,discuss.channel.user,model_discuss_channel,base.group_user,1,1,1,0
access_discuss_channel_system,discuss.channel.system,model_discuss_channel,base.group_system,1,1,1,1
access_discuss_channel_member_public,discuss.channel.member.public,model_discuss_channel_member,base.group_public,1,1,1,1
access_discuss_channel_member_portal,discuss.channel.member.portal,model_discuss_channel_member,base.group_portal,1,1,1,1
access_discuss_channel_member_user,discuss.channel.member.user,model_discuss_channel_member,base.group_user,1,1,1,1
access_reply_button_template,access_reply_button_template,model_reply_button_template,base.group_user,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
13 access_whatsapp_template_button_user access.whatsapp.template.button model_whatsapp_template_button base.group_user 1 0 0 0
14 access_whatsapp_template_variable_administrator access.whatsapp.template.variable model_whatsapp_template_variable group_whatsapp_admin 1 1 1 1
15 access_whatsapp_template_variable_user access.whatsapp.template.variable model_whatsapp_template_variable base.group_user 1 0 0 0
16 access_discuss_channel_public discuss.channel.public model_discuss_channel base.group_public 1 0 0 0
17 access_discuss_channel_portal discuss.channel.portal model_discuss_channel base.group_portal 1 0 0 0
18 access_discuss_channel_user discuss.channel.user model_discuss_channel base.group_user 1 1 1 0
19 access_discuss_channel_system discuss.channel.system model_discuss_channel base.group_system 1 1 1 1
20 access_discuss_channel_member_public discuss.channel.member.public model_discuss_channel_member base.group_public 1 1 1 1
21 access_discuss_channel_member_portal discuss.channel.member.portal model_discuss_channel_member base.group_portal 1 1 1 1
22 access_discuss_channel_member_user discuss.channel.member.user model_discuss_channel_member base.group_user 1 1 1 1
23 access_reply_button_template access_reply_button_template model_reply_button_template base.group_user 1 1 1 1
24

View File

@ -683,10 +683,10 @@ class WhatsAppTemplateSync(WhatsAppTemplateCommon):
for field, update_values, expected_values, data in update_scenarios:
with self.subTest(field=field):
basic_template.write(update_values)
basic_template.flush_recordset()
basic_template.flush()
with self.mock_mail_app():
self._receive_template_update(field=field, account=self.whatsapp_account, data=data)
basic_template.flush_recordset()
basic_template.flush()
for fname, fvalue in expected_values.items():
self.assertEqual(basic_template[fname], fvalue)

View File

@ -225,7 +225,6 @@ class WhatsAppApi:
""" This method is used to test connection of WhatsApp Business Account"""
_logger.info("Test connection: Verify set phone uid is available in account %s [%s]", self.wa_account_id.name, self.wa_account_id.id)
response = self.__api_requests("GET", f"/{self.wa_account_id.account_uid}/phone_numbers", auth_type='bearer')
print('responseresponse', response)
data = response.json().get('data', [])
phone_values = [phone['id'] for phone in data if 'id' in phone]
if self.wa_account_id.phone_uid not in phone_values:

View File

@ -8,29 +8,29 @@
<header>
<field name="has_action" invisible="1"/>
<button name="button_reset_to_draft"
string="Reset to draft" type="object"
attrs="{'invisible': [('status', 'not in', ['rejected', 'approved'])]}"
groups="whatsapp.group_whatsapp_admin"/>
string="Reset to draft" type="object"
attrs="{'invisible': [('status', 'not in', ['rejected', 'approved'])]}"
groups="whatsapp.group_whatsapp_admin"/>
<button name="button_submit_template" string="Submit for Approval"
class="btn btn-primary" type="object"
attrs="{'invisible': ['|', ('wa_account_id', '=', False), ('status', '!=', 'draft')]}"
groups="whatsapp.group_whatsapp_admin"/>
class="btn btn-primary" type="object"
attrs="{'invisible': ['|', ('wa_account_id', '=', False), ('status', '!=', 'draft')]}"
groups="whatsapp.group_whatsapp_admin"/>
<button name="button_create_action" string="Allow Multi"
class="btn btn-primary" type="object"
attrs="{'invisible': ['|', ('has_action', '!=', False), ('status', '!=', 'approved')]}"
groups="whatsapp.group_whatsapp_admin"/>
class="btn btn-primary" type="object"
attrs="{'invisible': ['|', ('has_action', '!=', False), ('status', '!=', 'approved')]}"
groups="whatsapp.group_whatsapp_admin"/>
<button name="button_delete_action" string="Disallow Multi"
type="object"
groups="whatsapp.group_whatsapp_admin"
attrs="{'invisible': ['|', ('has_action', '=', False), ('status', '!=', 'approved')]}"
confirm="There might be other templates that still need the Multi"/>
type="object"
groups="whatsapp.group_whatsapp_admin"
attrs="{'invisible': ['|', ('has_action', '=', False), ('status', '!=', 'approved')]}"
confirm="There might be other templates that still need the Multi"/>
<button name="button_sync_template" string="Sync Template"
type="object"
attrs="{'invisible': [('wa_template_uid', '=', False)]}"
groups="whatsapp.group_whatsapp_admin"/>
type="object"
attrs="{'invisible': [('wa_template_uid', '=', False)]}"
groups="whatsapp.group_whatsapp_admin"/>
<button name="%(whatsapp_preview_action_from_template)d" string="Preview"
type="action" target="new"
context="{'active_id': id, 'dialog_size': 'medium'}"/>
type="action" target="new"
context="{'active_id': id, 'dialog_size': 'medium'}"/>
<field name="status" widget="statusbar" statusbar_visible="draft,pending,approved"/>
</header>
<sheet>
@ -39,63 +39,76 @@
<field name="messages_count" string="Messages" widget="statinfo"/>
</button>
</div>
<widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" attrs="{'invisible': [('active', '!=', False)]}"/>
<widget name="web_ribbon" title="Archived" bg_color="text-bg-danger"
attrs="{'invisible': [('active', '!=', False)]}"/>
<div class="oe_title">
<label for="name"/>
<h1><field name="name" required="1"/></h1>
<h1>
<field name="name" required="1"/>
</h1>
</div>
<group>
<group>
<field name="active" invisible="1"/>
<field name="wa_account_id"
groups="whatsapp.group_whatsapp_admin"
attrs="{'readonly': [('status', '!=', 'draft')]}"/>
groups="whatsapp.group_whatsapp_admin"
attrs="{'readonly': [('status', '!=', 'draft')]}"/>
<field name="model" invisible="1"/>
<field name="model_id"/>
<field name="phone_field"
options="{'model': 'model'}"/>
options="{'model': 'model'}"/>
<field name="template_type" attrs="{'readonly': [('status', '!=', 'draft')]}"/>
<field name="allowed_user_ids"
widget="many2many_tags_avatar"
placeholder="Users who can use this template.If empty then public."/>
widget="many2many_tags_avatar"
placeholder="Users who can use this template.If empty then public."/>
</group>
<group>
<field name="template_name"
attrs="{'readonly': ['|', ('wa_template_uid', '=', False), ('status', '!=', 'draft')]}"/>
attrs="{'readonly': ['|', ('wa_template_uid', '=', False), ('status', '!=', 'draft')]}"/>
<field name="lang_code"
attrs="{'readonly': [('status', '!=', 'draft')]}"/>
attrs="{'readonly': [('status', '!=', 'draft')]}"/>
<field name="header_type"
attrs="{'readonly': [('status', '!=', 'draft')]}" required="1"/>
<field name="header_text" attrs="{'invisible': [('header_type', '!=', 'text')], 'required': [('header_type', '=', 'text')], 'readonly': [('status', '!=', 'draft')]}"
placeholder="e.g. Invitation for {{1}}"/>
attrs="{'readonly': [('status', '!=', 'draft')]}" required="1"/>
<field name="header_text"
attrs="{'invisible': [('header_type', '!=', 'text')], 'required': [('header_type', '=', 'text')], 'readonly': [('status', '!=', 'draft')]}"
placeholder="e.g. Invitation for {{1}}"/>
<field name="header_attachment_ids"
widget="many2many_binary"
attrs="{'invisible': [('header_type', 'not in', ['image', 'video', 'document'])]}"/>
widget="many2many_binary"
attrs="{'invisible': [('header_type', 'not in', ['image', 'video', 'document'])]}"/>
<field name="report_id"
attrs="{'invisible': [('header_type', '!=', 'document')]}"/>
attrs="{'invisible': [('header_type', '!=', 'document')]}"/>
<field name="footer_text"
attrs="{'readonly': [('status', '!=', 'draft')]}"/>
attrs="{'readonly': [('status', '!=', 'draft')]}"/>
<field name="quality" widget="priority"
attrs="{'invisible': [('quality', '=', 'none')]}" readonly="1"/>
attrs="{'invisible': [('quality', '=', 'none')]}" readonly="1"/>
<field name="wa_template_uid" invisible="1"/>
</group>
</group>
<notebook>
<page id="body" name="Body" string="Body">
<field name="body"
placeholder="Your whatsapp message, use {{1}}, {{2}} as placeholders for dynamic variable"
attrs="{'readonly': [('status', '!=', 'draft')]}" required="1"/>
placeholder="Your whatsapp message, use {{1}}, {{2}} as placeholders for dynamic variable"
attrs="{'readonly': [('status', '!=', 'draft')]}" required="1"/>
</page>
<page id="buttons" name="Buttons" string="Buttons">
<field name="button_ids" attrs="{'readonly': [('status', '!=', 'draft')]}">
<field name="button_ids">
<tree editable="bottom">
<field name="sequence" widget="handle"/>
<field name="button_type"/>
<field name="name" required="1"/>
<field name="call_number" attrs="{'readonly': [('button_type', '!=', 'phone_number')], 'required': [('button_type', '=', 'phone_number')], 'invisible': [('button_type', '!=', 'phone_number')]}"/>
<field name="website_url" attrs="{'readonly': [('button_type', '!=', 'url')], 'required': [('button_type', '=', 'url')], 'invisible': [('button_type', '!=', 'url')]}"
placeholder="e.g. https://www.example.com"/>
<field name="url_type" attrs="{'required': [('button_type', '=', 'url')], 'invisible': [('button_type', '!=', 'url')]}"/>
<field name="reply_template_id" domain="[('status', '=', 'approved')]"
attrs="{'invisible': [('button_type', '!=', 'quick_reply')], 'required': [('button_type', '=', 'quick_reply')]}"/>
<field name="call_number"
attrs="{'readonly': [('button_type', '!=', 'phone_number')], 'required': [('button_type', '=', 'phone_number')], 'invisible': [('button_type', '!=', 'phone_number')]}"/>
<field name="website_url"
attrs="{'readonly': [('button_type', '!=', 'url')], 'required': [('button_type', '=', 'url')], 'invisible': [('button_type', '!=', 'url')]}"
placeholder="e.g. https://www.example.com"/>
<field name="url_type"
attrs="{'required': [('button_type', '=', 'url')], 'invisible': [('button_type', '!=', 'url')]}"/>
<button name="action_open_edit_reply_template"
string="Reply Template" type="object"
attrs="{'invisible': ['|', ('button_type', '!=', 'quick_reply'), ('parent.status', '!=', 'approved')]}"/>
</tree>
</field>
</page>
@ -106,8 +119,8 @@
<field name="demo_value" required="1"/>
<field name="field_type"/>
<field name="field_name"
attrs="{'required': [('field_type', '=', 'field')], 'readonly': [('field_type', '!=', 'field')], 'invisible': [('field_type', '!=', 'field')]}"
options="{'model': 'model'}"/>
attrs="{'required': [('field_type', '=', 'field')], 'readonly': [('field_type', '!=', 'field')], 'invisible': [('field_type', '!=', 'field')]}"
options="{'model': 'model'}"/>
<field name="model" invisible="1"/>
<field name="line_type" invisible="1" force_save="1"/>
<field name="name" invisible="1" force_save="1"/>
@ -176,7 +189,8 @@
<field name="quality" widget="priority" readonly="1"/>
<field name="create_date" widget="date"/>
<div>
<i class="fa fa-whatsapp me-1" title="Messages Count" aria-label="Messages Count"/>
<i class="fa fa-whatsapp me-1" title="Messages Count"
aria-label="Messages Count"/>
<field name="messages_count"/>
</div>
</div>
@ -193,24 +207,24 @@
</record>
<record id="whatsapp_template_view_search" model="ir.ui.view">
<field name="name">whatsapp.template.view.search</field>
<field name="model">whatsapp.template</field>
<field name="arch" type="xml">
<search string="Templates">
<field name="name"/>
<field name="model_id"/>
<field name="model" invisible="1"/>
<separator/>
<filter string="My Templates" name="my" domain="[('create_uid', '=', uid)]"/>
<separator/>
<filter string="Archived" name="filter_archived" domain="[('active', '=', False)]"/>
<group expand="0" string="Group By">
<filter string="Model" name="model_id" domain="[]" context="{'group_by': 'model_id'}"/>
<filter string="Status" name="status" domain="[]" context="{'group_by': 'status'}"/>
</group>
</search>
</field>
</record>
<field name="name">whatsapp.template.view.search</field>
<field name="model">whatsapp.template</field>
<field name="arch" type="xml">
<search string="Templates">
<field name="name"/>
<field name="model_id"/>
<field name="model" invisible="1"/>
<separator/>
<filter string="My Templates" name="my" domain="[('create_uid', '=', uid)]"/>
<separator/>
<filter string="Archived" name="filter_archived" domain="[('active', '=', False)]"/>
<group expand="0" string="Group By">
<filter string="Model" name="model_id" domain="[]" context="{'group_by': 'model_id'}"/>
<filter string="Status" name="status" domain="[]" context="{'group_by': 'status'}"/>
</group>
</search>
</field>
</record>
<record id="whatsapp_template_action" model="ir.actions.act_window">
<field name="name">WhatsApp Template</field>
@ -222,9 +236,14 @@
Create or sync WhatsApp Templates.
</p>
<p>
You can retrieve templates from Facebook by clicking <strong>Sync Templates</strong> on the <a name="%(whatsapp.whatsapp_account_action)d" type="action" class="oe_link">WhatsApp Business Account</a>.
You can retrieve templates from Facebook by clicking <strong>Sync Templates</strong> on the <a
name="%(whatsapp.whatsapp_account_action)d" type="action" class="oe_link">WhatsApp Business
Account</a>.
Or create templates here and send them for approval.
Please refer the <a href="https://developers.facebook.com/docs/whatsapp/message-templates/guidelines" target="_blank">Template Guidelines</a>
Please refer the
<a href="https://developers.facebook.com/docs/whatsapp/message-templates/guidelines" target="_blank">
Template Guidelines
</a>
</p>
</field>
</record>

View File

@ -2,3 +2,4 @@
from . import whatsapp_composer
from . import whatsapp_preview
from . import reply_button_template

View File

@ -0,0 +1,18 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class WhatsAppComposer(models.TransientModel):
_name = 'reply.button.template'
_description = 'Reply Button Template Wizard'
button_line_id = fields.Many2one('whatsapp.template.button', string='Button Line ID')
template_id = fields.Many2one(string='Reply Template', comodel_name='whatsapp.template', required=True)
def change_template(self):
self.button_line_id.write({'reply_template_id': self.template_id.id})

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="reply_button_template_view_wizard" model="ir.ui.view">
<field name="name">reply.button.templat.wizard</field>
<field name="model">reply.button.template</field>
<field name="arch" type="xml">
<form string="Send WhatsApp">
<sheet>
<group>
<field name="template_id"
options="{'no_create_edit': True, 'no_create': True, 'no_open': True}"
domain="[('status', '=', 'approved')]"
/>
<!-- domain="[('model', '=', res_model), ('status', '=', 'approved')]"/>-->
</group>
</sheet>
<footer>
<button string="Edit" type="object" class="oe_highlight"
name="change_template"/>
<button name="cancel" special="cancel" data-hotkey="z" string="Close" class="btn-secondary"/>
</footer>
</form>
</field>
</record>
<!-- <record id="action_reply_button_template" model="ir.actions.act_window">-->
<!-- <field name="name">Reply Template</field>-->
<!-- <field name="res_model">reply.button.template</field>-->
<!-- <field name="binding_model_id" eval="False"/>-->
<!-- <field name="type">ir.actions.act_window</field>-->
<!-- <field name="context">{default_button_line_id': active_id}</field>-->
<!-- <field name="view_id" ref="whatsapp.reply_button_template_view_wizard"/>-->
<!-- <field name="target">new</field>-->
<!-- </record>-->
<!-- <record id="action_reply_button_template" model="ir.actions.act_window">-->
<!-- <field name="name">Reply Template</field>-->
<!-- <field name="res_model">whatsapp.preview</field>-->
<!-- <field name="binding_model_id" eval="False"/>-->
<!-- <field name="view_mode">form</field>-->
<!-- <field name="view_id" ref="whatsapp_preview_view_form"/>-->
<!-- <field name="target">new</field>-->
<!-- <field name="context">{'default_wa_template_id': active_id}</field>-->
<!-- </record>-->
</odoo>

View File

@ -5,7 +5,7 @@ import logging
from ast import literal_eval
from odoo import api, fields, models, _
from odoo import api, fields, models, _, SUPERUSER_ID
from odoo.exceptions import ValidationError, RedirectWarning, UserError
from ..tools import phone_validation as wa_phone_validation
@ -223,7 +223,6 @@ class WhatsAppComposer(models.TransientModel):
def _send_whatsapp_template(self, force_send_by_cron=False):
records = self._get_active_records()
print('_send_whatsapp_template_send_whatsapp_template_send_whatsapp_template', records)
if self.wa_template_id and self.wa_template_id.variable_ids:
field_types = self.wa_template_id.variable_ids.mapped('field_type')
@ -272,11 +271,9 @@ class WhatsAppComposer(models.TransientModel):
'wa_template_id': self.wa_template_id.id,
'wa_account_id': self.wa_template_id.wa_account_id.id,
})
print('message_valsmessage_vals', message_vals)
if message_vals:
message = self.env['whatsapp.message'].create(message_vals)
print("messagemessage", message)
message._send(force_send_by_cron=force_send_by_cron)
message.with_user(SUPERUSER_ID)._send(force_send_by_cron=force_send_by_cron)
def _get_text_free_json(self):
"""This method is used to prepare free text json using values set in free text field of composer."""