[IMP] web_widget_one2many_product_picker: Better usability

pull/1802/head
Alexandre D. Díaz 2021-02-11 16:36:04 +01:00
parent c8f718cc3a
commit aa4ef9b0ed
12 changed files with 283 additions and 59 deletions

View File

@ -180,6 +180,11 @@ Other example for 'purchase.order.line' fields:
Usage Usage
===== =====
Parts of the widget:
~~~~~~~~~~~~~~~~~~~~
.. image:: https://raw.githubusercontent.com/OCA/web/12.0/web_widget_one2many_product_picker/static/img/product_picker_anat.png
Preview: Preview:
~~~~~~~~ ~~~~~~~~

View File

@ -1,3 +1,8 @@
Parts of the widget:
~~~~~~~~~~~~~~~~~~~~
.. image:: ../static/img/product_picker_anat.png
Preview: Preview:
~~~~~~~~ ~~~~~~~~

View File

@ -380,15 +380,16 @@ ul.auto-toc {
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#usage" id="id6">Usage</a><ul> <li><a class="reference internal" href="#usage" id="id6">Usage</a><ul>
<li><a class="reference internal" href="#preview" id="id7">Preview:</a></li> <li><a class="reference internal" href="#parts-of-the-widget" id="id7">Parts of the widget:</a></li>
<li><a class="reference internal" href="#preview" id="id8">Preview:</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id8">Known issues / Roadmap</a></li> <li><a class="reference internal" href="#known-issues-roadmap" id="id9">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id9">Bug Tracker</a></li> <li><a class="reference internal" href="#bug-tracker" id="id10">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id10">Credits</a><ul> <li><a class="reference internal" href="#credits" id="id11">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id11">Authors</a></li> <li><a class="reference internal" href="#authors" id="id12">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id12">Contributors</a></li> <li><a class="reference internal" href="#contributors" id="id13">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id13">Maintainers</a></li> <li><a class="reference internal" href="#maintainers" id="id14">Maintainers</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -564,22 +565,28 @@ options=&quot;{'search': [{'name': _('Starts With'), 'domain': [('name', '=like'
</div> </div>
<div class="section" id="usage"> <div class="section" id="usage">
<h1><a class="toc-backref" href="#id6">Usage</a></h1> <h1><a class="toc-backref" href="#id6">Usage</a></h1>
<div class="section" id="parts-of-the-widget">
<h2><a class="toc-backref" href="#id7">Parts of the widget:</a></h2>
<blockquote>
<img alt="https://raw.githubusercontent.com/OCA/web/12.0/web_widget_one2many_product_picker/static/img/product_picker_anat.png" src="https://raw.githubusercontent.com/OCA/web/12.0/web_widget_one2many_product_picker/static/img/product_picker_anat.png" />
</blockquote>
</div>
<div class="section" id="preview"> <div class="section" id="preview">
<h2><a class="toc-backref" href="#id7">Preview:</a></h2> <h2><a class="toc-backref" href="#id8">Preview:</a></h2>
<blockquote> <blockquote>
<img alt="https://raw.githubusercontent.com/OCA/web/12.0/web_widget_one2many_product_picker/static/img/product_picker.gif" src="https://raw.githubusercontent.com/OCA/web/12.0/web_widget_one2many_product_picker/static/img/product_picker.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/12.0/web_widget_one2many_product_picker/static/img/product_picker.gif" src="https://raw.githubusercontent.com/OCA/web/12.0/web_widget_one2many_product_picker/static/img/product_picker.gif" />
</blockquote> </blockquote>
</div> </div>
</div> </div>
<div class="section" id="known-issues-roadmap"> <div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id8">Known issues / Roadmap</a></h1> <h1><a class="toc-backref" href="#id9">Known issues / Roadmap</a></h1>
<ul class="simple"> <ul class="simple">
<li>Translations in the xml options attribute of the field that use the widget cant be exported automatically to be translated</li> <li>Translations in the xml options attribute of the field that use the widget cant be exported automatically to be translated</li>
<li>The product card animations can be improved. Currently the card is recreated, so we lost some elements to apply correct effects.</li> <li>The product card animations can be improved. Currently the card is recreated, so we lost some elements to apply correct effects.</li>
</ul> </ul>
</div> </div>
<div class="section" id="bug-tracker"> <div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id9">Bug Tracker</a></h1> <h1><a class="toc-backref" href="#id10">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>. <p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed If you spotted it first, help us smashing it by providing a detailed and welcomed
@ -587,15 +594,15 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<p>Do not contact contributors directly about support or help with technical issues.</p> <p>Do not contact contributors directly about support or help with technical issues.</p>
</div> </div>
<div class="section" id="credits"> <div class="section" id="credits">
<h1><a class="toc-backref" href="#id10">Credits</a></h1> <h1><a class="toc-backref" href="#id11">Credits</a></h1>
<div class="section" id="authors"> <div class="section" id="authors">
<h2><a class="toc-backref" href="#id11">Authors</a></h2> <h2><a class="toc-backref" href="#id12">Authors</a></h2>
<ul class="simple"> <ul class="simple">
<li>Tecnativa</li> <li>Tecnativa</li>
</ul> </ul>
</div> </div>
<div class="section" id="contributors"> <div class="section" id="contributors">
<h2><a class="toc-backref" href="#id12">Contributors</a></h2> <h2><a class="toc-backref" href="#id13">Contributors</a></h2>
<ul> <ul>
<li><p class="first"><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:</p> <li><p class="first"><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:</p>
<blockquote> <blockquote>
@ -609,7 +616,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
</ul> </ul>
</div> </div>
<div class="section" id="maintainers"> <div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id13">Maintainers</a></h2> <h2><a class="toc-backref" href="#id14">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p> <p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a> <a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose <p>OCA, or the Odoo Community Association, is a nonprofit organization whose

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View File

@ -89,9 +89,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateForm", f
model: this.basicFieldParams.model, model: this.basicFieldParams.model,
mainRecordData: this.getParent().getParent().state, mainRecordData: this.getParent().getParent().state,
}); });
if (this.id) { // if (this.id) {
this.basicFieldParams.model.save(this.id, {savePoint: true}); // this.basicFieldParams.model.save(this.id, {savePoint: true});
} // }
var def2 = this.formView.getController(this).then(function (controller) { var def2 = this.formView.getController(this).then(function (controller) {
self.controller = controller; self.controller = controller;
self.$el.empty(); self.$el.empty();

View File

@ -126,6 +126,10 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
state: this._getRecordState(), state: this._getRecordState(),
}) })
); );
if (this._disabled) {
this._disableQuickCreate();
}
}, },
/** /**
@ -138,7 +142,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
// Ensures that the record won't be created twice // Ensures that the record won't be created twice
this._disabled = true; this._disabled = true;
this.$el.addClass("o_disabled"); this.$el.addClass("o_disabled");
this.$("input:not(:disabled)") this.$("input:not(:disabled),button:not(:disabled)")
.addClass("o_temporarily_disabled") .addClass("o_temporarily_disabled")
.attr("disabled", "disabled"); .attr("disabled", "disabled");
}, },
@ -151,7 +155,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
// Allows to create again // Allows to create again
this._disabled = false; this._disabled = false;
this.$el.removeClass("o_disabled"); this.$el.removeClass("o_disabled");
this.$("input.o_temporarily_disabled") this.$("input.o_temporarily_disabled,button.o_temporarily_disabled")
.removeClass("o_temporarily_disabled") .removeClass("o_temporarily_disabled")
.attr("disabled", false); .attr("disabled", false);
}, },
@ -234,15 +238,14 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
* @returns {Deferred} * @returns {Deferred}
*/ */
_add: function () { _add: function () {
this.model.updateRecordContext(this.handle, {
has_changes_confirmed: true,
});
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 $.Deferred(); return $.Deferred();
} }
this.model.updateRecordContext(this.handle, {
has_changes_confirmed: true,
});
var self = this; var self = this;
this._disableQuickCreate(); this._disableQuickCreate();
return this.saveRecord(this.handle, { return this.saveRecord(this.handle, {
@ -251,39 +254,70 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
savePoint: true, savePoint: true,
viewType: "form", viewType: "form",
}).then(function () { }).then(function () {
self._enableQuickCreate();
var record = self.model.get(self.handle); var record = self.model.get(self.handle);
self.trigger_up("create_quick_record", { self.trigger_up("restore_flip_card", {
id: record.id, success_callback: function () {
self.trigger_up("create_quick_record", {
id: record.id,
});
self.model.unsetDirty(self.handle);
//self._updateButtons();
self._enableQuickCreate();
},
block: true,
}); });
self.model.unsetDirty(self.handle);
self._updateButtons();
self.trigger_up("restore_flip_card");
}); });
}, },
_remove: function () { _remove: function () {
this.trigger_up("restore_flip_card"); if (this._disabled) {
// Don't do anything if we are already creating a record
return $.Deferred();
}
this._disableQuickCreate();
this.trigger_up("restore_flip_card", {block: true});
var record = this.model.get(this.handle);
this.trigger_up("list_record_remove", { this.trigger_up("list_record_remove", {
id: this.renderer.state.id, id: record.id,
}); });
}, },
_change: function () { _change: function () {
var self = this;
if (this._disabled) {
// Don't do anything if we are already creating a record
return $.Deferred();
}
this._disableQuickCreate();
this.model.updateRecordContext(this.handle, { this.model.updateRecordContext(this.handle, {
has_changes_confirmed: true, has_changes_confirmed: true,
}); });
var record = this.model.get(this.handle); var record = this.model.get(this.handle);
this.trigger_up("update_quick_record", {
id: record.id, this.trigger_up("restore_flip_card", {
success_callback: function () {
self.trigger_up("update_quick_record", {
id: record.id,
});
self.model.unsetDirty(self.handle);
//self._updateButtons();
self._enableQuickCreate();
},
block: true,
}); });
this.trigger_up("restore_flip_card");
this.model.unsetDirty(this.handle);
this._updateButtons();
}, },
_discard: function () { _discard: function () {
var self = this; var self = this;
if (this._disabled) {
// Don't do anything if we are already creating a record
return $.Deferred();
}
this._disableQuickCreate();
var record = this.model.get(this.handle); var record = this.model.get(this.handle);
this.model.discardChanges(this.handle, { this.model.discardChanges(this.handle, {
rollback: true, rollback: true,
@ -295,11 +329,13 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
this.update({}, {reload: false}); this.update({}, {reload: false});
this.trigger_up("restore_flip_card"); this.trigger_up("restore_flip_card");
this._updateButtons(); this._updateButtons();
this._enableQuickCreate();
} else { } else {
this.update({}, {reload: false}).then(function () { this.update({}, {reload: false}).then(function () {
self.model.unsetDirty(self.handle); self.model.unsetDirty(self.handle);
self.trigger_up("restore_flip_card"); self.trigger_up("restore_flip_card");
self._updateButtons(); self._updateButtons();
self._enableQuickCreate();
}); });
} }
}, },

View File

@ -13,6 +13,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
var tools = require("web_widget_one2many_product_picker.tools"); var tools = require("web_widget_one2many_product_picker.tools");
var ProductPickerQuickModifPriceForm = require( var ProductPickerQuickModifPriceForm = require(
"web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm"); "web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm");
var FieldManagerMixin = require('web.FieldManagerMixin');
var qweb = core.qweb; var qweb = core.qweb;
var _t = core._t; var _t = core._t;
@ -42,6 +43,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
front: [], front: [],
back: [], back: [],
}; };
this._lazyUpdateRecord = _.debounce(this._updateRecord.bind(this), 450);
}, },
/** /**
@ -105,6 +108,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
if (state) { if (state) {
this._setState(state); this._setState(state);
} }
this.$card.removeClass("blocked");
// Avoid recreate active record // Avoid recreate active record
if (this.$card.hasClass("active")) { if (this.$card.hasClass("active")) {
this._processDynamicFields(); this._processDynamicFields();
@ -177,6 +181,7 @@ 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;
this.state = viewState; this.state = viewState;
if (recordSearch) { if (recordSearch) {
this.recordSearch = recordSearch; this.recordSearch = recordSearch;
} }
@ -193,6 +198,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
// Using directly the 'model record' instead of the state because // Using directly the 'model record' instead of the state because
// the state it's a parsed version of this record that doesn't // the state it's a parsed version of this record that doesn't
// contains the '_virtual' attribute. // contains the '_virtual' attribute.
var model = this.options.basicFieldParams.model;
var record = model.get(this.state.id);
return { return {
record_search: this.recordSearch, record_search: this.recordSearch,
user_context: this.getSession() && this.getSession().user_context || {}, user_context: this.getSession() && this.getSession().user_context || {},
@ -204,6 +211,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
monetary: this._getMonetaryFieldValue.bind(this), monetary: this._getMonetaryFieldValue.bind(this),
show_discount: this.options.showDiscount, show_discount: this.options.showDiscount,
is_virtual: this.is_virtual, is_virtual: this.is_virtual,
modified: record && record.context.product_picker_modified,
active_model: '', active_model: '',
}; };
}, },
@ -497,6 +505,93 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
); );
}, },
/**
* @private
* @returns {Promise}
*/
_saveRecord: function () {
var self = this;
var model = this.options.basicFieldParams.model;
var record = model.get(this.state.id);
return model.save(record.id, {
savePoint: true,
}).then(function () {
var record = model.get(self.state.id);
self.trigger_up("create_quick_record", {
id: record.id,
});
model.unsetDirty(self.state.id);
});
},
/**
* @private
*/
_updateRecord: function (changes) {
var model = this.options.basicFieldParams.model;
var record = model.get(this.state.id);
this.trigger_up("update_quick_record", {
id: record.id,
});
model.unsetDirty(this.state.id);
},
/**
* @private
* @returns {Promise}
*/
_addProduct: function () {
var self = this;
var changes = {};
if (this.state.data[this.options.fieldMap.product_uom_qty] === 0) {
changes[this.options.fieldMap.product_uom_qty] = 1;
}
var model = this.options.basicFieldParams.model;
this.$card.addClass("blocked");
return model.notifyChanges(this.state.id, changes).then(function () {
self._saveRecord();
});
},
/**
* @private
* @param {Number} amount
* @returns {Promise}
*/
_incProductQty: function (amount) {
var self = this;
this.state.data[this.options.fieldMap.product_uom_qty] += amount;
var model = this.options.basicFieldParams.model;
var record = model.get(this.state.id);
var state_data = record.data;
state_data[this.options.fieldMap.product_uom_qty] += amount;
var changes = _.pick(state_data, this.options.fieldMap.product_uom_qty);
return model.notifyChanges(record.id, changes).then(function () {
self._processDynamicFields();
self._lazyUpdateRecord();
});
},
/**
* @private
*/
_doInteractAnim: function (target, currentTarget) {
var $target = $(target);
var $currentTarget = $(currentTarget);
var $img = $currentTarget.find(".oe_flip_card_front img");
$target.addClass('o_catch_attention');
$target.on('animationend', function () {
$target.removeClass('o_catch_attention');
$target.off('animationend');
});
$img.addClass('oe_product_picker_catch_attention');
$img.on('animationend', function () {
$img.removeClass('oe_product_picker_catch_attention');
$img.off('animationend');
});
},
/** /**
* @private * @private
*/ */
@ -531,11 +626,31 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @param {ClickEvent} evt * @param {ClickEvent} evt
*/ */
_onClickFlipCard: function (evt) { _onClickFlipCard: function (evt) {
// Avoid clicks on form elements // Avoid clicks on form elements
if (['INPUT', 'BUTTON', 'A'].indexOf(evt.target.tagName) !== -1) { if (['INPUT', 'BUTTON', 'A'].indexOf(evt.target.tagName) !== -1 || this.$card.hasClass('blocked')) {
return; return;
} }
var $target = $(evt.target);
if (!this.options.readOnlyMode) {
if (
$target.hasClass('add_product') ||
$target.parents('.add_product').length
) {
if (!this.is_adding_product) {
this.is_adding_product = true;
this._addProduct();
this._doInteractAnim(evt.target, evt.currentTarget);
}
return;
} else if (
$target.hasClass('product_qty') ||
$target.parents('.product_qty').length
) {
this._incProductQty(1);
this._doInteractAnim(evt.target, evt.currentTarget);
return;
}
}
if (!this._clickFlipCardDelayed) { if (!this._clickFlipCardDelayed) {
this._clickFlipCardDelayed = setTimeout( this._clickFlipCardDelayed = setTimeout(
this._onClickDelayedFlipCard.bind(this, evt), this._onClickDelayedFlipCard.bind(this, evt),
@ -644,7 +759,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
/** /**
* @private * @private
*/ */
_onRestoreFlipCard: function () { _onRestoreFlipCard: function (evt) {
var self = this; var self = this;
this.$card.removeClass("active"); this.$card.removeClass("active");
this.$front.removeClass("d-none"); this.$front.removeClass("d-none");
@ -660,7 +775,16 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
zIndex: "", zIndex: "",
}); });
self.$card.off('transitionend'); self.$card.off('transitionend');
if (evt.data.success_callback) {
evt.data.success_callback();
}
}); });
} else if (evt.data.success_callback) {
evt.data.success_callback();
}
if (evt.data.block) {
this.$card.addClass("blocked");
} }
}, },

