[IMP] purchase_requsition: convert custom js to OWL

For original specs, see [1].

Some performance changes: the best price/date/unit price fields are now
loaded by JS instead of passed via action context (allows for easier/
consistent reuse of view + now we can refresh without undoing any state
changes due to buttons being pushed).

[1] https://github.com/odoo/odoo/commit/f533e40f0e3f1cd34f1047b70dfdf95837d84501

closes odoo/odoo#101502

X-original-commit: 41ae508c2e6fc3c40b29b1bfc4b5dd7c4440a719
Signed-off-by: Antony Lesuisse <al@odoo.com>
Signed-off-by: Tiffany Chang <tic@odoo.com>
This commit is contained in:
Tiffany Chang (tic)
2022-09-29 12:16:58 +02:00
parent f25e271c4a
commit c9ea3fcac6
15 changed files with 136 additions and 225 deletions
+4 -6
View File
@@ -27,12 +27,10 @@ are agreements you have with vendors to benefit from a predetermined pricing.
],
'assets': {
'web.assets_backend': [
'purchase_requisition/static/src/js/purchase_order_alternatives_widget.js',
'purchase_requisition/static/src/js/purchase_order_compare_list_renderer.js',
'purchase_requisition/static/src/js/purchase_order_line_compare_list_controller.js',
'purchase_requisition/static/src/js/purchase_order_line_compare_list_renderer.js',
'purchase_requisition/static/src/js/purchase_order_line_compare_list_view.js',
'purchase_requisition/static/src/xml/purchase_order_line_compare.xml',
'purchase_requisition/static/src/*/**.js',
'purchase_requisition/static/src/views/*/**.js',
'purchase_requisition/static/src/*/**.scss',
'purchase_requisition/static/src/*/**.xml',
],
},
'license': 'LGPL-3',
@@ -195,15 +195,9 @@ class PurchaseOrder(models.Model):
}
def action_compare_alternative_lines(self):
best_price_ids, best_date_ids, best_price_unit_ids = self.get_tender_best_lines()
ctx = dict(
self.env.context,
search_default_groupby_product=True,
params={
'best_price_ids': best_price_ids,
'best_date_ids': best_date_ids,
'best_price_unit_ids': best_price_unit_ids,
},
)
view_id = self.env.ref('purchase_requisition.purchase_order_line_compare_tree').id
return {
@@ -1,52 +0,0 @@
/** @odoo-module **/
import field_registry from 'web.field_registry';
import { FieldMany2Many } from 'web.relational_fields';
import PurchaseOrderCompareListRenderer from '@purchase_requisition/js/purchase_order_compare_list_renderer';
const FieldMany2ManySameWindow = FieldMany2Many.extend({
/**
* @override
*/
init: function (parent, name, record, options) {
this._super(...arguments);
this.isMany2Many = true;
},
/**
* Override to ensure we can't unlink a PO from itself (i.e. confusing behavior)
* @override
*/
_getRenderer: function () {
if (this.view.arch.tag === 'tree') {
return PurchaseOrderCompareListRenderer;
}
return this._super(...arguments);
},
/**
* Override to open record in same window w/breadcrumb extended
* @override
*/
_onOpenRecord: function (ev) {
ev.stopPropagation();
const targetId = ev.target.state.data.find(obj => {
return obj.id === ev.data.id;
}).res_id;
// don't re-open current record
if (targetId !== this.record.res_id) {
this._rpc({
model: this.model,
method: 'get_formview_action',
args: [[targetId]],
context: this.record.getContext(this.recordParams),
domain: this.record.getDomain(this.recordParams),
}).then(action => {
this.trigger_up('do_action', {action: action});
});
}
}
});
field_registry.add('many2many_same_window', FieldMany2ManySameWindow);
@@ -1,26 +0,0 @@
/** @odoo-module **/
import ListRenderer from 'web.ListRenderer';
const PurchaseOrderCompareListRenderer = ListRenderer.extend({
init: function (parent, state, params) {
this._super(...arguments);
this.active_id = parent.res_id;
},
/**
* @override
* @private
* @returns {Promise}
*/
_renderRow: function (record) {
const $tr = this._super(...arguments);
if (record.res_id === this.active_id && $tr.find('.o_list_record_remove').length) {
$tr.find(".o_list_record_remove").remove();
}
return $tr;
}
});
export default PurchaseOrderCompareListRenderer;
@@ -1,66 +0,0 @@
/** @odoo-module **/
import ListController from 'web.ListController';
const PurchaseOrderLineCompareListController = ListController.extend({
// -------------------------------------------------------------------------
// Public
// -------------------------------------------------------------------------
init: function (parent, model, renderer, params) {
this.context = renderer.state.getContext();
this.best_date_ids = this.context.params.best_date_ids;
this.best_price_ids = this.context.params.best_price_ids;
this.best_price_unit_ids = this.context.params.best_price_unit_ids;
return this._super(...arguments);
},
// -------------------------------------------------------------------------
// Handlers
// -------------------------------------------------------------------------
/**
* @override
*/
_onButtonClicked: function (ev) {
if (ev.data.attrs.class && ev.data.attrs.class.split(' ').includes('o_clear_qty_buttons')) {
ev.stopPropagation();
var self = this;
return this._callButtonAction(ev.data.attrs, ev.data.record).then(() => {
const context = this.model.localData[0] && this.model.localData[0].getContext() || {};
return self._rpc({
model: "purchase.order",
method: 'get_tender_best_lines',
args: [self.context.active_id],
context: context,
}).then((best_lines) => {
self.renderer.best_date_ids = best_lines[0] || [];
self.renderer.best_price_ids = best_lines[1] || [];
self.renderer.best_price_unit_ids = best_lines[2] || [];
self.reload();
});
});
} else {
this._super.apply(this, arguments);
}
},
_onHeaderButtonClicked: async function (node) {
await this._super(...arguments);
if (node.attrs.name && node.attrs.name === 'action_clear_quantities') {
const context = this.model.localData[0] && this.model.localData[0].getContext() || {};
const best_lines = await this._rpc({
model: 'purchase.order',
method: 'get_tender_best_lines',
args: this.context.active_ids,
context,
});
this.renderer.best_date_ids = best_lines[0];
this.renderer.best_price_ids = best_lines[1];
this.renderer.best_price_unit_ids = best_lines[2];
this.reload();
}
},
});
export default PurchaseOrderLineCompareListController;
@@ -1,35 +0,0 @@
/** @odoo-module **/
import ListRenderer from 'web.ListRenderer';
const PurchaseOrderLineCompareListRenderer = ListRenderer.extend({
init: function (parent, state, params) {
this._super(...arguments);
this.best_date_ids = this.state.context.params.best_date_ids;
this.best_price_ids = this.state.context.params.best_price_ids;
this.best_price_unit_ids = this.state.context.params.best_price_unit_ids;
},
/**
* @override
* @private
* @returns {Promise}
*/
_renderRow: function (record) {
const $tr = this._super(...arguments);
if (this.best_date_ids.includes(record.res_id)) {
$tr.find('[name="date_planned"]').toggleClass('text-success');
}
if (this.best_price_ids.includes(record.res_id)) {
$tr.find('[name="price_subtotal"]').toggleClass('text-success');
}
if (this.best_price_unit_ids.includes(record.res_id)) {
$tr.find('[name="price_unit"]').toggleClass('text-success');
}
return $tr;
}
});
export default PurchaseOrderLineCompareListRenderer;
@@ -1,17 +0,0 @@
/** @odoo-module **/
import ListView from 'web.ListView';
import PurchaseOrderLineCompareListController from '@purchase_requisition/js/purchase_order_line_compare_list_controller';
import PurchaseOrderLineCompareListRenderer from '@purchase_requisition/js/purchase_order_line_compare_list_renderer';
import viewRegistry from 'web.view_registry';
const PurchaseOrderLineCompareListView = ListView.extend({
config: Object.assign({}, ListView.prototype.config, {
Controller: PurchaseOrderLineCompareListController,
Renderer: PurchaseOrderLineCompareListRenderer,
}),
});
viewRegistry.add('purchase_order_line_compare', PurchaseOrderLineCompareListView);
export default PurchaseOrderLineCompareListView;
@@ -0,0 +1,52 @@
/** @odoo-module */
import { ListRenderer } from "@web/views/list/list_renderer";
const { onWillStart, useState, useSubEnv } = owl;
export class PurchaseOrderLineCompareListRenderer extends ListRenderer {
setup() {
super.setup();
this.bestFields = useState({
best_price_ids: [],
best_date_ids: [],
best_price_unit_ids: [],
});
onWillStart(async () => {
await this.updateBestFields();
});
const defaultOnClickViewButton = this.env.onClickViewButton;
useSubEnv({
onClickViewButton: async (params) => {
await defaultOnClickViewButton(params);
await this.updateBestFields();
}
});
}
async updateBestFields() {
[this.bestFields.best_price_ids,
this.bestFields.best_date_ids,
this.bestFields.best_price_unit_ids] = await this.props.list.model.orm.call(
"purchase.order",
"get_tender_best_lines",
[this.props.list.context.active_id],
{ context: this.props.list.context }
);
}
getCellClass(column, record) {
let classNames = super.getCellClass(...arguments);
const customClassNames = [];
if (column.name === "price_subtotal" && this.bestFields.best_price_ids.includes(record.resId)) {
customClassNames.push("text-success");
}
if (column.name === "date_planned" && this.bestFields.best_date_ids.includes(record.resId)) {
customClassNames.push("text-success");
}
if (column.name === "price_unit" && this.bestFields.best_price_unit_ids.includes(record.resId)) {
customClassNames.push("text-success");
}
return classNames.concat(" ", customClassNames.join(" "));
}
}
@@ -0,0 +1,13 @@
/** @odoo-module **/
import { listView } from '@web/views/list/list_view';
import { registry } from "@web/core/registry";
import { PurchaseOrderLineCompareListRenderer } from "./purchase_order_line_compare_list_renderer";
export const PurchaseOrderLineCompareListView = {
...listView,
Renderer: PurchaseOrderLineCompareListRenderer,
};
registry.category("views").add("purchase_order_line_compare", PurchaseOrderLineCompareListView);
@@ -0,0 +1,46 @@
/** @odoo-module */
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { X2ManyField } from "@web/views/fields/x2many/x2many_field";
import { ListRenderer } from "@web/views/list/list_renderer";
export class FieldMany2ManyAltPOsRenderer extends ListRenderer {
isCurrentRecord(record) {
return record.data.id === this.env.model.root.data.id;
}
}
FieldMany2ManyAltPOsRenderer.recordRowTemplate = "purchase_requisition.AltPOsListRenderer.RecordRow";
export class FieldMany2ManyAltPOs extends X2ManyField {
setup() {
this.orm = useService("orm");
this.action = useService("action");
// TODO: this is a terrible hack, make this a proper extension of many2many if/when possible
this.props.record.activeFields[this.props.name].widget = "many2many";
super.setup();
}
/**
* Override to: avoid reopening currently open record
* open record in same window w/breadcrumb extended
* @override
*/
async openRecord(record) {
if (record.data.id !== this.props.record.data.id) {
const action = await this.orm.call(record.resModel, "get_formview_action", [[record.data.id]], {
context: this.props.context,
});
await this.action.doAction(action);
}
}
}
FieldMany2ManyAltPOs.components = {
...X2ManyField.components,
ListRenderer: FieldMany2ManyAltPOsRenderer,
};
registry.category("fields").add("many2many_alt_pos", FieldMany2ManyAltPOs);
@@ -0,0 +1,3 @@
.o_field_many2many_alt_pos {
width: 100%;
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<!-- Ensure we can't unlink a PO from itself (i.e. confusing behavior) -->
<t t-name="purchase_requisition.AltPOsListRenderer.RecordRow" t-inherit="web.ListRenderer.RecordRow" t-inherit-mode="primary" owl="1">
<xpath expr="//t[@t-if='displayOptionalFields or activeActions']" position="attributes">
<attribute name="t-if">activeActions and !isCurrentRecord(record)</attribute>
</xpath>
</t>
</templates>
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-extend="ListView.buttons" t-name="PurchaseOrderLineCompare.Buttons">
<t t-jquery="button.o_list_button_discard" t-operation="after">
<button class="btn btn-secondary o_clear_qty_buttons me-2" type="button">
Clear Selected
</button>
</t>
</t>
</templates>
@@ -150,11 +150,13 @@ class TestPurchaseRequisition(TestPurchaseRequisitionCommon):
alt_po_1.order_line.date_planned += timedelta(days=1)
alt_po_1.order_line.price_unit = unit_price - 10
action = orig_po.action_compare_alternative_lines()
params = action['context']['params']
best_price_pol = self.env['purchase.order.line'].browse(params['best_price_ids'])
best_date_pol = self.env['purchase.order.line'].browse(params['best_date_ids'])
best_price_ids, best_date_ids, best_price_unit_ids = orig_po.get_tender_best_lines()
best_price_pol = self.env['purchase.order.line'].browse(best_price_ids)
best_date_pol = self.env['purchase.order.line'].browse(best_date_ids)
best_unit_price_pol = self.env['purchase.order.line'].browse(best_price_unit_ids)
self.assertEqual(best_price_pol.order_id.id, alt_po_1.id, "Best price PO line was not correctly calculated")
self.assertEqual(best_date_pol.order_id.id, orig_po.id, "Best date PO line was not correctly calculated")
self.assertEqual(best_unit_price_pol.order_id.id, alt_po_1.id, "Best unit price PO line was not correctly calculated")
# second flow: create extra alt PO, check that all 3 POs are correctly auto-linked
action = orig_po.action_create_alternative()
@@ -28,8 +28,8 @@
</p>
</group>
</group>
<field name="alternative_po_ids" attrs="{'readonly': [('id', '=', False)]}" widget="many2many_same_window" context="{'quotation_only': True}">
<tree string="Alternative Purchase Order" create="0" decoration-muted="state in ['cancel', 'purchase', 'done']" decoration-bf="id == parent.id">
<field name="alternative_po_ids" attrs="{'readonly': [('id', '=', False)]}" widget="many2many_alt_pos" context="{'quotation_only': True}">
<tree string="Alternative Purchase Order" decoration-muted="state in ['cancel', 'purchase', 'done']" decoration-bf="id == parent.id">
<control>
<create string="Link to Existing RfQ"/>
</control>