upload whatsapp module edition
This commit is contained in:
parent
d59d4a4352
commit
3dfcbd00aa
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
|
||||
from . import whatsapp_composer
|
||||
from . import whatsapp_preview
|
||||
from . import reply_button_template
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
@ -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>
|
||||
|
|
@ -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."""
|
||||
|
|
|
|||
Loading…
Reference in New Issue