[FIX] crm: update progressbars after adding forecast column
Impacted versions: 16.3+ How to reproduce: - open CRM > Forecast - drag&drop a lead from a column to another - click on "add next month" button Current Behavior: - the progressbars revert back to their state before the drag&drop Expected Behavior: - the progressbars keep their updated value after the drag&drop Technical explanation: After [1], the split of the progress bar logic from the relational model, only notifying the model of a reload is not enough to correctly update the progress bar values. It is therefore required to manually do it in the CRM Forecast view after adding a column. Master update: Furthermore, after [2], the adaptation of the codebase to the new relational model, the domain modification of the `forecast_kanban_model` was not transcripted correctly: the domain modification for the fillTemporalPeriod should be added by replacing the previous one if present, not simply added (because then we have a constantly growing domain with contradictory leaves). Also, since the new `RelationalModel` makes it mandatory to render the view after a `DynamicGroupList.load` because `DynamicGroupList` is `Reactive`, to avoid `progressBars` flickering when adding a forecast kanban column, the `ProgressBarHook._updateProgressBar` should store the result of its `read_progress_bar` call in `_pbCounts`. This makes it so that when the `DynamicGroupList.load` is done, already computed progressBars will keep their value and not switch back and forth between the previous state of `_pbCounts` computed in `loadProgressBar`. [1]: https://github.com/odoo/odoo/commit/58ca40b03215ef4c6c575267494dc8bccc30a033 [2]: https://github.com/odoo/odoo/commit/218ad8456a06503dd508e7216edcffdc90b35cac task-3497579 closes odoo/odoo#135149 X-original-commit: b53b46ec36818615b7beb1d24fd44c9f8d69063a Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com> Signed-off-by: Damien Abeloos (abd) <abd@odoo.com>
This commit is contained in:
@@ -8,6 +8,8 @@ export class ForecastKanbanModel extends CrmKanbanModel {
|
||||
super.setup(...arguments);
|
||||
this.fillTemporalService = fillTemporalService;
|
||||
this.forceNextRecompute = !params.state?.groups;
|
||||
this.originalDomain = null;
|
||||
this.fillTemporalDomain = null;
|
||||
}
|
||||
|
||||
async _webReadGroup(config, firstGroupByName, orderBy) {
|
||||
@@ -15,10 +17,20 @@ export class ForecastKanbanModel extends CrmKanbanModel {
|
||||
config.context = this.fillTemporalPeriod(config).getContext({
|
||||
context: config.context,
|
||||
});
|
||||
config.domain = this.fillTemporalPeriod(config).getDomain({
|
||||
domain: config.domain,
|
||||
forceStartBound: false,
|
||||
});
|
||||
// Domain leaves added by the fillTemporalPeriod should be replaced
|
||||
// between 2 _webReadGroup calls, not added on top of each other.
|
||||
// Keep track of the modified domain, and if encountered in the
|
||||
// future, modify the original domain instead. It is not robust
|
||||
// against external modification of `config.domain`, but currently
|
||||
// there are only replacements except this case.
|
||||
if (!this.originalDomain || this.fillTemporalDomain !== config.domain) {
|
||||
this.originalDomain = config.domain || [];
|
||||
}
|
||||
this.fillTemporalDomain = this.fillTemporalPeriod(config).getDomain({
|
||||
domain: this.originalDomain,
|
||||
forceStartBound: false,
|
||||
});
|
||||
config.domain = this.fillTemporalDomain;
|
||||
}
|
||||
return super._webReadGroup(...arguments);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export class ForecastKanbanRenderer extends CrmKanbanRenderer {
|
||||
})
|
||||
.expand();
|
||||
await this.props.list.load();
|
||||
await this.props.progressBarState?._updateProgressBar();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { fillTemporalService } from "@crm/views/fill_temporal_service";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import {
|
||||
click,
|
||||
dragAndDrop,
|
||||
getFixture,
|
||||
patchDate,
|
||||
} from '@web/../tests/helpers/utils';
|
||||
@@ -263,3 +264,118 @@ QUnit.module('Crm Fill Temporal Service', {
|
||||
}).end.toFormat('yyyy-MM-dd'), '2023-01-01');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.module('Crm Forecast main flow with progressBars', (hooks) => {
|
||||
function getCounters() {
|
||||
return [...target.querySelectorAll(".o_animated_number")].map((counter) => counter.innerText);
|
||||
}
|
||||
|
||||
function getProgressBarsColors() {
|
||||
return [...target.querySelectorAll(".o_column_progress")].map(columnProgressEl => {
|
||||
return [...columnProgressEl.querySelectorAll(".progress-bar")].map(progressBarEl => {
|
||||
return [...progressBarEl.classList.values()].filter(htmlClass => {
|
||||
return htmlClass.startsWith('bg-');
|
||||
})[0];
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
hooks.beforeEach(() => {
|
||||
serviceRegistry.add("fillTemporalService", fillTemporalService);
|
||||
this.testKanbanView = {
|
||||
arch: `
|
||||
<kanban js_class="forecast_kanban">
|
||||
<progressbar field="color" colors='{"s": "success", "w": "warning", "d": "danger"}' sum_field="int_field"/>
|
||||
<field name="date_deadline"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="name"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
searchViewArch: `
|
||||
<search>
|
||||
<filter name="forecast" string="Forecast" context="{'forecast_filter':1}"/>
|
||||
<filter name='groupby_date_deadline' context="{'group_by':'date_deadline'}"/>
|
||||
</search>`
|
||||
,
|
||||
serverData: {
|
||||
models: {
|
||||
'crm.lead': {
|
||||
fields: {
|
||||
color: {string: 'Color', type: 'string'},
|
||||
date_deadline: {string: 'Expected Closing', type: 'date'},
|
||||
int_field: {string: 'Value', type: 'integer', sortable: true},
|
||||
name: {string: 'Name', type: 'char'},
|
||||
},
|
||||
},
|
||||
},
|
||||
views: {},
|
||||
},
|
||||
resModel: 'crm.lead',
|
||||
type: "kanban",
|
||||
context: {
|
||||
search_default_forecast: true,
|
||||
search_default_groupby_date_deadline: true,
|
||||
forecast_field: 'date_deadline',
|
||||
},
|
||||
groupBy: ['date_deadline'],
|
||||
};
|
||||
target = getFixture();
|
||||
setupViewRegistries();
|
||||
// first september 2023
|
||||
patchDate(2023, 8, 1, 0, 0, 0);
|
||||
});
|
||||
|
||||
QUnit.test("Forecast drag&drop and add column", async (assert) => {
|
||||
this.testKanbanView.serverData.models['crm.lead'].records = [
|
||||
{id: 1, int_field: 7, color: 'd', name: 'Lead 1', date_deadline: '2023-09-03'},
|
||||
{id: 2, int_field: 20, color: 'w', name: 'Lead 2', date_deadline: '2023-09-05'},
|
||||
{id: 3, int_field: 300, color: 's', name: 'Lead 3', date_deadline: '2023-10-10'},
|
||||
];
|
||||
|
||||
await makeView({
|
||||
...this.testKanbanView,
|
||||
async mockRPC(route, args) {
|
||||
assert.step(args.method || route);
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(getCounters(), ["27", "300"]);
|
||||
assert.deepEqual(getProgressBarsColors(), [["bg-warning", "bg-danger"], ["bg-success"]]);
|
||||
|
||||
await dragAndDrop(
|
||||
".o_kanban_group:first-child .o_kanban_record",
|
||||
".o_kanban_group:nth-child(2)"
|
||||
);
|
||||
|
||||
assert.deepEqual(getCounters(), ["20", "307"]);
|
||||
assert.deepEqual(getProgressBarsColors(), [["bg-warning"], ["bg-success", "bg-danger"]]);
|
||||
|
||||
await click(target, ".o_kanban_add_column");
|
||||
|
||||
// Counters and progressbars should be unchanged after adding a column.
|
||||
assert.deepEqual(getCounters(), ["20", "307"]);
|
||||
assert.deepEqual(getProgressBarsColors(), [["bg-warning"], ["bg-success", "bg-danger"]]);
|
||||
|
||||
assert.verifySteps([
|
||||
// makeView
|
||||
"get_views",
|
||||
"web_read_group",
|
||||
"read_progress_bar",
|
||||
"web_search_read",
|
||||
"web_search_read",
|
||||
// drag&drop
|
||||
"web_save",
|
||||
"read_progress_bar",
|
||||
"web_read_group",
|
||||
// add column
|
||||
"web_read_group",
|
||||
"web_search_read",
|
||||
"web_search_read",
|
||||
"read_progress_bar"
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -263,6 +263,7 @@ class ProgressBarState {
|
||||
if (groupsId !== this.model.root.groups.map((g) => g.id).join()) {
|
||||
return;
|
||||
}
|
||||
this._pbCounts = res;
|
||||
for (const group of this.model.root.groups) {
|
||||
if (!group.isFolded) {
|
||||
const groupInfo = this.getGroupInfo(group);
|
||||
|
||||
Reference in New Issue
Block a user