19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:39 +01:00
parent 5df8c07b59
commit daa394e8b0
2114 changed files with 564841 additions and 299642 deletions

View file

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
from . import sms_account_code
from . import sms_account_phone
from . import sms_account_sender
from . import sms_composer
from . import sms_resend
from . import sms_template_preview
from . import sms_template_reset

View file

@ -0,0 +1,36 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, _
from odoo.addons.sms.tools.sms_api import ERROR_MESSAGES, SmsApi
from odoo.exceptions import ValidationError
class SmsAccountCode(models.TransientModel):
_name = 'sms.account.code'
_description = 'SMS Account Verification Code Wizard'
account_id = fields.Many2one('iap.account', required=True)
verification_code = fields.Char(required=True)
def action_register(self):
status = SmsApi(self.env, self.account_id)._verify_account(self.verification_code)['state']
if status != 'success':
raise ValidationError(ERROR_MESSAGES.get(status, ERROR_MESSAGES['unknown_error']))
self.account_id.state = "registered"
self.env['iap.account']._send_success_notification(
message=_("Your SMS account has been successfully registered."),
)
sender_name_wizard = self.env['sms.account.sender'].create({
'account_id': self.account_id.id,
})
return {
'type': 'ir.actions.act_window',
'target': 'new',
'name': _('Choose your sender name'),
'view_mode': 'form',
'res_model': 'sms.account.sender',
'res_id': sender_name_wizard.id,
}

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="sms_account_code_view_form" model="ir.ui.view">
<field name="name">sms.account.code.view.form</field>
<field name="model">sms.account.code</field>
<field name="arch" type="xml">
<form string="Register your SMS account">
<sheet>
<group>
<field name="verification_code" required="1"/>
</group>
<footer>
<button string="Register" name="action_register" type="object" class="btn btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,27 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, _
from odoo.addons.sms.tools.sms_api import ERROR_MESSAGES, SmsApi
from odoo.exceptions import ValidationError
class SmsAccountPhone(models.TransientModel):
_name = 'sms.account.phone'
_description = 'SMS Account Registration Phone Number Wizard'
account_id = fields.Many2one('iap.account', required=True)
phone_number = fields.Char(required=True)
def action_send_verification_code(self):
status = SmsApi(self.env, self.account_id)._send_verification_sms(self.phone_number)['state']
if status != 'success':
raise ValidationError(ERROR_MESSAGES.get(status, ERROR_MESSAGES['unknown_error']))
return {
'type': 'ir.actions.act_window',
'target': 'new',
'name': _('Register Account'),
'view_mode': 'form',
'res_model': 'sms.account.code',
'context': {'default_account_id': self.account_id.id},
}

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="sms_account_phone_view_form" model="ir.ui.view">
<field name="name">sms.account.phone.view.form</field>
<field name="model">sms.account.phone</field>
<field name="arch" type="xml">
<form string="Register your SMS account">
<sheet>
<h5>Enter a phone number to get an SMS with a verification code.</h5>
<group>
<field name="phone_number" placeholder="+1 555-555-555"/>
</group>
<footer>
<button string="Send verification code" name="action_send_verification_code" type="object" class="btn btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>

View file

@ -0,0 +1,25 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from odoo import api, fields, models
from odoo.addons.sms.tools.sms_api import ERROR_MESSAGES, SmsApi
from odoo.exceptions import ValidationError
class SmsAccountSender(models.TransientModel):
_name = 'sms.account.sender'
_description = 'SMS Account Sender Name Wizard'
account_id = fields.Many2one('iap.account', required=True)
sender_name = fields.Char()
@api.constrains("sender_name")
def _check_sender_name(self):
for record in self:
if not re.match(r"[a-zA-Z0-9\- ]{3,11}", record.sender_name):
raise ValidationError(self.env._("Your sender name must be between 3 and 11 characters long and only contain alphanumeric characters."))
def action_set_sender_name(self):
status = SmsApi(self.env, self.account_id)._set_sender_name(self.sender_name)['state']
if status != 'success':
raise ValidationError(ERROR_MESSAGES.get(status, ERROR_MESSAGES['unknown_error']))

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="sms_account_sender_view_form" model="ir.ui.view">
<field name="name">sms.account.sender.view.form</field>
<field name="model">sms.account.sender</field>
<field name="arch" type="xml">
<form string="Choose your sender name">
<sheet>
<p>
Your sender name must be between 3 and 11 characters long and only contain alphanumeric characters.
It must fit your company name, and you aren't allowed to modify it once you registered one, choose it carefully.
</p>
<p>
Note that this is not required, if you don't set a sender name, your SMS will be sent from a short code.
</p>
<group>
<field name="sender_name" required="1"/>
</group>
<footer>
<button string="Set sender name" name="action_set_sender_name" type="object" class="btn btn-primary"/>
<button string="Skip for now" class="btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>

