[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) <aab@odoo.com>
This commit is contained in:
FrancoisGe
2023-03-30 10:53:59 +02:00
parent 81ec49ef38
commit a2342f636c
10 changed files with 74 additions and 327 deletions
@@ -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");
}
}
@@ -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
@@ -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,
};
+1 -1
View File
@@ -619,7 +619,7 @@
<attribute name="js_class">forecast_kanban</attribute>
</xpath>
<xpath expr="//kanban" position="inside">
<field name="date_deadline" allow_group_range_value="true"/>
<field name="date_deadline"/>
</xpath>
<xpath expr="//field[@name='expected_revenue']" position="replace">
<field name="prorated_revenue"/>
@@ -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`;
@@ -16,6 +16,7 @@
openRecord.bind="openRecord"
noContentHelp="props.info.noContentHelp"
scrollTop.bind="scrollTop"
canQuickCreate="canQuickCreate"
/>
</Layout>
@@ -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;
}
// ------------------------------------------------------------------------
+16 -12
View File
@@ -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
) {
@@ -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"] =
"<form>" + '<field name="date"/>' + '<field name="datetime"/>' + "</form>";
const kanban = await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch:
'<kanban on_create="quick_create" quick_create_view="quick_form">' +
'<field name="date" allow_group_range_value="true"/>' +
'<field name="datetime" allow_group_range_value="true"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="display_name"/></div>' +
"</t></templates>" +
"</kanban>",
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:
"<kanban>" +
'<field name="date" allow_group_range_value="true"/>' +
'<field name="datetime" allow_group_range_value="true"/>' +
"<templates>" +
'<t t-name="kanban-box">' +
'<div><field name="display_name"/></div>' +
"</t>" +
"</templates>" +
"</kanban>",
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 */ `
<kanban>
<field name="date" allow_group_range_value="true" />
<progressbar field="foo" colors='{"yop": "success", "gnap": "warning", "blip": "danger"}' sum_field="int_field" />
<templates>
<div t-name="kanban-box">
<field name="id" />
</div>
</templates>
</kanban>
`,
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: `
<kanban sample="1">
<field name="date" allow_group_range_value="true"/>
<field name="date"/>
<field name="state"/>
<field name="int_field"/>
<progressbar field="state" sum_field="int_field" help="progress" colors="{}"/>
-1
View File
@@ -209,7 +209,6 @@
<rng:ref name="overload"/>
<rng:ref name="access_rights"/>
<rng:ref name="modifiable"/>
<rng:optional><rng:attribute name="allow_group_range_value"/></rng:optional>
<rng:optional><rng:attribute name="domain_filter"/></rng:optional>
<rng:optional><rng:attribute name="attrs"/></rng:optional>
<rng:optional><rng:attribute name="class"/></rng:optional>