mirror of https://github.com/OCA/web.git
[IMP] web_widget_one2many_product_picker: Refactor code
- fix: Search with wildcard - fix: Search input events on firefox - fix: Update lazy qty when use the tiled form - fix: Handle race conditions - fix: Return promise when recreate 'zombie' record - fix: Remove record - imp: Performancepull/2074/head
parent
6aa1f8e7b9
commit
898aca2511
|
@ -86,6 +86,7 @@ Widget options:
|
|||
* show_discount > Enable/Disable display discount (False by default)
|
||||
* show_subtotal > Enable/Disable show subtotal (True by default)
|
||||
* auto_save > Enable/Disable auto save (False by default)
|
||||
* auto_save_delay > The time (in milliseconds) to wait after the last interaction before launching the autosave (1500 by default)
|
||||
* all_domain > The domain used in 'All' section ([] by default)
|
||||
|
||||
If using auto save feature, you should keep in mind that the "Save" and "Discard" buttons
|
||||
|
@ -95,6 +96,7 @@ Widget options:
|
|||
* 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)
|
||||
* auto_focus > Keep the focus on the search box after performing a search (True by default)
|
||||
|
||||
All widget options are optional.
|
||||
Notice that you can call '_' method to use translations. This only can be used with this widget.
|
||||
|
@ -215,7 +217,8 @@ Known issues / Roadmap
|
|||
======================
|
||||
|
||||
* Translations in the xml 'options' attribute of the field that use the widget can't be exported automatically to be translated
|
||||
* The product card animations can be improved. Currently the card is recreated, so we lost some elements to apply correct effects.
|
||||
* The product card animations can be improved. Currently the card is recreated, so we lost some elements to apply correct effects
|
||||
* sale.order onchanges that affects to "order_line" subfields will be ommitted to increase the performance
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
@ -244,6 +247,8 @@ Contributors
|
|||
* Pedro M. Baeza
|
||||
* David Vidal
|
||||
|
||||
* Giovanni Serra <giovanni@gslab.it>
|
||||
|
||||
Maintainers
|
||||
~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
# Copyright 2020 Tecnativa - Alexandre Díaz
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from . import models
|
||||
|
|
|
@ -6,118 +6,143 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 12.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2021-02-17 13:45+0000\n"
|
||||
"Last-Translator: claudiagn <claudia.gargallo@qubiq.es>\n"
|
||||
"Language-Team: none\n"
|
||||
"POT-Creation-Date: 2022-03-04 18:46+0000\n"
|
||||
"PO-Revision-Date: 2022-03-04 19:49+0100\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.3.2\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: \n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml:6
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml:0
|
||||
#, python-format
|
||||
msgid "Accept"
|
||||
msgstr "Aceptar"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml:0
|
||||
#, python-format
|
||||
msgid "Add"
|
||||
msgstr "Añadir"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/js/widgets/field_one2many_product_picker.js:193
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/js/widgets/field_one2many_product_picker.js:0
|
||||
#, python-format
|
||||
msgid "All"
|
||||
msgstr "Todo"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: model:ir.model,name:web_widget_one2many_product_picker.model_base
|
||||
msgid "Base"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:35
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/js/widgets/field_one2many_product_picker.js:0
|
||||
#, python-format
|
||||
msgid "By Name"
|
||||
msgstr "Por Nombre"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: code:addons/web_widget_one2many_product_picker/models/base.py:0
|
||||
#, python-format
|
||||
msgid "Can't create the %s: Duplicated product! (Already in database)"
|
||||
msgstr "No se puede crear el %s: Producto duplicado! (Actualmente en la base de datos)"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: code:addons/web_widget_one2many_product_picker/models/base.py:0
|
||||
#, python-format
|
||||
msgid "Can't create the %s: Duplicated product! (Inside query)"
|
||||
msgstr "No se puede crear el %s: Producto duplicado! (En la petición)"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: code:addons/web_widget_one2many_product_picker/models/base.py:0
|
||||
#, python-format
|
||||
msgid "Can't write the %s: Duplicated product! (Already in database)"
|
||||
msgstr "No se puede escribir el %s: Producto duplicado! (Actualmente en la base de datos)"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml:0
|
||||
#, python-format
|
||||
msgid "Discard"
|
||||
msgstr "Descartar"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:0
|
||||
#, python-format
|
||||
msgid "Groups"
|
||||
msgstr "Grupos"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:43
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:0
|
||||
#, python-format
|
||||
msgid "LOADING..."
|
||||
msgstr "CARGANDO..."
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:0
|
||||
#, python-format
|
||||
msgid "Lines"
|
||||
msgstr "Línias"
|
||||
msgstr "Líneas"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:67
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:0
|
||||
#, python-format
|
||||
msgid "Load More"
|
||||
msgstr "Carga más"
|
||||
msgstr "Cargar más"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_modif_price.xml:11
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_modif_price.xml:0
|
||||
#, python-format
|
||||
msgid "Price:"
|
||||
msgstr "Precio:"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: model:ir.model,name:web_widget_one2many_product_picker.model_product_product
|
||||
msgid "Product"
|
||||
msgstr "Producto"
|
||||
msgid "Price"
|
||||
msgstr "Precio"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml:13
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml:0
|
||||
#, python-format
|
||||
msgid "Remove"
|
||||
msgstr "Eliminar"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:23
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:0
|
||||
#, python-format
|
||||
msgid "Search..."
|
||||
msgstr "Buscar..."
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:58
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:0
|
||||
#, python-format
|
||||
msgid "Subtotal:"
|
||||
msgstr "Subtotal:"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: model:ir.model.fields,help:web_widget_one2many_product_picker.field_product_product__image_variant_medium
|
||||
msgid ""
|
||||
"This field holds the image used as image for the product variantor product "
|
||||
"image medium, limited to 512x512px."
|
||||
msgstr ""
|
||||
"Aquest camp conté la imatge que s'utilitza com a imatge per al mitjà "
|
||||
"d'imatge del producte que varia el producte, limitada a 512x512px."
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: model:ir.model.fields,help:web_widget_one2many_product_picker.field_product_product__image_variant_big
|
||||
msgid ""
|
||||
"This field holds the image used as image for the product variantor product "
|
||||
"image, limited to 1024x1024px."
|
||||
msgstr ""
|
||||
"Aquest camp conté la imatge que s'utilitza com a imatge per al mitjà "
|
||||
"d'imatge del producte que varia el producte, limitada a 1024x1024px."
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: model:ir.model.fields,field_description:web_widget_one2many_product_picker.field_product_product__image_variant_big
|
||||
msgid "Variant Image Big (Computed)"
|
||||
msgstr "Imagen variante grande (calculada)"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: model:ir.model.fields,field_description:web_widget_one2many_product_picker.field_product_product__image_variant_medium
|
||||
msgid "Variant Image Medium (Computed)"
|
||||
msgstr "Imagen variante media (calculada)"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/js/views/One2ManyProductPicker/record.js:341
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/js/views/One2ManyProductPicker/record.js:0
|
||||
#, python-format
|
||||
msgid "[No widget %s]"
|
||||
msgstr "[Sin widget %s]"
|
||||
|
||||
#~ msgid "Add 1"
|
||||
#~ msgstr "Añadir 1"
|
||||
#~ msgid "HTTP Routing"
|
||||
#~ msgstr "Ruta HTTP"
|
||||
|
||||
#~ msgid "Pricelist Item"
|
||||
#~ msgstr "Item de Lista de precios"
|
||||
|
||||
#~ msgid "Product"
|
||||
#~ msgstr "Producto"
|
||||
|
|
|
@ -6,6 +6,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 13.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-03-04 18:46+0000\n"
|
||||
"PO-Revision-Date: 2022-03-04 18:46+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -13,6 +15,13 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml:0
|
||||
#, python-format
|
||||
msgid "Accept"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml:0
|
||||
|
@ -27,6 +36,11 @@ msgstr ""
|
|||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: model:ir.model,name:web_widget_one2many_product_picker.model_base
|
||||
msgid "Base"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/js/widgets/field_one2many_product_picker.js:0
|
||||
|
@ -34,6 +48,32 @@ msgstr ""
|
|||
msgid "By Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: code:addons/web_widget_one2many_product_picker/models/base.py:0
|
||||
#, python-format
|
||||
msgid "Can't create the %s: Duplicated product! (Already in database)"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: code:addons/web_widget_one2many_product_picker/models/base.py:0
|
||||
#, python-format
|
||||
msgid "Can't create the %s: Duplicated product! (Inside query)"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#: code:addons/web_widget_one2many_product_picker/models/base.py:0
|
||||
#, python-format
|
||||
msgid "Can't write the %s: Duplicated product! (Already in database)"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml:0
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml:0
|
||||
#, python-format
|
||||
msgid "Discard"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:0
|
||||
|
@ -41,6 +81,13 @@ msgstr ""
|
|||
msgid "Groups"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:0
|
||||
#, python-format
|
||||
msgid "LOADING..."
|
||||
msgstr ""
|
||||
|
||||
#. module: web_widget_one2many_product_picker
|
||||
#. openerp-web
|
||||
#: code:addons/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker.xml:0
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from . import base
|
|
@ -0,0 +1,60 @@
|
|||
# Copyright 2022 Tecnativa - Alexandre D. Díaz
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
from odoo import _, api, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class BaseModel(models.BaseModel):
|
||||
_inherit = "base"
|
||||
|
||||
@api.model
|
||||
def _check_product_picker_duplicated_products(self, vals_list):
|
||||
relation = self.env.context.get("product_picker_relation")
|
||||
if relation != self._name or not len(vals_list):
|
||||
return
|
||||
product_field = self.env.context.get("product_picker_product_field")
|
||||
product_ids = [
|
||||
values[product_field] for values in vals_list if product_field in values
|
||||
]
|
||||
num_products = len(product_ids)
|
||||
if not num_products:
|
||||
return
|
||||
elif num_products != len(set(product_ids)):
|
||||
raise ValidationError(
|
||||
_("Can't create the %s: Duplicated product! (Inside query)") % relation
|
||||
)
|
||||
relation_field = self.env.context.get("product_picker_relation_field")
|
||||
# All records have the same 'relation id' when created with the product picker
|
||||
relation_id = vals_list[0][relation_field]
|
||||
# When write maybe need get the value from the record
|
||||
if not relation_id:
|
||||
field_obj = self[relation_field]
|
||||
if field_obj:
|
||||
relation_id = relation_id.id
|
||||
has_product = (
|
||||
self.search(
|
||||
[
|
||||
(relation_field, "=", relation_id),
|
||||
(product_field, "in", product_ids),
|
||||
],
|
||||
count=True,
|
||||
limit=1,
|
||||
)
|
||||
!= 0
|
||||
)
|
||||
if has_product:
|
||||
raise ValidationError(
|
||||
_("Can't create the %s: Duplicated product! (Already in database)")
|
||||
% relation
|
||||
)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
"""Avoid create lines that have a product currently used when use the product picker"""
|
||||
self._check_product_picker_duplicated_products(vals_list)
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, values):
|
||||
"""Avoid write lines that have a product currently used when use the product picker"""
|
||||
self._check_product_picker_duplicated_products([values])
|
||||
return super().write(values)
|
|
@ -44,7 +44,7 @@ Widget options:
|
|||
* show_discount > Enable/Disable display discount (False by default)
|
||||
* show_subtotal > Enable/Disable show subtotal (True by default)
|
||||
* auto_save > Enable/Disable auto save (False by default)
|
||||
* all_domain > The domain used in 'All' section ([] by default)
|
||||
* auto_save_delay > The time (in milliseconds) to wait after the last interaction before launching the autosave (1500 by default)
|
||||
|
||||
If using auto save feature, you should keep in mind that the "Save" and "Discard" buttons
|
||||
will lose part of its functionality as the document will be saved every time you
|
||||
|
@ -53,6 +53,7 @@ Widget options:
|
|||
* 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)
|
||||
* auto_focus > Keep the focus on the search box after performing a search (True by default)
|
||||
|
||||
All widget options are optional.
|
||||
Notice that you can call '_' method to use translations. This only can be used with this widget.
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
* Translations in the xml 'options' attribute of the field that use the widget can't be exported automatically to be translated
|
||||
* The product card animations can be improved. Currently the card is recreated, so we lost some elements to apply correct effects.
|
||||
* The product card animations can be improved. Currently the card is recreated, so we lost some elements to apply correct effects
|
||||
* sale.order onchanges that affects to "order_line" subfields will be ommitted to increase the performance
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
|
||||
<title>Web Widget One2Many Product Picker</title>
|
||||
<style type="text/css">
|
||||
|
||||
|
@ -474,6 +474,8 @@ You need to define the view fields. The view must be of <tt class="docutils lite
|
|||
</li>
|
||||
<li><p class="first">auto_save > Enable/Disable auto save (False by default)</p>
|
||||
</li>
|
||||
<li><p class="first">auto_save_delay > The time (in milliseconds) to wait after the last interaction before launching the autosave (1500 by default)</p>
|
||||
</li>
|
||||
<li><p class="first">all_domain > The domain used in ‘All’ section ([] by default)</p>
|
||||
<p>If using auto save feature, you should keep in mind that the “Save” and “Discard” buttons
|
||||
will lose part of its functionality as the document will be saved every time you
|
||||
|
@ -485,6 +487,8 @@ modify/create a record with the widget.</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>
|
||||
<li><p class="first">auto_focus > Keep the focus on the search box after performing a search (True by default)</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>All widget options are optional.
|
||||
Notice that you can call ‘_’ method to use translations. This only can be used with this widget.</p>
|
||||
|
@ -605,7 +609,8 @@ accept changes.</p>
|
|||
<h1><a class="toc-backref" href="#id10">Known issues / Roadmap</a></h1>
|
||||
<ul class="simple">
|
||||
<li>Translations in the xml ‘options’ attribute of the field that use the widget can’t 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>
|
||||
<li>sale.order onchanges that affects to “order_line” subfields will be ommitted to increase the performance</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
|
@ -636,6 +641,8 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
|
|||
</ul>
|
||||
</blockquote>
|
||||
</li>
|
||||
<li><p class="first">Giovanni Serra <<a class="reference external" href="mailto:giovanni@gslab.it">giovanni@gslab.it</a>></p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
|
|
|
@ -3,17 +3,35 @@
|
|||
odoo.define("web_widget_one2many_product_picker.tools", function(require) {
|
||||
"use strict";
|
||||
|
||||
const field_utils = require("web.field_utils");
|
||||
var field_utils = require("web.field_utils");
|
||||
|
||||
/**
|
||||
* Truncate floats
|
||||
*
|
||||
* @param {Number} value
|
||||
* @param {Object} field_info
|
||||
* @param {Array} digist
|
||||
* @returns {Number}
|
||||
*/
|
||||
function float(value, field_info, digist) {
|
||||
var options = digist && {digist: digist};
|
||||
return field_utils.format.float(value, field_info, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the price with discount
|
||||
*
|
||||
* @param {Number} price
|
||||
* @param {Number} discount
|
||||
* @param {Array} digist
|
||||
* @returns {Number}
|
||||
*/
|
||||
function priceReduce(price, discount) {
|
||||
return price * (1.0 - discount / 100.0);
|
||||
function priceReduce(price, discount, digist) {
|
||||
var price_reduce = price * (1.0 - discount / 100.0);
|
||||
if (digist) {
|
||||
return float(price_reduce, undefined, digist);
|
||||
}
|
||||
return price_reduce;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,12 +52,6 @@ odoo.define("web_widget_one2many_product_picker.tools", function(require) {
|
|||
});
|
||||
}
|
||||
|
||||
function float(value, field_info, digits) {
|
||||
return field_utils.format.float(value, field_info, {
|
||||
digits: digits,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
monetary: monetary,
|
||||
float: float,
|
||||
|
|
|
@ -6,27 +6,23 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateForm", f
|
|||
) {
|
||||
"use strict";
|
||||
|
||||
const core = require("web.core");
|
||||
const Widget = require("web.Widget");
|
||||
const widgetRegistry = require("web.widget_registry");
|
||||
const ProductPickerQuickCreateFormView = require("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView")
|
||||
var core = require("web.core");
|
||||
var Widget = require("web.Widget");
|
||||
var widgetRegistry = require("web.widget_registry");
|
||||
var ProductPickerQuickCreateFormView = require("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView")
|
||||
.ProductPickerQuickCreateFormView;
|
||||
|
||||
const qweb = core.qweb;
|
||||
var qweb = core.qweb;
|
||||
|
||||
/**
|
||||
* This widget render a Form. Used by FieldOne2ManyProductPicker
|
||||
*/
|
||||
const ProductPickerQuickCreateForm = Widget.extend({
|
||||
var ProductPickerQuickCreateForm = Widget.extend({
|
||||
className: "oe_one2many_product_picker_quick_create",
|
||||
xmlDependencies: [
|
||||
"/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_create.xml",
|
||||
],
|
||||
|
||||
custom_events: {
|
||||
reload_view: "_onReloadView",
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
|
@ -51,54 +47,53 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateForm", f
|
|||
* @override
|
||||
*/
|
||||
start: function() {
|
||||
const def1 = this._super.apply(this, arguments);
|
||||
const form_arch = this._generateFormArch();
|
||||
const fieldsView = {
|
||||
arch: form_arch,
|
||||
fields: this.fields,
|
||||
viewFields: this.fields,
|
||||
base_model: this.basicFieldParams.field.relation,
|
||||
type: "form",
|
||||
model: this.basicFieldParams.field.relation,
|
||||
};
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function() {
|
||||
var form_arch = self._generateFormArch();
|
||||
var fieldsView = {
|
||||
arch: form_arch,
|
||||
fields: self.fields,
|
||||
viewFields: self.fields,
|
||||
base_model: self.basicFieldParams.field.relation,
|
||||
type: "form",
|
||||
model: self.basicFieldParams.field.relation,
|
||||
};
|
||||
|
||||
const node_context = this.node.attr("context") || "{}";
|
||||
this.nodeContext = py.eval(node_context, {
|
||||
active_id: this.res_id || false,
|
||||
var node_context = self.node.attr("context") || "{}";
|
||||
self.nodeContext = py.eval(node_context, {
|
||||
active_id: self.res_id || false,
|
||||
});
|
||||
var refinedContext = _.extend(
|
||||
{},
|
||||
self.main_state.getContext(),
|
||||
self.nodeContext
|
||||
);
|
||||
_.extend(refinedContext, self.editContext);
|
||||
self.formView = new ProductPickerQuickCreateFormView(fieldsView, {
|
||||
context: refinedContext,
|
||||
compareKey: self.compareKey,
|
||||
fieldMap: self.fieldMap,
|
||||
modelName: self.basicFieldParams.field.relation,
|
||||
userContext: self.getSession().user_context,
|
||||
ids: self.res_id ? [self.res_id] : [],
|
||||
currentId: self.res_id || undefined,
|
||||
mode: self.res_id && self.readonly ? "readonly" : "edit",
|
||||
recordID: self.id,
|
||||
index: 0,
|
||||
parentID: self.basicFieldParams.parentID,
|
||||
default_buttons: false,
|
||||
withControlPanel: false,
|
||||
model: self.basicFieldParams.model,
|
||||
mainRecordData: self.getParent().getParent().state,
|
||||
});
|
||||
return self.formView.getController(self).then(function(controller) {
|
||||
self.controller = controller;
|
||||
self.$el.empty();
|
||||
self.controller.appendTo(self.$el);
|
||||
self.trigger_up("back_form_loaded");
|
||||
return controller;
|
||||
});
|
||||
});
|
||||
const refinedContext = _.extend(
|
||||
{},
|
||||
this.main_state.getContext(),
|
||||
this.nodeContext
|
||||
);
|
||||
_.extend(refinedContext, this.editContext);
|
||||
this.formView = new ProductPickerQuickCreateFormView(fieldsView, {
|
||||
context: refinedContext,
|
||||
compareKey: this.compareKey,
|
||||
fieldMap: this.fieldMap,
|
||||
modelName: this.basicFieldParams.field.relation,
|
||||
userContext: this.getSession().user_context,
|
||||
ids: this.res_id ? [this.res_id] : [],
|
||||
currentId: this.res_id || undefined,
|
||||
mode: this.res_id && this.readonly ? "readonly" : "edit",
|
||||
recordID: this.id,
|
||||
index: 0,
|
||||
parentID: this.basicFieldParams.parentID,
|
||||
default_buttons: false,
|
||||
withControlPanel: false,
|
||||
model: this.basicFieldParams.model,
|
||||
mainRecordData: this.getParent().getParent().state,
|
||||
});
|
||||
// If (this.id) {
|
||||
// this.basicFieldParams.model.save(this.id, {savePoint: true});
|
||||
// }
|
||||
const def2 = this.formView.getController(this).then(controller => {
|
||||
this.controller = controller;
|
||||
this.$el.empty();
|
||||
this.controller.appendTo(this.$el);
|
||||
});
|
||||
|
||||
return Promise.all([def1, def2]);
|
||||
},
|
||||
|
||||
on_attach_callback: function() {
|
||||
|
@ -112,7 +107,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateForm", f
|
|||
* @returns {String}
|
||||
*/
|
||||
_generateFormArch: function() {
|
||||
let template =
|
||||
var template =
|
||||
"<templates><t t-name='One2ManyProductPicker.QuickCreateForm'>";
|
||||
template += this.basicFieldParams.field.views.form.arch;
|
||||
template += "</t></templates>";
|
||||
|
@ -122,42 +117,6 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateForm", f
|
|||
record_search: this.searchRecord,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {CustomEvent} evt
|
||||
*/
|
||||
_onReloadView: function(evt) {
|
||||
this.editContext = {
|
||||
ignore_onchanges: [this.compareKey],
|
||||
base_record_id: evt.data.baseRecordID || null,
|
||||
base_record_res_id: evt.data.baseRecordResID || null,
|
||||
base_record_compare_value: evt.data.baseRecordCompareValue || null,
|
||||
};
|
||||
|
||||
if (evt.data.baseRecordCompareValue === evt.data.compareValue) {
|
||||
this.res_id = evt.data.baseRecordResID;
|
||||
this.id = evt.data.baseRecordID;
|
||||
this.start();
|
||||
} else {
|
||||
this.getParent()
|
||||
._generateVirtualState({}, this.editContext)
|
||||
.then(state => {
|
||||
const data = {};
|
||||
data[this.compareKey] = {
|
||||
operation: "ADD",
|
||||
id: evt.data.compareValue,
|
||||
};
|
||||
this.basicFieldParams.model
|
||||
._applyChange(state.id, data)
|
||||
.then(() => {
|
||||
this.res_id = state.res_id;
|
||||
this.id = state.id;
|
||||
this.start();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
widgetRegistry.add(
|
||||
|
|
|
@ -10,19 +10,19 @@ odoo.define(
|
|||
* is used by the RecordQuickCreate in One2ManyProductPicker views.
|
||||
*/
|
||||
|
||||
const QuickCreateFormView = require("web.QuickCreateFormView");
|
||||
const BasicModel = require("web.BasicModel");
|
||||
const core = require("web.core");
|
||||
var QuickCreateFormView = require("web.QuickCreateFormView");
|
||||
var BasicModel = require("web.BasicModel");
|
||||
var core = require("web.core");
|
||||
|
||||
const qweb = core.qweb;
|
||||
var qweb = core.qweb;
|
||||
|
||||
BasicModel.include({
|
||||
_applyOnChange: function(values, record, viewType) {
|
||||
// Ignore changes by record context 'ignore_onchanges' fields
|
||||
if ("ignore_onchanges" in record.context) {
|
||||
const ignore_changes = record.context.ignore_onchanges;
|
||||
for (const index in ignore_changes) {
|
||||
const field_name = ignore_changes[index];
|
||||
var ignore_changes = record.context.ignore_onchanges;
|
||||
for (var index in ignore_changes) {
|
||||
var field_name = ignore_changes[index];
|
||||
delete values[field_name];
|
||||
}
|
||||
delete record.context.ignore_onchanges;
|
||||
|
@ -31,7 +31,7 @@ odoo.define(
|
|||
},
|
||||
});
|
||||
|
||||
const ProductPickerQuickCreateFormRenderer = QuickCreateFormView.prototype.config.Renderer.extend(
|
||||
var ProductPickerQuickCreateFormRenderer = QuickCreateFormView.prototype.config.Renderer.extend(
|
||||
{
|
||||
/**
|
||||
* @override
|
||||
|
@ -45,7 +45,7 @@ odoo.define(
|
|||
}
|
||||
);
|
||||
|
||||
const ProductPickerQuickCreateFormController = QuickCreateFormView.prototype.config.Controller.extend(
|
||||
var ProductPickerQuickCreateFormController = QuickCreateFormView.prototype.config.Controller.extend(
|
||||
{
|
||||
events: _.extend({}, QuickCreateFormView.prototype.events, {
|
||||
"click .oe_record_add": "_onClickAdd",
|
||||
|
@ -66,8 +66,9 @@ odoo.define(
|
|||
* @override
|
||||
*/
|
||||
_applyChanges: function() {
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
this._updateButtons();
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function() {
|
||||
self._updateButtons();
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -75,17 +76,13 @@ odoo.define(
|
|||
* Create or accept changes
|
||||
*/
|
||||
auto: function() {
|
||||
const record = this.model.get(this.handle);
|
||||
if (
|
||||
record.context.has_changes_confirmed ||
|
||||
typeof record.context.has_changes_confirmed === "undefined"
|
||||
) {
|
||||
var record = this.model.get(this.handle);
|
||||
if (!record.context.has_changes_unconfirmed) {
|
||||
return;
|
||||
}
|
||||
const state = this._getRecordState();
|
||||
if (state === "new") {
|
||||
if (this.model.isNew(record.id)) {
|
||||
this._add();
|
||||
} else if (state === "dirty") {
|
||||
} else if (this.model.isDirty(record.id)) {
|
||||
this._change();
|
||||
}
|
||||
},
|
||||
|
@ -99,16 +96,19 @@ odoo.define(
|
|||
* @returns {Object}
|
||||
*/
|
||||
_getRecordState: function() {
|
||||
const record = this.model.get(this.handle);
|
||||
let state = "record";
|
||||
if (this.model.isNew(record.id)) {
|
||||
var record = this.model.get(this.handle);
|
||||
var state = "record";
|
||||
if (
|
||||
this.model.isNew(record.id) &&
|
||||
this.model.isPureVirtual(record.id)
|
||||
) {
|
||||
state = "new";
|
||||
} else if (record.isDirty()) {
|
||||
} else if (record.context.has_changes_unconfirmed) {
|
||||
state = "dirty";
|
||||
}
|
||||
if (state === "new") {
|
||||
for (const index in this.mainRecordData.data) {
|
||||
const recordData = this.mainRecordData.data[index];
|
||||
for (var index in this.mainRecordData.data) {
|
||||
var recordData = this.mainRecordData.data[index];
|
||||
if (recordData.ref === record.ref) {
|
||||
if (record.isDirty()) {
|
||||
state = "dirty";
|
||||
|
@ -174,8 +174,8 @@ odoo.define(
|
|||
* @returns {Boolean}
|
||||
*/
|
||||
_needReloadCard: function(fields_changed) {
|
||||
for (const index in fields_changed) {
|
||||
const field = fields_changed[index];
|
||||
for (var index in fields_changed) {
|
||||
var field = fields_changed[index];
|
||||
if (field === this.fieldMap[this.compareKey]) {
|
||||
return true;
|
||||
}
|
||||
|
@ -192,51 +192,18 @@ odoo.define(
|
|||
* @param {ChangeEvent} ev
|
||||
*/
|
||||
_onFieldChanged: function(ev) {
|
||||
const fields_changed = Object.keys(ev.data.changes);
|
||||
if (this._needReloadCard(fields_changed)) {
|
||||
const field = ev.data.changes[fields_changed[0]];
|
||||
let new_value = false;
|
||||
if (typeof field === "object") {
|
||||
new_value = field.id;
|
||||
} else {
|
||||
new_value = field;
|
||||
}
|
||||
const reload_values = {
|
||||
compareValue: new_value,
|
||||
};
|
||||
const record = this.model.get(this.handle);
|
||||
if ("base_record_id" in record.context) {
|
||||
reload_values.baseRecordID = record.context.base_record_id;
|
||||
reload_values.baseRecordResID =
|
||||
record.context.base_record_res_id;
|
||||
reload_values.baseRecordCompareValue =
|
||||
record.context.base_record_compare_value;
|
||||
} else {
|
||||
let old_value = record.data[this.compareKey];
|
||||
if (typeof old_value === "object") {
|
||||
old_value = old_value.data.id;
|
||||
}
|
||||
reload_values.baseRecordID = record.id;
|
||||
reload_values.baseRecordResID = record.ref;
|
||||
reload_values.baseRecordCompareValue = old_value;
|
||||
}
|
||||
this.trigger_up("reload_view", reload_values);
|
||||
|
||||
// Discard current change
|
||||
ev.data.changes = {};
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
if (!_.isEmpty(ev.data.changes)) {
|
||||
if (this.model.isPureVirtual(this.handle)) {
|
||||
this.model.unsetDirty(this.handle);
|
||||
}
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
has_changes_confirmed: false,
|
||||
});
|
||||
this.trigger_up("quick_record_updated", {
|
||||
changes: ev.data.changes,
|
||||
});
|
||||
this._super.apply(this, arguments);
|
||||
if (!_.isEmpty(ev.data.changes)) {
|
||||
if (this.model.isPureVirtual(this.handle)) {
|
||||
this.model.unsetDirty(this.handle);
|
||||
}
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
has_changes_unconfirmed: true,
|
||||
});
|
||||
this.trigger_up("quick_record_updated", {
|
||||
changes: ev.data.changes,
|
||||
highlight: {qty: true},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -244,12 +211,14 @@ odoo.define(
|
|||
* @returns {Deferred}
|
||||
*/
|
||||
_add: function() {
|
||||
var self = this;
|
||||
if (this._disabled) {
|
||||
// Don't do anything if we are already creating a record
|
||||
return Promise.resolve();
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
has_changes_confirmed: true,
|
||||
need_notify: true,
|
||||
modified: true,
|
||||
});
|
||||
this._disableQuickCreate();
|
||||
return this.saveRecord(this.handle, {
|
||||
|
@ -257,105 +226,145 @@ odoo.define(
|
|||
reload: true,
|
||||
savePoint: true,
|
||||
viewType: "form",
|
||||
}).then(() => {
|
||||
const record = this.model.get(this.handle);
|
||||
this.model.updateRecordContext(this.handle, {saving: true});
|
||||
this.trigger_up("restore_flip_card", {
|
||||
success_callback: () => {
|
||||
this.trigger_up("create_quick_record", {
|
||||
}).then(function() {
|
||||
var record = self.model.get(self.handle);
|
||||
self.model.updateRecordContext(record.id, {
|
||||
has_changes_unconfirmed: false,
|
||||
lazy_qty: record.data[self.fieldMap.product_uom_qty],
|
||||
});
|
||||
self.trigger_up("block_card", {status: true});
|
||||
self.trigger_up("modify_quick_record", {
|
||||
id: record.id,
|
||||
});
|
||||
self.trigger_up("restore_flip_card", {
|
||||
success_callback: function() {
|
||||
self.trigger_up("create_quick_record", {
|
||||
id: record.id,
|
||||
callback: () => {
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
saving: false,
|
||||
});
|
||||
this.model.unsetDirty(this.handle);
|
||||
this._enableQuickCreate();
|
||||
on_onchange: function() {
|
||||
self.trigger_up("block_card", {status: false});
|
||||
self._enableQuickCreate();
|
||||
},
|
||||
});
|
||||
},
|
||||
block: true,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_remove: function() {
|
||||
var self = this;
|
||||
if (this._disabled) {
|
||||
return Promise.resolve();
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
need_notify: true,
|
||||
modified: true,
|
||||
});
|
||||
this._disableQuickCreate();
|
||||
this.trigger_up("restore_flip_card", {block: true});
|
||||
const record = this.model.get(this.handle);
|
||||
this.trigger_up("list_record_remove", {
|
||||
var record = this.model.get(this.handle);
|
||||
this.trigger_up("block_card", {status: true});
|
||||
this.trigger_up("modify_quick_record", {
|
||||
id: record.id,
|
||||
});
|
||||
this.trigger_up("restore_flip_card", {
|
||||
success_callback: function() {
|
||||
self.trigger_up("list_record_remove", {
|
||||
id: record.id,
|
||||
on_onchange: function() {
|
||||
self.trigger_up("block_card", {status: false});
|
||||
self._enableQuickCreate();
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
_change: function() {
|
||||
const self = this;
|
||||
var self = this;
|
||||
if (this._disabled) {
|
||||
// Don't do anything if we are already creating a record
|
||||
return Promise.resolve();
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
var record = self.model.get(self.handle);
|
||||
if (
|
||||
!this.model.isDirty(this.handle) ||
|
||||
!record.context.has_changes_unconfirmed
|
||||
) {
|
||||
this.trigger_up("restore_flip_card");
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
this._disableQuickCreate();
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
has_changes_confirmed: true,
|
||||
});
|
||||
const record = this.model.get(this.handle);
|
||||
|
||||
this.trigger_up("restore_flip_card", {
|
||||
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(() => {
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
need_notify: true,
|
||||
modified: true,
|
||||
});
|
||||
|
||||
this._disableQuickCreate();
|
||||
// SaveRecord used to make a save point.
|
||||
return this.saveRecord(this.handle, {
|
||||
stayInEdit: true,
|
||||
reload: true,
|
||||
savePoint: true,
|
||||
viewType: "form",
|
||||
}).then(function() {
|
||||
record = self.model.get(self.handle);
|
||||
self.model.updateRecordContext(record.id, {
|
||||
has_changes_unconfirmed: false,
|
||||
lazy_qty: record.data[self.fieldMap.product_uom_qty],
|
||||
});
|
||||
self.trigger_up("block_card", {status: true});
|
||||
self.trigger_up("modify_quick_record", {
|
||||
id: record.id,
|
||||
});
|
||||
self.trigger_up("restore_flip_card", {
|
||||
success_callback: function() {
|
||||
self.trigger_up("update_quick_record", {
|
||||
id: record.id,
|
||||
callback: function() {
|
||||
self.model.unsetDirty(self.handle);
|
||||
on_onchange: function() {
|
||||
self.trigger_up("block_card", {status: false});
|
||||
self._enableQuickCreate();
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
block: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_discard: function() {
|
||||
var self = this;
|
||||
if (this._disabled) {
|
||||
// Don't do anything if we are already creating a record
|
||||
return;
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
|
||||
var record = self.model.get(self.handle);
|
||||
if (
|
||||
!this.model.isDirty(this.handle) ||
|
||||
!record.context.has_changes_unconfirmed
|
||||
) {
|
||||
this.trigger_up("restore_flip_card");
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
|
||||
this._disableQuickCreate();
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
has_changes_confirmed: true,
|
||||
has_changes_unconfirmed: false,
|
||||
});
|
||||
this._disableQuickCreate();
|
||||
// Rollback to restore the save point
|
||||
this.model.discardChanges(this.handle, {
|
||||
rollback: true,
|
||||
});
|
||||
const record = this.model.get(this.handle);
|
||||
this.trigger_up("quick_record_updated", {
|
||||
changes: record.data,
|
||||
});
|
||||
|
||||
this.update({}, {reload: false}).then(() => {
|
||||
if (!this.model.isNew(record.id)) {
|
||||
this.model.unsetDirty(this.handle);
|
||||
}
|
||||
this.trigger_up("restore_flip_card");
|
||||
this._updateButtons();
|
||||
this._enableQuickCreate();
|
||||
return this.update({}, {reload: false}).then(function() {
|
||||
record = self.model.get(self.handle);
|
||||
self.trigger_up("quick_record_updated", {
|
||||
changes: record.data,
|
||||
});
|
||||
self.trigger_up("restore_flip_card", {
|
||||
success_callback: function() {
|
||||
self._updateButtons();
|
||||
self._enableQuickCreate();
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -397,7 +406,7 @@ odoo.define(
|
|||
}
|
||||
);
|
||||
|
||||
const ProductPickerQuickCreateFormView = QuickCreateFormView.extend({
|
||||
var ProductPickerQuickCreateFormView = QuickCreateFormView.extend({
|
||||
config: _.extend({}, QuickCreateFormView.prototype.config, {
|
||||
Renderer: ProductPickerQuickCreateFormRenderer,
|
||||
Controller: ProductPickerQuickCreateFormController,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* global py */
|
||||
// Copyright 2020 Tecnativa - Alexandre Díaz
|
||||
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
odoo.define(
|
||||
|
@ -5,21 +6,23 @@ odoo.define(
|
|||
function(require) {
|
||||
"use strict";
|
||||
|
||||
const core = require("web.core");
|
||||
const Widget = require("web.Widget");
|
||||
const ProductPickerQuickModifPriceFormView = require("web_widget_one2many_product_picker.ProductPickerQuickModifPriceFormView")
|
||||
var core = require("web.core");
|
||||
var Widget = require("web.Widget");
|
||||
var widgetRegistry = require("web.widget_registry");
|
||||
var ProductPickerQuickModifPriceFormView = require("web_widget_one2many_product_picker.ProductPickerQuickModifPriceFormView")
|
||||
.ProductPickerQuickModifPriceFormView;
|
||||
|
||||
const qweb = core.qweb;
|
||||
var qweb = core.qweb;
|
||||
|
||||
/**
|
||||
* This widget render a Form. Used by FieldOne2ManyProductPicker
|
||||
*/
|
||||
const ProductPickerQuickModifPriceForm = Widget.extend({
|
||||
var ProductPickerQuickModifPriceForm = Widget.extend({
|
||||
className: "oe_one2many_product_picker_quick_modif_price",
|
||||
xmlDependencies: [
|
||||
"/web_widget_one2many_product_picker/static/src/xml/one2many_product_picker_quick_modif_price.xml",
|
||||
],
|
||||
|
||||
events: {
|
||||
"click .oe_record_change": "_onClickChange",
|
||||
"click .oe_record_discard": "_onClickDiscard",
|
||||
|
@ -30,13 +33,14 @@ odoo.define(
|
|||
*/
|
||||
init: function(parent, options) {
|
||||
this._super.apply(this, arguments);
|
||||
this.trigger_up("pause_auto_save");
|
||||
this.state = options.state;
|
||||
this.main_state = options.main_state;
|
||||
this.node = options.node;
|
||||
this.fields = options.fields;
|
||||
this.fieldMap = options.fieldMap;
|
||||
this.searchRecord = options.searchRecord;
|
||||
this.fieldsInfo = options.fieldsInfo;
|
||||
this.fieldsInfo = _.extend({}, options.fieldsInfo);
|
||||
this.readonly = options.readonly;
|
||||
this.basicFieldParams = options.basicFieldParams;
|
||||
this.canEditPrice = options.canEditPrice;
|
||||
|
@ -45,56 +49,76 @@ odoo.define(
|
|||
this.res_id = this.state && this.state.res_id;
|
||||
this.id = this.state && this.state.id;
|
||||
this.editContext = {};
|
||||
this._fieldsInvisible = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function() {
|
||||
const def1 = this._super.apply(this, arguments);
|
||||
const fieldsView = {
|
||||
arch: this._generateFormArch(),
|
||||
fields: this.fields,
|
||||
viewFields: this.fields,
|
||||
base_model: this.basicFieldParams.field.relation,
|
||||
type: "form",
|
||||
model: this.basicFieldParams.field.relation,
|
||||
};
|
||||
this.formView = new ProductPickerQuickModifPriceFormView(fieldsView, {
|
||||
context: this.main_state.getContext(),
|
||||
fieldMap: this.fieldMap,
|
||||
modelName: this.basicFieldParams.field.relation,
|
||||
userContext: this.getSession().user_context,
|
||||
ids: this.res_id ? [this.res_id] : [],
|
||||
currentId: this.res_id || undefined,
|
||||
mode: this.res_id && this.readonly ? "readonly" : "edit",
|
||||
recordID: this.id,
|
||||
index: 0,
|
||||
parentID: this.basicFieldParams.parentID,
|
||||
default_buttons: true,
|
||||
withControlPanel: false,
|
||||
model: this.basicFieldParams.model,
|
||||
parentRecordData: this.basicFieldParams.recordData,
|
||||
currencyField: this.currencyField,
|
||||
disable_autofocus: true,
|
||||
});
|
||||
if (this.id) {
|
||||
this.basicFieldParams.model.save(this.id, {savePoint: true});
|
||||
}
|
||||
const def2 = this.formView.getController(this).then(controller => {
|
||||
this.controller = controller;
|
||||
this.$(".modal-body").empty();
|
||||
this.controller.appendTo(this.$(".modal-body"));
|
||||
this.$el.on("hidden.bs.modal", this._onModalHidden.bind(this));
|
||||
});
|
||||
var self = this;
|
||||
this._super.apply(this, arguments).then(function() {
|
||||
var fieldsView = {
|
||||
arch: self._generateFormArch(),
|
||||
fields: self.fields,
|
||||
viewFields: self.fields,
|
||||
base_model: self.basicFieldParams.field.relation,
|
||||
type: "form",
|
||||
model: self.basicFieldParams.field.relation,
|
||||
};
|
||||
|
||||
return Promise.all([def1, def2]);
|
||||
var node_context = self.node.attr("context") || "{}";
|
||||
self.nodeContext = py.eval(node_context, {
|
||||
active_id: self.res_id || false,
|
||||
});
|
||||
var refinedContext = _.extend(
|
||||
{},
|
||||
self.main_state.getContext(),
|
||||
self.nodeContext
|
||||
);
|
||||
_.extend(refinedContext, self.editContext);
|
||||
|
||||
self.formView = new ProductPickerQuickModifPriceFormView(
|
||||
fieldsView,
|
||||
{
|
||||
context: refinedContext,
|
||||
fieldMap: self.fieldMap,
|
||||
modelName: self.basicFieldParams.field.relation,
|
||||
userContext: self.getSession().user_context,
|
||||
ids: self.res_id ? [self.res_id] : [],
|
||||
currentId: self.res_id || undefined,
|
||||
mode: self.res_id && self.readonly ? "readonly" : "edit",
|
||||
recordID: self.id,
|
||||
index: 0,
|
||||
parentID: self.basicFieldParams.parentID,
|
||||
default_buttons: true,
|
||||
withControlPanel: false,
|
||||
model: self.basicFieldParams.model,
|
||||
parentRecordData: self.getParent().getParent().state,
|
||||
currencyField: self.currencyField,
|
||||
disable_autofocus: true,
|
||||
}
|
||||
);
|
||||
if (self.id) {
|
||||
self.basicFieldParams.model.save(self.id, {savePoint: true});
|
||||
}
|
||||
return self.formView.getController(self).then(function(controller) {
|
||||
self.controller = controller;
|
||||
self.$(".modal-body").empty();
|
||||
self.controller.appendTo(self.$(".modal-body"));
|
||||
self.$el.on("hidden.bs.modal", self._onModalHidden.bind(self));
|
||||
self.$el.find(".oe_record_change").removeClass("d-none");
|
||||
return controller;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
destroy: function() {
|
||||
this._restoreNoFetch();
|
||||
this.trigger_up("resume_auto_save");
|
||||
this.$el.off("hidden.bs.modal");
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
@ -108,29 +132,32 @@ odoo.define(
|
|||
* @returns {String}
|
||||
*/
|
||||
_generateFormArch: function() {
|
||||
const wanted_field_states = this._getWantedFieldState();
|
||||
let template =
|
||||
var wanted_field_states = this._getWantedFieldState();
|
||||
var template =
|
||||
"<templates><t t-name='One2ManyProductPicker.QuickModifPrice.Form'>";
|
||||
template += this.basicFieldParams.field.views.form.arch;
|
||||
template += "</t></templates>";
|
||||
qweb.add_template(template);
|
||||
const $arch = $(
|
||||
var $arch = $(
|
||||
qweb.render("One2ManyProductPicker.QuickModifPrice.Form", {
|
||||
field_map: this.fieldMap,
|
||||
record_search: this.searchRecord,
|
||||
})
|
||||
);
|
||||
|
||||
const field_names = Object.keys(
|
||||
var field_names = Object.keys(
|
||||
this.basicFieldParams.field.views.form.fields
|
||||
);
|
||||
let gen_arch = "<form><group>";
|
||||
for (const index in field_names) {
|
||||
const field_name = field_names[index];
|
||||
const $field = $arch.find("field[name='" + field_name + "']");
|
||||
const modifiers = $field.attr("modifiers")
|
||||
var gen_arch = "<form><group>";
|
||||
for (var index in field_names) {
|
||||
var field_name = field_names[index];
|
||||
var $field = $arch.find("field[name='" + field_name + "']");
|
||||
var modifiers = $field.attr("modifiers")
|
||||
? JSON.parse($field.attr("modifiers"))
|
||||
: {};
|
||||
if (!modifiers.invisible && !(field_name in wanted_field_states)) {
|
||||
this._fieldsInvisible.push(field_name);
|
||||
}
|
||||
modifiers.invisible = !(field_name in wanted_field_states);
|
||||
modifiers.readonly = wanted_field_states[field_name];
|
||||
$field.attr("modifiers", JSON.stringify(modifiers));
|
||||
|
@ -160,12 +187,40 @@ odoo.define(
|
|||
* @returns {Object}
|
||||
*/
|
||||
_getWantedFieldState: function() {
|
||||
const wantedFieldState = {};
|
||||
var wantedFieldState = {};
|
||||
wantedFieldState[this.fieldMap.discount] = !this.canEditDiscount;
|
||||
wantedFieldState[this.fieldMap.price_unit] = !this.canEditPrice;
|
||||
return wantedFieldState;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickDiscard: function(ev) {
|
||||
if (this.controller) {
|
||||
this._hideControlButtons(true);
|
||||
this.controller._onClickDiscard(ev);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onClickChange: function(ev) {
|
||||
var self = this;
|
||||
if (!this.controller) {
|
||||
return;
|
||||
}
|
||||
self._hideControlButtons(true);
|
||||
this.controller._onClickChange(ev).then(function(res) {
|
||||
if (res) {
|
||||
self.$el.modal("hide");
|
||||
} else {
|
||||
self._hideControlButtons(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
@ -173,84 +228,28 @@ odoo.define(
|
|||
this.destroy();
|
||||
},
|
||||
|
||||
_hideControlButtons: function(status) {
|
||||
this.$el.find(".oe_record_change").toggleClass("d-none", status);
|
||||
this.$el.find(".oe_record_discard").toggleClass("d-none", status);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickChange: function(ev) {
|
||||
ev.stopPropagation();
|
||||
const model = this.basicFieldParams.model;
|
||||
model.updateRecordContext(this.id, {
|
||||
has_changes_confirmed: true,
|
||||
});
|
||||
const is_virtual = model.isPureVirtual(this.id);
|
||||
|
||||
// If is a 'pure virtual' record, save it in the selected list
|
||||
if (is_virtual) {
|
||||
if (model.isDirty(this.id)) {
|
||||
this._disableQuickCreate();
|
||||
this.controller
|
||||
.saveRecord(this.id, {
|
||||
stayInEdit: true,
|
||||
reload: true,
|
||||
savePoint: true,
|
||||
viewType: "form",
|
||||
})
|
||||
.then(() => {
|
||||
this._enableQuickCreate();
|
||||
model.unsetDirty(this.id);
|
||||
this.trigger_up("create_quick_record", {
|
||||
id: this.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// If is a "normal" record, update it
|
||||
this.trigger_up("update_quick_record", {
|
||||
id: this.id,
|
||||
});
|
||||
model.unsetDirty(this.id);
|
||||
_restoreNoFetch: function() {
|
||||
var record = this.basicFieldParams.model.get(this.id);
|
||||
for (var field_name of this._fieldsInvisible) {
|
||||
record.fieldsInfo[record.viewType][field_name].__no_fetch = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickDiscard: function(ev) {
|
||||
ev.stopPropagation();
|
||||
const model = this.basicFieldParams.model;
|
||||
model.discardChanges(this.id, {
|
||||
rollback: true,
|
||||
});
|
||||
this.trigger_up("update_quick_record", {
|
||||
id: this.id,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_disableQuickCreate: function() {
|
||||
// Ensures that the record won't be created twice
|
||||
this.$el.addClass("o_disabled");
|
||||
this.$("input:not(:disabled),button:not(:disabled)")
|
||||
.addClass("o_temporarily_disabled")
|
||||
.attr("disabled", "disabled");
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_enableQuickCreate: function() {
|
||||
// Allows to create again
|
||||
this.$el.removeClass("o_disabled");
|
||||
this.$("input.o_temporarily_disabled,button.o_temporarily_disabled")
|
||||
.removeClass("o_temporarily_disabled")
|
||||
.attr("disabled", false);
|
||||
this._fieldsInvisible = [];
|
||||
},
|
||||
});
|
||||
|
||||
widgetRegistry.add(
|
||||
"product_picker_quick_modif_price_form",
|
||||
ProductPickerQuickModifPriceForm
|
||||
);
|
||||
|
||||
return ProductPickerQuickModifPriceForm;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -10,23 +10,24 @@ odoo.define(
|
|||
* is used by the RecordQuickCreate in One2ManyProductPicker views.
|
||||
*/
|
||||
|
||||
const QuickCreateFormView = require("web.QuickCreateFormView");
|
||||
const core = require("web.core");
|
||||
const tools = require("web_widget_one2many_product_picker.tools");
|
||||
var QuickCreateFormView = require("web.QuickCreateFormView");
|
||||
var core = require("web.core");
|
||||
var tools = require("web_widget_one2many_product_picker.tools");
|
||||
|
||||
const qweb = core.qweb;
|
||||
var qweb = core.qweb;
|
||||
|
||||
const ProductPickerQuickModifPriceFormRenderer = QuickCreateFormView.prototype.config.Renderer.extend(
|
||||
var ProductPickerQuickModifPriceFormRenderer = QuickCreateFormView.prototype.config.Renderer.extend(
|
||||
{
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function() {
|
||||
var self = this;
|
||||
this.$el.addClass(
|
||||
"oe_one2many_product_picker_form_view o_xxs_form_view"
|
||||
);
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
this._appendPrice();
|
||||
return this._super.apply(this, arguments).then(function() {
|
||||
self._appendPrice();
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -42,7 +43,7 @@ odoo.define(
|
|||
}
|
||||
);
|
||||
|
||||
const ProductPickerQuickModifPriceFormController = QuickCreateFormView.prototype.config.Controller.extend(
|
||||
var ProductPickerQuickModifPriceFormController = QuickCreateFormView.prototype.config.Controller.extend(
|
||||
{
|
||||
/**
|
||||
* @override
|
||||
|
@ -50,18 +51,20 @@ odoo.define(
|
|||
init: function(parent, model, renderer, params) {
|
||||
this.fieldMap = params.fieldMap;
|
||||
this.context = params.context;
|
||||
this._super.apply(this, arguments);
|
||||
this.currencyField = params.currencyField;
|
||||
this.parentRecordData = params.parentRecordData;
|
||||
this.fields = parent.state.fields;
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function() {
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
const record = this.model.get(this.handle);
|
||||
this._updatePrice(record.data);
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function() {
|
||||
var record = self.model.get(self.handle);
|
||||
self._updatePrice(record.data);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -70,8 +73,21 @@ odoo.define(
|
|||
*/
|
||||
_onFieldChanged: function(ev) {
|
||||
this._super.apply(this, arguments);
|
||||
const record = this.model.get(this.handle);
|
||||
this._updatePrice(_.extend({}, record.data, ev.data.changes));
|
||||
if (!_.isEmpty(ev.data.changes)) {
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
has_changes_unconfirmed: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_applyChanges: function() {
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
var record = this.model.get(this.handle);
|
||||
this._updatePrice(record.data);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -79,7 +95,7 @@ odoo.define(
|
|||
* @param {Object} values
|
||||
*/
|
||||
_updatePrice: function(values) {
|
||||
const price_reduce = tools.priceReduce(
|
||||
var price_reduce = tools.priceReduce(
|
||||
values[this.fieldMap.price_unit],
|
||||
values[this.fieldMap.discount]
|
||||
);
|
||||
|
@ -88,16 +104,136 @@ odoo.define(
|
|||
.html(
|
||||
tools.monetary(
|
||||
price_reduce,
|
||||
this.getParent().state.fields[this.fieldMap.price_unit],
|
||||
this.fields[this.fieldMap.price_unit],
|
||||
this.currencyField,
|
||||
values
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickChange: function(ev) {
|
||||
var self = this;
|
||||
var def = $.Deferred();
|
||||
ev.stopPropagation();
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
has_changes_unconfirmed: false,
|
||||
});
|
||||
|
||||
if (!this.model.isDirty(this.handle)) {
|
||||
return def.resolve();
|
||||
}
|
||||
|
||||
this._disableQuickCreate();
|
||||
this.trigger_up("quick_record_updated", {
|
||||
changes: _.extend(
|
||||
{},
|
||||
this.model.localData[this.handle].data,
|
||||
this.model.localData[this.handle]._changes
|
||||
),
|
||||
highlight: {price: true},
|
||||
});
|
||||
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
need_notify: true,
|
||||
modified: true,
|
||||
});
|
||||
this.trigger_up("block_card", {status: true});
|
||||
this.trigger_up("modify_quick_record", {
|
||||
id: this.handle,
|
||||
});
|
||||
|
||||
var is_virtual = this.model.isPureVirtual(this.handle);
|
||||
// If is a 'pure virtual' record, save it in the selected list
|
||||
if (is_virtual) {
|
||||
this.saveRecord(this.handle, {
|
||||
stayInEdit: true,
|
||||
reload: true,
|
||||
savePoint: true,
|
||||
viewType: "form",
|
||||
}).then(function() {
|
||||
def.resolve(true);
|
||||
self.trigger_up("create_quick_record", {
|
||||
id: self.handle,
|
||||
on_onchange: function() {
|
||||
self.trigger_up("block_card", {status: false});
|
||||
self._enableQuickCreate();
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
def.resolve(true);
|
||||
// If is a "normal" record, update it
|
||||
this.trigger_up("update_quick_record", {
|
||||
id: this.handle,
|
||||
on_onchange: function() {
|
||||
self.trigger_up("block_card", {status: false});
|
||||
self._enableQuickCreate();
|
||||
},
|
||||
});
|
||||
}
|
||||
return def;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickDiscard: function(ev) {
|
||||
var self = this;
|
||||
ev.stopPropagation();
|
||||
if (!this.model.isDirty(this.handle)) {
|
||||
return true;
|
||||
}
|
||||
this.model.discardChanges(this.handle, {
|
||||
rollback: true,
|
||||
});
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
need_notify: false,
|
||||
modified: false,
|
||||
});
|
||||
this.trigger_up("block_card", {status: true});
|
||||
this.trigger_up("modify_quick_record", {
|
||||
id: this.handle,
|
||||
});
|
||||
this.trigger_up("update_quick_record", {
|
||||
id: this.handle,
|
||||
on_onchange: function() {
|
||||
self.trigger_up("block_card", {status: false});
|
||||
self._enableQuickCreate();
|
||||
},
|
||||
});
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_disableQuickCreate: function() {
|
||||
// Ensures that the record won't be created twice
|
||||
this.$el.addClass("o_disabled");
|
||||
this.$("input:not(:disabled),button:not(:disabled)")
|
||||
.addClass("o_temporarily_disabled")
|
||||
.attr("disabled", "disabled");
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_enableQuickCreate: function() {
|
||||
// Allows to create again
|
||||
this.$el.removeClass("o_disabled");
|
||||
this.$("input.o_temporarily_disabled,button.o_temporarily_disabled")
|
||||
.removeClass("o_temporarily_disabled")
|
||||
.attr("disabled", false);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const ProductPickerQuickModifPriceFormView = QuickCreateFormView.extend({
|
||||
var ProductPickerQuickModifPriceFormView = QuickCreateFormView.extend({
|
||||
config: _.extend({}, QuickCreateFormView.prototype.config, {
|
||||
Renderer: ProductPickerQuickModifPriceFormRenderer,
|
||||
Controller: ProductPickerQuickModifPriceFormController,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,15 +5,14 @@ odoo.define(
|
|||
function(require) {
|
||||
"use strict";
|
||||
|
||||
const core = require("web.core");
|
||||
const BasicRenderer = require("web.BasicRenderer");
|
||||
const One2ManyProductPickerRecord = require("web_widget_one2many_product_picker.One2ManyProductPickerRecord");
|
||||
const ProductPickerQuickCreateForm = require("web_widget_one2many_product_picker.ProductPickerQuickCreateForm");
|
||||
var core = require("web.core");
|
||||
var BasicRenderer = require("web.BasicRenderer");
|
||||
var One2ManyProductPickerRecord = require("web_widget_one2many_product_picker.One2ManyProductPickerRecord");
|
||||
|
||||
const qweb = core.qweb;
|
||||
var qweb = core.qweb;
|
||||
|
||||
/* This is the renderer of the main widget */
|
||||
const One2ManyProductPickerRenderer = BasicRenderer.extend({
|
||||
var One2ManyProductPickerRenderer = BasicRenderer.extend({
|
||||
className: "oe_one2many_product_picker_view",
|
||||
|
||||
events: {
|
||||
|
@ -23,8 +22,7 @@ odoo.define(
|
|||
record_flip: "_onRecordFlip",
|
||||
},
|
||||
|
||||
DELAY_GET_RECORDS: 150,
|
||||
MIN_PERC_GET_RECORDS: 0.9,
|
||||
_instant_search_onchange_delay: 250,
|
||||
|
||||
/**
|
||||
* @override
|
||||
|
@ -59,15 +57,6 @@ odoo.define(
|
|||
_.invoke(_.compact(this.widgets), "on_detach_callback");
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} widget
|
||||
*/
|
||||
removeWidget: function(widget) {
|
||||
const index = this.widgets.indexOf(widget);
|
||||
widget.destroy();
|
||||
delete this.widgets[index];
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
|
@ -95,18 +84,33 @@ odoo.define(
|
|||
* @override
|
||||
*/
|
||||
updateState: function(state, params) {
|
||||
const force_update = params.force;
|
||||
var self = this;
|
||||
var force_update = params.force;
|
||||
delete params.force;
|
||||
const sparams = _.extend({}, params, {noRender: true});
|
||||
var sparams = _.extend({}, params, {noRender: true});
|
||||
if (!force_update && _.isEqual(this.state.data, state.data)) {
|
||||
return this._super(state, sparams);
|
||||
}
|
||||
const old_state = _.clone(this.state.data);
|
||||
return this._super(state, sparams).then(() => {
|
||||
this._updateStateRecords(old_state);
|
||||
var old_state = _.clone(this.state.data);
|
||||
return this._super(state, sparams).then(function() {
|
||||
self._updateStateRecords(old_state);
|
||||
});
|
||||
},
|
||||
|
||||
canBeUpdated: function() {
|
||||
var model = this.getParent().getBasicFieldParams().model;
|
||||
for (var widget of this.widgets) {
|
||||
if (!widget.state) {
|
||||
return false;
|
||||
}
|
||||
var record = model.localData[widget.state.id];
|
||||
if (record.context.in_timeout) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Because this widget doesn't support comments/sections line types
|
||||
* we need check if the line is valid to be shown.
|
||||
|
@ -117,53 +121,41 @@ odoo.define(
|
|||
*/
|
||||
_isValidLineState: function(state) {
|
||||
return (
|
||||
state &&
|
||||
state.data[this.options.field_map.product] &&
|
||||
state.data[this.options.field_map.product].data &&
|
||||
typeof state.data[this.options.field_map.product].data.id !==
|
||||
"undefined"
|
||||
);
|
||||
},
|
||||
|
||||
getProductIdFromState: function(state) {
|
||||
return (
|
||||
state &&
|
||||
state.data[this.options.field_map.product] &&
|
||||
state.data[this.options.field_map.product].data &&
|
||||
state.data[this.options.field_map.product].data.id
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} state_a
|
||||
* @param {Object} state_b
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_isEqualState: function(state_a, state_b) {
|
||||
if (state_a.id === state_b.id) {
|
||||
return true;
|
||||
}
|
||||
const product_id_a =
|
||||
state_a.data[this.options.field_map.product].data.id;
|
||||
const product_uom_id_a =
|
||||
state_a.data[this.options.field_map.product_uom].data.id;
|
||||
const product_id_b =
|
||||
state_b.data[this.options.field_map.product].data.id;
|
||||
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
|
||||
);
|
||||
getWidgetsByProduct: function(product_id) {
|
||||
var self = this;
|
||||
return _.filter(this.widgets, function(item) {
|
||||
return (
|
||||
self.getProductIdFromState(item.state) === product_id ||
|
||||
item.recordSearch.id === product_id
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} state
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_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;
|
||||
getWidgetsWithoutOnchange: function() {
|
||||
var model = this.getParent().getBasicFieldParams().model;
|
||||
return _.filter(this.widgets, function(item) {
|
||||
return (
|
||||
model.localData[item.state.id] &&
|
||||
model.localData[item.state.id].context.not_onchange
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -175,58 +167,62 @@ odoo.define(
|
|||
* @param {Array} states
|
||||
* @returns {Array}
|
||||
*/
|
||||
_processStatesToDestroy: function(states) {
|
||||
// Get widgets to destroy
|
||||
// Update states only affect to "non pure virtual" records
|
||||
const to_destroy = [];
|
||||
const to_add = [];
|
||||
for (const state of states) {
|
||||
for (let e = this.widgets.length - 1; e >= 0; --e) {
|
||||
const widget = this.widgets[e];
|
||||
if (widget && this._isEqualState(widget.state, state)) {
|
||||
// If already exists a widget for the product don't try create a new one
|
||||
let recreated = false;
|
||||
if (!this._existsWidgetWithState(widget.state)) {
|
||||
// Get the new state ID if exists to link it with the new record
|
||||
// This happens when remove a record that have a new state info
|
||||
for (
|
||||
let eb = this.state.data.length - 1;
|
||||
eb >= 0;
|
||||
--eb
|
||||
) {
|
||||
const state = this.state.data[eb];
|
||||
if (!this._isValidLineState(state)) {
|
||||
continue;
|
||||
}
|
||||
if (this._isEqualState(state, widget.state)) {
|
||||
widget.recreate(state);
|
||||
recreated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!recreated) {
|
||||
widget.markToDestroy();
|
||||
to_destroy.push(widget);
|
||||
const search_record = _.omit(
|
||||
widget.recordSearch,
|
||||
"__id"
|
||||
);
|
||||
_processStatesToDestroy: function(old_states) {
|
||||
var self = this;
|
||||
// States to remove
|
||||
// In 12.0, Odoo generates new ids for the states, so
|
||||
// all states will be removed and restored because it's
|
||||
// not possible identify a record without this id
|
||||
var to_destroy_ids = [];
|
||||
for (var index in old_states) {
|
||||
var old_state = old_states[index];
|
||||
if (!this._isValidLineState(old_state)) {
|
||||
continue;
|
||||
}
|
||||
var in_current_state = _.some(this.state.data, function(state) {
|
||||
return (
|
||||
self._isValidLineState(state) && state.id === old_state.id
|
||||
);
|
||||
});
|
||||
if (!in_current_state) {
|
||||
to_destroy_ids.push(old_state.id);
|
||||
}
|
||||
}
|
||||
|
||||
to_add.push([
|
||||
[search_record],
|
||||
{
|
||||
no_attach_widgets: false,
|
||||
no_process_records: false,
|
||||
position: widget.state.id,
|
||||
},
|
||||
]);
|
||||
}
|
||||
var model = this.getParent().getBasicFieldParams().model;
|
||||
var to_destroy = [];
|
||||
for (var widget of this.widgets) {
|
||||
if (!widget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify that doesn't exists any dead widget
|
||||
// This is necessary beceause auto-save uses
|
||||
// ADD + SAVE that generates two different
|
||||
// state ids
|
||||
var state_has_onchange =
|
||||
widget.state && !widget.state.context.not_onchange;
|
||||
var state_has_modified =
|
||||
widget.state && !widget.state.context.modified;
|
||||
if (
|
||||
!state_has_modified &&
|
||||
state_has_onchange &&
|
||||
!model.isPureVirtual(widget.state.id)
|
||||
) {
|
||||
to_destroy.push(widget);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var index_destroy in to_destroy_ids) {
|
||||
const state_id = to_destroy_ids[index_destroy];
|
||||
if (widget.state.id === state_id) {
|
||||
to_destroy.push(widget);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [to_destroy, to_add];
|
||||
return to_destroy;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -236,67 +232,71 @@ odoo.define(
|
|||
* @private
|
||||
* @returns {Array}
|
||||
*/
|
||||
_processCurrentStates: function() {
|
||||
_processCurrentStates: function(old_states) {
|
||||
var to_destroy = this._processStatesToDestroy(old_states);
|
||||
// Records to Update or Create
|
||||
const model = this.getParent().getBasicFieldParams().model;
|
||||
const to_destroy = [];
|
||||
const to_add = [];
|
||||
for (const index in this.state.data) {
|
||||
const state = this.state.data[index];
|
||||
var model = this.getParent().getBasicFieldParams().model;
|
||||
var to_add = [];
|
||||
for (var index in this.state.data) {
|
||||
var state = this.state.data[index];
|
||||
if (!this._isValidLineState(state)) {
|
||||
continue;
|
||||
}
|
||||
let exists = false;
|
||||
let search_record_index = false;
|
||||
let search_record = false;
|
||||
for (let e = this.widgets.length - 1; e >= 0; --e) {
|
||||
const widget = this.widgets[e];
|
||||
if (!widget || !widget.state) {
|
||||
var exists = false;
|
||||
var search_record_index = false;
|
||||
var search_record = false;
|
||||
for (var widget of this.widgets) {
|
||||
if (!widget) {
|
||||
// Already processed widget (deleted)
|
||||
continue;
|
||||
}
|
||||
|
||||
const is_equal_state = this._isEqualState(widget.state, state);
|
||||
if (widget.isMarkedToDestroy()) {
|
||||
exists = true;
|
||||
} else if (is_equal_state) {
|
||||
const record = model.get(widget.state.id);
|
||||
model.updateRecordContext(state.id, {
|
||||
lazy_qty: record.context.lazy_qty || 0,
|
||||
});
|
||||
var record = model.get(widget.state.id);
|
||||
// Re-use widgets is possible
|
||||
var is_to_destroy = _.findIndex(to_destroy, widget) >= 0;
|
||||
var is_widget_usable =
|
||||
widget.state.id === state.id ||
|
||||
widget.recordSearch.id ===
|
||||
state.data[this.options.field_map.product].data.id;
|
||||
if (is_widget_usable) {
|
||||
if (is_to_destroy) {
|
||||
to_destroy = _.without(to_destroy, widget);
|
||||
}
|
||||
if (record) {
|
||||
model.updateRecordContext(state.id, {
|
||||
lazy_qty: record.context.lazy_qty || 0,
|
||||
saving: record.context.saving || false,
|
||||
need_notify: record.context.need_notify || false,
|
||||
need_save: record.context.need_save || false,
|
||||
});
|
||||
}
|
||||
// Ensure use the updated state
|
||||
widget.recreate(state);
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
if (
|
||||
!is_equal_state &&
|
||||
} else if (
|
||||
widget.state &&
|
||||
!model.isPureVirtual(widget.state.id) &&
|
||||
widget.recordSearch.id ===
|
||||
state.data[this.options.field_map.product].data.id
|
||||
) {
|
||||
// Is a new record (can be other record for the same 'search record' or a replacement for a pure virtual)
|
||||
search_record_index = widget.state.id;
|
||||
search_record = widget.recordSearch;
|
||||
const record = model.get(widget.state.id);
|
||||
model.updateRecordContext(state.id, {
|
||||
lazy_qty: record.context.lazy_qty || 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Remove "pure virtual" records that have the same product that the new record
|
||||
if (
|
||||
widget.is_virtual &&
|
||||
this._isEqualState(widget.state, state)
|
||||
) {
|
||||
to_destroy.push(widget);
|
||||
delete this.widgets[e];
|
||||
const in_search_records = _.some(
|
||||
this.search_records,
|
||||
function(item) {
|
||||
return item.id === search_record.id;
|
||||
}
|
||||
);
|
||||
if (in_search_records) {
|
||||
// Is a new record (can be other record for the same 'search record')
|
||||
search_record_index = widget.state.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.state.data = _.compact(this.state.data);
|
||||
|
||||
// Add to create the new record
|
||||
if (!exists && search_record_index) {
|
||||
const new_search_record = _.extend({}, search_record, {
|
||||
var new_search_record = _.extend({}, search_record, {
|
||||
__id: state.id,
|
||||
});
|
||||
to_add.push([
|
||||
|
@ -309,68 +309,121 @@ odoo.define(
|
|||
]);
|
||||
}
|
||||
}
|
||||
return [to_add.reverse(), to_destroy];
|
||||
},
|
||||
|
||||
return [to_destroy, to_add];
|
||||
/**
|
||||
* This method checks and appends the missing
|
||||
* 'pure virtual' records
|
||||
*
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
checkVirtualRecords: function() {
|
||||
if (this.search_group.name === "main_lines") {
|
||||
return $.when();
|
||||
}
|
||||
var tasks = [];
|
||||
var to_add = this._processVirtualRecords();
|
||||
for (var params of to_add) {
|
||||
tasks.push(this.appendSearchRecords.apply(this, params)[0]);
|
||||
}
|
||||
return $.when(tasks);
|
||||
},
|
||||
|
||||
/**
|
||||
* This method checks the current widgets to generate the
|
||||
* missing 'pure virtual' record objects.
|
||||
*
|
||||
* @returns {Array}
|
||||
*/
|
||||
_processVirtualRecords: function() {
|
||||
var model = this.getParent().getBasicFieldParams().model;
|
||||
var products_done = [];
|
||||
var to_add = [];
|
||||
for (var search_record of this.search_records) {
|
||||
var widgets = this.getWidgetsByProduct(search_record.id);
|
||||
if (_.isEmpty(widgets)) {
|
||||
to_add.push([
|
||||
[_.omit(search_record, "__id")],
|
||||
{
|
||||
no_attach_widgets: true,
|
||||
no_process_records: true,
|
||||
},
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only add 'pure virtual' records if don't have a line
|
||||
var existing_widgets = _.filter(widgets, function(widget) {
|
||||
return !widget.isMarkedToDestroy();
|
||||
});
|
||||
var need_virtual = !_.some(existing_widgets, function(widget) {
|
||||
return widget.state && !model.isPureVirtual(widget.state.id);
|
||||
});
|
||||
if (
|
||||
need_virtual &&
|
||||
products_done.indexOf(search_record.id) === -1
|
||||
) {
|
||||
var has_virtual = _.some(existing_widgets, function(widget) {
|
||||
return (
|
||||
!widget.state ||
|
||||
(widget.state && model.isPureVirtual(widget.state.id))
|
||||
);
|
||||
});
|
||||
if (!has_virtual) {
|
||||
var search_record_index = _.max(widgets, function(widget) {
|
||||
return widget.$el.index();
|
||||
}).state.id;
|
||||
to_add.push([
|
||||
[_.omit(search_record, "__id")],
|
||||
{
|
||||
no_attach_widgets: true,
|
||||
no_process_records: true,
|
||||
position: search_record_index,
|
||||
},
|
||||
]);
|
||||
products_done.push(search_record.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return to_add;
|
||||
},
|
||||
|
||||
/**
|
||||
* When the state change this method tries to update current records, delete
|
||||
* or update them.
|
||||
* Thanks to this we don't need re-render 'pure virtual' records.
|
||||
* NOTE: The first load of the records don't trigger this method.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} old_states
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_updateStateRecords: function(old_states) {
|
||||
// States to remove
|
||||
const states_to_destroy = [];
|
||||
for (const index in old_states) {
|
||||
const old_state = old_states[index];
|
||||
if (!this._isValidLineState(old_state)) {
|
||||
continue;
|
||||
}
|
||||
let found = false;
|
||||
for (const e in this.state.data) {
|
||||
const current_state = this.state.data[e];
|
||||
if (!this._isValidLineState(current_state)) {
|
||||
continue;
|
||||
}
|
||||
if (this._isEqualState(current_state, old_state)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
states_to_destroy.push(old_state);
|
||||
}
|
||||
}
|
||||
|
||||
const def = $.Deferred();
|
||||
this.state.data = _.compact(this.state.data);
|
||||
const [to_destroy_old, to_add_virtual] = this._processStatesToDestroy(
|
||||
states_to_destroy
|
||||
);
|
||||
|
||||
const [
|
||||
destroyed_current,
|
||||
to_add_current,
|
||||
] = this._processCurrentStates();
|
||||
|
||||
const currentTasks = [];
|
||||
const to_add = [].concat(to_add_current, to_add_virtual);
|
||||
for (const params of to_add) {
|
||||
var self = this;
|
||||
var record_defs = this._processCurrentStates(old_states);
|
||||
var to_add_current = record_defs[0];
|
||||
var to_destroy = record_defs[1];
|
||||
_.invoke(to_destroy, "markToDestroy");
|
||||
var currentTasks = [];
|
||||
for (var params of to_add_current) {
|
||||
currentTasks.push(this.appendSearchRecords.apply(this, params)[0]);
|
||||
}
|
||||
|
||||
Promise.all(currentTasks).then(() => {
|
||||
_.invoke(to_destroy_old, "destroy");
|
||||
_.invoke(destroyed_current, "destroy");
|
||||
this.widgets = _.difference(this.widgets, to_destroy_old);
|
||||
def.resolve();
|
||||
});
|
||||
|
||||
return def;
|
||||
return $.when(currentTasks)
|
||||
.then(function() {
|
||||
return self.checkVirtualRecords();
|
||||
})
|
||||
.then(function() {
|
||||
var widgets_to_destroy = _.filter(self.widgets, function(
|
||||
widget
|
||||
) {
|
||||
return widget.isMarkedToDestroy();
|
||||
});
|
||||
self.widgets = _.difference(self.widgets, widgets_to_destroy);
|
||||
_.invoke(widgets_to_destroy, "destroy");
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
clearRecords: function() {
|
||||
|
@ -413,12 +466,12 @@ odoo.define(
|
|||
*/
|
||||
_sort_search_data: function(datas) {
|
||||
if (this.search_group.name === "main_lines") {
|
||||
const field_name = this.options.field_map.product;
|
||||
for (const index_datas in datas) {
|
||||
const data = datas[index_datas];
|
||||
var field_name = this.options.field_map.product;
|
||||
for (var index_datas in datas) {
|
||||
var data = datas[index_datas];
|
||||
|
||||
for (const index_state in this.state.data) {
|
||||
const state_data = this.state.data[index_state];
|
||||
for (var index_state in this.state.data) {
|
||||
var state_data = this.state.data[index_state];
|
||||
if (
|
||||
this._isValidLineState(state_data) &&
|
||||
state_data.data[field_name].res_id === data.id
|
||||
|
@ -427,9 +480,11 @@ odoo.define(
|
|||
}
|
||||
}
|
||||
}
|
||||
const sorted_datas = _.chain(datas)
|
||||
var sorted_datas = _.chain(datas)
|
||||
.sortBy("_order_value")
|
||||
.map(item => _.omit(item, "_order_value"))
|
||||
.map(function(item) {
|
||||
return _.omit(item, "_order_value");
|
||||
})
|
||||
.value()
|
||||
.reverse();
|
||||
return sorted_datas;
|
||||
|
@ -446,9 +501,10 @@ odoo.define(
|
|||
* @returns {Array}
|
||||
*/
|
||||
_processSearchRecords: function(results) {
|
||||
const field_name = this.options.field_map.product;
|
||||
const records = [];
|
||||
const states = [];
|
||||
var model = this.getParent().getBasicFieldParams().model;
|
||||
var field_name = this.options.field_map.product;
|
||||
var records = [];
|
||||
var states = [];
|
||||
|
||||
var test_values = function(field_value, record_search) {
|
||||
return (
|
||||
|
@ -458,59 +514,62 @@ odoo.define(
|
|||
);
|
||||
};
|
||||
|
||||
for (const index in results) {
|
||||
const record_search = results[index];
|
||||
let state_data_found = false;
|
||||
for (var index in results) {
|
||||
var record_search = results[index];
|
||||
|
||||
// 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;
|
||||
}
|
||||
var widget_created = false;
|
||||
|
||||
for (var index_data in this.state.data) {
|
||||
var state_record = this.state.data[index_data];
|
||||
var field_value = state_record.data[field_name];
|
||||
if (
|
||||
record_search.__id === widget.state.id ||
|
||||
(!record_search.__id &&
|
||||
widget.recordSearch.id === record_search.id)
|
||||
!this._isValidLineState(state_record) ||
|
||||
!test_values(field_value, record_search)
|
||||
) {
|
||||
state_data_found = true;
|
||||
if (widget.state) {
|
||||
states.push(widget.state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If already exists a widget with the search result
|
||||
// avoid create a new one
|
||||
if (state_data_found) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Analyze field records
|
||||
// If not found any widget we need create a new one
|
||||
// linked with the state record
|
||||
for (const index_data in this.state.data) {
|
||||
const state_record = this.state.data[index_data];
|
||||
if (!this._isValidLineState(state_record)) {
|
||||
continue;
|
||||
}
|
||||
const field_value = state_record.data[field_name];
|
||||
if (test_values(field_value, record_search)) {
|
||||
widget_created = true;
|
||||
// At this point the result has a state (line)
|
||||
// Search if already exists a widget using the state
|
||||
var widget = _.find(this.widgets, function(widget) {
|
||||
return (
|
||||
!widget.isMarkedToDestroy() &&
|
||||
widget.state &&
|
||||
widget.state.id === state_record.id
|
||||
);
|
||||
});
|
||||
if (widget) {
|
||||
// Don't need create a new widget (record)
|
||||
states.push(widget.state);
|
||||
} else {
|
||||
// Need create a new widget linked with the state
|
||||
records.push(
|
||||
_.extend({}, record_search, {
|
||||
__id: state_record.id,
|
||||
})
|
||||
);
|
||||
states.push(state_record);
|
||||
state_data_found = true;
|
||||
}
|
||||
}
|
||||
if (widget_created) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!state_data_found) {
|
||||
records.push(record_search);
|
||||
var widgets = this.getWidgetsByProduct(record_search.id);
|
||||
// Only can exists 'pure virtual' if no 'lines' assigned
|
||||
if (_.isEmpty(widgets)) {
|
||||
var has_virtual = _.some(widgets, function(widget) {
|
||||
return (
|
||||
!widget.isMarkedToDestroy() &&
|
||||
(!widget.state ||
|
||||
(widget.state &&
|
||||
model.isPureVirtual(widget.state.id)))
|
||||
);
|
||||
});
|
||||
if (!has_virtual) {
|
||||
// The result need a 'pure virtual' record
|
||||
records.push(_.omit(record_search, "__id"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -526,8 +585,8 @@ odoo.define(
|
|||
* @returns {Object}
|
||||
*/
|
||||
_getRecordDataById: function(id) {
|
||||
for (const index in this.state.data) {
|
||||
const record = this.state.data[index];
|
||||
for (var index in this.state.data) {
|
||||
var record = this.state.data[index];
|
||||
if (record.id === id) {
|
||||
return record;
|
||||
}
|
||||
|
@ -561,73 +620,55 @@ odoo.define(
|
|||
* @private
|
||||
* @param {Array} search_records
|
||||
* @param {Object} options
|
||||
* @returns {Array}
|
||||
*/
|
||||
_appendSearchRecords: function(search_records, options) {
|
||||
const processed_info = options.no_process_records
|
||||
? search_records
|
||||
: this._processSearchRecords(search_records);
|
||||
const records_to_add = processed_info.records || search_records;
|
||||
_.each(records_to_add, search_record => {
|
||||
const state_data = this._getRecordDataById(search_record.__id);
|
||||
const widget_options = this._getRecordOptions(search_record);
|
||||
widget_options.renderer_widget_index = this.widgets.length;
|
||||
const ProductPickerRecord = new One2ManyProductPickerRecord(
|
||||
this,
|
||||
var self = this;
|
||||
var processed_info =
|
||||
!options.no_process_records &&
|
||||
this._processSearchRecords(search_records);
|
||||
var records_to_add =
|
||||
(processed_info && processed_info.records) || search_records;
|
||||
_.each(records_to_add, function(search_record) {
|
||||
// Get record state (if can)
|
||||
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,
|
||||
widget_options
|
||||
);
|
||||
this.widgets.push(ProductPickerRecord);
|
||||
self.widgets.push(ProductPickerRecord);
|
||||
|
||||
// Simulate new lines to dispatch get_default & onchange's to get the
|
||||
// relevant data to print. This case increase the TTI time.
|
||||
if (!state_data) {
|
||||
const defVirtualState = ProductPickerRecord.generateVirtualState(
|
||||
this.options.instant_search
|
||||
);
|
||||
this.defsVirtualState.push(defVirtualState);
|
||||
var defVirtualState = ProductPickerRecord.generateVirtualState({
|
||||
onchange_delay: self.options.instant_search
|
||||
? self._instant_search_onchange_delay
|
||||
: 0,
|
||||
});
|
||||
self.defsVirtualState.push(defVirtualState);
|
||||
}
|
||||
|
||||
// At this point the widget will use the existing state (line) or
|
||||
// a simple state data. Using simple state data instead of waiting for
|
||||
// complete state (default + onchange) gives a low FCP time.
|
||||
const def = $.Deferred();
|
||||
ProductPickerRecord.appendTo(this.$recordsContainer).then(
|
||||
var def = ProductPickerRecord.appendTo(self.$recordsContainer).then(
|
||||
function(widget, widget_position) {
|
||||
if (typeof widget_position !== "undefined") {
|
||||
const $elm = this.$el.find(
|
||||
var $elm = self.$el.find(
|
||||
`[data-card-id="${widget_position}"]:first`
|
||||
);
|
||||
widget.$el.insertBefore($elm);
|
||||
widget.$el.insertAfter($elm);
|
||||
}
|
||||
def.resolve();
|
||||
}.bind(this, ProductPickerRecord, options.position)
|
||||
}.bind(self, ProductPickerRecord, options.position)
|
||||
);
|
||||
this.defs.push(def);
|
||||
self.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);
|
||||
}
|
||||
|
||||
return records_to_add;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -645,25 +686,80 @@ odoo.define(
|
|||
* @returns {Array}
|
||||
*/
|
||||
appendSearchRecords: function(search_records, options = {}) {
|
||||
var self = this;
|
||||
if (options.clear) {
|
||||
this.clearRecords();
|
||||
}
|
||||
this.trigger_up("loading_records");
|
||||
this.defs = [];
|
||||
this.defsVirtualState = [];
|
||||
const cur_widget_index = this.widgets.length;
|
||||
var cur_widget_index = this.widgets.length;
|
||||
this._appendSearchRecords(search_records, options);
|
||||
|
||||
const defs = this.defs;
|
||||
var defs = this.defs;
|
||||
delete this.defs;
|
||||
const defsVirtualState = this.defsVirtualState;
|
||||
var defsVirtualState = this.defsVirtualState;
|
||||
delete this.defsVirtualState;
|
||||
return [
|
||||
Promise.all(defs).then(() => {
|
||||
if (!options.no_attach_widgets && this._isInDom) {
|
||||
const new_widgets = this.widgets.slice(cur_widget_index);
|
||||
$.when(defs).then(function() {
|
||||
if (!options.no_attach_widgets && self._isInDom) {
|
||||
var new_widgets = self.widgets.slice(cur_widget_index);
|
||||
_.invoke(new_widgets, "on_attach_callback");
|
||||
}
|
||||
// Destroy unused
|
||||
if (options.cleanup) {
|
||||
self.search_records = _.compact(search_records);
|
||||
var widgets_to_destroy = _.filter(self.widgets, function(
|
||||
widget
|
||||
) {
|
||||
return (
|
||||
widget.isMarkedToDestroy() ||
|
||||
!_.some(self.search_records, function(
|
||||
search_record
|
||||
) {
|
||||
return (
|
||||
search_record.id ===
|
||||
widget.recordSearch.id &&
|
||||
!_.some(self.widgets, function(
|
||||
comp_widget
|
||||
) {
|
||||
return (
|
||||
comp_widget !== widget &&
|
||||
comp_widget.state &&
|
||||
comp_widget.recordSearch.id ===
|
||||
widget.recordSearch.id
|
||||
);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
_.invoke(widgets_to_destroy, "destroy");
|
||||
self.widgets = _.difference(
|
||||
self.widgets,
|
||||
widgets_to_destroy
|
||||
);
|
||||
} else {
|
||||
self.search_records = self.search_records || [];
|
||||
for (var search_record of search_records) {
|
||||
var has_search_record = _.some(
|
||||
self.search_records,
|
||||
function(item) {
|
||||
return (
|
||||
item.id === search_record.id &&
|
||||
item.__id === search_record.__id
|
||||
);
|
||||
}
|
||||
);
|
||||
if (!has_search_record) {
|
||||
self.search_records.push(search_record);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
Promise.all(defsVirtualState).then(() => {
|
||||
this.trigger_up("loading_records", {finished: true});
|
||||
$.when(defsVirtualState).then(function() {
|
||||
self.trigger_up("loading_records", {finished: true});
|
||||
}),
|
||||
];
|
||||
},
|
||||
|
@ -682,8 +778,8 @@ odoo.define(
|
|||
* @param {Integer} index
|
||||
*/
|
||||
doWidgetFlip: function(index) {
|
||||
const widget = this.widgets[index];
|
||||
const $actived_card = this.$el.find(".active");
|
||||
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");
|
||||
|
@ -692,11 +788,11 @@ odoo.define(
|
|||
widget._processWidgetFields(widget.$back);
|
||||
widget._processWidgets(widget.$back);
|
||||
widget._processDynamicFields();
|
||||
$.when(widget.defs).then(() => {
|
||||
$.when(widget.defs).then(function() {
|
||||
$actived_card.removeClass("active");
|
||||
$actived_card.find(".oe_flip_card_front").removeClass("d-none");
|
||||
widget.$card.addClass("active");
|
||||
setTimeout(() => {
|
||||
setTimeout(function() {
|
||||
widget.$(".oe_flip_card_front").addClass("d-none");
|
||||
}, 200);
|
||||
});
|
||||
|
@ -711,17 +807,25 @@ odoo.define(
|
|||
* @param {CustomEvent} evt
|
||||
*/
|
||||
_onRecordFlip: function(evt) {
|
||||
const prev_widget_index = evt.data.prev_widget_index;
|
||||
if (typeof prev_widget_index !== "undefined") {
|
||||
var prev_widget_index = evt.data.prev_widget_index;
|
||||
if (
|
||||
typeof prev_widget_index !== "undefined" &&
|
||||
this.widgets[prev_widget_index]
|
||||
) {
|
||||
// Only check 'back' widgets so there is where the form was created
|
||||
for (const index in this.widgets[prev_widget_index].widgets.back) {
|
||||
const widget = this.widgets[prev_widget_index].widgets.back[
|
||||
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) {
|
||||
if (
|
||||
widget.controller &&
|
||||
widget.className ===
|
||||
"oe_one2many_product_picker_quick_create"
|
||||
) {
|
||||
widget.controller.auto();
|
||||
}
|
||||
}
|
||||
this.widgets[prev_widget_index].recreate();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
// 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) {
|
||||
id = id || this.handle;
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
if (this.renderer && !_.isEmpty(this.renderer.allFieldWidgets)) {
|
||||
const product_picker_widgets = _.filter(
|
||||
this.renderer.allFieldWidgets[id],
|
||||
item => item.attrs.widget === "one2many_product_picker"
|
||||
);
|
||||
_.invoke(
|
||||
product_picker_widgets,
|
||||
"onDocumentConfirmChanges",
|
||||
fields,
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
|
@ -3,7 +3,8 @@
|
|||
odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
||||
"use strict";
|
||||
|
||||
const BasicModel = require("web.BasicModel");
|
||||
var BasicModel = require("web.BasicModel");
|
||||
var FieldOne2ManyProductPicker = require("web_widget_one2many_product_picker.FieldOne2ManyProductPicker");
|
||||
|
||||
BasicModel.include({
|
||||
/**
|
||||
|
@ -36,12 +37,21 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} id
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isSaving: function(id) {
|
||||
var data = this.localData[id];
|
||||
return data._virtual || false;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} id
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isPureVirtual: function(id) {
|
||||
const data = this.localData[id];
|
||||
var data = this.localData[id];
|
||||
return data._virtual || false;
|
||||
},
|
||||
|
||||
|
@ -50,7 +60,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
* @param {Boolean} status
|
||||
*/
|
||||
setPureVirtual: function(id, status) {
|
||||
const data = this.localData[id];
|
||||
var data = this.localData[id];
|
||||
if (status) {
|
||||
data._virtual = true;
|
||||
} else {
|
||||
|
@ -62,9 +72,9 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
* @param {String} id
|
||||
*/
|
||||
unsetDirty: function(id) {
|
||||
const data = this.localData[id];
|
||||
var data = this.localData[id];
|
||||
data._isDirty = false;
|
||||
this._visitChildren(data, r => {
|
||||
this._visitChildren(data, function(r) {
|
||||
r._isDirty = false;
|
||||
});
|
||||
},
|
||||
|
@ -81,14 +91,14 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
return false;
|
||||
}
|
||||
|
||||
const data = this.localData[id];
|
||||
const to_remove = [];
|
||||
this._visitChildren(data, item => {
|
||||
var data = this.localData[id];
|
||||
var to_remove = [];
|
||||
this._visitChildren(data, function(item) {
|
||||
to_remove.push(item.id);
|
||||
});
|
||||
|
||||
to_remove.reverse();
|
||||
for (const remove_id of to_remove) {
|
||||
for (var remove_id of to_remove) {
|
||||
this.removeLine(remove_id);
|
||||
delete this.localData[remove_id];
|
||||
}
|
||||
|
@ -103,7 +113,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
*
|
||||
* @param {Object} record
|
||||
* @param {Object} params
|
||||
* @returns {Promise}
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_makeDefaultRecordNoDatapoint: function(record, params) {
|
||||
var self = this;
|
||||
|
@ -128,15 +138,20 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
);
|
||||
}
|
||||
|
||||
return this._rpc({
|
||||
model: record.model,
|
||||
method: "default_get",
|
||||
args: [fields_key],
|
||||
context: params.context,
|
||||
}).then(function(result) {
|
||||
return this._rpc(
|
||||
{
|
||||
model: record.model,
|
||||
method: "default_get",
|
||||
args: [fields_key],
|
||||
context: params.context,
|
||||
},
|
||||
{
|
||||
shadow: true,
|
||||
}
|
||||
).then(function(result) {
|
||||
// Interrupt point (used in instant search)
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
|
||||
// We want to overwrite the default value of the handle field (if any),
|
||||
|
@ -157,44 +172,43 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
.applyDefaultValues(record.id, result, {fieldNames: fieldNames})
|
||||
.then(function() {
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
var def = new Promise(function(resolve, reject) {
|
||||
// Interrupt point (used in instant search)
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
}
|
||||
var always = function() {
|
||||
if (record._warning) {
|
||||
if (params.allowWarning) {
|
||||
delete record._warning;
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
var def = $.Deferred();
|
||||
// Interrupt point (used in instant search)
|
||||
if (!self.exists(record.id)) {
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
var always = function() {
|
||||
if (record._warning) {
|
||||
if (params.allowWarning) {
|
||||
delete record._warning;
|
||||
} else {
|
||||
def.reject();
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
self._performOnChange(record, fields_key)
|
||||
.then(always)
|
||||
.guardedCatch(always);
|
||||
});
|
||||
}
|
||||
def.resolve();
|
||||
};
|
||||
self._performOnChange(record, fields_key)
|
||||
.then(always)
|
||||
.guardedCatch(always);
|
||||
return def;
|
||||
})
|
||||
.then(function() {
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
return self._fetchRelationalData(record);
|
||||
})
|
||||
.then(function() {
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
return self._postprocess(record);
|
||||
})
|
||||
.then(function() {
|
||||
if (!self.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
// Save initial changes, so they can be restored later,
|
||||
// if we need to discard.
|
||||
|
@ -215,11 +229,11 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
* @returns {Object}
|
||||
*/
|
||||
createVirtualDatapoint: function(listID, options) {
|
||||
const list = this.localData[listID];
|
||||
const context = _.extend({}, this._getContext(list), options.context);
|
||||
var list = this.localData[listID];
|
||||
var context = _.extend({}, this._getContext(list), options.context);
|
||||
|
||||
const position = options ? options.position : "top";
|
||||
const params = {
|
||||
var position = options ? options.position : "top";
|
||||
var params = {
|
||||
context: context,
|
||||
fields: list.fields,
|
||||
fieldsInfo: list.fieldsInfo,
|
||||
|
@ -228,6 +242,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
viewType: list.viewType,
|
||||
allowWarning: true,
|
||||
doNotSetDirty: true,
|
||||
postonchange_values: options.onchange_values,
|
||||
};
|
||||
|
||||
var targetView = params.viewType;
|
||||
|
@ -251,7 +266,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
fields = _.defaults({}, fields, parentRecord.fields);
|
||||
}
|
||||
|
||||
const record = this._makeDataPoint({
|
||||
var record = this._makeDataPoint({
|
||||
modelName: list.model,
|
||||
fields: fields,
|
||||
fieldsInfo: fieldsInfo,
|
||||
|
@ -263,7 +278,8 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
this.setPureVirtual(record.id, true);
|
||||
this.updateRecordContext(record.id, {
|
||||
ignore_warning: true,
|
||||
not_onchange: true,
|
||||
not_onchange: true, // To know is the record has the initial onchange applied
|
||||
shadow: true, // To avoid show the loading backdrop
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -280,11 +296,16 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
* @returns {Deferred}
|
||||
*/
|
||||
createVirtualRecord: function(listID, options) {
|
||||
const list = this.localData[listID];
|
||||
const context = _.extend({}, this._getContext(list), options.context);
|
||||
var self = this;
|
||||
var list = this.localData[listID];
|
||||
var context = _.extend(
|
||||
{shadow: true},
|
||||
this._getContext(list),
|
||||
options.context
|
||||
);
|
||||
|
||||
const position = options ? options.position : "top";
|
||||
const params = {
|
||||
var position = options ? options.position : "top";
|
||||
var params = {
|
||||
context: context,
|
||||
fields: list.fields,
|
||||
fieldsInfo: list.fieldsInfo,
|
||||
|
@ -295,18 +316,17 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
doNotSetDirty: true,
|
||||
};
|
||||
|
||||
return new Promise(resolve => {
|
||||
this._makeDefaultRecord(list.model, params).then(recordID => {
|
||||
this.setPureVirtual(recordID, true);
|
||||
this.updateRecordContext(recordID, {
|
||||
ignore_warning: true,
|
||||
not_onchange: true,
|
||||
});
|
||||
resolve({
|
||||
record: this.get(recordID),
|
||||
params: params,
|
||||
});
|
||||
return this._makeDefaultRecord(list.model, params).then(function(recordID) {
|
||||
self.setPureVirtual(recordID, true);
|
||||
self.updateRecordContext(recordID, {
|
||||
ignore_warning: true,
|
||||
not_onchange: true,
|
||||
shadow: true,
|
||||
});
|
||||
return {
|
||||
record: self.get(recordID),
|
||||
params: params,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -319,16 +339,29 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
* @override
|
||||
*/
|
||||
_performOnChange: function(record) {
|
||||
if (record && record.context && record.context.ignore_warning) {
|
||||
const this_mp = _.clone(this);
|
||||
const super_call = this.trigger_up;
|
||||
this_mp.trigger_up = function(event_name, data) {
|
||||
if (event_name === "warning" && data.type === "dialog") {
|
||||
// Do nothing
|
||||
return;
|
||||
}
|
||||
return super_call.apply(this, arguments);
|
||||
}.bind(this);
|
||||
if (record && record.context) {
|
||||
record.context.not_onchange = false;
|
||||
var this_mp = _.clone(this);
|
||||
if (record.context.shadow) {
|
||||
// Force use 'shadow'
|
||||
var super__rpc_call = this._rpc;
|
||||
this_mp._rpc = function(params, options) {
|
||||
options = options || {};
|
||||
options.shadow = true;
|
||||
return super__rpc_call.call(this, params, options);
|
||||
}.bind(this);
|
||||
}
|
||||
if (record.context.ignore_warning) {
|
||||
var super_trigger_up_call = this.trigger_up;
|
||||
// Avoid show warnings
|
||||
this_mp.trigger_up = function(event_name, data) {
|
||||
if (event_name === "warning" && data.type === "dialog") {
|
||||
// Do nothing
|
||||
return;
|
||||
}
|
||||
return super_trigger_up_call.apply(this, arguments);
|
||||
}.bind(this);
|
||||
}
|
||||
return this._super.apply(this_mp, arguments);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
|
@ -343,17 +376,94 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
*/
|
||||
_applyOnChange: function(values, record) {
|
||||
if (!this.exists(record.id)) {
|
||||
return Promise.reject();
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Allow add multiple records at the same time
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_applyX2ManyChange: function(record, fieldName, command) {
|
||||
if (command.operation === "ADD_MULTIPLE") {
|
||||
var self = this;
|
||||
var localID =
|
||||
(record._changes && record._changes[fieldName]) ||
|
||||
record.data[fieldName];
|
||||
var list = this.localData[localID];
|
||||
list._changes = list._changes || [];
|
||||
// For now, we are in the context of a one2many field
|
||||
// the command should look like this:
|
||||
// { operation: 'ADD', ids: [id0,id1,id2,...] }
|
||||
// The corresponding record may contain value for fields that
|
||||
// are unknown in the list (e.g. fields that are in the
|
||||
// subrecord form view but not in the kanban or list view), so
|
||||
// to ensure that onchanges are correctly handled, we extend the
|
||||
// list's fields with those in the created record
|
||||
_.each(command.ids, function(id) {
|
||||
var newRecord = self.localData[id];
|
||||
_.defaults(list.fields, newRecord.fields);
|
||||
_.defaults(list.fieldsInfo, newRecord.fieldsInfo);
|
||||
newRecord.fields = list.fields;
|
||||
newRecord.fieldsInfo = list.fieldsInfo;
|
||||
newRecord.viewType = list.viewType;
|
||||
list._cache[newRecord.res_id] = newRecord.id;
|
||||
list._changes.push(command);
|
||||
});
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* This is necessary to avoid calculate onchanges that
|
||||
* affects to order_line.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_buildOnchangeSpecs: function(record) {
|
||||
var specs = this._super.apply(this, arguments);
|
||||
// This is necessary to improve the performance
|
||||
if (record.model === "sale.order" && specs) {
|
||||
// Its a change from product picker?
|
||||
// WORKAROUND: Done in this way to reutilice odoo methods
|
||||
var need_clean = false;
|
||||
var parent_controller = this.getParent();
|
||||
if (
|
||||
parent_controller &&
|
||||
parent_controller.renderer &&
|
||||
!_.isEmpty(parent_controller.renderer.allFieldWidgets)
|
||||
) {
|
||||
var order_line_widget = _.find(
|
||||
parent_controller.renderer.allFieldWidgets[
|
||||
parent_controller.handle
|
||||
],
|
||||
{name: "order_line"}
|
||||
);
|
||||
need_clean =
|
||||
order_line_widget instanceof FieldOne2ManyProductPicker &&
|
||||
!_.isEmpty(this.localData[record.data.order_line]);
|
||||
}
|
||||
if (need_clean) {
|
||||
var new_specs = _.clone(specs);
|
||||
for (var key in specs) {
|
||||
if (key.startsWith("order_line.")) {
|
||||
delete new_specs[key];
|
||||
}
|
||||
}
|
||||
return new_specs;
|
||||
}
|
||||
}
|
||||
return specs;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} recordID
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
hasChanges: function(recordID) {
|
||||
const record = this.localData[recordID];
|
||||
var record = this.localData[recordID];
|
||||
return record && !_.isEmpty(record._changes);
|
||||
},
|
||||
|
||||
|
@ -368,7 +478,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
* @param {Number} limit
|
||||
* @param {Number} offset
|
||||
* @param {Object} context
|
||||
* @returns {Promise}
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
fetchNameSearchFull: function(
|
||||
model_fields,
|
||||
|
@ -382,6 +492,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
offset,
|
||||
context
|
||||
) {
|
||||
var self = this;
|
||||
return this._rpc({
|
||||
model: model,
|
||||
method: "name_search",
|
||||
|
@ -392,9 +503,11 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
limit: this.limit,
|
||||
context: context || {},
|
||||
},
|
||||
}).then(results => {
|
||||
const record_ids = results.map(item => item[0]);
|
||||
return this.fetchGenericRecords(
|
||||
}).then(function(results) {
|
||||
var record_ids = results.map(function(item) {
|
||||
return item[0];
|
||||
});
|
||||
return self.fetchGenericRecords(
|
||||
model_fields,
|
||||
model,
|
||||
[["id", "in", record_ids]],
|
||||
|
@ -416,7 +529,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
* @param {Number} limit
|
||||
* @param {Number} offset
|
||||
* @param {Object} context
|
||||
* @returns {Promise}
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
fetchGenericRecords: function(
|
||||
model_fields,
|
||||
|
@ -428,6 +541,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
offset,
|
||||
context
|
||||
) {
|
||||
var self = this;
|
||||
return this._rpc({
|
||||
model: model,
|
||||
method: "search_read",
|
||||
|
@ -437,13 +551,13 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function(require) {
|
|||
offset: offset,
|
||||
orderBy: orderby,
|
||||
kwargs: {context: context},
|
||||
}).then(result => {
|
||||
for (const index in result) {
|
||||
const record = result[index];
|
||||
for (const fieldName in record) {
|
||||
const field = model_fields[fieldName];
|
||||
}).then(function(result) {
|
||||
for (var index in result) {
|
||||
var record = result[index];
|
||||
for (var fieldName in record) {
|
||||
var field = model_fields[fieldName];
|
||||
if (field.type !== "many2one") {
|
||||
record[fieldName] = this._parseServerValue(
|
||||
record[fieldName] = self._parseServerValue(
|
||||
model_fields[fieldName],
|
||||
record[fieldName]
|
||||
);
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
// 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.FormController", function(require) {
|
||||
"use strict";
|
||||
|
||||
var FormController = require("web.FormController");
|
||||
|
||||
FormController.include({
|
||||
custom_events: _.extend({}, FormController.prototype.custom_events, {
|
||||
using_product_picker: "_onUsingProductPicker",
|
||||
}),
|
||||
|
||||
/**
|
||||
* Disable product picker while saving
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
saveRecord: function() {
|
||||
var self = this;
|
||||
var always = function(changedFields) {
|
||||
self.renderer.invokeProductPicker(self.handle, "onDocumentSave", false);
|
||||
return changedFields;
|
||||
};
|
||||
this.renderer.invokeProductPicker(this.handle, "onDocumentSave", true);
|
||||
return this._super
|
||||
.apply(this, arguments)
|
||||
.then(always)
|
||||
.guardedCatch(always);
|
||||
},
|
||||
|
||||
/**
|
||||
* This is necessary to refresh 'one2many_product_picker' when some 'trigger_refresh_fields' fields changes.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_confirmChange: function(id, fields, e) {
|
||||
var self = this;
|
||||
id = id || this.handle;
|
||||
return this._super.apply(this, arguments).then(function(resetWidgets) {
|
||||
if (self.renderer) {
|
||||
self.renderer.invokeProductPicker(
|
||||
id,
|
||||
"onDocumentConfirmChanges",
|
||||
fields,
|
||||
e
|
||||
);
|
||||
}
|
||||
return resetWidgets;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
_onUsingProductPicker: function(ev) {
|
||||
this.model.updateRecordContext(this.handle, {
|
||||
product_picker_field: ev.data.field,
|
||||
product_picker_product_field: ev.data.product_field,
|
||||
product_picker_relation: ev.data.relation,
|
||||
product_picker_relation_field: ev.data.relation_field,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2020 Tecnativa - Alexandre Díaz
|
||||
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
odoo.define("web_widget_one2many_product_picker.FormRenderer", function(require) {
|
||||
"use strict";
|
||||
|
||||
var FormRenderer = require("web.FormRenderer");
|
||||
|
||||
FormRenderer.include({
|
||||
/**
|
||||
* Invoke the selected method on all product picer defined
|
||||
*/
|
||||
invokeProductPicker: function(recordID, method_name, ...params) {
|
||||
if (_.isEmpty(this.allFieldWidgets)) {
|
||||
return;
|
||||
}
|
||||
var product_picker_widgets = _.filter(
|
||||
this.allFieldWidgets[recordID],
|
||||
function(item) {
|
||||
return item.attrs.widget === "one2many_product_picker";
|
||||
}
|
||||
);
|
||||
_.invoke(product_picker_widgets, method_name, ...params);
|
||||
},
|
||||
});
|
||||
});
|
|
@ -1,22 +1,22 @@
|
|||
/* global py */
|
||||
// Copyright 2020 Tecnativa - Alexandre Díaz
|
||||
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
odoo.define("web_widget_one2many_product_picker.BasicView", function(require) {
|
||||
odoo.define("web_widget_one2many_product_picker.FormView", function(require) {
|
||||
"use strict";
|
||||
|
||||
const core = require("web.core");
|
||||
const pyUtils = require("web.py_utils");
|
||||
const BasicView = require("web.BasicView");
|
||||
var core = require("web.core");
|
||||
var pyUtils = require("web.py_utils");
|
||||
var FormView = require("web.FormView");
|
||||
|
||||
const _t = core._t;
|
||||
var _t = core._t;
|
||||
|
||||
// Add ref to _() -> _t() call
|
||||
const PY_t = new py.PY_def.fromJSON(function() {
|
||||
const args = py.PY_parseArgs(arguments, ["str"]);
|
||||
var PY_t = new py.PY_def.fromJSON(function() {
|
||||
var args = py.PY_parseArgs(arguments, ["str"]);
|
||||
return py.str.fromJSON(_t(args.str.toJSON()));
|
||||
});
|
||||
|
||||
BasicView.include({
|
||||
FormView.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,27 @@
|
|||
.oe_field_one2many_product_picker {
|
||||
/** FIX AUTO-SCROLL ON SAMSUNG DEVICES **/
|
||||
* {
|
||||
overflow-anchor: none !important;
|
||||
scroll-snap-stop: normal !important;
|
||||
overscroll-behavior: unset !important;
|
||||
scroll-behavior: unset !important;
|
||||
}
|
||||
|
||||
.container,
|
||||
header,
|
||||
footer,
|
||||
.container-fluid,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
section {
|
||||
overflow-anchor: none !important;
|
||||
scroll-snap-stop: normal !important;
|
||||
overscroll-behavior: unset !important;
|
||||
scroll-behavior: unset !important;
|
||||
}
|
||||
/** END FIX **/
|
||||
|
||||
&.oe_field_one2many_product_picker_maximized {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -15,6 +38,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.badge {
|
||||
filter: opacity(0.6);
|
||||
}
|
||||
pointer-events: none;
|
||||
|
||||
> div.loading {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
width: unset !important;
|
||||
}
|
||||
|
@ -30,6 +64,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
div.loading {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
padding: 0.5em;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 2px solid gray;
|
||||
z-index: 2;
|
||||
box-shadow: 2px 2px 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o_cp_buttons {
|
||||
width: 100%;
|
||||
|
||||
|
@ -57,6 +104,11 @@
|
|||
margin-right: 0;
|
||||
}
|
||||
|
||||
.o_catch_attention {
|
||||
animation: none;
|
||||
outline: 1px solid fade-in(theme-color("warning"), 0.5);
|
||||
}
|
||||
|
||||
.oe_flip_card {
|
||||
user-select: none;
|
||||
background-color: transparent;
|
||||
|
@ -67,9 +119,17 @@
|
|||
height $one2many-product-picker-transition-3d-time;
|
||||
height: $one2many-product-picker-card-min-height;
|
||||
|
||||
&.blocked {
|
||||
.badge {
|
||||
filter: opacity(0.7);
|
||||
}
|
||||
div.loading {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.5;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
&.oe_flip_card_maximized {
|
||||
|
@ -138,7 +198,7 @@
|
|||
transform-style: preserve-3d;
|
||||
|
||||
.img-fluid {
|
||||
transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%) !important;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: -1;
|
||||
|
@ -234,6 +294,7 @@
|
|||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
width: 100%;
|
||||
|
||||
.oe_one2many_product_picker_form_buttons {
|
||||
display: flex;
|
||||
|
@ -263,4 +324,39 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Extra tools **/
|
||||
.icon-waiting {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/* clears the 'X' from Internet Explorer */
|
||||
input[type="search"]::-ms-clear {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
input[type="search"]::-ms-reveal {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* clears the 'X' from Chrome */
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-results-button,
|
||||
input[type="search"]::-webkit-search-results-decoration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/** Modal Price **/
|
||||
.oe_product_picker_quick_modif_price {
|
||||
.modal-body {
|
||||
min-height: 7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,12 @@
|
|||
aria-describedby="btnGroupAddon2"
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
id="product_picker_clear_input"
|
||||
class='o_fullscreen btn btn-secondary'
|
||||
>
|
||||
<i class='fa fa-eraser' />
|
||||
</button>
|
||||
<button
|
||||
id="product_picker_maximize"
|
||||
class='o_fullscreen btn btn-primary'
|
||||
|
@ -88,7 +94,7 @@
|
|||
</t>
|
||||
<t t-name="One2ManyProductPicker.ActionButton">
|
||||
<div class="safezone d-inline-block float-left m-0 pb-2 pr-2 text-left">
|
||||
<t t-if="is_saving && lazy_qty > 0">
|
||||
<t t-if="need_notify || need_save || is_saving">
|
||||
<span
|
||||
class="badge record_saving badge-warning font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
|
||||
><span class="lazy_product_qty" t-esc="lazy_qty || '1'" /> x <t
|
||||
|
@ -97,15 +103,23 @@
|
|||
</t>
|
||||
<t t-elif="!is_virtual">
|
||||
<span
|
||||
t-att-data-field="field_map[field_uom_qty]"
|
||||
t-attf-data-esc="'{{floatFixed(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 record_saving {{modified && 'badge-warning' || 'badge-success'}} font-weight-bold rounded-0 mt-0 px-2 py-3 product_qty"
|
||||
><span
|
||||
t-att-data-field="field_map[field_uom_qty]"
|
||||
t-attf-data-esc="str(floatFixed('{{field_map[field_uom_qty]}}'))"
|
||||
class="lazy_product_qty"
|
||||
/> x <span
|
||||
t-att-data-field="field_map[field_uom]"
|
||||
t-attf-data-esc="obj.{{field_map[field_uom]}}.data.display_name"
|
||||
/></span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span
|
||||
class="badge badge-primary font-weight-bold rounded-0 mt-0 px-2 py-3 add_product"
|
||||
><i class="fa fa-plus" /> 1 <t
|
||||
><i class="fa fa-plus" /><span
|
||||
class="lazy_product_qty"
|
||||
t-esc="lazy_qty || '1'"
|
||||
/> x <t
|
||||
t-esc="state.data[field_map[field_uom]].data.display_name"
|
||||
/></span>
|
||||
</t>
|
||||
|
@ -116,12 +130,12 @@
|
|||
<t t-if="show_discount">
|
||||
<span
|
||||
t-att-data-field="field_map.discount"
|
||||
t-attf-data-esc="str({{field_map.discount}} * -1.0) +'%'"
|
||||
t-attf-data-esc="str(obj.{{field_map.discount}} * -1.0) +'%'"
|
||||
class="badge badge-dark discount_price font-weight-bold rounded-0 mt-1 p-2"
|
||||
/>
|
||||
<span
|
||||
t-att-data-field="field_map.price_unit"
|
||||
t-attf-data-esc="'{{monetary('price_unit',true)}}'"
|
||||
data-esc="str(monetary('price_unit'))"
|
||||
class="badge font-weight-bold rounded-0 original_price"
|
||||
/>
|
||||
<span
|
||||
|
@ -132,7 +146,7 @@
|
|||
<t t-else="has_onchange">
|
||||
<span
|
||||
t-att-data-field="field_map.price_unit"
|
||||
t-attf-data-esc="'{{monetary('price_unit',true)}}'"
|
||||
data-esc="str(monetary('price_unit'))"
|
||||
class="badge badge-info price_unit font-weight-bold rounded-0 mt-1 p-2"
|
||||
/>
|
||||
</t>
|
||||
|
@ -140,7 +154,7 @@
|
|||
</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-attf-class="oe_flip_card_front p-0 {{((need_notify || need_save || modified || is_saving) && 'border-warning') || (state && !is_virtual && 'border-success') || ''}}"
|
||||
>
|
||||
<t t-if="state">
|
||||
<div class="indicator_zones float-left">
|
||||
|
@ -150,7 +164,7 @@
|
|||
<span
|
||||
data-field="display_name"
|
||||
class="oe_one2many_product_picker_title position-absolute fixed-bottom p-1"
|
||||
data-esc="display_name"
|
||||
data-esc="obj.display_name"
|
||||
/>
|
||||
<img
|
||||
alt=""
|
||||
|
@ -175,6 +189,9 @@
|
|||
</t>
|
||||
<t t-name="One2ManyProductPicker.FlipCard.Back">
|
||||
<div class="oe_flip_card_back">
|
||||
<span class="icon-waiting">
|
||||
<i class="fa fa-cog fa-spin fa-3x fa-fw" />
|
||||
</span>
|
||||
<widget
|
||||
name="product_picker_quick_create_form"
|
||||
t-att-compare-key="field_map.product_uom"
|
||||
|
@ -183,12 +200,11 @@
|
|||
</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"
|
||||
class="oe_flip_container p-1 col-12 col-sm-6 col-md-4 col-lg-4 col-xl-4 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 t-attf-class="oe_flip_card {{!state && 'disabled' || ''}}">
|
||||
<div class="loading">LOADING...</div>
|
||||
<div class="oe_flip_card_inner text-center">
|
||||
<t t-call="One2ManyProductPicker.FlipCard.Front" />
|
||||
<t t-call="One2ManyProductPicker.FlipCard.Back" />
|
||||
|
@ -207,12 +223,13 @@
|
|||
>
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body p-0" />
|
||||
<div class="modal-body p-0">
|
||||
<span class="icon-waiting">
|
||||
<i class="fa fa-cog fa-spin fa-3x fa-fw" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
class="btn btn-success oe_record_change mr-auto"
|
||||
data-dismiss="modal"
|
||||
>
|
||||
<button class="btn btn-success oe_record_change mr-auto d-none">
|
||||
<i class="fa fa-check" />
|
||||
</button>
|
||||
<button
|
||||
|
|
|
@ -2,22 +2,22 @@
|
|||
<t t-name="One2ManyProductPicker.QuickCreate.FormButtons">
|
||||
<div class="oe_one2many_product_picker_form_buttons">
|
||||
<t t-if="state == 'new'">
|
||||
<button t-attf-class="btn btn-primary oe_record_add">Add</button>
|
||||
<button t-attf-class="btn btn-primary oe_record_add w-100">Add</button>
|
||||
</t>
|
||||
<t t-elif="state == 'dirty'">
|
||||
<button class="btn btn-success oe_record_change w-100">
|
||||
<i class="fa fa-check" />
|
||||
<button class="btn btn-success oe_record_change w-50">
|
||||
<i class="fa fa-check" /> Accept
|
||||
</button>
|
||||
<button class="btn btn-warning oe_record_discard ml-1">
|
||||
<i class="fa fa-times" />
|
||||
<button class="btn btn-warning oe_record_discard ml-1 w-50">
|
||||
<i class="fa fa-times" /> Discard
|
||||
</button>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<button class="btn btn-danger oe_record_remove w-100"><i
|
||||
<button class="btn btn-danger oe_record_remove w-50"><i
|
||||
class="fa fa-trash"
|
||||
/> Remove</button>
|
||||
<button class="btn btn-warning oe_record_discard ml-1">
|
||||
<i class="fa fa-times" />
|
||||
<button class="btn btn-warning oe_record_discard ml-1 w-50">
|
||||
<i class="fa fa-times" /> Discard
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
|
|
|
@ -53,17 +53,21 @@
|
|||
type="text/javascript"
|
||||
src="/web_widget_one2many_product_picker/static/src/js/views/One2ManyProductPicker/quick_modif_price_form.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/web_widget_one2many_product_picker/static/src/js/views/basic_view.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/web_widget_one2many_product_picker/static/src/js/views/basic_model.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/web_widget_one2many_product_picker/static/src/js/views/basic_controller.js"
|
||||
src="/web_widget_one2many_product_picker/static/src/js/views/form_view.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/web_widget_one2many_product_picker/static/src/js/views/form_controller.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/web_widget_one2many_product_picker/static/src/js/views/form_renderer.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
|
Loading…
Reference in New Issue