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 @@
-