From 6785c99e78c14bbce2d1092098b65af0930790f9 Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Thu, 8 Jan 2026 20:41:47 +0000 Subject: [PATCH 01/14] [FIX] Odoo 18.0 Access Error Log Message --- .../static/src/views/timeline/timeline_model.esm.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index 1c5c3ce07b65..69273e324c6d 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -33,18 +33,18 @@ export class TimelineModel extends Model { onWillStart(async () => { this.write_right = await this.orm.call( this.model_name, - "check_access_rights", - ["write", false] + "check_access", + ["", 'write'] ); this.unlink_right = await this.orm.call( this.model_name, - "check_access_rights", - ["unlink", false] + "check_access", + ["", 'unlink'] ); this.create_right = await this.orm.call( this.model_name, - "check_access_rights", - ["create", false] + "check_access", + ["", 'create'] ); }); } From 61bc77269f7b38193a10043a6c3e3344f364eeb1 Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Thu, 8 Jan 2026 21:26:37 +0000 Subject: [PATCH 02/14] [fix] Functional multilabels for timeline view --- .../src/views/timeline/timeline_model.esm.js | 76 +++++++++++-------- .../views/timeline/timeline_renderer.esm.js | 10 ++- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index 69273e324c6d..52834176e4c6 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -65,9 +65,9 @@ export class TimelineModel extends Model { // In the module sale_timesheet_timeline, it is used // with default_group_by = task_user_ids let field_to_order = this.params.default_group_by; - if (this.fields[field_to_order].type === "many2many") { - field_to_order = undefined; - } + // if (this.fields[field_to_order].type === "many2many") { + // field_to_order = undefined; + // } this.data = await this.keepLast.add( this.orm.call(this.model_name, "search_read", [], { fields: fields, @@ -86,40 +86,56 @@ export class TimelineModel extends Model { * @returns {Object} */ _event_data_transform(record) { + + console.log("Transforming event data:", record); const [date_start, date_stop] = this._get_event_dates(record); let group = record[this.last_group_bys[0]]; - if (group && Array.isArray(group) && group.length > 0) { - group = group[0]; - } else { - group = -1; + let groups = []; + if (group && Array.isArray(group) && group.length === 2) { + // Issue is right here for many to many. Need to return multiple timeline items. + groups.push(group[0]); } - let colorToApply = false; - for (const color of this.colors) { - if (evaluate(color.ast, record)) { - colorToApply = color.color; - } + else{ + groups = group || -1; } - - let content = record.display_name; - if (this.recordTemplate) { - content = this._render_timeline_item(record); + if (groups.length === 0) { + groups = [-1]; } + + let all_timeline_items = []; + console.log("Determined groups for record:", groups); + for (const this_group of groups) { + console.log("Processing group for record:", this_group); + let colorToApply = false; + for (const color of this.colors) { + if (evaluate(color.ast, record)) { + colorToApply = color.color; + } + } + + let content = record.display_name; + if (this.recordTemplate) { + content = this._render_timeline_item(record); + } - const timeline_item = { - start: date_start.toJSDate(), - content: content, - id: record.id, - order: record.order, - group: group, - evt: record, - style: `background-color: ${colorToApply};`, - }; - // Only specify range end when there actually is one. - // ➔ Instantaneous events / those with inverted dates are displayed as points. - if (date_stop && DateTime.fromISO(date_start) < DateTime.fromISO(date_stop)) { - timeline_item.end = date_stop.toJSDate(); + const timeline_item = { + start: date_start.toJSDate(), + content: content, + id: record.id + (this_group ? `_${this_group}` : ''), + order: record.order, + group: this_group, + evt: record, + style: `background-color: ${colorToApply};`, + }; + // Only specify range end when there actually is one. + // ➔ Instantaneous events / those with inverted dates are displayed as points. + if (date_stop && DateTime.fromISO(date_start) < DateTime.fromISO(date_stop)) { + timeline_item.end = date_stop.toJSDate(); + } + all_timeline_items.push(timeline_item); } - return timeline_item; + console.log("Generated timeline items for record:", all_timeline_items); + return all_timeline_items; } /** * Get dates from given event diff --git a/web_timeline/static/src/views/timeline/timeline_renderer.esm.js b/web_timeline/static/src/views/timeline/timeline_renderer.esm.js index 4aaba72d0cfc..c8f1d71162b0 100644 --- a/web_timeline/static/src/views/timeline/timeline_renderer.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_renderer.esm.js @@ -326,13 +326,17 @@ export class TimelineRenderer extends Component { * @private */ async on_data_loaded(records, adjust_window) { - const data = []; + let data = []; // Changed to non const to allow concat + for (const record of records) { if (record[this.date_start]) { - data.push(this.model._event_data_transform(record)); + // Change here to allow multiple items per record + // Does this make the get_m2m_grouping_datas function obsolete? + data = data.concat(this.model._event_data_transform(record)); } } - const groups = await this.split_groups(records); + + const groups = await this.split_groups(records); // I don't like this functionality this.timeline.setGroups(groups); this.timeline.setItems(data); const mode = !this.mode.data || this.mode.data === "fit"; From bc5308128df598a8eb7040d3855244822c9853a0 Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 14:37:51 +0000 Subject: [PATCH 03/14] Functional Click Throughs --- .../views/timeline/timeline_controller.esm.js | 18 +++++++++++++++++- .../src/views/timeline/timeline_model.esm.js | 12 ++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_controller.esm.js b/web_timeline/static/src/views/timeline/timeline_controller.esm.js index 1292e1a59bda..9dcb8f846c73 100644 --- a/web_timeline/static/src/views/timeline/timeline_controller.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_controller.esm.js @@ -80,6 +80,16 @@ export class TimelineController extends Component { * @returns {jQuery.Deferred} */ _onItemDoubleClick(event) { + // Resume thought here tommorrow... Do we need to parse the id here? + console.log("Double click event", event); + + const item_id = typeof event.item === 'string' && event.item.indexOf('_') !== -1 + ? Number(event.item.split('_')[0]) || event.item.split('_')[0] + : Number(event.item) || event.item; + + event.item = item_id; // Update event item to be just the ID + console.log("Parsed item_id:", event); + return this.openItem(event.item, false); } @@ -92,7 +102,13 @@ export class TimelineController extends Component { * @returns {Object} */ _onUpdate(item) { - const item_id = Number(item.evt.id) || item.evt.id; + + if (typeof item.evt.id === 'string' && item.evt.id.indexOf('_') !== -1) { + const item_id = item.evt.id.split('_')[0] + } + else { + const item_id = Number(item.evt.id) || item.evt.id; + } return this.openItem(item_id, true); } diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index 52834176e4c6..09f6bc0715a9 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -210,8 +210,16 @@ export class TimelineModel extends Model { * @returns {jQuery.Deferred} */ async remove_completed(event) { - await this.orm.call(this.model_name, "unlink", [[event.evt.id]]); - const unlink_index = this.data.findIndex((item) => item.id === event.evt.id); + + if (typeof event.evt.id === 'string' && event.evt.id.indexOf('_') !== -1) { + const item_id = event.evt.id.split('_')[0] + } + else { + const item_id = Number(event.evt.id) || event.evt.id; + } + + await this.orm.call(this.model_name, "unlink", [[item_id]]); + const unlink_index = this.data.findIndex((item) => item.id === item_id); if (unlink_index !== -1) { this.data.splice(unlink_index, 1); } From cf0fc28964de96cfb39a8479fc5a405f0474162b Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 15:01:32 +0000 Subject: [PATCH 04/14] Removing debugging statements --- .../static/src/views/timeline/timeline_controller.esm.js | 4 ---- .../static/src/views/timeline/timeline_model.esm.js | 6 +----- .../static/src/views/timeline/timeline_renderer.esm.js | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_controller.esm.js b/web_timeline/static/src/views/timeline/timeline_controller.esm.js index 9dcb8f846c73..b27a9d36d8b6 100644 --- a/web_timeline/static/src/views/timeline/timeline_controller.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_controller.esm.js @@ -80,15 +80,11 @@ export class TimelineController extends Component { * @returns {jQuery.Deferred} */ _onItemDoubleClick(event) { - // Resume thought here tommorrow... Do we need to parse the id here? - console.log("Double click event", event); - const item_id = typeof event.item === 'string' && event.item.indexOf('_') !== -1 ? Number(event.item.split('_')[0]) || event.item.split('_')[0] : Number(event.item) || event.item; event.item = item_id; // Update event item to be just the ID - console.log("Parsed item_id:", event); return this.openItem(event.item, false); } diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index 09f6bc0715a9..fb0c63a86294 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -87,12 +87,10 @@ export class TimelineModel extends Model { */ _event_data_transform(record) { - console.log("Transforming event data:", record); const [date_start, date_stop] = this._get_event_dates(record); let group = record[this.last_group_bys[0]]; let groups = []; if (group && Array.isArray(group) && group.length === 2) { - // Issue is right here for many to many. Need to return multiple timeline items. groups.push(group[0]); } else{ @@ -103,9 +101,8 @@ export class TimelineModel extends Model { } let all_timeline_items = []; - console.log("Determined groups for record:", groups); + for (const this_group of groups) { - console.log("Processing group for record:", this_group); let colorToApply = false; for (const color of this.colors) { if (evaluate(color.ast, record)) { @@ -134,7 +131,6 @@ export class TimelineModel extends Model { } all_timeline_items.push(timeline_item); } - console.log("Generated timeline items for record:", all_timeline_items); return all_timeline_items; } /** diff --git a/web_timeline/static/src/views/timeline/timeline_renderer.esm.js b/web_timeline/static/src/views/timeline/timeline_renderer.esm.js index c8f1d71162b0..60d296038f49 100644 --- a/web_timeline/static/src/views/timeline/timeline_renderer.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_renderer.esm.js @@ -336,7 +336,7 @@ export class TimelineRenderer extends Component { } } - const groups = await this.split_groups(records); // I don't like this functionality + const groups = await this.split_groups(records); this.timeline.setGroups(groups); this.timeline.setItems(data); const mode = !this.mode.data || this.mode.data === "fit"; From 765f6176d6f5b56d0d3cef7f1c152dd7d04396ba Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 15:11:06 +0000 Subject: [PATCH 05/14] Appeasing the linting gods. --- .../src/views/timeline/timeline_controller.esm.js | 14 ++++++-------- .../src/views/timeline/timeline_model.esm.js | 9 +++------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_controller.esm.js b/web_timeline/static/src/views/timeline/timeline_controller.esm.js index b27a9d36d8b6..9b50d42bf0d7 100644 --- a/web_timeline/static/src/views/timeline/timeline_controller.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_controller.esm.js @@ -83,8 +83,9 @@ export class TimelineController extends Component { const item_id = typeof event.item === 'string' && event.item.indexOf('_') !== -1 ? Number(event.item.split('_')[0]) || event.item.split('_')[0] : Number(event.item) || event.item; - - event.item = item_id; // Update event item to be just the ID + + // Update event item to be just the ID + event.item = item_id; return this.openItem(event.item, false); } @@ -99,12 +100,9 @@ export class TimelineController extends Component { */ _onUpdate(item) { - if (typeof item.evt.id === 'string' && item.evt.id.indexOf('_') !== -1) { - const item_id = item.evt.id.split('_')[0] - } - else { - const item_id = Number(item.evt.id) || item.evt.id; - } + const item_id = typeof item.evt.id === 'string' && item.evt.id.indexOf('_') !== -1 + ? Number(item.evt.id.split('_')[0]) || item.evt.id.split('_')[0] + : Number(item.evt.id) || item.evt.id; return this.openItem(item_id, true); } diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index fb0c63a86294..1fd7a6d4c7c1 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -207,12 +207,9 @@ export class TimelineModel extends Model { */ async remove_completed(event) { - if (typeof event.evt.id === 'string' && event.evt.id.indexOf('_') !== -1) { - const item_id = event.evt.id.split('_')[0] - } - else { - const item_id = Number(event.evt.id) || event.evt.id; - } + const item_id = typeof event.evt.id === 'string' && event.evt.id.indexOf('_') !== -1 + ? Number(event.evt.id.split('_')[0]) || event.evt.id.split('_')[0] + : Number(event.evt.id) || event.evt.id; await this.orm.call(this.model_name, "unlink", [[item_id]]); const unlink_index = this.data.findIndex((item) => item.id === item_id); From 1515d405775f6ce80de97c51a8cd4b5f27f1a34b Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 15:38:46 +0000 Subject: [PATCH 06/14] Deprecated but functional --- .../static/src/views/timeline/timeline_model.esm.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index 1fd7a6d4c7c1..bd1ebe369571 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -33,18 +33,18 @@ export class TimelineModel extends Model { onWillStart(async () => { this.write_right = await this.orm.call( this.model_name, - "check_access", - ["", 'write'] + "check_access_rights", + ["write", false] ); this.unlink_right = await this.orm.call( this.model_name, - "check_access", - ["", 'unlink'] + "check_access_rights", + ["unlink", false] ); this.create_right = await this.orm.call( this.model_name, - "check_access", - ["", 'create'] + "check_access_rights", + ["create", false] ); }); } From e87e0092c1b9c6ec824d5d09113afba17eec7958 Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 15:53:59 +0000 Subject: [PATCH 07/14] Linting? --- .../views/timeline/timeline_controller.esm.js | 14 ++++++----- .../src/views/timeline/timeline_model.esm.js | 25 +++++++++++-------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_controller.esm.js b/web_timeline/static/src/views/timeline/timeline_controller.esm.js index 9b50d42bf0d7..aeef04fef541 100644 --- a/web_timeline/static/src/views/timeline/timeline_controller.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_controller.esm.js @@ -80,9 +80,10 @@ export class TimelineController extends Component { * @returns {jQuery.Deferred} */ _onItemDoubleClick(event) { - const item_id = typeof event.item === 'string' && event.item.indexOf('_') !== -1 - ? Number(event.item.split('_')[0]) || event.item.split('_')[0] - : Number(event.item) || event.item; + const item_id = + typeof event.item === 'string' && event.item.indexOf('_') !== -1 + ? Number(event.item.split('_')[0]) || event.item.split('_')[0] + : Number(event.item) || event.item; // Update event item to be just the ID event.item = item_id; @@ -100,9 +101,10 @@ export class TimelineController extends Component { */ _onUpdate(item) { - const item_id = typeof item.evt.id === 'string' && item.evt.id.indexOf('_') !== -1 - ? Number(item.evt.id.split('_')[0]) || item.evt.id.split('_')[0] - : Number(item.evt.id) || item.evt.id; + const item_id = + typeof item.evt.id === 'string' && item.evt.id.indexOf('_') !== -1 + ? Number(item.evt.id.split('_')[0]) || item.evt.id.split('_')[0] + : Number(item.evt.id) || item.evt.id; return this.openItem(item_id, true); } diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index bd1ebe369571..f97c7175d987 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -64,7 +64,7 @@ export class TimelineModel extends Model { // because it is not supported by Odoo // In the module sale_timesheet_timeline, it is used // with default_group_by = task_user_ids - let field_to_order = this.params.default_group_by; + const field_to_order = this.params.default_group_by; // if (this.fields[field_to_order].type === "many2many") { // field_to_order = undefined; // } @@ -88,12 +88,13 @@ export class TimelineModel extends Model { _event_data_transform(record) { const [date_start, date_stop] = this._get_event_dates(record); - let group = record[this.last_group_bys[0]]; + const group = record[this.last_group_bys[0]]; let groups = []; - if (group && Array.isArray(group) && group.length === 2) { + if (group && Array.isArray(group) && group.length === 2 && typeof group[1] === 'string') { + // TODO: this breaks if m2m and only 2 items + // I guess detect if its a number/id or array of ids groups.push(group[0]); - } - else{ + } else{ groups = group || -1; } if (groups.length === 0) { @@ -118,7 +119,7 @@ export class TimelineModel extends Model { const timeline_item = { start: date_start.toJSDate(), content: content, - id: record.id + (this_group ? `_${this_group}` : ''), + id: record.id + (this_group ? `_${this_group}` : ""), order: record.order, group: this_group, evt: record, @@ -126,7 +127,10 @@ export class TimelineModel extends Model { }; // Only specify range end when there actually is one. // ➔ Instantaneous events / those with inverted dates are displayed as points. - if (date_stop && DateTime.fromISO(date_start) < DateTime.fromISO(date_stop)) { + if ( + date_stop && + DateTime.fromISO(date_start) < DateTime.fromISO(date_stop) + ) { timeline_item.end = date_stop.toJSDate(); } all_timeline_items.push(timeline_item); @@ -207,9 +211,10 @@ export class TimelineModel extends Model { */ async remove_completed(event) { - const item_id = typeof event.evt.id === 'string' && event.evt.id.indexOf('_') !== -1 - ? Number(event.evt.id.split('_')[0]) || event.evt.id.split('_')[0] - : Number(event.evt.id) || event.evt.id; + const item_id = + typeof event.evt.id === 'string' && event.evt.id.indexOf('_') !== -1 + ? Number(event.evt.id.split('_')[0]) || event.evt.id.split('_')[0] + : Number(event.evt.id) || event.evt.id; await this.orm.call(this.model_name, "unlink", [[item_id]]); const unlink_index = this.data.findIndex((item) => item.id === item_id); From 2db813822f93ebbc12ef533bef08a6b629b98b0f Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 17:10:06 +0000 Subject: [PATCH 08/14] Linting --- .../src/views/timeline/timeline_controller.esm.js | 6 +++--- .../src/views/timeline/timeline_model.esm.js | 15 ++++++++++----- .../src/views/timeline/timeline_renderer.esm.js | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_controller.esm.js b/web_timeline/static/src/views/timeline/timeline_controller.esm.js index aeef04fef541..3a8776048203 100644 --- a/web_timeline/static/src/views/timeline/timeline_controller.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_controller.esm.js @@ -80,13 +80,13 @@ export class TimelineController extends Component { * @returns {jQuery.Deferred} */ _onItemDoubleClick(event) { - const item_id = + const item_id = typeof event.item === 'string' && event.item.indexOf('_') !== -1 ? Number(event.item.split('_')[0]) || event.item.split('_')[0] : Number(event.item) || event.item; // Update event item to be just the ID - event.item = item_id; + event.item = item_id; return this.openItem(event.item, false); } @@ -101,7 +101,7 @@ export class TimelineController extends Component { */ _onUpdate(item) { - const item_id = + const item_id = typeof item.evt.id === 'string' && item.evt.id.indexOf('_') !== -1 ? Number(item.evt.id.split('_')[0]) || item.evt.id.split('_')[0] : Number(item.evt.id) || item.evt.id; diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index f97c7175d987..6f9c5c35146a 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -65,7 +65,7 @@ export class TimelineModel extends Model { // In the module sale_timesheet_timeline, it is used // with default_group_by = task_user_ids const field_to_order = this.params.default_group_by; - // if (this.fields[field_to_order].type === "many2many") { + // If (this.fields[field_to_order].type === "many2many") { // field_to_order = undefined; // } this.data = await this.keepLast.add( @@ -90,17 +90,22 @@ export class TimelineModel extends Model { const [date_start, date_stop] = this._get_event_dates(record); const group = record[this.last_group_bys[0]]; let groups = []; - if (group && Array.isArray(group) && group.length === 2 && typeof group[1] === 'string') { + if (group && + Array.isArray(group) && + group.length === 2 && + typeof group[1] === 'string' + ) { // TODO: this breaks if m2m and only 2 items // I guess detect if its a number/id or array of ids groups.push(group[0]); - } else{ + } else { groups = group || -1; } if (groups.length === 0) { groups = [-1]; } + // eslint-disable-next-line prefer-const let all_timeline_items = []; for (const this_group of groups) { @@ -128,7 +133,7 @@ export class TimelineModel extends Model { // Only specify range end when there actually is one. // ➔ Instantaneous events / those with inverted dates are displayed as points. if ( - date_stop && + date_stop && DateTime.fromISO(date_start) < DateTime.fromISO(date_stop) ) { timeline_item.end = date_stop.toJSDate(); @@ -211,7 +216,7 @@ export class TimelineModel extends Model { */ async remove_completed(event) { - const item_id = + const item_id = typeof event.evt.id === 'string' && event.evt.id.indexOf('_') !== -1 ? Number(event.evt.id.split('_')[0]) || event.evt.id.split('_')[0] : Number(event.evt.id) || event.evt.id; diff --git a/web_timeline/static/src/views/timeline/timeline_renderer.esm.js b/web_timeline/static/src/views/timeline/timeline_renderer.esm.js index 60d296038f49..fd9c578b8386 100644 --- a/web_timeline/static/src/views/timeline/timeline_renderer.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_renderer.esm.js @@ -336,7 +336,7 @@ export class TimelineRenderer extends Component { } } - const groups = await this.split_groups(records); + const groups = await this.split_groups(records); this.timeline.setGroups(groups); this.timeline.setItems(data); const mode = !this.mode.data || this.mode.data === "fit"; From f9aa6bd3ba9cbe90a37c8b4fed86cff0ccf8402b Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 17:13:23 +0000 Subject: [PATCH 09/14] Lint --- .../src/views/timeline/timeline_controller.esm.js | 8 ++++---- .../static/src/views/timeline/timeline_model.esm.js | 13 +++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_controller.esm.js b/web_timeline/static/src/views/timeline/timeline_controller.esm.js index 3a8776048203..4a868250dba6 100644 --- a/web_timeline/static/src/views/timeline/timeline_controller.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_controller.esm.js @@ -81,8 +81,8 @@ export class TimelineController extends Component { */ _onItemDoubleClick(event) { const item_id = - typeof event.item === 'string' && event.item.indexOf('_') !== -1 - ? Number(event.item.split('_')[0]) || event.item.split('_')[0] + typeof event.item === "string" && event.item.indexOf("_") !== -1 + ? Number(event.item.split("_")[0]) || event.item.split("_")[0] : Number(event.item) || event.item; // Update event item to be just the ID @@ -102,8 +102,8 @@ export class TimelineController extends Component { _onUpdate(item) { const item_id = - typeof item.evt.id === 'string' && item.evt.id.indexOf('_') !== -1 - ? Number(item.evt.id.split('_')[0]) || item.evt.id.split('_')[0] + typeof item.evt.id === "string" && item.evt.id.indexOf("_") !== -1 + ? Number(item.evt.id.split("_")[0]) || item.evt.id.split("_")[0] : Number(item.evt.id) || item.evt.id; return this.openItem(item_id, true); } diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index 6f9c5c35146a..d4c4c6909c6b 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -90,10 +90,11 @@ export class TimelineModel extends Model { const [date_start, date_stop] = this._get_event_dates(record); const group = record[this.last_group_bys[0]]; let groups = []; - if (group && + if ( + group && Array.isArray(group) && group.length === 2 && - typeof group[1] === 'string' + typeof group[1] === "string" ) { // TODO: this breaks if m2m and only 2 items // I guess detect if its a number/id or array of ids @@ -105,10 +106,10 @@ export class TimelineModel extends Model { groups = [-1]; } - // eslint-disable-next-line prefer-const + // eslint-disable-next-line prefer-constd let all_timeline_items = []; - for (const this_group of groups) { + for (const this_group of groups) { let colorToApply = false; for (const color of this.colors) { if (evaluate(color.ast, record)) { @@ -217,8 +218,8 @@ export class TimelineModel extends Model { async remove_completed(event) { const item_id = - typeof event.evt.id === 'string' && event.evt.id.indexOf('_') !== -1 - ? Number(event.evt.id.split('_')[0]) || event.evt.id.split('_')[0] + typeof event.evt.id === "string" && event.evt.id.indexOf("_") !== -1 + ? Number(event.evt.id.split("_")[0]) || event.evt.id.split("_")[0] : Number(event.evt.id) || event.evt.id; await this.orm.call(this.model_name, "unlink", [[item_id]]); From c7197011570b5ba18865d3eddc7e5e4d8b71ca67 Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 17:16:03 +0000 Subject: [PATCH 10/14] Helps if i can spell --- web_timeline/static/src/views/timeline/timeline_model.esm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index d4c4c6909c6b..6f9811f15978 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -106,7 +106,7 @@ export class TimelineModel extends Model { groups = [-1]; } - // eslint-disable-next-line prefer-constd + // eslint-disable-next-line prefer-const let all_timeline_items = []; for (const this_group of groups) { From 15aceb701effcbed44f2e03559b6356dfd0a89d5 Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 17:18:20 +0000 Subject: [PATCH 11/14] whitespace --- .../static/src/views/timeline/timeline_controller.esm.js | 1 - web_timeline/static/src/views/timeline/timeline_model.esm.js | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_controller.esm.js b/web_timeline/static/src/views/timeline/timeline_controller.esm.js index 4a868250dba6..f4012303f71b 100644 --- a/web_timeline/static/src/views/timeline/timeline_controller.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_controller.esm.js @@ -100,7 +100,6 @@ export class TimelineController extends Component { * @returns {Object} */ _onUpdate(item) { - const item_id = typeof item.evt.id === "string" && item.evt.id.indexOf("_") !== -1 ? Number(item.evt.id.split("_")[0]) || item.evt.id.split("_")[0] diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index 6f9811f15978..40dd4023ec35 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -86,7 +86,6 @@ export class TimelineModel extends Model { * @returns {Object} */ _event_data_transform(record) { - const [date_start, date_stop] = this._get_event_dates(record); const group = record[this.last_group_bys[0]]; let groups = []; @@ -105,7 +104,7 @@ export class TimelineModel extends Model { if (groups.length === 0) { groups = [-1]; } - + // eslint-disable-next-line prefer-const let all_timeline_items = []; @@ -216,7 +215,6 @@ export class TimelineModel extends Model { * @returns {jQuery.Deferred} */ async remove_completed(event) { - const item_id = typeof event.evt.id === "string" && event.evt.id.indexOf("_") !== -1 ? Number(event.evt.id.split("_")[0]) || event.evt.id.split("_")[0] From 284ce12fc14db027706602c3ef1ff207bc157894 Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 18:36:56 +0000 Subject: [PATCH 12/14] Allow creating new on m2m groupings --- .../src/views/timeline/timeline_controller.esm.js | 14 ++++++++++++-- .../src/views/timeline/timeline_renderer.esm.js | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_controller.esm.js b/web_timeline/static/src/views/timeline/timeline_controller.esm.js index f4012303f71b..446a77f2ac4b 100644 --- a/web_timeline/static/src/views/timeline/timeline_controller.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_controller.esm.js @@ -87,7 +87,6 @@ export class TimelineController extends Component { // Update event item to be just the ID event.item = item_id; - return this.openItem(event.item, false); } @@ -112,6 +111,12 @@ export class TimelineController extends Component { * @param {Boolean} is_editable */ openItem(item_id, is_editable) { + const _id = + typeof item_id === "string" && item_id.indexOf("_") !== -1 + ? Number(item_id.split("_")[0]) || item_id.split("_")[0] + : Number(item_id) || item_id; + item_id = _id; + if (this.open_popup_action) { const options = { resModel: this.model.model_name, @@ -254,7 +259,12 @@ export class TimelineController extends Component { context[`default_${this.date_delay}`] = diff.hours; } if (item.group > 0) { - context[`default_${this.model.last_group_bys[0]}`] = item.group; + if (this.model.fields[this.model.last_group_bys[0]].type !== "many2many") { + context[`default_${this.model.last_group_bys[0]}`] = item.group; + } + else { + context[`default_${this.model.last_group_bys[0]}`] = [item.group]; + } } // Show popup this.dialogService.add( diff --git a/web_timeline/static/src/views/timeline/timeline_renderer.esm.js b/web_timeline/static/src/views/timeline/timeline_renderer.esm.js index fd9c578b8386..e5f7937cb138 100644 --- a/web_timeline/static/src/views/timeline/timeline_renderer.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_renderer.esm.js @@ -429,6 +429,8 @@ export class TimelineRenderer extends Component { * @private */ on_timeline_double_click(e) { + // Fun Fact: this function never gets called. + // On_timeline_click fires twice but this never does. if (e.what === "item" && e.item !== -1) { this.props.onItemDoubleClick(e); } From b7698f8c70e6e3244eb0da78732bd141fd43fa48 Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Fri, 9 Jan 2026 18:39:11 +0000 Subject: [PATCH 13/14] lint --- .../static/src/views/timeline/timeline_controller.esm.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web_timeline/static/src/views/timeline/timeline_controller.esm.js b/web_timeline/static/src/views/timeline/timeline_controller.esm.js index 446a77f2ac4b..66bb705a91fe 100644 --- a/web_timeline/static/src/views/timeline/timeline_controller.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_controller.esm.js @@ -261,8 +261,7 @@ export class TimelineController extends Component { if (item.group > 0) { if (this.model.fields[this.model.last_group_bys[0]].type !== "many2many") { context[`default_${this.model.last_group_bys[0]}`] = item.group; - } - else { + } else { context[`default_${this.model.last_group_bys[0]}`] = [item.group]; } } From 6930fd2cbb3b588d9fd95917612abb3abc9d60d8 Mon Sep 17 00:00:00 2001 From: Chad Woitas Date: Mon, 12 Jan 2026 22:38:57 +0000 Subject: [PATCH 14/14] Allow M2M creation of jobs --- web_timeline/static/src/views/timeline/timeline_model.esm.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web_timeline/static/src/views/timeline/timeline_model.esm.js b/web_timeline/static/src/views/timeline/timeline_model.esm.js index 40dd4023ec35..884873364c04 100644 --- a/web_timeline/static/src/views/timeline/timeline_model.esm.js +++ b/web_timeline/static/src/views/timeline/timeline_model.esm.js @@ -99,12 +99,13 @@ export class TimelineModel extends Model { // I guess detect if its a number/id or array of ids groups.push(group[0]); } else { - groups = group || -1; + groups = group || []; } if (groups.length === 0) { groups = [-1]; } + console.log("groups", groups); // eslint-disable-next-line prefer-const let all_timeline_items = [];