[IMP] web,sale: allow selection widget in kanban view

Currently, users who want to change the visibility of product's
documents need to open their form view and edit it.

The selection widget now allows users to quickly edit the way a document
is displayed to their customers from the product's documents kanban view.

Part-of: odoo/odoo#137975
This commit is contained in:
Valentin Chevalier
2023-10-25 18:10:55 +00:00
parent 7dda6bb927
commit e3fe2b01f1
7 changed files with 191 additions and 23 deletions
@@ -7,7 +7,7 @@ import { useFileViewer } from "@web/core/file_viewer/file_viewer_hook";
export class ProductDocumentKanbanRecord extends KanbanRecord {
setup() {
super.setup();
this.attachmentService = useService("mail.attachment");
this.store = useService("mail.store");
this.fileViewer = useFileViewer();
}
/**
@@ -19,7 +19,7 @@ export class ProductDocumentKanbanRecord extends KanbanRecord {
if (ev.target.closest(CANCEL_GLOBAL_CLICK)) {
return;
} else if (ev.target.closest(".o_kanban_previewer")) {
const attachment = this.attachmentService.insert({
const attachment = this.store.Attachment.insert({
id: this.props.record.data.ir_attachment_id[0],
filename: this.props.record.data.name,
name: this.props.record.data.name,
@@ -0,0 +1,3 @@
.o_kanban_previewer:hover {
cursor: pointer;
}
@@ -50,7 +50,7 @@
<a t-attf-href="/web/content/#{record.ir_attachment_id.raw_value}?download=true" download="" class="dropdown-item">Download</a>
</t>
<t t-name="kanban-box">
<div class="oe_kanban_global_area o_kanban_attachment oe_kanban_global_click">
<div class="oe_kanban_global_area o_kanban_attachment">
<div class="ribbon ribbon-top-right" invisible="active">
<span class="text-bg-danger">Archived</span>
</div>
@@ -69,16 +69,12 @@
<div class="o_kanban_record_title">
<field name="name" class="o_text_overflow"/>
</div>
<div class="o_kanban_record_body" name="body">
<div class="o_kanban_record_body"
name="body"
t-if="record.type.raw_value == 'url'">
<field name="url" widget="url" invisible="type == 'binary'"/>
</div>
<div class="o_kanban_record_bottom">
<span class="oe_kanban_bottom_left" name="bottom_left">
</span>
<div class="oe_kanban_bottom_right">
<field name="create_uid" widget="many2one_avatar_user"/>
</div>
</div>
<div class="o_kanban_record_bottom flex-column" name="bottom"/>
</div>
</div>
</div>
+6 -6
View File
@@ -19,12 +19,12 @@
<field name="model">product.document</field>
<field name="inherit_id" ref="product.product_document_kanban"/>
<field name="arch" type="xml">
<span name="bottom_left" position="inside">
<t t-if="record.attached_on.raw_value">
<strong>Visible at</strong>
<field name="attached_on"/>
</t>
</span>
<div name="bottom" position="inside">
<div class="mt-2">
<span>Visible at</span>
<field name="attached_on" class="ms-2" widget="selection"/>
</div>
</div>
</field>
</record>
@@ -13,6 +13,10 @@ export class SelectionField extends Component {
placeholder: { type: String, optional: true },
required: { type: Boolean, optional: true },
domain: { type: Array, optional: true },
autosave: { type: Boolean, optional: true },
};
static defaultProps = {
autosave: false,
};
setup() {
@@ -68,15 +72,15 @@ export class SelectionField extends Component {
switch (this.type) {
case "many2one":
if (value === false) {
this.props.record.update({ [this.props.name]: false });
this.props.record.update({ [this.props.name]: false }, { save: this.props.autosave });
} else {
this.props.record.update({
[this.props.name]: this.options.find((option) => option[0] === value),
});
}, { save: this.props.autosave });
}
break;
case "selection":
this.props.record.update({ [this.props.name]: value });
this.props.record.update({ [this.props.name]: value }, { save: this.props.autosave });
break;
}
}
@@ -87,13 +91,19 @@ export const selectionField = {
displayName: _t("Selection"),
supportedTypes: ["many2one", "selection"],
isEmpty: (record, fieldName) => record.data[fieldName] === false,
extractProps({ attrs }, dynamicInfo) {
return {
extractProps({ attrs, viewType }, dynamicInfo) {
const props = {
autosave: viewType === "kanban",
placeholder: attrs.placeholder,
required: dynamicInfo.required,
domain: dynamicInfo.domain(),
};
if (viewType === "kanban") {
props.readonly = dynamicInfo.readonly;
};
return props;
},
};
registry.category("fields").add("selection", selectionField);
registry.category("fields").add("kanban.selection", selectionField);
@@ -6,7 +6,11 @@
<span t-esc="string" t-att-raw-value="value" />
</t>
<t t-else="">
<select class="o_input pe-3" t-on-change="onChange" t-att-id="props.id">
<select
class="o_input pe-3"
t-on-change="onChange"
t-on-click.stop="() =&gt; {}"
t-att-id="props.id">
<option
t-att-selected="false === value"
t-att-value="stringify(false)"
@@ -368,4 +368,159 @@ QUnit.module("Fields", (hooks) => {
assert.strictEqual(placeholderOption.textContent, "Placeholder");
assert.strictEqual(placeholderOption.value, "false");
});
QUnit.test("SelectionField in kanban view", async function (assert) {
assert.expect(3);
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: `
<kanban>
<templates>
<t t-name="kanban-box">
<div>
<field name="color" widget="selection" />
</div>
</t>
</templates>
</kanban>`,
domain: [["id", "=", 1]],
});
assert.containsOnce(
target,
".o_field_widget[name='color'] select",
"SelectionKanbanField widget applied to selection field"
);
assert.containsN(
target.querySelector(".o_field_widget[name='color']"),
"option",
3,
"Three options are displayed (one blank option)"
);
assert.deepEqual(
[...target.querySelectorAll(".o_field_widget[name='color'] option")].map(
(option) => option.value
),
["false", "\"red\"", "\"black\""]
);
});
QUnit.test("SelectionField - auto save record in kanban view", async function (assert) {
assert.expect(2);
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: `
<kanban>
<templates>
<t t-name="kanban-box">
<div>
<field name="color" widget="selection" />
</div>
</t>
</templates>
</kanban>`,
domain: [["id", "=", 1]],
mockRPC(_route, { method }) {
if (method === "web_save") {
assert.step("web_save");
}
},
});
await editSelect(target, ".o_field_widget[name='color'] select", '"black"');
assert.verifySteps(["web_save"]);
});
QUnit.test(
"SelectionField don't open form view on click in kanban view",
async function (assert) {
assert.expect(1);
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: `
<kanban>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<field name="color" widget="selection" />
</div>
</t>
</templates>
</kanban>`,
domain: [["id", "=", 1]],
selectRecord: () => {
assert.step("selectRecord");
},
});
await click(target, ".o_field_widget[name='color'] select");
assert.verifySteps([]);
});
QUnit.test("SelectionField is disabled if field readonly", async function (assert) {
assert.expect(1);
serverData.models.partner.fields.color.readonly = true;
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: `
<kanban>
<templates>
<t t-name="kanban-box">
<div>
<field name="color" widget="selection" />
</div>
</t>
</templates>
</kanban>
`,
domain: [["id", "=", 1]],
});
assert.containsOnce(
target,
".o_field_widget[name='color'] span",
"field should be readonly"
);
});
QUnit.test("SelectionField is disabled with a readonly attribute", async function (assert) {
assert.expect(1);
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: `
<kanban>
<templates>
<t t-name="kanban-box">
<div>
<field name="color" widget="selection" readonly="1" />
</div>
</t>
</templates>
</kanban>
`,
domain: [["id", "=", 1]],
});
assert.containsOnce(
target,
".o_field_widget[name='color'] span",
"field should be readonly"
);
});
});