mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 01:32:02 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
364
odoo-bringout-oca-ocb-mail/mail/static/src/models/partner.js
Normal file
364
odoo-bringout-oca-ocb-mail/mail/static/src/models/partner.js
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
/** @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',
|
||||
}),
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue