mail/static/tests/helpers/mock_server/models/mail_thread.js
Данил Воробьев 6e6f15d803 initial commit
2024-05-03 09:40:35 +00:00

505 lines
19 KiB
JavaScript

/* @odoo-module */
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
import { parseEmail } from "@mail/js/utils";
import { serializeDateTime, today } from "@web/core/l10n/dates";
patch(MockServer.prototype, {
async _performRPC(route, args) {
if (args.method === "message_subscribe") {
const ids = args.args[0];
const partner_ids = args.args[1] || args.kwargs.partner_ids;
const subtype_ids = args.args[2] || args.kwargs.subtype_ids;
return this._mockMailThreadMessageSubscribe(args.model, ids, partner_ids, subtype_ids);
}
if (args.method === "message_unsubscribe") {
const ids = args.args[0];
const partner_ids = args.args[1] || args.kwargs.partner_ids;
return this._mockMailThreadMessageUnsubscribe(args.model, ids, partner_ids);
}
if (args.method === "message_get_followers") {
const ids = args.args[0];
const after = args.args[1] || args.kwargs.after;
const limit = args.args[2] || args.kwargs.limit;
return this._mockMailThreadMessageGetFollowers(args.model, ids, after, limit);
}
if (args.method === "message_post") {
const id = args.args[0];
const kwargs = args.kwargs;
const context = kwargs.context;
delete kwargs.context;
return this._mockMailThreadMessagePost(args.model, [id], kwargs, context);
}
if (args.method === "notify_cancel_by_type") {
return this._mockMailThreadNotifyCancelByType(
args.model,
args.kwargs.notification_type
);
}
return super._performRPC(route, args);
},
/**
* Simulates `_message_compute_author` on `mail.thread`.
*
* @private
* @param {string} model
* @param {integer[]} ids
* @param {Object} [context={}]
* @returns {Array}
*/
_MockMailThread_MessageComputeAuthor(model, ids, author_id, email_from, context = {}) {
if (author_id === undefined) {
// For simplicity partner is not guessed from email_from here, but
// that would be the first step on the server.
const author = this.getRecords(
"res.partner",
[["id", "=", this.pyEnv.currentUser.partner_id]],
{
active_test: false,
}
)[0];
author_id = author.id;
email_from = `${author.display_name} <${author.email}>`;
}
if (email_from === undefined) {
if (author_id) {
const author = this.getRecords("res.partner", [["id", "=", author_id]], {
active_test: false,
})[0];
email_from = `${author.display_name} <${author.email}>`;
}
}
if (!email_from) {
throw Error("Unable to log message due to missing author email.");
}
return [author_id, email_from];
},
/**
* @param {string} model
* @param {integer[]} ids
*
* @returns {Map<integer:string>}
* Simulate `_message_compute_subject` on `mail.thread`
*/
mockMailThread_MessageComputeSubject(model, ids) {
const records = this.getRecords(model, [["id", "in", ids]]);
return new Map(records.map((record) => [record.id, record.name || ""]));
},
/**
* Simulates `_get_customer_information` on `mail.thread`.
*
* @private
* @param {string} model
* @param {integer[]} ids
* @returns {Object}
*/
_mockMailThread_GetCustomerInformation(model, ids) {
return {};
},
/**
* Simulates `_message_add_suggested_recipient` on `mail.thread`.
*
* @private
* @param {string} model
* @param {integer[]} ids
* @param {Object} result
* @param {Object} [param3={}]
* @param {string} [param3.email]
* @param {integer} [param3.partner]
* @param {string} [param3.reason]
* @returns {Object}
*/
_mockMailThread_MessageAddSuggestedRecipient(
model,
ids,
result,
{ email, partner, lang, reason = "" } = {}
) {
const record = this.getRecords(model, [["id", "in", ids]])[0];
if (email !== undefined && partner === undefined) {
const partnerInfo = parseEmail(email);
partner = this.getRecords("res.partner", [["email", "=", partnerInfo[1]]])[0];
}
if (partner) {
result[record.id].push([partner.id, partner.display_name, lang, reason, {}]);
} else {
const partnerCreateValues = this._mockMailThread_GetCustomerInformation(model, ids);
result[record.id].push([false, email, reason, lang, partnerCreateValues]);
}
return result;
},
/**
* Simulates `message_get_followers` on `mail.thread`.
*
* @private
* @param {string} model
* @param {integer[]} ids
* @param {integer} [after]
* @param {integer} [limit=100]
* @returns {Object[]}
*/
_mockMailThreadMessageGetFollowers(model, ids, after, limit = 100, kwargs = {}) {
const domain = [
["res_id", "=", ids[0]],
["res_model", "=", model],
];
if (after) {
domain.push(["id", ">", after]);
}
if (kwargs.filter_recipients) {
domain.push(["partner_id", "!=", this.pyEnv.currentPartnerId]);
}
const followers = this.getRecords("mail.followers", domain).sort(
(f1, f2) => (f1.id < f2.id ? -1 : 1) // sorted from lowest ID to highest ID (i.e. from oldest to youngest)
);
followers.length = Math.min(followers.length, limit);
return this._mockMailFollowers_FormatForChatter(followers.map((follower) => follower.id));
},
/**
* Simulates `_message_get_suggested_recipients` on `mail.thread`.
*
* @private
* @param {string} model
* @param {integer[]} ids
* @returns {Object}
*/
_mockMailThread_MessageGetSuggestedRecipients(model, ids) {
if (model === "res.fake") {
return this._mockResFake_MessageGetSuggestedRecipients(model, ids);
}
const result = ids.reduce((result, id) => (result[id] = []), {});
const records = this.getRecords(model, [["id", "in", ids]]);
for (const record in records) {
if (record.user_id) {
const user = this.getRecords("res.users", [["id", "=", record.user_id]]);
if (user.partner_id) {
const reason = this.models[model].fields["user_id"].string;
const partner = this.getRecords("res.partner", [["id", "=", user.partner_id]]);
this._mockMailThread_MessageAddSuggestedRecipient(model, ids, result, {
email: partner.email,
partner,
reason,
});
}
}
}
return result;
},
/**
* Simulates `message_post` on `mail.thread`.
*
* @private
* @param {string} model
* @param {integer[]} ids
* @param {Object} kwargs
* @param {Object} [context]
* @returns {Object}
*/
_mockMailThreadMessagePost(model, ids, kwargs, context) {
const id = ids[0]; // ensure_one
if (kwargs.partner_emails) {
kwargs.partner_ids = kwargs.partner_ids || [];
for (const email of kwargs.partner_emails) {
const partner = this.getRecords("res.partner", [["email", "=", email]]);
if (partner.length !== 0) {
kwargs.partner_ids.push(partner[0].id);
} else {
const partner_id = this.pyEnv["res.partner"].create(
Object.assign({ email }, kwargs.partner_additional_values[email] || {})
);
kwargs.partner_ids.push(partner_id);
}
}
}
delete kwargs.partner_emails;
delete kwargs.partner_additional_values;
if (context?.["mail_post_autofollow"] && kwargs["partner_ids"].length > 0) {
this._mockMailThreadMessageSubscribe(model, ids, kwargs["partner_ids"]);
}
if (kwargs.attachment_ids) {
const attachments = this.getRecords("ir.attachment", [
["id", "in", kwargs.attachment_ids],
["res_model", "=", "mail.compose.message"],
["res_id", "=", 0],
]);
const attachmentIds = attachments.map((attachment) => attachment.id);
this.pyEnv["ir.attachment"].write(attachmentIds, {
res_id: id,
res_model: model,
});
kwargs.attachment_ids = attachmentIds.map((attachmentId) => [4, attachmentId]);
}
const subtype_xmlid = kwargs.subtype_xmlid || "mail.mt_note";
let author_id;
let email_from;
const author_guest_id = this.pyEnv.currentUser?._is_public()
? this._mockMailGuest__getGuestFromContext()?.id
: undefined;
if (!author_guest_id) {
[author_id, email_from] = this._MockMailThread_MessageComputeAuthor(
model,
ids,
kwargs.author_id,
kwargs.email_from,
context
);
}
const values = Object.assign({}, kwargs, {
author_id,
author_guest_id,
email_from,
is_discussion: subtype_xmlid === "mail.mt_comment",
is_note: subtype_xmlid === "mail.mt_note",
model,
res_id: id,
});
delete values.subtype_xmlid;
const messageId = this.pyEnv["mail.message"].create(values);
for (const partnerId of kwargs.partner_ids || []) {
this.pyEnv["mail.notification"].create({
mail_message_id: messageId,
notification_type: "inbox",
res_partner_id: partnerId,
});
}
this._mockMailThread_NotifyThread(model, ids, messageId, context?.temporary_id);
return Object.assign(this._mockMailMessageMessageFormat([messageId])[0], {
temporary_id: context?.temporary_id,
});
},
/**
* Simulates `message_subscribe` on `mail.thread`.
*
* @private
* @param {string} model not in server method but necessary for thread mock
* @param {integer[]} ids
* @param {integer[]} partner_ids
* @param {integer[]} [subtype_ids]
* @returns {boolean}
*/
_mockMailThreadMessageSubscribe(model, ids, partner_ids, subtype_ids) {
for (const id of ids) {
for (const partner_id of partner_ids) {
let followerId = this.pyEnv["mail.followers"].search([
["partner_id", "=", partner_id],
])[0];
if (!followerId) {
if (!subtype_ids || subtype_ids.length === 0) {
subtype_ids = this.pyEnv["mail.message.subtype"].search([
["default", "=", true],
"|",
["res_model", "=", model],
["res_model", "=", false],
]);
}
followerId = this.pyEnv["mail.followers"].create({
is_active: true,
partner_id,
res_id: id,
res_model: model,
subtype_ids: subtype_ids,
});
}
this.pyEnv[model].write(ids, {
message_follower_ids: [[4, followerId]],
});
this.pyEnv["res.partner"].write([partner_id], {
message_follower_ids: [[4, followerId]],
});
}
}
},
/**
* Simulates `_notify_thread` on `mail.thread`.
* Simplified version that sends notification to author and channel.
*
* @private
* @param {string} model not in server method but necessary for thread mock
* @param {integer[]} ids
* @param {integer} messageId
* @returns {boolean}
*/
_mockMailThread_NotifyThread(model, ids, messageId, temporary_id) {
const message = this.getRecords("mail.message", [["id", "=", messageId]])[0];
const messageFormat = this._mockMailMessageMessageFormat([messageId])[0];
const notifications = [];
if (model === "discuss.channel") {
const channels = this.getRecords("discuss.channel", [["id", "=", message.res_id]]);
for (const channel of channels) {
const now = serializeDateTime(today());
notifications.push([
[channel, "members"],
"mail.record/insert",
{
Thread: {
id: channel.id,
is_pinned: true,
model: "discuss.channel",
},
},
]);
notifications.push([
channel,
"discuss.channel/last_interest_dt_changed",
{
id: channel.id,
last_interest_dt: now,
},
]);
notifications.push([
channel,
"discuss.channel/new_message",
{
id: channel.id,
message: Object.assign(messageFormat, { temporary_id }),
},
]);
if (message.author_id === this.pyEnv.currentPartnerId) {
this._mockDiscussChannel_ChannelSeen(ids, message.id);
}
}
}
this.pyEnv["bus.bus"]._sendmany(notifications);
},
/**
* Simulates `message_unsubscribe` on `mail.thread`.
*
* @private
* @param {string} model not in server method but necessary for thread mock
* @param {integer[]} ids
* @param {integer[]} partner_ids
* @returns {boolean|undefined}
*/
_mockMailThreadMessageUnsubscribe(model, ids, partner_ids) {
if (!partner_ids) {
return true;
}
const followers = this.getRecords("mail.followers", [
["res_model", "=", model],
["res_id", "in", ids],
["partner_id", "in", partner_ids || []],
]);
this.pyEnv["mail.followers"].unlink(followers.map((follower) => follower.id));
},
/**
* Simulates `_message_track` on `mail.thread`
*/
_mockMailThread_MessageTrack(
modelName,
trackedFieldNames,
initialTrackedFieldValuesByRecordId
) {
const trackFieldNamesToField = this.mockFieldsGet(modelName, trackedFieldNames);
const tracking = {};
const records = this.models[modelName].records;
for (const record of records) {
tracking[record.id] = this._mockMailBaseModel__MailTrack(
modelName,
trackFieldNamesToField,
initialTrackedFieldValuesByRecordId[record.id],
record
);
}
for (const record of records) {
const { trackingValueIds, changedFieldNames } = tracking[record.id] || {};
if (!changedFieldNames || !changedFieldNames.length) {
continue;
}
const changedFieldsInitialValues = {};
const initialFieldValues = initialTrackedFieldValuesByRecordId[record.id];
for (const fname in changedFieldNames) {
changedFieldsInitialValues[fname] = initialFieldValues[fname];
}
const subtype = this._mockMailThread_TrackSubtype(changedFieldsInitialValues);
this._mockMailThreadMessagePost(modelName, [record.id], {
subtype_id: subtype.id,
tracking_value_ids: trackingValueIds,
});
}
return tracking;
},
/**
* Simulates `_track_finalize` on `mail.thread`
*/
_mockMailThread_TrackFinalize(model, initialTrackedFieldValuesByRecordId) {
this._mockMailThread_MessageTrack(
model,
this._mockMailThread_TrackGetFields(model),
initialTrackedFieldValuesByRecordId
);
},
/**
* Simulates `_track_get_fields` on `mail.thread`
*/
_mockMailThread_TrackGetFields(model) {
return Object.entries(this.models[model].fields).reduce((prev, next) => {
if (next[1].tracking) {
prev.push(next[0]);
}
return prev;
}, []);
},
/**
* Simulates `_track_prepare` on `mail.thread`
*/
_mockMailThread_TrackPrepare(model) {
const trackedFieldNames = this._mockMailThread_TrackGetFields(model);
if (!trackedFieldNames.length) {
return;
}
const initialTrackedFieldValuesByRecordId = {};
for (const record of this.models[model].records) {
const values = {};
initialTrackedFieldValuesByRecordId[record.id] = values;
for (const fname of trackedFieldNames) {
values[fname] = record[fname];
}
}
return initialTrackedFieldValuesByRecordId;
},
/**
* Simulates `_track_subtype` on `mail.thread`
*/
_mockMailThread_TrackSubtype(initialFieldValuesByRecordId) {
return false;
},
/**
* Simulate the `notify_cancel_by_type` on `mail.thread` .
* Note that this method is overridden by snailmail module but not simulated here.
*/
_mockMailThreadNotifyCancelByType(model, notificationType) {
// Query matching notifications
const notifications = this.getRecords("mail.notification", [
["notification_type", "=", notificationType],
["notification_status", "in", ["bounce", "exception"]],
]).filter((notification) => {
const message = this.getRecords("mail.message", [
["id", "=", notification.mail_message_id],
])[0];
return message.model === model && message.author_id === this.pyEnv.currentPartnerId;
});
// Update notification status
this.pyEnv["mail.notification"].write(
notifications.map((notification) => notification.id),
{ notification_status: "canceled" }
);
// Send bus notifications to update status of notifications in the web client
this.pyEnv["bus.bus"]._sendone(
this.pyEnv.currentPartner,
"mail.message/notification_update",
{
elements: this._mockMailMessage_MessageNotificationFormat(
notifications.map((notification) => notification.mail_message_id)
),
}
);
},
});