mirror of https://github.com/OCA/web.git
[IMP] web_widget_one2many_product_picker: Instant search mode
parent
417bb032a9
commit
699cc2e5bc
|
@ -91,6 +91,8 @@ Widget options:
|
|||
modify/create a record with the widget.
|
||||
|
||||
* ignore_warning > Enable/Disable display onchange warnings (False by default)
|
||||
* instant_search > Enable/Disable instant search mode (False by default)
|
||||
* trigger_refresh_fields > Fields in the main record that dispatch a widget refresh (["partner_id", "currency_id"] by default)
|
||||
|
||||
All widget options are optional.
|
||||
Notice that you can call '_' method to use translations. This only can be used with this widget.
|
||||
|
|
|
@ -49,6 +49,8 @@ Widget options:
|
|||
modify/create a record with the widget.
|
||||
|
||||
* ignore_warning > Enable/Disable display onchange warnings (False by default)
|
||||
* instant_search > Enable/Disable instant search mode (False by default)
|
||||
* trigger_refresh_fields > Fields in the main record that dispatch a widget refresh (["partner_id", "currency_id"] by default)
|
||||
|
||||
All widget options are optional.
|
||||
Notice that you can call '_' method to use translations. This only can be used with this widget.
|
||||
|
|
|
@ -479,6 +479,10 @@ modify/create a record with the widget.</p>
|
|||
</li>
|
||||
<li><p class="first">ignore_warning > Enable/Disable display onchange warnings (False by default)</p>
|
||||
</li>
|
||||
<li><p class="first">instant_search > Enable/Disable instant search mode (False by default)</p>
|
||||
</li>
|
||||
<li><p class="first">trigger_refresh_fields > Fields in the main record that dispatch a widget refresh ([“partner_id”, “currency_id”] by default)</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>All widget options are optional.
|
||||
Notice that you can call ‘_’ method to use translations. This only can be used with this widget.</p>
|
||||
|
|
|
@ -62,6 +62,15 @@ odoo.define(
|
|||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_applyChanges: function() {
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
this._updateButtons();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create or accept changes
|
||||
*/
|
||||
|
@ -227,7 +236,6 @@ odoo.define(
|
|||
this.trigger_up("quick_record_updated", {
|
||||
changes: ev.data.changes,
|
||||
});
|
||||
this._updateButtons();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -261,7 +269,6 @@ odoo.define(
|
|||
saving: false,
|
||||
});
|
||||
this.model.unsetDirty(this.handle);
|
||||
// Self._updateButtons();
|
||||
this._enableQuickCreate();
|
||||
},
|
||||
});
|
||||
|
@ -298,13 +305,25 @@ odoo.define(
|
|||
|
||||
this.trigger_up("restore_flip_card", {
|
||||
success_callback: function() {
|
||||
self.trigger_up("update_quick_record", {
|
||||
id: record.id,
|
||||
callback: function() {
|
||||
self.model.unsetDirty(self.handle);
|
||||
// Self._updateButtons();
|
||||
self._enableQuickCreate();
|
||||
},
|
||||
// Qty are handled in a special way because can be modified without
|
||||
// wait for server response
|
||||
self.model.localData[record.id].data[
|
||||
self.fieldMap.product_uom_qty
|
||||
] = record.data[self.fieldMap.product_uom_qty];
|
||||
// SaveRecord used to make a save point.
|
||||
self.saveRecord(self.handle, {
|
||||
stayInEdit: true,
|
||||
reload: true,
|
||||
savePoint: true,
|
||||
viewType: "form",
|
||||
}).then(() => {
|
||||
self.trigger_up("update_quick_record", {
|
||||
id: record.id,
|
||||
callback: function() {
|
||||
self.model.unsetDirty(self.handle);
|
||||
self._enableQuickCreate();
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
block: true,
|
||||
|
@ -314,29 +333,30 @@ odoo.define(
|
|||
_discard: function() {
|
||||
if (this._disabled) {
|
||||
// Don't do anything if we are already creating a record
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this._disableQuickCreate();
|
||||
const record = this.model.get(this.handle);
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
has_changes_confirmed: true,
|
||||
});
|
||||
// Rollback to restore the save point
|
||||
this.model.discardChanges(this.handle, {
|
||||
rollback: true,
|
||||
});
|
||||
const record = this.model.get(this.handle);
|
||||
this.trigger_up("quick_record_updated", {
|
||||
changes: record.data,
|
||||
});
|
||||
if (this.model.isNew(record.id)) {
|
||||
this.update({}, {reload: false});
|
||||
|
||||
this.update({}, {reload: false}).then(() => {
|
||||
if (!this.model.isNew(record.id)) {
|
||||
this.model.unsetDirty(this.handle);
|
||||
}
|
||||
this.trigger_up("restore_flip_card");
|
||||
this._updateButtons();
|
||||
this._enableQuickCreate();
|
||||
} else {
|
||||
this.update({}, {reload: false}).then(() => {
|
||||
this.model.unsetDirty(this.handle);
|
||||
this.trigger_up("restore_flip_card");
|
||||
this._updateButtons();
|
||||
this._enableQuickCreate();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,7 +42,6 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
front: [],
|
||||
back: [],
|
||||
};
|
||||
|
||||
this._lazyUpdateRecord = _.debounce(this._updateRecord.bind(this), 450);
|
||||
},
|
||||
|
||||
|
@ -51,8 +50,10 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
generateVirtualState: function() {
|
||||
return this._generateVirtualState().then(this.recreate.bind(this));
|
||||
generateVirtualState: function(simple_mode) {
|
||||
return this._generateVirtualState(undefined, undefined, simple_mode).then(
|
||||
this.recreate.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -80,11 +81,26 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
* @override
|
||||
*/
|
||||
destroy: function() {
|
||||
this.abortTimeouts();
|
||||
if (this.state) {
|
||||
this.options.basicFieldParams.model.removeVirtualRecord(this.state.id);
|
||||
}
|
||||
this.$el.remove();
|
||||
this.$card.off("");
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
abortTimeouts: function() {
|
||||
if (this._timerOnChange) {
|
||||
clearTimeout(this._timerOnChange);
|
||||
this._timerOnChange = false;
|
||||
}
|
||||
if (this.state) {
|
||||
const model = this.options.basicFieldParams.model;
|
||||
model.updateRecordContext(this.state.id, {aborted: true});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
|
@ -105,6 +121,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
* @returns {Promise}
|
||||
*/
|
||||
recreate: function(state) {
|
||||
if (!this.getParent()) {
|
||||
// It's a zombie record! ensure kill it!
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state) {
|
||||
this._setState(state);
|
||||
}
|
||||
|
@ -121,6 +143,15 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
return this._render();
|
||||
},
|
||||
|
||||
markToDestroy: function() {
|
||||
this.toDestroy = true;
|
||||
this.$el.hide();
|
||||
},
|
||||
|
||||
isMarkedToDestroy: function() {
|
||||
return this.toDestroy === true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates the URL for the given product using the selected field
|
||||
*
|
||||
|
@ -151,7 +182,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
price,
|
||||
this.state.fields[field_name],
|
||||
this.options.currencyField,
|
||||
this.state.data
|
||||
this.options.basicFieldParams.record.data
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -184,12 +215,15 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
|
||||
this.fields = this.getParent().state.fields;
|
||||
this.fieldsInfo = this.getParent().state.fieldsInfo.form;
|
||||
const model = this.options.basicFieldParams.model;
|
||||
if (this.state && (!viewState || this.state.id !== viewState.id)) {
|
||||
model.removeVirtualRecord(this.state.id);
|
||||
}
|
||||
this.state = viewState;
|
||||
|
||||
if (recordSearch) {
|
||||
this.recordSearch = recordSearch;
|
||||
}
|
||||
const model = this.options.basicFieldParams.model;
|
||||
this.is_virtual =
|
||||
(this.state && model.isPureVirtual(this.state.id)) || false;
|
||||
|
||||
|
@ -203,6 +237,16 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
this._incProductQty(lazy_qty - 1);
|
||||
}
|
||||
}
|
||||
|
||||
this._setMasterUomMap();
|
||||
},
|
||||
|
||||
_setMasterUomMap: function() {
|
||||
this.master_uom_map = {
|
||||
field_uom: "product_uom",
|
||||
field_uom_qty: "product_uom_qty",
|
||||
search_field_uom: "uom_id",
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -232,6 +276,9 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
auto_save: this.options.autoSave,
|
||||
is_saving: record && record.context.saving,
|
||||
lazy_qty: record && record.context.lazy_qty,
|
||||
has_onchange: record && !record.context.not_onchange,
|
||||
field_uom: this.master_uom_map.field_uom,
|
||||
field_uom_qty: this.master_uom_map.field_uom_qty,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -243,25 +290,151 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
*/
|
||||
_getInternalVirtualRecordContext: function() {
|
||||
const context = {};
|
||||
context["default_" + this.options.basicFieldParams.relation_field] =
|
||||
this.options.basicFieldParams.state.id || null;
|
||||
context[`default_${this.options.basicFieldParams.relation_field}`] =
|
||||
this.options.basicFieldParams.record.id || null;
|
||||
context[`default_${this.options.fieldMap.product}`] =
|
||||
this.recordSearch.id || null;
|
||||
context[`default_${this.master_uom_map.field_uom_qty}`] = 1.0;
|
||||
return context;
|
||||
},
|
||||
|
||||
/**
|
||||
* Forced data used in virtual states.
|
||||
* Be careful with the onchanges sequence. Think as user interaction, not as CRUD operation.
|
||||
* Be careful with the onchanges sequence. Think as user interaction ("ADD", "DELETE", ... commands), not as CRUD operation.
|
||||
*
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getInternalVirtualRecordData: function() {
|
||||
const data = {};
|
||||
data[this.options.fieldMap.product] = {
|
||||
operation: "ADD",
|
||||
id: this.recordSearch.id,
|
||||
};
|
||||
return data;
|
||||
// To be overwritten
|
||||
return {};
|
||||
},
|
||||
|
||||
_generateVirtualStateSimple: function(context, def_values) {
|
||||
const model = this.options.basicFieldParams.model;
|
||||
return new Promise(resolve => {
|
||||
const record_def = model.createVirtualDatapoint(
|
||||
this.options.basicFieldParams.value.id,
|
||||
{
|
||||
context: context,
|
||||
}
|
||||
);
|
||||
model.applyDefaultValues(record_def.record.id, def_values).then(() => {
|
||||
const new_state = model.get(record_def.record.id);
|
||||
const product_uom_id =
|
||||
new_state.data[
|
||||
this.options.fieldMap[this.master_uom_map.field_uom]
|
||||
].id;
|
||||
// Apply default values
|
||||
model
|
||||
.applyDefaultValues(
|
||||
product_uom_id,
|
||||
{
|
||||
display_name: this.recordSearch[
|
||||
this.master_uom_map.search_field_uom
|
||||
][1],
|
||||
},
|
||||
{
|
||||
fieldNames: ["display_name"],
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
return model._fetchRelationalData(record_def.record);
|
||||
})
|
||||
.then(() => {
|
||||
return model._postprocess(record_def.record);
|
||||
})
|
||||
.then(() => {
|
||||
this._timerOnChange = setTimeout(
|
||||
(current_batch_id, record_def) => {
|
||||
this._timerOnChange = false;
|
||||
if (
|
||||
current_batch_id !=
|
||||
this.options.basicFieldParams
|
||||
.current_batch_id ||
|
||||
record_def.record.context.aborted
|
||||
) {
|
||||
return;
|
||||
}
|
||||
model
|
||||
._makeDefaultRecordNoDatapoint(
|
||||
record_def.record,
|
||||
record_def.params
|
||||
)
|
||||
.then(() => {
|
||||
if (record_def.record.context.aborted) {
|
||||
return;
|
||||
}
|
||||
model.updateRecordContext(
|
||||
record_def.record.id,
|
||||
{
|
||||
not_onchange: false,
|
||||
}
|
||||
);
|
||||
this.recreate(
|
||||
model.get(record_def.record.id)
|
||||
);
|
||||
});
|
||||
},
|
||||
750,
|
||||
this.options.basicFieldParams.current_batch_id,
|
||||
record_def
|
||||
);
|
||||
|
||||
resolve(model.get(record_def.record.id));
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_generateVirtualStateFull: function(data, context, def_values) {
|
||||
const model = this.options.basicFieldParams.model;
|
||||
return new Promise(resolve => {
|
||||
model
|
||||
.createVirtualRecord(this.options.basicFieldParams.value.id, {
|
||||
context: context,
|
||||
})
|
||||
.then(result => {
|
||||
// Apply default values
|
||||
model
|
||||
.applyDefaultValues(result.record.id, def_values)
|
||||
.then(() => {
|
||||
const new_state = model.get(result.record.id);
|
||||
const product_uom_id =
|
||||
new_state.data[
|
||||
this.options.fieldMap[
|
||||
this.master_uom_map.field_uom
|
||||
]
|
||||
].id;
|
||||
model
|
||||
.applyDefaultValues(
|
||||
product_uom_id,
|
||||
{
|
||||
display_name: this.recordSearch[
|
||||
this.master_uom_map.search_field_uom
|
||||
][1],
|
||||
},
|
||||
{
|
||||
fieldNames: ["display_name"],
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
const sdata = _.extend(
|
||||
{},
|
||||
this._getInternalVirtualRecordData(),
|
||||
data
|
||||
);
|
||||
this._applyChanges(
|
||||
result.record.id,
|
||||
sdata,
|
||||
result.params
|
||||
).then(() =>
|
||||
resolve(model.get(result.record.id))
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -270,19 +443,39 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
* @param {Object} context
|
||||
* @returns {Object}
|
||||
*/
|
||||
_generateVirtualState: function(data, context) {
|
||||
const model = this.options.basicFieldParams.model;
|
||||
_generateVirtualState: function(data, context, simple_mode) {
|
||||
const scontext = _.extend(
|
||||
{},
|
||||
this._getInternalVirtualRecordContext(),
|
||||
context
|
||||
);
|
||||
// Force qty to 1.0 to launch correct onchanges
|
||||
scontext[`default_${this.options.fieldMap.product_uom_qty}`] = 1.0;
|
||||
const sdata = _.extend({}, this._getInternalVirtualRecordData(), data);
|
||||
return model.createVirtualRecord(this.options.basicFieldParams.value.id, {
|
||||
data: sdata,
|
||||
context: scontext,
|
||||
// Apply default values
|
||||
const def_values = {
|
||||
[this.options.fieldMap.product]: this.recordSearch.id,
|
||||
[this.options.fieldMap[this.master_uom_map.field_uom_qty]]: 1.0,
|
||||
[this.options.fieldMap[this.master_uom_map.field_uom]]: this
|
||||
.recordSearch[this.master_uom_map.search_field_uom][0],
|
||||
};
|
||||
if (simple_mode) {
|
||||
return this._generateVirtualStateSimple(scontext, def_values);
|
||||
}
|
||||
return this._generateVirtualStateFull(data, scontext, def_values);
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply changes (with onchange)
|
||||
*
|
||||
* @param {Integer/String} record_id
|
||||
* @param {Object} changes
|
||||
* @param {Object} options
|
||||
*/
|
||||
_applyChanges: function(record_id, changes, options) {
|
||||
const model = this.options.basicFieldParams.model;
|
||||
return model._applyChange(record_id, changes, options).then(() => {
|
||||
model.updateRecordContext(record_id, {
|
||||
not_onchange: false,
|
||||
});
|
||||
this.recreate(model.get(record_id));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -482,15 +675,18 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
this.$el.find(to_find.join()).each((key, value) => {
|
||||
const $elm = $(value);
|
||||
const format_out = $elm.data("esc") || $elm.data("field");
|
||||
$elm.html(
|
||||
py.eval(format_out, _.extend({}, state_data, this.recordSearch))
|
||||
const text_out = py.eval(
|
||||
format_out,
|
||||
_.extend({}, state_data, this.recordSearch)
|
||||
);
|
||||
$elm.html(text_out);
|
||||
$elm.attr("title", text_out);
|
||||
});
|
||||
|
||||
if (this.options.showDiscount) {
|
||||
const field_map = this.options.fieldMap;
|
||||
if (state_data) {
|
||||
const has_discount = state_data[field_map.discount] > 0.0;
|
||||
const has_discount = state_data[field_map.discount] !== 0.0;
|
||||
this.$el
|
||||
.find(".original_price,.discount_price")
|
||||
.toggleClass("d-none", !has_discount);
|
||||
|
@ -526,7 +722,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
price_reduce,
|
||||
this.state.fields[field_map.price_unit],
|
||||
this.options.currencyField,
|
||||
this.state.data
|
||||
this.options.basicFieldParams.record.data
|
||||
)
|
||||
);
|
||||
},
|
||||
|
@ -594,9 +790,14 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
if (record.context.saving) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const changes = _.pick(record.data, this.options.fieldMap.product_uom_qty);
|
||||
if (changes[this.options.fieldMap.product_uom_qty] === 0) {
|
||||
changes[this.options.fieldMap.product_uom_qty] = 1;
|
||||
const changes = _.pick(
|
||||
record.data,
|
||||
this.options.fieldMap[this.master_uom_map.field_uom_qty]
|
||||
);
|
||||
if (
|
||||
changes[this.options.fieldMap[this.master_uom_map.field_uom_qty]] === 0
|
||||
) {
|
||||
changes[this.options.fieldMap[this.master_uom_map.field_uom_qty]] = 1;
|
||||
}
|
||||
this.$card.addClass("blocked");
|
||||
return model.notifyChanges(record.id, changes).then(() => {
|
||||
|
@ -627,15 +828,25 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
// wait for the Odoo response.
|
||||
const model_record_data = model.localData[this.state.id].data;
|
||||
if (
|
||||
_.isNull(model_record_data[this.options.fieldMap.product_uom_qty])
|
||||
_.isNull(
|
||||
model_record_data[
|
||||
this.options.fieldMap[this.master_uom_map.field_uom_qty]
|
||||
]
|
||||
)
|
||||
) {
|
||||
model_record_data[this.options.fieldMap.product_uom_qty] = 1;
|
||||
model_record_data[
|
||||
this.options.fieldMap[this.master_uom_map.field_uom_qty]
|
||||
] = 1;
|
||||
}
|
||||
model_record_data[this.options.fieldMap.product_uom_qty] += amount;
|
||||
model_record_data[
|
||||
this.options.fieldMap[this.master_uom_map.field_uom_qty]
|
||||
] += amount;
|
||||
return model
|
||||
.notifyChanges(record.id, {
|
||||
[this.options.fieldMap.product_uom_qty]:
|
||||
model_record_data[this.options.fieldMap.product_uom_qty],
|
||||
[this.options.fieldMap[this.master_uom_map.field_uom_qty]]:
|
||||
model_record_data[
|
||||
this.options.fieldMap[this.master_uom_map.field_uom_qty]
|
||||
],
|
||||
})
|
||||
.then(() => {
|
||||
this._processDynamicFields();
|
||||
|
@ -836,6 +1047,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
_onRestoreFlipCard: function(evt) {
|
||||
this.$card.removeClass("active");
|
||||
this.$front.removeClass("d-none");
|
||||
const $img = this.$front.find("img");
|
||||
const cur_img_src = $img.attr("src");
|
||||
if (this.$card.hasClass("oe_flip_card_maximized")) {
|
||||
this.$card.removeClass("oe_flip_card_maximized");
|
||||
this.$card.on("transitionend", () => {
|
||||
|
@ -859,6 +1072,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
if (evt.data.block) {
|
||||
this.$card.addClass("blocked");
|
||||
}
|
||||
$img.attr("src", $img.data("srcAlt"));
|
||||
$img.data("srcAlt", cur_img_src);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -878,6 +1093,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
|||
*/
|
||||
_onQuickRecordUpdated: function(evt) {
|
||||
this._processDynamicFields(Object.keys(evt.data.changes));
|
||||
// This.recreate();
|
||||
this.trigger_up("update_subtotal");
|
||||
},
|
||||
});
|
||||
|
|
|
@ -40,9 +40,7 @@ odoo.define(
|
|||
// 'receive' more arguments.
|
||||
this.options = parent.options;
|
||||
this.mode = parent.mode;
|
||||
this.search_data = parent._searchRecords;
|
||||
this.search_group = parent._activeSearchGroup;
|
||||
this.last_search_data_count = parent._lastSearchRecordsCount;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -50,7 +48,7 @@ odoo.define(
|
|||
*/
|
||||
on_attach_callback: function() {
|
||||
this._isInDom = true;
|
||||
_.invoke(this.widgets, "on_attach_callback");
|
||||
_.invoke(_.compact(this.widgets), "on_attach_callback");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -58,7 +56,7 @@ odoo.define(
|
|||
*/
|
||||
on_detach_callback: function() {
|
||||
this._isInDom = false;
|
||||
_.invoke(this.widgets, "on_detach_callback");
|
||||
_.invoke(_.compact(this.widgets), "on_detach_callback");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -78,16 +76,10 @@ odoo.define(
|
|||
},
|
||||
|
||||
/**
|
||||
* @param {Object} search_data
|
||||
* @param {Number} count
|
||||
* @param {Object} search_group
|
||||
*/
|
||||
updateSearchData: function(search_data, count, search_group) {
|
||||
this.search_data = search_data;
|
||||
this.last_search_data_count = count;
|
||||
updateSearchGroup: function(search_group) {
|
||||
this.search_group = search_group;
|
||||
this._loadMoreWorking = false;
|
||||
this.$btnLoadMore.attr("disabled", false);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -115,20 +107,11 @@ odoo.define(
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Recreate the given widget by the state id
|
||||
*
|
||||
* @param {String} state_id
|
||||
* @param {Object} new_state
|
||||
*/
|
||||
updateRecord: function(state_id, new_state) {
|
||||
for (let eb = this.widgets.length - 1; eb >= 0; --eb) {
|
||||
const widget = this.widgets[eb];
|
||||
if (widget.state.id === state_id) {
|
||||
widget.recreate(new_state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_isValidLineState: function(state) {
|
||||
return (
|
||||
state.data[this.options.field_map.product] &&
|
||||
state.data[this.options.field_map.product].data.id
|
||||
);
|
||||
},
|
||||
|
||||
_isEqualState: function(state_a, state_b) {
|
||||
|
@ -137,9 +120,31 @@ odoo.define(
|
|||
}
|
||||
const product_id_a =
|
||||
state_a.data[this.options.field_map.product].data.id;
|
||||
const product_uom_id_a =
|
||||
state_a.data[this.options.field_map.product_uom].data.id;
|
||||
const product_id_b =
|
||||
state_b.data[this.options.field_map.product].data.id;
|
||||
return product_id_a === product_id_b;
|
||||
const product_uom_id_b =
|
||||
state_b.data[this.options.field_map.product_uom].data.id;
|
||||
|
||||
return (
|
||||
product_id_a === product_id_b &&
|
||||
product_uom_id_a === product_uom_id_b
|
||||
);
|
||||
},
|
||||
|
||||
_existsWidgetWithState: function(state) {
|
||||
for (let eb = this.widgets.length - 1; eb >= 0; --eb) {
|
||||
const widget = this.widgets[eb];
|
||||
if (
|
||||
widget &&
|
||||
widget.state &&
|
||||
this._isEqualState(widget.state, state)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -152,63 +157,52 @@ odoo.define(
|
|||
* @returns {Array}
|
||||
*/
|
||||
_processStatesToDestroy: function(states) {
|
||||
// Get widgets to destroy
|
||||
// Update states only affect to "non pure virtual" records
|
||||
const to_destroy = [];
|
||||
const to_add = [];
|
||||
for (const state of states) {
|
||||
for (let e = this.widgets.length - 1; e >= 0; --e) {
|
||||
const widget = this.widgets[e];
|
||||
if (widget && this._isEqualState(widget.state, state)) {
|
||||
to_destroy.push(widget);
|
||||
delete this.widgets[e];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If doesn't exists other records with the same product, we need
|
||||
// create a 'pure virtual' record again.
|
||||
const to_add = [];
|
||||
for (const index_destroy in to_destroy) {
|
||||
const widget_destroyed = to_destroy[index_destroy];
|
||||
let found = false;
|
||||
// If already exists a widget for the product don't try create a new one
|
||||
for (let eb = this.widgets.length - 1; eb >= 0; --eb) {
|
||||
const widget = this.widgets[eb];
|
||||
if (
|
||||
widget &&
|
||||
widget.state &&
|
||||
this._isEqualState(widget.state, widget_destroyed.state)
|
||||
) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Get the new state ID if exists to link it with the new record
|
||||
let state_id = null;
|
||||
for (let eb = this.state.data.length - 1; eb >= 0; --eb) {
|
||||
const state = this.state.data[eb];
|
||||
if (this._isEqualState(state, widget_destroyed.state)) {
|
||||
state_id = state.id;
|
||||
break;
|
||||
// If already exists a widget for the product don't try create a new one
|
||||
let recreated = false;
|
||||
if (!this._existsWidgetWithState(widget.state)) {
|
||||
// Get the new state ID if exists to link it with the new record
|
||||
// This happens when remove a record that have a new state info
|
||||
for (
|
||||
let eb = this.state.data.length - 1;
|
||||
eb >= 0;
|
||||
--eb
|
||||
) {
|
||||
const state = this.state.data[eb];
|
||||
if (!this._isValidLineState(state)) {
|
||||
continue;
|
||||
}
|
||||
if (this._isEqualState(state, widget.state)) {
|
||||
widget.recreate(state);
|
||||
recreated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!recreated) {
|
||||
widget.markToDestroy();
|
||||
to_destroy.push(widget);
|
||||
const search_record = _.omit(
|
||||
widget.recordSearch,
|
||||
"__id"
|
||||
);
|
||||
|
||||
to_add.push([
|
||||
[search_record],
|
||||
{
|
||||
no_attach_widgets: false,
|
||||
no_process_records: false,
|
||||
position: widget.state.id,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
// "Lines" section doesn't show virtual records
|
||||
if (
|
||||
(state_id && this.search_group.name === "main_lines") ||
|
||||
this.search_group.name !== "main_lines"
|
||||
) {
|
||||
const widget_product_id =
|
||||
widget_destroyed.state.data[
|
||||
this.options.field_map.product
|
||||
].data.id;
|
||||
const search_record = _.find(this.search_data, {
|
||||
id: widget_product_id,
|
||||
});
|
||||
const new_search_record = _.extend({}, search_record, {
|
||||
__id: state_id,
|
||||
});
|
||||
const card_id = widget_destroyed.$el.data("cardId");
|
||||
to_add.push([[new_search_record], false, true, card_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -225,12 +219,16 @@ odoo.define(
|
|||
*/
|
||||
_processCurrentStates: function() {
|
||||
// Records to Update or Create
|
||||
const model = this.getParent().getBasicFieldParams().model;
|
||||
const to_destroy = [];
|
||||
const to_add = [];
|
||||
for (const index in this.state.data) {
|
||||
const state = this.state.data[index];
|
||||
if (!this._isValidLineState(state)) {
|
||||
continue;
|
||||
}
|
||||
let exists = false;
|
||||
let search_record_index = -1;
|
||||
let search_record_index = false;
|
||||
let search_record = false;
|
||||
for (let e = this.widgets.length - 1; e >= 0; --e) {
|
||||
const widget = this.widgets[e];
|
||||
|
@ -238,24 +236,28 @@ odoo.define(
|
|||
// Already processed widget (deleted)
|
||||
continue;
|
||||
}
|
||||
if (this._isEqualState(widget.state, state)) {
|
||||
var model = this.getParent().getBasicFieldParams().model;
|
||||
var record = model.get(widget.state.id);
|
||||
|
||||
const is_equal_state = this._isEqualState(widget.state, state);
|
||||
if (widget.isMarkedToDestroy()) {
|
||||
exists = true;
|
||||
} else if (is_equal_state) {
|
||||
const record = model.get(widget.state.id);
|
||||
model.updateRecordContext(state.id, {
|
||||
lazy_qty: record.context.lazy_qty || 0,
|
||||
});
|
||||
widget.recreate(state);
|
||||
exists = true;
|
||||
break;
|
||||
} else if (
|
||||
}
|
||||
if (
|
||||
!is_equal_state &&
|
||||
widget.recordSearch.id ===
|
||||
state.data[this.options.field_map.product].data.id
|
||||
state.data[this.options.field_map.product].data.id
|
||||
) {
|
||||
// Is a new record (can be other record for the same 'search record' or a replacement for a pure virtual)
|
||||
search_record_index = widget.$el.index();
|
||||
search_record_index = widget.state.id;
|
||||
search_record = widget.recordSearch;
|
||||
var model = this.getParent().getBasicFieldParams().model;
|
||||
var record = model.get(widget.state.id);
|
||||
const record = model.get(widget.state.id);
|
||||
model.updateRecordContext(state.id, {
|
||||
lazy_qty: record.context.lazy_qty || 0,
|
||||
});
|
||||
|
@ -273,16 +275,18 @@ odoo.define(
|
|||
|
||||
this.state.data = _.compact(this.state.data);
|
||||
|
||||
// Need add a new one?
|
||||
if (!exists && search_record_index !== -1) {
|
||||
// Add to create the new record
|
||||
if (!exists && search_record_index) {
|
||||
const new_search_record = _.extend({}, search_record, {
|
||||
__id: state.id,
|
||||
});
|
||||
to_add.push([
|
||||
[new_search_record],
|
||||
false,
|
||||
true,
|
||||
search_record_index,
|
||||
{
|
||||
no_attach_widgets: true,
|
||||
no_process_records: true,
|
||||
position: search_record_index,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -304,9 +308,15 @@ odoo.define(
|
|||
const states_to_destroy = [];
|
||||
for (const index in old_states) {
|
||||
const old_state = old_states[index];
|
||||
if (!this._isValidLineState(old_state)) {
|
||||
continue;
|
||||
}
|
||||
let found = false;
|
||||
for (const e in this.state.data) {
|
||||
const current_state = this.state.data[e];
|
||||
if (!this._isValidLineState(current_state)) {
|
||||
continue;
|
||||
}
|
||||
if (this._isEqualState(current_state, old_state)) {
|
||||
found = true;
|
||||
break;
|
||||
|
@ -323,47 +333,40 @@ odoo.define(
|
|||
states_to_destroy
|
||||
);
|
||||
|
||||
// Make widgets to destroy invisible to avoid render 'dance'
|
||||
for (const widget of to_destroy_old) {
|
||||
widget.$el.hide();
|
||||
const [
|
||||
destroyed_current,
|
||||
to_add_current,
|
||||
] = this._processCurrentStates();
|
||||
|
||||
const currentTasks = [];
|
||||
const to_add = [].concat(to_add_current, to_add_virtual);
|
||||
for (const params of to_add) {
|
||||
currentTasks.push(this.appendSearchRecords.apply(this, params)[0]);
|
||||
}
|
||||
|
||||
const oldTasks = [];
|
||||
for (const params of to_add_virtual) {
|
||||
oldTasks.push(this.appendSearchRecords.apply(this, params)[0]);
|
||||
}
|
||||
Promise.all(oldTasks).then(() => {
|
||||
const [
|
||||
to_destroy_current,
|
||||
to_add_current,
|
||||
] = this._processCurrentStates();
|
||||
|
||||
// Make widgets to destroy invisible to avoid render 'dance'
|
||||
for (const widget of to_destroy_current) {
|
||||
widget.$el.hide();
|
||||
}
|
||||
|
||||
const currentTasks = [];
|
||||
for (const params of to_add_current) {
|
||||
currentTasks.push(
|
||||
this.appendSearchRecords.apply(this, params)[0]
|
||||
);
|
||||
}
|
||||
Promise.all(currentTasks).then(() => {
|
||||
_.invoke(to_destroy_old, "destroy");
|
||||
_.invoke(to_destroy_current, "destroy");
|
||||
def.resolve();
|
||||
});
|
||||
Promise.all(currentTasks).then(() => {
|
||||
_.invoke(to_destroy_old, "destroy");
|
||||
_.invoke(destroyed_current, "destroy");
|
||||
this.widgets = _.difference(this.widgets, to_destroy_old);
|
||||
def.resolve();
|
||||
});
|
||||
|
||||
return def;
|
||||
},
|
||||
|
||||
clearRecords: function() {
|
||||
_.invoke(_.compact(this.widgets), "destroy");
|
||||
this.widgets = [];
|
||||
if (this.$recordsContainer) {
|
||||
this.$recordsContainer.empty();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_renderView: function() {
|
||||
const oldWidgets = _.compact(this.widgets);
|
||||
_.invoke(_.compact(this.widgets), "destroy");
|
||||
this.widgets = [];
|
||||
this.$recordsContainer = $("<DIV/>", {
|
||||
class: "w-100 row",
|
||||
|
@ -374,23 +377,14 @@ odoo.define(
|
|||
this.$btnLoadMore = this.$extraButtonsContainer.find(
|
||||
"#productPickerLoadMore"
|
||||
);
|
||||
this.search_data = this._sort_search_data(this.search_data);
|
||||
return new Promise(resolve => {
|
||||
const defs = this.appendSearchRecords(this.search_data, true);
|
||||
Promise.all(defs).then(() => {
|
||||
_.invoke(oldWidgets, "destroy");
|
||||
this.$el.empty();
|
||||
this.$el.append(this.$recordsContainer);
|
||||
this.$el.append(this.$extraButtonsContainer);
|
||||
this.showLoadMore(
|
||||
this.last_search_data_count >= this.options.records_per_page
|
||||
);
|
||||
if (this._isInDom) {
|
||||
_.invoke(this.widgets, "on_attach_callback");
|
||||
}
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
// This.search_data = this._sort_search_data(this.search_data);
|
||||
this.$el.empty();
|
||||
this.$el.append(this.$recordsContainer);
|
||||
this.$el.append(this.$extraButtonsContainer);
|
||||
// This.showLoadMore(
|
||||
// this.last_search_data_count >= this.options.records_per_page
|
||||
// );
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -405,7 +399,10 @@ odoo.define(
|
|||
|
||||
for (const index_state in this.state.data) {
|
||||
const state_data = this.state.data[index_state];
|
||||
if (state_data.data[field_name].res_id === data.id) {
|
||||
if (
|
||||
this._isValidLineState(state_data) &&
|
||||
state_data.data[field_name].res_id === data.id
|
||||
) {
|
||||
data._order_value = state_data.res_id;
|
||||
}
|
||||
}
|
||||
|
@ -431,32 +428,76 @@ odoo.define(
|
|||
_processSearchRecords: function(results) {
|
||||
const field_name = this.options.field_map.product;
|
||||
const records = [];
|
||||
const states = [];
|
||||
|
||||
var test_values = function(field_value, record_search) {
|
||||
return (
|
||||
(typeof field_value === "object" &&
|
||||
field_value.data.id === record_search.id) ||
|
||||
field_value === record_search.id
|
||||
);
|
||||
};
|
||||
|
||||
for (const index in results) {
|
||||
const record_search = results[index];
|
||||
let state_data_found = false;
|
||||
|
||||
// Analyze 'pure virtual' records
|
||||
// Pure virtual records aren't linked with field list
|
||||
// so we need search them linked in the widgets.
|
||||
for (const index_widget in this.widgets) {
|
||||
const widget = this.widgets[index_widget];
|
||||
if (widget.isMarkedToDestroy()) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
record_search.__id === widget.state.id ||
|
||||
(!record_search.__id &&
|
||||
widget.recordSearch.id === record_search.id)
|
||||
) {
|
||||
state_data_found = true;
|
||||
if (widget.state) {
|
||||
states.push(widget.state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If already exists a widget with the search result
|
||||
// avoid create a new one
|
||||
if (state_data_found) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Analyze field records
|
||||
// If not found any widget we need create a new one
|
||||
// linked with the state record
|
||||
for (const index_data in this.state.data) {
|
||||
const state_record = this.state.data[index_data];
|
||||
const field = state_record.data[field_name];
|
||||
if (
|
||||
(typeof field === "object" &&
|
||||
field.data.id === record_search.id) ||
|
||||
field === record_search.id
|
||||
) {
|
||||
if (!this._isValidLineState(state_record)) {
|
||||
continue;
|
||||
}
|
||||
const field_value = state_record.data[field_name];
|
||||
if (test_values(field_value, record_search)) {
|
||||
records.push(
|
||||
_.extend({}, record_search, {
|
||||
__id: state_record.id,
|
||||
})
|
||||
);
|
||||
states.push(state_record);
|
||||
state_data_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state_data_found) {
|
||||
records.push(record_search);
|
||||
}
|
||||
}
|
||||
|
||||
return records;
|
||||
return {
|
||||
records: records,
|
||||
states: states,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -502,15 +543,12 @@ odoo.define(
|
|||
* @param {Boolean} no_process_records
|
||||
* @param {Number} position
|
||||
*/
|
||||
_appendSearchRecords: function(
|
||||
search_records,
|
||||
no_process_records,
|
||||
position
|
||||
) {
|
||||
const processed_records = no_process_records
|
||||
_appendSearchRecords: function(search_records, options) {
|
||||
const processed_info = options.no_process_records
|
||||
? search_records
|
||||
: this._processSearchRecords(search_records);
|
||||
_.each(processed_records, search_record => {
|
||||
const records_to_add = processed_info.records || search_records;
|
||||
_.each(records_to_add, search_record => {
|
||||
const state_data = this._getRecordDataById(search_record.__id);
|
||||
const widget_options = this._getRecordOptions(search_record);
|
||||
widget_options.renderer_widget_index = this.widgets.length;
|
||||
|
@ -524,7 +562,9 @@ odoo.define(
|
|||
// Simulate new lines to dispatch get_default & onchange's to get the
|
||||
// relevant data to print. This case increase the TTI time.
|
||||
if (!state_data) {
|
||||
const defVirtualState = ProductPickerRecord.generateVirtualState();
|
||||
const defVirtualState = ProductPickerRecord.generateVirtualState(
|
||||
this.options.instant_search
|
||||
);
|
||||
this.defsVirtualState.push(defVirtualState);
|
||||
}
|
||||
|
||||
|
@ -536,15 +576,39 @@ odoo.define(
|
|||
function(widget, widget_position) {
|
||||
if (typeof widget_position !== "undefined") {
|
||||
const $elm = this.$el.find(
|
||||
`[data-card-id="${position}"]`
|
||||
`[data-card-id="${widget_position}"]:first`
|
||||
);
|
||||
widget.$el.insertBefore($elm);
|
||||
}
|
||||
def.resolve();
|
||||
}.bind(this, ProductPickerRecord, position)
|
||||
}.bind(this, ProductPickerRecord, options.position)
|
||||
);
|
||||
this.defs.push(def);
|
||||
});
|
||||
// Destroy unused
|
||||
if (options.cleanup) {
|
||||
const num_widgets = this.widgets.length;
|
||||
for (
|
||||
let index_widget = num_widgets - 1;
|
||||
index_widget >= 0;
|
||||
--index_widget
|
||||
) {
|
||||
const widget = this.widgets[index_widget];
|
||||
let found_state = false;
|
||||
for (const state of processed_info.states) {
|
||||
if (widget.state && widget.state.id === state.id) {
|
||||
found_state = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_state && widget.state) {
|
||||
widget.destroy();
|
||||
delete this.widgets[index_widget];
|
||||
}
|
||||
}
|
||||
// Clean widget array
|
||||
this.widgets = _.compact(this.widgets);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -563,24 +627,20 @@ odoo.define(
|
|||
* @param {Number} position
|
||||
* @returns {Array}
|
||||
*/
|
||||
appendSearchRecords: function(
|
||||
search_records,
|
||||
no_attach_widgets,
|
||||
no_process_records,
|
||||
position
|
||||
) {
|
||||
appendSearchRecords: function(search_records, options = {}) {
|
||||
this.trigger_up("loading_records");
|
||||
this.defs = [];
|
||||
this.defsVirtualState = [];
|
||||
const cur_widget_index = this.widgets.length;
|
||||
this._appendSearchRecords(search_records, no_process_records, position);
|
||||
this._appendSearchRecords(search_records, options);
|
||||
|
||||
const defs = this.defs;
|
||||
delete this.defs;
|
||||
const defsVirtualState = this.defsVirtualState;
|
||||
delete this.defsVirtualState;
|
||||
return [
|
||||
Promise.all(defs).then(() => {
|
||||
if (!no_attach_widgets && this._isInDom) {
|
||||
if (!options.no_attach_widgets && this._isInDom) {
|
||||
const new_widgets = this.widgets.slice(cur_widget_index);
|
||||
_.invoke(new_widgets, "on_attach_callback");
|
||||
}
|
||||
|
@ -597,7 +657,6 @@ odoo.define(
|
|||
_onClickLoadMore: function() {
|
||||
this.$btnLoadMore.attr("disabled", true);
|
||||
this.trigger_up("load_more");
|
||||
this._loadMoreWorking = true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2021 Tecnativa - Alexandre Díaz
|
||||
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
odoo.define("web_widget_one2many_product_picker.BasicController", function(require) {
|
||||
"use strict";
|
||||
|
||||
const BasicController = require("web.BasicController");
|
||||
|
||||
BasicController.include({
|
||||
/**
|
||||
* This is necessary to refresh 'one2many_product_picker' when some 'trigger_refresh_fields' fields changes.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_confirmChange: function(id, fields, e) {
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
const product_picker_widgets = _.filter(
|
||||
this.renderer.allFieldWidgets[this.handle],
|
||||
item => item.attrs.widget === "one2many_product_picker"
|
||||
);
|
||||
for (const widget of product_picker_widgets) {
|
||||
const trigger_fields = widget.options.trigger_refresh_fields || [];
|
||||
if (
|
||||
_.difference(trigger_fields, fields).length !==
|
||||
trigger_fields.length
|
||||
) {
|
||||
widget._reset(this.model.get(this.handle), e);
|
||||
// Force re-launch onchanges on 'pure virtual' records
|
||||
widget.renderer.clearRecords();
|
||||
widget._render();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
|
@ -7,19 +7,37 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
|
||||
BasicModel.include({
|
||||
/**
|
||||
* @param {Number/String} handle
|
||||
* @override
|
||||
*/
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* This is necessary because 'pure virtual' records
|
||||
* can be destroyed at any time.
|
||||
*
|
||||
* @param {String} id
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
exists: function(id) {
|
||||
return !_.isEmpty(this.localData[id]);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} id
|
||||
* @param {Object} context
|
||||
*/
|
||||
updateRecordContext: function(handle, context) {
|
||||
this.localData[handle].context = _.extend(
|
||||
updateRecordContext: function(id, context) {
|
||||
this.localData[id].context = _.extend(
|
||||
{},
|
||||
this.localData[handle].context,
|
||||
this.localData[id].context,
|
||||
context
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Number/String} id
|
||||
* @param {String} id
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isPureVirtual: function(id) {
|
||||
|
@ -28,7 +46,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
},
|
||||
|
||||
/**
|
||||
* @param {Number/String} id
|
||||
* @param {String} id
|
||||
* @param {Boolean} status
|
||||
*/
|
||||
setPureVirtual: function(id, status) {
|
||||
|
@ -41,7 +59,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
},
|
||||
|
||||
/**
|
||||
* @param {Number/String} id
|
||||
* @param {String} id
|
||||
*/
|
||||
unsetDirty: function(id) {
|
||||
const data = this.localData[id];
|
||||
|
@ -52,7 +70,204 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
},
|
||||
|
||||
/**
|
||||
* Generates a virtual records without link it
|
||||
* 'Pure virtual' records are not used by other
|
||||
* elements so can be removed safesly
|
||||
*
|
||||
* @param {String} id
|
||||
*/
|
||||
removeVirtualRecord: function(id) {
|
||||
if (!this.isPureVirtual(id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = this.localData[id];
|
||||
const to_remove = [];
|
||||
this._visitChildren(data, item => {
|
||||
to_remove.push(item.id);
|
||||
});
|
||||
|
||||
to_remove.reverse();
|
||||
for (const remove_id of to_remove) {
|
||||
this.removeLine(remove_id);
|
||||
delete this.localData[remove_id];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This is a cloned method from Odoo framework.
|
||||
* Virtual records are processed in two parts,
|
||||
* this is the second part and here we trigger onchange
|
||||
* process.
|
||||
*
|
||||
* @param {Object} record
|
||||
* @param {Object} params
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_makeDefaultRecordNoDatapoint: function(record, params) {
|
||||
var self = this;
|
||||
|
||||
var targetView = params.viewType;
|
||||
var fieldsInfo = params.fieldsInfo;
|
||||
var fieldNames = Object.keys(fieldsInfo[targetView]);
|
||||
var fields_key = _.without(fieldNames, "__last_update");
|
||||
|
||||
// Fields that are present in the originating view, that need to be initialized
|
||||
// Hence preventing their value to crash when getting back to the originating view
|
||||
var parentRecord =
|
||||
params.parentID && this.localData[params.parentID].type === "list"
|
||||
? this.localData[params.parentID]
|
||||
: null;
|
||||
|
||||
if (parentRecord && parentRecord.viewType in parentRecord.fieldsInfo) {
|
||||
var originView = parentRecord.viewType;
|
||||
fieldNames = _.union(
|
||||
fieldNames,
|
||||
Object.keys(parentRecord.fieldsInfo[originView])
|
||||
);
|
||||
}
|
||||
|
||||
return this._rpc({
|
||||
model: record.model,
|
||||
method: "default_get",
|
||||
args: [fields_key],
|
||||
context: params.context,
|
||||
}).then(function(result) {
|
||||
// Interrupt point (used in instant search)
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
// We want to overwrite the default value of the handle field (if any),
|
||||
// in order for new lines to be added at the correct position.
|
||||
// -> This is a rare case where the defaul_get from the server
|
||||
// will be ignored by the view for a certain field (usually "sequence").
|
||||
|
||||
var overrideDefaultFields = self._computeOverrideDefaultFields(
|
||||
params.parentID,
|
||||
params.position
|
||||
);
|
||||
|
||||
if (overrideDefaultFields) {
|
||||
result[overrideDefaultFields.field] = overrideDefaultFields.value;
|
||||
}
|
||||
|
||||
return self
|
||||
.applyDefaultValues(record.id, result, {fieldNames: fieldNames})
|
||||
.then(function() {
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
}
|
||||
var def = new Promise(function(resolve, reject) {
|
||||
var always = function() {
|
||||
if (record._warning) {
|
||||
if (params.allowWarning) {
|
||||
delete record._warning;
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
self._performOnChange(record, fields_key)
|
||||
.then(always)
|
||||
.guardedCatch(always);
|
||||
});
|
||||
return def;
|
||||
})
|
||||
.then(function() {
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
}
|
||||
return self._fetchRelationalData(record);
|
||||
})
|
||||
.then(function() {
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
}
|
||||
return self._postprocess(record);
|
||||
})
|
||||
.then(function() {
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
}
|
||||
// Save initial changes, so they can be restored later,
|
||||
// if we need to discard.
|
||||
self.save(record.id, {savePoint: true});
|
||||
|
||||
return record.id;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Virtual records are processed in two parts,
|
||||
* this is the first part and here we create
|
||||
* the state (without aditional process)
|
||||
*
|
||||
* @param {String} listID
|
||||
* @param {Object} options
|
||||
* @returns {Object}
|
||||
*/
|
||||
createVirtualDatapoint: function(listID, options) {
|
||||
const list = this.localData[listID];
|
||||
const context = _.extend({}, this._getContext(list), options.context);
|
||||
|
||||
const position = options ? options.position : "top";
|
||||
const params = {
|
||||
context: context,
|
||||
fields: list.fields,
|
||||
fieldsInfo: list.fieldsInfo,
|
||||
parentID: list.id,
|
||||
position: position,
|
||||
viewType: list.viewType,
|
||||
allowWarning: true,
|
||||
doNotSetDirty: true,
|
||||
};
|
||||
|
||||
var targetView = params.viewType;
|
||||
var fields = params.fields;
|
||||
var fieldsInfo = params.fieldsInfo;
|
||||
|
||||
// Fields that are present in the originating view, that need to be initialized
|
||||
// Hence preventing their value to crash when getting back to the originating view
|
||||
var parentRecord =
|
||||
params.parentID && this.localData[params.parentID].type === "list"
|
||||
? this.localData[params.parentID]
|
||||
: null;
|
||||
|
||||
if (parentRecord && parentRecord.viewType in parentRecord.fieldsInfo) {
|
||||
var originView = parentRecord.viewType;
|
||||
fieldsInfo[targetView] = _.defaults(
|
||||
{},
|
||||
fieldsInfo[targetView],
|
||||
parentRecord.fieldsInfo[originView]
|
||||
);
|
||||
fields = _.defaults({}, fields, parentRecord.fields);
|
||||
}
|
||||
|
||||
const record = this._makeDataPoint({
|
||||
modelName: list.model,
|
||||
fields: fields,
|
||||
fieldsInfo: fieldsInfo,
|
||||
context: params.context,
|
||||
parentID: params.parentID,
|
||||
res_ids: params.res_ids,
|
||||
viewType: targetView,
|
||||
});
|
||||
this.setPureVirtual(record.id, true);
|
||||
this.updateRecordContext(record.id, {
|
||||
ignore_warning: true,
|
||||
not_onchange: true,
|
||||
});
|
||||
|
||||
return {
|
||||
record: record,
|
||||
params: params,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates a virtual records without hard-link to any model.
|
||||
*
|
||||
* @param {Integer/String} listID
|
||||
* @param {Object} options
|
||||
|
@ -77,14 +292,14 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
return new Promise(resolve => {
|
||||
this._makeDefaultRecord(list.model, params).then(recordID => {
|
||||
this.setPureVirtual(recordID, true);
|
||||
this.updateRecordContext(recordID, {ignore_warning: true});
|
||||
if (options.data) {
|
||||
this._applyChange(recordID, options.data, params).then(() => {
|
||||
resolve(this.get(recordID));
|
||||
});
|
||||
} else {
|
||||
resolve(this.get(recordID));
|
||||
}
|
||||
this.updateRecordContext(recordID, {
|
||||
ignore_warning: true,
|
||||
not_onchange: true,
|
||||
});
|
||||
resolve({
|
||||
record: this.get(recordID),
|
||||
params: params,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -92,7 +307,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
/**
|
||||
* Adds support to avoid show onchange warnings.
|
||||
* The implementation is a pure hack that clone
|
||||
* the context and do a monkey path the
|
||||
* the context and do a monkey patch to the
|
||||
* 'trigger_up' method.
|
||||
*
|
||||
* @override
|
||||
|
@ -112,5 +327,28 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* This happens when the user discard main document changes (isn't a rollback)
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
discardChanges: function(id, options) {
|
||||
this._super.apply(this, arguments);
|
||||
options = options || {};
|
||||
var isNew = this.isNew(id);
|
||||
var rollback = "rollback" in options ? options.rollback : isNew;
|
||||
if (rollback) {
|
||||
return;
|
||||
}
|
||||
const element = this.localData[id];
|
||||
this._visitChildren(element, function(elem) {
|
||||
if (_.isEmpty(elem._changes)) {
|
||||
if (elem.context.product_picker_modified) {
|
||||
elem.context.product_picker_modified = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
"click .oe_btn_lines": "_onClickLines",
|
||||
"click .oe_btn_search_group": "_onClickSearchGroup",
|
||||
"search .oe_search_input": "_onSearch",
|
||||
"input .oe_search_input": "_onInputSearch",
|
||||
"focusin .oe_search_input": "_onFocusInSearch",
|
||||
"show.bs.dropdown .o_cp_buttons": "_onShowSearchDropdown",
|
||||
"click #product_picker_maximize": "_onClickMaximize",
|
||||
|
@ -41,19 +42,17 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
}),
|
||||
|
||||
_auto_search_delay: 450,
|
||||
_input_instant_search_time: 150,
|
||||
|
||||
// Model product.product fields
|
||||
search_read_fields: ["id", "display_name"],
|
||||
search_read_fields: ["id", "display_name", "uom_id"],
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function(parent, name, record) {
|
||||
init: function(parent) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
// This is the parent state
|
||||
this.state = record;
|
||||
|
||||
// Use jquery 'extend' to have a 'deep' merge.
|
||||
this.options = $.extend(
|
||||
true,
|
||||
|
@ -79,32 +78,31 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
if (this.view) {
|
||||
this._processGroups();
|
||||
}
|
||||
|
||||
this._currentSearchBatchID = 0;
|
||||
|
||||
this._lazyRenderSearchRecords = _.debounce(() => {
|
||||
this.doRenderSearchRecords();
|
||||
++this._currentSearchBatchID;
|
||||
}, this._input_instant_search_time);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
willStart: function() {
|
||||
if (!this.view) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (this.mode === "readonly") {
|
||||
this._updateSearchContext(-1);
|
||||
} else {
|
||||
this._updateSearchContext(0);
|
||||
}
|
||||
return Promise.all([
|
||||
this._super.apply(this, arguments),
|
||||
this._getSearchRecords(),
|
||||
]);
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
if (this.isReadonly) {
|
||||
// Show Lines
|
||||
this._updateSearchContext(-1);
|
||||
} else {
|
||||
this._updateSearchContext(0);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the lines counter badge
|
||||
*/
|
||||
updateBadgeLines: function() {
|
||||
const records = this.parent_controller.model.get(this.state.id).data[
|
||||
const records = this.parent_controller.model.get(this.record.id).data[
|
||||
this.name
|
||||
].data;
|
||||
this.$badgeLines.text(records.length);
|
||||
|
@ -116,7 +114,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
}
|
||||
let prices = [];
|
||||
const field_map = this.options.field_map;
|
||||
const records = this.parent_controller.model.get(this.state.id).data[
|
||||
const records = this.parent_controller.model.get(this.record.id).data[
|
||||
this.name
|
||||
].data;
|
||||
if (this.options.show_discount) {
|
||||
|
@ -145,7 +143,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
total,
|
||||
this.value.fields[this.options.field_map.price_unit],
|
||||
this.options.currency_field,
|
||||
this.state.data
|
||||
this.record.data
|
||||
);
|
||||
this.$totalZone.find(".total_price").html(total || 0.0);
|
||||
},
|
||||
|
@ -161,12 +159,13 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
domain: this.record.getDomain(this.recordParams),
|
||||
field: this.field,
|
||||
parentID: this.value.id,
|
||||
state: this.state,
|
||||
record: this.record,
|
||||
model: this.parent_controller.model,
|
||||
fieldName: this.name,
|
||||
recordData: this.recordData,
|
||||
value: this.value,
|
||||
relation_field: this.state.fields[this.name].relation_field,
|
||||
relation_field: this.record.fields[this.name].relation_field,
|
||||
current_batch_id: this._currentSearchBatchID,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -252,21 +251,30 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
*/
|
||||
_render: function() {
|
||||
const def = this._super.apply(this, arguments);
|
||||
if (def) {
|
||||
this.renderer.updateSearchGroup(this._activeSearchGroup);
|
||||
|
||||
// Parent implementation can return 'undefined' :(
|
||||
return (
|
||||
def &&
|
||||
def.then(() => {
|
||||
if (
|
||||
!this.$el.hasClass("oe_field_one2many_product_picker_maximized")
|
||||
) {
|
||||
this.$el.addClass("position-relative d-flex flex-column");
|
||||
}
|
||||
// Check maximize state
|
||||
if (!this.$el.hasClass("oe_field_one2many_product_picker_maximized")) {
|
||||
this.$el.addClass("position-relative d-flex flex-column");
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
this._getSearchRecords()
|
||||
.then(records => {
|
||||
return this.renderer.appendSearchRecords(records, {
|
||||
cleanup: false,
|
||||
});
|
||||
})
|
||||
.then(() => resolve());
|
||||
}).then(() => {
|
||||
if (this.options.show_subtotal) {
|
||||
this._addTotalsZone();
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return def;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -274,10 +282,14 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
*/
|
||||
doRenderSearchRecords: function() {
|
||||
return new Promise(resolve => {
|
||||
this._getSearchRecords().then(() => {
|
||||
this.renderer.$el.scrollTop(0);
|
||||
this.renderer._renderView().then(() => resolve());
|
||||
});
|
||||
this._getSearchRecords()
|
||||
.then(records => {
|
||||
this.renderer.$el.scrollTop(0);
|
||||
return this.renderer.appendSearchRecords(records, {
|
||||
cleanup: true,
|
||||
});
|
||||
})
|
||||
.then(() => resolve());
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -311,7 +323,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
* @param {Boolean} merge
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_getSearchRecords: function(options, merge) {
|
||||
_getSearchRecords: function(options) {
|
||||
const arch = this.view.arch;
|
||||
const search_mode = this.options.search[this._searchMode];
|
||||
const field_name = this.options.field_map.product;
|
||||
|
@ -367,24 +379,8 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
}
|
||||
|
||||
task.then(results => {
|
||||
if (merge) {
|
||||
this._searchRecords = _.union(
|
||||
this._searchRecords || [],
|
||||
results
|
||||
);
|
||||
} else {
|
||||
this._searchRecords = results;
|
||||
}
|
||||
this._lastSearchRecordsCount = results.length;
|
||||
this._searchOffset = offset + limit;
|
||||
if (this.renderer) {
|
||||
this.renderer.updateSearchData(
|
||||
this._searchRecords,
|
||||
this._lastSearchRecordsCount,
|
||||
this._activeSearchGroup
|
||||
);
|
||||
}
|
||||
|
||||
this.renderer.showLoadMore(limit && results.length === limit);
|
||||
resolve(results);
|
||||
});
|
||||
});
|
||||
|
@ -531,9 +527,11 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
price_unit: "price_unit",
|
||||
discount: "discount",
|
||||
},
|
||||
trigger_refresh_fields: ["partner_id", "currency_id"],
|
||||
auto_save: false,
|
||||
ignore_warning: false,
|
||||
all_domain: [],
|
||||
instant_search: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -588,12 +586,17 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
return [];
|
||||
}
|
||||
const field_name = this.options.field_map.product;
|
||||
const lines = this.parent_controller.model.get(this.state.id).data[
|
||||
const lines = this.parent_controller.model.get(this.record.id).data[
|
||||
this.name
|
||||
].data;
|
||||
const ids = _.map(lines, line => {
|
||||
return line.data[field_name].data.id;
|
||||
});
|
||||
// Here only get lines with product_id assigned
|
||||
// This happens beacuse sale.order has lines for sections/comments
|
||||
const ids = _.chain(lines)
|
||||
.filter(line => line.data[field_name] !== false)
|
||||
.map(line => {
|
||||
return line.data[field_name].data.id;
|
||||
})
|
||||
.value();
|
||||
return [["id", "in", ids]];
|
||||
},
|
||||
|
||||
|
@ -614,6 +617,9 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
this._searchContext.order = [{name: "sequence"}, {name: "id"}];
|
||||
this._searchContext.activeTest = false;
|
||||
}
|
||||
if (this.renderer) {
|
||||
this.renderer.updateSearchGroup(this._activeSearchGroup);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -621,6 +627,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
* that the search results. Use directy in-memory values.
|
||||
*/
|
||||
showLines: function() {
|
||||
this.renderer.clearRecords();
|
||||
this._updateSearchContext(-1);
|
||||
this._clearSearchInput();
|
||||
this.$btnLines
|
||||
|
@ -635,6 +642,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
* @param {Number} group_id
|
||||
*/
|
||||
showGroup: function(group_id) {
|
||||
this.renderer.clearRecords();
|
||||
this._updateSearchContext(group_id);
|
||||
this.doRenderSearchRecords();
|
||||
this.$btnLines.removeClass("active");
|
||||
|
@ -668,6 +676,15 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
this.doRenderSearchRecords();
|
||||
},
|
||||
|
||||
_onInputSearch: function(evt) {
|
||||
if (!this.options.instant_search) {
|
||||
return;
|
||||
}
|
||||
this._searchContext.text = evt.target.value;
|
||||
this._lazyRenderSearchRecords();
|
||||
// This.doRenderSearchRecords()
|
||||
},
|
||||
|
||||
/**
|
||||
* Auto select all content when user enters into fields with this
|
||||
* widget.
|
||||
|
@ -707,7 +724,8 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
*/
|
||||
_onCreateQuickRecord: function(evt) {
|
||||
evt.stopPropagation();
|
||||
this.parent_controller.model.setPureVirtual(evt.data.id, false);
|
||||
var model = this.parent_controller.model;
|
||||
model.setPureVirtual(evt.data.id, false);
|
||||
|
||||
if (this.options.auto_save) {
|
||||
// Dont trigger state update
|
||||
|
@ -718,16 +736,15 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
this.parent_controller
|
||||
.saveRecord(undefined, {stayInEdit: true})
|
||||
.then(() => {
|
||||
// Because 'create' generates a new state and we can't know these new id we
|
||||
// need force update all the current states.
|
||||
this._setValue(
|
||||
{operation: "UPDATE", id: evt.data.id},
|
||||
{doNotSetDirty: true}
|
||||
).then(() => {
|
||||
if (evt.data.callback) {
|
||||
evt.data.callback();
|
||||
}
|
||||
});
|
||||
self.renderer.updateState(
|
||||
model.get(self.parent_controller.handle).data[
|
||||
self.name
|
||||
],
|
||||
{force: true}
|
||||
);
|
||||
if (evt.data.callback) {
|
||||
evt.data.callback();
|
||||
}
|
||||
});
|
||||
if (evt.data.callback) {
|
||||
evt.data.callback();
|
||||
|
@ -758,21 +775,15 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
self.parent_controller
|
||||
.saveRecord(undefined, {stayInEdit: true})
|
||||
.then(function() {
|
||||
// Workaround to get updated values
|
||||
self.parent_controller.model
|
||||
.reload(self.value.id)
|
||||
.then(function(result) {
|
||||
var new_data = self.parent_controller.model.get(
|
||||
result
|
||||
);
|
||||
self.value.data = new_data.data;
|
||||
self.renderer.updateState(self.value, {
|
||||
force: true,
|
||||
});
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
self.renderer.updateState(
|
||||
self.parent_controller.model.get(
|
||||
self.parent_controller.handle
|
||||
).data[self.name],
|
||||
{force: true}
|
||||
);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
if (callback) {
|
||||
callback();
|
||||
|
@ -844,12 +855,9 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
if (this._isLoading) {
|
||||
return;
|
||||
}
|
||||
this._getSearchRecords(
|
||||
{
|
||||
offset: this._searchOffset,
|
||||
},
|
||||
true
|
||||
).then(records => {
|
||||
this._getSearchRecords({
|
||||
offset: this._searchOffset,
|
||||
}).then(records => {
|
||||
this.renderer.appendSearchRecords(records);
|
||||
});
|
||||
},
|
||||
|
@ -872,7 +880,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
|||
*/
|
||||
_blockControlPanel: function(block) {
|
||||
if (this.$buttons) {
|
||||
this.$buttons.find("input,button").attr("disabled", block);
|
||||
this.$buttons.find("button").attr("disabled", block);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -83,11 +83,6 @@
|
|||
.oe_flip_card_inner {
|
||||
height: 100% !important;
|
||||
box-shadow: 0px 0px 15px;
|
||||
.img-fluid {
|
||||
transform: translateY(-50%) !important;
|
||||
top: 50%;
|
||||
position: relative;
|
||||
}
|
||||
.oe_one2many_product_picker_title {
|
||||
font-size: 1.95rem !important;
|
||||
}
|
||||
|
@ -142,6 +137,14 @@
|
|||
$one2many-product-picker-transition-3d-time/2;
|
||||
transform-style: preserve-3d;
|
||||
|
||||
.img-fluid {
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.position-absolute {
|
||||
z-index: 1;
|
||||
}
|
||||
|
@ -150,8 +153,19 @@
|
|||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.indicator_zones {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
max-width: 50%;
|
||||
align-items: flex-start;
|
||||
|
||||
> span.badge {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.badge_price {
|
||||
top: 50%;
|
||||
top: 55%;
|
||||
right: -2px;
|
||||
transform: translateY(-50%);
|
||||
display: grid;
|
||||
|
@ -190,8 +204,17 @@
|
|||
.o_form_view.o_form_nosheet {
|
||||
padding: $one2many-product-picker-card-form-padding;
|
||||
|
||||
.o_field_widget .o_input_dropdown > input {
|
||||
height: unset;
|
||||
.o_field_widget {
|
||||
&:not(.widget_numeric_step) {
|
||||
max-width: 95%;
|
||||
}
|
||||
|
||||
.o_input_dropdown > input {
|
||||
height: unset;
|
||||
}
|
||||
}
|
||||
.btn.w-100 {
|
||||
max-width: 95%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -211,6 +234,16 @@
|
|||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
|
||||
.oe_one2many_product_picker_form_buttons {
|
||||
display: flex;
|
||||
padding: 0 3px;
|
||||
justify-content: center;
|
||||
|
||||
.oe_record_remove {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,101 @@
|
|||
>Load More</button>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="One2ManyProductPicker.ActionButton">
|
||||
<div class="safezone d-inline-block float-left m-0 pb-2 pr-2 text-left">
|
||||
<t t-if="is_saving && lazy_qty > 0">
|
||||
<span
|
||||
class="badge record_saving badge-warning font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
|
||||
><span class="lazy_product_qty" t-esc="lazy_qty || '1'" /> x <t
|
||||
t-esc="state.data[field_map[field_uom]].data.display_name"
|
||||
/></span>
|
||||
</t>
|
||||
<t t-elif="!is_virtual">
|
||||
<span
|
||||
t-att-data-field="field_map[field_uom_qty]"
|
||||
t-attf-data-esc="str({{field_map[field_uom_qty]}}) + ' x ' + {{field_map[field_uom]}}.data.display_name"
|
||||
t-attf-class="badge {{modified && 'badge-warning' || 'badge-success'}} font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
|
||||
/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span
|
||||
class="badge badge-primary font-weight-bold rounded-0 mt-0 px-2 py-3 add_product"
|
||||
><i class="fa fa-plus" /> 1 <t
|
||||
t-esc="state.data[field_map[field_uom]].data.display_name"
|
||||
/></span>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="One2ManyProductPicker.PriceZone">
|
||||
<div class="position-absolute m-0 text-left badge_price">
|
||||
<t t-if="show_discount">
|
||||
<span
|
||||
t-att-data-field="field_map.discount"
|
||||
t-attf-data-esc="str({{field_map.discount}} * -1.0) +'%'"
|
||||
class="badge badge-dark discount_price font-weight-bold rounded-0 mt-1 p-2"
|
||||
/>
|
||||
<span
|
||||
t-att-data-field="field_map.price_unit"
|
||||
t-attf-data-esc="'{{monetary('price_unit',true)}}'"
|
||||
class="badge font-weight-bold rounded-0 original_price"
|
||||
/>
|
||||
<span
|
||||
t-if="has_onchange"
|
||||
class="badge badge-info price_unit font-weight-bold rounded-0 mt-1 p-2"
|
||||
/>
|
||||
</t>
|
||||
<t t-else="has_onchange">
|
||||
<span
|
||||
t-att-data-field="field_map.price_unit"
|
||||
t-attf-data-esc="'{{monetary('price_unit',true)}}'"
|
||||
class="badge badge-info price_unit font-weight-bold rounded-0 mt-1 p-2"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="One2ManyProductPicker.FlipCard.Front">
|
||||
<div
|
||||
t-attf-class="oe_flip_card_front p-0 {{((modified || is_saving) && 'border-warning') || (state && !is_virtual && 'border-success') || ''}}"
|
||||
>
|
||||
<t t-if="state">
|
||||
<div class="indicator_zones float-left">
|
||||
<t t-call="One2ManyProductPicker.ActionButton" />
|
||||
<t t-call="One2ManyProductPicker.PriceZone" />
|
||||
</div>
|
||||
<span
|
||||
data-field="display_name"
|
||||
class="oe_one2many_product_picker_title position-absolute fixed-bottom p-1"
|
||||
data-esc="display_name"
|
||||
/>
|
||||
<img
|
||||
alt=""
|
||||
class="img img-fluid"
|
||||
t-att-src="image(state.data[field_map.product].data.id,'image_128')"
|
||||
t-att-data-src-alt="image(state.data[field_map.product].data.id,'image_1024')"
|
||||
/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span
|
||||
class="oe_one2many_product_picker_title position-absolute fixed-bottom p-1"
|
||||
t-esc="record_search.display_name"
|
||||
/>
|
||||
<img
|
||||
alt=""
|
||||
class="img img-fluid"
|
||||
t-att-src="image(record_search.id,'image_128')"
|
||||
t-att-data-src-alt="image(record_search.id,'image_1024')"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="One2ManyProductPicker.FlipCard.Back">
|
||||
<div class="oe_flip_card_back">
|
||||
<widget
|
||||
name="product_picker_quick_create_form"
|
||||
t-att-compare-key="field_map.product_uom"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="One2ManyProductPicker.FlipCard">
|
||||
<div
|
||||
class="oe_flip_container p-1 col-12 col-sm-8 col-md-6 col-lg-4 col-xl-3 col-xxl-2"
|
||||
|
@ -95,101 +190,8 @@
|
|||
t-attf-class="oe_flip_card {{!state && 'disabled' || (auto_save && (!is_virtual || is_saving) && !state.data.id && 'blocked') || ''}}"
|
||||
>
|
||||
<div class="oe_flip_card_inner text-center">
|
||||
<div
|
||||
t-attf-class="oe_flip_card_front p-0 {{((modified || is_saving) && 'border-warning') || (state && !is_virtual && 'border-success') || ''}}"
|
||||
>
|
||||
<t t-if="state">
|
||||
<t t-if="is_saving && lazy_qty > 0">
|
||||
<div
|
||||
class="safezone position-absolute m-0 pb-2 pr-2 text-left"
|
||||
>
|
||||
<span
|
||||
class="badge record_saving badge-warning font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
|
||||
><span
|
||||
class="lazy_product_qty"
|
||||
t-esc="lazy_qty || '1'"
|
||||
/> x <t
|
||||
t-esc="state.data[field_map.product_uom].data.display_name"
|
||||
/></span>
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="!is_virtual">
|
||||
<div
|
||||
class="safezone position-absolute m-0 pb-2 pr-2 text-left"
|
||||
>
|
||||
<span
|
||||
t-att-data-field="field_map.product_uom_qty"
|
||||
t-attf-data-esc="str({{field_map.product_uom_qty}}) + ' x ' + {{field_map.product_uom}}.data.display_name"
|
||||
t-attf-class="badge {{modified && 'badge-warning' || 'badge-success'}} font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div
|
||||
class="safezone position-absolute m-0 pb-2 pr-2 text-left"
|
||||
>
|
||||
<span
|
||||
class="badge badge-primary font-weight-bold rounded-0 mt-0 px-2 py-3 add_product"
|
||||
><i class="fa fa-plus" /> 1 <t
|
||||
t-esc="state.data[field_map.product_uom].data.display_name"
|
||||
/></span>
|
||||
</div>
|
||||
</t>
|
||||
<div class="position-absolute m-0 text-left badge_price">
|
||||
<t t-if="show_discount">
|
||||
<span
|
||||
t-att-data-field="field_map.discount"
|
||||
t-attf-data-esc="'-' + str({{field_map.discount}}) +'%'"
|
||||
class="badge badge-dark discount_price font-weight-bold rounded-0 mt-1 p-2"
|
||||
/>
|
||||
<span
|
||||
t-att-data-field="field_map.price_unit"
|
||||
t-attf-data-esc="'{{monetary('price_unit',true)}}'"
|
||||
class="badge font-weight-bold rounded-0 original_price"
|
||||
/>
|
||||
<span
|
||||
class="badge badge-info price_unit font-weight-bold rounded-0 mt-1 p-2"
|
||||
/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span
|
||||
t-att-data-field="field_map.price_unit"
|
||||
t-attf-data-esc="'{{monetary('price_unit',true)}}'"
|
||||
class="badge badge-info price_unit font-weight-bold rounded-0 mt-1 p-2"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
<span
|
||||
data-field="display_name"
|
||||
class="oe_one2many_product_picker_title position-absolute fixed-bottom p-1"
|
||||
data-esc="display_name"
|
||||
/>
|
||||
<img
|
||||
alt=""
|
||||
class="img img-fluid"
|
||||
t-att-src="image(state.data[field_map.product].data.id,'image_512')"
|
||||
t-att-data-src-alt="image(state.data[field_map.product].data.id,'image_1024')"
|
||||
/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span
|
||||
class="oe_one2many_product_picker_title position-absolute fixed-bottom p-1"
|
||||
t-esc="record_search.display_name"
|
||||
/>
|
||||
<img
|
||||
alt=""
|
||||
class="img img-fluid"
|
||||
t-att-src="image(record_search.id,'image_512')"
|
||||
t-att-data-src-alt="image(record_search.id,'image_1024')"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="oe_flip_card_back">
|
||||
<widget
|
||||
name="product_picker_quick_create_form"
|
||||
t-att-compare-key="field_map.product_uom"
|
||||
/>
|
||||
</div>
|
||||
<t t-call="One2ManyProductPicker.FlipCard.Front" />
|
||||
<t t-call="One2ManyProductPicker.FlipCard.Back" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
<button t-attf-class="btn btn-primary oe_record_add">Add</button>
|
||||
</t>
|
||||
<t t-elif="state == 'dirty'">
|
||||
<button class="btn btn-success oe_record_change mr-2">
|
||||
<button class="btn btn-success oe_record_change w-100">
|
||||
<i class="fa fa-check" />
|
||||
</button>
|
||||
<button class="btn btn-warning oe_record_discard ml-2">
|
||||
<button class="btn btn-warning oe_record_discard ml-1">
|
||||
<i class="fa fa-times" />
|
||||
</button>
|
||||
</t>
|
||||
|
@ -16,6 +16,9 @@
|
|||
<button class="btn btn-danger oe_record_remove w-100"><i
|
||||
class="fa fa-trash"
|
||||
/> Remove</button>
|
||||
<button class="btn btn-warning oe_record_discard ml-1">
|
||||
<i class="fa fa-times" />
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
|
|
@ -61,6 +61,10 @@
|
|||
type="text/javascript"
|
||||
src="/web_widget_one2many_product_picker/static/src/js/views/basic_model.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/web_widget_one2many_product_picker/static/src/js/views/basic_controller.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/web_widget_one2many_product_picker/static/src/js/widgets/field_one2many_product_picker.js"
|
||||
|
|
Loading…
Reference in New Issue