[REF] stock: counted qty, forecast, json stock widgets to owl
This pr follow the framework refactoring and translate the widgets of stock module to wowl. CountedQuantityWidgetField needs to add an upper listener on input since the function onChange and onInput of `useInputField` are not overidable. Json fields had their template in the json send from the server. With wowl, the template needs to be declare before the setup. So a new wiget is create for each template and those widgets extends the a common component that put the json content in the props. co-author svs-odoo <svs@odoo.com> X-original-commit: 350a7a0d6f4816ee75676b80a9a5b9769bf80fe6 Part-of: odoo/odoo#101305
This commit is contained in:
@@ -519,13 +519,12 @@ class Picking(models.Model):
|
||||
picking_no_alert.json_popover = False
|
||||
for picking in (self - picking_no_alert):
|
||||
picking.json_popover = json.dumps({
|
||||
'popoverTemplate': 'stock.PopoverStockRescheduling',
|
||||
'delay_alert_date': format_datetime(self.env, picking.delay_alert_date, dt_format=False),
|
||||
'late_elements': [{
|
||||
'id': late_move.id,
|
||||
'name': late_move.display_name,
|
||||
'model': late_move._name,
|
||||
} for late_move in picking.move_ids.filtered(lambda m: m.delay_alert_date).move_orig_ids._delay_alert_get_documents()
|
||||
'id': late_move.id,
|
||||
'name': late_move.display_name,
|
||||
'model': late_move._name,
|
||||
} for late_move in picking.move_ids.filtered(lambda m: m.delay_alert_date).move_orig_ids._delay_alert_get_documents()
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/** @odoo-module alias=stock.counted_quantity_widget **/
|
||||
|
||||
import BasicFields from 'web.basic_fields';
|
||||
import fieldRegistry from 'web.field_registry';
|
||||
|
||||
const CountedQuantityWidgetField = BasicFields.FieldFloat.extend({
|
||||
supportedFieldTypes: ['float'],
|
||||
|
||||
_renderReadonly: function () {
|
||||
if (this.recordData.inventory_quantity_set) {
|
||||
this.el.textContent = this._formatValue(this.recordData.inventory_quantity);
|
||||
} else {
|
||||
this.el.textContent = "";
|
||||
}
|
||||
},
|
||||
|
||||
_onChange: function () {
|
||||
if (!this.recordData.inventory_quantity_set) {
|
||||
this.recordData.inventory_quantity_set = true;
|
||||
}
|
||||
this._super.apply(this);
|
||||
},
|
||||
|
||||
_isSameValue: function(value) {
|
||||
// We want to trigger the update of the view when inserting 0
|
||||
if (value == 0) {
|
||||
return false;
|
||||
}
|
||||
return this._super(...arguments);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
fieldRegistry.add('counted_quantity_widget', CountedQuantityWidgetField);
|
||||
|
||||
export default CountedQuantityWidgetField;
|
||||
@@ -1,90 +0,0 @@
|
||||
odoo.define('stock.forecast_widget', function (require) {
|
||||
'use strict';
|
||||
|
||||
const AbstractField = require('web.AbstractField');
|
||||
const fieldRegistry = require('web.field_registry');
|
||||
const field_utils = require('web.field_utils');
|
||||
const utils = require('web.utils');
|
||||
const core = require('web.core');
|
||||
const QWeb = core.qweb;
|
||||
|
||||
const ForecastWidgetField = AbstractField.extend({
|
||||
supportedFieldTypes: ['float'],
|
||||
|
||||
_render: function () {
|
||||
var data = Object.assign({}, this.record.data, {
|
||||
forecast_availability_str: field_utils.format.float(
|
||||
this.record.data.forecast_availability,
|
||||
this.record.fields.forecast_availability,
|
||||
this.nodeOptions
|
||||
),
|
||||
reserved_availability_str: field_utils.format.float(
|
||||
this.record.data.reserved_availability,
|
||||
this.record.fields.reserved_availability,
|
||||
this.nodeOptions
|
||||
),
|
||||
forecast_expected_date_str: field_utils.format.date(
|
||||
this.record.data.forecast_expected_date,
|
||||
this.record.fields.forecast_expected_date
|
||||
),
|
||||
});
|
||||
if (data.forecast_expected_date && data.date_deadline) {
|
||||
data.forecast_is_late = data.forecast_expected_date > data.date_deadline;
|
||||
}
|
||||
data.will_be_fulfilled = utils.round_decimals(data.forecast_availability, this.record.fields.forecast_availability.digits[1]) >= utils.round_decimals(data.product_qty, this.record.fields.forecast_availability.digits[1]);
|
||||
|
||||
this.$el.html(QWeb.render('stock.forecastWidget', data));
|
||||
this.$('.o_forecast_report_button').on('click', this._onOpenReport.bind(this));
|
||||
},
|
||||
|
||||
isSet: function () {
|
||||
return true;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Opens the Forecast Report for the `stock.move` product.
|
||||
*
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onOpenReport: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if (!this.recordData.id) {
|
||||
return;
|
||||
}
|
||||
this._rpc({
|
||||
model: 'stock.move',
|
||||
method: 'action_product_forecast_report',
|
||||
args: [this.recordData.id],
|
||||
}).then(action => {
|
||||
action.context = Object.assign(action.context || {}, {
|
||||
active_model: 'product.product',
|
||||
active_id: this.recordData.product_id.res_id,
|
||||
});
|
||||
this.do_action(action);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const JsonWidget = AbstractField.extend({
|
||||
supportedFieldTypes: ['char'],
|
||||
|
||||
_render: function () {
|
||||
var value = JSON.parse(this.value);
|
||||
if (!value || !value.template) {
|
||||
this.$el.html('');
|
||||
return;
|
||||
}
|
||||
$(QWeb.render(value.template, value)).appendTo(this.$el);
|
||||
},
|
||||
});
|
||||
|
||||
fieldRegistry.add('forecast_widget', ForecastWidgetField);
|
||||
fieldRegistry.add('json_widget', JsonWidget);
|
||||
|
||||
return ForecastWidgetField;
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { FloatField } from "@web/views/fields/float/float_field";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const { useEffect, useRef } = owl;
|
||||
|
||||
export class CountedQuantityWidgetField extends FloatField {
|
||||
setup() {
|
||||
// Need to adapt useInputField to overide onInput and onChange
|
||||
super.setup();
|
||||
|
||||
const inputRef = useRef("numpadDecimal");
|
||||
|
||||
useEffect(
|
||||
(inputEl) => {
|
||||
if (inputEl) {
|
||||
inputEl.addEventListener("input", this.onInput.bind(this));
|
||||
return () => {
|
||||
inputEl.removeEventListener("input", this.onInput.bind(this));
|
||||
};
|
||||
}
|
||||
},
|
||||
() => [inputRef.el]
|
||||
);
|
||||
}
|
||||
|
||||
onInput(ev) {
|
||||
this.props.setDirty(true);
|
||||
return this.props.record.update({ inventory_quantity_set: true });
|
||||
}
|
||||
|
||||
get formattedValue() {
|
||||
if (
|
||||
this.props.readonly &&
|
||||
!this.props.value & !this.props.record.data.inventory_quantity_set
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
return super.formattedValue;
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("fields").add("counted_quantity_widget", CountedQuantityWidgetField);
|
||||
@@ -0,0 +1,52 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { FloatField } from "@web/views/fields/float/float_field";
|
||||
import { formatDate } from "@web/core/l10n/dates";
|
||||
import { formatFloat } from "@web/views/fields/formatters";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class ForecastWidgetField extends FloatField {
|
||||
setup() {
|
||||
const { data, fields } = this.props.record;
|
||||
this.actionService = useService("action");
|
||||
this.orm = useService("orm");
|
||||
this.resId = data.id;
|
||||
|
||||
this.reservedAvailability = formatFloat(data.reserved_availability, {
|
||||
...fields.reserved_availability,
|
||||
...this.nodeOptions,
|
||||
});
|
||||
this.forecastExpectedDate = formatDate(
|
||||
data.forecast_expected_date,
|
||||
fields.forecast_expected_date
|
||||
);
|
||||
if (data.forecast_expected_date && data.date_deadline) {
|
||||
this.forecastIsLate = data.forecast_expected_date > data.date_deadline;
|
||||
}
|
||||
this.willBeFulfilled = data.forecast_availability >= data.product_qty;
|
||||
this.state = data.state;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Opens the Forecast Report for the `stock.move` product.
|
||||
*/
|
||||
async _openReport(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if (!this.resId) {
|
||||
return;
|
||||
}
|
||||
const action = await this.orm.call("stock.move", "action_product_forecast_report", [
|
||||
this.resId,
|
||||
]);
|
||||
this.actionService.doAction(action);
|
||||
}
|
||||
}
|
||||
ForecastWidgetField.template = "stock.ForecastWidget";
|
||||
|
||||
registry.category("fields").add("forecast_widget", ForecastWidgetField);
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="stock.ForecastWidget" owl="1">
|
||||
<span t-if="['draft', 'partially_available', 'assigned', 'cancel', 'done'].includes(state)"
|
||||
t-out="reservedAvailability"/>
|
||||
<span t-elif="!forecastExpectedDate and willBeFulfilled" class="text-success">
|
||||
Available
|
||||
</span>
|
||||
<span t-elif="forecastExpectedDate and willBeFulfilled"
|
||||
t-att-class="forecastIsLate ? 'text-danger' : 'text-warning'">
|
||||
Exp <t t-out="forecastExpectedDate"/>
|
||||
</span>
|
||||
<span t-else="" class="text-danger">Not Available</span>
|
||||
<button t-if="props.record.data.product_type == 'product'" title="Forecasted Report"
|
||||
t-on-click="_openReport" t-att="resId ? {} : {'disabled': ''}"
|
||||
class="o_forecast_report_button btn btn-link o_icon_button ms-2">
|
||||
<i class="fa fa-fw fa-area-chart"
|
||||
t-att-class="state != 'draft' and (!willBeFulfilled or forecastIsLate) ? 'text-danger' : ''"/>
|
||||
</button>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,53 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component, onWillStart } = owl;
|
||||
|
||||
export class JsonPpopOver extends Component {
|
||||
setup() {
|
||||
this.props = { ...this.props, ...JSON.parse(this.props.value) };
|
||||
}
|
||||
}
|
||||
|
||||
JsonPpopOver.displayName = _lt("Json Popup");
|
||||
JsonPpopOver.supportedTypes = ["char"];
|
||||
|
||||
export class PopOverLeadDays extends JsonPpopOver {
|
||||
setup() {
|
||||
super.setup();
|
||||
const user = useService("user");
|
||||
onWillStart(async () => {
|
||||
this.displayUOM = await user.hasGroup("uom.group_uom");
|
||||
});
|
||||
}
|
||||
|
||||
get qtyForecast() {
|
||||
return this._formatQty("qty_forecast");
|
||||
}
|
||||
get qtyToOrder() {
|
||||
return this._formatQty("qty_to_order");
|
||||
}
|
||||
get productMaxQty() {
|
||||
return this._formatQty("product_max_qty");
|
||||
}
|
||||
get productMinQty() {
|
||||
return this._formatQty("product_min_qty");
|
||||
}
|
||||
|
||||
_formatQty(field) {
|
||||
return this.displayUOM
|
||||
? `${this.props[field]} ${this.props.product_uom_name}`
|
||||
: this.props[field];
|
||||
}
|
||||
}
|
||||
|
||||
PopOverLeadDays.template = "stock.leadDaysPopOver";
|
||||
|
||||
export class ReplenishmentHistoryWidget extends JsonPpopOver {}
|
||||
ReplenishmentHistoryWidget.template = "stock.replenishmentHistory";
|
||||
|
||||
registry.category("fields").add("lead_days_widget", PopOverLeadDays);
|
||||
registry.category("fields").add("replenishment_history_widget", ReplenishmentHistoryWidget);
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<div t-name="stock.leadDaysPopOver" owl="1">
|
||||
<h2>Lead Times</h2>
|
||||
<hr/>
|
||||
<p>
|
||||
The forecasted stock on the <b t-esc="props.lead_days_date"/>
|
||||
is <t t-if="props.qty_to_order <= 0"><b><t t-esc="qtyForecast"/></b></t><t t-else="">
|
||||
below the inventory <b>minimum of <t t-esc="productMinQty"/>
|
||||
: <t t-esc="qtyToOrder"/> should be replenished</b> to reach the maximum of
|
||||
<t t-esc="productMaxQty"/>.</t>
|
||||
</p>
|
||||
<table t-if="props.lead_days_description" class="table table-borderless table-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Today</td>
|
||||
<td class="text-end" t-out="props.today"/>
|
||||
</tr>
|
||||
<tr t-foreach="props.lead_days_description" t-key="descr" t-as="descr">
|
||||
<td t-out="descr[0]"/>
|
||||
<td class="text-end" t-out="descr[1]"/>
|
||||
</tr>
|
||||
<tr class="table-info">
|
||||
<td>Forecasted Date</td>
|
||||
<td class="text-end text-nowrap">
|
||||
= <t t-out="props.lead_days_date"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div t-name="stock.replenishmentHistory" owl="1">
|
||||
<h2>Sales History</h2>
|
||||
<hr/>
|
||||
<table t-if="props.replenishment_history" class="table table-borderless">
|
||||
<tbody>
|
||||
<t t-foreach="props.replenishment_history" t-key="line" t-as="line">
|
||||
<tr>
|
||||
<td><t t-esc="line.name"/></td>
|
||||
<td class="text-end text-nowrap" t-esc="line.quantity + ' ' + line.uom_name"/>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="stock.forecastWidget">
|
||||
<span t-if="['draft', 'partially_available', 'assigned', 'cancel', 'done'].includes(state)" t-esc="reserved_availability_str"/>
|
||||
<span t-elif="!forecast_expected_date_str and will_be_fulfilled" class="text-success">Available</span>
|
||||
<span t-elif="forecast_expected_date_str and will_be_fulfilled" t-att-class="forecast_is_late ? 'text-danger' : 'text-warning'">Exp <t t-esc="forecast_expected_date_str"/></span>
|
||||
<span t-else="" class="text-danger">Not Available</span>
|
||||
<button t-if="product_type == 'product'" t-att="id ? {} : {'disabled': ''}" class="o_forecast_report_button btn btn-link o_icon_button ms-2" title="Forecasted Report">
|
||||
<i t-attf-class="fa fa-fw fa-area-chart {{ state != 'draft' and (!will_be_fulfilled or forecast_is_late) ? 'text-danger' : '' }}"/>
|
||||
</button>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -1,41 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<div t-name="stock.leadDaysPopOver">
|
||||
<h2>Lead Times</h2>
|
||||
<hr/>
|
||||
<p>
|
||||
The forecasted stock on the <b><t t-esc="lead_days_date"/></b>
|
||||
is <t t-if="qty_to_order <= 0"><b><t t-esc="qty_forecast"/> <t t-esc="product_uom_name"/></b></t><t t-else="">
|
||||
below the inventory <b>minimum of <t t-esc="product_min_qty"/> <t t-esc="product_uom_name"/>
|
||||
: <t t-esc="qty_to_order"/> <t t-esc="product_uom_name"/> should be replenished</b> to reach the maximum of
|
||||
<t t-esc="product_max_qty"/> <t t-esc="product_uom_name"/>.</t>
|
||||
</p>
|
||||
<table t-if="lead_days_description" class="table table-borderless table-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Today
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<t t-out="today"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-foreach="lead_days_description" t-as="descr">
|
||||
<td><t t-out="descr[0]"/></td>
|
||||
<td class="text-end"><t t-out="descr[1]"/></td>
|
||||
</tr>
|
||||
<tr class="table-info">
|
||||
<td>
|
||||
Forecasted Date
|
||||
</td>
|
||||
<td class="text-end text-nowrap">
|
||||
= <t t-out="lead_days_date"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<t t-name="StockOrderpoint.Buttons">
|
||||
<span>
|
||||
<button type="button" class="btn d-none btn-primary o_button_order">
|
||||
@@ -46,19 +10,4 @@
|
||||
</button>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<div t-name="stock.replenishmentHistory">
|
||||
<h2>Sales History</h2>
|
||||
<hr/>
|
||||
<table t-if="replenishment_history" class="table table-borderless">
|
||||
<tbody>
|
||||
<t t-foreach="replenishment_history" t-as="line">
|
||||
<tr>
|
||||
<td><t t-esc="line.name"/></td>
|
||||
<td class="text-end text-nowrap" t-esc="line.quantity + ' ' + line.uom_name"/>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</templates>
|
||||
|
||||
@@ -42,7 +42,6 @@ class StockReplenishmentInfo(models.TransientModel):
|
||||
dummy, lead_days_description = orderpoint.rule_ids._get_lead_days(
|
||||
orderpoint.product_id, **orderpoints_values)
|
||||
replenishment_report.json_lead_days = dumps({
|
||||
'template': 'stock.leadDaysPopOver',
|
||||
'lead_days_date': format_date(self.env, replenishment_report.orderpoint_id.lead_days_date),
|
||||
'lead_days_description': lead_days_description,
|
||||
'today': format_date(self.env, fields.Date.today()),
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
<field name="qty_to_order" invisible="1"/>
|
||||
<div class="row ml8">
|
||||
<group class="col-6">
|
||||
<field nolabel="1" name="json_lead_days" widget="json_widget"/>
|
||||
<field nolabel="1" name="json_lead_days" widget="lead_days_widget"/>
|
||||
</group>
|
||||
<group class="col-6">
|
||||
<field nolabel="1" name="json_replenishment_history" widget="json_widget"/>
|
||||
<field nolabel="1" name="json_replenishment_history" widget="replenishment_history_widget"/>
|
||||
</group>
|
||||
</div>
|
||||
<notebook>
|
||||
|
||||
Reference in New Issue
Block a user