View file

@ -2,20 +2,20 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from ast import literal_eval
from uuid import uuid4
from odoo import api, fields, models, _
from odoo.addons.phone_validation.tools import phone_validation
from odoo.addons.sms.tools.sms_tools import sms_content_to_rendered_html
from odoo.exceptions import UserError
class SendSMS(models.TransientModel):
class SmsComposer(models.TransientModel):
_name = 'sms.composer'
_description = 'Send SMS Wizard'
@api.model
def default_get(self, fields):
result = super(SendSMS, self).default_get(fields)
result = super().default_get(fields)
result['res_model'] = result.get('res_model') or self.env.context.get('active_model')
@ -47,14 +47,16 @@ class SendSMS(models.TransientModel):
# options for comment and mass mode
mass_keep_log = fields.Boolean('Keep a note on document', default=True)
mass_force_send = fields.Boolean('Send directly', default=False)
mass_use_blacklist = fields.Boolean('Use blacklist', default=True)
use_exclusion_list = fields.Boolean(
'Use Exclusion List', default=True, copy=False,
help='Prevent sending messages to blacklisted contacts. Disable only when absolutely necessary.')
# recipients
recipient_valid_count = fields.Integer('# Valid recipients', compute='_compute_recipients', compute_sudo=False)
recipient_invalid_count = fields.Integer('# Invalid recipients', compute='_compute_recipients', compute_sudo=False)
recipient_single_description = fields.Text('Recipients (Partners)', compute='_compute_recipient_single', compute_sudo=False)
recipient_single_number = fields.Char('Stored Recipient Number', compute='_compute_recipient_single', compute_sudo=False)
recipient_single_description = fields.Text('Recipients (Partners)', compute='_compute_recipient_single_non_stored', compute_sudo=False)
recipient_single_number = fields.Char('Stored Recipient Number', compute='_compute_recipient_single_non_stored', compute_sudo=False)
recipient_single_number_itf = fields.Char(
'Recipient Number', compute='_compute_recipient_single',
'Recipient Number', compute='_compute_recipient_single_stored',
readonly=False, compute_sudo=False, store=True,
help='Phone number of the recipient. If changed, it will be recorded on recipient\'s profile.')
recipient_single_valid = fields.Boolean("Is valid", compute='_compute_recipient_single_valid', compute_sudo=False)
@ -113,22 +115,32 @@ class SendSMS(models.TransientModel):
) else 1
@api.depends('res_model', 'number_field_name')
def _compute_recipient_single(self):
def _compute_recipient_single_stored(self):
for composer in self:
records = composer._get_records()
if not records or not composer.comment_single_recipient:
composer.recipient_single_number_itf = ''
continue
records.ensure_one()
# If the composer was opened with a specific field use that, otherwise get the partner's
res = records._sms_get_recipients_info(force_field=composer.number_field_name, partner_fallback=not composer.number_field_name)
if not composer.recipient_single_number_itf:
composer.recipient_single_number_itf = res[records.id]['sanitized'] or res[records.id]['number'] or ''
if not composer.number_field_name:
composer.number_field_name = res[records.id]['field_store']
@api.depends('res_model', 'number_field_name')
def _compute_recipient_single_non_stored(self):
for composer in self:
records = composer._get_records()
if not records or not composer.comment_single_recipient:
composer.recipient_single_description = False
composer.recipient_single_number = ''
composer.recipient_single_number_itf = ''
continue
records.ensure_one()
res = records._sms_get_recipients_info(force_field=composer.number_field_name, partner_fallback=True)
composer.recipient_single_description = res[records.id]['partner'].name or records._sms_get_default_partners().display_name
composer.recipient_single_description = res[records.id]['partner'].name or records._mail_get_partners()[records[0].id].display_name
composer.recipient_single_number = res[records.id]['sanitized'] or res[records.id]['number'] or ''
if not composer.recipient_single_number_itf:
composer.recipient_single_number_itf = res[records.id]['sanitized'] or res[records.id]['number'] or ''
if not composer.number_field_name:
composer.number_field_name = res[records.id]['field_store']
@api.depends('recipient_single_number', 'recipient_single_number_itf')
def _compute_recipient_single_valid(self):
@ -136,8 +148,7 @@ class SendSMS(models.TransientModel):
value = composer.recipient_single_number_itf or composer.recipient_single_number
if value:
records = composer._get_records()
sanitized = phone_validation.phone_sanitize_numbers_w_record([value], records)[value]['sanitized']
composer.recipient_single_valid = bool(sanitized)
composer.recipient_single_valid = bool(records._phone_format(number=value)) if len(records) == 1 else False
else:
composer.recipient_single_valid = False
@ -147,9 +158,8 @@ class SendSMS(models.TransientModel):
if composer.numbers:
record = composer._get_records() if composer.res_model and composer.res_id else self.env.user
numbers = [number.strip() for number in composer.numbers.split(',')]
sanitize_res = phone_validation.phone_sanitize_numbers_w_record(numbers, record)
sanitized_numbers = [info['sanitized'] for info in sanitize_res.values() if info['sanitized']]
invalid_numbers = [number for number, info in sanitize_res.items() if info['code']]
sanitized_numbers = [record._phone_format(number=number) for number in numbers]
invalid_numbers = [number for sanitized, number in zip(sanitized_numbers, numbers) if not sanitized]
if invalid_numbers:
raise UserError(_('Following numbers are not correctly encoded: %s', repr(invalid_numbers)))
composer.sanitized_numbers = ','.join(sanitized_numbers)
@ -160,7 +170,8 @@ class SendSMS(models.TransientModel):
def _compute_body(self):
for record in self:
if record.template_id and record.composition_mode == 'comment' and record.res_id:
record.body = record.template_id._render_field('body', [record.res_id], compute_lang=True)[record.res_id]
additional_context = record._get_additional_render_context().get('body', {})
record.body = record.template_id._render_field('body', [record.res_id], compute_lang=True, add_context=additional_context)[record.res_id]
elif record.template_id:
record.body = record.template_id.body
@ -197,13 +208,17 @@ class SendSMS(models.TransientModel):
return self._action_send_sms_mass(records)
def _action_send_sms_numbers(self):
numbers = self.sanitized_numbers.split(',') if self.sanitized_numbers else [self.recipient_single_number_itf or self.recipient_single_number or '']
self.env['sms.api']._send_sms_batch([{
'res_id': 0,
'number': number,
'content': self.body,
} for number in numbers])
return True
sms_values = [
{
'body': self.body,
'number': number
} for number in (
self.sanitized_numbers.split(',') if self.sanitized_numbers else [self.recipient_single_number_itf or self.recipient_single_number or '']
)
]
sms_su = self.env['sms.sms'].sudo().create(sms_values)
sms_su.send()
return sms_su
def _action_send_sms_comment_single(self, records=None):
# If we have a recipient_single_original number, it's possible this number has been corrected in the popup
@ -235,15 +250,17 @@ class SendSMS(models.TransientModel):
def _action_send_sms_mass(self, records=None):
records = records if records is not None else self._get_records()
sms_record_values = self._prepare_mass_sms_values(records)
sms_all = self._prepare_mass_sms(records, sms_record_values)
if sms_all and self.mass_keep_log and records and isinstance(records, self.pool['mail.thread']):
log_values = self._prepare_mass_log_values(records, sms_record_values)
records._message_log_batch(**log_values)
sms_record_values_filtered = self._filter_out_and_handle_revoked_sms_values(self._prepare_mass_sms_values(records))
records_filtered = records.filtered(lambda record: record.id in sms_record_values_filtered)
if self.mass_keep_log and sms_record_values_filtered and isinstance(records_filtered, self.pool['mail.thread']):
log_values = self._prepare_mass_log_values(records_filtered, sms_record_values_filtered)
mail_messages = records_filtered._message_log_batch(**log_values)
for idx, record in enumerate(records_filtered):
sms_record_values_filtered[record.id]['mail_message_id'] = mail_messages[idx].id
sms_all = self._prepare_mass_sms(records_filtered, sms_record_values_filtered)
if sms_all and self.mass_force_send:
sms_all.filtered(lambda sms: sms.state == 'outgoing').send(auto_commit=False, raise_exception=False)
sms_all.filtered(lambda sms: sms.state == 'outgoing').send(raise_exception=False)
return self.env['sms.sms'].sudo().search([('id', 'in', sms_all.ids)])
return sms_all
@ -251,10 +268,19 @@ class SendSMS(models.TransientModel):
# Mass mode specific
# ------------------------------------------------------------
def _filter_out_and_handle_revoked_sms_values(self, sms_values_all):
"""Meant to be overridden to filter out and handle sms that must not be sent.
:param dict sms_values_all: sms values by res_id
:returns: filtered sms_vals_all
:rtype: dict
"""
return sms_values_all
def _get_blacklist_record_ids(self, records, recipients_info):
""" Get a list of blacklisted records. Those will be directly canceled
with the right error code. """
if self.mass_use_blacklist:
if self.use_exclusion_list:
bl_numbers = self.env['phone.blacklist'].sudo().search([]).mapped('number')
return [r.id for r in records if recipients_info[r.id]['sanitized'] in bl_numbers]
return []
@ -281,10 +307,11 @@ class SendSMS(models.TransientModel):
return recipients_info
def _prepare_body_values(self, records):
additional_context = self._get_additional_render_context().get('body', {})
if self.template_id and self.body == self.template_id.body:
all_bodies = self.template_id._render_field('body', records.ids, compute_lang=True)
all_bodies = self.template_id._render_field('body', records.ids, compute_lang=True, add_context=additional_context)
else:
all_bodies = self.env['mail.render.mixin']._render_template(self.body, records._name, records.ids)
all_bodies = self.env['mail.render.mixin']._render_template(self.body, records._name, records.ids, add_context=additional_context)
return all_bodies
def _prepare_mass_sms_values(self, records):
@ -316,10 +343,11 @@ class SendSMS(models.TransientModel):
result[record.id] = {
'body': all_bodies[record.id],
'partner_id': recipients['partner'].id,
'number': sanitized if sanitized else recipients['number'],
'state': state,
'failure_type': failure_type,
'number': sanitized if sanitized else recipients['number'],
'partner_id': recipients['partner'].id,
'state': state,
'uuid': uuid4().hex,
}
return result
@ -339,6 +367,18 @@ class SendSMS(models.TransientModel):
'message_type': 'sms',
}
# ------------------------------------------------------------
# Render
# ------------------------------------------------------------
def _get_additional_render_context(self):
"""
Return a dict associating fields with their relevant render context if any.
e.g. {'body': {'additional_value': self.env.context.get('additional_value')}}
"""
return {}
# ------------------------------------------------------------
# Tools
# ------------------------------------------------------------
@ -348,7 +388,8 @@ class SendSMS(models.TransientModel):
if composition_mode == 'comment':
if not body and template_id and res_id:
template = self.env['sms.template'].browse(template_id)
result['body'] = template._render_template(template.body, res_model, [res_id])[res_id]
additional_context = self._get_additional_render_context().get('body', {})
result['body'] = template._render_template(template.body, res_model, [res_id], add_context=additional_context)[res_id]
elif template_id:
template = self.env['sms.template'].browse(template_id)
result['body'] = template.body

