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

@ -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