[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:
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user