View file

@ -6,8 +6,8 @@
<field name="arch" type="xml">
<form string="Send an SMS">
<!-- Single mode information (invalid number) -->
<div colspan="2" class="alert alert-danger text-center mb-0" role="alert"
attrs="{'invisible': ['|', '|', ('res_model_description', '=', False), ('comment_single_recipient', '=', False), ('recipient_single_valid', '=', True)]}">
<div class="alert alert-danger text-center" role="alert"
invisible="not res_model_description or not comment_single_recipient or recipient_single_valid">
<p class="my-0">
<strong>Invalid number:</strong>
<span> make sure to set a country on the </span>
@ -17,8 +17,8 @@
</div>
<!-- Mass mode information (res_ids versus active domain) -->
<div colspan="2" class="alert alert-info text-center mb-0" role="alert"
attrs="{'invisible': ['|', ('comment_single_recipient', '=', True), ('recipient_invalid_count', '=', 0)]}">
<div class="alert alert-info text-center" role="alert"
invisible="comment_single_recipient or recipient_invalid_count == 0">
<p class="my-0">
<field class="oe_inline fw-bold" name="recipient_invalid_count"/> out of
<field class="oe_inline fw-bold" name="res_ids_count"/> recipients have an invalid phone number and will not receive this text message.
@ -37,37 +37,45 @@
<field name="number_field_name" invisible="1"/>
<field name="numbers" invisible="1"/>
<field name="sanitized_numbers" invisible="1"/>
<field name="template_id" invisible="1"/>
<label for="recipient_single_description" string="Recipient"
<label for="recipient_single_description" string="To"
class="fw-bold"
attrs="{'invisible': [('comment_single_recipient', '=', False)]}"/>
<div attrs="{'invisible': [('comment_single_recipient', '=', False)]}">
<field name="recipient_single_description" class="oe_inline" attrs="{'invisible': [('recipient_single_description', '=', False)]}"/>
<field name="recipient_single_number_itf" class="oe_inline" nolabel="1" onchange_on_keydown="True" placeholder="e.g. +1 415 555 0100"/>
invisible="not comment_single_recipient"/>
<div invisible="not comment_single_recipient">
<field name="recipient_single_description" class="w-auto text-muted me-2" invisible="not recipient_single_description"/>
<field name="recipient_single_number_itf" class="oe_inline border-bottom" nolabel="1" onchange_on_keydown="True" placeholder="e.g. +1 415 555 0100"/>
</div>
<field name="body" widget="sms_widget" attrs="{'invisible': ['|', ('comment_single_recipient', '=', False), ('recipient_single_valid', '=', True)]}"/>
<field name="body" widget="sms_widget" attrs="{'invisible': [('comment_single_recipient', '=', True), ('recipient_single_valid', '=', False)]}" default_focus="1"/>
<field name="mass_keep_log" invisible="1"/>
</group>
<field name="body" widget="sms_widget" invisible="comment_single_recipient or recipient_single_valid"
options="{'dynamic_placeholder': true, 'dynamic_placeholder_model_reference_field': 'res_model'}"
placeholder="Write a message..."/>
<field name="body" widget="sms_widget" invisible="comment_single_recipient or not recipient_single_valid" default_focus="1"
options="{'dynamic_placeholder': true, 'dynamic_placeholder_model_reference_field': 'res_model'}"
placeholder="Write a message..."/>
<field name="body" widget="sms_widget" invisible="not comment_single_recipient"
placeholder="Write a message..."/>
<field name="mass_keep_log" invisible="1"/>
<field name="use_exclusion_list" invisible="1"/>
</sheet>
<footer>
<!-- attrs doesn't work for 'disabled'-->
<button string="Send SMS" type="object" class="oe_highlight" name="action_send_sms" data-hotkey="q"
attrs="{'invisible': ['|',('composition_mode', 'not in', ('comment', 'numbers')),('recipient_single_valid', '=', False)]}"/>
<button string="Send SMS" type="object" class="oe_highlight" name="action_send_sms" data-hotkey="q"
attrs="{'invisible': ['|',('composition_mode', 'not in', ('comment', 'numbers')),('recipient_single_valid', '=', True)]}" disabled='1'/>
<button string="Send" type="object" class="oe_highlight" name="action_send_sms" data-hotkey="q"
invisible="composition_mode not in ('comment', 'numbers') or not recipient_single_valid"/>
<button string="Send" type="object" class="oe_highlight" name="action_send_sms" data-hotkey="q"
invisible="composition_mode not in ('comment', 'numbers') or recipient_single_valid" disabled='1'/>
<button string="Put in queue" type="object" class="oe_highlight" name="action_send_sms" data-hotkey="q"
attrs="{'invisible': [('composition_mode', '!=', 'mass')]}"/>
<button string="Send Now" type="object" name="action_send_sms_mass_now" data-hotkey="w"
attrs="{'invisible': [('composition_mode', '!=', 'mass')]}"/>
<button string="Close" class="btn btn-secondary" special="cancel" data-hotkey="z"/>
invisible="composition_mode != 'mass'"/>
<button string="Send now" type="object" name="action_send_sms_mass_now" data-hotkey="w"
invisible="composition_mode != 'mass'"/>
<button string="Discard" class="btn btn-secondary" special="cancel" data-hotkey="x"/>
</footer>
</form>
</field>
</record>
<record id="sms_composer_action_form" model="ir.actions.act_window">
<field name="name">Send SMS Text Message</field>
<field name="name">Send SMS</field>
<field name="res_model">sms.composer</field>
<field name="view_mode">form</field>
<field name="target">new</field>

