Merge PR #1802 into 12.0

Signed-off-by pedrobaeza
This commit is contained in:
OCA-git-bot
2021-02-15 20:06:19 +00:00
15 changed files with 515 additions and 116 deletions

View File

@@ -180,6 +180,15 @@ Other example for 'purchase.order.line' fields:
Usage
=====
When you change the value of a field and switch to edit another record, the
changes will be applied to the previous record without having to click on
accept changes.
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:
~~~~~~~~

View File

@@ -1,3 +1,12 @@
When you change the value of a field and switch to edit another record, the
changes will be applied to the previous record without having to click on
accept changes.
Parts of the widget:
~~~~~~~~~~~~~~~~~~~~
.. image:: ../static/img/product_picker_anat.png
Preview:
~~~~~~~~

View File

@@ -380,15 +380,16 @@ ul.auto-toc {
</ul>
</li>
<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>
</li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id8">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="#credits" id="id10">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id11">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id12">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id13">Maintainers</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="id10">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id11">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id12">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id13">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id14">Maintainers</a></li>
</ul>
</li>
</ul>
@@ -564,22 +565,31 @@ options=&quot;{'search': [{'name': _('Starts With'), 'domain': [('name', '=like'
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id6">Usage</a></h1>
<p>When you change the value of a field and switch to edit another record, the
changes will be applied to the previous record without having to click on
accept changes.</p>
<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">
<h2><a class="toc-backref" href="#id7">Preview:</a></h2>
<h2><a class="toc-backref" href="#id8">Preview:</a></h2>
<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" />
</blockquote>
</div>
</div>
<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">
<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>
</ul>
</div>
<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>.
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
@@ -587,15 +597,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>
</div>
<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">
<h2><a class="toc-backref" href="#id11">Authors</a></h2>
<h2><a class="toc-backref" href="#id12">Authors</a></h2>
<ul class="simple">
<li>Tecnativa</li>
</ul>
</div>
<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>
<li><p class="first"><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:</p>
<blockquote>
@@ -609,7 +619,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
</ul>
</div>
<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>
<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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

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

View File

@@ -18,6 +18,8 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
BasicModel.include({
_applyOnChange: function (values, record, viewType) {
var vt = viewType || record.viewType;
// Ignore changes by record context 'ignore_onchanges' fields
if ('ignore_onchanges' in record.context) {
var ignore_changes = record.context.ignore_onchanges;
for (var index in ignore_changes) {
@@ -64,11 +66,28 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
},
/**
* Updates buttons depending on record status
*
* @private
* Create or accept changes
*/
_updateButtons: function () {
auto: function () {
var record = this.model.get(this.handle);
if (record.context.has_changes_confirmed || typeof record.context.has_changes_confirmed === "undefined") {
return;
}
var state = this._getRecordState();
if (state === "new") {
this._add();
} else if (state === "dirty") {
this._change();
}
},
/**
* Know the real state of the record
* - record: Normal
* - new: Is a new record
* - dirty: Has changes
*/
_getRecordState: function () {
var record = this.model.get(this.handle);
var state = "record";
if (this.model.isNew(record.id)) {
@@ -89,25 +108,41 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
}
}
}
return state;
},
/**
* Updates buttons depending on record status
*
* @private
*/
_updateButtons: function () {
this.$el.find(
".oe_one2many_product_picker_form_buttons").remove();
this.$el.find(".o_form_view").append(
qweb.render(
"One2ManyProductPicker.QuickCreate.FormButtons", {
state: state,
state: this._getRecordState(),
})
);
if (this._disabled) {
this._disableQuickCreate();
}
},
/**
* @private
*/
_disableQuickCreate: function () {
if (!this.$el) {
return;
}
// Ensures that the record won't be created twice
this._disabled = true;
this.$el.addClass("o_disabled");
this.$("input:not(:disabled)")
this.$("input:not(:disabled),button:not(:disabled)")
.addClass("o_temporarily_disabled")
.attr("disabled", "disabled");
},
@@ -120,7 +155,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
// Allows to create again
this._disabled = false;
this.$el.removeClass("o_disabled");
this.$("input.o_temporarily_disabled")
this.$("input.o_temporarily_disabled,button.o_temporarily_disabled")
.removeClass("o_temporarily_disabled")
.attr("disabled", false);
},
@@ -208,6 +243,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
// Don't do anything if we are already creating a record
return $.Deferred();
}
this.model.updateRecordContext(this.handle, {
has_changes_confirmed: true,
});
var self = this;
this._disableQuickCreate();
return this.saveRecord(this.handle, {
@@ -216,64 +254,70 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
savePoint: true,
viewType: "form",
}).then(function () {
self._enableQuickCreate();
var record = self.model.get(self.handle);
self.trigger_up("create_quick_record", {
id: record.id,
self.trigger_up("restore_flip_card", {
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();
});
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickAdd: function (ev) {
ev.stopPropagation();
this.model.updateRecordContext(this.handle, {
has_changes_confirmed: true,
});
this._add();
},
_remove: function () {
if (this._disabled) {
/**
* @private
* @param {MouseEvent} ev
*/
_onClickRemove: function (ev) {
ev.stopPropagation();
// 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", {
id: this.renderer.state.id,
id: record.id,
});
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickChange: function (ev) {
ev.stopPropagation();
_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, {
has_changes_confirmed: true,
});
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();
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickDiscard: function (ev) {
_discard: function () {
var self = this;
ev.stopPropagation();
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);
this.model.discardChanges(this.handle, {
rollback: true,
@@ -285,14 +329,52 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
this.update({}, {reload: false});
this.trigger_up("restore_flip_card");
this._updateButtons();
this._enableQuickCreate();
} else {
this.update({}, {reload: false}).then(function () {
self.model.unsetDirty(self.handle);
self.trigger_up("restore_flip_card");
self._updateButtons();
self._enableQuickCreate();
});
}
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickAdd: function (ev) {
ev.stopPropagation();
this._add();
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickRemove: function (ev) {
ev.stopPropagation();
this._remove();
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickChange: function (ev) {
ev.stopPropagation();
this._change();
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickDiscard: function (ev) {
ev.stopPropagation();
this._discard();
},
}
);
@@ -302,6 +384,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
Controller: ProductPickerQuickCreateFormController,
}),
/**
* @override
*/
init: function (viewInfo, params) {
this._super.apply(this, arguments);
this.controllerParams.compareKey = params.compareKey;

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 ProductPickerQuickModifPriceForm = require(
"web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm");
var FieldManagerMixin = require('web.FieldManagerMixin');
var qweb = core.qweb;
var _t = core._t;
@@ -38,7 +39,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
this.subWidgets = {};
this._clickFlipCardCount = 0;
this._setState(state, options.searchRecord);
this.widgets = [];
this.widgets = {
front: [],
back: [],
};
this._lazyUpdateRecord = _.debounce(this._updateRecord.bind(this), 450);
},
/**
@@ -71,6 +77,14 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
_.invoke(this.subWidgets, "on_detach_callback");
},
/**
* @override
*/
destroy: function () {
this.$card.off("")
this._super.apply(this, arguments);
},
/**
* @override
*/
@@ -94,6 +108,13 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
if (state) {
this._setState(state);
}
this.$card.removeClass("blocked");
// Avoid recreate active record
if (this.$card.hasClass("active")) {
this._processDynamicFields();
return $.when();
}
this.on_detach_callback();
return this._render();
},
@@ -160,6 +181,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
this.fields = this.getParent().state.fields;
this.fieldsInfo = this.getParent().state.fieldsInfo.form;
this.state = viewState;
if (recordSearch) {
this.recordSearch = recordSearch;
}
@@ -176,6 +198,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
// Using directly the 'model record' instead of the state because
// the state it's a parsed version of this record that doesn't
// contains the '_virtual' attribute.
var model = this.options.basicFieldParams.model;
var record = model.get(this.state.id);
return {
record_search: this.recordSearch,
user_context: this.getSession() && this.getSession().user_context || {},
@@ -187,6 +211,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
monetary: this._getMonetaryFieldValue.bind(this),
show_discount: this.options.showDiscount,
is_virtual: this.is_virtual,
modified: record && record.context.product_picker_modified,
active_model: '',
};
},
@@ -230,6 +255,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
var model = this.options.basicFieldParams.model;
var scontext = _.extend(
{}, this._getInternalVirtualRecordContext(), context);
// Force qty to 1.0 to launch correct onchanges
scontext[`default_${this.options.fieldMap.product_uom_qty}`] = 1.0;
var sdata = _.extend({}, this._getInternalVirtualRecordData(), data);
return model.createVirtualRecord(
this.options.basicFieldParams.value.id, {
@@ -238,10 +265,20 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
});
},
_detachAllWidgets: function () {
_.invoke(this.widgets.front, "on_detach_callback");
_.invoke(this.widgets.back, "on_detach_callback");
this.widgets = {
front: [],
back: [],
};
},
/**
* @override
*/
_render: function () {
this._detachAllWidgets();
this.defs = [];
this._replaceElement(
qweb.render(
@@ -249,11 +286,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
this._getQWebContext()
)
);
this.$el.data("renderer_widget_index", this.options.renderer_widget_index);
this.$card = this.$(".oe_flip_card");
this.$front = this.$(".oe_flip_card_front");
this.$back = this.$(".oe_flip_card_back");
this._processWidgetFields(this.$front);
this._processWidgets(this.$front);
this._processWidgets(this.$front, 'front');
this._processDynamicFields();
return $.when.apply(this, this.defs);
},
@@ -265,7 +303,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @private
* @param {jQueryElement} $container
*/
_processWidgetFields: function ($container) {
_processWidgetFields: function ($container, widget_list) {
var self = this;
$container.find("field").each(function () {
var $field = $(this);
@@ -357,7 +395,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @private
* @param {jQueryElement} $container
*/
_processWidgets: function ($container) {
_processWidgets: function ($container, widget_zone) {
var self = this;
$container.find("widget").each(function () {
var $field = $(this);
@@ -375,7 +413,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
data: self.state && self.state.data,
});
self.widgets.push(widget);
self.widgets[widget_zone].push(widget);
var def = widget
._widgetRenderAndInsert(function () {
@@ -469,6 +507,91 @@ 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);
self.$card.find('.o_catch_attention').removeClass('o_catch_attention');
});
},
/**
* @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);
this.$card.find('.o_catch_attention').removeClass('o_catch_attention');
},
/**
* @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');
$img.addClass('oe_product_picker_catch_attention');
$img.on('animationend', function () {
$img.removeClass('oe_product_picker_catch_attention');
$img.off('animationend');
});
},
/**
* @private
*/
@@ -503,11 +626,31 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @param {ClickEvent} evt
*/
_onClickFlipCard: function (evt) {
// 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;
}
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) {
this._clickFlipCardDelayed = setTimeout(
this._onClickDelayedFlipCard.bind(this, evt),
@@ -534,21 +677,28 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
}
if (this.$card.hasClass("active")) {
this.$card.removeClass("active");
this.$card.find('.oe_flip_card_front').removeClass("d-none");
this.$front.removeClass("d-none");
} else {
var self = this;
this.defs = [];
this._processWidgetFields(this.$back);
this._processWidgets(this.$back);
if (!this.widgets.back.length) {
this._processWidgetFields(this.$back);
this._processWidgets(this.$back, 'back');
}
this._processDynamicFields();
$.when(this.defs).then(function () {
var $actived_card = self.$el.parent().find(".active");
$actived_card.removeClass("active");
$actived_card.find('.oe_flip_card_front').removeClass("d-none");
self.$card.addClass("active");
setTimeout(function () {
self.$('.oe_flip_card_front').addClass("d-none");
}, 200);
self.$card.on('transitionend', function () {
self.$front.addClass("d-none");
self.$card.off('transitionend');
});
self.trigger_up("record_flip", {
widget_index: self.$el.data("renderer_widget_index"),
prev_widget_index: $actived_card.parent().data("renderer_widget_index"),
});
});
}
},
@@ -609,9 +759,33 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
/**
* @private
*/
_onRestoreFlipCard: function () {
this.$(".oe_flip_card").removeClass("active");
this.$('.oe_flip_card_front').removeClass("d-none");
_onRestoreFlipCard: function (evt) {
var self = this;
this.$card.removeClass("active");
this.$front.removeClass("d-none");
if (this.$card.hasClass('oe_flip_card_maximized')) {
this.$card.removeClass('oe_flip_card_maximized');
this.$card.on('transitionend', function () {
self.$card.css({
position: "",
top: "",
left: "",
width: "",
height: "",
zIndex: "",
});
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

@@ -9,6 +9,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
var BasicRenderer = require("web.BasicRenderer");
var One2ManyProductPickerRecord = require(
"web_widget_one2many_product_picker.One2ManyProductPickerRecord");
var ProductPickerQuickCreateForm = require(
"web_widget_one2many_product_picker.ProductPickerQuickCreateForm");
var qweb = core.qweb;
@@ -19,6 +21,9 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
events: {
'click #productPickerLoadMore': '_onClickLoadMore',
},
custom_events: {
'record_flip': '_onRecordFlip',
},
DELAY_GET_RECORDS: 150,
MIN_PERC_GET_RECORDS: 0.9,
@@ -62,8 +67,9 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
* @param {Object} widget
*/
removeWidget: function (widget) {
this.widgets.splice(this.widgets.indexOf(widget), 1);
var index = this.widgets.indexOf(widget);
widget.destroy();
delete this.widgets[index];
},
/**
@@ -98,14 +104,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
*/
updateState: function (state, params) {
var self = this;
var sparams = _.extend({}, params, {noRender: true});
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);
return this._super(
state,
_.extend({}, params, {noRender: true})
).then(function () {
return this._super(state, sparams).then(function () {
self._updateStateRecords(old_state);
});
},
@@ -115,7 +119,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
* @param {Array[Object]} states
* @returns {Deferred}
*/
_removeRecords: function (states) {
_removeRecords: function (states, new_states) {
var defs = [];
var to_destroy = [];
for (var index_state in states) {
@@ -128,7 +132,6 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
}
}
}
this.widgets = _.compact(this.widgets);
if (this.search_group.name === "main_lines") {
_.invoke(to_destroy, "destroy");
@@ -142,18 +145,33 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
var widget_product_id = widget_destroyed.state
.data[this.options.field_map.product].data.id;
var found = false;
// If already exists a widget for the product don't try create a new one
for (var eb = this.widgets.length-1; eb>=0; --eb) {
var widget = this.widgets[eb];
if (
widget &&
widget.state &&
widget.state.data[this.options.field_map.product].data.id === widget_product_id
) {
found = true;
break;
}
}
if (!found) {
// Get the new state ID if exists to link it with the new record
var new_state_id = undefined;
for (var eb = new_states.length-1; eb>=0; --eb) {
var state = new_states[eb];
if (
state.data[this.options.field_map.product].data.id === widget_product_id
) {
new_state_id = state.id;
break;
}
}
var search_record = _.find(this.search_data, {id: widget_product_id});
var new_search_record = _.extend({}, search_record, {__id: state.id});
var new_search_record = _.extend({}, search_record, {__id: new_state_id});
var search_record_index = widget_destroyed.$el.index();
defs.push(
this.appendSearchRecords(
@@ -188,7 +206,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
var found = false;
for (var e in this.state.data) {
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;
break;
}
@@ -197,7 +215,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
states_to_destroy.push(old_state);
}
}
this._removeRecords(states_to_destroy);
this._removeRecords(states_to_destroy, this.state.data);
// Records to Update or Create
var defs = [];
@@ -209,12 +227,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
var search_record = false;
for (var e = this.widgets.length-1; e>=0; --e) {
var widget = this.widgets[e];
if (!widget) {
if (!widget || !widget.state) {
// Already processed widget (deleted)
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);
exists = true;
break;
@@ -243,7 +261,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
defs.push(this.appendSearchRecords([new_search_record], false, true, search_record_index)[0]);
}
}
this.widgets = _.compact(this.widgets);
_.invoke(to_destroy, "destroy");
return $.when(defs);
},
@@ -253,7 +271,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
*/
_renderView: function () {
var self = this;
var oldWidgets = this.widgets;
var oldWidgets = _.compact(this.widgets);
this.widgets = [];
this.$recordsContainer = $("<DIV/>", {
class: "w-100 row",
@@ -277,7 +295,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
* @param {Array[Object]} results
@@ -360,10 +379,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
no_process_records?search_records:this._processSearchRecords(search_records);
_.each(processed_records, function (search_record) {
var state_data = self._getRecordDataById(search_record.__id);
var widget_options = self._getRecordOptions(search_record);
widget_options.renderer_widget_index = self.widgets.length;
var ProductPickerRecord = new One2ManyProductPickerRecord(
self,
state_data,
self._getRecordOptions(search_record)
widget_options
);
self.widgets.push(ProductPickerRecord);
@@ -380,12 +401,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
// the search data. Using search data instead of waiting for
// simulated state gives a low FCP time.
var def = ProductPickerRecord.appendTo(self.$recordsContainer)
.then(function () {
if (typeof position !== "undefined") {
var $elm = self.$el.find("> div > div:nth("+position+")");
ProductPickerRecord.$el.insertAfter($elm);
.then(function (widget, widget_position) {
if (typeof widget_position !== "undefined") {
var $elm = this.$el.find("> div > div:nth("+widget_position+")");
widget.$el.insertAfter($elm);
}
});
}.bind(self, ProductPickerRecord, position));
if (def.state() === "pending") {
self.defs.push(def);
}
@@ -441,6 +462,53 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
this._loadMoreWorking = true;
},
/**
* Do card flip
*
* @param {Integer} index
*/
doWidgetFlip: function (index) {
var widget = this.widgets[index];
var $actived_card = this.$el.find(".active");
if (widget.$card.hasClass("active")) {
widget.$card.removeClass("active");
widget.$card.find('.oe_flip_card_front').removeClass("d-none");
} else {
var self = widget;
widget.defs = [];
widget._processWidgetFields(widget.$back);
widget._processWidgets(widget.$back);
widget._processDynamicFields();
$.when(widget.defs).then(function () {
$actived_card.removeClass("active");
$actived_card.find('.oe_flip_card_front').removeClass("d-none");
self.$card.addClass("active");
setTimeout(function () {
self.$('.oe_flip_card_front').addClass("d-none");
}, 200);
});
}
},
/**
* Handle card flip.
* Used to create/update the record
*
* @param {CustomEvent} evt
*/
_onRecordFlip: function (evt) {
var prev_widget_index = evt.data.prev_widget_index;
if (typeof prev_widget_index !== "undefined") {
// Only check 'back' widgets so there is where the form was created
for (var index in this.widgets[prev_widget_index].widgets.back) {
var widget = this.widgets[prev_widget_index].widgets.back[index];
if (widget instanceof ProductPickerQuickCreateForm) {
widget.controller.auto();
}
}
}
}
});
return One2ManyProductPickerRenderer;

View File

@@ -580,11 +580,20 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
* @param {CustomEvent} evt
*/
_onCreateQuickRecord: function (evt) {
var self = this;
this.parent_controller.model.setPureVirtual(evt.data.id, false);
this._setValue({operation: "ADD", id: evt.data.id});
if (this.options.auto_save) {
this.parent_controller.saveRecord(undefined, {stayInEdit: true});
if (!self.options.auto_save) {
self.parent_controller.model.updateRecordContext(evt.data.id, {
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
*/
_onUpdateQuickRecord: function (evt) {
this._setValue({operation: "UPDATE", id: evt.data.id, data: evt.data.data});
if (this.options.auto_save) {
this.parent_controller.saveRecord(undefined, {stayInEdit: true});
var self = this;
if (!self.options.auto_save) {
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

@@ -1,7 +1,6 @@
$one2many-product-picker-max-height: 500px;
$one2many-product-picker-card-min-height: 150px;
$one2many-product-picker-card-max-height: 150px;
$one2many-product-picker-transition-3d-time: 0.3s;
$one2many-product-picker-transition-3d-time: 0.15s;
$one2many-product-picker-card-form-padding: 0em;
$one2many-product-picker-quick-modif-price-max-width: 400px;
$one2many-product-picker-zoom-scale: 1.4;

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;
height: $one2many-product-picker-card-min-height;
&.blocked {
filter: blur(2px);
}
&.disabled {
filter: grayscale(100%);
opacity: 0.5;
@@ -211,6 +215,9 @@
font-size: 0.95rem;
z-index: 0;
}
.add_product, .product_qty, .price_unit {
cursor: pointer;
}
}
}
}
@@ -230,3 +237,20 @@
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 t-attf-class="oe_flip_card {{!state &amp;&amp; 'disabled' || ''}}">
<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="!is_virtual">
<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>
</t>
<div class="position-absolute m-0 text-left badge_price">

View File

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

View File

@@ -26,8 +26,6 @@ odoo.define('web_widget_one2many_product_picker.widget_tests', function (require
'</form>';
};
console.log(getArch());
QUnit.module('Web Widget One2Many Product Picker', {
beforeEach: function () {
this.data = {