diff --git a/addons/hr/static/src/core/web/thread_actions.js b/addons/hr/static/src/core/web/thread_actions.js
index 93db4d07bd5..db9836d8c1a 100644
--- a/addons/hr/static/src/core/web/thread_actions.js
+++ b/addons/hr/static/src/core/web/thread_actions.js
@@ -28,10 +28,10 @@ threadActionsRegistry.add("open-hr-profile", {
const orm = useService("orm");
const store = useService("mail.store");
let employeeId;
- if (!component.thread?.correspondent?.employeeId && component.thread?.chatPartnerId) {
+ if (!component.thread?.correspondent?.employeeId && component.thread?.chatPartner) {
const employees = await orm.silent.searchRead(
"hr.employee",
- [["user_partner_id", "=", component.thread.chatPartnerId]],
+ [["user_partner_id", "=", component.thread.chatPartner.id]],
["id"]
);
employeeId = employees[0]?.id;
diff --git a/addons/mail/static/src/core/common/attachment_model.js b/addons/mail/static/src/core/common/attachment_model.js
index bf35db8d174..a78c0c45400 100644
--- a/addons/mail/static/src/core/common/attachment_model.js
+++ b/addons/mail/static/src/core/common/attachment_model.js
@@ -39,7 +39,7 @@ export class Attachment extends Record {
id;
mimetype;
name;
- originThreadLocalId;
+ originThread = Record.one("Thread");
res_name;
type;
/** @type {string} */
@@ -48,16 +48,10 @@ export class Attachment extends Record {
url;
/** @type {boolean} */
uploading;
- /** @type {import("@mail/core/common/message_model").Message} */
- message;
+ message = Record.one("Message");
/** @type {string} */
create_date;
- /** @type {import("@mail/core/common/thread_model").Thread} */
- get originThread() {
- return this._store.Thread.records[this.originThreadLocalId];
- }
-
get isDeletable() {
return true;
}
diff --git a/addons/mail/static/src/core/common/attachment_service.js b/addons/mail/static/src/core/common/attachment_service.js
index edf4238867f..6add0e631d2 100644
--- a/addons/mail/static/src/core/common/attachment_service.js
+++ b/addons/mail/static/src/core/common/attachment_service.js
@@ -43,11 +43,10 @@ export class AttachmentService {
const threadData = Array.isArray(data.originThread)
? data.originThread[0][1]
: data.originThread;
- this.store.Thread.insert({
+ attachment.originThread = this.store.Thread.insert({
model: threadData.model,
id: threadData.id,
});
- attachment.originThreadLocalId = this.store.Thread.localId(threadData);
const thread = attachment.originThread;
if (attachment.notIn(thread.attachments)) {
thread.attachments.push(attachment);
@@ -59,7 +58,7 @@ export class AttachmentService {
/**
* Remove the given attachment globally.
*
- * @param {Attachment} attachment
+ * @param {import("@mail/core/common/attachment_model").Attachment} attachment
*/
remove(attachment) {
if (attachment.tmpUrl) {
diff --git a/addons/mail/static/src/core/common/channel_member_model.js b/addons/mail/static/src/core/common/channel_member_model.js
index 46c0b866ef4..1ec3dc78cf5 100644
--- a/addons/mail/static/src/core/common/channel_member_model.js
+++ b/addons/mail/static/src/core/common/channel_member_model.js
@@ -6,8 +6,8 @@ import { Record } from "@mail/core/common/record";
* @class ChannelMember
* @typedef Data
* @property {number} id
- * @property {string} personaLocalId
- * @property {number} threadId
+ * @property {import("@mail/core/common/persona_model").Persona} persona
+ * @property {import("@mail/core/common/thread_model").Thread} thread
*/
export class ChannelMember extends Record {
static id = "id";
@@ -34,25 +34,9 @@ export class ChannelMember extends Record {
/** @type {number} */
id;
- personaLocalId;
- rtcSessionId;
- threadId;
-
- get persona() {
- return this._store.Persona.records[this.personaLocalId];
- }
-
- set persona(persona) {
- this.personaLocalId = persona?.localId;
- }
-
- get rtcSession() {
- return this._store.RtcSession.get(this.rtcSessionId);
- }
-
- get thread() {
- return this._store.Thread.get({ model: "discuss.channel", id: this.threadId });
- }
+ persona = Record.one("Persona");
+ rtcSession = Record.one("RtcSession");
+ thread = Record.one("Thread");
/**
* @returns {string}
diff --git a/addons/mail/static/src/core/common/channel_member_service.js b/addons/mail/static/src/core/common/channel_member_service.js
index 260c6c3aafa..0886d0b77be 100644
--- a/addons/mail/static/src/core/common/channel_member_service.js
+++ b/addons/mail/static/src/core/common/channel_member_service.js
@@ -25,13 +25,16 @@ export class ChannelMemberService {
channelId: memberData.persona.guest ? memberData.channel.id : null,
});
}
- member.threadId = memberData.threadId ?? member.threadId ?? memberData.channel?.id;
- if (member.threadId && !member.thread) {
- this.store.Thread.insert({
- id: member.threadId,
+ let thread = memberData.thread ?? member.thread;
+ if (!thread && memberData.channel?.id) {
+ thread = this.store.Thread.insert({
+ id: memberData.channel.id,
model: "discuss.channel",
});
}
+ if (thread && !member.thread) {
+ member.thread = thread;
+ }
switch (command) {
case "insert":
{
diff --git a/addons/mail/static/src/core/common/chat_window_container.xml b/addons/mail/static/src/core/common/chat_window_container.xml
index e69842193c6..ca4fe096b88 100644
--- a/addons/mail/static/src/core/common/chat_window_container.xml
+++ b/addons/mail/static/src/core/common/chat_window_container.xml
@@ -6,7 +6,7 @@
-
+
@@ -21,7 +21,7 @@
diff --git a/addons/mail/static/src/core/common/chat_window_model.js b/addons/mail/static/src/core/common/chat_window_model.js
index 2e45e6e1e98..a62df699c90 100644
--- a/addons/mail/static/src/core/common/chat_window_model.js
+++ b/addons/mail/static/src/core/common/chat_window_model.js
@@ -8,7 +8,7 @@ import { _t } from "@web/core/l10n/translation";
/** @typedef {{ thread?: import("@mail/core/common/thread_model").Thread, folded?: boolean, replaceNewMessageChatWindow?: boolean }} ChatWindowData */
export class ChatWindow extends Record {
- static id = "threadLocalId";
+ static id = "thread";
/** @type {ChatWindow[]} */
static records = [];
/** @returns {ChatWindow} */
@@ -24,7 +24,7 @@ export class ChatWindow extends Record {
* @returns {ChatWindow}
*/
static insert(data = {}) {
- const chatWindow = this.records.find((c) => c.threadLocalId === data.thread?.localId);
+ const chatWindow = this.records.find((c) => c.thread?.eq(data.thread));
if (!chatWindow) {
const chatWindow = this.new(data);
Object.assign(chatWindow, { thread: data.thread });
@@ -57,20 +57,11 @@ export class ChatWindow extends Record {
return chatWindow;
}
- /** @type {import("@mail/core/common/thread_model").Thread.localId} */
- threadLocalId;
+ thread = Record.one("Thread");
autofocus = 0;
folded = false;
hidden = false;
- get thread() {
- return this._store.Thread.records[this.threadLocalId];
- }
-
- set thread(thread) {
- this.threadLocalId = thread?.localId;
- }
-
get displayName() {
return this.thread?.displayName ?? _t("New message");
}
diff --git a/addons/mail/static/src/core/common/composer_model.js b/addons/mail/static/src/core/common/composer_model.js
index 076200c2a6c..5891b0f8156 100644
--- a/addons/mail/static/src/core/common/composer_model.js
+++ b/addons/mail/static/src/core/common/composer_model.js
@@ -7,7 +7,7 @@ import { OR, Record } from "@mail/core/common/record";
*/
export class Composer extends Record {
- static id = OR("threadLocalId", "messageLocalId");
+ static id = OR("thread", "message");
/** @returns {Composer} */
static new(data) {
return super.new(data);
@@ -57,8 +57,7 @@ export class Composer extends Record {
/** @type {import("@mail/core/common/attachment_model").Attachment[]} */
attachments = [];
- /** @type {import("@mail/core/common/message_model").Message.localId} */
- messageLocalId;
+ message = Record.one("Message");
/** @type {RawMentions} */
rawMentions = {
partnerIds: new Set(),
@@ -68,8 +67,7 @@ export class Composer extends Record {
cannedResponseIds = new Set();
/** @type {string} */
textInputContent;
- /** @type {import("@mail/core/common/thread_model").Thread.localId} */
- threadLocalId;
+ thread = Record.one("Thread");
/** @type {{ start: number, end: number, direction: "forward" | "backward" | "none"}}*/
selection = {
start: 0,
@@ -79,26 +77,6 @@ export class Composer extends Record {
/** @type {boolean} */
forceCursorMove;
isFocused = false;
-
- /** @type {import("@mail/core/common/message_model").Message} */
- get message() {
- return this._store.Message.records[this.messageLocalId];
- }
-
- /** @param {import("@mail/core/common/message_model").Message} */
- set message(newMessage) {
- this.messageLocalId = newMessage?.localId;
- }
-
- /** @type {import("@mail/core/common/thread_model").Thread} */
- get thread() {
- return this._store.Thread.records[this.threadLocalId];
- }
-
- /** @param {import("@mail/core/common/thread_model").Thread} */
- set thread(newThread) {
- this.threadLocalId = newThread?.localId;
- }
}
Composer.register();
diff --git a/addons/mail/static/src/core/common/follower_model.js b/addons/mail/static/src/core/common/follower_model.js
index 4f4a73fe77a..6e95d63e6a8 100644
--- a/addons/mail/static/src/core/common/follower_model.js
+++ b/addons/mail/static/src/core/common/follower_model.js
@@ -37,14 +37,12 @@ export class Follower extends Record {
return follower;
}
- /** @type {import("@mail/core/common/thread_model").Thread} */
- followedThread;
+ followedThread = Record.one("Thread");
/** @type {number} */
id;
/** @type {boolean} */
isActive;
- /** @type {import("@mail/core/common/persona_model").Persona} */
- partner;
+ partner = Record.one("Persona");
/**
* @returns {boolean}
diff --git a/addons/mail/static/src/core/common/link_preview_confirm_delete.js b/addons/mail/static/src/core/common/link_preview_confirm_delete.js
index 134347fd46a..1a23a1c6c9f 100644
--- a/addons/mail/static/src/core/common/link_preview_confirm_delete.js
+++ b/addons/mail/static/src/core/common/link_preview_confirm_delete.js
@@ -23,7 +23,7 @@ export class LinkPreviewConfirmDelete extends Component {
}
get message() {
- return this.store.Message.get(this.props.linkPreview.message_id);
+ return this.props.linkPreview.message;
}
onClickOk() {
diff --git a/addons/mail/static/src/core/common/link_preview_model.js b/addons/mail/static/src/core/common/link_preview_model.js
index d37dc88d2fe..18a9696b35a 100644
--- a/addons/mail/static/src/core/common/link_preview_model.js
+++ b/addons/mail/static/src/core/common/link_preview_model.js
@@ -18,6 +18,8 @@ export class LinkPreview extends Record {
*/
static insert(data) {
const message = this.store.Message.get(data.message_id);
+ data.message = message;
+ delete data.message_id;
let linkPreview = message?.linkPreviews.find((lp) => lp.id === data.id);
if (linkPreview) {
return Object.assign(linkPreview, data);
@@ -30,8 +32,7 @@ export class LinkPreview extends Record {
/** @type {number} */
id;
- /** @type {number} */
- message_id;
+ message = Record.one("Message");
/** @type {string} */
image_mimetype;
/** @type {string} */
diff --git a/addons/mail/static/src/core/common/message_model.js b/addons/mail/static/src/core/common/message_model.js
index 4887bbb6dee..c802fb71ff7 100644
--- a/addons/mail/static/src/core/common/message_model.js
+++ b/addons/mail/static/src/core/common/message_model.js
@@ -42,8 +42,7 @@ export class Message extends Record {
/** @type {Object[]} */
attachments = [];
- /** @type {import("@mail/core/common/persona_model").Persona} */
- author;
+ author = Record.one("Persona");
/** @type {string} */
body;
/** @type {string} */
@@ -64,8 +63,7 @@ export class Message extends Record {
needaction_partner_ids = [];
/** @type {number[]} */
history_partner_ids = [];
- /** @type {Message|undefined} */
- parentMessage;
+ parentMessage = Record.one("Message");
/** @type {MessageReactions[]} */
reactions = [];
/** @type {import("@mail/core/common/notification_model").Notification[]} */
diff --git a/addons/mail/static/src/core/common/message_reactions_model.js b/addons/mail/static/src/core/common/message_reactions_model.js
index d46d3ec88f1..b568a42deeb 100644
--- a/addons/mail/static/src/core/common/message_reactions_model.js
+++ b/addons/mail/static/src/core/common/message_reactions_model.js
@@ -3,7 +3,7 @@
import { AND, Record } from "@mail/core/common/record";
export class MessageReactions extends Record {
- static id = AND("messageId", "content");
+ static id = AND("message", "content");
/** @returns {MessageReactions} */
static new(data) {
return super.new(data);
@@ -48,7 +48,7 @@ export class MessageReactions extends Record {
Object.assign(reaction, {
count: data.count,
content: data.content,
- messageId: data.message.id,
+ message: data.message,
personaLocalIds: reaction.personaLocalIds.filter(
(localId) => !personasToUnlink.has(localId)
),
@@ -62,8 +62,7 @@ export class MessageReactions extends Record {
count;
/** @type {number[]} */
personaLocalIds = [];
- /** @type {number} */
- messageId;
+ message = Record.one("Message");
/** @type {import("@mail/core/common/persona_model").Persona[]} */
get personas() {
diff --git a/addons/mail/static/src/core/common/message_service.js b/addons/mail/static/src/core/common/message_service.js
index 24d9b125c8a..0183085c111 100644
--- a/addons/mail/static/src/core/common/message_service.js
+++ b/addons/mail/static/src/core/common/message_service.js
@@ -182,7 +182,7 @@ export class MessageService {
{
action: "remove",
content: reaction.content,
- message_id: reaction.messageId,
+ message_id: reaction.message.id,
},
{ silent: true }
);
@@ -284,7 +284,7 @@ export class MessageService {
replaceArrayWithCompare(
message.notifications,
notifications.map((notification) =>
- this.store.Notification.insert({ ...notification, messageId: message.id })
+ this.store.Notification.insert({ ...notification, message })
)
);
replaceArrayWithCompare(
@@ -342,7 +342,7 @@ export class MessageService {
updateNotification(notification, data) {
Object.assign(notification, {
- messageId: data.messageId,
+ message: data.message,
notification_status: data.notification_status,
notification_type: data.notification_type,
failure_type: data.failure_type,
@@ -390,10 +390,10 @@ export class MessageService {
group.notifications = group.notifications.filter(
({ id }) => !notificationIdsToRemove.has(id)
);
- group.lastMessageId = group.notifications[0]?.message.id;
+ group.lastMessage = group.notifications[0]?.message;
for (const notification of group.notifications) {
- if (group.lastMessageId < notification.message.id) {
- group.lastMessageId = notification.message.id;
+ if (group.lastMessage?.id < notification.message?.id) {
+ group.lastMessage = notification.message;
}
}
group.resIds.add(data.resId);
diff --git a/addons/mail/static/src/core/common/notification_group_model.js b/addons/mail/static/src/core/common/notification_group_model.js
index 3211a805045..15f8830e86a 100644
--- a/addons/mail/static/src/core/common/notification_group_model.js
+++ b/addons/mail/static/src/core/common/notification_group_model.js
@@ -51,8 +51,7 @@ export class NotificationGroup extends Record {
modelName;
/** @type {string} */
resModel;
- /** @type {number} */
- lastMessageId;
+ lastMessage = Record.one("Message");
/** @type {Set} */
resIds = new Set();
/** @type {'sms' | 'email'} */
@@ -66,10 +65,6 @@ export class NotificationGroup extends Record {
return _t("An error occurred when sending an email");
}
- get lastMessage() {
- return this._store.Message.get(this.lastMessageId);
- }
-
get datetime() {
return this.lastMessage?.datetime;
}
diff --git a/addons/mail/static/src/core/common/notification_model.js b/addons/mail/static/src/core/common/notification_model.js
index 28b46030e6f..42e3d820d87 100644
--- a/addons/mail/static/src/core/common/notification_model.js
+++ b/addons/mail/static/src/core/common/notification_model.js
@@ -29,20 +29,14 @@ export class Notification extends Record {
/** @type {number} */
id;
- /** @type {number} */
- messageId;
+ message = Record.one("Message");
/** @type {string} */
notification_status;
/** @type {string} */
notification_type;
/** @type {string} */
failure_type;
- /** @type {import("@mail/core/common/persona_model").Persona} */
- persona;
-
- get message() {
- return this._store.Message.get(this.messageId);
- }
+ persona = Record.one("Persona");
get isFailure() {
return ["exception", "bounce"].includes(this.notification_status);
diff --git a/addons/mail/static/src/core/common/persona_model.js b/addons/mail/static/src/core/common/persona_model.js
index 72e10517157..fc8574f7809 100644
--- a/addons/mail/static/src/core/common/persona_model.js
+++ b/addons/mail/static/src/core/common/persona_model.js
@@ -35,8 +35,6 @@ export class Persona extends Record {
return persona;
}
- /** @type {string} */
- localId;
/** @type {number} */
id;
/** @type {boolean | undefined} */
diff --git a/addons/mail/static/src/core/common/persona_service.js b/addons/mail/static/src/core/common/persona_service.js
index 8d3c04c8539..5d6cf610a3a 100644
--- a/addons/mail/static/src/core/common/persona_service.js
+++ b/addons/mail/static/src/core/common/persona_service.js
@@ -110,7 +110,7 @@ export class PersonaService {
}
return b.lastInterestDateTime.ts - a.lastInterestDateTime.ts;
})
- .map((thread) => thread.chatPartnerId);
+ .map((thread) => thread.chatPartner?.id);
}
}
diff --git a/addons/mail/static/src/core/common/record.js b/addons/mail/static/src/core/common/record.js
index 2ab6de49bde..d0f984801fd 100644
--- a/addons/mail/static/src/core/common/record.js
+++ b/addons/mail/static/src/core/common/record.js
@@ -5,6 +5,7 @@ import { registry } from "@web/core/registry";
export const modelRegistry = registry.category("discuss.model");
+const ONE_SYM = Symbol("one");
const OR_SYM = Symbol("or");
const AND_SYM = Symbol("and");
@@ -31,7 +32,7 @@ export class Record {
}
static localId(data) {
let idStr;
- if (typeof data === "object") {
+ if (typeof data === "object" && data !== null) {
idStr = this._localId(this.id, data);
} else {
idStr = data; // non-object data => single id
@@ -40,6 +41,10 @@ export class Record {
}
static _localId(expr, data, { brackets = false } = {}) {
if (!Array.isArray(expr)) {
+ if (this.Class.__rels__.has(expr)) {
+ // relational field (note: optional when OR)
+ return `(${data[expr]?.localId})`;
+ }
return data[expr];
}
const vals = [];
@@ -79,13 +84,28 @@ export class Record {
}
return record;
}
-
+ /**
+ * @template {keyof import("model ").Models} M
+ * @param {M} modelName
+ * @returns {import("models").Models[M]}
+ */
+ static one(modelName) {
+ return ONE_SYM;
+ }
/**
* @param {Object} data
* @returns {Record}
*/
static insert(data) {}
+ /**
+ * Raw relational values of the record, each of which contains object id(s)
+ * rather than the record(s). This allows data in store and models being normalized,
+ * which eases handling relations notably in when a record gets deleted.
+ *
+ * @type {Map}
+ */
+ __rels__ = new Map();
/** @type {import("@mail/core/common/store_service").Store} */
_store;
/**
diff --git a/addons/mail/static/src/core/common/store_service.js b/addons/mail/static/src/core/common/store_service.js
index c6ab8ef2156..5a0b36f580c 100644
--- a/addons/mail/static/src/core/common/store_service.js
+++ b/addons/mail/static/src/core/common/store_service.js
@@ -7,7 +7,7 @@ import { reactive } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { debounce } from "@web/core/utils/timing";
-import { modelRegistry } from "./record";
+import { modelRegistry, Record } from "./record";
export class Store {
/** @type {typeof import("@mail/core/web/activity_model").Activity} */
@@ -187,9 +187,43 @@ export const storeService = {
// work-around: make an object whose prototype is the class, so that static props become
// instance props.
const entry = Object.assign(Object.create(Model), { env, store: res });
- entry.Class = Model;
+ // Produce another class with changed prototype, so that there are automatic get/set on relational fields
+ const Class = {
+ [Model.name]: class extends Model {
+ static __rels__ = new Set();
+ constructor() {
+ super();
+ for (const name of this.constructor.__rels__) {
+ // Relational fields contain symbols for detection in original class.
+ // This constructor is called on genuine records:
+ // - 'one' fields => undefined
+ const newVal = undefined;
+ this[name] = newVal;
+ this.__rels__.set(name, newVal);
+ }
+ }
+ },
+ }[Model.name];
+ entry.Class = Class;
entry.records = JSON.parse(JSON.stringify(Model.records));
res[name] = entry;
+ // Detect relational fields with a dummy record and setup getter/setters on them
+ const obj = new Model();
+ for (const [name, val] of Object.entries(obj)) {
+ if (val !== Record.one()) {
+ continue;
+ }
+ Class.__rels__.add(name);
+ Object.defineProperty(Class.prototype, name, {
+ get() {
+ const localId = this.__rels__.get(name);
+ return res.get(localId);
+ },
+ set(record) {
+ this.__rels__.set(name, record?.localId);
+ },
+ });
+ }
}
onChange(res.Thread, "records", () => res.updateBusSubscription());
services.ui.bus.addEventListener("resize", () => {
diff --git a/addons/mail/static/src/core/common/thread_icon.js b/addons/mail/static/src/core/common/thread_icon.js
index a7267c85ba0..abd326af2de 100644
--- a/addons/mail/static/src/core/common/thread_icon.js
+++ b/addons/mail/static/src/core/common/thread_icon.js
@@ -24,6 +24,6 @@ export class ThreadIcon extends Component {
}
get chatPartner() {
- return this.store.Persona.get({ type: "partner", id: this.props.thread.chatPartnerId });
+ return this.props.thread.chatPartner;
}
}
diff --git a/addons/mail/static/src/core/common/thread_model.js b/addons/mail/static/src/core/common/thread_model.js
index 57f400fa007..bf535570faf 100644
--- a/addons/mail/static/src/core/common/thread_model.js
+++ b/addons/mail/static/src/core/common/thread_model.js
@@ -101,21 +101,18 @@ export class Thread extends Record {
areAttachmentsLoaded = false;
/** @type {import("@mail/core/common/attachment_model").Attachment[]} */
attachments = [];
- /** @type {integer} */
- activeRtcSessionId;
+ activeRtcSession = Record.one("RtcSession");
/** @type {object|undefined} */
channel;
/** @type {import("@mail/core/common/channel_member_model").ChannelMember[]} */
channelMembers = [];
/** @type {Object} */
rtcSessions = {};
- invitingRtcSessionId;
+ rtcInvitingSession = Record.one("RtcSession");
/** @type {Set} */
invitedMemberIds = new Set();
- /** @type {integer} */
- chatPartnerId;
- /** @type {import("@mail/core/common/composer_model").Composer} */
- composer;
+ chatPartner = Record.one("Persona");
+ composer = Record.one("Composer");
counter = 0;
/** @type {string} */
customName;
@@ -123,8 +120,7 @@ export class Thread extends Record {
description;
/** @type {Set} */
followers = new Set();
- /** @type {import("@mail/core/common/follower_model").Follower} */
- selfFollower;
+ selfFollower = Record.one("Follower");
/** @type {integer|undefined} */
followersCount;
isAdmin = false;
@@ -133,8 +129,7 @@ export class Thread extends Record {
isLoadingAttachments = false;
isLoadedDeferred = new Deferred();
isLoaded = false;
- /** @type {import("@mail/core/common/attachment_model").Attachment} */
- mainAttachment;
+ mainAttachment = Record.one("Attachment");
memberCount = 0;
message_needaction_counter = 0;
message_unread_counter = 0;
@@ -197,8 +192,7 @@ export class Thread extends Record {
canPostOnReadonly;
/** @type {String} */
last_interest_dt;
- /** @type {number} */
- lastServerMessageId;
+ lastServerMessage = Record.one("Message");
/** @type {Boolean} */
is_editable;
@@ -211,14 +205,6 @@ export class Thread extends Record {
});
}
- get activeRtcSession() {
- return this._store.RtcSession.get(this.activeRtcSessionId);
- }
-
- set activeRtcSession(session) {
- this.activeRtcSessionId = session?.id;
- }
-
get areAllMembersLoaded() {
return this.memberCount === this.channelMembers.length;
}
@@ -276,12 +262,8 @@ export class Thread extends Record {
}
get displayName() {
- if (this.type === "chat" && this.chatPartnerId) {
- return (
- this.customName ||
- this._store.Persona.get({ type: "partner", id: this.chatPartnerId })
- .nameOrDisplayName
- );
+ if (this.type === "chat" && this.chatPartner) {
+ return this.customName || this.chatPartner.nameOrDisplayName;
}
if (this.type === "group" && !this.name) {
const listFormatter = new Intl.ListFormat(
@@ -471,10 +453,6 @@ export class Thread extends Record {
return this.memberCount - this.channelMembers.length;
}
- get rtcInvitingSession() {
- return this._store.RtcSession.get(this.invitingRtcSessionId);
- }
-
get hasNeedactionMessages() {
return this.needactionMessages.length > 0;
}
diff --git a/addons/mail/static/src/core/common/thread_service.js b/addons/mail/static/src/core/common/thread_service.js
index 5be8b53d8c5..0b7d2f8bc10 100644
--- a/addons/mail/static/src/core/common/thread_service.js
+++ b/addons/mail/static/src/core/common/thread_service.js
@@ -78,7 +78,7 @@ export class ThreadService {
thread.memberCount = results["memberCount"];
for (const channelMember of channelMembers) {
if (channelMember.persona || channelMember.partner) {
- this.store.ChannelMember.insert({ ...channelMember, threadId: thread.id });
+ this.store.ChannelMember.insert({ ...channelMember, thread });
}
}
}
@@ -533,7 +533,7 @@ export class ThreadService {
return;
}
return Object.values(this.store.Thread.records).find(
- (thread) => thread.type === "chat" && thread.chatPartnerId === partner.id
+ (thread) => thread.type === "chat" && thread.chatPartner?.eq(partner)
);
}
@@ -707,7 +707,12 @@ export class ThreadService {
if (serverData.channel && "message_unread_counter" in serverData.channel) {
thread.message_unread_counter = serverData.channel.message_unread_counter;
}
- thread.lastServerMessageId = serverData.last_message_id ?? thread.lastServerMessageId;
+ const lastServerMessageId = serverData.last_message_id ?? thread.lastServerMessage?.id;
+ if (thread.lastServerMessage?.id !== lastServerMessageId) {
+ thread.lastServerMessage = this.store.Message.insert({
+ id: lastServerMessageId,
+ });
+ }
if (thread.model === "discuss.channel" && serverData.channel) {
nullifyClearCommands(serverData.channel);
thread.channel = assignDefined(thread.channel ?? {}, serverData.channel);
@@ -730,7 +735,7 @@ export class ThreadService {
(serverData.channel.channelMembers[0][1].length === 1 &&
member.persona?.eq(thread._store.user))
) {
- thread.chatPartnerId = member.persona.id;
+ thread.chatPartner = member.persona;
}
}
}
diff --git a/addons/mail/static/src/core/web/thread_service_patch.js b/addons/mail/static/src/core/web/thread_service_patch.js
index 09b9bb4e38e..2ff8805239f 100644
--- a/addons/mail/static/src/core/web/thread_service_patch.js
+++ b/addons/mail/static/src/core/web/thread_service_patch.js
@@ -166,9 +166,7 @@ patch(ThreadService.prototype, {
thread.suggestedRecipients = recipients;
},
async leaveChannel(channel) {
- const chatWindow = this.store.ChatWindow.records.find(
- (c) => c.threadLocalId === channel.localId
- );
+ const chatWindow = this.store.ChatWindow.records.find((c) => c.thread?.eq(channel));
if (chatWindow) {
this.chatWindowService.close(chatWindow);
}
@@ -249,9 +247,7 @@ patch(ThreadService.prototype, {
follower.delete();
},
unpin(thread) {
- const chatWindow = this.store.ChatWindow.records.find(
- (c) => c.threadLocalId === thread.localId
- );
+ const chatWindow = this.store.ChatWindow.records.find((c) => c.thread?.eq(thread));
if (chatWindow) {
this.chatWindowService.close(chatWindow);
}
diff --git a/addons/mail/static/src/discuss/call/common/rtc_service.js b/addons/mail/static/src/discuss/call/common/rtc_service.js
index ddae9b97f23..5238e01e484 100644
--- a/addons/mail/static/src/discuss/call/common/rtc_service.js
+++ b/addons/mail/static/src/discuss/call/common/rtc_service.js
@@ -290,7 +290,9 @@ export class Rtc {
thread,
record: data.rtc_inviting_session,
});
- thread.invitingRtcSessionId = data.rtc_inviting_session.id;
+ thread.rtcInvitingSession = this.store.RtcSession.insert({
+ id: data.rtc_inviting_session.id,
+ });
if (!this.store.ringingThreads.includes(thread.localId)) {
this.store.ringingThreads.push(thread.localId);
}
@@ -298,7 +300,7 @@ export class Rtc {
if ("rtcInvitingSession" in data) {
if (Array.isArray(data.rtcInvitingSession)) {
if (data.rtcInvitingSession[0][0] === "unlink") {
- thread.invitingRtcSessionId = undefined;
+ thread.rtcInvitingSession = undefined;
removeFromArray(this.store.ringingThreads, thread.localId);
}
return;
@@ -307,7 +309,9 @@ export class Rtc {
thread,
record: data.rtcInvitingSession,
});
- thread.invitingRtcSessionId = data.rtcInvitingSession.id;
+ thread.rtcInvitingSession = this.store.RtcSession.insert({
+ id: data.rtcInvitingSession.id,
+ });
this.store.ringingThreads.push(thread.localId);
}
if ("rtcSessions" in data) {
diff --git a/addons/mail/static/src/discuss/call/common/rtc_session_model.js b/addons/mail/static/src/discuss/call/common/rtc_session_model.js
index 9fff093ad73..59fc621ba91 100644
--- a/addons/mail/static/src/discuss/call/common/rtc_session_model.js
+++ b/addons/mail/static/src/discuss/call/common/rtc_session_model.js
@@ -29,7 +29,7 @@ export class RtcSession extends Record {
}
if (channelMember) {
const channelMemberRecord = this.store.ChannelMember.insert(channelMember);
- channelMemberRecord.rtcSessionId = session.id;
+ channelMemberRecord.rtcSession = session;
session.channelMemberId = channelMemberRecord.id;
if (channelMemberRecord.thread) {
channelMemberRecord.thread.rtcSessions[session.id] = session;
diff --git a/addons/mail/static/src/discuss/core/common/channel_invitation.js b/addons/mail/static/src/discuss/core/common/channel_invitation.js
index 135338e1182..c0046d68309 100644
--- a/addons/mail/static/src/discuss/core/common/channel_invitation.js
+++ b/addons/mail/static/src/discuss/core/common/channel_invitation.js
@@ -103,7 +103,7 @@ export class ChannelInvitation extends Component {
async onClickInvite() {
if (this.props.thread.type === "chat") {
await this.discussCoreCommonService.startChat([
- this.props.thread.chatPartnerId,
+ this.props.thread.chatPartner?.id,
...this.state.selectedPartners.map((partner) => partner.id),
]);
} else {
@@ -120,13 +120,13 @@ export class ChannelInvitation extends Component {
} else if (this.props.thread.type === "group") {
return _t("Invite to Group Chat");
} else if (this.props.thread.type === "chat") {
- if (this.props.thread.chatPartnerId === this.store.self.id) {
+ if (this.props.thread.chatPartner?.eq(this.store.self)) {
if (this.state.selectedPartners.length === 0) {
return _t("Invite");
}
if (this.state.selectedPartners.length === 1) {
- const alreadyChat = Object.values(this.store.Thread.records).some(
- (thread) => thread.chatPartnerId === this.state.selectedPartners[0].id
+ const alreadyChat = Object.values(this.store.Thread.records).some((thread) =>
+ thread.chatPartner?.eq(this.state.selectedPartners[0])
);
if (alreadyChat) {
return _t("Go to conversation");
diff --git a/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js b/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js
index 37c3a707257..3bab9b66963 100644
--- a/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js
+++ b/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js
@@ -269,7 +269,7 @@ export class DiscussCoreCommon {
}
}
}
- if (channel.chatPartnerId !== this.store.odoobot?.id) {
+ if (!channel.chatPartner?.eq(this.store.odoobot)) {
if (
!this.presence.isOdooFocused() &&
channel.isChatChannel &&
diff --git a/addons/mail/static/src/discuss/core/common/thread_model_patch.js b/addons/mail/static/src/discuss/core/common/thread_model_patch.js
index 8b12c3f76d9..787642e52d4 100644
--- a/addons/mail/static/src/discuss/core/common/thread_model_patch.js
+++ b/addons/mail/static/src/discuss/core/common/thread_model_patch.js
@@ -15,7 +15,7 @@ patch(Thread.prototype, {
);
}
if (this.type === "chat") {
- return `/web/image/res.partner/${this.chatPartnerId}/avatar_128`;
+ return `/web/image/res.partner/${this.chatPartner.id}/avatar_128`;
}
return super.imgUrl;
},
diff --git a/addons/mail/static/src/discuss/core/web/command_palette.js b/addons/mail/static/src/discuss/core/web/command_palette.js
index 3015f2a90d1..e3170bfdb97 100644
--- a/addons/mail/static/src/discuss/core/web/command_palette.js
+++ b/addons/mail/static/src/discuss/core/web/command_palette.js
@@ -44,14 +44,14 @@ commandProviderRegistry.add("mail.partner", {
if (!options.searchValue) {
mentionedChannels.slice(0, 3).map((channel) => {
if (channel.type === "chat") {
- displayedPartnerIds.add(channel.chatPartnerId);
+ displayedPartnerIds.add(channel.chatPartner.id);
}
commands.push({
Component: DiscussCommand,
async action() {
switch (channel.type) {
case "chat":
- threadService.openChat({ partnerId: channel.chatPartnerId });
+ threadService.openChat({ partnerId: channel.chatPartner.id });
break;
case "group":
threadService.open(channel);
diff --git a/addons/mail/static/src/discuss/core/web/discuss_core_web_service.js b/addons/mail/static/src/discuss/core/web/discuss_core_web_service.js
index af9a855930c..53d09bc885b 100644
--- a/addons/mail/static/src/discuss/core/web/discuss_core_web_service.js
+++ b/addons/mail/static/src/discuss/core/web/discuss_core_web_service.js
@@ -90,8 +90,8 @@ export class DiscussCoreWeb {
if (data.serverFoldState && thread && data.serverFoldState !== thread.state) {
thread.state = data.serverFoldState;
if (thread.state === "closed") {
- const chatWindow = this.store.ChatWindow.records.find(
- (chatWindow) => chatWindow.threadLocalId === thread.localId
+ const chatWindow = this.store.ChatWindow.records.find((chatWindow) =>
+ chatWindow.thread?.eq(thread)
);
if (chatWindow) {
this.chatWindowService.close(chatWindow);
diff --git a/addons/mail/tooling/types/models.d.ts b/addons/mail/tooling/types/models.d.ts
new file mode 100644
index 00000000000..f71b13b9af9
--- /dev/null
+++ b/addons/mail/tooling/types/models.d.ts
@@ -0,0 +1,20 @@
+/* add this file in jsconfig.json, in typeRoots array */
+declare module "models" {
+ import { Attachment } from "@mail/core/common/attachment_model";
+ import { Composer } from "@mail/core/common/composer_model";
+ import { Follower } from "@mail/core/common/follower_model";
+ import { Message } from "@mail/core/common/message_model";
+ import { Persona } from "@mail/core/common/persona_model";
+ import { RtcSession } from "@mail/discuss/call/common/rtc_session_model";
+ import { Thread } from "@mail/core/common/thread_model";
+
+ export interface Models {
+ "Attachment": Attachment,
+ "Composer": Composer,
+ "Follower": Follower,
+ "Message": Message,
+ "Persona": Persona,
+ "RtcSession": RtcSession,
+ "Thread": Thread,
+ }
+}