summaryrefslogtreecommitdiff
path: root/addons/purchase/static
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/purchase/static
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/purchase/static')
-rw-r--r--addons/purchase/static/description/icon.pngbin0 -> 7623 bytes
-rw-r--r--addons/purchase/static/description/icon.svg1
-rw-r--r--addons/purchase/static/src/img/OTDPurchase.gifbin0 -> 20751 bytes
-rw-r--r--addons/purchase/static/src/js/purchase_dashboard.js277
-rw-r--r--addons/purchase/static/src/js/purchase_datetimepicker.js6
-rw-r--r--addons/purchase/static/src/js/purchase_toaster_button.js42
-rw-r--r--addons/purchase/static/src/js/tours/purchase.js137
-rw-r--r--addons/purchase/static/src/scss/purchase.scss54
-rw-r--r--addons/purchase/static/src/xml/purchase_dashboard.xml68
-rw-r--r--addons/purchase/static/src/xml/purchase_toaster_button.xml10
-rw-r--r--addons/purchase/static/xls/product_purchase.xlsbin0 -> 6656 bytes
11 files changed, 595 insertions, 0 deletions
diff --git a/addons/purchase/static/description/icon.png b/addons/purchase/static/description/icon.png
new file mode 100644
index 00000000..02b73216
--- /dev/null
+++ b/addons/purchase/static/description/icon.png
Binary files differ
diff --git a/addons/purchase/static/description/icon.svg b/addons/purchase/static/description/icon.svg
new file mode 100644
index 00000000..89c7ef89
--- /dev/null
+++ b/addons/purchase/static/description/icon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="100%"><stop offset="0%" stop-color="#94B6C8"/><stop offset="100%" stop-color="#6A9EBA"/></linearGradient><path id="d" d="M53.551 19.955H16.223c-2.07 0-3.742 1.64-3.742 3.663v26.864c0 2.022 1.673 3.663 3.742 3.663h37.328c2.07 0 3.742-1.64 3.742-3.663V23.618c0-2.023-1.672-3.663-3.742-3.663zm-36.86 3.663h36.393c.257 0 .467.206.467.458v3.205H16.223v-3.205c0-.252.21-.458.467-.458zm36.393 26.864H16.69a.464.464 0 0 1-.467-.458V37.05h37.328v12.974c0 .252-.21.458-.467.458zM27.42 42.85v3.053c0 .503-.42.916-.934.916h-5.601a.928.928 0 0 1-.934-.916V42.85c0-.504.42-.916.934-.916h5.601c.514 0 .934.412.934.916zm14.937 0v3.053c0 .503-.42.916-.934.916h-10.58a.928.928 0 0 1-.934-.916V42.85c0-.504.42-.916.934-.916h10.58c.514 0 .934.412.934.916z"/><path id="e" d="M53.551 17.955H16.223c-2.07 0-3.742 1.64-3.742 3.663v26.864c0 2.022 1.673 3.663 3.742 3.663h37.328c2.07 0 3.742-1.64 3.742-3.663V21.618c0-2.023-1.672-3.663-3.742-3.663zm-36.86 3.663h36.393c.257 0 .467.206.467.458v3.205H16.223v-3.205c0-.252.21-.458.467-.458zm36.393 26.864H16.69a.464.464 0 0 1-.467-.458V35.05h37.328v12.974c0 .252-.21.458-.467.458zM27.42 40.85v3.053c0 .503-.42.916-.934.916h-5.601a.928.928 0 0 1-.934-.916V40.85c0-.504.42-.916.934-.916h5.601c.514 0 .934.412.934.916zm14.937 0v3.053c0 .503-.42.916-.934.916h-10.58a.928.928 0 0 1-.934-.916V40.85c0-.504.42-.916.934-.916h10.58c.514 0 .934.412.934.916z"/></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M43.95 70H4c-2 0-4-.149-4-4.163V38.095l13.403-18.86L56 20.04l1.006 29.996L43.95 70z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><use fill="#000" fill-opacity=".3" fill-rule="nonzero" xlink:href="#d"/><use fill="#FFF" fill-rule="nonzero" xlink:href="#e"/></g></g></svg> \ No newline at end of file
diff --git a/addons/purchase/static/src/img/OTDPurchase.gif b/addons/purchase/static/src/img/OTDPurchase.gif
new file mode 100644
index 00000000..ad1879d9
--- /dev/null
+++ b/addons/purchase/static/src/img/OTDPurchase.gif
Binary files differ
diff --git a/addons/purchase/static/src/js/purchase_dashboard.js b/addons/purchase/static/src/js/purchase_dashboard.js
new file mode 100644
index 00000000..62dbde12
--- /dev/null
+++ b/addons/purchase/static/src/js/purchase_dashboard.js
@@ -0,0 +1,277 @@
+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');
+
+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: $action.attr('name')+"_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',
+ });
+ 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: $action.attr('name')+"_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',
+ });
+ 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
+};
+
+});
diff --git a/addons/purchase/static/src/js/purchase_datetimepicker.js b/addons/purchase/static/src/js/purchase_datetimepicker.js
new file mode 100644
index 00000000..de1be92f
--- /dev/null
+++ b/addons/purchase/static/src/js/purchase_datetimepicker.js
@@ -0,0 +1,6 @@
+$(function () {
+ $('input.o-purchase-datetimepicker').datetimepicker();
+ $('input.o-purchase-datetimepicker').on("hide.datetimepicker", function () {
+ $(this).parents('form').submit();
+ });
+})
diff --git a/addons/purchase/static/src/js/purchase_toaster_button.js b/addons/purchase/static/src/js/purchase_toaster_button.js
new file mode 100644
index 00000000..54285818
--- /dev/null
+++ b/addons/purchase/static/src/js/purchase_toaster_button.js
@@ -0,0 +1,42 @@
+odoo.define('purchase.ToasterButton', function (require) {
+ 'use strict';
+
+ const widgetRegistry = require('web.widget_registry');
+ const Widget = require('web.Widget');
+
+
+ const ToasterButton = Widget.extend({
+ template: 'purchase.ToasterButton',
+ xmlDependencies: ['/purchase/static/src/xml/purchase_toaster_button.xml'],
+ events: Object.assign({}, Widget.prototype.events, {
+ 'click .fa-info-circle': '_onClickButton',
+ }),
+
+ init: function (parent, data, node) {
+ this._super(...arguments);
+ this.button_name = node.attrs.button_name;
+ this.title = node.attrs.title;
+ this.id = data.res_id;
+ this.model = data.model;
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+ _onClickButton: function (ev) {
+ this._rpc({
+ method: this.button_name,
+ model: this.model,
+ args: [[this.id]],
+ }).then(res => {
+ if (res) {
+ this.do_notify(false, res.toast_message);
+ }
+ })
+ },
+ });
+
+ widgetRegistry.add('toaster_button', ToasterButton);
+
+ return ToasterButton;
+});
diff --git a/addons/purchase/static/src/js/tours/purchase.js b/addons/purchase/static/src/js/tours/purchase.js
new file mode 100644
index 00000000..3c63bcc3
--- /dev/null
+++ b/addons/purchase/static/src/js/tours/purchase.js
@@ -0,0 +1,137 @@
+odoo.define('purchase.purchase_steps', function (require) {
+"use strict";
+
+var core = require('web.core');
+
+var PurchaseAdditionalTourSteps = core.Class.extend({
+
+ _get_purchase_stock_steps: function () {
+ return [
+ {
+ auto: true, // Useless final step to trigger congratulation message
+ trigger: ".o_purchase_order",
+ },
+ ];
+ },
+
+});
+
+return PurchaseAdditionalTourSteps;
+
+});
+
+odoo.define('purchase.tour', function(require) {
+"use strict";
+
+var core = require('web.core');
+var tour = require('web_tour.tour');
+
+var _t = core._t;
+var PurchaseAdditionalTourSteps = require('purchase.purchase_steps');
+
+tour.register('purchase_tour' , {
+ url: "/web",
+ sequence: 40,
+}, [tour.stepUtils.showAppsMenuItem(), {
+ trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]',
+ content: _t("Let's try the Purchase app to manage the flow from purchase to reception and invoice control."),
+ position: 'right',
+ edition: 'community'
+}, {
+ trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]',
+ content: _t("Let's try the Purchase app to manage the flow from purchase to reception and invoice control."),
+ position: 'bottom',
+ edition: 'enterprise'
+}, {
+ trigger: ".o_list_button_add",
+ extra_trigger: ".o_purchase_order",
+ content: _t("Let's create your first request for quotation."),
+ position: "bottom",
+}, {
+ trigger: ".o_form_editable .o_field_many2one[name='partner_id']",
+ extra_trigger: ".o_purchase_order",
+ content: _t("Search a vendor name, or create one on the fly."),
+ position: "bottom",
+ run: function (actions) {
+ actions.text("Agrolait", this.$anchor.find("input"));
+ },
+}, {
+ trigger: ".ui-menu-item > a",
+ auto: true,
+ in_modal: false,
+}, {
+ trigger: ".o_field_x2many_list_row_add > a",
+ extra_trigger: ".o_field_many2one[name='partner_id'] .o_external_button",
+ content: _t("Add some products or services to your quotation."),
+ position: "bottom",
+}, {
+ trigger: ".o_field_widget[name=product_id], .o_field_widget[name=product_template_id]",
+ extra_trigger: ".o_purchase_order",
+ content: _t("Select a product, or create a new one on the fly."),
+ position: "right",
+ run: function (actions) {
+ var $input = this.$anchor.find('input');
+ actions.text("DESK0001", $input.length === 0 ? this.$anchor : $input);
+ // fake keydown to trigger search
+ var keyDownEvent = jQuery.Event("keydown");
+ keyDownEvent.which = 42;
+ this.$anchor.trigger(keyDownEvent);
+ var $descriptionElement = $('.o_form_editable textarea[name="name"]');
+ // when description changes, we know the product has been created
+ $descriptionElement.change(function () {
+ $descriptionElement.addClass('product_creation_success');
+ });
+ },
+}, {
+ trigger: '.ui-menu.ui-widget .ui-menu-item a:contains("DESK0001")',
+ auto: true,
+}, {
+ trigger: '.o_form_editable textarea[name="name"].product_creation_success',
+ auto: true,
+ run: function () {} // wait for product creation
+}, {
+ trigger: ".o_form_editable input[name='product_qty'] ",
+ extra_trigger: ".o_purchase_order",
+ content: _t("Indicate the product quantity you want to order."),
+ position: "right",
+ run: 'text 12.0'
+},
+...tour.stepUtils.statusbarButtonsSteps('Send by Email', _t("Send the request for quotation to your vendor."), ".o_statusbar_buttons button[name='action_rfq_send']"),
+{
+ trigger: ".modal-content",
+ auto: true,
+ run: function(actions){
+ // Check in case user must add email to vendor
+ var $input = $(".modal-content input[name='email']");
+ if ($input.length) {
+ actions.text("agrolait@example.com", $input);
+ actions.click($(".modal-footer button"));
+ }
+ }
+}, {
+ trigger: ".modal-footer button[name='action_send_mail']",
+ extra_trigger: ".modal-footer button[name='action_send_mail']",
+ content: _t("Send the request for quotation to your vendor."),
+ position: "left",
+ run: 'click',
+}, {
+ trigger: ".ui-sortable-handle",
+ extra_trigger: ".o_purchase_order",
+ content: _t("Click here to edit or move the quotation line."),
+ position: "bottom",
+ run: 'click',
+}, {
+ trigger: ".o_form_editable input[name='price_unit']",
+ extra_trigger: ".o_form_editable input[name='price_unit']",
+ content: _t("Once you get the price from the vendor, you can complete the purchase order with the right price."),
+ position: "right",
+ run: 'text 200.00'
+}, {
+ auto: true,
+ trigger: ".o_purchase_order",
+ run: 'click',
+}, ...tour.stepUtils.statusbarButtonsSteps('Confirm Order', _t("Confirm your purchase.")),
+...new PurchaseAdditionalTourSteps()._get_purchase_stock_steps(),
+]);
+
+});
diff --git a/addons/purchase/static/src/scss/purchase.scss b/addons/purchase/static/src/scss/purchase.scss
new file mode 100644
index 00000000..1c6644c4
--- /dev/null
+++ b/addons/purchase/static/src/scss/purchase.scss
@@ -0,0 +1,54 @@
+.o_purchase_dashboard {
+ flex: 1 0 100%;
+ padding: 10px 10px 18px 10px;
+
+ background-color: $o-view-background-color;
+ position: relative;
+ max-width:100%;
+
+ .row {
+ margin-right: 0;
+ margin-left: 0;
+ }
+
+ .o_dashboard_action {
+ cursor: pointer;
+ }
+
+ .table {
+ margin-bottom: 0;
+ table-layout: fixed;
+ border-spacing: 10px 0px;
+ border-collapse: separate;
+ font-size: 13px;
+
+ > thead, tbody {
+ & > tr > td {
+ text-align: center;
+ width: 25%;
+ height: 33px;
+ vertical-align: middle;
+ border-top: 1px solid $o-view-background-color;
+ background-color: $o-brand-lightsecondary;
+
+ &.o_text{
+ background-color: $o-view-background-color;
+ }
+
+ > a:hover {
+ text-decoration: none;
+ }
+
+ &.o_main {
+ background-color: $o-brand-primary;
+ &:hover {
+ background-color: darken($o-brand-primary, 10%);
+ }
+ > a {
+ color: white;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/addons/purchase/static/src/xml/purchase_dashboard.xml b/addons/purchase/static/src/xml/purchase_dashboard.xml
new file mode 100644
index 00000000..a12d3d1a
--- /dev/null
+++ b/addons/purchase/static/src/xml/purchase_dashboard.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates>
+ <!-- This template is for a table at the top of purchase views that shows some KPIs. -->
+ <t t-name="purchase.PurchaseDashboard">
+ <div class="o_purchase_dashboard container">
+ <div class="row">
+ <div class="col-sm-5">
+ <table class="table table-sm">
+ <!-- thead needed to avoid list view rendering error for some reason -->
+ <thead>
+ <tr>
+ <!-- can't use th tag due to list rendering error when no values in list... -->
+ <td class="o_text">
+ <div>All RFQs</div>
+ </td>
+ <td class="o_main o_dashboard_action" title="All Draft RFQs" name="purchase.purchase_action_dashboard" context='{"search_default_draft_rfqs": true}'>
+ <a href="#"><t t-esc="values['all_to_send']"/><br/>To Send</a>
+ </td>
+ <td class="o_main o_dashboard_action" title="All Waiting RFQs" name="purchase.purchase_action_dashboard" context='{"search_default_waiting_rfqs": true}'>
+ <a href="#"><t t-esc="values['all_waiting']"/><br/>Waiting</a>
+ </td>
+ <td class="o_main o_dashboard_action" title="All Late RFQs" name="purchase.purchase_action_dashboard" context='{"search_default_late_rfqs": true}'>
+ <a href="#"><t t-esc="values['all_late']"/><br/>Late</a>
+ </td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="o_text">
+ <div>My RFQs</div>
+ </td>
+ <td class="o_main o_dashboard_action" title="My Draft RFQs" name="purchase.purchase_action_dashboard" context='{"search_default_draft_rfqs": true, "search_default_my_purchases": true}'>
+ <a href="#"><t t-esc="values['my_to_send']"/></a>
+ </td>
+ <td class="o_main o_dashboard_action" title="My Waiting RFQs" name="purchase.purchase_action_dashboard" context='{"search_default_waiting_rfqs": true, "search_default_my_purchases": true}'>
+ <a href="#"><t t-esc="values['my_waiting']"/></a>
+ </td>
+ <td class="o_main o_dashboard_action" title="My Late RFQs" name="purchase.purchase_action_dashboard" context='{"search_default_late_rfqs": true, "search_default_my_purchases": true}'>
+ <a href="#"><t t-esc="values['my_late']"/></a>
+ </td>
+ </tr>
+ </tbody>
+ </table></div>
+
+ <div class="col-sm-7">
+ <table class="table table-sm">
+ <!-- thead needed to avoid list view rendering error for some reason -->
+ <thead>
+ <tr>
+ <!-- can't use th tag due to list rendering error when no values in list... -->
+ <td class="o_text">Avg Order Value (<t t-esc="values['company_currency_symbol']"/>)</td>
+ <td><span><t t-esc="values['all_avg_order_value']"/></span></td>
+ <td class="o_text">Purchased Last 7 Days (<t t-esc="values['company_currency_symbol']"/>)</td>
+ <td><span><t t-esc="values['all_total_last_7_days']"/></span></td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="o_text">Lead Time to Purchase</td>
+ <td><span><t t-esc="values['all_avg_days_to_purchase']"/> &#160;Days</span></td>
+ <td class="o_text">RFQs Sent Last 7 Days</td>
+ <td><span><t t-esc="values['all_sent_rfqs']"/></span></td>
+ </tr>
+ </tbody>
+ </table></div>
+ </div></div>
+ </t>
+</templates>
diff --git a/addons/purchase/static/src/xml/purchase_toaster_button.xml b/addons/purchase/static/src/xml/purchase_toaster_button.xml
new file mode 100644
index 00000000..276ee823
--- /dev/null
+++ b/addons/purchase/static/src/xml/purchase_toaster_button.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<template>
+
+ <t t-name="purchase.ToasterButton">
+ <button t-att-name="widget.button_name" type="button" class="btn oe_inline btn-link" t-att-title="widget.title">
+ <i class="fa fa-fw o_button_icon fa-info-circle"></i>
+ </button>
+ </t>
+
+</template>
diff --git a/addons/purchase/static/xls/product_purchase.xls b/addons/purchase/static/xls/product_purchase.xls
new file mode 100644
index 00000000..a953da71
--- /dev/null
+++ b/addons/purchase/static/xls/product_purchase.xls
Binary files differ