From fa993591f56e2256811e6815199ef9636687057e Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 27 Mar 2026 21:03:18 -0400 Subject: [PATCH 1/9] fix(spreadsheet_oca): revision commands field size and model description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - commands field: Char → Text (JSON revision commands can exceed 255 chars, causing silent truncation of collaborative editing history) - _description: remove TODO placeholder, use proper model description Co-Authored-By: Claude Opus 4.6 (1M context) --- spreadsheet_oca/models/spreadsheet_oca_revision.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spreadsheet_oca/models/spreadsheet_oca_revision.py b/spreadsheet_oca/models/spreadsheet_oca_revision.py index b445b598..fce6056b 100644 --- a/spreadsheet_oca/models/spreadsheet_oca_revision.py +++ b/spreadsheet_oca/models/spreadsheet_oca_revision.py @@ -6,7 +6,7 @@ class SpreadsheetOcaRevision(models.Model): _name = "spreadsheet.oca.revision" - _description = "Spreadsheet Oca Revision" # TODO + _description = "Spreadsheet Revision" model = fields.Char(required=True) res_id = fields.Integer(required=True, index=True) @@ -14,4 +14,4 @@ class SpreadsheetOcaRevision(models.Model): client_id = fields.Char() server_revision_id = fields.Char() next_revision_id = fields.Char() - commands = fields.Char() + commands = fields.Text() From 1975987e2265e58291374f3401785565f40771d8 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 27 Mar 2026 21:31:07 -0400 Subject: [PATCH 2/9] fix(spreadsheet_oca): use record.name instead of self.name in _compute_filename In a multi-record compute, self.name raises ValueError (singleton check) or returns the wrong name. Must iterate with record.name. --- spreadsheet_oca/models/spreadsheet_spreadsheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spreadsheet_oca/models/spreadsheet_spreadsheet.py b/spreadsheet_oca/models/spreadsheet_spreadsheet.py index 55a9ae9f..7dbc5e91 100644 --- a/spreadsheet_oca/models/spreadsheet_spreadsheet.py +++ b/spreadsheet_oca/models/spreadsheet_spreadsheet.py @@ -59,7 +59,7 @@ class SpreadsheetSpreadsheet(models.Model): @api.depends("name") def _compute_filename(self): for record in self: - record.filename = "%s.json" % (self.name or _("Unnamed")) + record.filename = "%s.json" % (record.name or _("Unnamed")) def create_document_from_attachment(self, attachment_ids): attachments = self.env["ir.attachment"].browse(attachment_ids) From e6f218b3494271b9587b4a80e1abadb24bfdb328 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 27 Mar 2026 21:31:15 -0400 Subject: [PATCH 3/9] fix(spreadsheet_oca): clean up orphaned revisions on spreadsheet unlink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The write() override already clears revisions when spreadsheet_raw changes, but unlink() was missing — deleting a spreadsheet left orphaned revision records in the database. Add unlink() to cascade the cleanup. --- spreadsheet_oca/models/spreadsheet_abstract.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spreadsheet_oca/models/spreadsheet_abstract.py b/spreadsheet_oca/models/spreadsheet_abstract.py index 86a1e317..68663784 100644 --- a/spreadsheet_oca/models/spreadsheet_abstract.py +++ b/spreadsheet_oca/models/spreadsheet_abstract.py @@ -177,3 +177,7 @@ def write(self, vals): if "spreadsheet_raw" in vals: self.spreadsheet_revision_ids.unlink() return super().write(vals) + + def unlink(self): + self.spreadsheet_revision_ids.unlink() + return super().unlink() From 189debde8d98c63dac49f8abd88338999cb63e0e Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 27 Mar 2026 21:31:23 -0400 Subject: [PATCH 4/9] fix(spreadsheet_oca): correct ACL group reference and copy-paste error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Line 2: group_user → spreadsheet_oca.group_user (explicit module prefix) - Line 5: name field said "revision" but model is import_mode (copy-paste) --- spreadsheet_oca/security/ir.model.access.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spreadsheet_oca/security/ir.model.access.csv b/spreadsheet_oca/security/ir.model.access.csv index 1898b166..f62bbd69 100644 --- a/spreadsheet_oca/security/ir.model.access.csv +++ b/spreadsheet_oca/security/ir.model.access.csv @@ -1,8 +1,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_spreadsheet_spreadsheet,access_spreadsheet_spreadsheet,model_spreadsheet_spreadsheet,group_user,1,1,1,1 +access_spreadsheet_spreadsheet,access_spreadsheet_spreadsheet,model_spreadsheet_spreadsheet,spreadsheet_oca.group_user,1,1,1,1 access_spreadsheet_oca_revision,access_spreadsheet_oca_revision,model_spreadsheet_oca_revision,base.group_user,1,1,1,1 spreadsheet_oca.access_spreadsheet_spreadsheet_import,access_spreadsheet_spreadsheet_import,spreadsheet_oca.model_spreadsheet_spreadsheet_import,base.group_user,1,1,1,1 -access_spreadsheet_import_mode,access_spreadsheet_oca_revision,model_spreadsheet_spreadsheet_import_mode,base.group_user,1,0,0,0 +access_spreadsheet_import_mode,access_spreadsheet_import_mode,model_spreadsheet_spreadsheet_import_mode,base.group_user,1,0,0,0 access_spreadsheet_select_row_number,access_spreadsheet_select_row_number,model_spreadsheet_select_row_number,base.group_user,1,1,1,1 access_spreadsheet_spreadsheet_tag,access_spreadsheet_spreadsheet_tag,model_spreadsheet_spreadsheet_tag,spreadsheet_oca.group_user,1,0,0,0 access_spreadsheet_spreadsheet_manager_tag,access_spreadsheet_spreadsheet_manager_tag,model_spreadsheet_spreadsheet_tag,spreadsheet_oca.group_manager,1,1,1,1 From b4dde8c9587651d040696717a1c4d3b226aeb285 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 27 Mar 2026 22:32:44 -0400 Subject: [PATCH 5/9] fix(spreadsheet_oca): await orm.call on save and use const/let over var - saveRecord: add missing await on orm.call write (silently swallowed errors could cause data loss) - Replace var with const/let throughout (5 occurrences) --- .../src/spreadsheet/bundle/spreadsheet_action.esm.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spreadsheet_oca/static/src/spreadsheet/bundle/spreadsheet_action.esm.js b/spreadsheet_oca/static/src/spreadsheet/bundle/spreadsheet_action.esm.js index 81de6fa6..b3596759 100644 --- a/spreadsheet_oca/static/src/spreadsheet/bundle/spreadsheet_action.esm.js +++ b/spreadsheet_oca/static/src/spreadsheet/bundle/spreadsheet_action.esm.js @@ -66,7 +66,7 @@ export class ActionSpreadsheetOca extends Component { return; } if (this.spreadsheetId) { - this.orm.call(this.model, "write", [this.spreadsheetId, data]); + await this.orm.call(this.model, "write", [this.spreadsheetId, data]); } else { this.spreadsheetId = await this.orm.call(this.model, "create", [data]); } @@ -82,7 +82,7 @@ export class ActionSpreadsheetOca extends Component { cleanSearchParams() { const searchParams = this.import_data.searchParams; const context = {}; - for (var key of Object.keys(searchParams.context)) { + for (const key of Object.keys(searchParams.context)) { if (key.startsWith("pivot_") || key.startsWith("graph_")) { continue; } @@ -91,7 +91,7 @@ export class ActionSpreadsheetOca extends Component { return {...searchParams, context}; } async importDataGraph(spreadsheet_model) { - var sheetId = spreadsheet_model.getters.getActiveSheetId(); + let sheetId = spreadsheet_model.getters.getActiveSheetId(); if (this.import_data.new === undefined && this.import_data.new_sheet) { sheetId = uuidGenerator.uuidv4(); spreadsheet_model.dispatch("CREATE_SHEET", { @@ -136,7 +136,7 @@ export class ActionSpreadsheetOca extends Component { }); } importCreateOrReuseSheet(spreadsheet_model) { - var sheetId = spreadsheet_model.getters.getActiveSheetId(); + let sheetId = spreadsheet_model.getters.getActiveSheetId(); if (this.import_data.new === undefined) { sheetId = uuidGenerator.uuidv4(); spreadsheet_model.dispatch("CREATE_SHEET", { @@ -153,7 +153,7 @@ export class ActionSpreadsheetOca extends Component { return sheetId; } async importDataList(spreadsheet_model) { - var sheetId = this.importCreateOrReuseSheet(spreadsheet_model); + let sheetId = this.importCreateOrReuseSheet(spreadsheet_model); if (!sheetId) { const sheetIds = spreadsheet_model.getters.getSheetIds(); sheetId = sheetIds.length ? sheetIds[0] : uuidGenerator.uuidv4(); @@ -194,7 +194,7 @@ export class ActionSpreadsheetOca extends Component { }); } async importDataPivot(spreadsheet_model) { - var sheetId = this.importCreateOrReuseSheet(spreadsheet_model); + const sheetId = this.importCreateOrReuseSheet(spreadsheet_model); const pivotId = uuidGenerator.uuidv4(); const fields = this.import_data.metaData.fields || {}; const activeMeasures = this.import_data.metaData.activeMeasures; From 56ed884cae40f0a0d988d3efe8af0a68c64b72d6 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 27 Mar 2026 22:33:04 -0400 Subject: [PATCH 6/9] =?UTF-8?q?fix(spreadsheet=5Foca):=20PivotPanelDisplay?= =?UTF-8?q?=20.properties=20=E2=86=92=20.props?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OWL uses .props for prop validation, not .properties. Without this, PivotPanelDisplay has no prop validation at all. --- .../src/spreadsheet/bundle/filter_panel_datasources.esm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spreadsheet_oca/static/src/spreadsheet/bundle/filter_panel_datasources.esm.js b/spreadsheet_oca/static/src/spreadsheet/bundle/filter_panel_datasources.esm.js index a8081f6b..9625679a 100644 --- a/spreadsheet_oca/static/src/spreadsheet/bundle/filter_panel_datasources.esm.js +++ b/spreadsheet_oca/static/src/spreadsheet/bundle/filter_panel_datasources.esm.js @@ -147,7 +147,7 @@ PivotPanelDisplay.components = { PivotTitleSectionInsertion, PivotLayoutConfiguratorWithAggregators, }; -PivotPanelDisplay.properties = { +PivotPanelDisplay.props = { pivotId: String, }; From 924d80db45a14f4f943b92b85b4c8d81c9776077 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 27 Mar 2026 22:34:05 -0400 Subject: [PATCH 7/9] =?UTF-8?q?fix(spreadsheet=5Foca):=20remove=20dead=20u?= =?UTF-8?q?seService(dialog),=20var=20=E2=86=92=20const=20in=20filter/pivo?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - spreadsheet_renderer: remove useService("dialog") in non-component constructor (dead code, fragile — would crash if called outside setup) - filter.esm.js: var → const/let (3 occurrences) - pivot_controller.esm.js: var → let --- spreadsheet_oca/static/src/spreadsheet/bundle/filter.esm.js | 6 +++--- .../src/spreadsheet/bundle/spreadsheet_renderer.esm.js | 1 - .../static/src/spreadsheet/pivot_controller.esm.js | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/spreadsheet_oca/static/src/spreadsheet/bundle/filter.esm.js b/spreadsheet_oca/static/src/spreadsheet/bundle/filter.esm.js index 799faf37..cfa24ae4 100644 --- a/spreadsheet_oca/static/src/spreadsheet/bundle/filter.esm.js +++ b/spreadsheet_oca/static/src/spreadsheet/bundle/filter.esm.js @@ -105,12 +105,12 @@ export class EditFilterPanel extends Component { this.state.modelData.hasParentRelation = hasParentRelation; } } - var ModelFields = []; - for (var [objectType, objectClass] of Object.entries( + const ModelFields = []; + for (const [objectType, objectClass] of Object.entries( globalFiltersFieldMatchers )) { for (const objectId of objectClass.getIds()) { - var fields = objectClass.getFields(objectId); + const fields = objectClass.getFields(objectId); this.state.objects[objectType + "_" + objectId] = { id: objectType + "_" + objectId, objectId: objectId, diff --git a/spreadsheet_oca/static/src/spreadsheet/bundle/spreadsheet_renderer.esm.js b/spreadsheet_oca/static/src/spreadsheet/bundle/spreadsheet_renderer.esm.js index 83aae8cf..0bdb2a77 100644 --- a/spreadsheet_oca/static/src/spreadsheet/bundle/spreadsheet_renderer.esm.js +++ b/spreadsheet_oca/static/src/spreadsheet/bundle/spreadsheet_renderer.esm.js @@ -25,7 +25,6 @@ class SpreadsheetTransportService { this.res_id = res_id; this.channel = "spreadsheet_oca;" + this.model + ";" + this.res_id; this.bus_service.addChannel(this.channel); - this.dialog = useService("dialog"); this.bus_service.subscribe("notification", (payload) => { if (payload.id === this.res_id) { this._handleNotification(payload); diff --git a/spreadsheet_oca/static/src/spreadsheet/pivot_controller.esm.js b/spreadsheet_oca/static/src/spreadsheet/pivot_controller.esm.js index 54e68e6f..291f76df 100644 --- a/spreadsheet_oca/static/src/spreadsheet/pivot_controller.esm.js +++ b/spreadsheet_oca/static/src/spreadsheet/pivot_controller.esm.js @@ -36,7 +36,7 @@ patch(PivotRenderer.prototype, { ); }, getSpreadsheetInsertionTooltip() { - var message = _t("Add to spreadsheet"); + let message = _t("Add to spreadsheet"); if (this.containsDuplicatedGroupBys()) { message = _t("Duplicated groupbys in pivot are not supported"); } else if (this.isComparingInfo()) { From df5eab2cd1fea9a0863e19c63757d96154733ac5 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 27 Mar 2026 22:36:07 -0400 Subject: [PATCH 8/9] fix(spreadsheet_oca): add missing notification service, await dashboard save MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - spreadsheet_tree_view: this.notification was used but never initialized via useService — would crash on XLSX upload with notifications - spreadsheet_to_dashboard: await onSpreadsheetSaved() before creating dashboard to prevent race condition --- .../static/src/bundle/spreadsheet_to_dashboard.esm.js | 2 +- .../static/src/spreadsheet_tree/spreadsheet_tree_view.esm.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spreadsheet_dashboard_oca/static/src/bundle/spreadsheet_to_dashboard.esm.js b/spreadsheet_dashboard_oca/static/src/bundle/spreadsheet_to_dashboard.esm.js index f98c64d4..c0cdab03 100644 --- a/spreadsheet_dashboard_oca/static/src/bundle/spreadsheet_to_dashboard.esm.js +++ b/spreadsheet_dashboard_oca/static/src/bundle/spreadsheet_to_dashboard.esm.js @@ -34,7 +34,7 @@ patch(SpreadsheetRenderer.prototype, { const record = this.props.record; const resId = this.props.res_id; const name = record.name; - this.onSpreadsheetSaved(); + await this.onSpreadsheetSaved(); this.env.services.action.doAction( { name: _t("Add to dashboard"), diff --git a/spreadsheet_oca/static/src/spreadsheet_tree/spreadsheet_tree_view.esm.js b/spreadsheet_oca/static/src/spreadsheet_tree/spreadsheet_tree_view.esm.js index de4655ae..07fb041a 100644 --- a/spreadsheet_oca/static/src/spreadsheet_tree/spreadsheet_tree_view.esm.js +++ b/spreadsheet_oca/static/src/spreadsheet_tree/spreadsheet_tree_view.esm.js @@ -10,6 +10,7 @@ import {useService} from "@web/core/utils/hooks"; class SpreadsheetFileUploader extends Component { setup() { this.orm = useService("orm"); + this.notification = useService("notification"); this.attachmentIdsToProcess = []; this.action = useService("action"); } From 7992bde3da4e801df3634763838e846f2c7d3771 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Mon, 18 May 2026 22:37:16 -0400 Subject: [PATCH 9/9] style: remove trailing whitespace in ir_model.py