mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 19:31:59 +02:00
364 lines
14 KiB
JavaScript
364 lines
14 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { registerModel } from '@mail/model/model_core';
|
|
import { attr, many, one } from '@mail/model/model_field';
|
|
import { clear, insert } from '@mail/model/model_field_command';
|
|
import { cleanSearchTerm } from '@mail/utils/utils';
|
|
|
|
registerModel({
|
|
name: 'Partner',
|
|
modelMethods: {
|
|
/**
|
|
* Fetches partners matching the given search term to extend the
|
|
* JS knowledge and to update the suggestion list accordingly.
|
|
*
|
|
* @param {string} searchTerm
|
|
* @param {Object} [options={}]
|
|
* @param {Thread} [options.thread] prioritize and/or restrict
|
|
* result in the context of given thread
|
|
*/
|
|
async fetchSuggestions(searchTerm, { thread } = {}) {
|
|
const kwargs = { search: searchTerm };
|
|
const isNonPublicChannel = thread && thread.model === 'mail.channel' && (thread.authorizedGroupFullName || thread.channel.channel_type !== 'channel');
|
|
if (isNonPublicChannel) {
|
|
kwargs.channel_id = thread.id;
|
|
}
|
|
const suggestedPartners = await this.messaging.rpc(
|
|
{
|
|
model: 'res.partner',
|
|
method: 'get_mention_suggestions',
|
|
kwargs,
|
|
},
|
|
{ shadow: true },
|
|
);
|
|
this.messaging.models['Partner'].insert(suggestedPartners);
|
|
},
|
|
/**
|
|
* Returns a sort function to determine the order of display of partners
|
|
* in the suggestion list.
|
|
*
|
|
* @param {string} searchTerm
|
|
* @param {Object} [options={}]
|
|
* @param {Thread} [options.thread] prioritize result in the
|
|
* context of given thread
|
|
* @returns {function}
|
|
*/
|
|
getSuggestionSortFunction(searchTerm, { thread } = {}) {
|
|
const cleanedSearchTerm = cleanSearchTerm(searchTerm);
|
|
return (a, b) => {
|
|
const isAInternalUser = a.user && a.user.isInternalUser;
|
|
const isBInternalUser = b.user && b.user.isInternalUser;
|
|
if (isAInternalUser && !isBInternalUser) {
|
|
return -1;
|
|
}
|
|
if (!isAInternalUser && isBInternalUser) {
|
|
return 1;
|
|
}
|
|
if (thread && thread.channel) {
|
|
const isAMember = a.persona.channelMembers.includes(thread.channel);
|
|
const isBMember = b.persona.channelMembers.includes(thread.channel);
|
|
if (isAMember && !isBMember) {
|
|
return -1;
|
|
}
|
|
if (!isAMember && isBMember) {
|
|
return 1;
|
|
}
|
|
}
|
|
if (thread) {
|
|
const isAFollower = thread.followersPartner.includes(a);
|
|
const isBFollower = thread.followersPartner.includes(b);
|
|
if (isAFollower && !isBFollower) {
|
|
return -1;
|
|
}
|
|
if (!isAFollower && isBFollower) {
|
|
return 1;
|
|
}
|
|
}
|
|
const cleanedAName = cleanSearchTerm(a.name || '');
|
|
const cleanedBName = cleanSearchTerm(b.name || '');
|
|
if (cleanedAName.startsWith(cleanedSearchTerm) && !cleanedBName.startsWith(cleanedSearchTerm)) {
|
|
return -1;
|
|
}
|
|
if (!cleanedAName.startsWith(cleanedSearchTerm) && cleanedBName.startsWith(cleanedSearchTerm)) {
|
|
return 1;
|
|
}
|
|
if (cleanedAName < cleanedBName) {
|
|
return -1;
|
|
}
|
|
if (cleanedAName > cleanedBName) {
|
|
return 1;
|
|
}
|
|
const cleanedAEmail = cleanSearchTerm(a.email || '');
|
|
const cleanedBEmail = cleanSearchTerm(b.email || '');
|
|
if (cleanedAEmail.startsWith(cleanedSearchTerm) && !cleanedAEmail.startsWith(cleanedSearchTerm)) {
|
|
return -1;
|
|
}
|
|
if (!cleanedBEmail.startsWith(cleanedSearchTerm) && cleanedBEmail.startsWith(cleanedSearchTerm)) {
|
|
return 1;
|
|
}
|
|
if (cleanedAEmail < cleanedBEmail) {
|
|
return -1;
|
|
}
|
|
if (cleanedAEmail > cleanedBEmail) {
|
|
return 1;
|
|
}
|
|
return a.id - b.id;
|
|
};
|
|
},
|
|
/**
|
|
* Search for partners matching `keyword`.
|
|
*
|
|
* @param {Object} param0
|
|
* @param {function} param0.callback
|
|
* @param {string} param0.keyword
|
|
* @param {integer} [param0.limit=10]
|
|
*/
|
|
async imSearch({ callback, keyword, limit = 10 }) {
|
|
// prefetched partners
|
|
let partners = [];
|
|
const cleanedSearchTerm = cleanSearchTerm(keyword);
|
|
for (const partner of this.all(partner => partner.active)) {
|
|
if (partners.length < limit) {
|
|
if (
|
|
partner.name &&
|
|
partner.user &&
|
|
cleanSearchTerm(partner.name).includes(cleanedSearchTerm)
|
|
) {
|
|
partners.push(partner);
|
|
}
|
|
}
|
|
}
|
|
if (!partners.length) {
|
|
const partnersData = await this.messaging.rpc(
|
|
{
|
|
model: 'res.partner',
|
|
method: 'im_search',
|
|
args: [keyword, limit]
|
|
},
|
|
{ shadow: true }
|
|
);
|
|
const newPartners = this.insert(partnersData);
|
|
partners.push(...newPartners);
|
|
}
|
|
callback(partners);
|
|
},
|
|
/**
|
|
* Returns partners that match the given search term.
|
|
*
|
|
* @param {string} searchTerm
|
|
* @param {Object} [options={}]
|
|
* @param {Thread} [options.thread] prioritize and/or restrict
|
|
* result in the context of given thread
|
|
* @returns {[Partner[], Partner[]]}
|
|
*/
|
|
searchSuggestions(searchTerm, { thread } = {}) {
|
|
let partners;
|
|
const isNonPublicChannel = thread && thread.channel && (thread.authorizedGroupFullName || thread.channel.channel_type !== 'channel');
|
|
if (isNonPublicChannel) {
|
|
// Only return the channel members when in the context of a
|
|
// group restricted channel. Indeed, the message with the mention
|
|
// would be notified to the mentioned partner, so this prevents
|
|
// from inadvertently leaking the private message to the
|
|
// mentioned partner.
|
|
partners = thread.channel.channelMembers.filter(member => member.persona && member.persona.partner).map(member => member.persona.partner);
|
|
} else {
|
|
partners = this.messaging.models['Partner'].all();
|
|
}
|
|
const cleanedSearchTerm = cleanSearchTerm(searchTerm);
|
|
const mainSuggestionList = [];
|
|
const extraSuggestionList = [];
|
|
for (const partner of partners) {
|
|
if (
|
|
(!partner.active && partner !== this.messaging.partnerRoot) ||
|
|
partner.is_public
|
|
) {
|
|
// ignore archived partners (except OdooBot), public partners (technical)
|
|
continue;
|
|
}
|
|
if (!partner.name) {
|
|
continue;
|
|
}
|
|
if (
|
|
(cleanSearchTerm(partner.name).includes(cleanedSearchTerm)) ||
|
|
(partner.email && cleanSearchTerm(partner.email).includes(cleanedSearchTerm))
|
|
) {
|
|
if (partner.user) {
|
|
mainSuggestionList.push(partner);
|
|
} else {
|
|
extraSuggestionList.push(partner);
|
|
}
|
|
}
|
|
}
|
|
return [mainSuggestionList, extraSuggestionList];
|
|
},
|
|
},
|
|
recordMethods: {
|
|
/**
|
|
* Checks whether this partner has a related user and links them if
|
|
* applicable.
|
|
*/
|
|
async checkIsUser() {
|
|
const userIds = await this.messaging.rpc({
|
|
model: 'res.users',
|
|
method: 'search',
|
|
args: [[['partner_id', '=', this.id]]],
|
|
kwargs: {
|
|
context: { active_test: false },
|
|
},
|
|
}, { shadow: true });
|
|
if (!this.exists()) {
|
|
return;
|
|
}
|
|
this.update({ hasCheckedUser: true });
|
|
if (userIds.length > 0) {
|
|
this.update({ user: insert({ id: userIds[0] }) });
|
|
}
|
|
},
|
|
/**
|
|
* Gets the chat between the user of this partner and the current user.
|
|
*
|
|
* If a chat is not appropriate, a notification is displayed instead.
|
|
*
|
|
* @returns {Channel|undefined}
|
|
*/
|
|
async getChat() {
|
|
if (!this.user && !this.hasCheckedUser) {
|
|
await this.checkIsUser();
|
|
if (!this.exists()) {
|
|
return;
|
|
}
|
|
}
|
|
// prevent chatting with non-users
|
|
if (!this.user) {
|
|
this.messaging.notify({
|
|
message: this.env._t("You can only chat with partners that have a dedicated user."),
|
|
type: 'info',
|
|
});
|
|
return;
|
|
}
|
|
return this.user.getChat();
|
|
},
|
|
/**
|
|
* Opens a chat between the user of this partner and the current user
|
|
* and returns it.
|
|
*
|
|
* If a chat is not appropriate, a notification is displayed instead.
|
|
*
|
|
* @param {Object} [options] forwarded to @see `Thread:open()`
|
|
*/
|
|
async openChat(options) {
|
|
const chat = await this.getChat();
|
|
if (!this.exists() || !chat) {
|
|
return;
|
|
}
|
|
await chat.thread.open(options);
|
|
if (!this.exists()) {
|
|
return;
|
|
}
|
|
},
|
|
/**
|
|
* Opens the most appropriate view that is a profile for this partner.
|
|
*/
|
|
async openProfile() {
|
|
return this.messaging.openDocument({
|
|
id: this.id,
|
|
model: 'res.partner',
|
|
});
|
|
},
|
|
},
|
|
fields: {
|
|
active: attr({
|
|
default: true,
|
|
}),
|
|
avatarUrl: attr({
|
|
compute() {
|
|
return `/web/image/res.partner/${this.id}/avatar_128`;
|
|
},
|
|
}),
|
|
channelInvitationFormSelectablePartnerViews: many('ChannelInvitationFormSelectablePartnerView', {
|
|
inverse: 'partner',
|
|
}),
|
|
channelInvitationFormSelectedPartnerViews: many('ChannelInvitationFormSelectedPartnerView', {
|
|
inverse: 'partner',
|
|
}),
|
|
country: one('Country'),
|
|
/**
|
|
* Deprecated.
|
|
* States the `display_name` of this partner, as returned by the server.
|
|
* The value of this field is unreliable (notably its value depends on
|
|
* context on which it was received) therefore it should only be used as
|
|
* a default if the actual `name` is missing (@see `nameOrDisplayName`).
|
|
* And if a specific name format is required, it should be computed from
|
|
* relevant fields instead.
|
|
*/
|
|
display_name: attr(),
|
|
displayName: attr({
|
|
compute() {
|
|
if (this.display_name) {
|
|
return this.display_name;
|
|
}
|
|
if (this.user && this.user.displayName) {
|
|
return this.user.displayName;
|
|
}
|
|
return clear();
|
|
},
|
|
default: "",
|
|
}),
|
|
dmChatWithCurrentPartner: one('Channel', {
|
|
inverse: 'correspondentOfDmChat',
|
|
}),
|
|
email: attr(),
|
|
/**
|
|
* Whether an attempt was already made to fetch the user corresponding
|
|
* to this partner. This prevents doing the same RPC multiple times.
|
|
*/
|
|
hasCheckedUser: attr({
|
|
default: false,
|
|
}),
|
|
id: attr({
|
|
identifying: true,
|
|
}),
|
|
im_status: attr(),
|
|
isImStatusSet: attr({
|
|
compute() {
|
|
return Boolean(this.im_status && this.im_status !== 'im_partner');
|
|
},
|
|
}),
|
|
/**
|
|
* States whether this partner is online.
|
|
*/
|
|
isOnline: attr({
|
|
compute() {
|
|
return ['online', 'away'].includes(this.im_status);
|
|
},
|
|
}),
|
|
is_public: attr(),
|
|
model: attr({
|
|
default: 'res.partner',
|
|
}),
|
|
name: attr(),
|
|
nameOrDisplayName: attr({
|
|
compute() {
|
|
return this.name || this.displayName;
|
|
},
|
|
}),
|
|
persona: one('Persona', {
|
|
default: {},
|
|
inverse: 'partner',
|
|
readonly: true,
|
|
required: true,
|
|
}),
|
|
suggestable: one('ComposerSuggestable', {
|
|
default: {},
|
|
inverse: 'partner',
|
|
readonly: true,
|
|
required: true,
|
|
}),
|
|
user: one('User', {
|
|
inverse: 'partner',
|
|
}),
|
|
volumeSetting: one('res.users.settings.volumes', {
|
|
inverse: 'partner_id',
|
|
}),
|
|
},
|
|
});
|