diff --git a/addons/web/static/src/views/list/list_renderer.js b/addons/web/static/src/views/list/list_renderer.js index e6aabcfeed3..be0921bf9d4 100644 --- a/addons/web/static/src/views/list/list_renderer.js +++ b/addons/web/static/src/views/list/list_renderer.js @@ -670,10 +670,21 @@ export class ListRenderer extends Component { } get getEmptyRowIds() { - const nbEmptyRow = Math.max(0, 4 - this.props.list.records.length); + let nbEmptyRow = Math.max(0, 4 - this.props.list.records.length); + if (nbEmptyRow > 0 && this.displayRowCreates) { + nbEmptyRow -= 1; + } return Array.from(Array(nbEmptyRow).keys()); } + get displayRowCreates() { + const activeActions = this.props.activeActions; + return ( + activeActions && + ("canLink" in activeActions ? activeActions.canLink : activeActions.canCreate) + ); + } + // Group headers logic: // if there are aggregates, the first th spans until the first // aggregate column then all cells between aggregates are rendered @@ -1064,7 +1075,7 @@ export class ListRenderer extends Component { switch (hotkey) { case "tab": // X2many add a line - if (activeActions && (activeActions.canLink || activeActions.canCreate)) { + if (this.displayRowCreates) { if (record.isNew && !record.isDirty) { list.unselectRecord(true); return false; @@ -1133,7 +1144,7 @@ export class ListRenderer extends Component { case "tab": { const index = list.records.indexOf(record); if (index === list.records.length - 1) { - if (activeActions && (activeActions.canLink || activeActions.canCreate)) { + if (this.displayRowCreates) { if (record.isNew && !record.isDirty) { list.unselectRecord(true); return false; @@ -1208,11 +1219,7 @@ export class ListRenderer extends Component { if (futureRecord) { futureRecord.switchMode("edit"); - } else if ( - this.lastIsDirty || - !record.canBeAbandoned || - (activeActions && (activeActions.canLink || activeActions.canCreate)) - ) { + } else if (this.lastIsDirty || !record.canBeAbandoned || this.displayRowCreates) { this.props.onAdd({ group }); } else { futureRecord = list.records.at(0); diff --git a/addons/web/static/src/views/list/list_renderer.xml b/addons/web/static/src/views/list/list_renderer.xml index 9b2ed6fdbf8..ccf5fd68ddd 100644 --- a/addons/web/static/src/views/list/list_renderer.xml +++ b/addons/web/static/src/views/list/list_renderer.xml @@ -34,26 +34,6 @@ - - - - - - - - - - - @@ -93,6 +73,25 @@ + + + + + + + + + + ​ diff --git a/addons/web/static/tests/views/fields/one2many_field_tests.js b/addons/web/static/tests/views/fields/one2many_field_tests.js index 41e24be93ea..2925976ba7c 100644 --- a/addons/web/static/tests/views/fields/one2many_field_tests.js +++ b/addons/web/static/tests/views/fields/one2many_field_tests.js @@ -12252,4 +12252,50 @@ QUnit.module("Fields", (hooks) => { "localStorage getItem optional_fields,partner,form,100000001,turtles,list,display_name", ]); }); + + QUnit.test( + "if there are less than 4 lines in a one2many, empty lines must be displayed to cover the difference.", + async function (assert) { + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + + + + +
`, + resId: 1, + }); + + // Should contain 4 blank lines + assert.containsNone(target, ".o_list_renderer tbody tr .o_data_row"); + assert.containsNone(target, ".o_list_renderer tbody tr .o_field_x2many_list_row_add"); + assert.containsN(target, ".o_list_renderer tbody tr", 4); + + await clickEdit(target); + // Should only contain the "Add a line" line and 3 blank lines + assert.containsNone(target, ".o_list_renderer tbody tr .o_data_row"); + assert.containsOnce(target, ".o_list_renderer tbody tr .o_field_x2many_list_row_add"); + assert.hasClass( + target.querySelector(".o_list_renderer tbody tr td"), + "o_field_x2many_list_row_add" + ); + assert.containsN(target, ".o_list_renderer tbody tr", 4); + + await addRow(target); + // Should only contain a new row, the "Add a line" line and 2 blank lines + assert.containsOnce(target, ".o_list_renderer tbody tr.o_data_row"); + assert.hasClass(target.querySelector(".o_list_renderer tbody tr"), "o_data_row"); + assert.containsOnce(target, ".o_list_renderer tbody tr .o_field_x2many_list_row_add"); + assert.hasClass( + target.querySelectorAll(".o_list_renderer tbody tr")[1].querySelector("td"), + "o_field_x2many_list_row_add" + ); + assert.containsN(target, ".o_list_renderer tbody tr", 4); + } + ); }); diff --git a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js index 4f9cd36f7b1..0c62de8193e 100644 --- a/odoo/addons/test_main_flows/static/tests/tours/main_flow.js +++ b/odoo/addons/test_main_flows/static/tests/tours/main_flow.js @@ -332,7 +332,7 @@ tour.stepUtils.autoExpandMoreButtons('.o_form_readonly'), position: 'bottom', }, { trigger: '.o_form_button_save', - extra_trigger: ".o_field_widget[name=bom_line_ids] tr:nth-child(5):has(.o_field_x2many_list_row_add)", + extra_trigger: ".o_field_widget[name=bom_line_ids] tr:nth-child(3):has(.o_field_x2many_list_row_add)", content: _t('Save the bom.'), position: 'bottom', }, {