From 037e5b823be9e44b8efd6cb7a42711fd136fc2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Lef=C3=A8vre=20=28lul=29?= Date: Thu, 16 Nov 2023 14:27:14 +0100 Subject: [PATCH] [FIX] spreadsheet: update o_spreadsheet to latest version https://github.com/odoo/o-spreadsheet/commit/8bd0fb2b4 [REL] 17.0.3 https://github.com/odoo/o-spreadsheet/commit/fe3ba2a88 [FIX] text_helper: strikethrough wrong positioning https://github.com/odoo/o-spreadsheet/commit/742581e80 [IMP] demo: add demo data for data validation https://github.com/odoo/o-spreadsheet/commit/0323dd37a [FIX] FiltersPlugin: removing row with data filter header Task: 3546012 https://github.com/odoo/o-spreadsheet/commit/fc2a17482 [FIX] FilterEvaluationPlugin: insert/remove rows on data filter header Task: 3546012 https://github.com/odoo/o-spreadsheet/commit/3c3bbe9a2 [MOV] tests: fix typo in test file name of context menu https://github.com/odoo/o-spreadsheet/commit/c53528f7c [FIX] F&R: Fix css rule https://github.com/odoo/o-spreadsheet/commit/b6fa9634f [FIX] Menus: Show Insert col/row menu when full sheet selected Task: 3450188 https://github.com/odoo/o-spreadsheet/commit/80bd3b30f [REV] Charts: Do not rely on history for the chartRuntimes Task: 3578417 https://github.com/odoo/o-spreadsheet/commit/3d6f4f954 [IMP] evaluation: improve perfs of `getCorrespondingFormulaCell` Task: 3584306 https://github.com/odoo/o-spreadsheet/commit/17ba12211 [FIX] cell: update cell tokens and dependencies Task: 3584306 https://github.com/odoo/o-spreadsheet/commit/c0cb4cf86 [FIX] find_and_replace: take array formula result into account Task: 3413999 [IMP] spreadsheet: remove useless tokenize calls The pivot/list helpers were tokenizing the cell's content when trying to find list/pivot formulas. But we can skip the tokenize call as we already have the tokens in the cell's compiledFormula. closes odoo/odoo#142323 Task: 3584306 Related: odoo/enterprise#50888 Signed-off-by: Adrien Minne (adrm) Co-authored-by: Adrien Minne --- .../src/helpers/odoo_functions_helpers.js | 16 +- .../static/src/list/list_actions.js | 7 +- .../static/src/list/list_helpers.js | 14 +- .../static/src/list/plugins/list_ui_plugin.js | 16 +- .../static/src/o_spreadsheet/o_spreadsheet.js | 221 +++++++++++------- .../src/o_spreadsheet/o_spreadsheet.xml | 6 +- .../static/src/pivot/pivot_actions.js | 5 +- .../static/src/pivot/pivot_helpers.js | 14 +- .../src/pivot/plugins/pivot_ui_plugin.js | 33 ++- .../global_filters_model_test.js | 37 +-- .../static/tests/pivots/pivot_helpers_test.js | 40 ++-- .../spreadsheet/static/tests/utils/getters.js | 2 +- .../spreadsheet_account/static/src/index.js | 4 +- .../spreadsheet_account/static/src/utils.js | 17 +- 14 files changed, 264 insertions(+), 168 deletions(-) diff --git a/addons/spreadsheet/static/src/helpers/odoo_functions_helpers.js b/addons/spreadsheet/static/src/helpers/odoo_functions_helpers.js index 056fcdb7eae..b1e3ccd48b4 100644 --- a/addons/spreadsheet/static/src/helpers/odoo_functions_helpers.js +++ b/addons/spreadsheet/static/src/helpers/odoo_functions_helpers.js @@ -2,33 +2,37 @@ import * as spreadsheet from "@odoo/o-spreadsheet"; -const { parse, iterateAstNodes } = spreadsheet; +const { parseTokens, iterateAstNodes } = spreadsheet; /** * @typedef {Object} OdooFunctionDescription * @property {string} functionName Name of the function * @property {Array} args Arguments of the function + * + * @typedef {Object} Token + * @property {string} type + * @property {string} value + */ /** * This function is used to search for the functions which match the given matcher * from the given formula * - * @param {string} formula + * @param {Token[]} tokens * @param {string[]} functionNames e.g. ["ODOO.LIST", "ODOO.LIST.HEADER"] * @private * @returns {Array} */ -export function getOdooFunctions(formula, functionNames) { - const formulaUpperCased = formula.toUpperCase(); +export function getOdooFunctions(tokens, functionNames) { // Parsing is an expensive operation, so we first check if the // formula contains one of the function names - if (!functionNames.some((fn) => formulaUpperCased.includes(fn.toUpperCase()))) { + if (!tokens.some((t) => t.type === "SYMBOL" && functionNames.includes(t.value.toUpperCase()))) { return []; } let ast; try { - ast = parse(formula); + ast = parseTokens(tokens); } catch { return []; } diff --git a/addons/spreadsheet/static/src/list/list_actions.js b/addons/spreadsheet/static/src/list/list_actions.js index ace1d774752..1abdead8577 100644 --- a/addons/spreadsheet/static/src/list/list_actions.js +++ b/addons/spreadsheet/static/src/list/list_actions.js @@ -9,7 +9,7 @@ export const SEE_RECORD_LIST = async (position, env) => { if (!cell) { return; } - const { args } = getFirstListFunction(cell.content); + const { args } = getFirstListFunction(cell.compiledFormula.tokens); const evaluatedArgs = args .map(astToFormula) .map((arg) => env.model.getters.evaluateFormula(sheetId, arg)); @@ -36,7 +36,8 @@ export const SEE_RECORD_LIST_VISIBLE = (position, env) => { evaluatedCell.type !== "empty" && evaluatedCell.type !== "error" && cell && - getNumberOfListFormulas(cell.content) === 1 && - getFirstListFunction(cell.content).functionName === "ODOO.LIST" + cell.isFormula && + getNumberOfListFormulas(cell.compiledFormula.tokens) === 1 && + getFirstListFunction(cell.compiledFormula.tokens).functionName === "ODOO.LIST" ); }; diff --git a/addons/spreadsheet/static/src/list/list_helpers.js b/addons/spreadsheet/static/src/list/list_helpers.js index 9eea03d90f9..d44d7b6fabd 100644 --- a/addons/spreadsheet/static/src/list/list_helpers.js +++ b/addons/spreadsheet/static/src/list/list_helpers.js @@ -2,25 +2,27 @@ import { getOdooFunctions } from "../helpers/odoo_functions_helpers"; +/** @typedef {import("@spreadsheet/helpers/odoo_functions_helpers").Token} Token */ + /** * Parse a spreadsheet formula and detect the number of LIST functions that are * present in the given formula. * - * @param {string} formula + * @param {Token[]} tokens * * @returns {number} */ -export function getNumberOfListFormulas(formula) { - return getOdooFunctions(formula, ["ODOO.LIST", "ODOO.LIST.HEADER"]).length; +export function getNumberOfListFormulas(tokens) { + return getOdooFunctions(tokens, ["ODOO.LIST", "ODOO.LIST.HEADER"]).length; } /** * Get the first List function description of the given formula. * - * @param {string} formula + * @param {Token[]} tokens * * @returns {import("../helpers/odoo_functions_helpers").OdooFunctionDescription|undefined} */ -export function getFirstListFunction(formula) { - return getOdooFunctions(formula, ["ODOO.LIST", "ODOO.LIST.HEADER"])[0]; +export function getFirstListFunction(tokens) { + return getOdooFunctions(tokens, ["ODOO.LIST", "ODOO.LIST.HEADER"])[0]; } diff --git a/addons/spreadsheet/static/src/list/plugins/list_ui_plugin.js b/addons/spreadsheet/static/src/list/plugins/list_ui_plugin.js index a5958296515..47ddfadd126 100644 --- a/addons/spreadsheet/static/src/list/plugins/list_ui_plugin.js +++ b/addons/spreadsheet/static/src/list/plugins/list_ui_plugin.js @@ -53,7 +53,7 @@ export class ListUIPlugin extends spreadsheet.UIPlugin { const cells = this.getters.getCells(sheetId); for (const cell of Object.values(cells)) { if (cell.isFormula) { - this._addListPositionToDataSource(cell.content); + this._addListPositionToDataSource(cell); } } } @@ -87,7 +87,11 @@ export class ListUIPlugin extends spreadsheet.UIPlugin { } case "UPDATE_CELL": if (cmd.content) { - this._addListPositionToDataSource(cmd.content); + const position = { sheetId: cmd.sheetId, col: cmd.col, row: cmd.row }; + const cell = this.getters.getCell(position); + if (cell && cell.isFormula) { + this._addListPositionToDataSource(cell); + } } break; case "UNDO": @@ -204,11 +208,11 @@ export class ListUIPlugin extends spreadsheet.UIPlugin { * * @param {string} content Odoo list formula */ - _addListPositionToDataSource(content) { - if (getNumberOfListFormulas(content) !== 1) { + _addListPositionToDataSource(cell) { + if (getNumberOfListFormulas(cell.compiledFormula.tokens) !== 1) { return; } - const { functionName, args } = getFirstListFunction(content); + const { functionName, args } = getFirstListFunction(cell.compiledFormula.tokens); if (functionName !== "ODOO.LIST") { return; } @@ -254,7 +258,7 @@ export class ListUIPlugin extends spreadsheet.UIPlugin { const cell = this.getters.getCell(position); const sheetId = position.sheetId; if (cell && cell.isFormula) { - const listFunction = getFirstListFunction(cell.content); + const listFunction = getFirstListFunction(cell.compiledFormula.tokens); if (listFunction) { const content = astToFormula(listFunction.args[0]); return this.getters.evaluateFormula(sheetId, content).toString(); diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js index 3542cccab95..00d35dad4cd 100644 --- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js +++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js @@ -2,9 +2,9 @@ /** * This file is generated by o-spreadsheet build tools. Do not edit it. * @see https://github.com/odoo/o-spreadsheet - * @version 17.0.2 - * @date 2023-11-03T12:32:51.249Z - * @hash 3184728ea + * @version 17.0.3 + * @date 2023-11-16T13:27:10.083Z + * @hash 8bd0fb2b4 */ (function (exports, owl) { @@ -4541,14 +4541,14 @@ switch (context.textBaseline) { case "top": underlineY += boxHeight - 2 * strokeWidth; - strikeY += boxHeight - textHeight; + strikeY += boxHeight / 2 - strokeWidth; break; case "middle": underlineY += boxHeight / 2 - strokeWidth; break; case "alphabetic": underlineY += 2 * strokeWidth; - strikeY -= textHeight / 2 - strokeWidth / 2; + strikeY -= 3 * strokeWidth; break; case "bottom": underlineY = y; @@ -5967,7 +5967,7 @@ return { cellData: {} }; } const sheetId = data.sheetId; - const content = getters.getTranslatedCellFormula(sheetId, x, y, cell.compiledFormula, cell.dependencies); + const content = getters.getTranslatedCellFormula(sheetId, x, y, cell.compiledFormula); return { cellData: { border: data.border, @@ -10694,6 +10694,17 @@ const IS_ONLY_ONE_RANGE = (env) => { return env.model.getters.getSelectedZones().length === 1; }; + const CAN_INSERT_HEADER = (env, dimension) => { + if (!IS_ONLY_ONE_RANGE(env)) { + return false; + } + const activeHeaders = dimension === "COL" ? env.model.getters.getActiveCols() : env.model.getters.getActiveRows(); + const ortogonalActiveHeaders = dimension === "COL" ? env.model.getters.getActiveRows() : env.model.getters.getActiveCols(); + const sheetId = env.model.getters.getActiveSheetId(); + const zone = env.model.getters.getSelectedZone(); + const allSheetSelected = isEqual(zone, env.model.getters.getSheetZone(sheetId)); + return isConsecutive(activeHeaders) && (ortogonalActiveHeaders.size === 0 || allSheetSelected); + }; const undo = { name: _t("Undo"), @@ -19371,9 +19382,7 @@ const number = getRowsNumber(env); return number === 1 ? _t("Insert row") : _t("Insert %s rows", number.toString()); }, - isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) && - IS_ONLY_ONE_RANGE(env) && - env.model.getters.getActiveCols().size === 0, + isVisible: (env) => CAN_INSERT_HEADER(env, "ROW"), icon: "o-spreadsheet-Icon.INSERT_ROW", }; const rowInsertRowBefore = { @@ -19382,9 +19391,7 @@ return number === 1 ? _t("Insert row above") : _t("Insert %s rows above", number.toString()); }, execute: INSERT_ROWS_BEFORE_ACTION, - isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) && - IS_ONLY_ONE_RANGE(env) && - env.model.getters.getActiveCols().size === 0, + isVisible: (env) => CAN_INSERT_HEADER(env, "ROW"), icon: "o-spreadsheet-Icon.INSERT_ROW_BEFORE", }; const topBarInsertRowsBefore = { @@ -19415,9 +19422,7 @@ const number = getRowsNumber(env); return number === 1 ? _t("Insert row below") : _t("Insert %s rows below", number.toString()); }, - isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) && - IS_ONLY_ONE_RANGE(env) && - env.model.getters.getActiveCols().size === 0, + isVisible: (env) => CAN_INSERT_HEADER(env, "ROW"), icon: "o-spreadsheet-Icon.INSERT_ROW_AFTER", }; const topBarInsertRowsAfter = { @@ -19435,9 +19440,7 @@ const number = getColumnsNumber(env); return number === 1 ? _t("Insert column") : _t("Insert %s columns", number.toString()); }, - isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) && - IS_ONLY_ONE_RANGE(env) && - env.model.getters.getActiveRows().size === 0, + isVisible: (env) => CAN_INSERT_HEADER(env, "COL"), icon: "o-spreadsheet-Icon.INSERT_COL", }; const colInsertColsBefore = { @@ -19448,9 +19451,7 @@ : _t("Insert %s columns left", number.toString()); }, execute: INSERT_COLUMNS_BEFORE_ACTION, - isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) && - IS_ONLY_ONE_RANGE(env) && - env.model.getters.getActiveRows().size === 0, + isVisible: (env) => CAN_INSERT_HEADER(env, "COL"), icon: "o-spreadsheet-Icon.INSERT_COL_BEFORE", }; const topBarInsertColsBefore = { @@ -19483,9 +19484,7 @@ : _t("Insert %s columns right", number.toString()); }, execute: INSERT_COLUMNS_AFTER_ACTION, - isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) && - IS_ONLY_ONE_RANGE(env) && - env.model.getters.getActiveRows().size === 0, + isVisible: (env) => CAN_INSERT_HEADER(env, "COL"), icon: "o-spreadsheet-Icon.INSERT_COL_AFTER", }; const topBarInsertColsAfter = { @@ -23826,7 +23825,7 @@ } .o-input-count { width: fit-content; - padding: 4 0 4 4; + padding: 4px 0 4px 4px; } } } @@ -34947,6 +34946,9 @@ // ----------------------------------------------------------------------------- function compile(formula) { const tokens = rangeTokenize(formula); + return compileTokens(tokens); + } + function compileTokens(tokens) { const { dependencies, constantValues } = formulaArguments(tokens); const cacheKey = compilationCacheKey(tokens, dependencies, constantValues); if (!functionCache[cacheKey]) { @@ -35313,11 +35315,11 @@ for (const sheet of Object.keys(this.cells)) { for (const cell of Object.values(this.cells[sheet] || {})) { if (cell.isFormula) { - for (const range of cell.dependencies) { + for (const range of cell.compiledFormula.dependencies) { if (!sheetId || range.sheetId === sheetId) { const change = applyChange(range); if (change.changeType !== "NONE") { - this.history.update("cells", sheet, cell.id, "dependencies", cell.dependencies.indexOf(range), change.range); + this.history.update("cells", sheet, cell.id, "compiledFormula", "dependencies", cell.compiledFormula.dependencies.indexOf(range), change.range); } } } @@ -35513,10 +35515,10 @@ /* * Reconstructs the original formula string based on a normalized form and its dependencies */ - getFormulaCellContent(sheetId, cell, dependencies) { - const ranges = dependencies || cell.dependencies; + getFormulaCellContent(sheetId, compiledFormula, dependencies) { + const ranges = dependencies || compiledFormula.dependencies; let rangeIndex = 0; - return concat(cell.compiledFormula.tokens.map((token) => { + return concat(compiledFormula.tokens.map((token) => { if (token.type === "REFERENCE") { const range = ranges[rangeIndex++]; return this.getters.getRangeString(range, sheetId); @@ -35527,12 +35529,9 @@ /* * Constructs a formula string based on an initial formula and a translation vector */ - getTranslatedCellFormula(sheetId, offsetX, offsetY, compiledFormula, dependencies) { - const adaptedDependencies = this.getters.createAdaptedRanges(dependencies, offsetX, offsetY, sheetId); - return this.getFormulaCellContent(sheetId, { - compiledFormula, - dependencies: adaptedDependencies, - }); + getTranslatedCellFormula(sheetId, offsetX, offsetY, compiledFormula) { + const adaptedDependencies = this.getters.createAdaptedRanges(compiledFormula.dependencies, offsetX, offsetY, sheetId); + return this.getFormulaCellContent(sheetId, compiledFormula, adaptedDependencies); } getCellStyle(position) { return this.getters.getCell(position)?.style || {}; @@ -35712,8 +35711,10 @@ style, format, isFormula: true, - compiledFormula, - dependencies: [], + compiledFormula: { + ...compiledFormula, + dependencies: [], + }, }; } /** @@ -35722,7 +35723,7 @@ */ createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId) { const dependencies = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc)); - return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getFormulaCellContent.bind(this)); + return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString.bind(this)); } createErrorFormula(id, content, format, style, error) { return { @@ -35732,13 +35733,12 @@ format, isFormula: true, compiledFormula: { - dependencies: [], tokens: tokenize(content), + dependencies: [], execute: function () { throw error; }, }, - dependencies: [], }; } checkCellOutOfSheet(sheetId, col, row) { @@ -35751,27 +35751,44 @@ } class FormulaCellWithDependencies { id; - compiledFormula; format; style; - dependencies; sheetId; - getFormulaCellContent; + getRangeString; isFormula = true; - constructor(id, compiledFormula, format, style, dependencies, sheetId, getFormulaCellContent) { + compiledFormula; + constructor(id, compiledFormula, format, style, dependencies, sheetId, getRangeString) { this.id = id; - this.compiledFormula = compiledFormula; this.format = format; this.style = style; - this.dependencies = dependencies; this.sheetId = sheetId; - this.getFormulaCellContent = getFormulaCellContent; + this.getRangeString = getRangeString; + let rangeIndex = 0; + const tokens = compiledFormula.tokens.map((token) => { + if (token.type === "REFERENCE") { + const index = rangeIndex++; + return new RangeReferenceToken(() => this.getRangeString(dependencies[index], this.sheetId)); + } + return token; + }); + this.compiledFormula = { + ...compiledFormula, + dependencies, + tokens, + }; } get content() { - return this.getFormulaCellContent(this.sheetId, { - dependencies: this.dependencies, - compiledFormula: this.compiledFormula, - }); + return concat(this.compiledFormula.tokens.map((token) => token.value)); + } + } + class RangeReferenceToken { + getValue; + type = "REFERENCE"; + constructor(getValue) { + this.getValue = getValue; + } + get value() { + return this.getValue(); } } @@ -36916,6 +36933,13 @@ } onDeleteColumnsRows(cmd) { for (const table of this.getFilterTables(cmd.sheetId)) { + // Remove the filter tables whose data filter headers are in the removed rows. + if (cmd.dimension === "ROW" && cmd.elements.includes(table.zone.top)) { + const tables = { ...this.tables[cmd.sheetId] }; + delete tables[table.id]; + this.history.update("tables", cmd.sheetId, tables); + continue; + } const zone = reduceZoneOnDeletion(table.zone, cmd.dimension === "COL" ? "left" : "top", cmd.elements); if (!zone) { const tables = { ...this.tables[cmd.sheetId] }; @@ -39964,6 +39988,13 @@ return (this.evaluatedCells.get(this.encodePosition(position)) || createEvaluatedCell("", { locale: this.getters.getLocale() })); } + getSpreadPositionsOf(position) { + const positionId = this.encodePosition(position); + if (!this.spreadingRelations.isArrayFormula(positionId)) { + return []; + } + return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.decodePosition.bind(this)); + } getArrayFormulaSpreadingOn(position) { const positionId = this.encodePosition(position); const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId); @@ -40122,7 +40153,7 @@ const position = this.compilationParams[2].getters.getCellPosition(cellId); return toXC(position.col, position.row); }; - const formulaReturn = cellData.compiledFormula.execute(cellData.dependencies, ...this.compilationParams); + const formulaReturn = cellData.compiledFormula.execute(cellData.compiledFormula.dependencies, ...this.compilationParams); if (!isMatrix(formulaReturn)) { return createEvaluatedCell(formulaReturn.value, { format: cellData.format || formulaReturn.format, @@ -40222,7 +40253,7 @@ return []; } const dependencies = []; - for (const range of cell.dependencies) { + for (const range of cell.compiledFormula.dependencies) { if (range.invalidSheetName || range.invalidXc) { continue; } @@ -40428,6 +40459,7 @@ "getEvaluatedCell", "getEvaluatedCells", "getEvaluatedCellsInZone", + "getSpreadPositionsOf", ]; shouldRebuildDependenciesGraph = true; evaluator; @@ -40537,6 +40569,9 @@ getEvaluatedCellsInZone(sheetId, zone) { return positions(zone).map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId, col, row })); } + getSpreadPositionsOf(position) { + return this.evaluator.getSpreadPositionsOf(position); + } // --------------------------------------------------------------------------- // Export // --------------------------------------------------------------------------- @@ -40574,10 +40609,10 @@ */ getCorrespondingFormulaCell(position) { const cell = this.getters.getCell(position); - if (cell && cell.content) { - if (cell.isFormula && !isBadExpression(cell.content)) { - return cell; - } + if (cell && cell.isFormula) { + return isBadExpression(cell.compiledFormula.tokens) ? undefined : cell; + } + else if (cell && cell.content) { return undefined; } const spreadingFormulaPosition = this.evaluator.getArrayFormulaSpreadingOn(position); @@ -40594,9 +40629,9 @@ return buildCompilationParameters(this.config.custom, this.getters, (position) => this.evaluator.getEvaluatedCell(position)); } } - function isBadExpression(formula) { + function isBadExpression(tokens) { try { - compile(formula); + compileTokens(tokens); return false; } catch (error) { @@ -40765,22 +40800,20 @@ invalidateCFEvaluationCommands.has(cmd.type) || cmd.type === "EVALUATE_CELLS" || cmd.type === "UPDATE_CELL") { - if (cmd.type !== "UNDO" && cmd.type !== "REDO") { - for (const chartId in this.charts) { - this.history.update("charts", chartId, undefined); - } + for (const chartId in this.charts) { + this.charts[chartId] = undefined; } } switch (cmd.type) { case "UPDATE_CHART": case "CREATE_CHART": case "DELETE_FIGURE": - this.history.update("charts", cmd.id, undefined); + this.charts[cmd.id] = undefined; break; case "DELETE_SHEET": for (let chartId in this.charts) { if (!this.getters.isChartDefined(chartId)) { - this.history.update("charts", chartId, undefined); + this.charts[chartId] = undefined; } } break; @@ -40792,8 +40825,7 @@ if (!chart) { throw new Error(`No chart for the given id: ${figureId}`); } - const runtime = this.createRuntimeChart(chart); - this.history.update("charts", figureId, runtime); + this.charts[figureId] = this.createRuntimeChart(chart); } return this.charts[figureId]; } @@ -40945,7 +40977,10 @@ const values = cf.rule.values.map((value, i) => { const compiledFormula = formulas[i]; if (compiledFormula) { - return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, compiledFormula, compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d))); + return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, { + ...compiledFormula, + dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)), + }); } return value; }); @@ -41317,7 +41352,10 @@ } try { const formula = compile(value); - const translatedFormula = this.getters.getTranslatedCellFormula(sheetId, offset.col, offset.row, formula, formula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d))); + const translatedFormula = this.getters.getTranslatedCellFormula(sheetId, offset.col, offset.row, { + ...formula, + dependencies: formula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)), + }); const evaluated = this.getters.evaluateFormula(sheetId, translatedFormula); return evaluated && !isMatrix(evaluated) ? evaluated.toString() : ""; } @@ -43569,7 +43607,7 @@ } let content = origin.cell.content; if (origin.cell.isFormula && operation === "COPY") { - content = this.getters.getTranslatedCellFormula(sheetId, col - origin.position.col, row - origin.position.row, origin.cell.compiledFormula, origin.cell.dependencies); + content = this.getters.getTranslatedCellFormula(sheetId, col - origin.position.col, row - origin.position.row, origin.cell.compiledFormula); } this.dispatch("UPDATE_CELL", { ...target, @@ -44101,20 +44139,29 @@ const sheetId = this.getters.getActiveSheetId(); const cells = this.getters.getCells(sheetId); const matches = []; - if (this.toSearch) { + if (this.toSearch && this.currentSearchRegex) { for (const cell of Object.values(cells)) { const { col, row } = this.getters.getCellPosition(cell.id); + const cellPosition = { sheetId, col, row }; const isColHidden = this.getters.isColHidden(sheetId, col); const isRowHidden = this.getters.isRowHidden(sheetId, row); if (isColHidden || isRowHidden) { continue; } - if (cell && - this.currentSearchRegex && - this.currentSearchRegex.test(this.getSearchableString({ sheetId, col, row }))) { + if (this.currentSearchRegex.test(this.getSearchableString(cellPosition))) { const match = { col, row, selected: false }; matches.push(match); } + for (const spreadPosition of this.getters.getSpreadPositionsOf(cellPosition)) { + if (this.currentSearchRegex.test(this.getSearchableString(spreadPosition))) { + const match = { + col: spreadPosition.col, + row: spreadPosition.row, + selected: false, + }; + matches.push(match); + } + } } } return matches.sort(this.sortByRowThenColumn); @@ -44173,16 +44220,19 @@ return; } const sheetId = this.getters.getActiveSheetId(); - const cell = this.getters.getCell({ sheetId, ...selectedMatch }); - const { col, row } = selectedMatch; + const position = { sheetId, ...selectedMatch }; + const cell = this.getters.getCell(position); + if (!cell?.content) { + return; + } if (cell?.isFormula && !this.searchOptions.searchFormulas) { return; } const replaceRegex = new RegExp(this.currentSearchRegex.source, this.currentSearchRegex.flags + "g"); - const toReplace = this.getSearchableString({ sheetId, col, row }); + const toReplace = this.getSearchableString(position); const content = toReplace.replace(replaceRegex, replaceWith); const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale()); - this.dispatch("UPDATE_CELL", { sheetId, col, row, content: canonicalContent }); + this.dispatch("UPDATE_CELL", { ...position, content: canonicalContent }); } /** * Replace the value of the currently selected match @@ -45757,7 +45807,7 @@ if (cell.isFormula) { const position = this.getters.getCellPosition(cell.id); // we only have a vertical offset - content = this.getters.getTranslatedCellFormula(sheetId, 0, newRow - position.row, cell.compiledFormula, cell.dependencies); + content = this.getters.getTranslatedCellFormula(sheetId, 0, newRow - position.row, cell.compiledFormula); } newCellValues.style = cell.style; newCellValues.content = content; @@ -47820,6 +47870,8 @@ case "EVALUATE_CELLS": case "ACTIVATE_SHEET": case "REMOVE_FILTER_TABLE": + case "ADD_COLUMNS_ROWS": + case "REMOVE_COLUMNS_ROWS": this.isEvaluationDirty = true; break; case "START": @@ -55812,7 +55864,6 @@ GaugeChartDesignPanel, ScorecardChartConfigPanel, ScorecardChartDesignPanel, - SelectionInput, FigureComponent, Menu, SelectionInput, @@ -55844,6 +55895,7 @@ exports.addFunction = addFunction; exports.astToFormula = astToFormula; exports.compile = compile; + exports.compileTokens = compileTokens; exports.components = components; exports.constants = constants; exports.convertAstNodes = convertAstNodes; @@ -55859,6 +55911,7 @@ exports.links = links; exports.load = load; exports.parse = parse; + exports.parseTokens = parseTokens; exports.readonlyAllowedCommands = readonlyAllowedCommands; exports.registries = registries; exports.setDefaultSheetViewSize = setDefaultSheetViewSize; @@ -55866,9 +55919,9 @@ exports.tokenize = tokenize; - __info__.version = "17.0.2"; - __info__.date = "2023-11-03T12:32:51.249Z"; - __info__.hash = "3184728ea"; + __info__.version = "17.0.3"; + __info__.date = "2023-11-16T13:27:10.083Z"; + __info__.hash = "8bd0fb2b4"; })(this.o_spreadsheet = this.o_spreadsheet || {}, owl); diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml index 7b245cdedc8..c79ca7a4257 100644 --- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml +++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml @@ -1,9 +1,9 @@ diff --git a/addons/spreadsheet/static/src/pivot/pivot_actions.js b/addons/spreadsheet/static/src/pivot/pivot_actions.js index cb4113ad10d..8e41669bada 100644 --- a/addons/spreadsheet/static/src/pivot/pivot_actions.js +++ b/addons/spreadsheet/static/src/pivot/pivot_actions.js @@ -32,7 +32,8 @@ export const SEE_RECORDS_PIVOT_VISIBLE = (position, env) => { evaluatedCell.type !== "error" && argsDomain !== undefined && cell && - getNumberOfPivotFormulas(cell.content) === 1 + cell.isFormula && + getNumberOfPivotFormulas(cell.compiledFormula.tokens) === 1 ); }; @@ -56,7 +57,7 @@ export function SET_FILTER_MATCHING_CONDITION(position, env) { return false; } const matchingFilters = env.model.getters.getFiltersMatchingPivotArgs(pivotId, domainArgs); - const pivotFunction = getFirstPivotFunction(cell.content).functionName; + const pivotFunction = getFirstPivotFunction(cell.compiledFormula.tokens).functionName; return ( SEE_RECORDS_PIVOT_VISIBLE(position, env) && (pivotFunction === "ODOO.PIVOT" || diff --git a/addons/spreadsheet/static/src/pivot/pivot_helpers.js b/addons/spreadsheet/static/src/pivot/pivot_helpers.js index 7c2be294a51..54402ffbc0b 100644 --- a/addons/spreadsheet/static/src/pivot/pivot_helpers.js +++ b/addons/spreadsheet/static/src/pivot/pivot_helpers.js @@ -3,6 +3,8 @@ import { _t } from "@web/core/l10n/translation"; import { getOdooFunctions } from "../helpers/odoo_functions_helpers"; +/** @typedef {import("@spreadsheet/helpers/odoo_functions_helpers").Token} Token */ + export const pivotFormulaRegex = /^=.*PIVOT/; //-------------------------------------------------------------------------- @@ -13,12 +15,12 @@ export const pivotFormulaRegex = /^=.*PIVOT/; * Parse a spreadsheet formula and detect the number of PIVOT functions that are * present in the given formula. * - * @param {string} formula + * @param {Token[]} tokens * * @returns {number} */ -export function getNumberOfPivotFormulas(formula) { - return getOdooFunctions(formula, [ +export function getNumberOfPivotFormulas(tokens) { + return getOdooFunctions(tokens, [ "ODOO.PIVOT", "ODOO.PIVOT.HEADER", "ODOO.PIVOT.POSITION", @@ -29,12 +31,12 @@ export function getNumberOfPivotFormulas(formula) { /** * Get the first Pivot function description of the given formula. * - * @param {string} formula + * @param {Token[]} tokens * * @returns {import("../helpers/odoo_functions_helpers").OdooFunctionDescription|undefined} */ -export function getFirstPivotFunction(formula) { - return getOdooFunctions(formula, [ +export function getFirstPivotFunction(tokens) { + return getOdooFunctions(tokens, [ "ODOO.PIVOT", "ODOO.PIVOT.HEADER", "ODOO.PIVOT.POSITION", diff --git a/addons/spreadsheet/static/src/pivot/plugins/pivot_ui_plugin.js b/addons/spreadsheet/static/src/pivot/plugins/pivot_ui_plugin.js index 7a5f382804c..fca02712d3b 100644 --- a/addons/spreadsheet/static/src/pivot/plugins/pivot_ui_plugin.js +++ b/addons/spreadsheet/static/src/pivot/plugins/pivot_ui_plugin.js @@ -15,6 +15,7 @@ const { DateTime } = luxon; /** * @typedef {import("./pivot_core_plugin").PivotDefinition} PivotDefinition * @typedef {import("@spreadsheet/global_filters/plugins/global_filters_core_plugin").FieldMatching} FieldMatching + * @typedef {import("@spreadsheet/helpers/odoo_functions_helpers").Token} Token */ /** @@ -77,7 +78,7 @@ export class PivotUIPlugin extends spreadsheet.UIPlugin { const { col, row } = event.anchor.cell; const cell = this.getters.getCell({ sheetId, col, row }); if (cell !== undefined && cell.content.startsWith("=ODOO.PIVOT.HEADER(")) { - const filters = this.getFiltersMatchingPivot(cell.content); + const filters = this._getFiltersMatchingPivot(cell.compiledFormula.tokens); this.dispatch("SET_MANY_GLOBAL_FILTER_VALUE", { filters }); } break; @@ -187,7 +188,7 @@ export class PivotUIPlugin extends spreadsheet.UIPlugin { getPivotIdFromPosition(position) { const cell = this.getters.getCorrespondingFormulaCell(position); if (cell && cell.isFormula) { - const pivotFunction = this.getters.getFirstPivotFunction(cell.content); + const pivotFunction = this.getters.getFirstPivotFunction(cell.compiledFormula.tokens); if (pivotFunction) { return pivotFunction.args[0].toString(); } @@ -195,8 +196,9 @@ export class PivotUIPlugin extends spreadsheet.UIPlugin { return undefined; } - getFirstPivotFunction(formula) { - const pivotFunction = getFirstPivotFunction(formula); + getFirstPivotFunction(tokens) { + console.log("getFirstPivotFunction", tokens); + const pivotFunction = getFirstPivotFunction(tokens); if (!pivotFunction) { return undefined; } @@ -204,6 +206,12 @@ export class PivotUIPlugin extends spreadsheet.UIPlugin { const evaluatedArgs = args.map((argAst) => { if (argAst.type == "EMPTY") { return undefined; + } else if ( + argAst.type === "STRING" || + argAst.type === "BOOLEAN" || + argAst.type === "NUMBER" + ) { + return argAst.value; } const argsString = astToFormula(argAst); return this.getters.evaluateFormula(this.getters.getActiveSheetId(), argsString); @@ -229,11 +237,17 @@ export class PivotUIPlugin extends spreadsheet.UIPlugin { */ getPivotDomainArgsFromPosition(position) { const cell = this.getters.getCorrespondingFormulaCell(position); - if (!cell || !cell.isFormula || getNumberOfPivotFormulas(cell.content) === 0) { + if ( + !cell || + !cell.isFormula || + getNumberOfPivotFormulas(cell.compiledFormula.tokens) === 0 + ) { return undefined; } const mainPosition = this.getters.getCellPosition(cell.id); - const { args, functionName } = this.getters.getFirstPivotFunction(cell.content); + const { args, functionName } = this.getters.getFirstPivotFunction( + cell.compiledFormula.tokens + ); if (functionName === "ODOO.PIVOT.TABLE") { const pivotId = args[0]; const dataSource = this.getPivotDataSource(pivotId); @@ -321,12 +335,12 @@ export class PivotUIPlugin extends spreadsheet.UIPlugin { /** * Get the filter impacted by a pivot formula's argument - * @param {string} formula Formula of the pivot cell + * @param {Token[]} tokens Formula of the pivot cell * * @returns {Array} */ - getFiltersMatchingPivot(formula) { - const functionDescription = this.getters.getFirstPivotFunction(formula); + _getFiltersMatchingPivot(tokens) { + const functionDescription = this.getters.getFirstPivotFunction(tokens); if (!functionDescription) { return []; } @@ -503,7 +517,6 @@ PivotUIPlugin.getters = [ "getPivotIdFromPosition", "getPivotCellValue", "getPivotGroupByValues", - "getFiltersMatchingPivot", "getFiltersMatchingPivotArgs", "getPivotDataSourceId", "getPivotTableStructure", diff --git a/addons/spreadsheet/static/tests/global_filters/global_filters_model_test.js b/addons/spreadsheet/static/tests/global_filters/global_filters_model_test.js index 6e5ec2ac47a..f4342302058 100644 --- a/addons/spreadsheet/static/tests/global_filters/global_filters_model_test.js +++ b/addons/spreadsheet/static/tests/global_filters/global_filters_model_test.js @@ -2,7 +2,7 @@ import { nextTick, patchDate } from "@web/../tests/helpers/utils"; import { CommandResult } from "@spreadsheet/o_spreadsheet/cancelled_reason"; -import { Model, DispatchResult, helpers } from "@odoo/o-spreadsheet"; +import { Model, DispatchResult, helpers, tokenize } from "@odoo/o-spreadsheet"; import { createModelWithDataSource, waitForDataSourcesLoaded, @@ -47,6 +47,7 @@ import { import { GlobalFiltersUIPlugin } from "@spreadsheet/global_filters/plugins/global_filters_ui_plugin"; import { migrate } from "@spreadsheet/o_spreadsheet/migration"; import { toRangeData } from "../utils/zones"; +import { PivotUIPlugin } from "@spreadsheet/pivot/index"; const { DateTime } = luxon; const { toZone } = helpers; @@ -68,6 +69,11 @@ const DEFAULT_FIELD_MATCHINGS = { 1: { chain: "date", type: "date" }, }; +function getFiltersMatchingPivot(model, formula) { + const pivotUIPlugin = model["handlers"].find((handler) => handler instanceof PivotUIPlugin); + return pivotUIPlugin._getFiltersMatchingPivot(tokenize(formula)); +} + QUnit.module("spreadsheet > Global filters model", {}, () => { QUnit.test("Can add a global filter", async function (assert) { assert.expect(4); @@ -1772,21 +1778,25 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { pivot: { 1: { chain: "date", type: "date" } }, } ); - const relationalFilters1 = model.getters.getFiltersMatchingPivot( + const relationalFilters1 = getFiltersMatchingPivot( + model, '=ODOO.PIVOT.HEADER(1,"product_id",37)' ); assert.deepEqual(relationalFilters1, [{ filterId: "42", value: [37] }]); - const relationalFilters2 = model.getters.getFiltersMatchingPivot( + const relationalFilters2 = getFiltersMatchingPivot( + model, '=ODOO.PIVOT.HEADER(1,"product_id","41")' ); assert.deepEqual(relationalFilters2, [{ filterId: "42", value: [41] }]); - const dateFilters1 = model.getters.getFiltersMatchingPivot( + const dateFilters1 = getFiltersMatchingPivot( + model, '=ODOO.PIVOT.HEADER(1,"date:month","08/2016")' ); assert.deepEqual(dateFilters1, [ { filterId: "43", value: { yearOffset: -6, period: "august" } }, ]); - const dateFilters2 = model.getters.getFiltersMatchingPivot( + const dateFilters2 = getFiltersMatchingPivot( + model, '=ODOO.PIVOT.HEADER(1,"date:year","2016")' ); assert.deepEqual(dateFilters2, [{ filterId: "43", value: { yearOffset: -6 } }]); @@ -1797,7 +1807,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { "getFiltersMatchingPivot return an empty array if there is no pivot formula", async function (assert) { const model = await createModelWithDataSource(); - const result = model.getters.getFiltersMatchingPivot("=1"); + const result = getFiltersMatchingPivot(model, "=1"); assert.deepEqual(result, []); } ); @@ -1837,7 +1847,8 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { pivot: { 1: { chain: "product_id", type: "many2one" } }, } ); - const filters = model.getters.getFiltersMatchingPivot( + const filters = getFiltersMatchingPivot( + model, '=ODOO.PIVOT.HEADER(1,"date:month","08/2016","product_id","41")' ); assert.deepEqual(filters, [{ filterId: "42", value: [41] }]); @@ -1864,7 +1875,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { }, { pivot: { 1: { chain: "product_id", type: "many2one" } } } ); - const filters = model.getters.getFiltersMatchingPivot(getCellFormula(model, "B3")); + const filters = getFiltersMatchingPivot(model, getCellFormula(model, "B3")); assert.deepEqual(filters, [ { filterId: "42", @@ -1894,7 +1905,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { }, { pivot: { 1: { chain: "product_id", type: "many2one" } } } ); - const filters = model.getters.getFiltersMatchingPivot(getCellFormula(model, "B3")); + const filters = getFiltersMatchingPivot(model, getCellFormula(model, "B3")); assert.deepEqual(filters, [ { filterId: "42", @@ -1919,7 +1930,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { type: "relation", defaultValue: [], }); - const filters = model.getters.getFiltersMatchingPivot(getCellFormula(model, "B3")); + const filters = getFiltersMatchingPivot(model, getCellFormula(model, "B3")); assert.deepEqual(filters, []); } ); @@ -1944,7 +1955,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { }, { pivot: { 1: { chain: "product_id", type: "many2one" } } } ); - const filters = model.getters.getFiltersMatchingPivot(getCellFormula(model, "B3")); + const filters = getFiltersMatchingPivot(model, getCellFormula(model, "B3")); assert.deepEqual(filters, []); } ); @@ -1966,7 +1977,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { defaultValue: [1], pivotFields: { 1: { field: "product_id", type: "many2one" } }, }); - const filters = model.getters.getFiltersMatchingPivot(getCellFormula(model, "B3")); + const filters = getFiltersMatchingPivot(model, getCellFormula(model, "B3")); assert.deepEqual(filters, []); } ); @@ -2062,7 +2073,7 @@ QUnit.module("spreadsheet > Global filters model", {}, () => { type: "relation", defaultValue: [], }); - const filters = model.getters.getFiltersMatchingPivot(getCellFormula(model, "B2")); + const filters = getFiltersMatchingPivot(model, getCellFormula(model, "B2")); assert.deepEqual(filters, []); } ); diff --git a/addons/spreadsheet/static/tests/pivots/pivot_helpers_test.js b/addons/spreadsheet/static/tests/pivots/pivot_helpers_test.js index be0e2aa91a0..6696b34776d 100644 --- a/addons/spreadsheet/static/tests/pivots/pivot_helpers_test.js +++ b/addons/spreadsheet/static/tests/pivots/pivot_helpers_test.js @@ -3,7 +3,7 @@ import { getFirstPivotFunction, getNumberOfPivotFormulas } from "@spreadsheet/pi import { getFirstListFunction, getNumberOfListFormulas } from "@spreadsheet/list/list_helpers"; import { toNormalizedPivotValue } from "@spreadsheet/pivot/pivot_model"; import { pivotTimeAdapter } from "@spreadsheet/pivot/pivot_time_adapters"; -import { constants } from "@odoo/o-spreadsheet"; +import { constants, tokenize } from "@odoo/o-spreadsheet"; const { DEFAULT_LOCALE } = constants; function stringArg(value) { @@ -13,14 +13,15 @@ function stringArg(value) { QUnit.module("spreadsheet > pivot_helpers", {}, () => { QUnit.test("Basic formula extractor", async function (assert) { const formula = `=ODOO.PIVOT("1", "test") + ODOO.LIST("2", "hello", "bla")`; + const tokens = tokenize(formula); let functionName; let args; - ({ functionName, args } = getFirstPivotFunction(formula)); + ({ functionName, args } = getFirstPivotFunction(tokens)); assert.strictEqual(functionName, "ODOO.PIVOT"); assert.strictEqual(args.length, 2); assert.deepEqual(args[0], stringArg("1")); assert.deepEqual(args[1], stringArg("test")); - ({ functionName, args } = getFirstListFunction(formula)); + ({ functionName, args } = getFirstListFunction(tokens)); assert.strictEqual(functionName, "ODOO.LIST"); assert.strictEqual(args.length, 3); assert.deepEqual(args[0], stringArg("2")); @@ -30,33 +31,34 @@ QUnit.module("spreadsheet > pivot_helpers", {}, () => { QUnit.test("Extraction with two PIVOT formulas", async function (assert) { const formula = `=ODOO.PIVOT("1", "test") + ODOO.PIVOT("2", "hello", "bla")`; - const { functionName, args } = getFirstPivotFunction(formula); + const tokens = tokenize(formula); + const { functionName, args } = getFirstPivotFunction(tokens); assert.strictEqual(functionName, "ODOO.PIVOT"); assert.strictEqual(args.length, 2); assert.deepEqual(args[0], stringArg("1")); assert.deepEqual(args[1], stringArg("test")); - assert.strictEqual(getFirstListFunction(formula), undefined); + assert.strictEqual(getFirstListFunction(tokens), undefined); }); QUnit.test("Number of formulas", async function (assert) { const formula = `=ODOO.PIVOT("1", "test") + ODOO.PIVOT("2", "hello", "bla") + ODOO.LIST("1", "bla")`; - assert.strictEqual(getNumberOfPivotFormulas(formula), 2); - assert.strictEqual(getNumberOfListFormulas(formula), 1); - assert.strictEqual(getNumberOfPivotFormulas("=1+1"), 0); - assert.strictEqual(getNumberOfListFormulas("=1+1"), 0); - assert.strictEqual(getNumberOfPivotFormulas("=bla"), 0); - assert.strictEqual(getNumberOfListFormulas("=bla"), 0); + assert.strictEqual(getNumberOfPivotFormulas(tokenize(formula)), 2); + assert.strictEqual(getNumberOfListFormulas(tokenize(formula)), 1); + assert.strictEqual(getNumberOfPivotFormulas(tokenize("=1+1")), 0); + assert.strictEqual(getNumberOfListFormulas(tokenize("=1+1")), 0); + assert.strictEqual(getNumberOfPivotFormulas(tokenize("=bla")), 0); + assert.strictEqual(getNumberOfListFormulas(tokenize("=bla")), 0); }); QUnit.test("getFirstPivotFunction does not crash when given crap", async function (assert) { - assert.strictEqual(getFirstListFunction("=SUM(A1)"), undefined); - assert.strictEqual(getFirstPivotFunction("=SUM(A1)"), undefined); - assert.strictEqual(getFirstListFunction("=1+1"), undefined); - assert.strictEqual(getFirstPivotFunction("=1+1"), undefined); - assert.strictEqual(getFirstListFunction("=bla"), undefined); - assert.strictEqual(getFirstPivotFunction("=bla"), undefined); - assert.strictEqual(getFirstListFunction("bla"), undefined); - assert.strictEqual(getFirstPivotFunction("bla"), undefined); + assert.strictEqual(getFirstListFunction(tokenize("=SUM(A1)")), undefined); + assert.strictEqual(getFirstPivotFunction(tokenize("=SUM(A1)")), undefined); + assert.strictEqual(getFirstListFunction(tokenize("=1+1")), undefined); + assert.strictEqual(getFirstPivotFunction(tokenize("=1+1")), undefined); + assert.strictEqual(getFirstListFunction(tokenize("=bla")), undefined); + assert.strictEqual(getFirstPivotFunction(tokenize("=bla")), undefined); + assert.strictEqual(getFirstListFunction(tokenize("bla")), undefined); + assert.strictEqual(getFirstPivotFunction(tokenize("bla")), undefined); }); }); diff --git a/addons/spreadsheet/static/tests/utils/getters.js b/addons/spreadsheet/static/tests/utils/getters.js index 2ba73089c71..8c754a05f11 100644 --- a/addons/spreadsheet/static/tests/utils/getters.js +++ b/addons/spreadsheet/static/tests/utils/getters.js @@ -65,7 +65,7 @@ export function getCells(model, sheetId = model.getters.getActiveSheetId()) { */ export function getCellFormula(model, xc, sheetId = model.getters.getActiveSheetId()) { const cell = getCell(model, xc, sheetId); - return cell && cell.isFormula ? model.getters.getFormulaCellContent(sheetId, cell) : ""; + return cell && cell.isFormula ? cell.content : ""; } /** diff --git a/addons/spreadsheet_account/static/src/index.js b/addons/spreadsheet_account/static/src/index.js index 1afa6839641..6400c2207ce 100644 --- a/addons/spreadsheet_account/static/src/index.js +++ b/addons/spreadsheet_account/static/src/index.js @@ -20,7 +20,7 @@ cellMenuRegistry.add("move_lines_see_records", { const position = env.model.getters.getActivePosition(); const sheetId = position.sheetId; const cell = env.model.getters.getCell(position); - const { args } = getFirstAccountFunction(cell.content); + const { args } = getFirstAccountFunction(cell.compiledFormula.tokens); let [codes, date_range, offset, companyId, includeUnposted] = args .map(astToFormula) .map((arg) => env.model.getters.evaluateFormula(sheetId, arg)); @@ -46,7 +46,7 @@ cellMenuRegistry.add("move_lines_see_records", { !evaluatedCell.error && evaluatedCell.value !== "" && cell && - getNumberOfAccountFormulas(cell.content) === 1 + getNumberOfAccountFormulas(cell.compiledFormula.tokens) === 1 ); }, }); diff --git a/addons/spreadsheet_account/static/src/utils.js b/addons/spreadsheet_account/static/src/utils.js index d408fd0c507..e9dc03fa3a1 100644 --- a/addons/spreadsheet_account/static/src/utils.js +++ b/addons/spreadsheet_account/static/src/utils.js @@ -1,22 +1,25 @@ /** @odoo-module **/ import { getOdooFunctions } from "@spreadsheet/helpers/odoo_functions_helpers"; -/** @typedef {import("@spreadsheet/helpers/odoo_functions_helpers").OdooFunctionDescription} OdooFunctionDescription*/ +/** + * @typedef {import("@spreadsheet/helpers/odoo_functions_helpers").Token} Token + * @typedef {import("@spreadsheet/helpers/odoo_functions_helpers").OdooFunctionDescription} OdooFunctionDescription + */ /** - * @param {string} formula + * @param {Token[]} tokens * @returns {number} */ -export function getNumberOfAccountFormulas(formula) { - return getOdooFunctions(formula, ["ODOO.BALANCE", "ODOO.CREDIT", "ODOO.DEBIT"]).length; +export function getNumberOfAccountFormulas(tokens) { + return getOdooFunctions(tokens, ["ODOO.BALANCE", "ODOO.CREDIT", "ODOO.DEBIT"]).length; } /** * Get the first Account function description of the given formula. * - * @param {string} formula + * @param {Token[]} tokens * @returns {OdooFunctionDescription | undefined} */ -export function getFirstAccountFunction(formula) { - return getOdooFunctions(formula, ["ODOO.BALANCE", "ODOO.CREDIT", "ODOO.DEBIT"])[0]; +export function getFirstAccountFunction(tokens) { + return getOdooFunctions(tokens, ["ODOO.BALANCE", "ODOO.CREDIT", "ODOO.DEBIT"])[0]; }