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.
|
modify/create a record with the widget.
|
||||||
|
|
||||||
* ignore_warning > Enable/Disable display onchange warnings (False by default)
|
* 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.
|
All widget options are optional.
|
||||||
Notice that you can call '_' method to use translations. This only can be used with this widget.
|
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.
|
modify/create a record with the widget.
|
||||||
|
|
||||||
* ignore_warning > Enable/Disable display onchange warnings (False by default)
|
* 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.
|
All widget options are optional.
|
||||||
Notice that you can call '_' method to use translations. This only can be used with this widget.
|
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>
|
||||||
<li><p class="first">ignore_warning > Enable/Disable display onchange warnings (False by default)</p>
|
<li><p class="first">ignore_warning > Enable/Disable display onchange warnings (False by default)</p>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
<p>All widget options are optional.
|
<p>All widget options are optional.
|
||||||
Notice that you can call ‘_’ method to use translations. This only can be used with this widget.</p>
|
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);
|
this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_applyChanges: function() {
|
||||||
|
return this._super.apply(this, arguments).then(() => {
|
||||||
|
this._updateButtons();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create or accept changes
|
* Create or accept changes
|
||||||
*/
|
*/
|
||||||
|
@ -227,7 +236,6 @@ odoo.define(
|
||||||
this.trigger_up("quick_record_updated", {
|
this.trigger_up("quick_record_updated", {
|
||||||
changes: ev.data.changes,
|
changes: ev.data.changes,
|
||||||
});
|
});
|
||||||
this._updateButtons();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -261,7 +269,6 @@ odoo.define(
|
||||||
saving: false,
|
saving: false,
|
||||||
});
|
});
|
||||||
this.model.unsetDirty(this.handle);
|
this.model.unsetDirty(this.handle);
|
||||||
// Self._updateButtons();
|
|
||||||
this._enableQuickCreate();
|
this._enableQuickCreate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -298,14 +305,26 @@ odoo.define(
|
||||||
|
|
||||||
this.trigger_up("restore_flip_card", {
|
this.trigger_up("restore_flip_card", {
|
||||||
success_callback: function() {
|
success_callback: function() {
|
||||||
|
// 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", {
|
self.trigger_up("update_quick_record", {
|
||||||
id: record.id,
|
id: record.id,
|
||||||
callback: function() {
|
callback: function() {
|
||||||
self.model.unsetDirty(self.handle);
|
self.model.unsetDirty(self.handle);
|
||||||
// Self._updateButtons();
|
|
||||||
self._enableQuickCreate();
|
self._enableQuickCreate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
block: true,
|
block: true,
|
||||||
});
|
});
|
||||||
|
@ -314,29 +333,30 @@ odoo.define(
|
||||||
_discard: function() {
|
_discard: function() {
|
||||||
if (this._disabled) {
|
if (this._disabled) {
|
||||||
// Don't do anything if we are already creating a record
|
// Don't do anything if we are already creating a record
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._disableQuickCreate();
|
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, {
|
this.model.discardChanges(this.handle, {
|
||||||
rollback: true,
|
rollback: true,
|
||||||
});
|
});
|
||||||
|
const record = this.model.get(this.handle);
|
||||||
this.trigger_up("quick_record_updated", {
|
this.trigger_up("quick_record_updated", {
|
||||||
changes: record.data,
|
changes: record.data,
|
||||||
});
|
});
|
||||||
if (this.model.isNew(record.id)) {
|
|
||||||
this.update({}, {reload: false});
|
|
||||||
this.trigger_up("restore_flip_card");
|
|
||||||
this._updateButtons();
|
|
||||||
this._enableQuickCreate();
|
|
||||||
} else {
|
|
||||||
this.update({}, {reload: false}).then(() => {
|
this.update({}, {reload: false}).then(() => {
|
||||||
|
if (!this.model.isNew(record.id)) {
|
||||||
this.model.unsetDirty(this.handle);
|
this.model.unsetDirty(this.handle);
|
||||||
|
}
|
||||||
this.trigger_up("restore_flip_card");
|
this.trigger_up("restore_flip_card");
|
||||||
this._updateButtons();
|
this._updateButtons();
|
||||||
this._enableQuickCreate();
|
this._enableQuickCreate();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -42,7 +42,6 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
||||||
front: [],
|
front: [],
|
||||||
back: [],
|
back: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
this._lazyUpdateRecord = _.debounce(this._updateRecord.bind(this), 450);
|
this._lazyUpdateRecord = _.debounce(this._updateRecord.bind(this), 450);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -51,8 +50,10 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
||||||
*
|
*
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
generateVirtualState: function() {
|
generateVirtualState: function(simple_mode) {
|
||||||
return this._generateVirtualState().then(this.recreate.bind(this));
|
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
|
* @override
|
||||||
*/
|
*/
|
||||||
destroy: function() {
|
destroy: function() {
|
||||||
|
this.abortTimeouts();
|
||||||
|
if (this.state) {
|
||||||
|
this.options.basicFieldParams.model.removeVirtualRecord(this.state.id);
|
||||||
|
}
|
||||||
this.$el.remove();
|
this.$el.remove();
|
||||||
this.$card.off("");
|
this.$card.off("");
|
||||||
this._super.apply(this, arguments);
|
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
|
* @override
|
||||||
*/
|
*/
|
||||||
|
@ -105,6 +121,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
recreate: function(state) {
|
recreate: function(state) {
|
||||||
|
if (!this.getParent()) {
|
||||||
|
// It's a zombie record! ensure kill it!
|
||||||
|
this.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
this._setState(state);
|
this._setState(state);
|
||||||
}
|
}
|
||||||
|
@ -121,6 +143,15 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
||||||
return this._render();
|
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
|
* 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,
|
price,
|
||||||
this.state.fields[field_name],
|
this.state.fields[field_name],
|
||||||
this.options.currencyField,
|
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.fields = this.getParent().state.fields;
|
||||||
this.fieldsInfo = this.getParent().state.fieldsInfo.form;
|
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;
|
this.state = viewState;
|
||||||
|
|
||||||
if (recordSearch) {
|
if (recordSearch) {
|
||||||
this.recordSearch = recordSearch;
|
this.recordSearch = recordSearch;
|
||||||
}
|
}
|
||||||
const model = this.options.basicFieldParams.model;
|
|
||||||
this.is_virtual =
|
this.is_virtual =
|
||||||
(this.state && model.isPureVirtual(this.state.id)) || false;
|
(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._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,
|
auto_save: this.options.autoSave,
|
||||||
is_saving: record && record.context.saving,
|
is_saving: record && record.context.saving,
|
||||||
lazy_qty: record && record.context.lazy_qty,
|
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() {
|
_getInternalVirtualRecordContext: function() {
|
||||||
const context = {};
|
const context = {};
|
||||||
context["default_" + this.options.basicFieldParams.relation_field] =
|
context[`default_${this.options.basicFieldParams.relation_field}`] =
|
||||||
this.options.basicFieldParams.state.id || null;
|
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;
|
return context;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forced data used in virtual states.
|
* 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
|
* @private
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
_getInternalVirtualRecordData: function() {
|
_getInternalVirtualRecordData: function() {
|
||||||
const data = {};
|
// To be overwritten
|
||||||
data[this.options.fieldMap.product] = {
|
return {};
|
||||||
operation: "ADD",
|
},
|
||||||
id: this.recordSearch.id,
|
|
||||||
};
|
_generateVirtualStateSimple: function(context, def_values) {
|
||||||
return data;
|
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
|
* @param {Object} context
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
_generateVirtualState: function(data, context) {
|
_generateVirtualState: function(data, context, simple_mode) {
|
||||||
const model = this.options.basicFieldParams.model;
|
|
||||||
const scontext = _.extend(
|
const scontext = _.extend(
|
||||||
{},
|
{},
|
||||||
this._getInternalVirtualRecordContext(),
|
this._getInternalVirtualRecordContext(),
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
// Force qty to 1.0 to launch correct onchanges
|
// Apply default values
|
||||||
scontext[`default_${this.options.fieldMap.product_uom_qty}`] = 1.0;
|
const def_values = {
|
||||||
const sdata = _.extend({}, this._getInternalVirtualRecordData(), data);
|
[this.options.fieldMap.product]: this.recordSearch.id,
|
||||||
return model.createVirtualRecord(this.options.basicFieldParams.value.id, {
|
[this.options.fieldMap[this.master_uom_map.field_uom_qty]]: 1.0,
|
||||||
data: sdata,
|
[this.options.fieldMap[this.master_uom_map.field_uom]]: this
|
||||||
context: scontext,
|
.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) => {
|
this.$el.find(to_find.join()).each((key, value) => {
|
||||||
const $elm = $(value);
|
const $elm = $(value);
|
||||||
const format_out = $elm.data("esc") || $elm.data("field");
|
const format_out = $elm.data("esc") || $elm.data("field");
|
||||||
$elm.html(
|
const text_out = py.eval(
|
||||||
py.eval(format_out, _.extend({}, state_data, this.recordSearch))
|
format_out,
|
||||||
|
_.extend({}, state_data, this.recordSearch)
|
||||||
);
|
);
|
||||||
|
$elm.html(text_out);
|
||||||
|
$elm.attr("title", text_out);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.options.showDiscount) {
|
if (this.options.showDiscount) {
|
||||||
const field_map = this.options.fieldMap;
|
const field_map = this.options.fieldMap;
|
||||||
if (state_data) {
|
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
|
this.$el
|
||||||
.find(".original_price,.discount_price")
|
.find(".original_price,.discount_price")
|
||||||
.toggleClass("d-none", !has_discount);
|
.toggleClass("d-none", !has_discount);
|
||||||
|
@ -526,7 +722,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
||||||
price_reduce,
|
price_reduce,
|
||||||
this.state.fields[field_map.price_unit],
|
this.state.fields[field_map.price_unit],
|
||||||
this.options.currencyField,
|
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) {
|
if (record.context.saving) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
const changes = _.pick(record.data, this.options.fieldMap.product_uom_qty);
|
const changes = _.pick(
|
||||||
if (changes[this.options.fieldMap.product_uom_qty] === 0) {
|
record.data,
|
||||||
changes[this.options.fieldMap.product_uom_qty] = 1;
|
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");
|
this.$card.addClass("blocked");
|
||||||
return model.notifyChanges(record.id, changes).then(() => {
|
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.
|
// wait for the Odoo response.
|
||||||
const model_record_data = model.localData[this.state.id].data;
|
const model_record_data = model.localData[this.state.id].data;
|
||||||
if (
|
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
|
return model
|
||||||
.notifyChanges(record.id, {
|
.notifyChanges(record.id, {
|
||||||
[this.options.fieldMap.product_uom_qty]:
|
[this.options.fieldMap[this.master_uom_map.field_uom_qty]]:
|
||||||
model_record_data[this.options.fieldMap.product_uom_qty],
|
model_record_data[
|
||||||
|
this.options.fieldMap[this.master_uom_map.field_uom_qty]
|
||||||
|
],
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._processDynamicFields();
|
this._processDynamicFields();
|
||||||
|
@ -836,6 +1047,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
||||||
_onRestoreFlipCard: function(evt) {
|
_onRestoreFlipCard: function(evt) {
|
||||||
this.$card.removeClass("active");
|
this.$card.removeClass("active");
|
||||||
this.$front.removeClass("d-none");
|
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")) {
|
if (this.$card.hasClass("oe_flip_card_maximized")) {
|
||||||
this.$card.removeClass("oe_flip_card_maximized");
|
this.$card.removeClass("oe_flip_card_maximized");
|
||||||
this.$card.on("transitionend", () => {
|
this.$card.on("transitionend", () => {
|
||||||
|
@ -859,6 +1072,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
|
||||||
if (evt.data.block) {
|
if (evt.data.block) {
|
||||||
this.$card.addClass("blocked");
|
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) {
|
_onQuickRecordUpdated: function(evt) {
|
||||||
this._processDynamicFields(Object.keys(evt.data.changes));
|
this._processDynamicFields(Object.keys(evt.data.changes));
|
||||||
|
// This.recreate();
|
||||||
this.trigger_up("update_subtotal");
|
this.trigger_up("update_subtotal");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,9 +40,7 @@ odoo.define(
|
||||||
// 'receive' more arguments.
|
// 'receive' more arguments.
|
||||||
this.options = parent.options;
|
this.options = parent.options;
|
||||||
this.mode = parent.mode;
|
this.mode = parent.mode;
|
||||||
this.search_data = parent._searchRecords;
|
|
||||||
this.search_group = parent._activeSearchGroup;
|
this.search_group = parent._activeSearchGroup;
|
||||||
this.last_search_data_count = parent._lastSearchRecordsCount;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,7 +48,7 @@ odoo.define(
|
||||||
*/
|
*/
|
||||||
on_attach_callback: function() {
|
on_attach_callback: function() {
|
||||||
this._isInDom = true;
|
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() {
|
on_detach_callback: function() {
|
||||||
this._isInDom = false;
|
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
|
* @param {Object} search_group
|
||||||
*/
|
*/
|
||||||
updateSearchData: function(search_data, count, search_group) {
|
updateSearchGroup: function(search_group) {
|
||||||
this.search_data = search_data;
|
|
||||||
this.last_search_data_count = count;
|
|
||||||
this.search_group = search_group;
|
this.search_group = search_group;
|
||||||
this._loadMoreWorking = false;
|
|
||||||
this.$btnLoadMore.attr("disabled", false);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -115,20 +107,11 @@ odoo.define(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_isValidLineState: function(state) {
|
||||||
* Recreate the given widget by the state id
|
return (
|
||||||
*
|
state.data[this.options.field_map.product] &&
|
||||||
* @param {String} state_id
|
state.data[this.options.field_map.product].data.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_isEqualState: function(state_a, state_b) {
|
_isEqualState: function(state_a, state_b) {
|
||||||
|
@ -137,9 +120,31 @@ odoo.define(
|
||||||
}
|
}
|
||||||
const product_id_a =
|
const product_id_a =
|
||||||
state_a.data[this.options.field_map.product].data.id;
|
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 =
|
const product_id_b =
|
||||||
state_b.data[this.options.field_map.product].data.id;
|
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}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
_processStatesToDestroy: function(states) {
|
_processStatesToDestroy: function(states) {
|
||||||
|
// Get widgets to destroy
|
||||||
|
// Update states only affect to "non pure virtual" records
|
||||||
const to_destroy = [];
|
const to_destroy = [];
|
||||||
|
const to_add = [];
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
for (let e = this.widgets.length - 1; e >= 0; --e) {
|
for (let e = this.widgets.length - 1; e >= 0; --e) {
|
||||||
const widget = this.widgets[e];
|
const widget = this.widgets[e];
|
||||||
if (widget && this._isEqualState(widget.state, state)) {
|
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
|
// 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) {
|
let recreated = false;
|
||||||
const widget = this.widgets[eb];
|
if (!this._existsWidgetWithState(widget.state)) {
|
||||||
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
|
// Get the new state ID if exists to link it with the new record
|
||||||
let state_id = null;
|
// This happens when remove a record that have a new state info
|
||||||
for (let eb = this.state.data.length - 1; eb >= 0; --eb) {
|
for (
|
||||||
|
let eb = this.state.data.length - 1;
|
||||||
|
eb >= 0;
|
||||||
|
--eb
|
||||||
|
) {
|
||||||
const state = this.state.data[eb];
|
const state = this.state.data[eb];
|
||||||
if (this._isEqualState(state, widget_destroyed.state)) {
|
if (!this._isValidLineState(state)) {
|
||||||
state_id = state.id;
|
continue;
|
||||||
|
}
|
||||||
|
if (this._isEqualState(state, widget.state)) {
|
||||||
|
widget.recreate(state);
|
||||||
|
recreated = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// "Lines" section doesn't show virtual records
|
}
|
||||||
if (
|
if (!recreated) {
|
||||||
(state_id && this.search_group.name === "main_lines") ||
|
widget.markToDestroy();
|
||||||
this.search_group.name !== "main_lines"
|
to_destroy.push(widget);
|
||||||
) {
|
const search_record = _.omit(
|
||||||
const widget_product_id =
|
widget.recordSearch,
|
||||||
widget_destroyed.state.data[
|
"__id"
|
||||||
this.options.field_map.product
|
);
|
||||||
].data.id;
|
|
||||||
const search_record = _.find(this.search_data, {
|
to_add.push([
|
||||||
id: widget_product_id,
|
[search_record],
|
||||||
});
|
{
|
||||||
const new_search_record = _.extend({}, search_record, {
|
no_attach_widgets: false,
|
||||||
__id: state_id,
|
no_process_records: false,
|
||||||
});
|
position: widget.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() {
|
_processCurrentStates: function() {
|
||||||
// Records to Update or Create
|
// Records to Update or Create
|
||||||
|
const model = this.getParent().getBasicFieldParams().model;
|
||||||
const to_destroy = [];
|
const to_destroy = [];
|
||||||
const to_add = [];
|
const to_add = [];
|
||||||
for (const index in this.state.data) {
|
for (const index in this.state.data) {
|
||||||
const state = this.state.data[index];
|
const state = this.state.data[index];
|
||||||
|
if (!this._isValidLineState(state)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let exists = false;
|
let exists = false;
|
||||||
let search_record_index = -1;
|
let search_record_index = false;
|
||||||
let search_record = false;
|
let search_record = false;
|
||||||
for (let e = this.widgets.length - 1; e >= 0; --e) {
|
for (let e = this.widgets.length - 1; e >= 0; --e) {
|
||||||
const widget = this.widgets[e];
|
const widget = this.widgets[e];
|
||||||
|
@ -238,24 +236,28 @@ odoo.define(
|
||||||
// Already processed widget (deleted)
|
// Already processed widget (deleted)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (this._isEqualState(widget.state, state)) {
|
|
||||||
var model = this.getParent().getBasicFieldParams().model;
|
const is_equal_state = this._isEqualState(widget.state, state);
|
||||||
var record = model.get(widget.state.id);
|
if (widget.isMarkedToDestroy()) {
|
||||||
|
exists = true;
|
||||||
|
} else if (is_equal_state) {
|
||||||
|
const record = model.get(widget.state.id);
|
||||||
model.updateRecordContext(state.id, {
|
model.updateRecordContext(state.id, {
|
||||||
lazy_qty: record.context.lazy_qty || 0,
|
lazy_qty: record.context.lazy_qty || 0,
|
||||||
});
|
});
|
||||||
widget.recreate(state);
|
widget.recreate(state);
|
||||||
exists = true;
|
exists = true;
|
||||||
break;
|
break;
|
||||||
} else if (
|
}
|
||||||
|
if (
|
||||||
|
!is_equal_state &&
|
||||||
widget.recordSearch.id ===
|
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)
|
// 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;
|
search_record = widget.recordSearch;
|
||||||
var model = this.getParent().getBasicFieldParams().model;
|
const record = model.get(widget.state.id);
|
||||||
var record = model.get(widget.state.id);
|
|
||||||
model.updateRecordContext(state.id, {
|
model.updateRecordContext(state.id, {
|
||||||
lazy_qty: record.context.lazy_qty || 0,
|
lazy_qty: record.context.lazy_qty || 0,
|
||||||
});
|
});
|
||||||
|
@ -273,16 +275,18 @@ odoo.define(
|
||||||
|
|
||||||
this.state.data = _.compact(this.state.data);
|
this.state.data = _.compact(this.state.data);
|
||||||
|
|
||||||
// Need add a new one?
|
// Add to create the new record
|
||||||
if (!exists && search_record_index !== -1) {
|
if (!exists && search_record_index) {
|
||||||
const new_search_record = _.extend({}, search_record, {
|
const new_search_record = _.extend({}, search_record, {
|
||||||
__id: state.id,
|
__id: state.id,
|
||||||
});
|
});
|
||||||
to_add.push([
|
to_add.push([
|
||||||
[new_search_record],
|
[new_search_record],
|
||||||
false,
|
{
|
||||||
true,
|
no_attach_widgets: true,
|
||||||
search_record_index,
|
no_process_records: true,
|
||||||
|
position: search_record_index,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,9 +308,15 @@ odoo.define(
|
||||||
const states_to_destroy = [];
|
const states_to_destroy = [];
|
||||||
for (const index in old_states) {
|
for (const index in old_states) {
|
||||||
const old_state = old_states[index];
|
const old_state = old_states[index];
|
||||||
|
if (!this._isValidLineState(old_state)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let found = false;
|
let found = false;
|
||||||
for (const e in this.state.data) {
|
for (const e in this.state.data) {
|
||||||
const current_state = this.state.data[e];
|
const current_state = this.state.data[e];
|
||||||
|
if (!this._isValidLineState(current_state)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (this._isEqualState(current_state, old_state)) {
|
if (this._isEqualState(current_state, old_state)) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
|
@ -323,47 +333,40 @@ odoo.define(
|
||||||
states_to_destroy
|
states_to_destroy
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make widgets to destroy invisible to avoid render 'dance'
|
|
||||||
for (const widget of to_destroy_old) {
|
|
||||||
widget.$el.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldTasks = [];
|
|
||||||
for (const params of to_add_virtual) {
|
|
||||||
oldTasks.push(this.appendSearchRecords.apply(this, params)[0]);
|
|
||||||
}
|
|
||||||
Promise.all(oldTasks).then(() => {
|
|
||||||
const [
|
const [
|
||||||
to_destroy_current,
|
destroyed_current,
|
||||||
to_add_current,
|
to_add_current,
|
||||||
] = this._processCurrentStates();
|
] = this._processCurrentStates();
|
||||||
|
|
||||||
// Make widgets to destroy invisible to avoid render 'dance'
|
const currentTasks = [];
|
||||||
for (const widget of to_destroy_current) {
|
const to_add = [].concat(to_add_current, to_add_virtual);
|
||||||
widget.$el.hide();
|
for (const params of to_add) {
|
||||||
|
currentTasks.push(this.appendSearchRecords.apply(this, params)[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTasks = [];
|
|
||||||
for (const params of to_add_current) {
|
|
||||||
currentTasks.push(
|
|
||||||
this.appendSearchRecords.apply(this, params)[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Promise.all(currentTasks).then(() => {
|
Promise.all(currentTasks).then(() => {
|
||||||
_.invoke(to_destroy_old, "destroy");
|
_.invoke(to_destroy_old, "destroy");
|
||||||
_.invoke(to_destroy_current, "destroy");
|
_.invoke(destroyed_current, "destroy");
|
||||||
|
this.widgets = _.difference(this.widgets, to_destroy_old);
|
||||||
def.resolve();
|
def.resolve();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return def;
|
return def;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearRecords: function() {
|
||||||
|
_.invoke(_.compact(this.widgets), "destroy");
|
||||||
|
this.widgets = [];
|
||||||
|
if (this.$recordsContainer) {
|
||||||
|
this.$recordsContainer.empty();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
_renderView: function() {
|
_renderView: function() {
|
||||||
const oldWidgets = _.compact(this.widgets);
|
_.invoke(_.compact(this.widgets), "destroy");
|
||||||
this.widgets = [];
|
this.widgets = [];
|
||||||
this.$recordsContainer = $("<DIV/>", {
|
this.$recordsContainer = $("<DIV/>", {
|
||||||
class: "w-100 row",
|
class: "w-100 row",
|
||||||
|
@ -374,23 +377,14 @@ odoo.define(
|
||||||
this.$btnLoadMore = this.$extraButtonsContainer.find(
|
this.$btnLoadMore = this.$extraButtonsContainer.find(
|
||||||
"#productPickerLoadMore"
|
"#productPickerLoadMore"
|
||||||
);
|
);
|
||||||
this.search_data = this._sort_search_data(this.search_data);
|
// 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.empty();
|
||||||
this.$el.append(this.$recordsContainer);
|
this.$el.append(this.$recordsContainer);
|
||||||
this.$el.append(this.$extraButtonsContainer);
|
this.$el.append(this.$extraButtonsContainer);
|
||||||
this.showLoadMore(
|
// This.showLoadMore(
|
||||||
this.last_search_data_count >= this.options.records_per_page
|
// this.last_search_data_count >= this.options.records_per_page
|
||||||
);
|
// );
|
||||||
if (this._isInDom) {
|
return this._super.apply(this, arguments);
|
||||||
_.invoke(this.widgets, "on_attach_callback");
|
|
||||||
}
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -405,7 +399,10 @@ odoo.define(
|
||||||
|
|
||||||
for (const index_state in this.state.data) {
|
for (const index_state in this.state.data) {
|
||||||
const state_data = this.state.data[index_state];
|
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;
|
data._order_value = state_data.res_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,32 +428,76 @@ odoo.define(
|
||||||
_processSearchRecords: function(results) {
|
_processSearchRecords: function(results) {
|
||||||
const field_name = this.options.field_map.product;
|
const field_name = this.options.field_map.product;
|
||||||
const records = [];
|
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) {
|
for (const index in results) {
|
||||||
const record_search = results[index];
|
const record_search = results[index];
|
||||||
let state_data_found = false;
|
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) {
|
for (const index_data in this.state.data) {
|
||||||
const state_record = this.state.data[index_data];
|
const state_record = this.state.data[index_data];
|
||||||
const field = state_record.data[field_name];
|
if (!this._isValidLineState(state_record)) {
|
||||||
if (
|
continue;
|
||||||
(typeof field === "object" &&
|
}
|
||||||
field.data.id === record_search.id) ||
|
const field_value = state_record.data[field_name];
|
||||||
field === record_search.id
|
if (test_values(field_value, record_search)) {
|
||||||
) {
|
|
||||||
records.push(
|
records.push(
|
||||||
_.extend({}, record_search, {
|
_.extend({}, record_search, {
|
||||||
__id: state_record.id,
|
__id: state_record.id,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
states.push(state_record);
|
||||||
state_data_found = true;
|
state_data_found = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state_data_found) {
|
if (!state_data_found) {
|
||||||
records.push(record_search);
|
records.push(record_search);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return records;
|
return {
|
||||||
|
records: records,
|
||||||
|
states: states,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -502,15 +543,12 @@ odoo.define(
|
||||||
* @param {Boolean} no_process_records
|
* @param {Boolean} no_process_records
|
||||||
* @param {Number} position
|
* @param {Number} position
|
||||||
*/
|
*/
|
||||||
_appendSearchRecords: function(
|
_appendSearchRecords: function(search_records, options) {
|
||||||
search_records,
|
const processed_info = options.no_process_records
|
||||||
no_process_records,
|
|
||||||
position
|
|
||||||
) {
|
|
||||||
const processed_records = no_process_records
|
|
||||||
? search_records
|
? search_records
|
||||||
: this._processSearchRecords(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 state_data = this._getRecordDataById(search_record.__id);
|
||||||
const widget_options = this._getRecordOptions(search_record);
|
const widget_options = this._getRecordOptions(search_record);
|
||||||
widget_options.renderer_widget_index = this.widgets.length;
|
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
|
// Simulate new lines to dispatch get_default & onchange's to get the
|
||||||
// relevant data to print. This case increase the TTI time.
|
// relevant data to print. This case increase the TTI time.
|
||||||
if (!state_data) {
|
if (!state_data) {
|
||||||
const defVirtualState = ProductPickerRecord.generateVirtualState();
|
const defVirtualState = ProductPickerRecord.generateVirtualState(
|
||||||
|
this.options.instant_search
|
||||||
|
);
|
||||||
this.defsVirtualState.push(defVirtualState);
|
this.defsVirtualState.push(defVirtualState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,15 +576,39 @@ odoo.define(
|
||||||
function(widget, widget_position) {
|
function(widget, widget_position) {
|
||||||
if (typeof widget_position !== "undefined") {
|
if (typeof widget_position !== "undefined") {
|
||||||
const $elm = this.$el.find(
|
const $elm = this.$el.find(
|
||||||
`[data-card-id="${position}"]`
|
`[data-card-id="${widget_position}"]:first`
|
||||||
);
|
);
|
||||||
widget.$el.insertBefore($elm);
|
widget.$el.insertBefore($elm);
|
||||||
}
|
}
|
||||||
def.resolve();
|
def.resolve();
|
||||||
}.bind(this, ProductPickerRecord, position)
|
}.bind(this, ProductPickerRecord, options.position)
|
||||||
);
|
);
|
||||||
this.defs.push(def);
|
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
|
* @param {Number} position
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
appendSearchRecords: function(
|
appendSearchRecords: function(search_records, options = {}) {
|
||||||
search_records,
|
|
||||||
no_attach_widgets,
|
|
||||||
no_process_records,
|
|
||||||
position
|
|
||||||
) {
|
|
||||||
this.trigger_up("loading_records");
|
this.trigger_up("loading_records");
|
||||||
this.defs = [];
|
this.defs = [];
|
||||||
this.defsVirtualState = [];
|
this.defsVirtualState = [];
|
||||||
const cur_widget_index = this.widgets.length;
|
const cur_widget_index = this.widgets.length;
|
||||||
this._appendSearchRecords(search_records, no_process_records, position);
|
this._appendSearchRecords(search_records, options);
|
||||||
|
|
||||||
const defs = this.defs;
|
const defs = this.defs;
|
||||||
delete this.defs;
|
delete this.defs;
|
||||||
const defsVirtualState = this.defsVirtualState;
|
const defsVirtualState = this.defsVirtualState;
|
||||||
delete this.defsVirtualState;
|
delete this.defsVirtualState;
|
||||||
return [
|
return [
|
||||||
Promise.all(defs).then(() => {
|
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);
|
const new_widgets = this.widgets.slice(cur_widget_index);
|
||||||
_.invoke(new_widgets, "on_attach_callback");
|
_.invoke(new_widgets, "on_attach_callback");
|
||||||
}
|
}
|
||||||
|
@ -597,7 +657,6 @@ odoo.define(
|
||||||
_onClickLoadMore: function() {
|
_onClickLoadMore: function() {
|
||||||
this.$btnLoadMore.attr("disabled", true);
|
this.$btnLoadMore.attr("disabled", true);
|
||||||
this.trigger_up("load_more");
|
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({
|
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
|
* @param {Object} context
|
||||||
*/
|
*/
|
||||||
updateRecordContext: function(handle, context) {
|
updateRecordContext: function(id, context) {
|
||||||
this.localData[handle].context = _.extend(
|
this.localData[id].context = _.extend(
|
||||||
{},
|
{},
|
||||||
this.localData[handle].context,
|
this.localData[id].context,
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Number/String} id
|
* @param {String} id
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
isPureVirtual: function(id) {
|
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
|
* @param {Boolean} status
|
||||||
*/
|
*/
|
||||||
setPureVirtual: function(id, 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) {
|
unsetDirty: function(id) {
|
||||||
const data = this.localData[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 {Integer/String} listID
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
|
@ -77,14 +292,14 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this._makeDefaultRecord(list.model, params).then(recordID => {
|
this._makeDefaultRecord(list.model, params).then(recordID => {
|
||||||
this.setPureVirtual(recordID, true);
|
this.setPureVirtual(recordID, true);
|
||||||
this.updateRecordContext(recordID, {ignore_warning: true});
|
this.updateRecordContext(recordID, {
|
||||||
if (options.data) {
|
ignore_warning: true,
|
||||||
this._applyChange(recordID, options.data, params).then(() => {
|
not_onchange: true,
|
||||||
resolve(this.get(recordID));
|
});
|
||||||
|
resolve({
|
||||||
|
record: this.get(recordID),
|
||||||
|
params: params,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
resolve(this.get(recordID));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -92,7 +307,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
||||||
/**
|
/**
|
||||||
* Adds support to avoid show onchange warnings.
|
* Adds support to avoid show onchange warnings.
|
||||||
* The implementation is a pure hack that clone
|
* 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.
|
* 'trigger_up' method.
|
||||||
*
|
*
|
||||||
* @override
|
* @override
|
||||||
|
@ -112,5 +327,28 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
||||||
}
|
}
|
||||||
return this._super.apply(this, arguments);
|
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_lines": "_onClickLines",
|
||||||
"click .oe_btn_search_group": "_onClickSearchGroup",
|
"click .oe_btn_search_group": "_onClickSearchGroup",
|
||||||
"search .oe_search_input": "_onSearch",
|
"search .oe_search_input": "_onSearch",
|
||||||
|
"input .oe_search_input": "_onInputSearch",
|
||||||
"focusin .oe_search_input": "_onFocusInSearch",
|
"focusin .oe_search_input": "_onFocusInSearch",
|
||||||
"show.bs.dropdown .o_cp_buttons": "_onShowSearchDropdown",
|
"show.bs.dropdown .o_cp_buttons": "_onShowSearchDropdown",
|
||||||
"click #product_picker_maximize": "_onClickMaximize",
|
"click #product_picker_maximize": "_onClickMaximize",
|
||||||
|
@ -41,19 +42,17 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
}),
|
}),
|
||||||
|
|
||||||
_auto_search_delay: 450,
|
_auto_search_delay: 450,
|
||||||
|
_input_instant_search_time: 150,
|
||||||
|
|
||||||
// Model product.product fields
|
// Model product.product fields
|
||||||
search_read_fields: ["id", "display_name"],
|
search_read_fields: ["id", "display_name", "uom_id"],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
init: function(parent, name, record) {
|
init: function(parent) {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
|
|
||||||
// This is the parent state
|
|
||||||
this.state = record;
|
|
||||||
|
|
||||||
// Use jquery 'extend' to have a 'deep' merge.
|
// Use jquery 'extend' to have a 'deep' merge.
|
||||||
this.options = $.extend(
|
this.options = $.extend(
|
||||||
true,
|
true,
|
||||||
|
@ -79,32 +78,31 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
if (this.view) {
|
if (this.view) {
|
||||||
this._processGroups();
|
this._processGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._currentSearchBatchID = 0;
|
||||||
|
|
||||||
|
this._lazyRenderSearchRecords = _.debounce(() => {
|
||||||
|
this.doRenderSearchRecords();
|
||||||
|
++this._currentSearchBatchID;
|
||||||
|
}, this._input_instant_search_time);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
willStart: function() {
|
willStart: function() {
|
||||||
if (!this.view) {
|
return this._super.apply(this, arguments).then(() => {
|
||||||
return Promise.resolve();
|
if (this.isReadonly) {
|
||||||
}
|
// Show Lines
|
||||||
|
|
||||||
if (this.mode === "readonly") {
|
|
||||||
this._updateSearchContext(-1);
|
this._updateSearchContext(-1);
|
||||||
} else {
|
} else {
|
||||||
this._updateSearchContext(0);
|
this._updateSearchContext(0);
|
||||||
}
|
}
|
||||||
return Promise.all([
|
});
|
||||||
this._super.apply(this, arguments),
|
|
||||||
this._getSearchRecords(),
|
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the lines counter badge
|
* Updates the lines counter badge
|
||||||
*/
|
*/
|
||||||
updateBadgeLines: function() {
|
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
|
this.name
|
||||||
].data;
|
].data;
|
||||||
this.$badgeLines.text(records.length);
|
this.$badgeLines.text(records.length);
|
||||||
|
@ -116,7 +114,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
}
|
}
|
||||||
let prices = [];
|
let prices = [];
|
||||||
const field_map = this.options.field_map;
|
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
|
this.name
|
||||||
].data;
|
].data;
|
||||||
if (this.options.show_discount) {
|
if (this.options.show_discount) {
|
||||||
|
@ -145,7 +143,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
total,
|
total,
|
||||||
this.value.fields[this.options.field_map.price_unit],
|
this.value.fields[this.options.field_map.price_unit],
|
||||||
this.options.currency_field,
|
this.options.currency_field,
|
||||||
this.state.data
|
this.record.data
|
||||||
);
|
);
|
||||||
this.$totalZone.find(".total_price").html(total || 0.0);
|
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),
|
domain: this.record.getDomain(this.recordParams),
|
||||||
field: this.field,
|
field: this.field,
|
||||||
parentID: this.value.id,
|
parentID: this.value.id,
|
||||||
state: this.state,
|
record: this.record,
|
||||||
model: this.parent_controller.model,
|
model: this.parent_controller.model,
|
||||||
fieldName: this.name,
|
fieldName: this.name,
|
||||||
recordData: this.recordData,
|
recordData: this.recordData,
|
||||||
value: this.value,
|
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() {
|
_render: function() {
|
||||||
const def = this._super.apply(this, arguments);
|
const def = this._super.apply(this, arguments);
|
||||||
|
if (def) {
|
||||||
|
this.renderer.updateSearchGroup(this._activeSearchGroup);
|
||||||
|
|
||||||
// Parent implementation can return 'undefined' :(
|
// Check maximize state
|
||||||
return (
|
if (!this.$el.hasClass("oe_field_one2many_product_picker_maximized")) {
|
||||||
def &&
|
|
||||||
def.then(() => {
|
|
||||||
if (
|
|
||||||
!this.$el.hasClass("oe_field_one2many_product_picker_maximized")
|
|
||||||
) {
|
|
||||||
this.$el.addClass("position-relative d-flex flex-column");
|
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) {
|
if (this.options.show_subtotal) {
|
||||||
this._addTotalsZone();
|
this._addTotalsZone();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -274,10 +282,14 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
*/
|
*/
|
||||||
doRenderSearchRecords: function() {
|
doRenderSearchRecords: function() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this._getSearchRecords().then(() => {
|
this._getSearchRecords()
|
||||||
|
.then(records => {
|
||||||
this.renderer.$el.scrollTop(0);
|
this.renderer.$el.scrollTop(0);
|
||||||
this.renderer._renderView().then(() => resolve());
|
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
|
* @param {Boolean} merge
|
||||||
* @returns {Deferred}
|
* @returns {Deferred}
|
||||||
*/
|
*/
|
||||||
_getSearchRecords: function(options, merge) {
|
_getSearchRecords: function(options) {
|
||||||
const arch = this.view.arch;
|
const arch = this.view.arch;
|
||||||
const search_mode = this.options.search[this._searchMode];
|
const search_mode = this.options.search[this._searchMode];
|
||||||
const field_name = this.options.field_map.product;
|
const field_name = this.options.field_map.product;
|
||||||
|
@ -367,24 +379,8 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
}
|
}
|
||||||
|
|
||||||
task.then(results => {
|
task.then(results => {
|
||||||
if (merge) {
|
|
||||||
this._searchRecords = _.union(
|
|
||||||
this._searchRecords || [],
|
|
||||||
results
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this._searchRecords = results;
|
|
||||||
}
|
|
||||||
this._lastSearchRecordsCount = results.length;
|
|
||||||
this._searchOffset = offset + limit;
|
this._searchOffset = offset + limit;
|
||||||
if (this.renderer) {
|
this.renderer.showLoadMore(limit && results.length === limit);
|
||||||
this.renderer.updateSearchData(
|
|
||||||
this._searchRecords,
|
|
||||||
this._lastSearchRecordsCount,
|
|
||||||
this._activeSearchGroup
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(results);
|
resolve(results);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -531,9 +527,11 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
price_unit: "price_unit",
|
price_unit: "price_unit",
|
||||||
discount: "discount",
|
discount: "discount",
|
||||||
},
|
},
|
||||||
|
trigger_refresh_fields: ["partner_id", "currency_id"],
|
||||||
auto_save: false,
|
auto_save: false,
|
||||||
ignore_warning: false,
|
ignore_warning: false,
|
||||||
all_domain: [],
|
all_domain: [],
|
||||||
|
instant_search: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -588,12 +586,17 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const field_name = this.options.field_map.product;
|
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
|
this.name
|
||||||
].data;
|
].data;
|
||||||
const ids = _.map(lines, line => {
|
// 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;
|
return line.data[field_name].data.id;
|
||||||
});
|
})
|
||||||
|
.value();
|
||||||
return [["id", "in", ids]];
|
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.order = [{name: "sequence"}, {name: "id"}];
|
||||||
this._searchContext.activeTest = false;
|
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.
|
* that the search results. Use directy in-memory values.
|
||||||
*/
|
*/
|
||||||
showLines: function() {
|
showLines: function() {
|
||||||
|
this.renderer.clearRecords();
|
||||||
this._updateSearchContext(-1);
|
this._updateSearchContext(-1);
|
||||||
this._clearSearchInput();
|
this._clearSearchInput();
|
||||||
this.$btnLines
|
this.$btnLines
|
||||||
|
@ -635,6 +642,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
* @param {Number} group_id
|
* @param {Number} group_id
|
||||||
*/
|
*/
|
||||||
showGroup: function(group_id) {
|
showGroup: function(group_id) {
|
||||||
|
this.renderer.clearRecords();
|
||||||
this._updateSearchContext(group_id);
|
this._updateSearchContext(group_id);
|
||||||
this.doRenderSearchRecords();
|
this.doRenderSearchRecords();
|
||||||
this.$btnLines.removeClass("active");
|
this.$btnLines.removeClass("active");
|
||||||
|
@ -668,6 +676,15 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
this.doRenderSearchRecords();
|
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
|
* Auto select all content when user enters into fields with this
|
||||||
* widget.
|
* widget.
|
||||||
|
@ -707,7 +724,8 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
*/
|
*/
|
||||||
_onCreateQuickRecord: function(evt) {
|
_onCreateQuickRecord: function(evt) {
|
||||||
evt.stopPropagation();
|
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) {
|
if (this.options.auto_save) {
|
||||||
// Dont trigger state update
|
// Dont trigger state update
|
||||||
|
@ -718,17 +736,16 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
this.parent_controller
|
this.parent_controller
|
||||||
.saveRecord(undefined, {stayInEdit: true})
|
.saveRecord(undefined, {stayInEdit: true})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Because 'create' generates a new state and we can't know these new id we
|
self.renderer.updateState(
|
||||||
// need force update all the current states.
|
model.get(self.parent_controller.handle).data[
|
||||||
this._setValue(
|
self.name
|
||||||
{operation: "UPDATE", id: evt.data.id},
|
],
|
||||||
{doNotSetDirty: true}
|
{force: true}
|
||||||
).then(() => {
|
);
|
||||||
if (evt.data.callback) {
|
if (evt.data.callback) {
|
||||||
evt.data.callback();
|
evt.data.callback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
if (evt.data.callback) {
|
if (evt.data.callback) {
|
||||||
evt.data.callback();
|
evt.data.callback();
|
||||||
}
|
}
|
||||||
|
@ -758,22 +775,16 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
self.parent_controller
|
self.parent_controller
|
||||||
.saveRecord(undefined, {stayInEdit: true})
|
.saveRecord(undefined, {stayInEdit: true})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
// Workaround to get updated values
|
self.renderer.updateState(
|
||||||
self.parent_controller.model
|
self.parent_controller.model.get(
|
||||||
.reload(self.value.id)
|
self.parent_controller.handle
|
||||||
.then(function(result) {
|
).data[self.name],
|
||||||
var new_data = self.parent_controller.model.get(
|
{force: true}
|
||||||
result
|
|
||||||
);
|
);
|
||||||
self.value.data = new_data.data;
|
|
||||||
self.renderer.updateState(self.value, {
|
|
||||||
force: true,
|
|
||||||
});
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
@ -844,12 +855,9 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
if (this._isLoading) {
|
if (this._isLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._getSearchRecords(
|
this._getSearchRecords({
|
||||||
{
|
|
||||||
offset: this._searchOffset,
|
offset: this._searchOffset,
|
||||||
},
|
}).then(records => {
|
||||||
true
|
|
||||||
).then(records => {
|
|
||||||
this.renderer.appendSearchRecords(records);
|
this.renderer.appendSearchRecords(records);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -872,7 +880,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
|
||||||
*/
|
*/
|
||||||
_blockControlPanel: function(block) {
|
_blockControlPanel: function(block) {
|
||||||
if (this.$buttons) {
|
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 {
|
.oe_flip_card_inner {
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
box-shadow: 0px 0px 15px;
|
box-shadow: 0px 0px 15px;
|
||||||
.img-fluid {
|
|
||||||
transform: translateY(-50%) !important;
|
|
||||||
top: 50%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.oe_one2many_product_picker_title {
|
.oe_one2many_product_picker_title {
|
||||||
font-size: 1.95rem !important;
|
font-size: 1.95rem !important;
|
||||||
}
|
}
|
||||||
|
@ -142,6 +137,14 @@
|
||||||
$one2many-product-picker-transition-3d-time/2;
|
$one2many-product-picker-transition-3d-time/2;
|
||||||
transform-style: preserve-3d;
|
transform-style: preserve-3d;
|
||||||
|
|
||||||
|
.img-fluid {
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
z-index: -1;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
.position-absolute {
|
.position-absolute {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
@ -150,8 +153,19 @@
|
||||||
font-size: 1rem;
|
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 {
|
.badge_price {
|
||||||
top: 50%;
|
top: 55%;
|
||||||
right: -2px;
|
right: -2px;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -190,10 +204,19 @@
|
||||||
.o_form_view.o_form_nosheet {
|
.o_form_view.o_form_nosheet {
|
||||||
padding: $one2many-product-picker-card-form-padding;
|
padding: $one2many-product-picker-card-form-padding;
|
||||||
|
|
||||||
.o_field_widget .o_input_dropdown > input {
|
.o_field_widget {
|
||||||
|
&:not(.widget_numeric_step) {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_input_dropdown > input {
|
||||||
height: unset;
|
height: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.btn.w-100 {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.oe_flip_card_front {
|
.oe_flip_card_front {
|
||||||
|
@ -211,6 +234,16 @@
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 0;
|
left: 0;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
.oe_one2many_product_picker_form_buttons {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 3px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.oe_record_remove {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,60 +86,37 @@
|
||||||
>Load More</button>
|
>Load More</button>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-name="One2ManyProductPicker.FlipCard">
|
<t t-name="One2ManyProductPicker.ActionButton">
|
||||||
<div
|
<div class="safezone d-inline-block float-left m-0 pb-2 pr-2 text-left">
|
||||||
class="oe_flip_container p-1 col-12 col-sm-8 col-md-6 col-lg-4 col-xl-3 col-xxl-2"
|
|
||||||
t-att-data-card-id="state && state.id || record_search.id"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
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">
|
<t t-if="is_saving && lazy_qty > 0">
|
||||||
<div
|
|
||||||
class="safezone position-absolute m-0 pb-2 pr-2 text-left"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
class="badge record_saving badge-warning font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
|
class="badge record_saving badge-warning font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
|
||||||
><span
|
><span class="lazy_product_qty" t-esc="lazy_qty || '1'" /> x <t
|
||||||
class="lazy_product_qty"
|
t-esc="state.data[field_map[field_uom]].data.display_name"
|
||||||
t-esc="lazy_qty || '1'"
|
|
||||||
/> x <t
|
|
||||||
t-esc="state.data[field_map.product_uom].data.display_name"
|
|
||||||
/></span>
|
/></span>
|
||||||
</div>
|
|
||||||
</t>
|
</t>
|
||||||
<t t-elif="!is_virtual">
|
<t t-elif="!is_virtual">
|
||||||
<div
|
|
||||||
class="safezone position-absolute m-0 pb-2 pr-2 text-left"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
t-att-data-field="field_map.product_uom_qty"
|
t-att-data-field="field_map[field_uom_qty]"
|
||||||
t-attf-data-esc="str({{field_map.product_uom_qty}}) + ' x ' + {{field_map.product_uom}}.data.display_name"
|
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-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 t-else="">
|
<t t-else="">
|
||||||
<div
|
|
||||||
class="safezone position-absolute m-0 pb-2 pr-2 text-left"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
class="badge badge-primary font-weight-bold rounded-0 mt-0 px-2 py-3 add_product"
|
class="badge badge-primary font-weight-bold rounded-0 mt-0 px-2 py-3 add_product"
|
||||||
><i class="fa fa-plus" /> 1 <t
|
><i class="fa fa-plus" /> 1 <t
|
||||||
t-esc="state.data[field_map.product_uom].data.display_name"
|
t-esc="state.data[field_map[field_uom]].data.display_name"
|
||||||
/></span>
|
/></span>
|
||||||
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
|
<t t-name="One2ManyProductPicker.PriceZone">
|
||||||
<div class="position-absolute m-0 text-left badge_price">
|
<div class="position-absolute m-0 text-left badge_price">
|
||||||
<t t-if="show_discount">
|
<t t-if="show_discount">
|
||||||
<span
|
<span
|
||||||
t-att-data-field="field_map.discount"
|
t-att-data-field="field_map.discount"
|
||||||
t-attf-data-esc="'-' + str({{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"
|
class="badge badge-dark discount_price font-weight-bold rounded-0 mt-1 p-2"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
|
@ -148,10 +125,11 @@
|
||||||
class="badge font-weight-bold rounded-0 original_price"
|
class="badge font-weight-bold rounded-0 original_price"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
|
t-if="has_onchange"
|
||||||
class="badge badge-info price_unit font-weight-bold rounded-0 mt-1 p-2"
|
class="badge badge-info price_unit font-weight-bold rounded-0 mt-1 p-2"
|
||||||
/>
|
/>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="has_onchange">
|
||||||
<span
|
<span
|
||||||
t-att-data-field="field_map.price_unit"
|
t-att-data-field="field_map.price_unit"
|
||||||
t-attf-data-esc="'{{monetary('price_unit',true)}}'"
|
t-attf-data-esc="'{{monetary('price_unit',true)}}'"
|
||||||
|
@ -159,6 +137,16 @@
|
||||||
/>
|
/>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</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
|
<span
|
||||||
data-field="display_name"
|
data-field="display_name"
|
||||||
class="oe_one2many_product_picker_title position-absolute fixed-bottom p-1"
|
class="oe_one2many_product_picker_title position-absolute fixed-bottom p-1"
|
||||||
|
@ -167,7 +155,7 @@
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
class="img img-fluid"
|
class="img img-fluid"
|
||||||
t-att-src="image(state.data[field_map.product].data.id,'image_512')"
|
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-att-data-src-alt="image(state.data[field_map.product].data.id,'image_1024')"
|
||||||
/>
|
/>
|
||||||
</t>
|
</t>
|
||||||
|
@ -179,17 +167,31 @@
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
class="img img-fluid"
|
class="img img-fluid"
|
||||||
t-att-src="image(record_search.id,'image_512')"
|
t-att-src="image(record_search.id,'image_128')"
|
||||||
t-att-data-src-alt="image(record_search.id,'image_1024')"
|
t-att-data-src-alt="image(record_search.id,'image_1024')"
|
||||||
/>
|
/>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
|
</t>
|
||||||
|
<t t-name="One2ManyProductPicker.FlipCard.Back">
|
||||||
<div class="oe_flip_card_back">
|
<div class="oe_flip_card_back">
|
||||||
<widget
|
<widget
|
||||||
name="product_picker_quick_create_form"
|
name="product_picker_quick_create_form"
|
||||||
t-att-compare-key="field_map.product_uom"
|
t-att-compare-key="field_map.product_uom"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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"
|
||||||
|
t-att-data-card-id="state && state.id || record_search.id"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
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">
|
||||||
|
<t t-call="One2ManyProductPicker.FlipCard.Front" />
|
||||||
|
<t t-call="One2ManyProductPicker.FlipCard.Back" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
<button t-attf-class="btn btn-primary oe_record_add">Add</button>
|
<button t-attf-class="btn btn-primary oe_record_add">Add</button>
|
||||||
</t>
|
</t>
|
||||||
<t t-elif="state == 'dirty'">
|
<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" />
|
<i class="fa fa-check" />
|
||||||
</button>
|
</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" />
|
<i class="fa fa-times" />
|
||||||
</button>
|
</button>
|
||||||
</t>
|
</t>
|
||||||
|
@ -16,6 +16,9 @@
|
||||||
<button class="btn btn-danger oe_record_remove w-100"><i
|
<button class="btn btn-danger oe_record_remove w-100"><i
|
||||||
class="fa fa-trash"
|
class="fa fa-trash"
|
||||||
/> Remove</button>
|
/> Remove</button>
|
||||||
|
<button class="btn btn-warning oe_record_discard ml-1">
|
||||||
|
<i class="fa fa-times" />
|
||||||
|
</button>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
|
|
|
@ -61,6 +61,10 @@
|
||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
src="/web_widget_one2many_product_picker/static/src/js/views/basic_model.js"
|
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
|
<script
|
||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
src="/web_widget_one2many_product_picker/static/src/js/widgets/field_one2many_product_picker.js"
|
src="/web_widget_one2many_product_picker/static/src/js/widgets/field_one2many_product_picker.js"
|
||||||
|
|
Loading…
Reference in New Issue