View File

@ -104,14 +104,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
*/ */
updateState: function (state, params) { updateState: function (state, params) {
var self = this; var self = this;
var sparams = _.extend({}, params, {noRender: true});
if (_.isEqual(this.state.data, state.data)) { if (_.isEqual(this.state.data, state.data)) {
return this._super.apply(this, arguments); return this._super(state, sparams);
} }
var old_state = _.clone(this.state.data); var old_state = _.clone(this.state.data);
return this._super( return this._super(state, sparams).then(function () {
state,
_.extend({}, params, {noRender: true})
).then(function () {
self._updateStateRecords(old_state); self._updateStateRecords(old_state);
}); });
}, },
@ -151,6 +149,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
var widget = this.widgets[eb]; var widget = this.widgets[eb];
if ( if (
widget && widget &&
widget.state &&
widget.state.data[this.options.field_map.product].data.id === widget_product_id widget.state.data[this.options.field_map.product].data.id === widget_product_id
) { ) {
found = true; found = true;
@ -194,7 +193,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
var found = false; var found = false;
for (var e in this.state.data) { for (var e in this.state.data) {
var current_state = this.state.data[e]; var current_state = this.state.data[e];
if (current_state.id === old_state.id) { if (current_state.id === old_state.id || (typeof current_state.data.id !== 'undefined' && current_state.data.id === old_state.data.id)) {
found = true; found = true;
break; break;
} }
@ -203,6 +202,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
states_to_destroy.push(old_state); states_to_destroy.push(old_state);
} }
} }
this._removeRecords(states_to_destroy); this._removeRecords(states_to_destroy);
// Records to Update or Create // Records to Update or Create
@ -215,12 +215,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
var search_record = false; var search_record = false;
for (var e = this.widgets.length-1; e>=0; --e) { for (var e = this.widgets.length-1; e>=0; --e) {
var widget = this.widgets[e]; var widget = this.widgets[e];
if (!widget) { if (!widget || !widget.state) {
// Already processed widget (deleted) // Already processed widget (deleted)
continue; continue;
} }
if (widget.state.id === state.id) { if (widget.state.id === state.id || (typeof state.data.id !== 'undefined' && widget.state.data.id === state.data.id)) {
widget.recreate(state); widget.recreate(state);
exists = true; exists = true;
break; break;
@ -236,8 +236,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
// Remove "pure virtual" records that have the same product that the new record // Remove "pure virtual" records that have the same product that the new record
if ( if (
widget.is_virtual && widget.is_virtual &&
widget.state.data[this.options.field_map.product].data.id === state.data[this.options.field_map.product].data.id && widget.state.data[this.options.field_map.product].data.id === state.data[this.options.field_map.product].data.id
widget.state.data[this.options.compa].data.id === state.data[this.options.field_map.product].data.id
) { ) {
to_destroy.push(widget); to_destroy.push(widget);
delete this.widgets[e]; delete this.widgets[e];
@ -284,7 +283,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
}, },
/** /**
* Compare search results with current lines * Compare search results with current lines.
* Link a current state with the 'search record'.
* *
* @private * @private
* @param {Array[Object]} results * @param {Array[Object]} results

View File

@ -580,11 +580,20 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
* @param {CustomEvent} evt * @param {CustomEvent} evt
*/ */
_onCreateQuickRecord: function (evt) { _onCreateQuickRecord: function (evt) {
var self = this;
this.parent_controller.model.setPureVirtual(evt.data.id, false); this.parent_controller.model.setPureVirtual(evt.data.id, false);
this._setValue({operation: "ADD", id: evt.data.id}); if (!self.options.auto_save) {
if (this.options.auto_save) { self.parent_controller.model.updateRecordContext(evt.data.id, {
this.parent_controller.saveRecord(undefined, {stayInEdit: true}); product_picker_modified: true,
});
} }
this._setValue({operation: "ADD", id: evt.data.id}).then(function () {
if (self.options.auto_save) {
self.parent_controller.saveRecord(undefined, {stayInEdit: true}).then(function () {
self.renderer.updateState(self.value);
});
}
});
}, },
/** /**
@ -594,10 +603,19 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
* @param {CustomEevent} evt * @param {CustomEevent} evt
*/ */
_onUpdateQuickRecord: function (evt) { _onUpdateQuickRecord: function (evt) {
this._setValue({operation: "UPDATE", id: evt.data.id, data: evt.data.data}); var self = this;
if (this.options.auto_save) { if (!self.options.auto_save) {
this.parent_controller.saveRecord(undefined, {stayInEdit: true}); self.parent_controller.model.updateRecordContext(evt.data.id, {
product_picker_modified: true,
});
} }
this._setValue({operation: "UPDATE", id: evt.data.id, data: evt.data.data}).then(function () {
if (self.options.auto_save) {
self.parent_controller.saveRecord(undefined, {stayInEdit: true}).then(function () {
self.renderer.updateState(self.value);
});
}
});
}, },
/** /**

View File

@ -59,6 +59,10 @@
transition: top $one2many-product-picker-transition-3d-time, left $one2many-product-picker-transition-3d-time, width $one2many-product-picker-transition-3d-time, height $one2many-product-picker-transition-3d-time; transition: top $one2many-product-picker-transition-3d-time, left $one2many-product-picker-transition-3d-time, width $one2many-product-picker-transition-3d-time, height $one2many-product-picker-transition-3d-time;
height: $one2many-product-picker-card-min-height; height: $one2many-product-picker-card-min-height;
&.blocked {
filter: blur(2px);
}
&.disabled { &.disabled {
filter: grayscale(100%); filter: grayscale(100%);
opacity: 0.5; opacity: 0.5;
@ -211,6 +215,9 @@
font-size: 0.95rem; font-size: 0.95rem;
z-index: 0; z-index: 0;
} }
.add_product, .product_qty, .price_unit {
cursor: pointer;
}
} }
} }
} }
@ -230,3 +237,20 @@
text-align: center; text-align: center;
} }
} }
.oe_product_picker_catch_attention {
position: relative;
animation: productPickerCatchAttention 200ms normal forwards;
}
@keyframes productPickerCatchAttention {
0% {
transform: scale(1.0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1.0);
}
}

View File

@ -72,11 +72,16 @@
<div class="oe_flip_container p-1 col-4 col-sm-4 col-md-2 col-lg-2 col-xl-1"> <div class="oe_flip_container p-1 col-4 col-sm-4 col-md-2 col-lg-2 col-xl-1">
<div t-attf-class="oe_flip_card {{!state &amp;&amp; 'disabled' || ''}}"> <div t-attf-class="oe_flip_card {{!state &amp;&amp; 'disabled' || ''}}">
<div class="oe_flip_card_inner text-center"> <div class="oe_flip_card_inner text-center">
<div t-attf-class="oe_flip_card_front p-0 {{state &amp;&amp; !is_virtual &amp;&amp; 'border-primary' || ''}}"> <div t-attf-class="oe_flip_card_front p-0 {{(modified &amp;&amp; 'border-warning') || (state &amp;&amp; !is_virtual &amp;&amp; 'border-success') || ''}}">
<t t-if="state"> <t t-if="state">
<t t-if="!is_virtual"> <t t-if="!is_virtual">
<div class="position-absolute m-0 text-left"> <div class="position-absolute m-0 text-left">
<span t-att-data-field="field_map.product_uom_qty" t-attf-data-esc="str({{field_map.product_uom_qty}}) + ' x ' + {{field_map.product_uom}}.data.display_name" class="badge badge-primary font-weight-bold rounded-0 mt-1 p-2" /> <span t-att-data-field="field_map.product_uom_qty" t-attf-data-esc="str({{field_map.product_uom_qty}}) + ' x ' + {{field_map.product_uom}}.data.display_name" t-attf-class="badge {{modified &amp;&amp; 'badge-warning' || 'badge-success'}} font-weight-bold rounded-0 mt-1 p-2 product_qty" />
</div>
</t>
<t t-else="">
<div class="position-absolute m-0 text-left">
<span class="badge badge-primary font-weight-bold rounded-0 mt-1 p-2 add_product"><i class="fa fa-plus"></i> Add 1 <t t-esc="state.data[field_map.product_uom].data.display_name"/></span>
</div> </div>
</t> </t>
<div class="position-absolute m-0 text-left badge_price"> <div class="position-absolute m-0 text-left badge_price">

View File

@ -3,7 +3,7 @@
<t t-name="One2ManyProductPicker.QuickCreate.FormButtons"> <t t-name="One2ManyProductPicker.QuickCreate.FormButtons">
<div class="oe_one2many_product_picker_form_buttons"> <div class="oe_one2many_product_picker_form_buttons">
<t t-if="state == 'new'"> <t t-if="state == 'new'">
<button 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"><i class="fa fa-check" /></button> <button class="btn btn-success oe_record_change mr-2"><i class="fa fa-check" /></button>