[IMP] purchase: Purchase Dashboard to OWL

This commit converts the purchase dashboard to owl component
and removes its legacy code.

closes odoo/odoo#97590

Taskid: 2920812
Signed-off-by: Arnold Moyaux (arm) <arm@odoo.com>
This commit is contained in:
Ahmed Khalaf (ahkh)
2022-08-29 23:46:48 +02:00
parent e4988b70ab
commit 275fa2f0ce
11 changed files with 130 additions and 336 deletions
+3 -3
View File
@@ -39,7 +39,8 @@
'assets': {
'web.assets_backend': [
'purchase/static/src/toaster_button/*',
'purchase/static/src/js/purchase_dashboard.js',
'purchase/static/src/views/*.js',
'purchase/static/src/views/*.scss',
'purchase/static/src/js/purchase_toaster_button.js',
'purchase/static/src/js/tours/purchase.js',
],
@@ -48,8 +49,7 @@
'purchase/static/src/js/purchase_portal_sidebar.js',
],
'web.assets_qweb': [
'purchase/static/src/toaster_button/*.xml',
'purchase/static/src/xml/**/*',
'purchase/static/src/**/*.xml',
],
},
'license': 'LGPL-3',
@@ -1,280 +0,0 @@
odoo.define('purchase.dashboard', function (require) {
"use strict";
/**
* This file defines the Purchase Dashboard view (alongside its renderer, model
* and controller). This Dashboard is added to the top of list and kanban Purchase
* views, it extends both views with essentially the same code except for
* _onDashboardActionClicked function so we can apply filters without changing our
* current view.
*/
var core = require('web.core');
var ListController = require('web.ListController');
var ListModel = require('web.ListModel');
var ListRenderer = require('web.ListRenderer');
var ListView = require('web.ListView');
var KanbanController = require('web.KanbanController');
var KanbanModel = require('web.KanbanModel');
var KanbanRenderer = require('web.KanbanRenderer');
var KanbanView = require('web.KanbanView');
var SampleServer = require('web.SampleServer');
var view_registry = require('web.view_registry');
const session = require('web.session');
var QWeb = core.qweb;
// Add mock of method 'retrieve_dashboard' in SampleServer, so that we can have
// the sample data in empty purchase kanban and list view
let dashboardValues;
SampleServer.mockRegistry.add('purchase.order/retrieve_dashboard', () => {
return Object.assign({}, dashboardValues);
});
//--------------------------------------------------------------------------
// List View
//--------------------------------------------------------------------------
var PurchaseListDashboardRenderer = ListRenderer.extend({
events:_.extend({}, ListRenderer.prototype.events, {
'click .o_dashboard_action': '_onDashboardActionClicked',
}),
/**
* @override
* @private
* @returns {Promise}
*/
_renderView: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
var values = self.state.dashboardValues;
var purchase_dashboard = QWeb.render('purchase.PurchaseDashboard', {
values: values,
});
self.$el.prepend(purchase_dashboard);
});
},
/**
* @private
* @param {MouseEvent}
*/
_onDashboardActionClicked: function (e) {
e.preventDefault();
var $action = $(e.currentTarget);
this.trigger_up('dashboard_open_action', {
action_name: "purchase.purchase_action_dashboard_list",
action_context: $action.attr('context'),
});
},
});
var PurchaseListDashboardModel = ListModel.extend({
/**
* @override
*/
init: function () {
this.dashboardValues = {};
this._super.apply(this, arguments);
},
/**
* @override
*/
__get: function (localID) {
var result = this._super.apply(this, arguments);
if (_.isObject(result)) {
result.dashboardValues = this.dashboardValues[localID];
}
return result;
},
/**
* @override
* @returns {Promise}
*/
__load: function () {
return this._loadDashboard(this._super.apply(this, arguments));
},
/**
* @override
* @returns {Promise}
*/
__reload: function () {
return this._loadDashboard(this._super.apply(this, arguments));
},
/**
* @private
* @param {Promise} super_def a promise that resolves with a dataPoint id
* @returns {Promise -> string} resolves to the dataPoint id
*/
_loadDashboard: function (super_def) {
var self = this;
var dashboard_def = this._rpc({
model: 'purchase.order',
method: 'retrieve_dashboard',
context: session.user_context,
});
return Promise.all([super_def, dashboard_def]).then(function(results) {
var id = results[0];
dashboardValues = results[1];
self.dashboardValues[id] = dashboardValues;
return id;
});
},
});
var PurchaseListDashboardController = ListController.extend({
custom_events: _.extend({}, ListController.prototype.custom_events, {
dashboard_open_action: '_onDashboardOpenAction',
}),
/**
* @private
* @param {OdooEvent} e
*/
_onDashboardOpenAction: function (e) {
return this.do_action(e.data.action_name,
{additional_context: JSON.parse(e.data.action_context)});
},
});
var PurchaseListDashboardView = ListView.extend({
config: _.extend({}, ListView.prototype.config, {
Model: PurchaseListDashboardModel,
Renderer: PurchaseListDashboardRenderer,
Controller: PurchaseListDashboardController,
}),
});
//--------------------------------------------------------------------------
// Kanban View
//--------------------------------------------------------------------------
var PurchaseKanbanDashboardRenderer = KanbanRenderer.extend({
events:_.extend({}, KanbanRenderer.prototype.events, {
'click .o_dashboard_action': '_onDashboardActionClicked',
}),
/**
* @override
* @private
* @returns {Promise}
*/
_render: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
var values = self.state.dashboardValues;
var purchase_dashboard = QWeb.render('purchase.PurchaseDashboard', {
values: values,
});
self.$el.parent().find(".o_purchase_dashboard").remove();
self.$el.before(purchase_dashboard);
});
},
/**
* @private
* @param {MouseEvent}
*/
_onDashboardActionClicked: function (e) {
e.preventDefault();
var $action = $(e.currentTarget);
this.trigger_up('dashboard_open_action', {
action_name: "purchase.purchase_action_dashboard_kanban",
action_context: $action.attr('context'),
});
},
});
var PurchaseKanbanDashboardModel = KanbanModel.extend({
/**
* @override
*/
init: function () {
this.dashboardValues = {};
this._super.apply(this, arguments);
},
/**
* @override
*/
__get: function (localID) {
var result = this._super.apply(this, arguments);
if (_.isObject(result)) {
result.dashboardValues = this.dashboardValues[localID];
}
return result;
},
/**
* @override
* @returns {Promise}
*/
__load: function () {
return this._loadDashboard(this._super.apply(this, arguments));
},
/**
* @override
* @returns {Promise}
*/
__reload: function () {
return this._loadDashboard(this._super.apply(this, arguments));
},
/**
* @private
* @param {Promise} super_def a promise that resolves with a dataPoint id
* @returns {Promise -> string} resolves to the dataPoint id
*/
_loadDashboard: function (super_def) {
var self = this;
var dashboard_def = this._rpc({
model: 'purchase.order',
method: 'retrieve_dashboard',
context: session.user_context,
});
return Promise.all([super_def, dashboard_def]).then(function(results) {
var id = results[0];
dashboardValues = results[1];
self.dashboardValues[id] = dashboardValues;
return id;
});
},
});
var PurchaseKanbanDashboardController = KanbanController.extend({
custom_events: _.extend({}, KanbanController.prototype.custom_events, {
dashboard_open_action: '_onDashboardOpenAction',
}),
/**
* @private
* @param {OdooEvent} e
*/
_onDashboardOpenAction: function (e) {
return this.do_action(e.data.action_name,
{additional_context: JSON.parse(e.data.action_context)});
},
});
var PurchaseKanbanDashboardView = KanbanView.extend({
config: _.extend({}, KanbanView.prototype.config, {
Model: PurchaseKanbanDashboardModel,
Renderer: PurchaseKanbanDashboardRenderer,
Controller: PurchaseKanbanDashboardController,
}),
});
view_registry.add('purchase_list_dashboard', PurchaseListDashboardView);
view_registry.add('purchase_kanban_dashboard', PurchaseKanbanDashboardView);
return {
PurchaseListDashboardModel: PurchaseListDashboardModel,
PurchaseListDashboardRenderer: PurchaseListDashboardRenderer,
PurchaseListDashboardController: PurchaseListDashboardController,
PurchaseKanbanDashboardModel: PurchaseKanbanDashboardModel,
PurchaseKanbanDashboardRenderer: PurchaseKanbanDashboardRenderer,
PurchaseKanbanDashboardController: PurchaseKanbanDashboardController
};
});
@@ -0,0 +1,34 @@
/** @odoo-module */
import { useService } from "@web/core/utils/hooks";
const { Component, onWillStart } = owl;
export class PurchaseDashBoard extends Component {
setup() {
this.orm = useService("orm");
this.action = useService("action");
onWillStart(async () => {
this.purchaseData = await this.orm.call(
"purchase.order",
"retrieve_dashboard",
);
});
}
/**
* This method clears the current search query and activates
* the filters found in `filter_name` attibute from button pressed
*/
setSearchContext(ev) {
let filter_name = ev.currentTarget.getAttribute("filter_name");
let filters = filter_name.split(',');
let searchItems = this.env.searchModel.getSearchItems((item) => filters.includes(item.name));
this.env.searchModel.query = [];
for (const item of searchItems){
this.env.searchModel.toggleSearchItem(item.id);
}
}
}
PurchaseDashBoard.template = 'purchase.PurchaseDashboard'
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="purchase.PurchaseDashboard">
<t t-name="purchase.PurchaseDashboard" owl="1">
<div class="o_purchase_dashboard container-fluid py-4 border-bottom bg-white">
<div class="row justify-content-between gap-3 gap-lg-0">
<div class="col-12 col-lg-5 col-xl-5 col-xxl-4 flex-grow-1 flex-lg-grow-0 flex-shrink-0">
@@ -9,19 +9,19 @@
All RFQs
</div>
<div class="g-col-9 g-col-sm-10 grid gap-1">
<div class="o_dashboard_action g-col-4 p-0" title="All Draft RFQs" context='{"search_default_draft_rfqs": true}'>
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="All Draft RFQs" filter_name="draft_rfqs">
<a href="#" class="btn btn-primary w-100 h-100 border-0 rounded-0 text-capitalize fw-normal">
<h2 t-out="values['all_to_send']"/>To Send
<h2 t-out="purchaseData['all_to_send']"/>To Send
</a>
</div>
<div class="o_dashboard_action g-col-4 p-0" title="All Waiting RFQs" context='{"search_default_waiting_rfqs": true}'>
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="All Waiting RFQs" filter_name="waiting_rfqs">
<a href="#" class="btn btn-primary w-100 h-100 border-0 rounded-0 text-capitalize fw-normal">
<h2 t-out="values['all_waiting']"/>Waiting
<h2 t-out="purchaseData['all_waiting']"/>Waiting
</a>
</div>
<div class="o_dashboard_action g-col-4 p-0" title="All Late RFQs" context='{"search_default_late_rfqs": true}'>
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="All Late RFQs" filter_name="late_rfqs">
<a href="#" class="btn btn-primary w-100 h-100 border-0 rounded-0 text-capitalize fw-normal">
<h2 t-out="values['all_late']"/>Late
<h2 t-out="purchaseData['all_late']"/>Late
</a>
</div>
</div>
@@ -31,19 +31,19 @@
My RFQs
</div>
<div class="g-col-9 g-col-sm-10 grid gap-2">
<div class="o_dashboard_action g-col-4 p-0" title="My Draft RFQs" context='{"search_default_draft_rfqs": true, "search_default_my_purchases": true}'>
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="My Draft RFQs" filter_name="draft_rfqs,my_purchases">
<a href="#" class="btn btn-light d-flex align-items-center w-100 h-100 p-0 border-0 bg-100 fw-normal">
<div class="w-100 p-2" t-out="values['my_to_send']"/>
<div class="w-100 p-2" t-out="purchaseData['my_to_send']"/>
</a>
</div>
<div class="o_dashboard_action g-col-4 p-0" title="My Waiting RFQs" context='{"search_default_waiting_rfqs": true, "search_default_my_purchases": true}'>
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="My Waiting RFQs" filter_name="waiting_rfqs,my_purchases">
<a href="#" class="btn btn-light d-flex align-items-center w-100 h-100 p-0 border-0 bg-100 fw-normal">
<div class="w-100 p-2" t-out="values['my_waiting']"/>
<div class="w-100 p-2" t-out="purchaseData['my_waiting']"/>
</a>
</div>
<div class="o_dashboard_action g-col-4 p-0" title="My Late RFQs" context='{"search_default_late_rfqs": true, "search_default_my_purchases": true}'>
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="My Late RFQs" filter_name="late_rfqs,my_purchases">
<a href="#" class="btn btn-light d-flex align-items-center w-100 h-100 p-0 border-0 bg-100 fw-normal">
<div class="w-100 p-2" t-out="values['my_late']"/>
<div class="w-100 p-2" t-out="purchaseData['my_late']"/>
</a>
</div>
</div>
@@ -57,7 +57,7 @@
Avg Order Value
</div>
<div class="g-col-12 g-col-sm-8 g-col-lg-5 d-flex align-items-center justify-content-center py-2 bg-light">
<span><t t-out="values['all_avg_order_value']"/></span>
<span><t t-out="purchaseData['all_avg_order_value']"/></span>
</div>
</div>
<div class="g-col-6 g-col-md-6 grid gap-1 gap-md-4">
@@ -65,7 +65,7 @@
Purchased Last 7 Days
</div>
<div class="g-col-12 g-col-sm-8 g-col-lg-6 d-flex align-items-center justify-content-center py-2 bg-light">
<span><t t-out="values['all_total_last_7_days']"/></span>
<span><t t-out="purchaseData['all_total_last_7_days']"/></span>
</div>
</div>
</div>
@@ -75,7 +75,7 @@
Lead Time to Purchase
</div>
<div class="g-col-12 g-col-sm-8 g-col-lg-5 d-flex align-items-center justify-content-center py-2 bg-light">
<span><t t-out="values['all_avg_days_to_purchase']"/> &#160;Days</span>
<span><t t-out="purchaseData['all_avg_days_to_purchase']"/> &#160;Days</span>
</div>
</div>
<div class="g-col-6 g-col-md-6 grid gap-1 gap-md-4">
@@ -83,7 +83,7 @@
RFQs Sent Last 7 Days
</div>
<div class="g-col-12 g-col-sm-8 g-col-lg-6 d-flex align-items-center justify-content-center py-2 bg-light">
<span><t t-out="values['all_sent_rfqs']"/></span>
<span><t t-out="purchaseData['all_sent_rfqs']"/></span>
</div>
</div>
</div>
@@ -93,4 +93,3 @@
</div>
</t>
</templates>
@@ -0,0 +1,19 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { kanbanView } from '@web/views/kanban/kanban_view';
import { KanbanRenderer } from '@web/views/kanban/kanban_renderer';
import { PurchaseDashBoard } from '@purchase/views/purchase_dashboard';
export class PurchaseDashBoardKanbanRenderer extends KanbanRenderer {};
PurchaseDashBoardKanbanRenderer.template = 'purchase.PurchaseKanbanView';
PurchaseDashBoardKanbanRenderer.components= Object.assign({}, KanbanRenderer.components, {PurchaseDashBoard})
export const PurchaseDashBoardKanbanView = {
...kanbanView,
Renderer: PurchaseDashBoardKanbanRenderer,
};
registry.category("views").add("purchase_dashboard_kanban", PurchaseDashBoardKanbanView);
@@ -0,0 +1,7 @@
<templates>
<t t-name="purchase.PurchaseKanbanView" t-inherit="web.KanbanRenderer" t-inherit-mode="primary" owl="1">
<xpath expr="//div[hasclass('o_kanban_renderer')]" position="before">
<PurchaseDashBoard />
</xpath>
</t>
</templates>
@@ -0,0 +1,18 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { listView } from "@web/views/list/list_view";
import { ListRenderer } from "@web/views/list/list_renderer";
import { PurchaseDashBoard } from '@purchase/views/purchase_dashboard';
export class PurchaseDashBoardRenderer extends ListRenderer {};
PurchaseDashBoardRenderer.template = 'purchase.PurchaseListView';
PurchaseDashBoardRenderer.components= Object.assign({}, ListRenderer.components, {PurchaseDashBoard})
export const PurchaseDashBoardListView = {
...listView,
Renderer: PurchaseDashBoardRenderer,
};
registry.category("views").add("purchase_dashboard_list", PurchaseDashBoardListView);
@@ -0,0 +1,3 @@
.o_rfq_no_content {
position: static;
}
@@ -0,0 +1,21 @@
<templates>
<t t-name="purchase.RfqNoContent" t-inherit="web.ActionHelper" t-inherit-mode="primary" owl="1">
<xpath expr="//div[hasclass('o_view_nocontent')]" position="attributes">
<attribute name="class" add="o_rfq_no_content" separator=" "/>
</xpath>
</t>
<t t-name="purchase.PurchaseListView" t-inherit="web.ListRenderer" t-inherit-mode="primary" owl="1">
<xpath expr="//t[@t-call='web.ActionHelper']" position="replace">
<PurchaseDashBoard />
<t t-if="showNoContentHelper">
<t t-if="!showTable" t-call="purchase.RfqNoContent">
<t t-set="noContentHelp" t-value="props.noContentHelp"/>
</t>
<t t-else="" t-call="web.ActionHelper">
<t t-set="noContentHelp" t-value="props.noContentHelp"/>
</t>
</t>
</xpath>
</t>
</templates>
+7 -34
View File
@@ -479,7 +479,7 @@
<field name="name">purchase.order.kanban</field>
<field name="model">purchase.order</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile" js_class="purchase_kanban_dashboard" sample="1">
<kanban class="o_kanban_mobile" js_class="purchase_dashboard_kanban" sample="1">
<field name="name"/>
<field name="partner_id" readonly="1"/>
<field name="amount_total"/>
@@ -518,6 +518,7 @@
<field name="name">purchase.order.view.kanban.without.dashboard</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.view_purchase_order_kanban"/>
<field name="mode">primary</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<xpath expr="//kanban" position="attributes">
@@ -563,7 +564,7 @@
<field name="arch" type="xml">
<tree string="Purchase Order" decoration-info="state in ['draft', 'sent']"
decoration-muted="state == 'cancel'"
class="o_purchase_order" js_class="purchase_list_dashboard" sample="1">
class="o_purchase_order" js_class="purchase_dashboard_list" sample="1">
<header>
<button name="action_create_invoice" type="object" string="Create Bills"/>
</header>
@@ -666,7 +667,10 @@
<field name="type">ir.actions.act_window</field>
<field name="res_model">purchase.order</field>
<field name="view_mode">tree,kanban,form,pivot,graph,calendar,activity</field>
<field name="view_id" ref="purchase_order_view_tree"/>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('purchase.purchase_order_view_tree')}),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('purchase.purchase_order_view_kanban_without_dashboard')}),
]"/>
<field name="domain">[('state','in',('purchase', 'done'))]</field>
<field name="search_view_id" ref="purchase_order_view_search"/>
<field name="context">{}</field>
@@ -819,37 +823,6 @@
<field name="target">new</field>
</record>
<!-- Dashboard action buttons -->
<!-- Dashboard action buttons: End in List view -->
<record id="purchase_action_dashboard_list" model="ir.actions.act_window">
<field name="name">Requests for Quotation</field>
<field name="res_model">purchase.order</field>
<field name="view_mode">form,tree,kanban,pivot,graph,calendar,activity</field>
<field name="view_id" ref="purchase.purchase_order_kpis_tree"/>
<field name="search_view_id" ref="view_purchase_order_filter"/>
<field name="target">main</field>
<field name="help" type="html">
<p class="o_view_nocontent_empty_folder">
No RFQs to display
</p>
</field>
</record>
<!-- Dashboard action buttons: End in Kanban view-->
<record id="purchase_action_dashboard_kanban" model="ir.actions.act_window">
<field name="name">Requests for Quotation</field>
<field name="res_model">purchase.order</field>
<field name="view_mode">form,tree,kanban,pivot,graph,calendar,activity</field>
<field name="view_id" ref="purchase.view_purchase_order_kanban"/>
<field name="search_view_id" ref="view_purchase_order_filter"/>
<field name="target">main</field>
<field name="help" type="html">
<p class="o_view_nocontent_empty_folder">
No RFQs to display
</p>
</field>
</record>
<record id="action_rfq_form" model="ir.actions.act_window">
<field name="name">Requests for Quotation</field>
<field name="res_model">purchase.order</field>
@@ -715,7 +715,7 @@ tour.stepUtils.openBuggerMenu("li.breadcrumb-item.active:contains('OP/')"),
...tour.stepUtils.goToAppSteps('purchase.menu_purchase_root', _t('Go to Purchase')),
{
mobile: false,
trigger: '.o_data_row:has(.o_data_cell:contains("the_flow.vendor"))',
trigger: '.o_data_row:has(.o_data_cell:contains("the_flow.vendor")) .o_data_cell:first',
content: _t('Select the generated request for quotation'),
position: 'bottom',
}, {