390 lines
12 KiB
JavaScript
390 lines
12 KiB
JavaScript
/** @odoo-module */
|
|
|
|
import * as spreadsheet from "@odoo/o-spreadsheet";
|
|
import { OdooCorePlugin } from "@spreadsheet/plugins";
|
|
const { tokenize, parse, convertAstNodes, astToFormula } = spreadsheet;
|
|
const { corePluginRegistry, migrationStepRegistry } = spreadsheet.registries;
|
|
|
|
export const ODOO_VERSION = 12;
|
|
|
|
const MAP_V1 = {
|
|
PIVOT: "ODOO.PIVOT",
|
|
"PIVOT.HEADER": "ODOO.PIVOT.HEADER",
|
|
"PIVOT.POSITION": "ODOO.PIVOT.POSITION",
|
|
"FILTER.VALUE": "ODOO.FILTER.VALUE",
|
|
LIST: "ODOO.LIST",
|
|
"LIST.HEADER": "ODOO.LIST.HEADER",
|
|
};
|
|
|
|
const MAP_FN_NAMES_V10 = {
|
|
"ODOO.PIVOT": "PIVOT.VALUE",
|
|
"ODOO.PIVOT.HEADER": "PIVOT.HEADER",
|
|
"ODOO.PIVOT.TABLE": "PIVOT",
|
|
};
|
|
|
|
const dmyRegex = /^([0|1|2|3][1-9])\/(0[1-9]|1[0-2])\/(\d{4})$/i;
|
|
|
|
migrationStepRegistry.add("odoo_migration", {
|
|
versionFrom: "16.1",
|
|
migrate(data) {
|
|
return migrateOdooData(data);
|
|
},
|
|
});
|
|
|
|
function migrateOdooData(data) {
|
|
const version = data.odooVersion || 0;
|
|
if (version < 1) {
|
|
data = migrate0to1(data);
|
|
}
|
|
if (version < 2) {
|
|
data = migrate1to2(data);
|
|
}
|
|
if (version < 3) {
|
|
data = migrate2to3(data);
|
|
}
|
|
if (version < 4) {
|
|
data = migrate3to4(data);
|
|
}
|
|
if (version < 5) {
|
|
data = migrate4to5(data);
|
|
}
|
|
if (version < 6) {
|
|
data = migrate5to6(data);
|
|
}
|
|
if (version < 7) {
|
|
data = migrate6to7(data);
|
|
}
|
|
if (version < 8) {
|
|
data = migrate7to8(data);
|
|
}
|
|
if (version < 9) {
|
|
data = migrate8to9(data);
|
|
}
|
|
if (version < 10) {
|
|
data = migrate9to10(data);
|
|
}
|
|
if (version < 11) {
|
|
data = migrate10to11(data);
|
|
}
|
|
if (version < 12) {
|
|
data = migrate11to12(data);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function parseDimension(dimension) {
|
|
const [name, granularity] = dimension.split(":");
|
|
if (granularity) {
|
|
return { name, granularity };
|
|
}
|
|
return { name };
|
|
}
|
|
|
|
function renameFunctions(data, map) {
|
|
for (const sheet of data.sheets || []) {
|
|
for (const xc in sheet.cells || []) {
|
|
const cell = sheet.cells[xc];
|
|
if (cell.content && cell.content.startsWith("=")) {
|
|
const tokens = tokenize(cell.content);
|
|
for (const token of tokens) {
|
|
if (token.type === "SYMBOL" && token.value.toUpperCase() in map) {
|
|
token.value = map[token.value.toUpperCase()];
|
|
}
|
|
}
|
|
cell.content = tokensToString(tokens);
|
|
}
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function tokensToString(tokens) {
|
|
return tokens.reduce((acc, token) => acc + token.value, "");
|
|
}
|
|
|
|
function migrate0to1(data) {
|
|
return renameFunctions(data, MAP_V1);
|
|
}
|
|
|
|
function migrate1to2(data) {
|
|
for (const sheet of data.sheets || []) {
|
|
for (const xc in sheet.cells || []) {
|
|
const cell = sheet.cells[xc];
|
|
if (cell.content && cell.content.startsWith("=")) {
|
|
try {
|
|
cell.content = migratePivotDaysParameters(cell.content);
|
|
} catch {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Migration of global filters
|
|
*/
|
|
function migrate2to3(data) {
|
|
if (data.globalFilters) {
|
|
for (const gf of data.globalFilters) {
|
|
if (gf.fields) {
|
|
gf.pivotFields = gf.fields;
|
|
delete gf.fields;
|
|
}
|
|
if (
|
|
gf.type === "date" &&
|
|
typeof gf.defaultValue === "object" &&
|
|
"year" in gf.defaultValue
|
|
) {
|
|
switch (gf.defaultValue.year) {
|
|
case "last_year":
|
|
gf.defaultValue.yearOffset = -1;
|
|
break;
|
|
case "antepenultimate_year":
|
|
gf.defaultValue.yearOffset = -2;
|
|
break;
|
|
case "this_year":
|
|
case undefined:
|
|
gf.defaultValue.yearOffset = 0;
|
|
break;
|
|
}
|
|
delete gf.defaultValue.year;
|
|
}
|
|
if (!gf.listFields) {
|
|
gf.listFields = {};
|
|
}
|
|
if (!gf.graphFields) {
|
|
gf.graphFields = {};
|
|
}
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Migration of list/pivot names
|
|
*/
|
|
function migrate3to4(data) {
|
|
if (data.lists) {
|
|
for (const list of Object.values(data.lists)) {
|
|
list.name = list.name || list.model;
|
|
}
|
|
}
|
|
if (data.pivots) {
|
|
for (const pivot of Object.values(data.pivots)) {
|
|
pivot.name = pivot.name || pivot.model;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function migrate4to5(data) {
|
|
for (const filter of data.globalFilters || []) {
|
|
for (const [id, fm] of Object.entries(filter.pivotFields || {})) {
|
|
if (!(data.pivots && id in data.pivots)) {
|
|
delete filter.pivotFields[id];
|
|
continue;
|
|
}
|
|
if (!data.pivots[id].fieldMatching) {
|
|
data.pivots[id].fieldMatching = {};
|
|
}
|
|
data.pivots[id].fieldMatching[filter.id] = { chain: fm.field, type: fm.type };
|
|
if ("offset" in fm) {
|
|
data.pivots[id].fieldMatching[filter.id].offset = fm.offset;
|
|
}
|
|
}
|
|
delete filter.pivotFields;
|
|
|
|
for (const [id, fm] of Object.entries(filter.listFields || {})) {
|
|
if (!(data.lists && id in data.lists)) {
|
|
delete filter.listFields[id];
|
|
continue;
|
|
}
|
|
if (!data.lists[id].fieldMatching) {
|
|
data.lists[id].fieldMatching = {};
|
|
}
|
|
data.lists[id].fieldMatching[filter.id] = { chain: fm.field, type: fm.type };
|
|
if ("offset" in fm) {
|
|
data.lists[id].fieldMatching[filter.id].offset = fm.offset;
|
|
}
|
|
}
|
|
delete filter.listFields;
|
|
|
|
const findFigureFromId = (id) => {
|
|
for (const sheet of data.sheets) {
|
|
const fig = sheet.figures.find((f) => f.id === id);
|
|
if (fig) {
|
|
return fig;
|
|
}
|
|
}
|
|
return undefined;
|
|
};
|
|
for (const [id, fm] of Object.entries(filter.graphFields || {})) {
|
|
const figure = findFigureFromId(id);
|
|
if (!figure) {
|
|
delete filter.graphFields[id];
|
|
continue;
|
|
}
|
|
if (!figure.data.fieldMatching) {
|
|
figure.data.fieldMatching = {};
|
|
}
|
|
figure.data.fieldMatching[filter.id] = { chain: fm.field, type: fm.type };
|
|
if ("offset" in fm) {
|
|
figure.data.fieldMatching[filter.id].offset = fm.offset;
|
|
}
|
|
}
|
|
delete filter.graphFields;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Convert pivot formulas days parameters from day/month/year
|
|
* format to the standard spreadsheet month/day/year format.
|
|
* e.g. =PIVOT.HEADER(1,"create_date:day","30/07/2022") becomes =PIVOT.HEADER(1,"create_date:day","07/30/2022")
|
|
* @param {string} formulaString
|
|
* @returns {string}
|
|
*/
|
|
function migratePivotDaysParameters(formulaString) {
|
|
const ast = parse(formulaString);
|
|
const convertedAst = convertAstNodes(ast, "FUNCALL", (ast) => {
|
|
if (["ODOO.PIVOT", "ODOO.PIVOT.HEADER"].includes(ast.value.toUpperCase())) {
|
|
for (const subAst of ast.args) {
|
|
if (subAst.type === "STRING") {
|
|
const date = subAst.value.match(dmyRegex);
|
|
if (date) {
|
|
subAst.value = `${[date[2], date[1], date[3]].join("/")}`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ast;
|
|
});
|
|
return "=" + astToFormula(convertedAst);
|
|
}
|
|
|
|
function migrate5to6(data) {
|
|
if (!data.globalFilters?.length) {
|
|
return data;
|
|
}
|
|
for (const filter of data.globalFilters) {
|
|
if (filter.type === "date" && ["year", "quarter", "month"].includes(filter.rangeType)) {
|
|
if (filter.defaultsToCurrentPeriod) {
|
|
filter.defaultValue = `this_${filter.rangeType}`;
|
|
}
|
|
filter.rangeType = "fixedPeriod";
|
|
}
|
|
delete filter.defaultsToCurrentPeriod;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Migrate the pivot data to add the type, by default "ODOO". And replace the
|
|
* pivot with a new object that contains type and definition (the old pivot).
|
|
*/
|
|
function migrate6to7(data) {
|
|
if (data.pivots) {
|
|
for (const [id, definition] of Object.entries(data.pivots)) {
|
|
definition.measures = definition.measures.map((measure) => measure.field);
|
|
const fieldMatching = definition.fieldMatching;
|
|
delete definition.fieldMatching;
|
|
data.pivots[id] = {
|
|
type: "ODOO",
|
|
definition,
|
|
fieldMatching,
|
|
};
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function migrate7to8(data) {
|
|
if (data.pivots) {
|
|
for (const [id, pivot] of Object.entries(data.pivots)) {
|
|
data.pivots[id] = {
|
|
type: pivot.type,
|
|
fieldMatching: pivot.fieldMatching,
|
|
...pivot.definition,
|
|
};
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function migrate8to9(data) {
|
|
if (data.pivots) {
|
|
for (const id of Object.keys(data.pivots)) {
|
|
data.pivots[id].formulaId = id;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function migrate9to10(data) {
|
|
return renameFunctions(data, MAP_FN_NAMES_V10);
|
|
}
|
|
|
|
function migrate10to11(data) {
|
|
if (data.pivots) {
|
|
for (const pivot of Object.values(data.pivots)) {
|
|
pivot.measures = pivot.measures.map((measure) => ({
|
|
name: measure,
|
|
}));
|
|
pivot.columns = pivot.colGroupBys.map(parseDimension);
|
|
delete pivot.colGroupBys;
|
|
pivot.rows = pivot.rowGroupBys.map(parseDimension);
|
|
delete pivot.rowGroupBys;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function migrate11to12(data) {
|
|
// remove the calls to ODOO.PIVOT.POSITION and replace
|
|
// the previous argument to a relative position
|
|
for (const sheet of data.sheets || []) {
|
|
for (const xc in sheet.cells || []) {
|
|
const cell = sheet.cells[xc];
|
|
if (
|
|
cell.content &&
|
|
cell.content.startsWith("=") &&
|
|
cell.content.includes("ODOO.PIVOT.POSITION")
|
|
) {
|
|
const tokens = tokenize(cell.content);
|
|
/* given that odoo.pivot.position is automatically set, we know that:
|
|
1) it is always on the form of ODOO.PIVOT.POSITION(1, ...)
|
|
2) it is always preceded by a dimension of a pivot or header, inside another pivot formula
|
|
3) there is only one odoo.pivot.position per cell
|
|
4) odoo.pivot.position can only exist after the 3rd token and needs at least 7 tokens to be valid*/
|
|
for (let i = 2; i < tokens.length - 7; i++) {
|
|
const token = tokens[i];
|
|
if (
|
|
token.type === "SYMBOL" &&
|
|
token.value.toUpperCase() === "ODOO.PIVOT.POSITION"
|
|
) {
|
|
const order = tokens[i + 6];
|
|
tokens[i - 2].value = '"#' + tokens[i - 2].value.slice(1); // "dimension" becomes "#dimension"
|
|
tokens.splice(i, 7); // remove "ODOO.PIVOT.POSITION", "(", "1", ",", "dimension", ", ", order
|
|
// tokens[i-1] is the comma before odoo.pivot.position
|
|
tokens[i] = order;
|
|
cell.content = tokensToString(tokens);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
export class OdooVersion extends OdooCorePlugin {
|
|
static getters = /** @type {const} */ ([]);
|
|
|
|
export(data) {
|
|
data.odooVersion = ODOO_VERSION;
|
|
}
|
|
}
|
|
|
|
corePluginRegistry.add("odooMigration", OdooVersion);
|