From a2342f636c712873088591bede4dbf1053e8f751 Mon Sep 17 00:00:00 2001 From: FrancoisGe Date: Wed, 29 Mar 2023 16:22:14 +0000 Subject: [PATCH] [REF] web,crm: remove allow_group_range_value The allow_group_range_value option is used in one arch of a kanban in CRM. We therefore believe that this is not standard behaviour. So we decided to move this logic to the custom view that uses it. Part of Task: 3179751 closes odoo/odoo#117023 Signed-off-by: Aaron Bohy (aab) --- .../forecast_kanban_controller.js | 9 + .../forecast_kanban_renderer.js | 4 + .../forecast_kanban/forecast_kanban_view.js | 8 +- addons/crm/views/crm_lead_views.xml | 2 +- .../src/views/kanban/kanban_controller.js | 26 +- .../src/views/kanban/kanban_controller.xml | 1 + .../src/views/kanban/kanban_renderer.js | 58 +--- .../web/static/src/views/relational_model.js | 28 +- .../static/tests/views/kanban_view_tests.js | 264 +----------------- odoo/addons/base/rng/common.rng | 1 - 10 files changed, 74 insertions(+), 327 deletions(-) create mode 100644 addons/crm/static/src/views/forecast_kanban/forecast_kanban_controller.js diff --git a/addons/crm/static/src/views/forecast_kanban/forecast_kanban_controller.js b/addons/crm/static/src/views/forecast_kanban/forecast_kanban_controller.js new file mode 100644 index 00000000000..588b95ed0be --- /dev/null +++ b/addons/crm/static/src/views/forecast_kanban/forecast_kanban_controller.js @@ -0,0 +1,9 @@ +/** @odoo-module **/ + +import { KanbanController } from "@web/views/kanban/kanban_controller"; + +export class ForecastKanbanController extends KanbanController { + isQuickCreateField(field) { + return super.isQuickCreateField(...arguments) || (field && field.name === "date_deadline"); + } +} diff --git a/addons/crm/static/src/views/forecast_kanban/forecast_kanban_renderer.js b/addons/crm/static/src/views/forecast_kanban/forecast_kanban_renderer.js index 1f77a87e999..5d3ab52d064 100644 --- a/addons/crm/static/src/views/forecast_kanban/forecast_kanban_renderer.js +++ b/addons/crm/static/src/views/forecast_kanban/forecast_kanban_renderer.js @@ -25,6 +25,10 @@ export class ForecastKanbanRenderer extends CrmKanbanRenderer { ); } + isMovableField(field) { + return super.isMovableField(...arguments) || field.name === "date_deadline"; + } + async addForecastColumn() { const { name, type, granularity } = this.props.list.groupByField; this.fillTemporalService diff --git a/addons/crm/static/src/views/forecast_kanban/forecast_kanban_view.js b/addons/crm/static/src/views/forecast_kanban/forecast_kanban_view.js index 956d7784d93..d399782498a 100644 --- a/addons/crm/static/src/views/forecast_kanban/forecast_kanban_view.js +++ b/addons/crm/static/src/views/forecast_kanban/forecast_kanban_view.js @@ -1,16 +1,18 @@ /** @odoo-module **/ +import { ForecastKanbanController } from "@crm/views/forecast_kanban/forecast_kanban_controller"; import { CrmKanbanArchParser } from "@crm/views/crm_kanban/crm_kanban_arch_parser"; -import { registry } from "@web/core/registry"; -import { kanbanView } from "@web/views/kanban/kanban_view"; +import { ForecastKanbanModel } from "@crm/views/forecast_kanban/forecast_kanban_model"; import { ForecastKanbanRenderer } from "@crm/views/forecast_kanban/forecast_kanban_renderer"; import { ForecastSearchModel } from "@crm/views/forecast_search_model"; -import { ForecastKanbanModel } from "@crm/views/forecast_kanban/forecast_kanban_model"; +import { registry } from "@web/core/registry"; +import { kanbanView } from "@web/views/kanban/kanban_view"; export const forecastKanbanView = { ...kanbanView, ArchParser: CrmKanbanArchParser, Model: ForecastKanbanModel, + Controller: ForecastKanbanController, Renderer: ForecastKanbanRenderer, SearchModel: ForecastSearchModel, }; diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml index 0104bb8a729..d46c51840b4 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -619,7 +619,7 @@ forecast_kanban - + diff --git a/addons/web/static/src/views/kanban/kanban_controller.js b/addons/web/static/src/views/kanban/kanban_controller.js index 9c010366fb8..9e80d9d225d 100644 --- a/addons/web/static/src/views/kanban/kanban_controller.js +++ b/addons/web/static/src/views/kanban/kanban_controller.js @@ -8,10 +8,12 @@ import { standardViewProps } from "@web/views/standard_view_props"; import { MultiRecordViewButton } from "@web/views/view_button/multi_record_view_button"; import { useViewButtons } from "@web/views/view_button/view_button_hook"; import { useSetupView } from "@web/views/view_hook"; -import { canQuickCreate, KanbanRenderer } from "./kanban_renderer"; +import { KanbanRenderer } from "./kanban_renderer"; import { Component, useRef } from "@odoo/owl"; +const QUICK_CREATE_FIELD_TYPES = ["char", "boolean", "many2one", "selection", "many2many"]; + // ----------------------------------------------------------------------------- export class KanbanController extends Component { @@ -92,9 +94,9 @@ export class KanbanController extends Component { } async createRecord(group) { - const { activeActions, onCreate } = this.props.archInfo; + const { onCreate } = this.props.archInfo; const { root } = this.model; - if (activeActions.quickCreate && onCreate === "quick_create" && canQuickCreate(root)) { + if (this.canQuickCreate && onCreate === "quick_create") { await root.quickCreate(group); } else if (onCreate && onCreate !== "quick_create") { const options = { @@ -120,6 +122,20 @@ export class KanbanController extends Component { return list.isGrouped ? list.groups.length > 0 || !createGroup : true; } + get canQuickCreate() { + const { activeActions } = this.props.archInfo; + if (!activeActions.quickCreate) { + return false; + } + + const list = this.model.root; + if (list.groups && !list.groups.length) { + return false; + } + + return this.isQuickCreateField(list.groupByField); + } + async beforeExecuteActionButton(clickParams) {} async afterExecuteActionButton(clickParams) {} @@ -129,6 +145,10 @@ export class KanbanController extends Component { scrollTop() { this.rootRef.el.querySelector(".o_content").scrollTo({ top: 0 }); } + + isQuickCreateField(field) { + return field && QUICK_CREATE_FIELD_TYPES.includes(field.type); + } } KanbanController.template = `web.KanbanView`; diff --git a/addons/web/static/src/views/kanban/kanban_controller.xml b/addons/web/static/src/views/kanban/kanban_controller.xml index ebdcb614255..39e2c50bc2e 100644 --- a/addons/web/static/src/views/kanban/kanban_controller.xml +++ b/addons/web/static/src/views/kanban/kanban_controller.xml @@ -16,6 +16,7 @@ openRecord.bind="openRecord" noContentHelp="props.info.noContentHelp" scrollTop.bind="scrollTop" + canQuickCreate="canQuickCreate" /> diff --git a/addons/web/static/src/views/kanban/kanban_renderer.js b/addons/web/static/src/views/kanban/kanban_renderer.js index aed78412b8c..b51c1e118cc 100644 --- a/addons/web/static/src/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/views/kanban/kanban_renderer.js @@ -9,7 +9,7 @@ import { registry } from "@web/core/registry"; import { useBus, useService } from "@web/core/utils/hooks"; import { useSortable } from "@web/core/utils/sortable"; import { sprintf } from "@web/core/utils/strings"; -import { archParseBoolean, isNull, isRelational } from "@web/views/utils"; +import { isNull, isRelational } from "@web/views/utils"; import { ColumnProgress } from "@web/views/view_components/column_progress"; import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog"; import { useBounceButton } from "@web/views/view_hook"; @@ -17,38 +17,10 @@ import { KanbanColumnQuickCreate } from "./kanban_column_quick_create"; import { KanbanRecord } from "./kanban_record"; import { KanbanRecordQuickCreate } from "./kanban_record_quick_create"; -import { Component, onWillDestroy, useRef, useState, onPatched, onWillPatch } from "@odoo/owl"; +import { Component, onPatched, onWillDestroy, onWillPatch, useRef, useState } from "@odoo/owl"; const DRAGGABLE_GROUP_TYPES = ["many2one"]; const MOVABLE_RECORD_TYPES = ["char", "boolean", "integer", "selection", "many2one"]; -const QUICK_CREATE_FIELD_TYPES = ["char", "boolean", "many2one", "selection", "many2many"]; - -/** - * @param {Object} groupByField - * @returns {boolean} - */ -export function isAllowedDateField(groupByField) { - return ( - ["date", "datetime"].includes(groupByField.type) && - archParseBoolean(groupByField.attrs.allow_group_range_value) - ); -} - -/** - * @param {DynamicList} list - * @returns {boolean} - */ -export function canQuickCreate(list) { - if (list.groups && !list.groups.length) { - return false; - } - - return ( - list.groupByField && - (isAllowedDateField(list.groupByField) || - QUICK_CREATE_FIELD_TYPES.includes(list.groupByField.type)) - ); -} function validateColumnQuickCreateExamples(data) { const { allowedGroupBys = [], examples = [], foldField = "" } = data; @@ -83,6 +55,7 @@ export class KanbanRenderer extends Component { "forceGlobalClick?", "noContentHelp?", "scrollTop?", + "canQuickCreate?", ]; static defaultProps = { scrollTop: () => {}, @@ -237,16 +210,12 @@ export class KanbanRenderer extends Component { if (!this.canResequenceRecords) { return false; } - if (!this.props.list.groupByField) { + const { groupByField } = this.props.list; + if (!groupByField) { return true; } - const { groupByField, fields } = this.props.list; - const { modifiers, type } = groupByField; - return Boolean( - !(modifiers && "readonly" in modifiers - ? modifiers.readonly - : fields[groupByField.name].readonly) && - (isAllowedDateField(groupByField) || MOVABLE_RECORD_TYPES.includes(type)) + return ( + !this.props.list.isFieldReadonly(groupByField.name) && this.isMovableField(groupByField) ); } @@ -254,14 +223,11 @@ export class KanbanRenderer extends Component { if (!this.props.list.isGrouped) { return false; } - const { groupByField, fields } = this.props.list; - const { modifiers, type } = groupByField; + const { name, type } = this.props.list.groupByField; const { groupsDraggable } = this.props.archInfo; return ( groupsDraggable && - !(modifiers && "readonly" in modifiers - ? modifiers.readonly - : fields[groupByField.name].readonly) && + !this.props.list.isFieldReadonly(name) && DRAGGABLE_GROUP_TYPES.includes(type) ); } @@ -390,6 +356,10 @@ export class KanbanRenderer extends Component { return this.state.processedIds.includes(id); } + isMovableField(field) { + return MOVABLE_RECORD_TYPES.includes(field.type); + } + // ------------------------------------------------------------------------ // Permissions // ------------------------------------------------------------------------ @@ -431,7 +401,7 @@ export class KanbanRenderer extends Component { } canQuickCreate() { - return this.props.archInfo.activeActions.quickCreate && canQuickCreate(this.props.list); + return this.props.canQuickCreate; } // ------------------------------------------------------------------------ diff --git a/addons/web/static/src/views/relational_model.js b/addons/web/static/src/views/relational_model.js index 9ff6d31c0b9..3371e07def4 100644 --- a/addons/web/static/src/views/relational_model.js +++ b/addons/web/static/src/views/relational_model.js @@ -298,6 +298,20 @@ class DataPoint { */ setup() {} + /** + * FIXME: memoize this at some point? + * @param {string} fieldName + * @returns {boolean} + */ + isFieldReadonly(fieldName) { + const activeField = this.activeFields[fieldName]; + const { readonly } = + activeField && activeField.modifiers && "readonly" in activeField.modifiers + ? activeField.modifiers + : this.fields[fieldName]; + return evalDomain(readonly, this.evalContext); + } + /** * @param {Object} [activeFields={}] */ @@ -687,7 +701,7 @@ export class Record extends DataPoint { !allFields && fieldName in this.activeFields && !this.activeFields[fieldName].forceSave && - this._isReadonly(fieldName) + this.isFieldReadonly(fieldName) ) { delete changes[fieldName]; continue; @@ -747,16 +761,6 @@ export class Record extends DataPoint { return invisible ? evalDomain(invisible, this.evalContext) : false; } - /** - * FIXME: memoize this at some point? - * @param {string} fieldName - * @returns {boolean} - */ - _isReadonly(fieldName) { - const { readonly } = this.activeFields[fieldName].modifiers || {}; - return evalDomain(readonly, this.evalContext); - } - /** * FIXME: memoize this at some point? * @param {string} fieldName @@ -1712,7 +1716,7 @@ class DynamicList extends DataPoint { if ( !Object.keys(changes).filter( (fieldName) => - record._isReadonly(fieldName) || + record.isFieldReadonly(fieldName) || (record._isRequired(fieldName) && !changes[fieldName]) ).length ) { diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js index a5bb2b8d733..7fa715ed160 100644 --- a/addons/web/static/tests/views/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban_view_tests.js @@ -3911,80 +3911,6 @@ QUnit.module("Views", (hooks) => { assert.verifySteps(["createRecord", "createRecord"]); }); - QUnit.test( - "quick create record if grouped on date(time) field with attribute allow_group_range_value: true", - async (assert) => { - serverData.models.partner.records[0].date = "2017-01-08"; - serverData.models.partner.records[1].date = "2017-01-09"; - serverData.models.partner.records[2].date = "2017-01-08"; - serverData.models.partner.records[3].date = "2017-01-10"; - serverData.models.partner.records[0].datetime = "2017-01-08 10:55:05"; - serverData.models.partner.records[1].datetime = "2017-01-09 11:31:10"; - serverData.models.partner.records[2].datetime = "2017-01-08 09:20:25"; - serverData.models.partner.records[3].datetime = "2017-01-10 08:05:51"; - serverData.views["partner,quick_form,form"] = - "
" + '' + '' + ""; - - const kanban = await makeView({ - type: "kanban", - resModel: "partner", - serverData, - arch: - '' + - '' + - '' + - '' + - '
' + - "
" + - "
", - groupBy: ["date"], - }); - - assert.containsOnce( - target, - ".o_kanban_header .o_kanban_quick_add i", - "quick create should be enabled when grouped on a non-readonly date field" - ); - - // clicking on CREATE in control panel should open a quick create - await createRecord(); - assert.containsOnce( - target, - ".o_kanban_group:first-child .o_kanban_quick_create", - "should have opened the quick create in the first column" - ); - assert.strictEqual( - target.querySelector( - ".o_kanban_group:first-child .o_kanban_quick_create .o_field_widget[name=date] .o_datepicker input" - ).value, - "01/31/2017" - ); - - await reload(kanban, { groupBy: ["datetime"] }); - - assert.containsOnce( - target, - ".o_kanban_header .o_kanban_quick_add i", - "quick create should be enabled when grouped on a non-readonly datetime field" - ); - - // clicking on CREATE in control panel should open a quick create - await createRecord(); - - assert.containsOnce( - target, - ".o_kanban_group:first-child .o_kanban_quick_create", - "should have opened the quick create in the first column" - ); - assert.strictEqual( - target.querySelector( - ".o_kanban_group:first-child .o_kanban_quick_create .o_field_widget[name=datetime] .o_datepicker input" - ).value, - "01/31/2017 23:59:59" - ); - } - ); - QUnit.test( "quick create record feature is properly enabled/disabled at reload", async (assert) => { @@ -5217,194 +5143,6 @@ QUnit.module("Views", (hooks) => { ); }); - QUnit.test( - "drag and drop record if grouped by date/time field with attribute allow_group_range_value: true", - async (assert) => { - assert.expect(16); - - serverData.models.partner.records[0].date = "2017-01-08"; - serverData.models.partner.records[1].date = "2017-01-09"; - serverData.models.partner.records[2].date = "2017-02-08"; - serverData.models.partner.records[3].date = "2017-02-10"; - serverData.models.partner.records[0].datetime = "2017-01-08 10:55:05"; - serverData.models.partner.records[1].datetime = "2017-01-09 11:31:10"; - serverData.models.partner.records[2].datetime = "2017-02-08 09:20:25"; - serverData.models.partner.records[3].datetime = "2017-02-10 08:05:51"; - - const kanban = await makeView({ - type: "kanban", - resModel: "partner", - serverData, - arch: - "" + - '' + - '' + - "" + - '' + - '
' + - "
" + - "
" + - "
", - groupBy: ["date:month"], - async mockRPC(route, { model, method, args }) { - if (route === "/web/dataset/resequence") { - assert.step("resequence"); - return true; - } - if (model === "partner" && method === "write") { - if ("date" in args[1]) { - assert.deepEqual(args[1], { date: "2017-02-28" }); - } else if ("datetime" in args[1]) { - assert.deepEqual(args[1], { datetime: "2017-02-28 22:59:59" }); - } - } - }, - }); - - assert.containsN(target, ".o_kanban_group", 2); - assert.containsN( - target, - ".o_kanban_group:first-child .o_kanban_record", - 2, - "1st column should contain 2 records of January month" - ); - assert.containsN( - target, - ".o_kanban_group:nth-child(2) .o_kanban_record", - 2, - "2nd column should contain 2 records of February month" - ); - - await dragAndDrop( - ".o_kanban_group:first-child .o_kanban_record", - ".o_kanban_group:nth-child(2)" - ); - - assert.containsOnce( - target, - ".o_kanban_group:first-child .o_kanban_record", - "Should only have one record remaining" - ); - assert.containsN( - target, - ".o_kanban_group:nth-child(2) .o_kanban_record", - 3, - "Should now have 3 records" - ); - assert.verifySteps(["resequence"]); - - await reload(kanban, { groupBy: ["datetime:month"] }); - - assert.containsN(target, ".o_kanban_group", 2); - assert.containsN( - target, - ".o_kanban_group:first-child .o_kanban_record", - 2, - "1st column should contain 2 records of January month" - ); - assert.containsN( - target, - ".o_kanban_group:nth-child(2) .o_kanban_record", - 2, - "2nd column should contain 2 records of February month" - ); - - await dragAndDrop( - ".o_kanban_group:first-child .o_kanban_record", - ".o_kanban_group:nth-child(2)" - ); - - assert.containsOnce( - target, - ".o_kanban_group:first-child .o_kanban_record", - "Should only have one record remaining" - ); - assert.containsN( - target, - ".o_kanban_group:nth-child(2) .o_kanban_record", - 3, - "Should now have 3 records" - ); - assert.verifySteps(["resequence"]); - } - ); - - QUnit.test("Move record in grouped by date, progress bars and sum field", async (assert) => { - serverData.models.partner.records[0].date = "2017-01-08"; - serverData.models.partner.records[1].date = "2017-01-09"; - serverData.models.partner.records[2].date = "2017-02-08"; - serverData.models.partner.records[3].date = "2017-02-10"; - - await makeView({ - type: "kanban", - resModel: "partner", - serverData, - arch: /* xml */ ` - - - - -
- -
-
-
- `, - groupBy: ["date:month"], - }); - - assert.containsN(target, ".o_kanban_group", 2); - assert.containsN( - target, - ".o_kanban_group:first-child .o_kanban_record", - 2, - "1st column should contain 2 records of January month" - ); - assert.containsN( - target, - ".o_kanban_group:nth-child(2) .o_kanban_record", - 2, - "2nd column should contain 2 records of February month" - ); - - assert.deepEqual( - getProgressBars(0).map((pb) => pb.style.width), - ["50%", "50%"] - ); - assert.deepEqual( - getProgressBars(1).map((pb) => pb.style.width), - ["50%", "50%"] - ); - assert.deepEqual(getCounters(), ["19", "13"]); - - await dragAndDrop( - ".o_kanban_group:first-child .o_kanban_record", - ".o_kanban_group:nth-child(2)" - ); - - assert.containsOnce( - target, - ".o_kanban_group:first-child .o_kanban_record", - "Should only have one record remaining" - ); - assert.containsN( - target, - ".o_kanban_group:nth-child(2) .o_kanban_record", - 3, - "Should now have 3 records" - ); - - assert.deepEqual( - getProgressBars(0).map((pb) => pb.style.width), - ["100%"] - ); - assert.deepEqual( - getProgressBars(1).map((pb) => pb.style.width), - ["33.3333%", "33.3333%", "33.3333%"] // abridged to e-4 - ); - assert.deepEqual(getCounters(), ["9", "23"]); - }); - QUnit.test( "completely prevent drag and drop if records_draggable set to false", async (assert) => { @@ -7302,7 +7040,7 @@ QUnit.module("Views", (hooks) => { await makeView({ arch: ` - + diff --git a/odoo/addons/base/rng/common.rng b/odoo/addons/base/rng/common.rng index af9db2ae9f0..25fb98a9277 100644 --- a/odoo/addons/base/rng/common.rng +++ b/odoo/addons/base/rng/common.rng @@ -209,7 +209,6 @@ -