[FIX] web: missing measures in graph view

Steps to reproduce
==================

- Install account_accountant
- Go to settings
- Enable Budget Management
- Go to Accounting > Reporting > Management > Budgets Analysis
- Switch to the graph view
- Change the measure to "Planned amount" and then back to
  "Practical amount"

=> The practical_amount measure is undefined,
   The theoritical_amount measure is missing.

Cause of the issue
==================

The view is defined as follows:

```xml
<graph string="Budget Lines" sample="1">
    <field name="crossovered_budget_id" type="row"/>
    <field name="planned_amount"  type="measure" string="Planned amount"/>
    <field name="theoritical_amount"  type="measure" string="Theoretical amount"/>
    <field name="practical_amount" type="measure" string="Practical amount"/>
</graph>
```

The theoritical_amount and practical_amount are non stored fields and
thus are skipped inside `computeReportMeasures` unless they are passed
in `activeMeasures | additionalMeasures`. [0]

When parsing the graph view, the last field of type measure is
passed to the graph model and is the one that will be used initially. [1]

This is why the practical_amount is initially defined.

Solution
========

We simply need to keep track of fields of type measure.
This was the case in 14.0 but got lost in the conversion.

---

[0]: https://github.com/odoo/odoo/blob/e7a9ebec3176c37485643fcda2381e489a1df86f/addons/web/static/src/views/helpers/utils.js#L49-L60
[1]: https://github.com/odoo/odoo/blob/0fb64bef16914937cf4a1d1618fb58ade6d16f14/addons/web/static/src/views/graph/graph_arch_parser.js#L63

opw-3713613

closes odoo/odoo#153967

X-original-commit: 76178cd61ba10ff29b24183edb550c54b319a230
Signed-off-by: Mathieu Duckerts-Antoine (dam) <dam@odoo.com>
Signed-off-by: Hubert Van De Walle <huvw@odoo.com>
This commit is contained in:
Hubert Van De Walle
2024-02-14 17:17:00 +00:00
parent ef1d087efd
commit d8ca1f3412
4 changed files with 51 additions and 2 deletions
@@ -9,7 +9,7 @@ const ORDERS = ["ASC", "DESC", "asc", "desc", null];
export class GraphArchParser {
parse(arch, fields = {}) {
const archInfo = { fields, fieldAttrs: {}, groupBy: [] };
const archInfo = { fields, fieldAttrs: {}, groupBy: [], measures: [] };
visitXML(arch, (node) => {
switch (node.tagName) {
case "graph": {
@@ -67,6 +67,7 @@ export class GraphArchParser {
}
const isMeasure = node.getAttribute("type") === "measure";
if (isMeasure) {
archInfo.measures.push(fieldName);
// the last field with type="measure" (if any) will be used as measure else __count
archInfo.measure = fieldName;
} else {
@@ -182,6 +182,7 @@ export class GraphModel extends Model {
this._normalize(metaData);
metaData.measures = computeReportMeasures(metaData.fields, metaData.fieldAttrs, [
...(metaData.viewMeasures || []),
metaData.measure,
]);
@@ -37,6 +37,7 @@ export const graphView = {
fields: fields,
groupBy: archInfo.groupBy,
measure: archInfo.measure || "__count",
viewMeasures: archInfo.measures,
mode: archInfo.mode || "bar",
order: archInfo.order || null,
resModel: resModel,
@@ -2440,7 +2440,7 @@ QUnit.module("Views", (hooks) => {
QUnit.test("process default view description", async function (assert) {
assert.expect(1);
const propsFromArch = new GraphArchParser().parse();
assert.deepEqual(propsFromArch, { fields: {}, fieldAttrs: {}, groupBy: [] });
assert.deepEqual(propsFromArch, { fields: {}, fieldAttrs: {}, groupBy: [], measures: [] });
});
QUnit.test("process simple arch (no field tag)", async function (assert) {
@@ -2454,6 +2454,7 @@ QUnit.module("Views", (hooks) => {
fields,
fieldAttrs: {},
groupBy: [],
measures: [],
mode: "line",
order: "ASC",
});
@@ -2465,6 +2466,7 @@ QUnit.module("Views", (hooks) => {
fields,
fieldAttrs: {},
groupBy: [],
measures: [],
stacked: false,
title: "Title",
});
@@ -2492,11 +2494,33 @@ QUnit.module("Views", (hooks) => {
fighters: { string: "FooFighters" },
},
measure: "revenue",
measures: ["revenue"],
groupBy: ["date:day", "foo"],
mode: "pie",
});
});
QUnit.test("process arch with non stored field tags of type measure", async function (assert) {
assert.expect(1);
const fields = serverData.models.foo.fields;
fields.revenue.store = false;
const arch = `
<graph>
<field name="product_id"/>
<field name="revenue" type="measure"/>
<field name="foo" type="measure"/>
</graph>
`;
const propsFromArch = new GraphArchParser().parse(arch, fields);
assert.deepEqual(propsFromArch, {
fields,
fieldAttrs: {},
measure: "foo",
measures: ["revenue", "foo"],
groupBy: ["product_id"],
});
});
QUnit.test("displaying chart data with three groupbys", async function (assert) {
// this test makes sure the line chart shows all data labels (X axis) when
// it is grouped by several fields
@@ -3078,6 +3102,28 @@ QUnit.module("Views", (hooks) => {
assert.strictEqual(getYAxeLabel(graph), "Product");
});
QUnit.test(
"non store fields defined on the arch are present in the measures",
async function (assert) {
serverData.models.foo.fields.revenue.store = false;
await makeView({
serverData,
type: "graph",
resModel: "foo",
arch: `<graph>
<field name="product_id"/>
<field name="revenue" type="measure"/>
<field name="foo" type="measure"/>
</graph>`,
});
await toggleMenu(target, "Measures");
assert.deepEqual(
Array.from(target.querySelectorAll(".o_menu_item")).map((e) => e.innerText.trim()),
["Foo", "Revenue", "Count"]
);
}
);
QUnit.test('graph view "graph_measure" field in context', async function (assert) {
assert.expect(6);
const graph = await makeView({