[IMP] web,project: kanban: quick create when grouped by many2many

This commit adds the ability to quick create records from the
kanban view when it is grouped by a many2many field.

This feature improve the user experience for multiple views, here are
some examples of new possible workflows:

- Project -> My tasks (this view is grouped by m2m) -> quick create
- Project -> Select a project -> group by Assignees -> quick create
- Multiples module -> group by tags -> quick create

The "My Tasks" view is grouped by the m2m `personal_stage_type_ids`
field which is a complicated computed field on which we cannot write. A
custom behaviour in their `ProjectTaskRecord` was necessary in order to
support this feature. Previously, they also implemented a custom
behaviour for this field to be able to move records between groups.

closes odoo/odoo#108150

Task-id: 2960497
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
fdardenne
2022-12-23 15:08:15 +01:00
parent a3d77e877d
commit 5358930855
3 changed files with 272 additions and 2 deletions
@@ -11,4 +11,14 @@ export class ProjectTaskRecord extends Record {
}
await super._applyChanges(changes);
}
get context() {
const context = super.context;
const value = context.default_personal_stage_type_ids;
if (value && Array.isArray(value)) {
context.default_personal_stage_type_id = value[0];
delete context.default_personal_stage_type_ids;
}
return context;
}
}
@@ -29,7 +29,7 @@ import { markRaw, markup, toRaw } from "@odoo/owl";
const preloadedDataRegistry = registry.category("preloadedData");
const { CREATE, UPDATE, DELETE, FORGET, LINK_TO, DELETE_ALL, REPLACE_WITH } = x2ManyCommands;
const QUICK_CREATE_FIELD_TYPES = ["char", "boolean", "many2one", "selection"];
const QUICK_CREATE_FIELD_TYPES = ["char", "boolean", "many2one", "selection", "many2many"];
const AGGREGATABLE_FIELD_TYPES = ["float", "integer", "monetary"]; // types that can be aggregated in grouped views
const DEFAULT_HANDLE_FIELD = "sequence";
const DEFAULT_QUICK_CREATE_FIELDS = {
@@ -1245,7 +1245,11 @@ export class Record extends DataPoint {
return;
}
const { domain, value: changes, warning } = await this.model.orm.call(
const {
domain,
value: changes,
warning,
} = await this.model.orm.call(
this.resModel,
"onchange",
[
@@ -2658,12 +2662,16 @@ export class Group extends DataPoint {
getServerValue() {
const { name, selection, type, granularity } = this.groupByField;
switch (type) {
case "many2one":
case "char":
case "boolean": {
return this.value || false;
}
case "many2many": {
return this.value ? [this.value] : false;
}
case "selection": {
const descriptor = selection.find((opt) => opt[0] === this.value);
return descriptor && descriptor[0];
@@ -1866,6 +1866,258 @@ QUnit.module("Views", (hooks) => {
]);
});
QUnit.test("quick create record in grouped on m2m (no quick_create_view)", async (assert) => {
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: `
<kanban on_create="quick_create">
<field name="category_ids"/>
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
groupBy: ["category_ids"],
async mockRPC(route, { method, args, kwargs }) {
assert.step(method || route);
if (method === "name_create") {
assert.strictEqual(args[0], "new partner");
const { default_category_ids } = kwargs.context;
assert.deepEqual(default_category_ids, [6]);
}
},
});
assert.containsOnce(
target,
".o_kanban_group:nth-child(2) .o_kanban_record",
"gold column should contain one records"
);
// click on 'Create', fill the quick create and validate
await quickCreateRecord(1);
await editQuickCreateInput("display_name", "new partner");
await validateRecord();
assert.containsN(
target,
".o_kanban_group:nth-child(2) .o_kanban_record",
2,
"gold column should contain two records"
);
assert.verifySteps([
"get_views",
"web_read_group", // initial read_group
"web_search_read", // initial search_read (first column)
"web_search_read", // initial search_read (second column)
"read", // read display_name of categories
"onchange", // quick create
"name_create", // should perform a name_create to create the record
"read",
"read", // read the created record
"onchange", // reopen the quick create automatically
]);
});
QUnit.test("quick create record in grouped on m2m in the None column", async (assert) => {
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: `
<kanban on_create="quick_create">
<field name="category_ids"/>
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
groupBy: ["category_ids"],
async mockRPC(route, { method, args, kwargs }) {
assert.step(method || route);
if (method === "name_create") {
assert.strictEqual(args[0], "new partner");
const { default_category_ids } = kwargs.context;
assert.deepEqual(default_category_ids, false);
}
},
});
await click(target, ".o_kanban_group:nth-child(1)");
assert.containsN(
target,
".o_kanban_group:nth-child(1) .o_kanban_record",
2,
"'None' column should contain two records"
);
// click on 'Create', fill the quick create and validate
await quickCreateRecord(0);
await editQuickCreateInput("display_name", "new partner");
await validateRecord();
assert.containsN(
target,
".o_kanban_group:nth-child(1) .o_kanban_record",
3,
"'None' column should contain three records"
);
assert.verifySteps([
"get_views",
"web_read_group", // initial read_group
"web_search_read", // initial search_read (first column)
"web_search_read", // initial search_read (second column)
"read", // read display_name of categories
"web_search_read", // read records when unfolding 'None'
"onchange", // quick create
"name_create", // should perform a name_create to create the record
"read", // read the created record
"onchange", // reopen the quick create automatically
]);
});
QUnit.test("quick create record in grouped on m2m (field not in template)", async (assert) => {
serverData.views["partner,some_view_ref,form"] = `
<form>
<field name="foo"/>
</form>`;
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: `
<kanban on_create="quick_create" quick_create_view="some_view_ref">
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
groupBy: ["category_ids"],
async mockRPC(route, { method, args, kwargs }) {
assert.step(method || route);
if (method === "create") {
assert.deepEqual(args[0], {
foo: "new partner",
});
const { default_category_ids } = kwargs.context;
assert.deepEqual(default_category_ids, [6]);
}
},
});
assert.containsOnce(
target,
".o_kanban_group:nth-child(2) .o_kanban_record",
"gold column should contain one records"
);
// click on 'Create', fill the quick create and validate
await quickCreateRecord(1);
await editQuickCreateInput("foo", "new partner");
await validateRecord();
assert.containsN(
target,
".o_kanban_group:nth-child(2) .o_kanban_record",
2,
"gold column should contain two records"
);
assert.verifySteps([
"get_views",
"web_read_group", // initial read_group
"web_search_read", // initial search_read (first column)
"web_search_read", // initial search_read (second column)
"get_views", // get form view
"onchange", // quick create
"create", // should perform a name_create to create the record
"read", // read the created record
"onchange", // reopen the quick create automatically
]);
});
QUnit.test("quick create record in grouped on m2m (field in the form view)", async (assert) => {
serverData.views["partner,some_view_ref,form"] = `
<form>
<field name="foo"/>
<field name="category_ids" widget="many2many_tags"/>
</form>`;
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: `
<kanban on_create="quick_create" quick_create_view="some_view_ref">
<templates>
<t t-name="kanban-box">
<div><field name="foo"/></div>
</t>
</templates>
</kanban>`,
groupBy: ["category_ids"],
async mockRPC(route, { method, args, kwargs }) {
assert.step(method || route);
if (method === "create") {
assert.deepEqual(args[0], {
category_ids: [[6, false, [6]]],
foo: "new partner",
});
const { default_category_ids } = kwargs.context;
assert.deepEqual(default_category_ids, [6]);
}
},
});
assert.containsOnce(
target,
".o_kanban_group:nth-child(2) .o_kanban_record",
"gold column should contain one records"
);
// click on 'Create', fill the quick create and validate
await quickCreateRecord(1);
// verify that the quick create m2m field contains the column value
assert.containsOnce(
target,
".o_tag_badge_text",
"quick create form should contain one tag in the m2m field"
);
assert.strictEqual(target.querySelector(".o_tag_badge_text").textContent, "gold");
await editQuickCreateInput("foo", "new partner");
await validateRecord();
assert.containsN(
target,
".o_kanban_group:nth-child(2) .o_kanban_record",
2,
"gold column should contain two records"
);
assert.verifySteps([
"get_views",
"web_read_group", // initial read_group
"web_search_read", // initial search_read (first column)
"web_search_read", // initial search_read (second column)
"get_views", // get form view
"onchange", // quick create
"read",
"create", // should perform a create to create the record
"read",
"onchange",
"read",
]);
});
QUnit.test("quick create record validation: stays open when invalid", async (assert) => {
await makeView({
type: "kanban",