Skip to content

Commit 9fb65d6

Browse files
committed
[FIX] xlsx: do not export dynamic tables to excel
Excel does not support array formulas inside tables. With this commit, if a table contains an array formula, it will not be exported to excel but be exported as individual cell styles instead. closes #8160 Task: 5214240 X-original-commit: de067dd Signed-off-by: Adrien Minne (adrm) <adrm@odoo.com> Signed-off-by: Rémi Rahir (rar) <rar@odoo.com>
1 parent 6626b46 commit 9fb65d6

File tree

2 files changed

+69
-8
lines changed

2 files changed

+69
-8
lines changed

src/plugins/ui_core_views/dynamic_tables.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import {
22
areZonesContinuous,
33
deepEquals,
4+
getItemId,
45
getZoneArea,
56
isInside,
7+
isObjectEmptyRecursive,
68
overlap,
9+
positions,
10+
toXC,
711
toZone,
812
union,
9-
zoneToXc,
1013
} from "../../helpers";
1114
import { createFilter } from "../../helpers/table_helpers";
1215
import { CellErrorType } from "../../types/errors";
@@ -15,6 +18,7 @@ import {
1518
Command,
1619
CoreTable,
1720
DynamicTable,
21+
ExcelTableData,
1822
ExcelWorkbookData,
1923
Filter,
2024
FilterId,
@@ -224,19 +228,51 @@ export class DynamicTablesPlugin extends CoreViewPlugin {
224228
return tableId + "_" + tableCol;
225229
}
226230

231+
/** Check if a table contains array formula, as we cannot have them in a table in Excel */
232+
private isTableExcelExportable(sheetId: UID, table: CoreTable): boolean {
233+
if (table.type === "dynamic") {
234+
return false;
235+
}
236+
237+
return !positions(table.range.zone).some((position) =>
238+
this.getters.getArrayFormulaSpreadingOn({ sheetId, ...position })
239+
);
240+
}
241+
227242
exportForExcel(data: ExcelWorkbookData) {
228243
for (const sheet of data.sheets) {
244+
const exportedTables: ExcelTableData[] = [];
229245
for (const tableData of sheet.tables) {
230246
const zone = toZone(tableData.range);
231247
const topLeft = { sheetId: sheet.id, col: zone.left, row: zone.top };
232248
const coreTable = this.getters.getCoreTable(topLeft);
233249
const table = this.getTable(topLeft);
234250

235-
if (coreTable?.type !== "dynamic" || !table) {
251+
if (!coreTable || !table || this.isTableExcelExportable(sheet.id, coreTable)) {
252+
exportedTables.push(tableData);
236253
continue;
237254
}
238-
tableData.range = zoneToXc(table.range.zone);
255+
sheet.styles = sheet.styles || {};
256+
sheet.borders = sheet.borders || {};
257+
258+
// If the table is not exportable to excel, we apply the cell styles to individual cells instead
259+
for (const position of positions(table.range.zone)) {
260+
const cellPosition = { sheetId: sheet.id, ...position };
261+
const style = this.getters.getCellComputedStyle(cellPosition);
262+
const border = this.getters.getCellComputedBorder(cellPosition);
263+
const xc = toXC(position.col, position.row);
264+
if (!isObjectEmptyRecursive(style)) {
265+
const styleId = getItemId(style, data.styles);
266+
sheet.styles[xc] = styleId;
267+
}
268+
269+
if (border) {
270+
const borderId = getItemId(border, data.borders);
271+
sheet.borders[xc] = borderId;
272+
}
273+
}
239274
}
275+
sheet.tables = exportedTables;
240276
}
241277
}
242278
}

tests/table/dynamic_table_plugin.test.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Model } from "../../src";
1+
import { BorderDescr, Model } from "../../src";
22
import { toZone, zoneToXc } from "../../src/helpers";
33
import { UID } from "../../src/types";
44
import {
@@ -239,12 +239,37 @@ describe("Dynamic tables", () => {
239239
expect(getTables(model, sheetId)).toMatchObject([{ zone: "A1:B2" }]);
240240
});
241241

242-
test("Dynamic tables are transformed into static tables when exporting for excel", () => {
243-
setCellContent(model, "A1", "=MUNIT(3)");
244-
createDynamicTable(model, "A1");
242+
test("Dynamic table is exported as individual cell style to Excel", async () => {
243+
setCellContent(model, "A1", "=MUNIT(2)");
244+
createDynamicTable(model, "A1", { styleId: "TableStyleLight8" });
245245

246246
const exported = getExportedExcelData(model);
247-
expect(exported.sheets[0].tables).toMatchObject([{ range: "A1:C3" }]);
247+
const sheetData = exported.sheets[0];
248+
expect(sheetData.tables).toHaveLength(0);
249+
expect(exported.styles).toEqual({
250+
"1": { fillColor: "#000000", textColor: "#FFFFFF", bold: true },
251+
});
252+
const border: BorderDescr = { color: "#000000", style: "thin" };
253+
expect(exported.borders).toEqual({
254+
"1": { top: border, left: border, bottom: border },
255+
"2": { top: border, bottom: border, right: border },
256+
});
257+
expect(sheetData.borders).toEqual({ A1: 1, A2: 1, B1: 2, B2: 2 });
258+
expect(sheetData.styles).toEqual({ A1: 1, B1: 1 });
259+
});
260+
261+
test("Tables that contains an array formula are also exported as individual cell styles", async () => {
262+
setCellContent(model, "A2", "=MUNIT(2)");
263+
createTable(model, "A1:B3", { styleId: "TableStyleLight8" });
264+
265+
const exported = getExportedExcelData(model);
266+
const sheetData = exported.sheets[0];
267+
expect(sheetData.tables).toHaveLength(0);
268+
expect(exported.styles).toEqual({
269+
"1": { fillColor: "#000000", textColor: "#FFFFFF", bold: true },
270+
});
271+
272+
expect(sheetData.styles).toEqual({ A1: 1, B1: 1 });
248273
});
249274
});
250275
});

0 commit comments

Comments
 (0)