[MERGE] (event)(_sale)(_product_configurator): clean up

1* Allow products access from product_id / product_template_id of sale.order.line
2* Allow use of optional products along rentals and events.

closes odoo/odoo#34170

Signed-off-by: Damien Bouvy (dbo) <dbo@odoo.com>
This commit is contained in:
Robot Odoo
2019-07-16 17:39:09 +02:00
committed by GitHub
12 changed files with 137 additions and 93 deletions
@@ -74,7 +74,7 @@ ProductConfiguratorWidget.include({
* @override
* @private
*/
_onEditProductConfiguration: function () {
_onEditLineConfiguration: function () {
if (this.recordData.event_ok) {
var defaultValues = {
default_product_id: this.recordData.product_id.data.id
@@ -117,17 +117,19 @@ ProductConfiguratorWidget.include({
self.trigger_up('field_changed', {
dataPointID: dataPointId,
changes: result.eventConfiguration,
// VFE NOTE : Do we unselect the row after event config?
// event_sale tour is made to work without unselectRow
/*onSuccess: function (){
self.getParent().unselectRow();
}*/
onSuccess: function () {
// Call post-init function.
self._onLineConfigured();
}
});
} else {
if (!self.recordData.event_id || !self.recordData.event_ticket_id) {
self.trigger_up('field_changed', {
dataPointID: dataPointId,
changes: {product_id: false},
changes: {
product_id: false,
name: ''
},
});
}
}
@@ -30,9 +30,6 @@
/>
<field name="event_ok" invisible="1"/>
</xpath>
<xpath expr="//field[@name='order_line']//tree//field[@name='product_id']" position="attributes">
<attribute name="options">{'no_open': True}</attribute>
</xpath>
<xpath expr="//field[@name='order_line']//tree//field[@name='product_id']" position="after">
<field name="event_ok" invisible="1" />
<field name="event_id" optional="hide" />
+1
View File
@@ -263,6 +263,7 @@ class ProductTemplate(models.Model):
product_template = product_template.with_context(current_attributes_price_extra=[v.price_extra or 0.0 for v in combination])
list_price = product_template.price_compute('list_price')[product_template.id]
price = product_template.price if pricelist else list_price
display_image = bool(product_template.image)
filtered_combination = combination._without_no_variant_attributes()
if filtered_combination:
@@ -17,7 +17,7 @@ var _t = core._t;
*/
var ProductConfiguratorWidget = relationalFields.FieldMany2One.extend({
events: _.extend({}, relationalFields.FieldMany2One.prototype.events, {
'click .o_edit_product_configuration': '_onEditProductConfiguration'
'click .o_edit_product_configuration': '_onEditConfiguration'
}),
/**
@@ -26,13 +26,35 @@ var ProductConfiguratorWidget = relationalFields.FieldMany2One.extend({
_render: function () {
this._super.apply(this, arguments);
if (this.mode === 'edit' && this.value &&
(this._isConfigurableProduct() || this._isConfigurableLine())) {
this._addConfigurationEditButton();
(this._isConfigurableProduct() || this._isConfigurableLine())) {
this._addProductLinkButton();
this._addConfigurationEditButton();
} else if (this.mode === 'edit' && this.value) {
this._addProductLinkButton();
} else {
this.$('.o_edit_product_configuration').hide();
}
},
/**
* Add button linking to product_id/product_template_id form.
*/
_addProductLinkButton: function () {
if (this.$('.o_external_button').length === 0) {
var $productLinkButton = $('<button>', {
type: 'button',
class: 'fa fa-external-link btn btn-secondary o_external_button',
tabindex: '-1',
draggable: false,
'aria-label': _t('External Link'),
title: _t('External Link')
});
var $inputDropdown = this.$('.o_input_dropdown');
$inputDropdown.after($productLinkButton);
}
},
/**
* If current product is configurable,
* Show edit button (in Edit Mode) after the product/product_template
@@ -98,7 +120,11 @@ var ProductConfiguratorWidget = relationalFields.FieldMany2One.extend({
if (ev.data.changes && !ev.data.preventProductIdCheck && ev.data.changes.product_template_id) {
self._onTemplateChange(ev.data.changes.product_template_id.id, ev.data.dataPointID);
} else if (ev.data.changes && ev.data.changes.product_id) {
self._onProductChange(ev.data.changes.product_id.id, ev.data.dataPointID);
self._onProductChange(ev.data.changes.product_id.id, ev.data.dataPointID).then(function (wizardOpened) {
if (!wizardOpened) {
self._onLineConfigured();
}
});
}
},
@@ -134,12 +160,51 @@ var ProductConfiguratorWidget = relationalFields.FieldMany2One.extend({
},
/**
* Hook for editing a configured line.
* The button triggering this function is only shown in Edit mode,
* when _isConfigurableProduct is True.
* Hook for configurator happening after line has been set
* (options, ...).
* Allows sale_product_configurator module to apply its options
* after line configuration has been done.
*
* @private
*/
_onLineConfigured: function () {
},
/**
* Triggered on click of the configuration button.
* It is only shown in Edit mode,
* when _isConfigurableProduct or _isConfigurableLine is True.
*
* After reflexion, when a line was configured through two wizards,
* only the line configuration will open.
*
* Two hooks are available depending on configurator category:
* _onEditLineConfiguration : line configurators
* _onEditProductConfiguration : product configurators
*
* @private
*/
_onEditConfiguration: function () {
if (this._isConfigurableLine()) {
this._onEditLineConfiguration();
} else if (this._isConfigurableProduct()) {
this._onEditProductConfiguration();
}
},
/**
* Hook for line configurators (rental, event)
* on line edition (pencil icon inside product field)
*/
_onEditLineConfiguration: function () {
},
/**
* Hook for product configurators (matrix, product)
* on line edition (pencil icon inside product field)
*/
_onEditProductConfiguration: function () {
},
@@ -34,7 +34,7 @@
<div class="o_setting_right_pane">
<label for="module_sale_product_configurator"/>
<div class="text-muted">
Configure your attributes with exclusions and select optional products
Select product attributes and optional products from the sales order
</div>
</div>
</div>
+1
View File
@@ -408,6 +408,7 @@
'readonly': [('product_updatable', '=', False)],
'required': [('display_type', '=', False)],
}"
options="{'no_open': True}"
force_save="1"
context="{
'partner_id': parent.partner_id,
@@ -7,9 +7,9 @@ from odoo.http import request
class ProductConfiguratorController(http.Controller):
@http.route(['/sale_product_configurator/configure'], type='json', auth="user", methods=['POST'])
def configure(self, product_id, pricelist_id, **kw):
def configure(self, product_template_id, pricelist_id, **kw):
add_qty = int(kw.get('add_qty', 1))
product_template = request.env['product.template'].browse(int(product_id))
product_template = request.env['product.template'].browse(int(product_template_id))
pricelist = self._get_pricelist(pricelist_id)
product_combination = False
@@ -116,7 +116,7 @@ var ProductConfiguratorFormController = FormController.extend({
return this._rpc({
route: '/sale_product_configurator/configure',
params: {
product_id: productTemplateId,
product_template_id: productTemplateId,
pricelist_id: this.renderer.pricelistId,
add_qty: data.quantity,
product_template_attribute_value_ids: this._getAttributeValueIds(
@@ -23,12 +23,19 @@ ProductConfiguratorWidget.include({
/**
* Set restoreProductTemplateId for further backtrack.
* Saves the optional products in the widget for future application
* post-line configuration.
*
* {OdooEvent ev}
* {Array} ev.data.optionalProducts the various selected optional products
* with their configuration
*
* @override
* @private
*/
_onFieldChanged: function (ev) {
this.restoreProductTemplateId = this.recordData.product_template_id;
this.optionalProducts = (ev.data && ev.data.optionalProducts) || this.optionalProducts;
this._super.apply(this, arguments);
},
@@ -58,7 +65,7 @@ ProductConfiguratorWidget.include({
productTemplateId
]
}).then(function (result) {
if (result) {
if (result && !result.has_optional_products) {
self.trigger_up('field_changed', {
dataPointID: dataPointId,
changes: {
@@ -67,17 +74,10 @@ ProductConfiguratorWidget.include({
operation: 'DELETE_ALL'
}
},
onSuccess: function () {
if (result.has_optional_products) {
// VFE WARNING optional products and configurable product_id don't go well
// together for the moment...
self._openOptionsConfigurator(productTemplateId, dataPointId);
}
}
});
} else {
self._openProductConfigurator({
configuratorMode: 'add',
configuratorMode: (result && result.has_optional_products ? 'options' : 'add'),
default_pricelist_id: self._getPricelistId(),
default_product_template_id: productTemplateId
},
@@ -89,14 +89,30 @@ ProductConfiguratorWidget.include({
});
},
_openOptionsConfigurator: function (productTemplateId, dataPointId) {
this._openProductConfigurator({
configuratorMode: 'options',
default_pricelist_id: this._getPricelistId(),
default_product_template_id: productTemplateId
},
dataPointId
);
/**
* When line is configured, apply the options defined earlier.
* @override
* @private
*/
_onLineConfigured: function () {
var self = this;
this._super.apply(this, arguments);
var parentList = self.getParent();
if (self.optionalProducts && self.optionalProducts.length !== 0) {
self.trigger_up('add_record', {
context: self._productsToRecords(self.optionalProducts),
forceEditable: 'bottom',
allowWarning: true,
onSuccess: function () {
// Leave edit mode of one2many list.
parentList.unselectRow();
}
});
} else if (!self._isConfigurableLine() && self._isConfigurableProduct()) {
// Leave edit mode of current line if line was configured
// only through the product configurator.
parentList.unselectRow();
}
},
/**
@@ -121,15 +137,15 @@ ProductConfiguratorWidget.include({
* @private
*/
_openProductConfigurator: function (data, dataPointId) {
this.optionalProducts = undefined;
var self = this;
this.do_action('sale_product_configurator.sale_product_configurator_action', {
additional_context: data,
on_close: function (result) {
if (result && result !== 'special') {
// TODO if mode == 'options'
// if no options chosen, don't trigger events...
self._addProducts(result, dataPointId);
} else {
// is this restoreProductTemplateId ability useful ???
if (self.restoreProductTemplateId) {
self.trigger_up('field_changed', {
dataPointID: dataPointId,
@@ -198,30 +214,11 @@ ProductConfiguratorWidget.include({
* @private
*/
_addProducts: function (result, dataPointId) {
var self = this;
this.trigger_up('field_changed', {
dataPointID: dataPointId,
preventProductIdCheck: true,
changes: this._getMainProductChanges(result.mainProduct),
onSuccess: function () {
var parentList = self.getParent();
if (result.options && result.options.length !== 0) {
self.trigger_up('add_record', {
context: self._productsToRecords(result.options),
forceEditable: 'bottom',
allowWarning: true,
onSuccess: function () {
// Leave edit mode of the last line added.
parentList.unselectRow();
}
});
} else if (!self._isConfigurableLine()) {
// Leave edit mode of current line if no other wizard
// will be opened by the field_changed event.
parentList.unselectRow();
}
}
optionalProducts: result.options,
changes: this._getMainProductChanges(result.mainProduct)
});
},
@@ -322,28 +319,28 @@ ProductConfiguratorWidget.include({
};
if (product.no_variant_attribute_values) {
var default_product_no_variant_attribute_values = [];
_.each(product.no_variant_attribute_values, function (attribute_value) {
default_product_no_variant_attribute_values.push(
[4, parseInt(attribute_value.value)]
var defaultProductNoVariantAttributeValues = [];
_.each(product.no_variant_attribute_values, function (attributeValue) {
defaultProductNoVariantAttributeValues.push(
[4, parseInt(attributeValue.value)]
);
});
record['default_product_no_variant_attribute_value_ids']
= default_product_no_variant_attribute_values;
= defaultProductNoVariantAttributeValues;
}
if (product.product_custom_attribute_values) {
var default_custom_attribute_values = [];
_.each(product.product_custom_attribute_values, function (attribute_value) {
default_custom_attribute_values.push(
var defaultCustomAttributeValues = [];
_.each(product.product_custom_attribute_values, function (attributeValue) {
defaultCustomAttributeValues.push(
[0, 0, {
attribute_value_id: attribute_value.attribute_value_id,
custom_value: attribute_value.custom_value
attribute_value_id: attributeValue.attribute_value_id,
custom_value: attributeValue.custom_value
}]
);
});
record['default_product_custom_attribute_value_ids']
= default_custom_attribute_values;
= defaultCustomAttributeValues;
}
records.push(record);
@@ -1,18 +0,0 @@
odoo.define("sale_product_configurator.tour", function (require) {
"use strict";
/**
* We need to replace the trigger of the product selection step because the
* "sale_product_configurator" module replaces the "product_id" field with
* a "product_template_id" field.
*/
var tour = require('web_tour.tour');
require('sale.tour');
var productSelectionStepIndex = _.findIndex(tour.tours.sale_tour.steps, function (step) {
return (step.id === 'product_selection_step');
});
var productSelectionStep = tour.tours.sale_tour.steps[productSelectionStepIndex];
productSelectionStep.trigger = ".o_form_editable .o_field_many2one[name='product_template_id']";
});
@@ -2,7 +2,6 @@
<odoo>
<template id="assets_backend" inherit_id="web.assets_backend" name="sale_product_configurator assets backend">
<xpath expr="script[last()]" position="after">
<script type="text/javascript" src="/sale_product_configurator/static/src/js/tours/sale_product_configurator.js"></script>
<script type="text/javascript" src="/sale/static/src/js/variant_mixin.js"></script>
<script type="text/javascript" src="/sale_product_configurator/static/src/js/product_configurator_renderer.js"></script>
<script type="text/javascript" src="/sale_product_configurator/static/src/js/product_configurator_controller.js"></script>
+2 -2
View File
@@ -27,10 +27,10 @@
<xpath expr="//field[@name='commitment_date']" position="after">
<field name="effective_date" groups="sale.group_sale_order_dates"/>
</xpath>
<xpath expr="//page/field[@name='order_line']/form/group/group/label[@for='customer_lead']" position="before">
<xpath expr="//page/field[@name='order_line']/form/group/group/field[@name='price_unit']" position="before">
<field name="product_packaging" attrs="{'invisible': [('product_id', '=', False)]}" context="{'default_product_id': product_id, 'tree_view_ref':'product.product_packaging_tree_view', 'form_view_ref':'product.product_packaging_form_view'}" domain="[('product_id','=',product_id)]" groups="product.group_stock_packaging" />
</xpath>
<xpath expr="//page/field[@name='order_line']/tree/field[@name='tax_id']" position="before">
<xpath expr="//page/field[@name='order_line']/tree/field[@name='price_unit']" position="before">
<field name="product_packaging" attrs="{'invisible': [('product_id', '=', False)]}" context="{'default_product_id': product_id, 'tree_view_ref':'product.product_packaging_tree_view', 'form_view_ref':'product.product_packaging_form_view'}" domain="[('product_id','=',product_id)]" groups="product.group_stock_packaging" optional="show"/>
</xpath>
<xpath expr="//field[@name='order_line']/form/group/group/field[@name='analytic_tag_ids']" position="before">