mirror of
https://github.com/bringout/oca-ocb-mail.git
synced 2026-04-20 07:22:00 +02:00
Initial commit: Mail packages
This commit is contained in:
commit
4e53507711
1948 changed files with 751201 additions and 0 deletions
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-inherit="mail.NotificationGroup" t-inherit-mode="extension">
|
||||
<xpath expr="//*[hasclass('o_NotificationGroup_inlineText')]" position="inside">
|
||||
<t t-if="notificationGroupView.notificationGroup.notification_type === 'sms'">
|
||||
An error occurred when sending an SMS.
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { PhoneField } from "@web/views/fields/phone/phone_field";
|
||||
import { SendSMSButton } from '@sms/components/sms_button/sms_button';
|
||||
|
||||
patch(PhoneField, "sms.PhoneField", {
|
||||
components: {
|
||||
...PhoneField.components,
|
||||
SendSMSButton
|
||||
},
|
||||
defaultProps: {
|
||||
...PhoneField.defaultProps,
|
||||
enableButton: true,
|
||||
},
|
||||
props: {
|
||||
...PhoneField.props,
|
||||
enableButton: { type: Boolean, optional: true },
|
||||
},
|
||||
extractProps: ({ attrs }) => {
|
||||
return {
|
||||
enableButton: attrs.options.enable_sms,
|
||||
placeholder: attrs.placeholder,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-inherit="web.PhoneField" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('o_phone_content')]//a" position="after">
|
||||
<t t-if="props.enableButton and props.value.length > 0">
|
||||
<SendSMSButton t-props="props" />
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-inherit="web.FormPhoneField" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('o_phone_content')]" position="inside">
|
||||
<t t-if="props.enableButton and props.value.length > 0">
|
||||
<SendSMSButton t-props="props" />
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component , status } = owl;
|
||||
|
||||
export class SendSMSButton extends Component {
|
||||
setup() {
|
||||
this.action = useService("action");
|
||||
this.user = useService("user");
|
||||
this.title = this.env._t("Send SMS Text Message");
|
||||
}
|
||||
get phoneHref() {
|
||||
return "sms:" + this.props.value.replace(/\s+/g, "");
|
||||
}
|
||||
async onClick() {
|
||||
await this.props.record.save();
|
||||
this.action.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
target: "new",
|
||||
name: this.title,
|
||||
res_model: "sms.composer",
|
||||
views: [[false, "form"]],
|
||||
context: {
|
||||
...this.user.context,
|
||||
default_res_model: this.props.record.resModel,
|
||||
default_res_id: this.props.record.resId,
|
||||
default_number_field_name: this.props.name,
|
||||
default_composition_mode: 'comment',
|
||||
}
|
||||
}, {
|
||||
onClose: () => {
|
||||
if (status(this) !== "destroyed") {
|
||||
this.props.record.load();
|
||||
this.props.record.model.notify();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
SendSMSButton.template = "sms.SendSMSButton";
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="sms.SendSMSButton" owl="1">
|
||||
<a
|
||||
t-att-title="title"
|
||||
t-att-href="phoneHref"
|
||||
t-on-click.prevent.stop="onClick"
|
||||
class="ms-3 d-inline-flex align-items-center o_field_phone_sms"
|
||||
><i class="fa fa-mobile"></i><small class="fw-bold ms-1">SMS</small></a>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import basic_fields from 'web.basic_fields';
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { EmojisTextField} from '@mail/views/fields/emojis_text_field/emojis_text_field';
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const DynamicPlaceholderFieldMixin = basic_fields.DynamicPlaceholderFieldMixin;
|
||||
/**
|
||||
* SmsWidget is a widget to display a textarea (the body) and a text representing
|
||||
* the number of SMS and the number of characters. This text is computed every
|
||||
* time the user changes the body.
|
||||
*/
|
||||
export class SmsWidget extends EmojisTextField {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.notification = useService('notification');
|
||||
}
|
||||
|
||||
get encoding() {
|
||||
return this._extractEncoding(this.props.value || '');
|
||||
}
|
||||
get nbrChar() {
|
||||
const content = this._getValueForSmsCounts(this.props.value || '');
|
||||
return content.length + (content.match(/\n/g) || []).length;
|
||||
}
|
||||
get nbrCharExplanation() {
|
||||
return '';
|
||||
}
|
||||
get nbrSMS() {
|
||||
return this._countSMS(this.nbrChar, this.encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a Model Field Selector in order to select fields
|
||||
* and create a dynamic placeholder string with or without
|
||||
* a default text value.
|
||||
*
|
||||
* @public
|
||||
* @param {String} baseModel
|
||||
* @param {Array} chain
|
||||
*
|
||||
*/
|
||||
async openDynamicPlaceholder(baseModel, chain = []) {
|
||||
const modelSelector = await this._openNewModelSelector(baseModel, chain);
|
||||
modelSelector.$el.css('margin-top', 4);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private: SMS
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Count the number of SMS of the content
|
||||
* @private
|
||||
* @returns {integer} Number of SMS
|
||||
*/
|
||||
_countSMS(nbrChar, encoding) {
|
||||
if (nbrChar === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (encoding === 'UNICODE') {
|
||||
if (nbrChar <= 70) {
|
||||
return 1;
|
||||
}
|
||||
return Math.ceil(nbrChar / 67);
|
||||
}
|
||||
if (nbrChar <= 160) {
|
||||
return 1;
|
||||
}
|
||||
return Math.ceil(nbrChar / 153);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the encoding depending on the characters in the content
|
||||
* @private
|
||||
* @param {String} content Content of the SMS
|
||||
* @returns {String} Encoding of the content (GSM7 or UNICODE)
|
||||
*/
|
||||
_extractEncoding(content) {
|
||||
if (String(content).match(RegExp("^[@£$¥èéùìòÇ\\nØø\\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\\\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà]*$"))) {
|
||||
return 'GSM7';
|
||||
}
|
||||
return 'UNICODE';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement if more characters are going to be sent then those appearing in
|
||||
* value, if that value is processed before being sent.
|
||||
* E.g., links are converted to trackers in mass_mailing_sms.
|
||||
*
|
||||
* Note: goes with an explanation in nbrCharExplanation
|
||||
*
|
||||
* @param {String} value content to be parsed for counting extra characters
|
||||
* @return string length-corrected value placeholder for the post-processed
|
||||
* state
|
||||
*/
|
||||
_getValueForSmsCounts(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
async onBlur() {
|
||||
var content = this.props.value || '';
|
||||
if( !content.trim().length && content.length > 0) {
|
||||
this.notification.add(
|
||||
this.env._t("Your SMS Text Message must include at least one non-whitespace character"),
|
||||
{ type: 'danger' },
|
||||
)
|
||||
await this.props.update(content.trim());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
async onInput(ev) {
|
||||
await this.props.update(this.targetEditElement.el.value);
|
||||
super.onInput(...arguments);
|
||||
const key = ev.originalEvent ? ev.originalEvent.data : '';
|
||||
if (this.props.dynamicPlaceholder && key === this.DYNAMIC_PLACEHOLDER_TRIGGER_KEY) {
|
||||
const baseModel = this.recordData && this.recordData.mailing_model_real ? this.recordData.mailing_model_real : undefined;
|
||||
if (baseModel) {
|
||||
this.openDynamicPlaceholder(baseModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
SmsWidget.template = 'sms.SmsWidget';
|
||||
SmsWidget.additionalClasses = [...(EmojisTextField.additionalClasses || []), 'o_field_text'];
|
||||
patch(SmsWidget.prototype, 'sms_widget_dynamic_placeholder_field_mixin', DynamicPlaceholderFieldMixin);
|
||||
registry.category("fields").add("sms_widget", SmsWidget);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="sms.SmsWidget" t-inherit="mail.EmojisTextField" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//textarea[1]" position="attributes">
|
||||
<attribute name="t-on-blur">onBlur</attribute>
|
||||
</xpath>
|
||||
<xpath expr="/*[last()]/*[last()]" position="after">
|
||||
<div class="o_sms_container">
|
||||
<span class="text-muted o_sms_count">
|
||||
<t t-out="nbrChar"/> characters<t t-out="nbrCharExplanation"/>, fits in <t t-out="nbrSMS"/> SMS (<t t-out="encoding"/>)
|
||||
<a href="https://iap-services.odoo.com/iap/sms/pricing" target="_blank"
|
||||
title="SMS Pricing" aria-label="SMS Pricing" class="fa fa-lg fa-info-circle"/>
|
||||
</span>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
odoo.define('sms.fields', function (require) {
|
||||
"use strict";
|
||||
|
||||
var basic_fields = require('web.basic_fields');
|
||||
var core = require('web.core');
|
||||
var session = require('web.session');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
/**
|
||||
* Override of FieldPhone to add a button calling SMS composer if option activated (default)
|
||||
*/
|
||||
|
||||
var Phone = basic_fields.FieldPhone;
|
||||
Phone.include({
|
||||
/**
|
||||
* By default, enable_sms is activated
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
init() {
|
||||
this._super.apply(this, arguments);
|
||||
this.enableSMS = 'enable_sms' in this.attrs.options ? this.attrs.options.enable_sms : true;
|
||||
// reinject in nodeOptions (and thus in this.attrs) to signal the property
|
||||
this.attrs.options.enable_sms = this.enableSMS;
|
||||
},
|
||||
/**
|
||||
* When the send SMS button is displayed, $el becomes a div wrapping
|
||||
* the original links.
|
||||
* This method makes sure we always focus the phone number
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
getFocusableElement() {
|
||||
if (this.enableSMS && this.mode === 'readonly') {
|
||||
return this.$el.filter('.' + this.className).find('a');
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Open SMS composer wizard
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onClickSMS: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
var context = session.user_context;
|
||||
context = _.extend({}, context, {
|
||||
default_res_model: this.model,
|
||||
default_res_id: parseInt(this.res_id),
|
||||
default_number_field_name: this.name,
|
||||
default_composition_mode: 'comment',
|
||||
});
|
||||
var self = this;
|
||||
return this.do_action({
|
||||
title: _t('Send SMS Text Message'),
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'sms.composer',
|
||||
target: 'new',
|
||||
views: [[false, 'form']],
|
||||
context: context,
|
||||
}, {
|
||||
on_close: function () {
|
||||
self.trigger_up('reload');
|
||||
}});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a button to call the composer wizard
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderReadonly: function () {
|
||||
var def = this._super.apply(this, arguments);
|
||||
if (this.enableSMS && this.value) {
|
||||
var $composerButton = $('<a>', {
|
||||
title: _t('Send SMS Text Message'),
|
||||
href: '',
|
||||
class: 'ms-3 d-inline-flex align-items-center o_field_phone_sms',
|
||||
html: $('<small>', {class: 'fw-bold ms-1', html: 'SMS'}),
|
||||
});
|
||||
$composerButton.prepend($('<i>', {class: 'fa fa-mobile'}));
|
||||
$composerButton.on('click', this._onClickSMS.bind(this));
|
||||
this.$el = this.$el.add($composerButton);
|
||||
}
|
||||
return def;
|
||||
},
|
||||
});
|
||||
|
||||
return Phone;
|
||||
|
||||
});
|
||||
26
odoo-bringout-oca-ocb-sms/sms/static/src/models/message.js
Normal file
26
odoo-bringout-oca-ocb-sms/sms/static/src/models/message.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerPatch } from '@mail/model/model_core';
|
||||
|
||||
registerPatch({
|
||||
name: 'Message',
|
||||
recordMethods: {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
openResendAction() {
|
||||
if (this.message_type === 'sms') {
|
||||
this.env.services.action.doAction(
|
||||
'sms.sms_resend_action',
|
||||
{
|
||||
additionalContext: {
|
||||
default_mail_message_id: this.id,
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
this._super(...arguments);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerPatch } from '@mail/model/model_core';
|
||||
|
||||
registerPatch({
|
||||
name: 'MessageView',
|
||||
fields: {
|
||||
failureNotificationIconClassName: {
|
||||
compute() {
|
||||
if (this.message && this.message.message_type === 'sms') {
|
||||
return 'fa fa-mobile';
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
},
|
||||
failureNotificationIconLabel: {
|
||||
compute() {
|
||||
if (this.message && this.message.message_type === 'sms') {
|
||||
return this.env._t("SMS");
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
},
|
||||
notificationIconClassName: {
|
||||
compute() {
|
||||
if (this.message && this.message.message_type === 'sms') {
|
||||
return 'fa fa-mobile';
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
},
|
||||
notificationIconLabel: {
|
||||
compute() {
|
||||
if (this.message && this.message.message_type === 'sms') {
|
||||
return this.env._t("SMS");
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerPatch } from '@mail/model/model_core';
|
||||
|
||||
registerPatch({
|
||||
name: 'NotificationGroup',
|
||||
recordMethods: {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_openDocuments() {
|
||||
if (this.notification_type !== 'sms') {
|
||||
return this._super(...arguments);
|
||||
}
|
||||
this.env.services.action.doAction({
|
||||
name: this.env._t("SMS Failures"),
|
||||
type: 'ir.actions.act_window',
|
||||
view_mode: 'kanban,list,form',
|
||||
views: [[false, 'kanban'], [false, 'list'], [false, 'form']],
|
||||
target: 'current',
|
||||
res_model: this.res_model,
|
||||
domain: [['message_has_sms_error', '=', true]],
|
||||
context: { create: false },
|
||||
});
|
||||
if (this.messaging.device.isSmall) {
|
||||
// messaging menu has a higher z-index than views so it must
|
||||
// be closed to ensure the visibility of the view
|
||||
this.messaging.messagingMenu.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerPatch } from '@mail/model/model_core';
|
||||
|
||||
registerPatch({
|
||||
name: 'NotificationGroupView',
|
||||
fields: {
|
||||
imageSrc: {
|
||||
compute() {
|
||||
if (this.notificationGroup.notification_type === 'sms') {
|
||||
return '/sms/static/img/sms_failure.svg';
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue