[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:
Horacio Tellez
2022-08-08 10:50:54 +02:00
parent 038bfa2ca1
commit bf3942b553
6 changed files with 141 additions and 138 deletions
+4 -1
View File
@@ -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',
-34
View File
@@ -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;