[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:
abd-msyukyu-odoo
2023-09-13 08:20:36 +00:00
parent 03547479f7
commit 6b74a84e44
4 changed files with 134 additions and 4 deletions
@@ -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);