[MIG] mail_broker: Migration to 14.0

pull/1305/head
Enric Tobella 2022-11-30 11:35:53 +01:00 committed by Enric Tobella
parent 294aa506d4
commit ad8cbb3438
17 changed files with 456 additions and 548 deletions

View File

@ -5,7 +5,7 @@
"name": "Mail Broker",
"summary": """
Set a broker""",
"version": "13.0.1.0.0",
"version": "14.0.1.0.0",
"license": "AGPL-3",
"author": "Creu Blanca,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/social",

View File

@ -17,6 +17,10 @@ class NewMailChatController(MailChatController):
def _poll(self, dbname, channels, last, options):
if request.session.uid:
if request.env.user.has_group("mail_broker.broker_user"):
for bot in request.env["mail.broker"].search([]):
channels.append((request.db, "mail.broker", bot.id))
return super()._poll(dbname, channels, last, options)
channels = list(channels)
for channel in request.env["mail.channel"].search(
[("public", "=", "broker")]
):
channels.append((request.db, "mail.channel", channel.id))
result = super()._poll(dbname, channels, last, options)
return result

View File

@ -42,7 +42,7 @@ class MailBroker(models.Model):
def _get_channel_id(self, chat_token):
return (
self.env["mail.broker.channel"]
self.env["mail.channel"]
.search(
[("token", "=", str(chat_token)), ("broker_id", "=", self.id)],
limit=1,
@ -94,7 +94,7 @@ class MailBroker(models.Model):
"channel_name": "broker_%s" % record.id,
"threads": [
thread._get_thread_data()
for thread in self.env["mail.broker.channel"].search(
for thread in self.env["mail.channel"].search(
[("show_on_app", "=", True), ("broker_id", "=", record.id)]
)
],
@ -107,7 +107,7 @@ class MailBroker(models.Model):
domain = [("broker_id", "=", self.id)]
if name:
domain += [("name", "ilike", "%" + name + "%")]
return self.env["mail.broker.channel"].search(domain).read(["name"])
return self.env["mail.channel"].search(domain).read(["name"])
def write(self, vals):
res = super(MailBroker, self).write(vals)

View File

@ -7,39 +7,32 @@ from xmlrpc.client import DateTime
from odoo import api, fields, models
class MailBrokerChannel(models.Model):
_name = "mail.broker.channel"
_description = "Mail Broker Channel"
class MailChannel(models.Model):
_inherit = "mail.channel"
name = fields.Char(required=True)
active = fields.Boolean(default=True)
token = fields.Char(required=True)
broker_id = fields.Many2one("mail.broker", required=True)
message_ids = fields.One2many(
token = fields.Char()
broker_id = fields.Many2one("mail.broker")
broker_message_ids = fields.One2many(
"mail.message.broker",
inverse_name="channel_id",
)
mail_message_ids = fields.One2many(
"mail.message",
inverse_name="broker_channel_id",
)
last_message_date = fields.Datetime(
compute="_compute_message_data",
store=True,
)
unread = fields.Integer(
public = fields.Selection(
selection_add=[("broker", "Broker")], ondelete={"broker": "set default"}
)
channel_type = fields.Selection(
selection_add=[("broker", "Broker")], ondelete={"broker": "set default"}
)
broker_unread = fields.Integer(
compute="_compute_message_data",
store=True,
)
broker_token = fields.Char(related="broker_id.token", store=True, required=False)
show_on_app = fields.Boolean()
partner_id = fields.Many2one("res.partner")
message_main_attachment_id = fields.Many2one(
string="Main Attachment",
comodel_name="ir.attachment",
index=True,
copy=False,
)
broker_partner_id = fields.Many2one("res.partner")
def message_fetch(self, domain=False, limit=30):
self.ensure_one()
@ -52,9 +45,9 @@ class MailBrokerChannel(models.Model):
)
@api.depends(
"mail_message_ids",
"mail_message_ids.date",
"mail_message_ids.broker_unread",
"message_ids",
"message_ids.date",
"message_ids.broker_unread",
)
def _compute_message_data(self):
for r in self:
@ -67,7 +60,7 @@ class MailBrokerChannel(models.Model):
)
.date
)
r.unread = self.env["mail.message"].search_count(
r.broker_unread = self.env["mail.message"].search_count(
[("broker_channel_id", "=", r.id), ("broker_unread", "=", True)]
)
@ -78,12 +71,19 @@ class MailBrokerChannel(models.Model):
"name": self.name,
"last_message_date": self.last_message_date,
"channel_type": "broker_thread",
"unread": self.unread,
"unread": self.broker_unread,
"broker_id": self.broker_id.id,
}
def _broker_message_post_vals(self, body, **kwargs):
subtype_id = kwargs.get("subtype_id", False)
def _broker_message_post_vals(
self,
body,
subtype_id=False,
author_id=False,
date=False,
message_id=False,
**kwargs
):
if not subtype_id:
subtype = kwargs.get("subtype") or "mt_note"
if "." not in subtype:
@ -91,21 +91,21 @@ class MailBrokerChannel(models.Model):
subtype_id = self.env["ir.model.data"].xmlid_to_res_id(subtype)
vals = {
"channel_id": self.id,
"channel_ids": [(4, self.id)],
"body": body,
"subtype_id": subtype_id,
"model": self._name,
"res_id": self.id,
"broker_type": self.broker_id.broker_type,
}
if kwargs.get("author_id", False):
vals["author_id"] = kwargs["author_id"]
if "date" in kwargs:
date = kwargs["date"]
if author_id:
vals["author_id"] = author_id
if date:
if isinstance(date, DateTime):
date = datetime.strptime(str(date), "%Y%m%dT%H:%M:%S")
vals["date"] = date
if "message_id" in kwargs:
vals["message_id"] = kwargs["message_id"]
if message_id:
vals["message_id"] = message_id
vals["broker_unread"] = kwargs.get("broker_unread", False)
vals["attachment_ids"] = []
for attachment_id in kwargs.get("attachment_ids", []):
@ -136,12 +136,40 @@ class MailBrokerChannel(models.Model):
):
return False
vals = self._broker_message_post_vals(
body, broker_unread=True, author_id=self.partner_id.id, **kwargs
body, broker_unread=True, author_id=self.broker_partner_id.id, **kwargs
)
vals["state"] = "received"
vals["broker_type"] = broker_type
return self.env["mail.message.broker"].create(vals)
@api.returns("mail.message", lambda value: value.id)
def message_post(self, *args, **kwargs):
message = super().message_post(*args, **kwargs)
if self.broker_id:
self.env["mail.message.broker"].create(
{
"mail_message_id": message.id,
"channel_id": self.id,
}
).send()
return message
@api.model
def channel_fetch_slot(self):
result = super().channel_fetch_slot()
broker_channels = self.env["mail.channel"].search([("public", "=", "broker")])
result["channel_channel"] += broker_channels.channel_info()
return result
def channel_info(self, *args, **kwargs):
result = super().channel_info(*args, **kwargs)
for channel, channel_info in zip(self, result):
channel_info["broker_id"] = (
channel.broker_id and channel.broker_id.id or False
)
channel_info["broker_unread_counter"] = channel.broker_unread
return result
@api.model_create_multi
def create(self, vals_list):
channels = super().create(vals_list)

View File

@ -9,7 +9,7 @@ class MailMessage(models.Model):
_inherit = "mail.message"
broker_channel_id = fields.Many2one(
"mail.broker.channel",
"mail.channel",
readonly=True,
compute="_compute_broker_channel_id",
store=True,
@ -51,5 +51,7 @@ class MailMessage(models.Model):
return result
def set_message_done(self):
self.write({"broker_unread": False})
# We need to set it as sudo in order to avoid collateral damages.
# In fact, it is done with sudo on the original method
self.sudo().filtered(lambda r: r.broker_unread).write({"broker_unread": False})
return super().set_message_done()

View File

@ -25,9 +25,7 @@ class MailMessageBroker(models.Model):
auto_join=True,
)
message_id = fields.Char(readonly=True)
channel_id = fields.Many2one(
"mail.broker.channel", required=True, ondelete="cascade"
)
channel_id = fields.Many2one("mail.channel", required=True, ondelete="cascade")
state = fields.Selection(
[
("outgoing", "Outgoing"),
@ -54,17 +52,10 @@ class MailMessageBroker(models.Model):
if self.env.context.get("notify_broker", False):
notifications = []
for message in messages:
notifications.append(
[
(
self._cr.dbname,
"mail.broker",
message.channel_id.broker_id.id,
),
{"message": message.mail_message_id.message_format()[0]},
]
notifications += message.channel_id._channel_message_notifications(
message.mail_message_id
)
self.env["bus.bus"].sendmany(notifications)
self.env["bus.bus"].sudo().sendmany(notifications)
return messages
def send(self, auto_commit=False, raise_exception=False, parse_mode="HTML"):

View File

@ -3,8 +3,5 @@ access_mail_message_broker_all,mail.message.broker.all,model_mail_message_broker
access_mail_message_broker_portal,mail.message.broker.portal,model_mail_message_broker,base.group_portal,1,1,1,0
access_mail_message_broker_user,mail.message.broker.user,model_mail_message_broker,base.group_user,1,1,1,0
access_mail_message_broker_system,mail.message.broker.system,model_mail_message_broker,base.group_system,1,1,1,1
access_mail_broker_channel_all,mail.telegram.chat.all,model_mail_broker_channel,,1,0,0,0
access_mail_broker_channel_system,mail_broker_channel,model_mail_broker_channel,base.group_system,1,1,1,1
access_mail_broker_all,mail.telegram.bot.all,model_mail_broker,,1,0,0,0
access_mail_broker_channel_user,mail_broker_manager_bot,model_mail_broker_channel,mail_broker.broker_user,1,1,0,0
access_mail_broker_system,mail_broker,model_mail_broker,base.group_system,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_mail_message_broker_portal mail.message.broker.portal model_mail_message_broker base.group_portal 1 1 1 0
4 access_mail_message_broker_user mail.message.broker.user model_mail_message_broker base.group_user 1 1 1 0
5 access_mail_message_broker_system mail.message.broker.system model_mail_message_broker base.group_system 1 1 1 1
access_mail_broker_channel_all mail.telegram.chat.all model_mail_broker_channel 1 0 0 0
access_mail_broker_channel_system mail_broker_channel model_mail_broker_channel base.group_system 1 1 1 1
6 access_mail_broker_all mail.telegram.bot.all model_mail_broker 1 0 0 0
access_mail_broker_channel_user mail_broker_manager_bot model_mail_broker_channel mail_broker.broker_user 1 1 0 0
7 access_mail_broker_system mail_broker model_mail_broker base.group_system 1 1 1 1

View File

@ -11,4 +11,25 @@
eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"
/>
</record>
<record id="mail_channel_broker_rule" model="ir.rule">
<field name="name">Mail.channel: access broker</field>
<field name="model_id" ref="mail.model_mail_channel" />
<field name="groups" eval="[(4, ref('mail_broker.broker_user'))]" />
<field name="domain_force">[('public', '=', 'broker')]</field>
<field name="perm_create" eval="False" />
<field name="perm_unlink" eval="True" />
</record>
<record id="ir_rule_mail_channel_partner_group_user" model="ir.rule">
<field
name="name"
>mail.channel.partner: write its own entries on broker channels</field>
<field name="model_id" ref="mail.model_mail_channel_partner" />
<field name="groups" eval="[(4, ref('mail_broker.broker_user'))]" />
<field name="domain_force">[('channel_id.public', '=', 'broker')]</field>
<field name="perm_read" eval="False" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="False" />
<field name="perm_unlink" eval="True" />
</record>
</odoo>

View File

@ -1,4 +1,4 @@
# Copyright 2018 ACSONE SA/NV
# Copyright 2022 CreuBlanca
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo.addons.base_rest import restapi
from odoo.addons.component.core import AbstractComponent
@ -17,6 +17,12 @@ class BrokerMethodParams(restapi.RestMethodParam):
def to_openapi_responses(self, service):
return {"200": {"content": {}}}
def to_openapi_query_parameters(self, service, spec):
return []
def to_json_schema(self, service, spec, direction):
return {}
class MailBrokerService(AbstractComponent):
_inherit = "base.rest.service"
@ -60,10 +66,10 @@ class MailBrokerService(AbstractComponent):
def _get_channel(self, broker, token, update, force_create=False):
chat_id = broker._get_channel_id(token)
if chat_id:
return self.env["mail.broker.channel"].browse(chat_id)
return broker.env["mail.channel"].browse(chat_id)
if not force_create and broker.has_new_channel_security:
return False
return self.env["mail.broker.channel"].create(
return broker.env["mail.channel"].create(
self._get_channel_vals(broker, token, update)
)
@ -72,6 +78,8 @@ class MailBrokerService(AbstractComponent):
"token": token,
"broker_id": broker.id,
"show_on_app": broker.show_on_app,
"public": "broker",
"channel_type": "broker",
}
def _send(self, record, auto_commit=False, raise_exception=False, parse_mode=False):

View File

@ -1,429 +1,124 @@
odoo.define("mail_broker.Broker", function (require) {
odoo.define("mail_broker/static/src/broker.js", function (require) {
"use strict";
var BasicComposer = require("mail.composer.Basic");
var ExtendedComposer = require("mail.composer.Extended");
var core = require("web.core");
var AbstractAction = require("web.AbstractAction");
// Var ControlPanelMixin = require("web.ControlPanelMixin");
var ThreadWidget = require("mail.widget.Thread");
var dom = require("web.dom");
const components = {
Discuss: require("mail_broker/static/src/discuss.js"),
};
const AbstractAction = require("web.AbstractAction");
const {action_registry} = require("web.core");
var QWeb = core.qweb;
var _t = core._t;
const {Component} = owl;
var Broker = AbstractAction.extend({
contentTemplate: "mail_broker.broker",
events: {
"click .o_mail_channel_settings": "_onChannelSettingsClicked",
"click .o_mail_discuss_item": "_onSelectBrokerChannel",
"click .o_mail_sidebar_title .o_add": "_onSearchThread",
"blur .o_mail_add_thread input": "_onSearchThreadBlur",
},
init: function (parent, action, options) {
this._super.apply(this, arguments);
template: "mail.widgets.Discuss",
hasControlPanel: false,
loadControlPanel: false,
withSearchBar: false,
searchMenuTypes: ["filter", "favorite"],
/**
* @override {web.AbstractAction}
* @param {web.ActionManager} parent
* @param {Object} action
* @param {Object} [action.context]
* @param {String} [action.context.active_id]
* @param {Object} [action.params]
* @param {String} [action.params.default_active_id]
* @param {Object} [options={}]
*/
init(parent, action, options = {}) {
this._super(...arguments);
// Control panel attributes
this.action = action;
this.action_manager = parent;
this.domain = [];
this.options = options || {};
this._threadsScrolltop = {};
this._composerStates = {};
this._defaultChatID =
this.actionManager = parent;
this.searchModelConfig.modelName = "mail.message";
this.discuss = undefined;
this.options = options;
this.component = undefined;
this._lastPushStateActiveThread = null;
},
/**
* @override
*/
async willStart() {
await this._super(...arguments);
this.env = Component.env;
await this.env.messagingCreatedPromise;
const initActiveId =
this.options.active_id ||
this.action.context.active_id ||
this.action.params.default_active_id;
this._selectedMessage = null;
(this.action.context && this.action.context.active_id) ||
(this.action.params && this.action.params.default_active_id) ||
"mail.box_inbox";
this.discuss = this.env.messaging.discuss;
this.discuss.update({initActiveId});
},
/**
* @override
* @override {web.AbstractAction}
*/
on_attach_callback: function () {
if (this._thread) {
this._threadWidget.scrollToPosition(
this._threadsScrolltop[this._thread.getID()]
);
this._loadEnoughMessages();
destroy() {
if (this.component) {
this.component.destroy();
this.component = undefined;
}
this._super(...arguments);
},
/**
* @override
* @override {web.AbstractAction}
*/
on_detach_callback: function () {
if (this._thread) {
this._threadsScrolltop[
this._thread.getID()
] = this._threadWidget.getScrolltop();
}
},
start: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
return self._initRender();
});
/*
Return this.alive($.when.apply($, defs))
.then(function() {
if (self._defaultChatID) {
return self.alive(self._setThread(self._defaultChatID));
}
})
.then(function() {
self._updateThreads();
self._startListening();
self._threadWidget.$el.on(
"scroll",
null,
_.debounce(function() {
var $noContent = self._threadWidget.$(".o_mail_no_content");
if (
self._threadWidget.getScrolltop() < 20 &&
!self._thread.isAllHistoryLoaded() &&
!$noContent.length
) {
self._loadMoreMessages();
}
if (self._threadWidget.isAtBottom()) {
self._thread.markAsRead();
}
}, 100)
);
});
*/
},
_initRender: function () {
var self = this;
this._basicComposer = new BasicComposer(this, {
mentionPartnersRestricted: true,
});
this._extendedComposer = new ExtendedComposer(this, {
mentionPartnersRestricted: true,
});
this._basicComposer
.on("post_message", this, this._onPostMessage)
.on("input_focused", this, this._onComposerFocused);
this._extendedComposer
.on("post_message", this, this._onPostMessage)
.on("input_focused", this, this._onComposerFocused);
this._renderButtons();
var defs = [];
defs.push(this._renderThread());
defs.push(this._basicComposer.appendTo(this.$(".o_mail_discuss_content")));
return Promise.all(defs)
.then(function () {
if (self._defaultChatID) {
return self._setThread(self._defaultChatID);
}
})
.then(function () {
self._updateThreads();
self._startListening();
self._threadWidget.$el.on(
"scroll",
null,
_.debounce(function () {
var $noContent = self._threadWidget.$(".o_mail_no_content");
if (
self._threadWidget.getScrolltop() < 20 &&
!self._thread.isAllHistoryLoaded() &&
!$noContent.length
) {
self._loadMoreMessages();
}
if (self._threadWidget.isAtBottom()) {
self._thread.markAsRead();
}
}, 100)
);
});
},
_startListening: function () {
this.call("mail_service", "getMailBus").on(
"new_message",
this,
this._onNewMessage
);
},
_setThread: function (threadID) {
this._storeThreadState();
var thread = this.call("mail_service", "getThread", threadID);
if (!thread) {
on_attach_callback() {
this._super(...arguments);
if (this.component) {
// Prevent twice call to on_attach_callback (FIXME)
return;
}
this._thread = thread;
var self = this;
this.messagesSeparatorPosition = undefined;
return this._fetchAndRenderThread().then(function () {
self._thread.markAsRead();
// Restore scroll position and composer of the new
// current thread
self._restoreThreadState();
// Update control panel before focusing the composer, otherwise
// focus is on the searchview
self.set("title", self._thread.getTitle());
self.action_manager.do_push_state({
action: self.action.id,
active_id: self._thread.getID(),
});
});
},
_storeThreadState: function () {
if (this._thread) {
this._threadsScrolltop[
this._thread.getID()
] = this._threadWidget.getScrolltop();
}
},
_loadEnoughMessages: function () {
var $el = this._threadWidget.el;
var loadMoreMessages =
$el.clientHeight &&
$el.clientHeight === $el.scrollHeight &&
!this._thread.isAllHistoryLoaded();
if (loadMoreMessages) {
return this._loadMoreMessages().then(
this._loadEnoughMessages.bind(this)
);
}
},
_getThreadRenderingOptions: function () {
if (_.isUndefined(this.messagesSeparatorPosition)) {
if (this._unreadCounter) {
var messageID = this._thread.getLastSeenMessageID();
this.messagesSeparatorPosition = messageID || "top";
} else {
// No unread message -> don't display separator
this.messagesSeparatorPosition = false;
}
}
return {
displayLoadMore: !this._thread.isAllHistoryLoaded(),
squashCloseMessages: true,
messagesSeparatorPosition: this.messagesSeparatorPosition,
displayEmailIcons: false,
displayReplyIcons: false,
displayBottomThreadFreeSpace: true,
displayModerationCommands: false,
displayMarkAsRead: false,
displayDocumentLinks: false,
displayStars: false,
};
},
_fetchAndRenderThread: function () {
var self = this;
return this._thread.fetchMessages().then(function () {
self._threadWidget.render(
self._thread,
self._getThreadRenderingOptions()
);
return self._loadEnoughMessages();
});
},
_renderButtons: function () {
// This is a hook just in case some buttons are required
},
_renderThread: function () {
this._threadWidget = new ThreadWidget(this, {
areMessageAttachmentsDeletable: false,
loadMoreOnScroll: true,
});
this._threadWidget.on("load_more_messages", this, this._loadMoreMessages);
return this._threadWidget.appendTo(this.$(".o_mail_discuss_content"));
},
_renderSidebar: function (options) {
var $sidebar = $(
QWeb.render("mail_broker.broker.Sidebar", {
activeThreadID: this._thread ? this._thread.getID() : undefined,
bots: options.bots,
})
);
return $sidebar;
},
_restoreThreadState: function () {
var $newMessagesSeparator = this.$(".o_thread_new_messages_separator");
if ($newMessagesSeparator.length) {
this._threadWidget.$el.scrollTo($newMessagesSeparator);
} else {
var newThreadScrolltop = this._threadsScrolltop[this._thread.getID()];
this._threadWidget.scrollToPosition(newThreadScrolltop);
}
},
_updateThreads: function () {
var bots = this.call("mail_service", "getBrokerBots");
var $sidebar = this._renderSidebar({
bots: bots,
});
this.$(".o_mail_discuss_sidebar").html($sidebar.contents());
var self = this;
_.each(bots, function (bot, broker_id) {
var $input = self.$(
".o_mail_add_thread[data-bot=" + broker_id + "] input"
);
self._prepareAddThreadInput($input, broker_id, bot);
});
},
_prepareAddThreadInput: function ($input, broker_id) {
var self = this;
$input.autocomplete({
source: function (request, response) {
self._lastSearchVal = _.escape(request.term);
self._searchChannel(broker_id, self._lastSearchVal).then(function (
result
) {
response(result);
});
},
select: function (ev, ui) {
self._setThread("broker_thread_" + ui.item.id);
self._updateThreads();
},
focus: function (ev) {
ev.preventDefault();
},
html: true,
});
},
_loadMoreMessages: function () {
var self = this;
var oldestMessageID = this.$(".o_thread_message").first().data("messageId");
var oldestMessageSelector =
'.o_thread_message[data-message-id="' + oldestMessageID + '"]';
var offset = -dom.getPosition(document.querySelector(oldestMessageSelector))
.top;
return this._thread.fetchMessages({loadMore: true}).then(function () {
if (self.messagesSeparatorPosition === "top") {
// Reset value to re-compute separator position
self.messagesSeparatorPosition = undefined;
}
self._threadWidget.render(
self._thread,
self._getThreadRenderingOptions()
);
offset += dom.getPosition(document.querySelector(oldestMessageSelector))
.top;
self._threadWidget.scrollToPosition(offset);
});
},
_onSearchThread: function (ev) {
ev.preventDefault();
var bot = $(ev.target).data("bot");
this.$(".o_mail_add_thread[data-bot=" + bot + "]")
.show()
.find("input")
.focus();
},
_onSearchThreadBlur: function () {
this.$(".o_mail_add_thread").hide();
},
_onChannelSettingsClicked: function (ev) {
ev.preventDefault();
const DiscussComponent = components.Discuss;
this.component = new DiscussComponent();
this._pushStateActionManagerEventListener = (ev) => {
ev.stopPropagation();
var threadID = $(ev.target).data("thread-id");
var thread = this.call("mail_service", "getThread", threadID);
this.do_action({
type: "ir.actions.act_window",
res_model: "mail.broker.channel",
res_id: thread.resId,
name: _t("Configure chat"),
views: [[false, "form"]],
target: "new",
});
},
_onNewMessage: function (message) {
var thread_id = "broker_thread_" + message.broker_channel_id;
if (this._thread && thread_id === this._thread.getID()) {
this._thread.markAsRead();
var shouldScroll = this._threadWidget.isAtBottom();
var self = this;
this._fetchAndRenderThread().then(function () {
if (shouldScroll) {
self._threadWidget.scrollToMessage({
msgID: message.getID(),
});
if (this._lastPushStateActiveThread === this.discuss.thread) {
return;
}
});
}
// Re-render sidebar to indicate that there is a new message in
// The corresponding threads
this._updateThreads();
// Dump scroll position of threads in which the new message arrived
this._threadsScrolltop = _.omit(
this._threadsScrolltop,
message.getThreadIDs()
);
},
_searchChannel: function (broker_id, searchVal) {
return this._rpc({
model: "mail.broker",
method: "channel_search",
args: [[parseInt(broker_id, 10)], searchVal],
}).then(function (result) {
var values = [];
_.each(result, function (channel) {
var escapedName = _.escape(channel.name);
values.push(
_.extend(channel, {
value: escapedName,
label: escapedName,
})
);
});
return values;
});
},
_onComposerFocused: function () {
// Hook
},
_onSelectBrokerChannel: function (ev) {
ev.preventDefault();
var threadID = $(ev.currentTarget).data("thread-id");
this._setThread(threadID);
this._updateThreads();
},
_onPostMessage: function (messageData) {
var self = this;
var options = {};
if (this._selectedMessage) {
messageData.subtype = this._selectedMessage.isNote()
? "mail.mt_note"
: "mail.mt_comment";
messageData.subtype_id = false;
messageData.broker_type = "comment";
this._pushStateActionManager();
this._lastPushStateActiveThread = this.discuss.thread;
};
options.documentID = this._selectedMessage.getDocumentID();
options.documentModel = this._selectedMessage.getDocumentModel();
}
this._thread
.postMessage(messageData, options)
.then(function () {
if (self._selectedMessage) {
self._renderSnackbar(
"mail.discuss.MessageSentSnackbar",
{
documentName: self._selectedMessage.getDocumentName(),
},
5000
this.el.addEventListener(
"o-push-state-action-manager",
this._pushStateActionManagerEventListener
);
self._unselectMessage();
} else {
self._threadWidget.scrollToBottom();
return this.component.mount(this.el);
},
/**
* @override {web.AbstractAction}
*/
on_detach_callback() {
this._super(...arguments);
if (this.component) {
this.component.destroy();
}
})
.catch(function () {
// TODO: Display notifications
this.component = undefined;
this.el.removeEventListener(
"o-push-state-action-manager",
this._pushStateActionManagerEventListener
);
this._lastPushStateActiveThread = null;
},
// --------------------------------------------------------------------------
// Private
// --------------------------------------------------------------------------
/**
* @private
*/
_pushStateActionManager() {
this.actionManager.do_push_state({
action: this.action.id,
active_id: this.discuss.activeId,
});
},
});
core.action_registry.add("mail.broker", Broker);
action_registry.add("mail.broker", Broker);
return Broker;
});

View File

@ -0,0 +1,85 @@
odoo.define("mail_broker/static/src/js/broker_model.js", function (require) {
"use strict";
const {
registerNewModel,
registerInstancePatchModel,
registerFieldPatchModel,
registerClassPatchModel,
} = require("mail/static/src/model/model_core.js");
const {attr, many2one} = require("mail/static/src/model/model_field.js");
function factoryBroker(dependencies) {
class Broker extends dependencies["mail.model"] {}
Broker.modelName = "mail.broker";
Broker.fields = {
id: attr(),
name: attr(),
};
return Broker;
}
registerNewModel("mail.broker", factoryBroker);
registerInstancePatchModel(
"mail.messaging_initializer",
"mail_broker/static/src/js/broker_model.js",
{
async _init({broker_slots}) {
_.each(broker_slots, (broker_slot) =>
this.env.models["mail.broker"].insert(
Object.assign({model: "mail.broker"}, broker_slot)
)
);
return this._super(...arguments);
},
}
);
registerInstancePatchModel(
"mail.discuss",
"mail_broker/static/src/js/broker_model.js",
{
async openBrokerChannel(thread) {
this.update({
brokerChannel: [["link", thread]],
});
this.focus();
this.env.bus.trigger("do-action", {
action: "mail_broker.mail_broker_action_window",
options: {
active_id: this.threadToActiveId(this),
clear_breadcrumbs: false,
on_reverse_breadcrumb: () => this.close(),
},
});
},
}
);
registerClassPatchModel(
"mail.thread",
"mail_broker/static/src/js/broker_model.js",
{
convertData(data) {
const data2 = this._super(data);
data2.broker_id = data.broker_id;
data2.broker_unread_counter = data.broker_unread_counter;
return data2;
},
}
);
registerFieldPatchModel(
"mail.discuss",
"mail_broker/static/src/js/broker_model.js",
{
brokerChannel: many2one("mail.thread"),
}
);
registerFieldPatchModel(
"mail.thread",
"mail_broker/static/src/js/broker_model.js",
{
broker_id: attr(),
broker_unread_counter: attr(),
}
);
});

View File

@ -0,0 +1,69 @@
odoo.define("mail_broker/static/src/discuss.js", function (require) {
"use strict";
const Discuss = require("mail/static/src/components/discuss/discuss.js");
const DiscussSidebar = require("mail/static/src/components/discuss_sidebar/discuss_sidebar.js");
const DiscussSidebarItem = require("mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js");
const {Component} = owl;
class BrokerDiscussSidebarItem extends DiscussSidebarItem {
get counter() {
if (this.thread.channel_type === "broker") {
return this.thread.broker_unread_counter;
}
return super.counter;
}
}
class DiscussSidebarBroker extends Component {
constructor(...args) {
super(...args);
this.mailBroker = this.env.models["mail.broker"].get(this.props.brokerId);
}
get quickSearchOrderedAndPinnedBrokerChannels() {
return this.env.models["mail.thread"]
.all((channel) => channel.broker_id === this.mailBroker.id)
.sort((c1, c2) => {
if (!c1.lastMessage && !c2.lastMessage) {
return c1.id < c2.id;
} else if (!c2.lastMessage) {
return -1;
} else if (!c1.lastMessage) {
return 1;
}
return c1.lastMessage.id < c2.lastMessage.id ? -1 : 1;
});
}
}
Object.assign(DiscussSidebarBroker, {
components: Object.assign(DiscussSidebar.components, {
DiscussSidebarItem: BrokerDiscussSidebarItem,
}),
props: {
brokerId: String,
},
template: "mail_broker.DiscussSidebarBroker",
});
class BrokerDiscussSidebar extends DiscussSidebar {
get mailBrokers() {
return this.env.models["mail.broker"].all();
}
}
Object.assign(BrokerDiscussSidebar, {
components: Object.assign(DiscussSidebar.components, {DiscussSidebarBroker}),
props: DiscussSidebar.props,
template: "mail_broker.DiscussSidebar",
});
class NewDiscuss extends Discuss {}
Object.assign(NewDiscuss, {
components: Object.assign({}, Discuss.components, {
DiscussSidebar: BrokerDiscussSidebar,
}),
props: Discuss.props,
template: Discuss.template,
});
return NewDiscuss;
});

View File

@ -1,18 +1,23 @@
odoo.define("mail_broker.model.Message", function (require) {
odoo.define("mail_broker/static/src/js/message.js", function (require) {
"use strict";
var Message = require("mail.model.Message");
const {registerClassPatchModel} = require("mail/static/src/model/model_core.js");
Message.include({
init: function (parent, data) {
this._super.apply(this, arguments);
this.broker_channel_id = data.broker_channel_id || false;
this.broker_unread = data.broker_unread || false;
this.broker_type = data.broker_type || false;
this.customer_status = data.customer_status || false;
registerClassPatchModel(
"mail.message",
"mail/static/src/models/message/message.js",
{
convertData(data) {
const data2 = this._super(data);
if ("broker_channel_id" in data) {
data2.broker_channel_id = data.broker_channel_id || false;
data2.broker_unread = data.broker_unread || false;
data2.broker_type = data.broker_type || false;
data2.customer_status = data.customer_status || false;
data2.isNeedaction = data2.isNeedaction || data.broker_unread;
}
return data2;
},
isNeedaction: function () {
return this._super.apply(this, arguments) || this.broker_unread;
},
});
}
);
});

View File

@ -65,7 +65,7 @@ odoo.define("mail_broker.BrokerThread", function (require) {
domain = [["id", "<", minMessageID]].concat(domain);
}
return this._rpc({
model: "mail.broker.channel",
model: "mail.channel",
method: "message_fetch",
args: [[this.resId], domain],
kwargs: this._getFetchMessagesKwargs(options),

View File

@ -1,5 +1,63 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="mail_broker.broker">
<div class="o_widget_Discuss" />
</t>
<t t-name="mail_broker.DiscussSidebarBroker" owl="1">
<div>
<div class="o_DiscussSidebar_groupHeader">
<div
class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupTitle o-clickable"
t-esc="mailBroker.name"
/>
<div class="o-autogrow" />
<div
class="o_DiscussSidebar_groupHeaderItem o_DiscussSidebar_groupHeaderItemAdd fa fa-plus"
t-on-click="_onClickChannelAdd"
title="Add or join a channel"
/>
</div>
<div class="o_DiscussSidebar_list">
<div class="o_DiscussSidebar_item o_DiscussSidebar_itemNew">
<AutocompleteInput
class="o_DiscussSidebar_itemNewInput"
customClass="'o_DiscussSidebar_newChannelAutocompleteSuggestions'"
isFocusOnMount="true"
isHtml="true"
placeholder="FIND_OR_CREATE_CHANNEL"
select="_onAddChannelAutocompleteSelect"
source="_onAddChannelAutocompleteSource"
/>
</div>
<t
t-foreach="quickSearchOrderedAndPinnedBrokerChannels"
t-as="brokerChannel"
t-key="brokerChannel.localId"
>
<DiscussSidebarItem
class="o_DiscussSidebar_item"
threadLocalId="brokerChannel.localId"
/>
</t>
</div>
</div>
</t>
<t t-name="mail_broker.DiscussSidebar" owl="1">
<div name="root" class="o_DiscussSidebar">
<div class="o_DiscussSidebar_group o_DiscussSidebar_groupChannel">
<div class="o_DiscussSidebar_list">
<t
t-foreach="mailBrokers"
t-as="mailBroker"
t-key="mailBroker.localId"
>
<DiscussSidebarBroker brokerId="mailBroker.localId" />
</t>
</div>
</div>
</div>
</t>
<!--
<t t-extend="mail.widget.Thread.Message">
<t t-jquery=".o_mail_info" t-operation="append">
<span
@ -44,5 +102,5 @@
<t t-jquery=".o_input" t-operation="attributes">
<attribute name="t-attf-data-bot">#{bot_key}</attribute>
</t>
</t>
</t>-->
</templates>

View File

@ -8,9 +8,16 @@
/>
<script
type="text/javascript"
src="/mail_broker/static/src/js/manager.js"
src="/mail_broker/static/src/js/discuss.js"
/><!--
<script
type="text/javascript"
src="/mail_broker/static/src/js/messaging_initializer.js"
/>-->
<script
type="text/javascript"
src="/mail_broker/static/src/js/broker_model.js"
/>
<script type="text/javascript" src="/mail_broker/static/src/js/thread.js" />
<script type="text/javascript" src="/mail_broker/static/src/js/broker.js" />
<link rel="stylesheet" href="/mail_broker/static/src/scss/broker.scss" />
</xpath>

View File

@ -2,72 +2,10 @@
<!-- Copyright 2020 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="mail_broker_channel_form_view">
<field name="name">mail.broker.channel.form</field>
<field name="model">mail.broker.channel</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<form>
<header />
<sheet>
<div class="oe_button_box" name="button_box" />
<group>
<field name="name" />
<field name="partner_id" />
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="mail_broker_channel_form_root_view">
<field name="name">mail.broker.channel.form</field>
<field name="model">mail.broker.channel</field>
<field name="inherit_id" ref="mail_broker.mail_broker_channel_form_view" />
<field name="groups_id" eval="[(6, 0, [ref('base.group_system')])]" />
<field name="arch" type="xml">
<field name="name" position="after">
<field name="token" />
<field name="broker_id" />
<field name="show_on_app" />
<field name="token" />
</field>
</field>
</record>
<record model="ir.ui.view" id="mail_broker_channel_search_view">
<field name="name">mail.broker.channel.search (in mail_broker)</field>
<field name="model">mail.broker.channel</field>
<field name="arch" type="xml">
<search>
<field name="name" />
</search>
</field>
</record>
<record model="ir.ui.view" id="mail_broker_channel_tree_view">
<field name="name">mail.broker.channel.tree (in mail_broker)</field>
<field name="model">mail.broker.channel</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="mail_broker_channel_act_window">
<field name="name">Mail Broker Channel</field>
<field name="res_model">mail.broker.channel</field>
<field name="view_mode">tree,form</field>
<field name="domain">[]</field>
<field name="context">{}</field>
</record>
<record model="ir.ui.menu" id="mail_broker_channel_menu">
<field name="name">Mail Broker Channel</field>
<field name="parent_id" ref="base.menu_email" />
<field name="action" ref="mail_broker_channel_act_window" />
<field name="sequence" eval="16" />
</record>
<record id="mail_broker_action_window" model="ir.actions.client">
<field name="name">Broker</field>
<field name="tag">mail.broker</field>
<field name="res_model">mail.broker.channel</field>
<field name="res_model">mail.channel</field>
<field name="params" eval="{}" />
</record>
<menuitem