View file

@ -1,121 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, exceptions, fields, models
class SMSRecipient(models.TransientModel):
_name = 'sms.resend.recipient'
_description = 'Resend Notification'
_rec_name = 'sms_resend_id'
sms_resend_id = fields.Many2one('sms.resend', required=True)
notification_id = fields.Many2one('mail.notification', required=True, ondelete='cascade')
resend = fields.Boolean(string='Try Again', default=True)
failure_type = fields.Selection(
related='notification_id.failure_type', string='Error Message', related_sudo=True, readonly=True)
partner_id = fields.Many2one('res.partner', 'Partner', related='notification_id.res_partner_id', readonly=True)
partner_name = fields.Char(string='Recipient Name', readonly='True')
sms_number = fields.Char(string='Phone Number')
class SMSResend(models.TransientModel):
_name = 'sms.resend'
_description = 'SMS Resend'
_rec_name = 'mail_message_id'
@api.model
def default_get(self, fields):
result = super(SMSResend, self).default_get(fields)
if 'recipient_ids' in fields and result.get('mail_message_id'):
mail_message_id = self.env['mail.message'].browse(result['mail_message_id'])
result['recipient_ids'] = [(0, 0, {
'notification_id': notif.id,
'resend': True,
'failure_type': notif.failure_type,
'partner_name': notif.res_partner_id.display_name or mail_message_id.record_name,
'sms_number': notif.sms_number,
}) for notif in mail_message_id.notification_ids if notif.notification_type == 'sms' and notif.notification_status in ('exception', 'bounce')]
return result
mail_message_id = fields.Many2one('mail.message', 'Message', readonly=True, required=True)
recipient_ids = fields.One2many('sms.resend.recipient', 'sms_resend_id', string='Recipients')
can_cancel = fields.Boolean(compute='_compute_can_cancel')
can_resend = fields.Boolean(compute='_compute_can_resend')
has_insufficient_credit = fields.Boolean(compute='_compute_has_insufficient_credit')
has_unregistered_account = fields.Boolean(compute='_compute_has_unregistered_account')
@api.depends("recipient_ids.failure_type")
def _compute_has_unregistered_account(self):
self.has_unregistered_account = self.recipient_ids.filtered(lambda p: p.failure_type == 'sms_acc')
@api.depends("recipient_ids.failure_type")
def _compute_has_insufficient_credit(self):
self.has_insufficient_credit = self.recipient_ids.filtered(lambda p: p.failure_type == 'sms_credit')
@api.depends("recipient_ids.resend")
def _compute_can_cancel(self):
self.can_cancel = self.recipient_ids.filtered(lambda p: not p.resend)
@api.depends('recipient_ids.resend')
def _compute_can_resend(self):
self.can_resend = any([recipient.resend for recipient in self.recipient_ids])
def _check_access(self):
if not self.mail_message_id or not self.mail_message_id.model or not self.mail_message_id.res_id:
raise exceptions.UserError(_('You do not have access to the message and/or related document.'))
record = self.env[self.mail_message_id.model].browse(self.mail_message_id.res_id)
record.check_access_rights('read')
record.check_access_rule('read')
def action_resend(self):
self._check_access()
all_notifications = self.env['mail.notification'].sudo().search([
('mail_message_id', '=', self.mail_message_id.id),
('notification_type', '=', 'sms'),
('notification_status', 'in', ('exception', 'bounce'))
])
sudo_self = self.sudo()
to_cancel_ids = [r.notification_id.id for r in sudo_self.recipient_ids if not r.resend]
to_resend_ids = [r.notification_id.id for r in sudo_self.recipient_ids if r.resend]
if to_cancel_ids:
all_notifications.filtered(lambda n: n.id in to_cancel_ids).write({'notification_status': 'canceled'})
if to_resend_ids:
record = self.env[self.mail_message_id.model].browse(self.mail_message_id.res_id)
sms_pid_to_number = dict((r.partner_id.id, r.sms_number) for r in self.recipient_ids if r.resend and r.partner_id)
pids = list(sms_pid_to_number.keys())
numbers = [r.sms_number for r in self.recipient_ids if r.resend and not r.partner_id]
recipients_data = []
all_recipients_data = self.env['mail.followers']._get_recipient_data(record, 'sms', False, pids=pids)[record.id]
for pid, pdata in all_recipients_data.items():
if pid and pdata['notif'] == 'sms':
recipients_data.append(pdata)
if recipients_data or numbers:
record._notify_thread_by_sms(
self.mail_message_id, recipients_data,
sms_numbers=numbers, sms_pid_to_number=sms_pid_to_number,
resend_existing=True, put_in_queue=False
)
self.mail_message_id._notify_message_notification_update()
return {'type': 'ir.actions.act_window_close'}
def action_cancel(self):
self._check_access()
sudo_self = self.sudo()
sudo_self.mapped('recipient_ids.notification_id').write({'notification_status': 'canceled'})
self.mail_message_id._notify_message_notification_update()
return {'type': 'ir.actions.act_window_close'}
def action_buy_credits(self):
url = self.env['iap.account'].get_credits_url(service_name='sms')
return {
'type': 'ir.actions.act_url',
'url': url,
}

View file

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data>
<record id="mail_resend_message_view_form" model="ir.ui.view">
<field name="name">sms.resend.form</field>
<field name="model">sms.resend</field>
<field name="groups_id" eval="[(4,ref('base.group_user'))]"/>
<field name="arch" type="xml">
<form string="Edit Partners">
<field name="mail_message_id" invisible="1"/>
<field name="can_resend" invisible="1"/>
<field name="has_insufficient_credit" invisible="1"/>
<field name="has_unregistered_account" invisible="1"/>
<field name="recipient_ids">
<tree string="Recipient" editable="top" create="0" delete="0">
<field name="partner_name"/>
<field name="sms_number"/>
<field name="failure_type" string="Reason" class="text-wrap"/>
<field name="resend" widget="boolean_toggle"/>
<field name="notification_id" invisible="1"/>
</tree>
</field>
<footer>
<button string="Buy credits" name="action_buy_credits" type="object" class="btn-primary o_mail_send"
attrs="{'invisible': [('has_insufficient_credit', '=', False)]}" data-hotkey="q"/>
<button string="Set up an account" name="action_buy_credits" type="object" class="btn-primary o_mail_send"
attrs="{'invisible': [('has_unregistered_account', '=', False)]}" data-hotkey="q"/>
<button string="Send &amp; Close" name="action_resend" type="object" class="btn-primary o_mail_send"
attrs="{'invisible': ['|', ('has_unregistered_account', '=', False), ('can_resend', '=', False)]}" data-hotkey="w"/>
<button string="Ignore all" name="action_cancel" type="object" class="btn-primary"
attrs="{'invisible': ['|', '|', ('has_insufficient_credit', '=', True), ('has_unregistered_account', '=', True), '&amp;', ('has_unregistered_account', '=', True), ('can_resend', '=', True)]}" data-hotkey="x"/>
<button string="Ignore all" name="action_cancel" type="object" class="btn-secondary"
attrs="{'invisible': ['!', '|', '|', ('has_insufficient_credit', '=', True), ('has_unregistered_account', '=', True), '&amp;', ('has_unregistered_account', '=', True), ('can_resend', '=', True)]}" data-hotkey="x"/>
<button string="Close" class="btn-secondary" special="cancel" data-hotkey="z"/>
</footer>
</form>
</field>
</record>
<record id="sms_resend_action" model="ir.actions.act_window">
<field name="name">Sending Failures</field>
<field name="res_model">sms.resend</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data></odoo>

