406 lines
16 KiB
JavaScript
406 lines
16 KiB
JavaScript
|
/* @odoo-module */
|
||
|
|
||
|
import { patch } from "@web/core/utils/patch";
|
||
|
import { MockServer } from "@web/../tests/helpers/mock_server";
|
||
|
|
||
|
patch(MockServer.prototype, {
|
||
|
async _performRPC(route, args) {
|
||
|
if (args.model === "res.partner" && args.method === "im_search") {
|
||
|
const name = args.args[0] || args.kwargs.search;
|
||
|
const limit = args.args[1] || args.kwargs.limit;
|
||
|
const excluded_ids = args.args[2] || args.kwargs.excluded_ids;
|
||
|
return this._mockResPartnerImSearch(name, limit, excluded_ids);
|
||
|
}
|
||
|
if (args.model === "res.partner" && args.method === "search_for_channel_invite") {
|
||
|
const search_term = args.args[0] || args.kwargs.search_term;
|
||
|
const channel_id = args.args[1] || args.kwargs.channel_id;
|
||
|
const limit = args.args[2] || args.kwargs.limit;
|
||
|
return this._mockResPartnerSearchForChannelInvite(search_term, channel_id, limit);
|
||
|
}
|
||
|
if (args.model === "res.partner" && args.method === "get_mention_suggestions") {
|
||
|
return this._mockResPartnerGetMentionSuggestions(args);
|
||
|
}
|
||
|
if (
|
||
|
args.model === "res.partner" &&
|
||
|
args.method === "get_mention_suggestions_from_channel"
|
||
|
) {
|
||
|
return this._mockResPartnerGetMentionSuggestionsFromChannel(args);
|
||
|
}
|
||
|
return super._performRPC(route, args);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Simulates `get_mention_suggestions` on `res.partner`.
|
||
|
*
|
||
|
* @private
|
||
|
* @returns {Array[]}
|
||
|
*/
|
||
|
_mockResPartnerGetMentionSuggestions(args) {
|
||
|
const search = (args.args[0] || args.kwargs.search || "").toLowerCase();
|
||
|
const limit = args.args[1] || args.kwargs.limit || 8;
|
||
|
/**
|
||
|
* Returns the given list of partners after filtering it according to
|
||
|
* the logic of the Python method `get_mention_suggestions` for the
|
||
|
* given search term. The result is truncated to the given limit and
|
||
|
* formatted as expected by the original method.
|
||
|
*
|
||
|
* @param {Object[]} partners
|
||
|
* @param {string} search
|
||
|
* @param {integer} limit
|
||
|
* @returns {Object[]}
|
||
|
*/
|
||
|
const mentionSuggestionsFilter = (partners, search, limit) => {
|
||
|
const matchingPartners = [
|
||
|
...this._mockResPartnerMailPartnerFormat(
|
||
|
partners
|
||
|
.filter((partner) => {
|
||
|
// no search term is considered as return all
|
||
|
if (!search) {
|
||
|
return true;
|
||
|
}
|
||
|
// otherwise name or email must match search term
|
||
|
if (partner.name && partner.name.toLowerCase().includes(search)) {
|
||
|
return true;
|
||
|
}
|
||
|
if (partner.email && partner.email.toLowerCase().includes(search)) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
})
|
||
|
.map((partner) => partner.id)
|
||
|
).values(),
|
||
|
];
|
||
|
// reduce results to max limit
|
||
|
matchingPartners.length = Math.min(matchingPartners.length, limit);
|
||
|
return matchingPartners;
|
||
|
};
|
||
|
|
||
|
// add main suggestions based on users
|
||
|
const partnersFromUsers = this.getRecords("res.users", [])
|
||
|
.map((user) => this.getRecords("res.partner", [["id", "=", user.partner_id]])[0])
|
||
|
.filter((partner) => partner);
|
||
|
const mainMatchingPartners = mentionSuggestionsFilter(partnersFromUsers, search, limit);
|
||
|
|
||
|
let extraMatchingPartners = [];
|
||
|
// if not enough results add extra suggestions based on partners
|
||
|
const remainingLimit = limit - mainMatchingPartners.length;
|
||
|
if (mainMatchingPartners.length < limit) {
|
||
|
const partners = this.getRecords("res.partner", [
|
||
|
["id", "not in", mainMatchingPartners.map((partner) => partner.id)],
|
||
|
]);
|
||
|
extraMatchingPartners = mentionSuggestionsFilter(partners, search, remainingLimit);
|
||
|
}
|
||
|
return mainMatchingPartners.concat(extraMatchingPartners);
|
||
|
},
|
||
|
/**
|
||
|
* Simulates `get_channel_mention_suggestions` on `res.partner`.
|
||
|
*
|
||
|
* @private
|
||
|
* @returns {Array[]}
|
||
|
*/
|
||
|
_mockResPartnerGetMentionSuggestionsFromChannel(args) {
|
||
|
const search = (args.args[0] || args.kwargs.search || "").toLowerCase();
|
||
|
const limit = args.args[1] || args.kwargs.limit || 8;
|
||
|
const channel_id = args.args[2] || args.kwargs.channel_id;
|
||
|
|
||
|
/**
|
||
|
* Returns the given list of partners after filtering it according to
|
||
|
* the logic of the Python method `get_mention_suggestions` for the
|
||
|
* given search term. The result is truncated to the given limit and
|
||
|
* formatted as expected by the original method.
|
||
|
*
|
||
|
* @param {Object[]} partners
|
||
|
* @param {string} search
|
||
|
* @param {integer} limit
|
||
|
* @returns {Object[]}
|
||
|
*/
|
||
|
const mentionSuggestionsFilter = (partners, search, limit) => {
|
||
|
const matchingPartners = [
|
||
|
...this._mockResPartnerMailPartnerFormat(
|
||
|
partners
|
||
|
.filter((partner) => {
|
||
|
const [member] = this.getRecords("discuss.channel.member", [
|
||
|
["channel_id", "=", channel_id],
|
||
|
["partner_id", "=", partner.id],
|
||
|
]);
|
||
|
if (!member) {
|
||
|
return false;
|
||
|
}
|
||
|
// no search term is considered as return all
|
||
|
if (!search) {
|
||
|
return true;
|
||
|
}
|
||
|
// otherwise name or email must match search term
|
||
|
if (partner.name && partner.name.toLowerCase().includes(search)) {
|
||
|
return true;
|
||
|
}
|
||
|
if (partner.email && partner.email.toLowerCase().includes(search)) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
})
|
||
|
.map((partner) => partner.id)
|
||
|
).values(),
|
||
|
].map((partnerFormat) => {
|
||
|
const [member] = this.getRecords("discuss.channel.member", [
|
||
|
["channel_id", "=", channel_id],
|
||
|
["partner_id", "=", partnerFormat.id],
|
||
|
]);
|
||
|
partnerFormat["channelMembers"] = [
|
||
|
[
|
||
|
"ADD",
|
||
|
this._mockDiscussChannelMember_DiscussChannelMemberFormat([member.id])[0],
|
||
|
],
|
||
|
];
|
||
|
return partnerFormat;
|
||
|
});
|
||
|
// reduce results to max limit
|
||
|
matchingPartners.length = Math.min(matchingPartners.length, limit);
|
||
|
return matchingPartners;
|
||
|
};
|
||
|
|
||
|
// add main suggestions based on users
|
||
|
const partnersFromUsers = this.getRecords("res.users", [])
|
||
|
.map((user) => this.getRecords("res.partner", [["id", "=", user.partner_id]])[0])
|
||
|
.filter((partner) => partner);
|
||
|
const mainMatchingPartners = mentionSuggestionsFilter(partnersFromUsers, search, limit);
|
||
|
|
||
|
let extraMatchingPartners = [];
|
||
|
// if not enough results add extra suggestions based on partners
|
||
|
const remainingLimit = limit - mainMatchingPartners.length;
|
||
|
if (mainMatchingPartners.length < limit) {
|
||
|
const partners = this.getRecords("res.partner", [
|
||
|
["id", "not in", mainMatchingPartners.map((partner) => partner.id)],
|
||
|
]);
|
||
|
extraMatchingPartners = mentionSuggestionsFilter(partners, search, remainingLimit);
|
||
|
}
|
||
|
return mainMatchingPartners.concat(extraMatchingPartners);
|
||
|
},
|
||
|
/**
|
||
|
* Simulates `_get_needaction_count` on `res.partner`.
|
||
|
*
|
||
|
* @private
|
||
|
* @param {integer} id
|
||
|
* @returns {integer}
|
||
|
*/
|
||
|
_mockResPartner_GetNeedactionCount(id) {
|
||
|
const partner = this.getRecords("res.partner", [["id", "=", id]])[0];
|
||
|
return this.getRecords("mail.notification", [
|
||
|
["res_partner_id", "=", partner.id],
|
||
|
["is_read", "=", false],
|
||
|
]).length;
|
||
|
},
|
||
|
/**
|
||
|
* Simulates `im_search` on `res.partner`.
|
||
|
*
|
||
|
* @private
|
||
|
* @param {string} [name='']
|
||
|
* @param {integer} [limit=20]
|
||
|
* @returns {Object[]}
|
||
|
*/
|
||
|
_mockResPartnerImSearch(name = "", limit = 20, excluded_ids = []) {
|
||
|
name = name.toLowerCase(); // simulates ILIKE
|
||
|
// simulates domain with relational parts (not supported by mock server)
|
||
|
const matchingPartners = this.getRecords("res.users", [])
|
||
|
.filter((user) => {
|
||
|
const partner = this.getRecords("res.partner", [["id", "=", user.partner_id]])[0];
|
||
|
// user must have a partner
|
||
|
if (!partner) {
|
||
|
return false;
|
||
|
}
|
||
|
// not current partner
|
||
|
if (partner.id === this.pyEnv.currentPartnerId) {
|
||
|
return false;
|
||
|
}
|
||
|
// no name is considered as return all
|
||
|
if (!name) {
|
||
|
return true;
|
||
|
}
|
||
|
if (partner.name && partner.name.toLowerCase().includes(name)) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
})
|
||
|
.map((user) => {
|
||
|
const partner = this.getRecords("res.partner", [["id", "=", user.partner_id]])[0];
|
||
|
return {
|
||
|
id: partner.id,
|
||
|
name: partner.name,
|
||
|
};
|
||
|
})
|
||
|
.sort((a, b) => (a.name === b.name ? a.id - b.id : a.name > b.name ? 1 : -1));
|
||
|
matchingPartners.length = Math.min(matchingPartners.length, limit);
|
||
|
const resultPartners = matchingPartners.filter(
|
||
|
(partner) => !excluded_ids.includes(partner.id)
|
||
|
);
|
||
|
return [
|
||
|
...this._mockResPartnerMailPartnerFormat(
|
||
|
resultPartners.map((partner) => partner.id)
|
||
|
).values(),
|
||
|
];
|
||
|
},
|
||
|
/**
|
||
|
* Simulates `mail_partner_format` on `res.partner`.
|
||
|
*
|
||
|
* @private
|
||
|
* @returns {integer[]} ids
|
||
|
* @returns {Map}
|
||
|
*/
|
||
|
_mockResPartnerMailPartnerFormat(ids) {
|
||
|
const partners = this.getRecords("res.partner", [["id", "in", ids]], {
|
||
|
active_test: false,
|
||
|
});
|
||
|
// Servers is also returning `is_internal_user` but not
|
||
|
// done here for simplification.
|
||
|
return new Map(
|
||
|
partners.map((partner) => {
|
||
|
const users = this.getRecords("res.users", [["id", "in", partner.user_ids]]);
|
||
|
const internalUsers = users.filter((user) => !user.share);
|
||
|
let mainUser;
|
||
|
if (internalUsers.length > 0) {
|
||
|
mainUser = internalUsers[0];
|
||
|
} else if (users.length > 0) {
|
||
|
mainUser = users[0];
|
||
|
}
|
||
|
return [
|
||
|
partner.id,
|
||
|
{
|
||
|
active: partner.active,
|
||
|
email: partner.email,
|
||
|
id: partner.id,
|
||
|
im_status: partner.im_status,
|
||
|
is_company: partner.is_company,
|
||
|
name: partner.name,
|
||
|
type: "partner",
|
||
|
user: mainUser
|
||
|
? {
|
||
|
id: mainUser.id,
|
||
|
isInternalUser: !mainUser.share,
|
||
|
}
|
||
|
: false,
|
||
|
},
|
||
|
];
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
/**
|
||
|
* Simulates `search_for_channel_invite` on `res.partner`.
|
||
|
*
|
||
|
* @private
|
||
|
* @param {string} [search_term='']
|
||
|
* @param {integer} [channel_id]
|
||
|
* @param {integer} [limit=30]
|
||
|
* @returns {Object[]}
|
||
|
*/
|
||
|
_mockResPartnerSearchForChannelInvite(search_term, channel_id, limit = 30) {
|
||
|
if (search_term) {
|
||
|
search_term = search_term.toLowerCase(); // simulates ILIKE
|
||
|
}
|
||
|
const memberPartnerIds = new Set(
|
||
|
this.getRecords("discuss.channel.member", [["channel_id", "=", channel_id]]).map(
|
||
|
(member) => member.partner_id
|
||
|
)
|
||
|
);
|
||
|
// simulates domain with relational parts (not supported by mock server)
|
||
|
const matchingPartners = [
|
||
|
...this._mockResPartnerMailPartnerFormat(
|
||
|
this.getRecords("res.users", [])
|
||
|
.filter((user) => {
|
||
|
const partner = this.getRecords("res.partner", [
|
||
|
["id", "=", user.partner_id],
|
||
|
])[0];
|
||
|
// user must have a partner
|
||
|
if (!partner) {
|
||
|
return false;
|
||
|
}
|
||
|
// user should not already be a member of the channel
|
||
|
if (memberPartnerIds.has(partner.id)) {
|
||
|
return false;
|
||
|
}
|
||
|
// no name is considered as return all
|
||
|
if (!search_term) {
|
||
|
return true;
|
||
|
}
|
||
|
if (partner.name && partner.name.toLowerCase().includes(search_term)) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
})
|
||
|
.map((user) => user.partner_id)
|
||
|
).values(),
|
||
|
];
|
||
|
const count = matchingPartners.length;
|
||
|
matchingPartners.length = Math.min(count, limit);
|
||
|
return {
|
||
|
count,
|
||
|
partners: matchingPartners,
|
||
|
};
|
||
|
},
|
||
|
/**
|
||
|
* Simulates `_message_fetch_failed` on `res.partner`.
|
||
|
*
|
||
|
* @private
|
||
|
* @param {integer} id
|
||
|
* @returns {Object[]}
|
||
|
*/
|
||
|
_mockResPartner_MessageFetchFailed(id) {
|
||
|
const partner = this.getRecords("res.partner", [["id", "=", id]], {
|
||
|
active_test: false,
|
||
|
})[0];
|
||
|
const messages = this.getRecords("mail.message", [
|
||
|
["author_id", "=", partner.id],
|
||
|
["res_id", "!=", 0],
|
||
|
["model", "!=", false],
|
||
|
["message_type", "!=", "user_notification"],
|
||
|
]).filter((message) => {
|
||
|
// Purpose is to simulate the following domain on mail.message:
|
||
|
// ['notification_ids.notification_status', 'in', ['bounce', 'exception']],
|
||
|
// But it's not supported by getRecords domain to follow a relation.
|
||
|
const notifications = this.getRecords("mail.notification", [
|
||
|
["mail_message_id", "=", message.id],
|
||
|
["notification_status", "in", ["bounce", "exception"]],
|
||
|
]);
|
||
|
return notifications.length > 0;
|
||
|
});
|
||
|
return this._mockMailMessage_MessageNotificationFormat(
|
||
|
messages.map((message) => message.id)
|
||
|
);
|
||
|
},
|
||
|
/**
|
||
|
* Simulates `_get_channels_as_member` on `res.partner`.
|
||
|
*
|
||
|
* @private
|
||
|
* @param {integer[]} ids
|
||
|
* @returns {Object}
|
||
|
*/
|
||
|
_mockResPartner_GetChannelsAsMember(ids) {
|
||
|
const partner = this.getRecords("res.partner", [["id", "in", ids]])[0];
|
||
|
const channelMembers = this.getRecords("discuss.channel.member", [
|
||
|
["partner_id", "=", partner.id],
|
||
|
]);
|
||
|
const channels = this.getRecords("discuss.channel", [
|
||
|
["channel_type", "in", ["channel", "group"]],
|
||
|
["channel_member_ids", "in", channelMembers.map((member) => member.id)],
|
||
|
]);
|
||
|
const directMessagesMembers = this.getRecords("discuss.channel.member", [
|
||
|
["partner_id", "=", partner.id],
|
||
|
["is_pinned", "=", true],
|
||
|
]);
|
||
|
const directMessages = this.getRecords("discuss.channel", [
|
||
|
["channel_type", "=", "chat"],
|
||
|
["channel_member_ids", "in", directMessagesMembers.map((member) => member.id)],
|
||
|
]);
|
||
|
return [...channels, ...directMessages];
|
||
|
},
|
||
|
/**
|
||
|
* Simulates `_get_current_persona` on `res.partner`.
|
||
|
*
|
||
|
*/
|
||
|
_mockResPartner__getCurrentPersona() {
|
||
|
if (this.pyEnv.currentUser?._is_public()) {
|
||
|
return [null, this._mockMailGuest__getGuestFromContext()];
|
||
|
}
|
||
|
return [this.pyEnv.currentPartner, null];
|
||
|
},
|
||
|
});
|