diff --git a/addons/im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js b/addons/im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js index b06d785c977..e9b573ffa02 100644 --- a/addons/im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js +++ b/addons/im_livechat/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js @@ -17,6 +17,16 @@ patchRecordMethods('mail.discuss_sidebar_category_item', { } return this._super(); }, + /** + * @override + */ + _computeCategoryCounterContribution() { + switch (this.channel.channel_type) { + case 'livechat': + return this.channel.localMessageUnreadCounter > 0 ? 1 : 0; + } + return this._super(); + }, /** * @override */ diff --git a/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js b/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js index e7c5fdb3c3c..c711228191c 100644 --- a/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js +++ b/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js @@ -14,7 +14,6 @@ patchRecordMethods('mail.messaging_initializer', { _initResUsersSettings({ is_discuss_sidebar_category_livechat_open }) { this.messaging.discuss.update({ categoryLivechat: insertAndReplace({ - counterComputeMethod: 'unread', isServerOpen: is_discuss_sidebar_category_livechat_open, name: this.env._t("Livechat"), serverStateKey: 'is_discuss_sidebar_category_livechat_open', diff --git a/addons/mail/static/src/model/model_field.js b/addons/mail/static/src/model/model_field.js index 2e0d5d0ee6b..2fd7fc2a07b 100644 --- a/addons/mail/static/src/model/model_field.js +++ b/addons/mail/static/src/model/model_field.js @@ -118,6 +118,16 @@ export class ModelField { * records. */ this.sort = sort; + /** + * Determines whether and how elements of this field should be summed + * into a counter field (only makes sense for relational x2many). + * + * It must contain an array of object with 2 keys: `from` giving the + * name of a field on the relation record that holds the contribution of + * that record to the sum, and `to` giving the name of a field on the + * current record which contains the sum. + */ + this.sumContributions = []; /** * This prop only makes sense in a relational field. Determine which * model name this relation refers to. diff --git a/addons/mail/static/src/model/model_field_relation_set.js b/addons/mail/static/src/model/model_field_relation_set.js index cab7375d67f..891219fdf97 100644 --- a/addons/mail/static/src/model/model_field_relation_set.js +++ b/addons/mail/static/src/model/model_field_relation_set.js @@ -1,5 +1,6 @@ /** @odoo-module */ +import { decrement, increment } from '@mail/model/model_field_command'; import { Listener } from '@mail/model/model_listener'; import { followRelations } from '@mail/model/model_utils'; @@ -21,6 +22,14 @@ export class RelationSet { this.sortArray = new Array(); this.sortListenerByValue = new Map(); } + if (this.field.sumContributions.length > 0) { + this.sumByValueByField = new Map(); + this.sumListenerByValueByField = new Map(); + for (const { to: sumFieldName } of this.field.sumContributions) { + this.sumByValueByField.set(sumFieldName, new Map()); + this.sumListenerByValueByField.set(sumFieldName, new Map()); + } + } } /** @@ -103,6 +112,34 @@ export class RelationSet { this.sortListenerByValue.set(value, listener); listener.onChange({ reason: 'initial call' }); } + for (const { from: contributionFieldName, to: sumFieldName } of this.field.sumContributions) { + this.sumByValueByField.get(sumFieldName).set(value, 0); + const listener = new Listener({ + isPartOfUpdateCycle: true, + name: `sum of field(${sumFieldName}) of ${this.record} from field(${contributionFieldName}) of ${value} through relation ${this.field}`, + onChange: info => { + // listen to the contribution to mark it as dependency + this.record.modelManager.startListening(listener); + const contribution = value[contributionFieldName]; + this.record.modelManager.stopListening(listener); + // retrieve the previous contribution and update it + const previousContribution = this.sumByValueByField.get(sumFieldName).get(value); + this.sumByValueByField.get(sumFieldName).set(value, contribution); + this.record.modelManager._update( + this.record, + { + [sumFieldName]: [ + decrement(previousContribution), + increment(contribution), + ], + }, + { allowWriteReadonly: true }, + ); + }, + }); + this.sumListenerByValueByField.get(sumFieldName).set(value, listener); + listener.onChange({ reason: 'initial call' }); + } } /** @@ -132,6 +169,16 @@ export class RelationSet { this.sortListenerByValue.delete(listener); this.record.modelManager.removeListener(listener); } + for (const { to: sumFieldName } of this.field.sumContributions) { + // remove contribution of current value + const contribution = this.sumByValueByField.get(sumFieldName).get(value); + this.sumByValueByField.get(sumFieldName).delete(value); + this.record.modelManager._update(this.record, { [sumFieldName]: decrement(contribution) }, { allowWriteReadonly: true }); + // remove listener on current value + const listener = this.sumListenerByValueByField.get(sumFieldName).get(value); + this.sumListenerByValueByField.get(sumFieldName).delete(value); + this.record.modelManager.removeListener(listener); + } return true; } diff --git a/addons/mail/static/src/model/model_manager.js b/addons/mail/static/src/model/model_manager.js index 37ba41a5630..a0d744acdc0 100644 --- a/addons/mail/static/src/model/model_manager.js +++ b/addons/mail/static/src/model/model_manager.js @@ -490,6 +490,7 @@ export class ModelManager { 'readonly', 'related', 'required', + 'sum', ].includes(key) ); if (invalidKeys.length > 0) { @@ -1176,11 +1177,25 @@ export class ModelManager { * 1. Prepare fields. */ for (const Model of Object.values(this.models)) { + const sumContributionsByFieldName = new Map(); // Make fields aware of their field name. for (const [fieldName, fieldData] of registry.get(Model.name).get('fields')) { Model.fields[fieldName] = new ModelField(Object.assign({}, fieldData, { fieldName, })); + if (fieldData.sum) { + const [relationFieldName, contributionFieldName] = fieldData.sum.split('.'); + if (!sumContributionsByFieldName.has(relationFieldName)) { + sumContributionsByFieldName.set(relationFieldName, []); + } + sumContributionsByFieldName.get(relationFieldName).push({ + from: contributionFieldName, + to: fieldName, + }); + } + } + for (const [fieldName, sumContributions] of sumContributionsByFieldName) { + Model.fields[fieldName].sumContributions = sumContributions; } } /** diff --git a/addons/mail/static/src/models/discuss_sidebar_category/discuss_sidebar_category.js b/addons/mail/static/src/models/discuss_sidebar_category/discuss_sidebar_category.js index 6aa06b0f689..1c65cdbea16 100644 --- a/addons/mail/static/src/models/discuss_sidebar_category/discuss_sidebar_category.js +++ b/addons/mail/static/src/models/discuss_sidebar_category/discuss_sidebar_category.js @@ -73,18 +73,6 @@ registerModel({ } return clear(); }, - /** - * @private - * @returns {integer} - */ - _computeCounter() { - switch (this.counterComputeMethod) { - case 'needaction': - return this.categoryItems.filter(categoryItem => categoryItem.channel.message_needaction_counter > 0).length; - case 'unread': - return this.categoryItems.filter(categoryItem => categoryItem.channel.localMessageUnreadCounter > 0).length; - } - }, /** * @private * @returns {FieldCommand} @@ -237,17 +225,13 @@ registerModel({ sort: '_sortDefinitionCategoryItems', }), /** - * The total amount unread/action-needed threads in the category. + * States the total amount of unread/action-needed threads in this + * category. */ counter: attr({ - compute: '_computeCounter', - }), - /** - * Determines how the counter of this category should be computed. - * Supported methods: 'needaction', 'unread'. - */ - counterComputeMethod: attr({ - required: true, + default: 0, + readonly: true, + sum: 'categoryItems.categoryCounterContribution', }), discussAsChannel: one2one('mail.discuss', { inverse: 'categoryChannel', diff --git a/addons/mail/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js b/addons/mail/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js index cebb2272316..85d02d7478d 100644 --- a/addons/mail/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js +++ b/addons/mail/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js @@ -33,13 +33,16 @@ registerModel({ }, /** * @private - * @returns {mail.thread} + * @returns {integer} */ - _computeChannel() { - return link(this.messaging.models['mail.thread'].findFromIdentifyingData({ - id: this.channel.id, - model: 'mail.channel', - })); + _computeCategoryCounterContribution() { + switch (this.channel.channel_type) { + case 'channel': + return this.channel.message_needaction_counter > 0 ? 1 : 0; + case 'chat': + case 'group': + return this.channel.localMessageUnreadCounter > 0 ? 1 : 0; + } }, /** * @private @@ -219,6 +222,14 @@ registerModel({ readonly: true, required: true, }), + /** + * Determines the contribution of this discuss sidebar category item to + * the counter of this category. + */ + categoryCounterContribution: attr({ + compute: '_computeCategoryCounterContribution', + readonly: true, + }), /** * Amount of unread/action-needed messages */ diff --git a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js index db2a933335f..1417b19eed4 100644 --- a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js +++ b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js @@ -244,7 +244,6 @@ registerModel({ categoryChannel: insertAndReplace({ autocompleteMethod: 'channel', commandAddTitleText: this.env._t("Add or join a channel"), - counterComputeMethod: 'needaction', hasAddCommand: true, hasViewCommand: true, isServerOpen: is_discuss_sidebar_category_channel_open, @@ -257,7 +256,6 @@ registerModel({ categoryChat: insertAndReplace({ autocompleteMethod: 'chat', commandAddTitleText: this.env._t("Start a conversation"), - counterComputeMethod: 'unread', hasAddCommand: true, isServerOpen: is_discuss_sidebar_category_chat_open, name: this.env._t("Direct Messages"),