[IMP] sale: progressbar js logic migration to owl framework
This PR is part of the whole migration of the sale javascript code and related code to the owl framework. Why: better developmet framework for javascript code in odoo. This PR in particular targets the progressbar of sales team appearing on the CRM app. Task - 2918635 closes odoo/odoo#95565 Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
This commit is contained in:
@@ -60,7 +60,7 @@ This module contains all the common features of Sales Management and eCommerce.
|
||||
'web.assets_backend': [
|
||||
'sale/static/src/scss/sale_onboarding.scss',
|
||||
'sale/static/src/scss/product_configurator.scss',
|
||||
'sale/static/src/js/sale.js',
|
||||
'sale/static/src/js/sale_progressbar_field.js',
|
||||
'sale/static/src/js/tours/sale.js',
|
||||
'sale/static/src/js/sale_order_line_mixin.js',
|
||||
'sale/static/src/js/product_configurator_widget.js',
|
||||
@@ -83,6 +83,9 @@ This module contains all the common features of Sales Management and eCommerce.
|
||||
'web.report_assets_common': [
|
||||
'sale/static/src/scss/sale_report.scss',
|
||||
],
|
||||
'web.assets_qweb': [
|
||||
'sale/static/src/xml/**/*',
|
||||
],
|
||||
},
|
||||
'post_init_hook': '_synchronize_cron',
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
odoo.define('sale.sales_team_dashboard', function (require) {
|
||||
"use strict";
|
||||
|
||||
const { FieldProgressBar } = require('web.basic_fields');
|
||||
const fieldRegistry = require('web.field_registry');
|
||||
|
||||
var core = require('web.core');
|
||||
var _t = core._t;
|
||||
|
||||
const SalesTeamProgressBar = FieldProgressBar.extend({
|
||||
_render() {
|
||||
this._super.apply(this, arguments);
|
||||
const isUnset = !this.recordData[this.nodeOptions.max_value];
|
||||
if (isUnset) {
|
||||
const msg = document.createElement('a');
|
||||
msg.innerText = _t("Click to define an invoicing target");
|
||||
msg.setAttribute('href', '#')
|
||||
msg.addEventListener("click", (ev) => {
|
||||
ev.preventDefault();
|
||||
msg.parentElement.removeChild(msg);
|
||||
for (let child of this.el.children) {
|
||||
child.classList.remove('d-none')
|
||||
}
|
||||
});
|
||||
for (let child of this.el.children) {
|
||||
child.classList.add('d-none');
|
||||
}
|
||||
this.el.insertBefore(msg, this.el.firstChild)
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
fieldRegistry.add("sales_team_progressbar", SalesTeamProgressBar);
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { KanbanProgressBarField } from "@web/views/fields/progress_bar/kanban_progress_bar_field";
|
||||
|
||||
const { useEffect } = owl;
|
||||
|
||||
/**
|
||||
* A custom Component for the view of sales teams on the kanban view in the CRM app.
|
||||
*
|
||||
* The wanted behavior is to show a progress bar when an invoicing target is defined or show
|
||||
* a link redirecting to the record's form view otherwise.
|
||||
*/
|
||||
export class SaleProgressBarField extends KanbanProgressBarField {
|
||||
/**
|
||||
* Anything used by the component is defined on the setup method.
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.actionService = useService("action");
|
||||
this.orm = useService("orm");
|
||||
|
||||
useEffect(() => {
|
||||
this.state.isInvoicingTargetDefined = this.props.record.data[this.props.maxValueField];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the form view of the record on click.
|
||||
*/
|
||||
async defineInvoicingTarget() {
|
||||
const { resId, resModel } = this.props.record;
|
||||
const action = await this.orm.call(resModel, "get_formview_action", [[resId]]);
|
||||
this.actionService.doAction(action, { props: { mode: "edit" } });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the template name used on the component.
|
||||
*/
|
||||
SaleProgressBarField.template = "sale.SaleProgressBarField";
|
||||
|
||||
registry.category("fields").add("sales_team_progressbar", SaleProgressBarField);
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="sale.SaleProgressBarField" owl="1">
|
||||
<t t-if="state.isInvoicingTargetDefined">
|
||||
<t t-call="web.ProgressBarField"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<a t-on-click.prevent="defineInvoicingTarget" href="#" class="sale_progressbar_form_link">
|
||||
Click to define an invoicing target
|
||||
</a>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,117 +1,90 @@
|
||||
odoo.define('sale.dashboard_tests', function (require) {
|
||||
"use strict";
|
||||
/** @odoo-module **/
|
||||
|
||||
var KanbanView = require('web.KanbanView');
|
||||
var testUtils = require('web.test_utils');
|
||||
import { click, getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
var createView = testUtils.createView;
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
QUnit.module('Sales Team Dashboard', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
'crm.team': {
|
||||
fields: {
|
||||
foo: {string: "Foo", type: 'char'},
|
||||
invoiced: {string: "Invoiced", type: 'integer'},
|
||||
invoiced_target: {string: "Invoiced_target", type: 'integer'},
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
QUnit.module("Sales Team Dashboard", {
|
||||
beforeEach() {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
"crm.team": {
|
||||
fields: {
|
||||
foo: { string: "Foo", type: "char" },
|
||||
invoiced: { string: "Invoiced", type: "integer" },
|
||||
invoiced_target: { string: "Invoiced_target", type: "integer" },
|
||||
},
|
||||
records: [{ id: 1, foo: "yop", invoiced: 0, invoiced_target: 0 }],
|
||||
},
|
||||
records: [
|
||||
{id: 1, foo: "yop"},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
setupViewRegistries();
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('edit target with several o_kanban_primary_bottom divs [REQUIRE FOCUS]', async function (assert) {
|
||||
assert.expect(7);
|
||||
QUnit.test("edit target with several o_kanban_primary_bottom divs", async (assert) => {
|
||||
assert.expect(4);
|
||||
|
||||
var kanban = await createView({
|
||||
View: KanbanView,
|
||||
model: 'crm.team',
|
||||
data: this.data,
|
||||
arch: '<kanban>' +
|
||||
'<field name="invoiced_target"/>' +
|
||||
'<templates>' +
|
||||
'<t t-name="kanban-box">' +
|
||||
'<div class="container o_kanban_card_content">' +
|
||||
`<field name="invoiced" widget="sales_team_progressbar" options="{'current_value': 'invoiced', 'max_value': 'invoiced_target', 'editable': true, 'edit_max_value': true}"/> ` +
|
||||
'<div class="col-12 o_kanban_primary_bottom"/>' +
|
||||
'<div class="col-12 o_kanban_primary_bottom bottom_block"/>' +
|
||||
'</div>' +
|
||||
'</t>' +
|
||||
'</templates>' +
|
||||
'</kanban>',
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'write') {
|
||||
assert.strictEqual(args.args[1].invoiced_target, 123,
|
||||
"new value is correctly saved");
|
||||
const fakeActionService = {
|
||||
start: () => ({
|
||||
async doAction(action) {
|
||||
assert.deepEqual(
|
||||
action,
|
||||
{
|
||||
res_model: "crm.team",
|
||||
target: "current",
|
||||
type: "ir.actions.act_window",
|
||||
method: "get_formview_action",
|
||||
},
|
||||
"should trigger do_action with the correct args"
|
||||
);
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
};
|
||||
serviceRegistry.add("action", fakeActionService, { force: true });
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "kanban",
|
||||
resModel: "crm.team",
|
||||
arch: /* xml */`
|
||||
<kanban>
|
||||
<field name="invoiced_target"/>
|
||||
<templates>
|
||||
<div t-name="kanban-box" class="container o_kanban_card_content">
|
||||
<field name="invoiced" widget="sales_team_progressbar" options="{'current_value': 'invoiced', 'max_value': 'invoiced_target', 'editable': true, 'edit_max_value': true}"/>
|
||||
<div class="col-12 o_kanban_primary_bottom"/>
|
||||
<div class="col-12 o_kanban_primary_bottom bottom_block"/>
|
||||
</div>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
resId: 1,
|
||||
async mockRPC(route, { method, model }) {
|
||||
if (route === "/web/dataset/call_kw/crm.team/get_formview_action") {
|
||||
return {
|
||||
method,
|
||||
res_model: model,
|
||||
target: "current",
|
||||
type: "ir.actions.act_window",
|
||||
};
|
||||
}
|
||||
if (args.method === 'read') { // Read happens after the write
|
||||
assert.deepEqual(args.args[1], ['invoiced_target', 'invoiced', 'display_name'],
|
||||
'the read (after write) should ask for invoiced_target');
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsOnce(kanban, '.o_legacy_kanban_view .o_progressbar:contains(Click to define an invoicing target)')
|
||||
assert.containsN(kanban, '.o_kanban_primary_bottom', 2,
|
||||
"should have two divs with classname 'o_kanban_primary_bottom'");
|
||||
|
||||
assert.containsNone(kanban, '.o_progressbar input')
|
||||
await testUtils.dom.click(kanban.$('.o_progressbar a'));
|
||||
assert.containsOnce(kanban, '.o_progressbar input')
|
||||
|
||||
kanban.$('.o_progressbar input').focus();
|
||||
kanban.$('.o_progressbar input').val('123');
|
||||
kanban.$('.o_progressbar input').trigger('blur');
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(kanban.$('.o_kanban_record').text().trim(), "0 / 123");
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('edit target supports push Enter', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var kanban = await createView({
|
||||
View: KanbanView,
|
||||
model: 'crm.team',
|
||||
data: this.data,
|
||||
arch: '<kanban>' +
|
||||
'<field name="invoiced_target"/>' +
|
||||
'<templates>' +
|
||||
'<t t-name="kanban-box">' +
|
||||
'<div class="container o_kanban_card_content">' +
|
||||
`<field name="invoiced" widget="sales_team_progressbar" options="{'current_value': 'invoiced', 'max_value': 'invoiced_target', 'editable': true, 'edit_max_value': true}"/> ` +
|
||||
'<div class="col-12 o_kanban_primary_bottom"/>' +
|
||||
'<div class="col-12 o_kanban_primary_bottom bottom_block"/>' +
|
||||
'</div>' +
|
||||
'</t>' +
|
||||
'</templates>' +
|
||||
'</kanban>',
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'write') {
|
||||
assert.strictEqual(args.args[1].invoiced_target, 123,
|
||||
"new value is correctly saved");
|
||||
}
|
||||
if (args.method === 'read') { // Read happens after the write
|
||||
assert.deepEqual(args.args[1], ['invoiced_target', 'invoiced', 'display_name'],
|
||||
'the read (after write) should ask for invoiced_target');
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.dom.click(kanban.$('.o_progressbar a'));
|
||||
kanban.$('.o_progressbar input').focus();
|
||||
kanban.$('.o_progressbar input').val('123');
|
||||
kanban.$('.o_progressbar input').trigger($.Event('keyup', {which: $.ui.keyCode.ENTER, keyCode: $.ui.keyCode.ENTER}));
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(kanban.$('.o_kanban_record').text().trim(), "0 / 123");
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_sales_team_progressbar:contains(Click to define an invoicing target)"
|
||||
);
|
||||
assert.containsN(target, ".o_kanban_primary_bottom", 2);
|
||||
assert.containsNone(target, ".o_progressbar input");
|
||||
|
||||
await click(target, ".sale_progressbar_form_link"); // should trigger a do_action
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { registry } from "@web/core/registry";
|
||||
import { ProgressBarField } from "./progress_bar_field";
|
||||
|
||||
class KanbanProgressBarField extends ProgressBarField {
|
||||
export class KanbanProgressBarField extends ProgressBarField {
|
||||
onClick() {
|
||||
if (this.props.isEditable && !this.props.record.isReadonly(this.props.name)) {
|
||||
this.state.isEditing = true;
|
||||
|
||||
Reference in New Issue
Block a user