View file

@ -4,8 +4,8 @@
from odoo import api, fields, models
class SMSTemplatePreview(models.TransientModel):
_name = "sms.template.preview"
class SmsTemplatePreview(models.TransientModel):
_name = 'sms.template.preview'
_description = "SMS Template Preview"
@api.model
@ -18,7 +18,7 @@ class SMSTemplatePreview(models.TransientModel):
@api.model
def default_get(self, fields):
result = super(SMSTemplatePreview, self).default_get(fields)
result = super().default_get(fields)
sms_template_id = self.env.context.get('default_sms_template_id')
if not sms_template_id or 'resource_ref' not in fields:
return result

View file

@ -12,8 +12,8 @@
<div class="o_row">
<span>Choose an example <field name="model_id" readonly="1"/> record:</span>
<div>
<field name="resource_ref" class="oe_inline" options="{'hide_model': True, 'no_create': True, 'no_open': True}" attrs="{'invisible': [('no_record', '=', True)]}"/>
<span class="text-warning" attrs="{'invisible': [('no_record', '=', False)]}">No records</span>
<field name="resource_ref" class="oe_inline" options="{'hide_model': True, 'no_create': True, 'no_open': True}" invisible="no_record"/>
<span class="text-warning" invisible="not no_record">No records</span>
</div>
</div>
<p>Choose a language: <field name="lang" class="oe_inline ml8"/></p>
@ -22,7 +22,7 @@
<field name="body" readonly="1" nolabel="1" options='{"safe": True}'/>
<hr/>
<footer>
<button string="Discard" class="btn-secondary" special="cancel" data-hotkey="z"/>
<button string="Discard" class="btn-secondary" special="cancel" data-hotkey="x"/>
</footer>
</form>
</field>
@ -31,7 +31,6 @@
<record id="sms_template_preview_action" model="ir.actions.act_window">
<field name="name">Template Preview</field>
<field name="res_model">sms.template.preview</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">form</field>
<field name="view_id" ref="sms_template_preview_form"/>
<field name="target">new</field>

View file

@ -4,7 +4,7 @@
from odoo import fields, models, _
class SMSTemplateReset(models.TransientModel):
class SmsTemplateReset(models.TransientModel):
_name = 'sms.template.reset'
_description = 'SMS Template Reset'

View file

@ -12,7 +12,7 @@
</div>
<footer>
<button string="Proceed" class="btn btn-primary" type="object" name="reset_template" data-hotkey="q"/>
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="x"/>
</footer>
</form>
</field>
@ -22,8 +22,7 @@
<field name="name">Reset SMS Template</field>
<field name="res_model">sms.template.reset</field>
<field name="binding_model_id" ref="sms.model_sms_template"/>
<field name="binding_view_types">list</field>
<field name="type">ir.actions.act_window</field>
<field name="binding_view_types">list,kanban</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">{