summaryrefslogtreecommitdiff
path: root/addons/sale_stock/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/sale_stock/static
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/sale_stock/static')
-rw-r--r--addons/sale_stock/static/src/js/qty_at_date_widget.js141
-rw-r--r--addons/sale_stock/static/src/xml/sale_stock.xml74
2 files changed, 215 insertions, 0 deletions
diff --git a/addons/sale_stock/static/src/js/qty_at_date_widget.js b/addons/sale_stock/static/src/js/qty_at_date_widget.js
new file mode 100644
index 00000000..384a963c
--- /dev/null
+++ b/addons/sale_stock/static/src/js/qty_at_date_widget.js
@@ -0,0 +1,141 @@
+odoo.define('sale_stock.QtyAtDateWidget', function (require) {
+"use strict";
+
+var core = require('web.core');
+var QWeb = core.qweb;
+
+var Widget = require('web.Widget');
+var widget_registry = require('web.widget_registry');
+var utils = require('web.utils');
+
+var _t = core._t;
+var time = require('web.time');
+
+var QtyAtDateWidget = Widget.extend({
+ template: 'sale_stock.qtyAtDate',
+ events: _.extend({}, Widget.prototype.events, {
+ 'click .fa-area-chart': '_onClickButton',
+ }),
+
+ /**
+ * @override
+ * @param {Widget|null} parent
+ * @param {Object} params
+ */
+ init: function (parent, params) {
+ this.data = params.data;
+ this.fields = params.fields;
+ this._updateData();
+ this._super(parent);
+ },
+
+ start: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ self._setPopOver();
+ });
+ },
+
+ _updateData: function() {
+ // add some data to simplify the template
+ if (this.data.scheduled_date) {
+ // The digit info need to get from free_qty_today in master (instead of virtual_available_at_date)
+ var qty_considered = this.data.state === 'sale' ? this.data.free_qty_today : this.data.virtual_available_at_date;
+ this.data.will_be_fulfilled = utils.round_decimals(qty_considered, this.fields.virtual_available_at_date.digits[1]) >= utils.round_decimals(this.data.qty_to_deliver, this.fields.qty_to_deliver.digits[1]);
+ this.data.will_be_late = this.data.forecast_expected_date && this.data.forecast_expected_date > this.data.scheduled_date;
+ if (['draft', 'sent'].includes(this.data.state)){
+ // Moves aren't created yet, then the forecasted is only based on virtual_available of quant
+ this.data.forecasted_issue = !this.data.will_be_fulfilled && !this.data.is_mto;
+ } else {
+ // Moves are created, using the forecasted data of related moves
+ this.data.forecasted_issue = !this.data.will_be_fulfilled || this.data.will_be_late;
+ }
+ }
+ },
+
+ updateState: function (state) {
+ this.$el.popover('dispose');
+ var candidate = state.data[this.getParent().currentRow];
+ if (candidate) {
+ this.data = candidate.data;
+ this._updateData();
+ this.renderElement();
+ this._setPopOver();
+ }
+ },
+ /**
+ * Redirect to the product graph view.
+ *
+ * @private
+ * @param {MouseEvent} event
+ * @returns {Promise} action loaded
+ */
+ async _openForecast(ev) {
+ ev.stopPropagation();
+ // TODO: in case of kit product, the forecast view should show the kit's components (get_component)
+ // The forecast_report doesn't not allow for now multiple products
+ var action = await this._rpc({
+ model: 'product.product',
+ method: 'action_product_forecast_report',
+ args: [[this.data.product_id.data.id]]
+ });
+ action.context = {
+ active_model: 'product.product',
+ active_id: this.data.product_id.data.id,
+ warehouse: this.data.warehouse_id && this.data.warehouse_id.res_id
+ };
+ return this.do_action(action);
+ },
+
+ _getContent() {
+ if (!this.data.scheduled_date) {
+ return;
+ }
+ this.data.delivery_date = this.data.scheduled_date.clone().add(this.getSession().getTZOffset(this.data.scheduled_date), 'minutes').format(time.getLangDateFormat());
+ if (this.data.forecast_expected_date) {
+ this.data.forecast_expected_date_str = this.data.forecast_expected_date.clone().add(this.getSession().getTZOffset(this.data.forecast_expected_date), 'minutes').format(time.getLangDateFormat());
+ }
+ const $content = $(QWeb.render('sale_stock.QtyDetailPopOver', {
+ data: this.data,
+ }));
+ $content.on('click', '.action_open_forecast', this._openForecast.bind(this));
+ return $content;
+ },
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+ /**
+ * Set a bootstrap popover on the current QtyAtDate widget that display available
+ * quantity.
+ */
+ _setPopOver() {
+ const $content = this._getContent();
+ if (!$content) {
+ return;
+ }
+ const options = {
+ content: $content,
+ html: true,
+ placement: 'left',
+ title: _t('Availability'),
+ trigger: 'focus',
+ delay: {'show': 0, 'hide': 100 },
+ };
+ this.$el.popover(options);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+ _onClickButton: function () {
+ // We add the property special click on the widget link.
+ // This hack allows us to trigger the popover (see _setPopOver) without
+ // triggering the _onRowClicked that opens the order line form view.
+ this.$el.find('.fa-area-chart').prop('special_click', true);
+ },
+});
+
+widget_registry.add('qty_at_date_widget', QtyAtDateWidget);
+
+return QtyAtDateWidget;
+});
diff --git a/addons/sale_stock/static/src/xml/sale_stock.xml b/addons/sale_stock/static/src/xml/sale_stock.xml
new file mode 100644
index 00000000..23b85b3b
--- /dev/null
+++ b/addons/sale_stock/static/src/xml/sale_stock.xml
@@ -0,0 +1,74 @@
+<templates>
+ <div t-name="sale_stock.qtyAtDate">
+ <div t-att-class="!widget.data.display_qty_widget ? 'invisible' : ''">
+ <a tabindex="0" t-attf-class="fa fa-area-chart {{ widget.data.forecasted_issue ? 'text-danger' : 'text-primary' }}"/>
+ </div>
+ </div>
+
+ <div t-name="sale_stock.QtyDetailPopOver">
+ <table class="table table-borderless table-sm">
+ <tbody>
+ <t t-if="!data.is_mto and ['draft', 'sent'].includes(data.state)">
+ <tr>
+ <td><strong>Forecasted Stock</strong><br /><small>On <span t-esc="data.delivery_date"/></small></td>
+ <td><b t-esc='data.virtual_available_at_date'/>
+ <t t-esc='data.product_uom.data.display_name'/></td>
+ </tr>
+ <tr>
+ <td><strong>Available</strong><br /><small>All planned operations included</small></td>
+ <td><b t-esc='data.free_qty_today' t-att-class="!data.will_be_fulfilled ? 'text-danger': ''"/>
+ <t t-esc='data.product_uom.data.display_name'/></td>
+ </tr>
+ </t>
+ <t t-elif="data.is_mto and ['draft', 'sent'].includes(data.state)">
+ <tr>
+ <td><strong>Expected Delivery</strong></td>
+ <td class="oe-right"><span t-esc="data.delivery_date"/></td>
+ </tr>
+ <tr>
+ <p>This product is replenished on demand.</p>
+ </tr>
+ </t>
+ <t t-elif="data.state == 'sale'">
+ <tr>
+ <td>
+ <strong>Reserved</strong><br/>
+ </td>
+ <td style="min-width: 50px; text-align: right;">
+ <b t-esc='data.qty_available_today'/> <t t-esc='data.product_uom.data.display_name'/>
+ </td>
+ </tr>
+ <tr t-if="data.qty_available_today &lt; data.qty_to_deliver">
+ <td>
+ <span t-if="data.will_be_fulfilled and data.forecast_expected_date_str">
+ Remaining demand available at <b t-esc="data.forecast_expected_date_str" t-att-class="data.scheduled_date &lt; data.forecast_expected_date ? 'text-danger' : ''"/>
+ </span>
+ <span t-elif="!data.will_be_fulfilled and data.forecast_expected_date_str" class="text-danger">
+ No enough future availaibility
+ </span>
+ <span t-elif="!data.will_be_fulfilled" class="text-danger">
+ No future availaibility
+ </span>
+ <span t-else="">
+ Available in stock
+ </span>
+ </td>
+ </tr>
+ </t>
+ </tbody>
+ </table>
+ <button t-if="!data.is_mto" class="text-left btn btn-link action_open_forecast"
+ type="button">
+ <i class="fa fa-fw o_button_icon fa-arrow-right"></i>
+ View Forecast
+ </button>
+ </div>
+
+ <div t-name="sale_stock.DelayAlertWidget">
+ <p>The delivery
+ <t t-foreach="late_elements" t-as="late_element">
+ <a t-esc="late_element.name" href="#" t-att-element-id="late_element.id" t-att-element-model="model"/>,
+ </t> will be late.
+ </p>
+ </div>
+</templates>