mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 16:31:58 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -1,146 +0,0 @@
|
|||
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.Legacy',
|
||||
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) {
|
||||
var qty_to_deliver = utils.round_decimals(this.data.qty_to_deliver, this.fields.qty_to_deliver.digits[1]);
|
||||
if (this.data.state === 'sale') {
|
||||
this.data.will_be_fulfilled = utils.round_decimals(this.data.free_qty_today, this.fields.free_qty_today.digits[1]) >= qty_to_deliver
|
||||
} else {
|
||||
this.data.will_be_fulfilled = utils.round_decimals(this.data.virtual_available_at_date, this.fields.virtual_available_at_date.digits[1]) >= qty_to_deliver
|
||||
}
|
||||
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,
|
||||
move_to_match_ids: this.data.move_ids.res_ids,
|
||||
sale_line_to_match_id: this.data.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.Legacy', {
|
||||
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;
|
||||
});
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
<templates>
|
||||
<div t-name="sale_stock.qtyAtDate.Legacy">
|
||||
<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.Legacy">
|
||||
<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 < 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 < 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-start btn btn-link action_open_forecast"
|
||||
type="button">
|
||||
<i class="fa fa-fw o_button_icon fa-arrow-right"></i>
|
||||
View Forecast
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { ProductCatalogKanbanRecord } from "@product/product_catalog/kanban_record";
|
||||
import { ProductCatalogSaleOrderLine } from "./sale_order_line/sale_order_line";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(ProductCatalogKanbanRecord.prototype, {
|
||||
updateQuantity(quantity) {
|
||||
if (this.env.orderResModel !== "sale.order" || this.productCatalogData.productType == "service") {
|
||||
super.updateQuantity(...arguments);
|
||||
} else if (
|
||||
this.productCatalogData.quantity === this.productCatalogData.deliveredQty &&
|
||||
quantity < this.productCatalogData.quantity
|
||||
) {
|
||||
// This condition is only triggered when the product was already at the minimum quantity
|
||||
// possible, as stated in the sale_stock module, then the user inputs a quantity lower
|
||||
// than this limit, in this case we need the record to forcefully update the record.
|
||||
this.props.record.load();
|
||||
this.props.record.model.notify();
|
||||
} else {
|
||||
super.updateQuantity(Math.max(quantity, this.productCatalogData.deliveredQty));
|
||||
}
|
||||
},
|
||||
|
||||
get orderLineComponent() {
|
||||
if (this.env.orderResModel === "sale.order") {
|
||||
return ProductCatalogSaleOrderLine;
|
||||
}
|
||||
return super.orderLineComponent;
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { _t } from "@web/core/l10n/translation";
|
||||
import { ProductCatalogOrderLine } from "@product/product_catalog/order_line/order_line";
|
||||
|
||||
export class ProductCatalogSaleOrderLine extends ProductCatalogOrderLine {
|
||||
static props = {
|
||||
...ProductCatalogOrderLine.props,
|
||||
deliveredQty: Number,
|
||||
}
|
||||
|
||||
get disableRemove() {
|
||||
return this.props.quantity === this.props.deliveredQty;
|
||||
}
|
||||
|
||||
get disabledButtonTooltip() {
|
||||
if (this.disableRemove) {
|
||||
return _t("The ordered quantity cannot be decreased below the amount already delivered. Instead, create a return in your inventory.");
|
||||
}
|
||||
return super.disabledButtonTooltip;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
import { formatMonetary } from "@web/views/fields/formatters";
|
||||
import { patch } from '@web/core/utils/patch';
|
||||
|
||||
import { ForecastedDetails } from "@stock/stock_forecasted/forecasted_details";
|
||||
|
||||
patch(ForecastedDetails.prototype, 'sale_stock.ForecastedDetails', {
|
||||
_formatMonetary(num, currencyId){
|
||||
return formatMonetary(num,{ currencyId: currencyId});
|
||||
}
|
||||
});
|
||||
|
|
@ -1,25 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="sale_stock.ForecastedDetails" owl="1" t-inherit="stock.ForecastedDetails" t-inherit-mode="extension">
|
||||
<t t-name="sale_stock.ForecastedDetails" t-inherit="stock.ForecastedDetails" t-inherit-mode="extension">
|
||||
<xpath expr="//tr[@name='draft_picking_out']" position="after">
|
||||
<tr t-if="props.docs.draft_sale_qty" name="draft_so_out" t-attf-class="#{props.docs.draft_sale_orders_matched and 'o_grid_match table-info'}">
|
||||
<td colspan="2">Quotations</td>
|
||||
<td t-out="_formatFloat(-props.docs.draft_sale_qty)" class="text-end"/>
|
||||
<tr t-if="currentProduct.draft_sale_qty.out" name="draft_so_out" t-attf-class="#{currentProduct.draft_sale_orders_matched and 'table-info'} #{multipleProducts and 'collapse show' or ''} collapseGroup_#{line.product.id}">
|
||||
<td/>
|
||||
<td>
|
||||
<t t-foreach="props.docs.draft_sale_orders" t-as="sale_order" t-key="sale_order_index">
|
||||
<t t-if="sale_order_index > 0"> | </t>
|
||||
<a t-attf-href="#" t-out="sale_order.name"
|
||||
class="fw-bold"
|
||||
t-on-click.prevent="() => this.props.openView('sale.order', 'form', sale_order.id)"/>
|
||||
</t>
|
||||
<t t-set="draft_so_out_count" t-value="currentProduct.draft_sale_orders.length"/>
|
||||
<a t-if="draft_so_out_count == 1" href="#"
|
||||
t-on-click.prevent="() => this.props.openView('sale.order', 'form', currentProduct.draft_sale_orders[0].id)">
|
||||
1 Quotation</a>
|
||||
<a t-else="" href="#"
|
||||
t-on-click.prevent="() => this.props.openView('sale.order', 'list', false, [['id', 'in', currentProduct.draft_sale_orders.map((so) => so.id)]])">
|
||||
<t t-out="draft_so_out_count"/> Quotations</a>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span class="text-danger">
|
||||
<t t-out="_formatFloat(-currentProduct.draft_sale_qty.out)"/> <t t-out="line.uom_id.display_name"/>
|
||||
</span>
|
||||
</td>
|
||||
<td colspan="3"/>
|
||||
</tr>
|
||||
</xpath>
|
||||
<xpath expr="//td[@name='usedby_cell']" position="inside">
|
||||
<span t-if="line.move_out and line.move_out.picking_id and line.move_out.picking_id.sale_id">
|
||||
| <span t-out="_formatMonetary(line.move_out.picking_id.sale_id.amount_untaxed, line.move_out.picking_id.sale_id.currency_id.id)" class="fw-bold"/>
|
||||
| <span t-out="line.move_out.picking_id.sale_id.partner_id.name"/>
|
||||
</span>
|
||||
<span t-if="line.move_out and line.move_out.picking_id and line.move_out.picking_id.sale_id" t-out="' - ' + line.move_out.picking_id.sale_id.partner_id.display_name"/>
|
||||
<t t-if="line.reservation and (line.document_out._name != line.reservation._name or line.document_out.id != line.reservation.id)">
|
||||
<span t-out="' - '"/>
|
||||
<a t-if="line.reservation" t-out="line.reservation.name" href="#" class="fw-bold"
|
||||
t-on-click.prevent="() => this.props.openView(line.reservation._name, 'form', line.reservation.id)"/>
|
||||
</t>
|
||||
<span t-if="line.document_out and line.is_matched and line.document_out._name === 'sale.order'" t-out="' (Your SO)'"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import { _t } from "@web/core/l10n/translation";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
import { StockValuationReport } from "@stock_account/stock_valuation/stock_valuation_report";
|
||||
|
||||
|
||||
patch(StockValuationReport.prototype, {
|
||||
get accrual() {
|
||||
const accrual = super.accrual;
|
||||
const notInvoicedDeliveredGoods = this.data.not_invoiced_delivered_goods;
|
||||
notInvoicedDeliveredGoods.display_name = _t("Goods Delivered Not Invoiced");
|
||||
notInvoicedDeliveredGoods.method = this.openSaleOrder.bind(this);
|
||||
notInvoicedDeliveredGoods.index = accrual.lines.length;
|
||||
accrual.value += notInvoicedDeliveredGoods.value;
|
||||
accrual.lines.push(notInvoicedDeliveredGoods);
|
||||
this.saleOrderIds = notInvoicedDeliveredGoods.lines.map((line) => line.id);
|
||||
return accrual;
|
||||
},
|
||||
|
||||
// On Click Methods --------------------------------------------------------
|
||||
openSaleOrder(line=false) {
|
||||
const action = {
|
||||
type: "ir.actions.act_window",
|
||||
name: _t("Sale Orders"),
|
||||
res_model: "sale.order",
|
||||
views: [[false, "list"], [false, "form"]],
|
||||
target: "current",
|
||||
}
|
||||
if (line?.id) {
|
||||
action.views = [[false, "form"]],
|
||||
action.res_id = line.id;
|
||||
} else {
|
||||
action.domain = [["id", "in", this.saleOrderIds]];
|
||||
}
|
||||
return this.actionService.doAction(action);
|
||||
},
|
||||
});
|
||||
|
|
@ -1,42 +1,56 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { formatDateTime } from "@web/core/l10n/dates";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { usePopover } from "@web/core/popover/popover_hook";
|
||||
|
||||
const { Component, EventBus, onWillRender } = owl;
|
||||
import { Component, onWillRender } from "@odoo/owl";
|
||||
import { standardWidgetProps } from "@web/views/widgets/standard_widget_props";
|
||||
import { roundPrecision } from "@web/core/utils/numbers";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
export class QtyAtDatePopover extends Component {
|
||||
static template = "sale_stock.QtyAtDatePopover";
|
||||
static props = {
|
||||
record: Object,
|
||||
calcData: Object,
|
||||
close: Function,
|
||||
};
|
||||
setup() {
|
||||
this.actionService = useService("action");
|
||||
}
|
||||
|
||||
openForecast() {
|
||||
this.actionService.doAction("stock.stock_replenishment_product_product_action", {
|
||||
this.actionService.doAction("stock.stock_forecasted_product_product_action", {
|
||||
additionalContext: {
|
||||
active_model: 'product.product',
|
||||
active_id: this.props.record.data.product_id[0],
|
||||
warehouse: this.props.record.data.warehouse_id && this.props.record.data.warehouse_id[0],
|
||||
move_to_match_ids: this.props.record.data.move_ids.records.map(record => record.data.id),
|
||||
sale_line_to_match_id: this.props.record.data.id,
|
||||
active_id: this.props.record.data.product_id.id,
|
||||
warehouse_id: this.props.record.data.warehouse_id && this.props.record.data.warehouse_id.id,
|
||||
move_to_match_ids: this.props.record.data.move_ids.currentIds,
|
||||
sale_line_to_match_id: this.props.record.resId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get forecastedLabel() {
|
||||
return _t('Forecasted Stock')
|
||||
}
|
||||
|
||||
get availableLabel() {
|
||||
return _t('Available')
|
||||
}
|
||||
}
|
||||
|
||||
QtyAtDatePopover.template = "sale_stock.QtyDetailPopOver";
|
||||
|
||||
export class QtyAtDateWidget extends Component {
|
||||
static components = { Popover: QtyAtDatePopover };
|
||||
static template = "sale_stock.QtyAtDate";
|
||||
static props = {...standardWidgetProps};
|
||||
setup() {
|
||||
this.bus = new EventBus();
|
||||
this.popover = usePopover();
|
||||
this.closePopover = null;
|
||||
this.popover = usePopover(this.constructor.components.Popover, { position: "top" });
|
||||
this.orm = useService("orm");
|
||||
this.calcData = {};
|
||||
onWillRender(() => {
|
||||
this.initCalcData();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
initCalcData() {
|
||||
|
|
@ -60,6 +74,27 @@ export class QtyAtDateWidget extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
async calcDataForDisplay() {
|
||||
const { data } = this.props.record;
|
||||
let lineUom;
|
||||
if (data.product_uom_id?.[0]) {
|
||||
lineUom = (await this.orm.read("uom.uom", [data.product_uom_id[0]], ["factor", "rounding"]))[0];
|
||||
}
|
||||
let lineProduct;
|
||||
if (data.product_id?.[0]) {
|
||||
lineProduct = await this.orm.searchRead("product.product", [["id", "=", data.product_id[0]]], ["uom_id"]);
|
||||
}
|
||||
let productUom;
|
||||
if (lineProduct?.[0]?.uom_id?.[0]) {
|
||||
productUom = (await this.orm.searchRead("uom.uom", [["id", "=", lineProduct[0].uom_id[0]]], ["factor", "name"]))[0];
|
||||
}
|
||||
if (lineUom && productUom) {
|
||||
this.calcData.product_uom_virtual_available_at_date = roundPrecision(data.virtual_available_at_date * lineUom.factor / productUom.factor, 1);
|
||||
this.calcData.product_uom_free_qty_today = roundPrecision(data.free_qty_today * lineUom.factor / productUom.factor, 1);
|
||||
this.calcData.product_uom_name = productUom.name;
|
||||
}
|
||||
}
|
||||
|
||||
updateCalcData() {
|
||||
// popup specific data
|
||||
const { data } = this.props.record;
|
||||
|
|
@ -72,21 +107,30 @@ export class QtyAtDateWidget extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
showPopup(ev) {
|
||||
async showPopup(ev) {
|
||||
const target = ev.currentTarget;
|
||||
await this.calcDataForDisplay();
|
||||
this.updateCalcData();
|
||||
this.closePopover = this.popover.add(
|
||||
ev.currentTarget,
|
||||
this.constructor.components.Popover,
|
||||
{bus: this.bus, record: this.props.record, calcData: this.calcData},
|
||||
{
|
||||
position: 'top',
|
||||
}
|
||||
);
|
||||
this.bus.addEventListener('close-popover', this.closePopover);
|
||||
this.popover.open(target, {
|
||||
record: this.props.record,
|
||||
calcData: this.calcData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QtyAtDateWidget.components = { Popover: QtyAtDatePopover };
|
||||
QtyAtDateWidget.template = "sale_stock.qtyAtDate";
|
||||
|
||||
registry.category("view_widgets").add("qty_at_date_widget", QtyAtDateWidget);
|
||||
export const qtyAtDateWidget = {
|
||||
component: QtyAtDateWidget,
|
||||
fieldDependencies: [
|
||||
{ name: 'display_qty_widget', type: 'boolean'},
|
||||
{ name: 'free_qty_today', type: 'float'},
|
||||
{ name: 'forecast_expected_date', type: 'datetime'},
|
||||
{ name: 'is_mto', type: 'boolean'},
|
||||
{ name: 'move_ids', type: 'one2many'},
|
||||
{ name: 'qty_available_today', type: 'float'},
|
||||
{ name: 'qty_to_deliver', type: 'float'},
|
||||
{ name: 'scheduled_date', type: 'datetime'},
|
||||
{ name: 'virtual_available_at_date', type: 'float'},
|
||||
{ name: 'warehouse_id', type: 'many2one'},
|
||||
],
|
||||
};
|
||||
registry.category("view_widgets").add("qty_at_date_widget", qtyAtDateWidget);
|
||||
|
|
|
|||
|
|
@ -1,27 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<template id="template" xml:space="preserve">
|
||||
|
||||
<!-- TODO: rename this to QtyAtDate in master version -->
|
||||
<t t-name="sale_stock.qtyAtDate" owl="1">
|
||||
<div t-att-class="!props.record.data.display_qty_widget ? 'invisible' : ''">
|
||||
<a t-att-tabindex="props.record.data.display_qty_widget ? '0' : '-1'" t-on-click="showPopup" t-attf-class="fa fa-area-chart {{ calcData.forecasted_issue ? 'text-danger' : 'text-primary' }}"/>
|
||||
</div>
|
||||
<t t-name="sale_stock.QtyAtDate">
|
||||
<a t-att-tabindex="props.record.data.display_qty_widget ? '0' : '-1'"
|
||||
t-on-click="showPopup"
|
||||
t-att-class="!props.record.data.display_qty_widget ? 'invisible' : ''"
|
||||
t-attf-class="fa fa-area-chart cursor-pointer {{ calcData.forecasted_issue ? 'text-danger' : '' }}"
|
||||
/>
|
||||
</t>
|
||||
|
||||
<!-- TODO: rename this to QtyAtDatePopup in master version -->
|
||||
<t t-name="sale_stock.QtyDetailPopOver" owl="1">
|
||||
<div>
|
||||
<t t-name="sale_stock.QtyAtDatePopover">
|
||||
<div class="p-2">
|
||||
<h6>Availability</h6>
|
||||
<table class="table table-borderless table-sm">
|
||||
<tbody>
|
||||
<t t-if="!props.record.data.is_mto and ['draft', 'sent'].includes(props.record.data.state)">
|
||||
<tr>
|
||||
<td><strong>Forecasted Stock</strong><br/><small>On <span t-out="props.calcData.delivery_date"/></small></td>
|
||||
<td><b t-out='props.record.data.virtual_available_at_date'/> <t t-out='props.record.data.product_uom[1]'/></td>
|
||||
<td><strong t-out="forecastedLabel"/><br/><small>On <span t-out="props.calcData.delivery_date"/></small></td>
|
||||
<td class="text-end">
|
||||
<b t-out='props.record.data.virtual_available_at_date'/> <t t-out='props.record.data.product_uom_id[1]'/>
|
||||
<t t-if="props.record.data.product_uom_id[1] !== props.calcData.product_uom_name">
|
||||
<br/><span class="fs-7 text-muted" t-out="props.calcData.product_uom_virtual_available_at_date"/> <span class="fs-7 text-muted" t-out='props.calcData.product_uom_name'/>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Available</strong><br /><small>All planned operations included</small></td>
|
||||
<td><b t-out='props.record.data.free_qty_today' t-att-class="!props.calcData.will_be_fulfilled ? 'text-danger': ''"/> <t t-out='props.record.data.product_uom[1]'/></td>
|
||||
<td><strong t-out="availableLabel"/><br /><small>All planned operations included</small></td>
|
||||
<td class="text-end">
|
||||
<b t-out='props.record.data.free_qty_today' t-att-class="!props.calcData.will_be_fulfilled ? 'text-danger': ''"/> <t t-out='props.record.data.product_uom_id[1]'/>
|
||||
<t t-if="props.record.data.product_uom_id[1] !== props.calcData.product_uom_name">
|
||||
<br/><span t-out="props.calcData.product_uom_free_qty_today" t-att-class="`fs-7 text-muted ${!props.calcData.will_be_fulfilled ? 'text-danger': ''}`"/> <span class="fs-7 text-muted" t-out='props.calcData.product_uom_name'/>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
<t t-elif="props.record.data.is_mto and ['draft', 'sent'].includes(props.record.data.state)">
|
||||
|
|
@ -39,7 +49,7 @@
|
|||
<strong>Reserved</strong><br/>
|
||||
</td>
|
||||
<td style="min-width: 50px; text-align: right;">
|
||||
<b t-out='props.record.data.qty_available_today'/> <t t-out='props.record.data.product_uom[1]'/>
|
||||
<b t-out='props.record.data.qty_available_today'/> <t t-out='props.record.data.product_uom_id[1]'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-if="props.record.data.qty_available_today < props.record.data.qty_to_deliver">
|
||||
|
|
@ -63,7 +73,7 @@
|
|||
</table>
|
||||
<button t-if="!props.record.data.is_mto" class="text-start btn btn-link"
|
||||
type="button" t-on-click="openForecast">
|
||||
<i class="fa fa-fw o_button_icon fa-arrow-right"></i>
|
||||
<i class="oi oi-fw o_button_icon oi-arrow-right"></i>
|
||||
View Forecast
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<templates>
|
||||
<div t-name="sale_stock.DelayAlertWidget" owl="1" class="m-1">
|
||||
<div t-name="sale_stock.DelayAlertWidget" class="m-1">
|
||||
<p>The delivery
|
||||
<t t-foreach="props.late_elements" t-as="late_element" t-key="late_element.id">
|
||||
<a t-esc="late_element.name" href="#" t-on-click="openElement" t-att-element-id="late_element.id" t-att-element-model="late_element.model"/>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("test_barcode_duplication_error", {steps: () => [
|
||||
{
|
||||
trigger: "div.o_form_sheet div.o_notebook li a:contains('Sales')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "div[name='uom_ids'] span.o_tag:contains('Pack of 6')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "div.modal-content",
|
||||
},
|
||||
{
|
||||
trigger: "div[name='product_uom_ids'] input",
|
||||
run: "edit test-1234",
|
||||
},
|
||||
{
|
||||
trigger: "div[name='product_uom_ids'] ul li.o_m2o_dropdown_option_create",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "div.modal-content.o_error_dialog main:contains('The operation cannot be completed: A barcode can only be assigned to one packaging.')",
|
||||
}
|
||||
]});
|
||||
Loading…
Add table
Add a link
Reference in a new issue