[IMP] stock, mrp: popover widget and extenstions to owl
This commit converts `popover_widget`, `stock_rescheduling_popover` and `mrp_workorder_popover` to OWL closes odoo/odoo#101987 Signed-off-by: Arnold Moyaux (arm) <arm@odoo.com>
This commit is contained in:
@@ -402,13 +402,12 @@ class MrpProduction(models.Model):
|
||||
production_no_alert.json_popover = False
|
||||
for production in (self - production_no_alert):
|
||||
production.json_popover = json.dumps({
|
||||
'popoverTemplate': 'stock.PopoverStockRescheduling',
|
||||
'delay_alert_date': format_datetime(self.env, production.delay_alert_date, dt_format=False),
|
||||
'late_elements': [{
|
||||
'id': late_document.id,
|
||||
'name': late_document.display_name,
|
||||
'model': late_document._name,
|
||||
} for late_document in production.move_raw_ids.filtered(lambda m: m.delay_alert_date).move_orig_ids._delay_alert_get_documents()
|
||||
'id': late_document.id,
|
||||
'name': late_document.display_name,
|
||||
'model': late_document._name,
|
||||
} for late_document in production.move_raw_ids.filtered(lambda m: m.delay_alert_date).move_orig_ids._delay_alert_get_documents()
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import PopoverWidget from 'stock.popover_widget';
|
||||
import fieldRegistry from 'web.field_registry';
|
||||
import { _t } from 'web.core';
|
||||
|
||||
|
||||
/**
|
||||
* Link to a Char field representing a JSON:
|
||||
* {
|
||||
* 'replan': <REPLAN_BOOL>, // Show the replan btn
|
||||
* 'color': '<COLOR_CLASS>', // Color Class of the icon (d-none to hide)
|
||||
* 'infos': [
|
||||
* {'msg' : '<MESSAGE>', 'color' : '<COLOR_CLASS>'},
|
||||
* {'msg' : '<MESSAGE>', 'color' : '<COLOR_CLASS>'},
|
||||
* ... ]
|
||||
* }
|
||||
*/
|
||||
var MrpWorkorderPopover = PopoverWidget.extend({
|
||||
popoverTemplate: 'mrp.workorderPopover',
|
||||
title: _t('Scheduling Information'),
|
||||
|
||||
_render: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (! this.$popover) {
|
||||
return;
|
||||
}
|
||||
var self = this;
|
||||
this.$popover.find('.action_replan_button').click(function (e) {
|
||||
self._onReplanClick(e);
|
||||
});
|
||||
},
|
||||
|
||||
_onReplanClick:function (e) {
|
||||
var self = this;
|
||||
this._rpc({
|
||||
model: 'mrp.workorder',
|
||||
method: 'action_replan',
|
||||
args: [[self.res_id]]
|
||||
}).then(function () {
|
||||
self.trigger_up('reload');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
fieldRegistry.add('mrp_workorder_popover', MrpWorkorderPopover);
|
||||
|
||||
export default MrpWorkorderPopover;
|
||||
@@ -0,0 +1,41 @@
|
||||
/** @odoo-module */
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { PopoverComponent, PopoverWidgetField } from '@stock/widgets/popover_widget';
|
||||
|
||||
/**
|
||||
* Link to a Char field representing a JSON:
|
||||
* {
|
||||
* 'replan': <REPLAN_BOOL>, // Show the replan btn
|
||||
* 'color': '<COLOR_CLASS>', // Color Class of the icon (d-none to hide)
|
||||
* 'infos': [
|
||||
* {'msg' : '<MESSAGE>', 'color' : '<COLOR_CLASS>'},
|
||||
* {'msg' : '<MESSAGE>', 'color' : '<COLOR_CLASS>'},
|
||||
* ... ]
|
||||
* }
|
||||
*/
|
||||
|
||||
class WorkOrderPopover extends PopoverComponent {
|
||||
setup(){
|
||||
this.orm = useService("orm");
|
||||
}
|
||||
|
||||
async onReplanClick() {
|
||||
await this.orm.call(
|
||||
'mrp.workorder',
|
||||
'action_replan',
|
||||
[this.props.record.resId]
|
||||
);
|
||||
await this.props.record.model.load();
|
||||
}
|
||||
};
|
||||
|
||||
WorkOrderPopover.template = 'mrp.workorderPopover';
|
||||
|
||||
class WorkOrderPopoverField extends PopoverWidgetField {};
|
||||
|
||||
WorkOrderPopoverField.components = {
|
||||
Popover: WorkOrderPopover
|
||||
};
|
||||
|
||||
registry.category("fields").add("mrp_workorder_popover", WorkOrderPopoverField);
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
|
||||
<div t-name="mrp.workorderPopover" owl="1" class="m-3">
|
||||
<h6>Scheduling Information</h6>
|
||||
<t t-foreach="props.infos" t-as="info" t-key="info_index">
|
||||
<i t-attf-class="fa fa-arrow-right me-2 #{ info.color }"></i><t t-esc="info.msg"/><br/>
|
||||
</t>
|
||||
<button t-if="props.replan" t-on-click="onReplanClick" class="btn btn-sm btn-primary m-1 float-end action_replan_button">Replan</button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
|
||||
<div t-name="mrp.workorderPopover">
|
||||
<t t-foreach="infos" t-as="info">
|
||||
<i t-attf-class="fa fa-arrow-right me-2 #{ info.color }"></i><t t-esc="info.msg"/><br/>
|
||||
</t>
|
||||
<button t-if="replan" class="btn btn-primary action_replan_button">Replan</button>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
@@ -64,10 +64,10 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div t-name="sale_stock.DelayAlertWidget">
|
||||
<div t-name="sale_stock.DelayAlertWidget" owl="1" class="m-1">
|
||||
<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 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"/>,
|
||||
</t> will be late.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
odoo.define('stock.popover_widget', function (require) {
|
||||
'use strict';
|
||||
|
||||
var AbstractField = require('web.AbstractField');
|
||||
var core = require('web.core');
|
||||
var QWeb = core.qweb;
|
||||
var Context = require('web.Context');
|
||||
var fieldRegistry = require('web.field_registry');
|
||||
|
||||
/**
|
||||
* Widget Popover for JSON field (char), by default render a simple html message
|
||||
* {
|
||||
* 'msg': '<CONTENT OF THE POPOVER>',
|
||||
* 'icon': '<FONT AWESOME CLASS>' (optionnal),
|
||||
* 'color': '<COLOR CLASS OF ICON>' (optionnal),
|
||||
* 'title': '<TITLE OF POPOVER>' (optionnal),
|
||||
* 'popoverTemplate': '<TEMPLATE OF THE TEMPLATE>' (optionnal)
|
||||
* }
|
||||
*/
|
||||
var PopoverWidgetField = AbstractField.extend({
|
||||
supportedFieldTypes: ['char'],
|
||||
buttonTemplape: 'stock.popoverButton',
|
||||
popoverTemplate: 'stock.popoverContent',
|
||||
trigger: 'focus',
|
||||
placement: 'top',
|
||||
html: true,
|
||||
color: 'text-primary',
|
||||
icon: 'fa-info-circle',
|
||||
|
||||
_render: function () {
|
||||
var value = JSON.parse(this.value);
|
||||
if (!value) {
|
||||
this.$el.html('');
|
||||
return;
|
||||
}
|
||||
this.$el.css('max-width', '17px');
|
||||
this.$el.html(QWeb.render(this.buttonTemplape, _.defaults(value, {color: this.color, icon: this.icon})));
|
||||
this.$el.addClass('o_widget');
|
||||
this.$el.find('a').prop('special_click', true);
|
||||
this.$popover = $(QWeb.render(value.popoverTemplate || this.popoverTemplate, value));
|
||||
this.$popover.on('click', '.action_open_forecast', this._openForecast.bind(this));
|
||||
this.$el.find('a').popover({
|
||||
content: this.$popover,
|
||||
html: this.html,
|
||||
placement: this.placement,
|
||||
title: value.title || this.title.toString(),
|
||||
trigger: this.trigger,
|
||||
delay: {'show': 0, 'hide': 100},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Redirect to the product forecasted report.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
* @returns {Promise} action loaded
|
||||
*/
|
||||
async _openForecast(ev) {
|
||||
ev.stopPropagation();
|
||||
const reportContext = {
|
||||
active_model: 'product.product',
|
||||
active_id: this.recordData.product_id.data.id,
|
||||
warehouse: this.recordData.warehouse_id.data.id,
|
||||
};
|
||||
const action = await this._rpc({
|
||||
model: reportContext.active_model,
|
||||
method: 'action_product_forecast_report',
|
||||
args: [[reportContext.active_id]],
|
||||
});
|
||||
action.context = new Context(action.context, reportContext);
|
||||
return this.do_action(action);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
this.$el.find('a').popover('dispose');
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
fieldRegistry.add('popover_widget', PopoverWidgetField);
|
||||
|
||||
return PopoverWidgetField;
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
odoo.define('stock.PopoverStockPicking', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
|
||||
var PopoverWidgetField = require('stock.popover_widget');
|
||||
var registry = require('web.field_registry');
|
||||
var _lt = core._lt;
|
||||
|
||||
var PopoverStockPicking = PopoverWidgetField.extend({
|
||||
title: _lt('Planning Issue'),
|
||||
trigger: 'focus',
|
||||
color: 'text-danger',
|
||||
icon: 'fa-exclamation-triangle',
|
||||
|
||||
_render: function () {
|
||||
this._super();
|
||||
if (this.$popover) {
|
||||
var self = this;
|
||||
this.$popover.find('a').on('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
self.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: ev.currentTarget.getAttribute('element-model'),
|
||||
res_id: parseInt(ev.currentTarget.getAttribute('element-id'), 10),
|
||||
views: [[false, 'form']],
|
||||
target: 'current'
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
registry.add('stock_rescheduling_popover', PopoverStockPicking);
|
||||
|
||||
return PopoverStockPicking;
|
||||
});
|
||||
@@ -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,53 +1,55 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { usePopover } from "@web/core/popover/popover_hook";
|
||||
const { Component } = owl;
|
||||
|
||||
const { Component, onWillStart } = owl;
|
||||
|
||||
export class JsonPpopOver extends Component {
|
||||
setup() {
|
||||
this.props = { ...this.props, ...JSON.parse(this.props.value) };
|
||||
/**
|
||||
* Extend this to add functionality to Popover (custom methods etc.)
|
||||
* need to extend PopoverWidgetField as well and set its Popover Component to new extension
|
||||
*/
|
||||
export class PopoverComponent extends Component {}
|
||||
PopoverComponent.template = 'stock.popoverContent';
|
||||
|
||||
/**
|
||||
* Widget Popover for JSON field (char), renders a popover above an icon button on click
|
||||
* {
|
||||
* 'msg': '<CONTENT OF THE POPOVER>' required if not 'popoverTemplate' is given,
|
||||
* 'icon': '<FONT AWESOME CLASS>' default='fa-info-circle',
|
||||
* 'color': '<COLOR CLASS OF ICON>' default='text-primary',
|
||||
* 'position': <POSITION OF THE POPOVER> default='top',
|
||||
* 'popoverTemplate': '<TEMPLATE OF THE POPOVER>' default='stock.popoverContent'
|
||||
* pass a template for popover to use, other data passed in JSON field will be passed
|
||||
* to popover template inside props (ex. props.someValue), must be owl template
|
||||
* }
|
||||
*/
|
||||
|
||||
export class PopoverWidgetField extends Component {
|
||||
setup(){
|
||||
this.popover = usePopover();
|
||||
this.closePopover = null;
|
||||
let fieldValue = this.props.record.data[this.props.name];
|
||||
this.jsonValue = JSON.parse(fieldValue);
|
||||
this.color = this.jsonValue.color || 'text-primary';
|
||||
this.icon = this.jsonValue.icon || 'fa-info-circle';
|
||||
}
|
||||
|
||||
showPopup(ev){
|
||||
if (this.jsonValue.popoverTemplate){
|
||||
this.constructor.components.Popover.template = this.jsonValue.popoverTemplate;
|
||||
}
|
||||
this.closePopover = this.popover.add(
|
||||
ev.currentTarget,
|
||||
this.constructor.components.Popover,
|
||||
{...this.jsonValue, record: this.props.record},
|
||||
{
|
||||
position: this.jsonValue.position || 'top',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
PopoverWidgetField.supportedTypes = ['char'];
|
||||
PopoverWidgetField.template = 'stock.popoverButton';
|
||||
PopoverWidgetField.components = { Popover: PopoverComponent }
|
||||
registry.category("fields").add("popover_widget", PopoverWidgetField);
|
||||
|
||||
@@ -1,47 +1,12 @@
|
||||
<?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>
|
||||
<t t-name="stock.popoverButton" owl="1">
|
||||
<a tabindex="0" t-on-click.stop="showPopup" t-attf-class="p-1 fa #{ icon || 'fa-info-circle'} #{ color || 'text-primary'}"/>
|
||||
</t>
|
||||
|
||||
<div t-name="stock.popoverContent" owl="1" class="m-3">
|
||||
<h6 t-out="props.title"/>
|
||||
<t t-out="props.msg"/>
|
||||
</div>
|
||||
</templates>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/** @odoo-module */
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { PopoverComponent, PopoverWidgetField } from '@stock/widgets/popover_widget';
|
||||
|
||||
class StockRescheculingPopoverComponent extends PopoverComponent {
|
||||
setup(){
|
||||
this.action = useService("action");
|
||||
}
|
||||
|
||||
openElement(ev){
|
||||
this.action.doAction({
|
||||
res_model: ev.currentTarget.getAttribute('element-model'),
|
||||
res_id: parseInt(ev.currentTarget.getAttribute('element-id')),
|
||||
views: [[false, "form"]],
|
||||
type: "ir.actions.act_window",
|
||||
view_mode: "form",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
StockRescheculingPopoverComponent.template ='stock.PopoverStockRescheduling';
|
||||
|
||||
class StockRescheculingPopover extends PopoverWidgetField {
|
||||
setup(){
|
||||
super.setup();
|
||||
this.color = this.jsonValue.color || 'text-danger';
|
||||
this.icon = this.jsonValue.icon || 'fa-exclamation-triangle';
|
||||
}
|
||||
|
||||
showPopup(ev){
|
||||
if (!this.jsonValue.late_elements){
|
||||
return;
|
||||
}
|
||||
super.showPopup(ev);
|
||||
}
|
||||
}
|
||||
StockRescheculingPopover.components = {
|
||||
Popover: StockRescheculingPopoverComponent
|
||||
}
|
||||
|
||||
registry.category("fields").add("stock_rescheduling_popover", StockRescheculingPopover);
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates>
|
||||
|
||||
<div t-name="stock.PopoverStockRescheduling" owl="1" class="m-3">
|
||||
<h6>Planning Issue</h6>
|
||||
<p>Preceding operations
|
||||
<t t-foreach="props.late_elements" t-as="late_element" t-key="late_element.id">
|
||||
<a t-out="late_element.name" t-on-click="openElement" href="#" t-att-element-id="late_element.id" t-att-element-model="late_element.model"/>,
|
||||
</t>
|
||||
planned on <t t-out="props.delay_alert_date"/>.</p>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="stock.popoverButton">
|
||||
<a tabindex="0" t-attf-class="p-1 fa #{ icon || 'fa-info-circle'} #{ color || 'text-primary'}"/>
|
||||
</t>
|
||||
|
||||
<div t-name="stock.popoverContent">
|
||||
<t t-esc="msg"/>
|
||||
</div>
|
||||
|
||||
<div t-name="stock.PopoverStockRescheduling">
|
||||
<p>Preceding operations
|
||||
<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="late_element.model"/>,
|
||||
</t>
|
||||
planned on <t t-esc="delay_alert_date"/>.</p>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -1,53 +1,59 @@
|
||||
odoo.define('stock.popover_widget_tests', function (require) {
|
||||
"use strict";
|
||||
/** @odoo-module **/
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
var FormView = require('web.FormView');
|
||||
var createView = testUtils.createView;
|
||||
import { getFixture, click } from "@web/../tests/helpers/utils";
|
||||
|
||||
QUnit.module('widgets', {}, function () {
|
||||
QUnit.module('ModelFieldSelector', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
partner: {
|
||||
fields: {
|
||||
json_data: {string: " ", type: "char"},
|
||||
},
|
||||
records: [
|
||||
{id:1, json_data:'{"color": "text-danger", "msg": "var that = self // why not?", "title": "JS Master"}'}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
}, function () {
|
||||
QUnit.test("Test creation/usage popover widget form", async function (assert) {
|
||||
assert.expect(6);
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let target;
|
||||
let serverData
|
||||
|
||||
QUnit.module('widgets', ()=> {
|
||||
QUnit.module('PopoverWidget', hooks => {
|
||||
hooks.beforeEach(()=>{
|
||||
target = getFixture();
|
||||
setupViewRegistries();
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
json_data: {string: " ", type: "char"},
|
||||
},
|
||||
records: [
|
||||
{id:1, json_data:'{"color": "text-danger", "msg": "var that = self // why not?", "title": "JS Master"}'}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:'<form string="Partners">' +
|
||||
'<field name="json_data" widget="popover_widget"/>' +
|
||||
'</form>',
|
||||
res_id: 1
|
||||
});
|
||||
|
||||
var $popover = $('div.popover');
|
||||
assert.strictEqual($popover.length, 0, "Shouldn't have a popover container in DOM");
|
||||
QUnit.test('Test creation/usage form popover widget', async assert => {
|
||||
assert.expect(5);
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="json_data" widget="popover_widget"/>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
assert.containsNone(target, 'div.popover', "Shouldn't have a popover container in DOM");
|
||||
assert.containsOnce(target, 'a.fa.fa-info-circle.text-danger', "Should have a popover icon/button in red");
|
||||
await click(target, 'a.fa.fa-info-circle.text-danger');
|
||||
assert.containsOnce(target, 'div.popover', "Should have a popover icon/button in red");
|
||||
assert.strictEqual(
|
||||
target.querySelector("div.popover").innerHTML.includes("var that = self // why not?"),
|
||||
true,
|
||||
"The message should be in DOM"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector("div.popover").innerHTML.includes("JS Master"),
|
||||
true,
|
||||
"The title should be in DOM"
|
||||
);
|
||||
|
||||
var $popoverButton = form.$('a.fa.fa-info-circle.text-danger');
|
||||
assert.strictEqual($popoverButton.length, 1, "Should have a popover icon/button in red");
|
||||
assert.strictEqual($popoverButton.prop('special_click'), true, "Special click properpy should be activated");
|
||||
await testUtils.dom.triggerEvent($popoverButton, 'focusin');
|
||||
$popover = $('div.popover');
|
||||
assert.strictEqual($popover.length, 1, "Should have a popover container in DOM");
|
||||
assert.strictEqual($popover.html().includes("var that = self // why not?"), true, "The message should be in DOM");
|
||||
assert.strictEqual($popover.html().includes("JS Master"), true, "The title should be in DOM");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
decoration-success="state == 'done'"
|
||||
decoration-warning="state not in ('draft','cancel','done','assigned')"/>
|
||||
<field name="activity_exception_decoration" widget="activity_exception"/>
|
||||
<field name="json_popover" nolabel="1" widget="stock_rescheduling_popover" attrs="{'invisible': [('json_popover', '=', False)]}"/>
|
||||
<field name="json_popover" widget="stock_rescheduling_popover" nolabel="1" attrs="{'invisible': [('json_popover', '=', False)]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Reference in New Issue
Block a user