diff --git a/addons/project/models/project_project.py b/addons/project/models/project_project.py index 55f5e28d628..9b2fa22c956 100644 --- a/addons/project/models/project_project.py +++ b/addons/project/models/project_project.py @@ -555,6 +555,17 @@ class Project(models.Model): def action_project_task_burndown_chart_report(self): action = self.env['ir.actions.act_window']._for_xml_id('project.action_project_task_burndown_chart_report') action['display_name'] = _("%(name)s's Burndown Chart", name=self.name) + context = action['context'].replace('active_id', str(self.id)) + context = ast.literal_eval(context) + context.update({ + 'stage_name_and_sequence_per_id': { + stage.id: { + 'sequence': stage.sequence, + 'name': stage.name + } for stage in self.type_ids + } + }) + action['context'] = context return action def action_project_timesheets(self): @@ -764,6 +775,12 @@ class Project(models.Model): 'action': 'project.action_project_task_burndown_chart_report', 'additional_context': json.dumps({ 'active_id': self.id, + 'stage_name_and_sequence_per_id': { + stage.id: { + 'sequence': stage.sequence, + 'name': stage.name + } for stage in self.type_ids + }, }), 'show': True, 'sequence': 60, diff --git a/addons/project/static/src/views/burndown_chart/burndown_chart_model.js b/addons/project/static/src/views/burndown_chart/burndown_chart_model.js index 7850ffa5849..ec088acdb2a 100644 --- a/addons/project/static/src/views/burndown_chart/burndown_chart_model.js +++ b/addons/project/static/src/views/burndown_chart/burndown_chart_model.js @@ -1,14 +1,84 @@ /** @odoo-module **/ import { GraphModel } from "@web/views/graph/graph_model"; +import { sortBy } from "@web/core/utils/arrays"; export class BurndownChartModel extends GraphModel { + /** + * @override + */ + setup(params) { + super.setup(params); + this.stageSeqAndNamePerId = {}; + } + + /** + * Fetch the sequence of each stage in the project. This function alters this.stageSeqAndNamePerId + * @protected + * @param {Object} context + */ + async _fetchStageInfo(context) { + const searchDomain = + !context.active_id || !context.default_project_id + ? [] + : [["project_ids", "in", context.active_id]]; + const data = await this.orm.webSearchRead("project.task.type", searchDomain, [ + "name", + "sequence", + "id", + ]); + const stageSeqAndNamePerId = {}; + for (const { id, name, sequence } of data.records) { + stageSeqAndNamePerId[id] = { name, sequence }; + } + return stageSeqAndNamePerId; + } + + /** + * @param {SearchParams} searchParams + */ + async load(searchParams) { + const { context, groupBy } = searchParams; + + if (groupBy.includes("stage_id")) { + if (context.stage_name_and_sequence_per_id && context.default_project_id) { + this.stageSeqAndNamePerId = context.stage_name_and_sequence_per_id; + } else { + // if the stage_name_and_sequence_per_id wasn't given by the action (for example if the page is simply reloaded) + this.stageSeqAndNamePerId = await this._fetchStageInfo(context); + } + } else { + this.stageSeqAndNamePerId = {}; + } + await super.load(searchParams); + } + + /** + * @override + */ + _prepareData() { + super._prepareData(); + const { groupBy } = this.searchParams; + const { mode } = this.metaData; + if (mode === "line" && groupBy.includes("stage_id")) { + this.data.datasets = sortBy(this.data.datasets, (dataSet) => { + const firstIdentifier = [...dataSet.identifiers][0]; + const group = Object.assign(...JSON.parse(firstIdentifier)); + const val = group.stage_id; + if (Array.isArray(val)) { + return this.stageSeqAndNamePerId[val[0]]?.sequence || -1; + } + return -1; + }); + } + } + /** * @protected * @override */ async _loadDataPoints(metaData) { - metaData.measures.__count.string = this.env._t('# of Tasks'); + metaData.measures.__count.string = this.env._t("# of Tasks"); return super._loadDataPoints(metaData); } } diff --git a/addons/project/static/tests/burndown_chart_tests.js b/addons/project/static/tests/burndown_chart_tests.js index 94fb5e716f1..a4d658dec98 100644 --- a/addons/project/static/tests/burndown_chart_tests.js +++ b/addons/project/static/tests/burndown_chart_tests.js @@ -45,7 +45,13 @@ QUnit.module("Project", {}, () => { { id: 2, name: "In Progress" }, { id: 3, name: "Done" }, ], - } + }, + "project.task.type": { + fields: { + name: { string: "Name", type: "char" }, + sequence: { type: "integer" }, + }, + }, }, views: { "burndown_chart,false,graph": ` diff --git a/addons/project/views/project_task_views.xml b/addons/project/views/project_task_views.xml index a26d77f4bfa..06c285b8840 100644 --- a/addons/project/views/project_task_views.xml +++ b/addons/project/views/project_task_views.xml @@ -493,8 +493,15 @@
- - + + diff --git a/addons/web/static/src/views/graph/graph_model.js b/addons/web/static/src/views/graph/graph_model.js index 9ffddafe61b..47a58a01e7c 100644 --- a/addons/web/static/src/views/graph/graph_model.js +++ b/addons/web/static/src/views/graph/graph_model.js @@ -116,7 +116,7 @@ export class GraphModel extends Model { this.initialGroupBy = searchParams.context.graph_groupbys || this.metaData.groupBy; // = arch groupBy --> change that } const metaData = this._buildMetaData(); - return this._fetchDataPoints(metaData); + await this._fetchDataPoints(metaData); } /** @@ -243,7 +243,7 @@ export class GraphModel extends Model { // dataPoints + labels --> datasetsTmp --> datasets const datasetsTmp = {}; for (const dataPt of dataPoints) { - const { domain, labelIndex, originIndex, trueLabel, value } = dataPt; + const { domain, labelIndex, originIndex, trueLabel, value, identifier } = dataPt; const datasetLabel = this._getDatasetLabel(dataPt); if (!(datasetLabel in datasetsTmp)) { let dataLength = labels.length; @@ -256,11 +256,13 @@ export class GraphModel extends Model { domains: new Array(dataLength).fill([]), label: datasetLabel, originIndex: originIndex, + identifiers: new Set(), }; } datasetsTmp[datasetLabel].data[labelIndex] = value; datasetsTmp[datasetLabel].domains[labelIndex] = domain; datasetsTmp[datasetLabel].trueLabels[labelIndex] = trueLabel; + datasetsTmp[datasetLabel].identifiers.add(identifier); } // sort by origin let datasets = sortBy(Object.values(datasetsTmp), "originIndex"); @@ -446,10 +448,11 @@ export class GraphModel extends Model { for (const group of data.groups) { const { __domain, __count } = group; const labels = []; - + const rawValues = []; for (const gb of groupBy) { let label; const val = group[gb.spec]; + rawValues.push({ [gb.spec]: val }); const fieldName = gb.fieldName; const { type } = fields[fieldName]; if (type === "boolean") { @@ -493,6 +496,7 @@ export class GraphModel extends Model { value, labels, originIndex, + identifier: JSON.stringify(rawValues), }); } return dataPoints; @@ -553,7 +557,7 @@ export class GraphModel extends Model { /** * @protected */ - async _prepareData() { + _prepareData() { const processedDataPoints = this._getProcessedDataPoints(); this.data = this._getData(processedDataPoints); this.lineOverlayDataset = null;