diff --git a/addons/crm/static/src/views/crm_kanban/crm_column_progress.js b/addons/crm/static/src/views/crm_kanban/crm_column_progress.js
index 2452bf91624..514d747ef2a 100644
--- a/addons/crm/static/src/views/crm_kanban/crm_column_progress.js
+++ b/addons/crm/static/src/views/crm_kanban/crm_column_progress.js
@@ -9,7 +9,7 @@ import { getCurrency } from "@web/core/currency";
export class CrmColumnProgress extends ColumnProgress {
static props = {
...ColumnProgress.props,
- progressAttributes: true,
+ progressBarState: { type: Object },
};
static template = "crm.ColumnProgress";
setup() {
@@ -18,20 +18,19 @@ export class CrmColumnProgress extends ColumnProgress {
this.showRecurringRevenue = false;
onWillStart(async () => {
- if (this.props.progressAttributes.recurring_revenue_sum_field) {
+ if (this.props.progressBarState.progressAttributes.recurring_revenue_sum_field) {
this.showRecurringRevenue = await this.user.hasGroup("crm.group_use_recurring_revenues");
}
});
}
getRecurringRevenueGroupAggregate(group) {
- const rrField = this.props.progressAttributes.recurring_revenue_sum_field;
- const value = group.getAggregates(rrField.name);
- const title = rrField.string || this.env._t("Count");
+ const rrField = this.props.progressBarState.progressAttributes.recurring_revenue_sum_field;
+ const aggregatedValue = this.props.progressBarState.getAggregateValue(group, rrField);
let currency = false;
- if (value && rrField.currency_field) {
+ if (aggregatedValue.value && rrField.currency_field) {
currency = getCurrency(session.company_currency_id);
}
- return { value, currency, title };
+ return { ...aggregatedValue, currency };
}
}
diff --git a/addons/crm/static/src/views/crm_kanban/crm_kanban_model.js b/addons/crm/static/src/views/crm_kanban/crm_kanban_model.js
index a006a7c0976..b3a32cfd4dd 100644
--- a/addons/crm/static/src/views/crm_kanban/crm_kanban_model.js
+++ b/addons/crm/static/src/views/crm_kanban/crm_kanban_model.js
@@ -11,19 +11,6 @@ export class CrmKanbanModel extends KanbanModel {
}
export class CrmKanbanDynamicGroupList extends CrmKanbanModel.DynamicGroupList {
- /**
- * @override
- *
- * Add the RRM field to the sumfields to fetch in read_group calls
- */
- get sumFields() {
- const result = super.sumFields;
- if (this.model.progressAttributes.recurring_revenue_sum_field) {
- result.push(this.model.progressAttributes.recurring_revenue_sum_field.name);
- }
- return result;
- }
-
/**
* @override
*
@@ -49,25 +36,5 @@ export class CrmKanbanDynamicGroupList extends CrmKanbanModel.DynamicGroupList {
}
}
-export class CrmKanbanGroup extends CrmKanbanModel.Group {
- /**
- * This is called whenever the progress bar is changed, for example
- * when filtering on a certain stage from the progress bar and is meant
- * to update `sum_field` aggregated value.
- * We also want to update the recurring revenue aggregate.
- */
- updateAggregates(groupData) {
- if (this.model.progressAttributes.recurring_revenue_sum_field) {
- const rrField = this.model.progressAttributes.recurring_revenue_sum_field;
- const group = groupData.find(g => this.valueEquals(g[this.groupByField.name]));
- if (rrField) {
- this.aggregates[rrField.name] = group ? group[rrField.name] : 0;
- }
- }
- return super.updateAggregates(...arguments);
- }
-}
-
CrmKanbanModel.DynamicGroupList = CrmKanbanDynamicGroupList;
-CrmKanbanModel.Group = CrmKanbanGroup;
CrmKanbanModel.services = [...KanbanModel.services, "effect"];
diff --git a/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.xml b/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.xml
index 776b13d7775..301cbdab7bf 100644
--- a/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.xml
+++ b/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.xml
@@ -2,7 +2,7 @@
- props.group.model.progressAttributes
+ props.progressBarState
diff --git a/addons/crm/static/src/views/crm_kanban/crm_kanban_view.js b/addons/crm/static/src/views/crm_kanban/crm_kanban_view.js
index c328dbf1efe..7900b671b76 100644
--- a/addons/crm/static/src/views/crm_kanban/crm_kanban_view.js
+++ b/addons/crm/static/src/views/crm_kanban/crm_kanban_view.js
@@ -10,7 +10,16 @@ export const crmKanbanView = {
...kanbanView,
ArchParser: CrmKanbanArchParser,
// Makes it easier to patch
- Controller: class extends kanbanView.Controller {},
+ Controller: class extends kanbanView.Controller {
+ get progressBarAggregateFields() {
+ const res = super.progressBarAggregateFields;
+ const progressAttributes = this.props.archInfo.progressAttributes;
+ if (progressAttributes && progressAttributes.recurring_revenue_sum_field) {
+ res.push(progressAttributes.recurring_revenue_sum_field);
+ }
+ return res;
+ }
+ },
Model: CrmKanbanModel,
Renderer: CrmKanbanRenderer,
};
diff --git a/addons/mail/static/src/views/activity/activity_renderer.js b/addons/mail/static/src/views/activity/activity_renderer.js
index 9a0dbf051e1..b2a2c7510ee 100644
--- a/addons/mail/static/src/views/activity/activity_renderer.js
+++ b/addons/mail/static/src/views/activity/activity_renderer.js
@@ -75,7 +75,7 @@ export class ActivityRenderer extends Component {
};
const typeId = group[0];
const isColumnFiltered = this.activeFilter.activityTypeId === group[0];
- const progressValue = isColumnFiltered ? this.activeFilter.progressValue : {active: null};
+ const progressValue = isColumnFiltered ? this.activeFilter.progressValue : { active: null };
let totalCount = 0;
for (const activities of Object.values(this.props.groupedActivities)) {
@@ -85,9 +85,11 @@ export class ActivityRenderer extends Component {
}
}
- const progressBars = [];
+ const progressBar = {
+ bars: [],
+ };
for (const [value, count] of Object.entries(types)) {
- progressBars.push({
+ progressBar.bars.push({
count: count.value,
value,
string: this.props.fields.activity_state.selection.find((e) => e[0] === value)[1],
@@ -103,7 +105,7 @@ export class ActivityRenderer extends Component {
data: {
count: totalCount,
filterProgressValue: (name) => this.onSetProgressBarState(typeId, name),
- progressBars,
+ progressBar,
progressValue,
},
};
@@ -113,7 +115,8 @@ export class ActivityRenderer extends Component {
return this.props.records.find((r) => r.resId === resId);
}
- onSetProgressBarState(typeId, name) {
+ onSetProgressBarState(typeId, bar) {
+ const name = bar.value;
if (this.activeFilter.progressValue.active === name) {
this.activeFilter.progressValue.active = null;
this.activeFilter.activityTypeId = null;
diff --git a/addons/mail/static/src/views/activity/activity_renderer.xml b/addons/mail/static/src/views/activity/activity_renderer.xml
index c819a453ad7..2cbee5ab561 100644
--- a/addons/mail/static/src/views/activity/activity_renderer.xml
+++ b/addons/mail/static/src/views/activity/activity_renderer.xml
@@ -24,7 +24,7 @@
-
+
diff --git a/addons/web/static/src/views/kanban/kanban_controller.js b/addons/web/static/src/views/kanban/kanban_controller.js
index 37926cf9ce3..12160785848 100644
--- a/addons/web/static/src/views/kanban/kanban_controller.js
+++ b/addons/web/static/src/views/kanban/kanban_controller.js
@@ -9,11 +9,13 @@ import { MultiRecordViewButton } from "@web/views/view_button/multi_record_view_
import { useViewButtons } from "@web/views/view_button/view_button_hook";
import { useSetupView } from "@web/views/view_hook";
import { KanbanRenderer } from "./kanban_renderer";
+import { useProgressBar } from "./progress_bar_hook";
import { SearchBar } from "@web/search/search_bar/search_bar";
import { useSearchBarToggler } from "@web/search/search_bar/search_bar_toggler";
import { CogMenu } from "@web/search/cog_menu/cog_menu";
import { Component, reactive, useRef } from "@odoo/owl";
+import { addDependencies } from "../utils";
const QUICK_CREATE_FIELD_TYPES = ["char", "boolean", "many2one", "selection", "many2many"];
@@ -24,6 +26,15 @@ export class KanbanController extends Component {
this.actionService = useService("action");
const { Model, archInfo } = this.props;
this.model = useModel(Model, this.modelParams);
+ if (archInfo.progressAttributes) {
+ const { activeBars } = this.props.state || {};
+ this.progressBarState = useProgressBar(
+ archInfo.progressAttributes,
+ this.model,
+ this.progressBarAggregateFields,
+ activeBars
+ );
+ }
this.headerButtons = archInfo.headerButtons;
const self = this;
@@ -56,6 +67,7 @@ export class KanbanController extends Component {
},
getLocalState: () => {
return {
+ activeBars: this.progressBarState?.activeBars,
rootState: this.model.root.exportState(),
};
},
@@ -85,9 +97,12 @@ export class KanbanController extends Component {
get modelParams() {
const { resModel, fields, archInfo, limit, defaultGroupBy, state } = this.props;
const { rootState } = state || {};
+
+ const activeFields = archInfo.activeFields;
+ addDependencies(this.progressBarAggregateFields, activeFields, fields);
+
return {
- activeFields: archInfo.activeFields,
- progressAttributes: archInfo.progressAttributes,
+ activeFields,
fields: { ...fields },
resModel,
handleField: archInfo.handleField,
@@ -97,10 +112,20 @@ export class KanbanController extends Component {
defaultOrder: archInfo.defaultOrder,
viewMode: "kanban",
openGroupsByDefault: true,
+ onRecordSaved: this.onRecordSaved.bind(this),
rootState,
};
}
+ get progressBarAggregateFields() {
+ const res = [];
+ const { progressAttributes } = this.props.archInfo;
+ if (progressAttributes && progressAttributes.sumField) {
+ res.push(progressAttributes.sumField);
+ }
+ return res;
+ }
+
get className() {
if (this.env.isSmall && this.model.root.isGrouped) {
const classList = (this.props.className || "").split(" ");
@@ -162,6 +187,15 @@ export class KanbanController extends Component {
return this.isQuickCreateField(list.groupByField);
}
+ onRecordSaved(record) {
+ if (this.model.root.isGrouped) {
+ const group = this.model.root.groups.find((l) =>
+ l.records.find((r) => r.id === record.id)
+ );
+ this.progressBarState?.updateCounts(group);
+ }
+ }
+
async beforeExecuteActionButton(clickParams) {}
async afterExecuteActionButton(clickParams) {}
diff --git a/addons/web/static/src/views/kanban/kanban_controller.xml b/addons/web/static/src/views/kanban/kanban_controller.xml
index d948992be94..7162b8c677c 100644
--- a/addons/web/static/src/views/kanban/kanban_controller.xml
+++ b/addons/web/static/src/views/kanban/kanban_controller.xml
@@ -49,6 +49,7 @@
scrollTop.bind="scrollTop"
canQuickCreate="canQuickCreate"
quickCreateState="quickCreateState"
+ progressBarState="progressBarState"
/>
diff --git a/addons/web/static/src/views/kanban/kanban_header.js b/addons/web/static/src/views/kanban/kanban_header.js
index 2e885ba6a4b..db150abf726 100644
--- a/addons/web/static/src/views/kanban/kanban_header.js
+++ b/addons/web/static/src/views/kanban/kanban_header.js
@@ -30,6 +30,7 @@ export class KanbanHeader extends Component {
quickCreateState: { type: Object },
scrollTop: { type: Function },
tooltipInfo: { type: Object },
+ progressBarState: { type: true, optional: true },
};
setup() {
@@ -59,6 +60,10 @@ export class KanbanHeader extends Component {
// Getters
// ------------------------------------------------------------------------
+ get progressBar() {
+ return this.props.progressBarState?.getGroupInfo(this.group);
+ }
+
get group() {
return this.props.group;
}
@@ -82,10 +87,9 @@ export class KanbanHeader extends Component {
}
get groupAggregate() {
- const { sumField } = this.group.model.progressAttributes;
- const value = this.group.getAggregates(sumField && sumField.name);
- const title = sumField ? sumField.string : this.env._t("Count");
- return { value, title };
+ const { group, progressBarState } = this.props;
+ const { sumField } = progressBarState.progressAttributes;
+ return progressBarState.getAggregateValue(group, sumField);
}
// ------------------------------------------------------------------------
@@ -121,13 +125,17 @@ export class KanbanHeader extends Component {
body: this.env._t(
"Are you sure that you want to archive all the records from this column?"
),
- confirm: () => this.group.list.archive(),
+ confirm: async () => {
+ await this.group.list.archive();
+ this.props.progressBarState?.updateCounts(this.group);
+ },
cancel: () => {},
});
}
unarchiveGroup() {
this.group.list.unarchive();
+ this.props.progressBarState?.updateCounts(this.group);
}
deleteGroup() {
@@ -190,4 +198,9 @@ export class KanbanHeader extends Component {
canQuickCreate() {
return this.props.canQuickCreate;
}
+
+ async onBarClicked(value) {
+ await this.props.progressBarState.selectBar(this.props.group.id, value);
+ this.props.scrollTop();
+ }
}
diff --git a/addons/web/static/src/views/kanban/kanban_header.xml b/addons/web/static/src/views/kanban/kanban_header.xml
index 8c4005cd7bd..6eb6fb6269a 100644
--- a/addons/web/static/src/views/kanban/kanban_header.xml
+++ b/addons/web/static/src/views/kanban/kanban_header.xml
@@ -51,8 +51,8 @@
-
diff --git a/addons/web/static/src/views/kanban/kanban_model.js b/addons/web/static/src/views/kanban/kanban_model.js
index 8ea5227b482..a673a63211b 100644
--- a/addons/web/static/src/views/kanban/kanban_model.js
+++ b/addons/web/static/src/views/kanban/kanban_model.js
@@ -1,6 +1,5 @@
/** @odoo-module **/
-import { Domain } from "@web/core/domain";
import {
DynamicGroupList,
DynamicRecordList,
@@ -9,17 +8,7 @@ import {
} from "@web/views/relational_model";
import { isRelational } from "@web/views/utils";
-/**
- * @typedef ProgressBar
- * @property {number} count
- * @property {any} value
- * @property {string} color
- * @property {string} string
- */
-
-import { EventBus, markRaw } from "@odoo/owl";
-
-const FALSE = Symbol("false");
+import { EventBus } from "@odoo/owl";
class TransactionInProgress extends Error {}
@@ -65,207 +54,18 @@ class KanbanGroup extends Group {
setup(_params, state = {}) {
super.setup(...arguments);
- /** @type {ProgressBar[]} */
- this.progressBars = this._generateProgressBars();
- this.progressValue = markRaw(state.progressValue || { active: null });
- this.list.domain = this.getProgressBarDomain();
this.tooltip = [];
this.model.transaction.register({
onStart: () => ({
count: this.count,
- progressBars: [...this.progressBars],
records: [...this.list.records],
}),
- onAbort: ({ count, progressBars, records }) => {
+ onAbort: ({ count, records }) => {
this.count = count;
- this.progressBars = progressBars;
this.list.records = records;
},
});
-
- this.model.bus.addEventListener("record-updated", ({ detail }) => {
- const { record, relatedRecords } = detail;
- const localIds = this.records.map((r) => r.id);
- const updatedIds = [record, ...relatedRecords].map((r) => r.id);
- if (localIds.some((id) => updatedIds.includes(id))) {
- this.model.bus.trigger("group-updated", {
- group: this,
- withProgressBars: true,
- });
- }
- });
- }
-
- get activeProgressBar() {
- return (
- this.hasActiveProgressValue &&
- this.progressBars.find((pv) => pv.value === this.progressValue.active)
- );
- }
-
- get hasActiveProgressValue() {
- return this.model.hasProgressBars && this.progressValue.active !== null;
- }
-
- /**
- * @override
- */
- async deleteRecords() {
- const records = await super.deleteRecords(...arguments);
- this.model.bus.trigger("group-updated", {
- group: this,
- withProgressBars: true,
- });
- return records;
- }
-
- /**
- * @override
- */
- empty() {
- super.empty();
-
- this.progressValue.active = null;
- for (const progressBar of this.progressBars) {
- progressBar.count = 0;
- }
- }
-
- /**
- * @override
- */
- exportState() {
- return {
- ...super.exportState(),
- progressValue: this.progressValue,
- };
- }
-
- /**
- * Checks if the current active progress bar value contains records, and
- * deactivates it if not.
- * @returns {Promise}
- */
- async checkActiveValue() {
- if (!this.hasActiveProgressValue) {
- return;
- }
- if (this.activeProgressBar.count === 0) {
- await this.filterProgressValue(null);
- }
- }
-
- async filterProgressValue(value) {
- this.progressValue.active = this.progressValue.active === value ? null : value;
- this.list.domain = this.getProgressBarDomain();
-
- // Do not update progress bars data when filtering on them.
- await Promise.all([this.list.load()]);
- this.model.bus.trigger("group-updated", { group: this, withProgressBars: false });
- }
-
- /**
- * @param {Object} record
- * @returns {ProgressBar}
- */
- findProgressValueFromRecord(record) {
- const { fieldName } = this.model.progressAttributes;
- const value = record.data[fieldName];
- return (
- this.progressBars.find((pv) => pv.value === value) ||
- this.progressBars.find((pv) => pv.value === FALSE)
- );
- }
-
- /**
- * @override
- */
- getAggregates(fieldName) {
- if (!this.hasActiveProgressValue) {
- return super.getAggregates(...arguments);
- }
- return fieldName ? this.aggregates[fieldName] : this.activeProgressBar.count;
- }
-
- getProgressBarDomain() {
- const { fieldName } = this.model.progressAttributes;
- const domains = [this.groupDomain];
- if (this.hasActiveProgressValue) {
- if (this.progressValue.active === FALSE) {
- const values = this.progressBars
- .map((pv) => pv.value)
- .filter((val) => val !== this.progressValue.active);
- domains.push(["!", [fieldName, "in", values]]);
- } else {
- domains.push([[fieldName, "=", this.progressValue.active]]);
- }
- }
- return Domain.and(domains).toList();
- }
-
- updateAggregates(groupData) {
- const fname = this.groupByField.name;
- const { sumField } = this.model.progressAttributes;
- const group = groupData.find((g) => this.valueEquals(g[fname]));
- if (sumField) {
- this.aggregates[sumField.name] = group ? group[sumField.name] : 0;
- }
- }
-
- /**
- * @param {Object} [progressData]
- * @returns {Promise}
- */
- async updateProgressData(progressData) {
- let value = this.displayName || this.value;
- if (value === true) {
- value = "True";
- } else if (value === false) {
- value = "False";
- }
- /** @type {Record} */
- const groupProgressData = progressData[value] || {};
- /** @type {Map} */
- const counts = new Map(
- groupProgressData ? Object.entries(groupProgressData) : [[FALSE, this.count]]
- );
- const total = [...counts.values()].reduce((acc, c) => acc + c, 0);
- counts.set(FALSE, this.count - total);
- for (const pv of this.progressBars) {
- pv.count = counts.get(pv.value) || 0;
- }
- await this.checkActiveValue();
- }
-
- // ------------------------------------------------------------------------
- // Protected
- // ------------------------------------------------------------------------
-
- /**
- * @returns {ProgressBar[]}
- */
- _generateProgressBars() {
- if (!this.model.hasProgressBars) {
- return [];
- }
- const { colors, fieldName } = this.model.progressAttributes;
- const { selection: fieldSelection } = this.fields[fieldName];
- /** @type {[string | typeof FALSE, string][]} */
- const colorEntries = Object.entries(colors);
- const selection = fieldSelection && Object.fromEntries(fieldSelection);
- colorEntries.push([FALSE, "200"]);
- return colorEntries.map(([value, color]) => {
- let string;
- if (value === FALSE) {
- string = this.model.env._t("Other");
- } else if (selection) {
- string = selection[value];
- } else {
- string = String(value);
- }
- return { count: 0, value, string, color };
- });
}
}
@@ -276,25 +76,6 @@ export class KanbanDynamicGroupList extends DynamicGroupList {
this.groupBy = this.groupBy.slice(0, 1);
this.limit = null;
-
- this.model.bus.addEventListener("group-updated", async ({ detail }) => {
- if (this.groups.some((g) => g.id === detail.group.id)) {
- await this.updateGroupProgressData([detail.group], detail.withProgressBars);
- this.model.notify();
- }
- });
- }
-
- get sumFields() {
- const { sumField } = this.model.progressAttributes;
- return sumField ? [sumField.name] : [];
- }
-
- /**
- * @override
- */
- get fieldNames() {
- return [...super.fieldNames, ...this.sumFields];
}
get currentParams() {
@@ -316,68 +97,16 @@ export class KanbanDynamicGroupList extends DynamicGroupList {
* @override
*/
async load() {
- const load = async () => {
- const previousGroups = this.groups.map((g, i) => [g, i]);
- await super.load();
- if (this.previousParams === this.currentParams) {
- for (const [group, index] of previousGroups) {
- const newGroup = this.groups.find((g) => group.valueEquals(g.value));
- if (!group.deleted && !newGroup) {
- group.empty();
- this.groups.splice(index, 0, group);
- }
+ const previousGroups = this.groups.map((g, i) => [g, i]);
+ await super.load();
+ if (this.previousParams === this.currentParams) {
+ for (const [group, index] of previousGroups) {
+ const newGroup = this.groups.find((g) => group.valueEquals(g.value));
+ if (!group.deleted && !newGroup) {
+ group.empty();
+ this.groups.splice(index, 0, group);
}
}
- };
- await this._loadWithProgressData(load());
- }
-
- /**
- * @param {KanbanGroup[]} groups
- * @param {boolean} withProgressBars
- * @returns {Promise}
- */
- async updateGroupProgressData(groups, withProgressBars) {
- if (!this.model.hasProgressBars) {
- return;
- }
-
- const gbFieldName = this.groupByField.name;
- const promises = {};
-
- if (withProgressBars) {
- const domain = Domain.or(groups.map((g) => g.groupDomain)).toList();
- promises.readProgressBar = this._fetchProgressData(domain);
- }
- // If we have a sumField, the aggregates must be re-fetched
- if (this.sumFields.length) {
- const domain = Domain.or(groups.map((g) => g.getProgressBarDomain())).toList();
- promises.webReadGroup = this.model.orm.webReadGroup(
- this.resModel,
- domain,
- this.sumFields,
- this.groupBy,
- { lazy: true }
- );
- }
-
- await Promise.all(Object.values(promises));
-
- // Update the aggregates for each group
- if (promises.webReadGroup) {
- const result = await promises.webReadGroup;
- const groupData = result.groups.map((group) => ({
- ...group,
- [gbFieldName]: this._getValueFromGroupData(group, this.firstGroupBy),
- }));
- for (const group of groups) {
- group.updateAggregates(groupData);
- }
- }
- // Update the progress bar data for each group
- if (promises.readProgressBar) {
- const result = await promises.readProgressBar;
- await Promise.all(groups.map((group) => group.updateProgressData(result)));
}
}
@@ -439,7 +168,6 @@ export class KanbanDynamicGroupList extends DynamicGroupList {
groupsToReload.push(targetGroup);
promises.push(record.load());
}
- promises.push(this.updateGroupProgressData(groupsToReload, true));
await Promise.all(promises);
}
@@ -453,40 +181,6 @@ export class KanbanDynamicGroupList extends DynamicGroupList {
return true;
}
-
- // ------------------------------------------------------------------------
- // Protected
- // ------------------------------------------------------------------------
-
- /**
- * @param {any[]} [domain]
- * @returns {Promise |