diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/purchase/static/src | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/purchase/static/src')
| -rw-r--r-- | addons/purchase/static/src/img/OTDPurchase.gif | bin | 0 -> 20751 bytes | |||
| -rw-r--r-- | addons/purchase/static/src/js/purchase_dashboard.js | 277 | ||||
| -rw-r--r-- | addons/purchase/static/src/js/purchase_datetimepicker.js | 6 | ||||
| -rw-r--r-- | addons/purchase/static/src/js/purchase_toaster_button.js | 42 | ||||
| -rw-r--r-- | addons/purchase/static/src/js/tours/purchase.js | 137 | ||||
| -rw-r--r-- | addons/purchase/static/src/scss/purchase.scss | 54 | ||||
| -rw-r--r-- | addons/purchase/static/src/xml/purchase_dashboard.xml | 68 | ||||
| -rw-r--r-- | addons/purchase/static/src/xml/purchase_toaster_button.xml | 10 |
8 files changed, 594 insertions, 0 deletions
diff --git a/addons/purchase/static/src/img/OTDPurchase.gif b/addons/purchase/static/src/img/OTDPurchase.gif Binary files differnew file mode 100644 index 00000000..ad1879d9 --- /dev/null +++ b/addons/purchase/static/src/img/OTDPurchase.gif 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']"/>  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> |
