summaryrefslogtreecommitdiff
path: root/addons/sale/static/src/js/product_configurator_widget.js
blob: 54bca05098f9578f0a43cc9059b5f52fa99b5e8c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
odoo.define('sale.product_configurator', function (require) {
var relationalFields = require('web.relational_fields');
var FieldsRegistry = require('web.field_registry');
var core = require('web.core');
var _t = core._t;

/**
 * The sale.product_configurator widget is a simple widget extending FieldMany2One
 * It allows the development of configuration strategies in other modules through
 * widget extensions.
 *
 *
 * !!! WARNING !!!
 *
 * This widget is only designed for sale_order_line creation/updates.
 * !!! It should only be used on a product_product or product_template field !!!
 */
var ProductConfiguratorWidget = relationalFields.FieldMany2One.extend({
    events: _.extend({}, relationalFields.FieldMany2One.prototype.events, {
        'click .o_edit_product_configuration': '_onEditConfiguration'
    }),

     /**
      * @override
      */
    _render: function () {
        this._super.apply(this, arguments);
        if (this.mode === 'edit' && this.value &&
        (this._isConfigurableProduct() || this._isConfigurableLine())) {
            this._addProductLinkButton();
            this._addConfigurationEditButton();
        } else if (this.mode === 'edit' && this.value) {
            this._addProductLinkButton();
            this.$('.o_edit_product_configuration').hide();
        } else {
            this.$('.o_external_button').hide();
            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
     */
    _addConfigurationEditButton: function () {
        var $inputDropdown = this.$('.o_input_dropdown');

        if ($inputDropdown.length !== 0 &&
            this.$('.o_edit_product_configuration').length === 0) {
            var $editConfigurationButton = $('<button>', {
                type: 'button',
                class: 'fa fa-pencil btn btn-secondary o_edit_product_configuration',
                tabindex: '-1',
                draggable: false,
                'aria-label': _t('Edit Configuration'),
                title: _t('Edit Configuration')
            });

            $inputDropdown.after($editConfigurationButton);
        }
    },

    /**
     * Hook to override with _onEditProductConfiguration
     * to know if edit pencil button has to be put next to the field
     *
     * @private
     */
    _isConfigurableProduct: function () {
        return false;
    },

    /**
     * Hook to override with _onEditProductConfiguration
     * to know if edit pencil button has to be put next to the field
     *
     * @private
     */
    _isConfigurableLine: function () {
        return false;
    },

    /**
     * Override catching changes on product_id or product_template_id.
     * Calls _onTemplateChange in case of product_template change.
     * Calls _onProductChange in case of product change.
     * Shouldn't be overridden by product configurators
     * or only to setup some data for further computation
     * before calling super.
     *
     * @override
     * @param {OdooEvent} ev
     * @param {boolean} ev.data.preventProductIdCheck prevent the product configurator widget
     *     from looping forever when it needs to change the 'product_template_id'
     *
     * @private
     */
    reset: async function (record, ev) {
        await this._super(...arguments);
        if (ev && ev.target === this) {
            if (ev.data.changes && !ev.data.preventProductIdCheck && ev.data.changes.product_template_id) {
                this._onTemplateChange(record.data.product_template_id.data.id, ev.data.dataPointID);
            } else if (ev.data.changes && ev.data.changes.product_id) {
                this._onProductChange(record.data.product_id.data && record.data.product_id.data.id, ev.data.dataPointID).then(wizardOpened => {
                    if (!wizardOpened) {
                        this._onLineConfigured();
                    }
                });
            }
        }
    },

    /**
     * Hook for product_template based configurators
     * (product configurator, matrix, ...).
     *
     * @param {integer} productTemplateId
     * @param {String} dataPointID
     *
     * @private
     */
    _onTemplateChange: function (productTemplateId, dataPointId) {
        return Promise.resolve(false);
    },

    /**
     * Hook for product_product based configurators
     * (event, rental, ...).
     * Should return
     *    true if product has been configured through wizard or
     *        the result of the super call for other wizard extensions
     *    false if the product wasn't configurable through the wizard
     *
     * @param {integer} productId
     * @param {String} dataPointID
     * @returns {Promise<Boolean>} stopPropagation true if a suitable configurator has been found.
     *
     * @private
     */
    _onProductChange: function (productId, dataPointId) {
        return Promise.resolve(false);
    },

    /**
     * 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 () {

    },

    /**
     * Utilities for recordData conversion
     */

    /**
     * Will convert the values contained in the recordData parameter to
     * a list of '4' operations that can be passed as a 'default_' parameter.
     *
     * @param {Object} recordData
     *
     * @private
     */
    _convertFromMany2Many: function (recordData) {
        if (recordData) {
            var convertedValues = [];
            _.each(recordData.res_ids, function (resId) {
                convertedValues.push([4, parseInt(resId)]);
            });

            return convertedValues;
        }

        return null;
    },

    /**
     * Will convert the values contained in the recordData parameter to
     * a list of '0' or '4' operations (based on wether the record is already persisted or not)
     * that can be passed as a 'default_' parameter.
     *
     * @param {Object} recordData
     *
     * @private
     */
    _convertFromOne2Many: function (recordData) {
        if (recordData) {
            var convertedValues = [];
            _.each(recordData.res_ids, function (resId) {
                if (isNaN(resId)) {
                    _.each(recordData.data, function (record) {
                        if (record.ref === resId) {
                            convertedValues.push([0, 0, {
                                custom_product_template_attribute_value_id: record.data.custom_product_template_attribute_value_id.data.id,
                                custom_value: record.data.custom_value
                            }]);
                        }
                    });
                } else {
                    convertedValues.push([4, resId]);
                }
            });

            return convertedValues;
        }

        return null;
    }
});

FieldsRegistry.add('product_configurator', ProductConfiguratorWidget);

return ProductConfiguratorWidget;

});