[FIX] web_widget_one2many_product_picker: Subtotal with discounts

pull/1858/head
Alexandre D. Díaz 2020-12-09 15:57:54 +01:00
parent f36b75005d
commit 9fa7deb549
14 changed files with 794 additions and 842 deletions

View File

@ -175,87 +175,6 @@ Other example for 'purchase.order.line' fields:
Usage
=====
You need to define the view fields. The view must be of ``form`` type.
This is an example that uses the 'sale.order.line' fields:
.. code:: xml
<field
name="order_line"
attrs="{'readonly': [('state', 'in', ('done','cancel'))]}"
nolabel="1"
mode="form"
widget="one2many_product_picker"
options="{'search': [{'name': 'Test', 'domain': [['name', 'ilike', '$search']]}] ,'edit_discount': True, 'show_discount': True, 'groups': [{'name': 'desk', 'string': _('Desks'), 'domain': [('name', 'ilike', '%desk%')], 'order': [{'name': 'id', 'asc': true}]}, {'name': 'chair', 'string': _('Chairs'), 'domain': [('name', 'ilike', '%chair%')]}]}"
>
<form>
<field name="state" invisible="1" />
<field name="display_type" invisible="1" />
<field name="currency_id" invisible="1" />
<field name="discount" widget="numeric_step" options="{'max': 100}" invisible="1"/>
<field name="price_unit" widget="numeric_step" invisible="1"/>
<field name="name" invisible="1" />
<field name="product_id" invisible="1" />
<field name="order_id" invisible="1"/>
<field name="product_uom_qty" class="mb-1" widget="numeric_step" context="{
'partner_id': parent.partner_id,
'quantity': product_uom_qty,
'pricelist': parent.pricelist_id,
'uom': product_uom,
'company_id': parent.company_id
}" />
<field name="product_uom" force_save="1" attrs="{
'readonly': [('state', 'in', ('sale','done', 'cancel'))],
'required': [('display_type', '=', False)],
}" context="{'company_id': parent.company_id}" class="mb-2" options="{'no_open': True, 'no_create': True, 'no_edit': True}" />
</form>
</field>
Other example for 'purchase.order.line' fields:
.. code:: xml
<field
name="order_line"
attrs="{'readonly': [('state', 'in', ('done','cancel'))]}"
nolabel="1"
widget="one2many_product_picker"
mode="form"
options="{'search': [{'name': _('Name'), 'domain': [['name', 'ilike', '$search']]}, {'name': _('Price'), 'domain': [['list_price', '=', $number_search]]}], 'field_map': {'product_uom_qty': 'product_qty'}, 'groups': [{'name': _('Desk'), 'domain': [['name', 'ilike', 'desk']], 'order': {'name': 'id', 'asc': true}}, {'name': _('Chairs'), 'domain': [['name', 'ilike', 'chair']]}]}"
>
<form>
<field name="name" invisible="1" />
<field name="product_id" invisible="1" />
<field name="price_unit" invisible="1" />
<field name="currency_id" invisible="1" />
<field name="order_id" invisible="1" />
<field name="date_planned" class="mb-1" />
<field name="product_qty" class="mb-1" widget="numeric_step" required="1" />
<field name="product_uom" class="mb-2" options="{'no_open': True, 'no_create': True, 'no_edit': True}" />
</form>
</field>
** In this example we don't use 'field_map' option because the default match with the sale.order.line field names.
Default context:
~~~~~~~~~~~~~~~~
The widget sends a defaults context with the 'search_read' request:
* active_search_group_name > Contains the name of the active search group
* 'all' > Is the hard-coded name for the 'All' group
* 'main_lines' > Is the hard-coded name for the 'Lines' group
* active_search_involved_fields > Contains an array of dictionaries with the fields used with the searchbox content
* 'type' > Can be 'text' or 'number'
* 'field' > The field name
* 'oper' > The operator used
Preview:
~~~~~~~~

View File

@ -1,84 +1,3 @@
You need to define the view fields. The view must be of ``form`` type.
This is an example that uses the 'sale.order.line' fields:
.. code:: xml
<field
name="order_line"
attrs="{'readonly': [('state', 'in', ('done','cancel'))]}"
nolabel="1"
mode="form"
widget="one2many_product_picker"
options="{'search': [{'name': 'Test', 'domain': [['name', 'ilike', '$search']]}] ,'edit_discount': True, 'show_discount': True, 'groups': [{'name': 'desk', 'string': _('Desks'), 'domain': [('name', 'ilike', '%desk%')], 'order': [{'name': 'id', 'asc': true}]}, {'name': 'chair', 'string': _('Chairs'), 'domain': [('name', 'ilike', '%chair%')]}]}"
>
<form>
<field name="state" invisible="1" />
<field name="display_type" invisible="1" />
<field name="currency_id" invisible="1" />
<field name="discount" widget="numeric_step" options="{'max': 100}" invisible="1"/>
<field name="price_unit" widget="numeric_step" invisible="1"/>
<field name="name" invisible="1" />
<field name="product_id" invisible="1" />
<field name="order_id" invisible="1"/>
<field name="product_uom_qty" class="mb-1" widget="numeric_step" context="{
'partner_id': parent.partner_id,
'quantity': product_uom_qty,
'pricelist': parent.pricelist_id,
'uom': product_uom,
'company_id': parent.company_id
}" />
<field name="product_uom" force_save="1" attrs="{
'readonly': [('state', 'in', ('sale','done', 'cancel'))],
'required': [('display_type', '=', False)],
}" context="{'company_id': parent.company_id}" class="mb-2" options="{'no_open': True, 'no_create': True, 'no_edit': True}" />
</form>
</field>
Other example for 'purchase.order.line' fields:
.. code:: xml
<field
name="order_line"
attrs="{'readonly': [('state', 'in', ('done','cancel'))]}"
nolabel="1"
widget="one2many_product_picker"
mode="form"
options="{'search': [{'name': _('Name'), 'domain': [['name', 'ilike', '$search']]}, {'name': _('Price'), 'domain': [['list_price', '=', $number_search]]}], 'field_map': {'product_uom_qty': 'product_qty'}, 'groups': [{'name': _('Desk'), 'domain': [['name', 'ilike', 'desk']], 'order': {'name': 'id', 'asc': true}}, {'name': _('Chairs'), 'domain': [['name', 'ilike', 'chair']]}]}"
>
<form>
<field name="name" invisible="1" />
<field name="product_id" invisible="1" />
<field name="price_unit" invisible="1" />
<field name="currency_id" invisible="1" />
<field name="order_id" invisible="1" />
<field name="date_planned" class="mb-1" />
<field name="product_qty" class="mb-1" widget="numeric_step" required="1" />
<field name="product_uom" class="mb-2" options="{'no_open': True, 'no_create': True, 'no_edit': True}" />
</form>
</field>
** In this example we don't use 'field_map' option because the default match with the sale.order.line field names.
Default context:
~~~~~~~~~~~~~~~~
The widget sends a defaults context with the 'search_read' request:
* active_search_group_name > Contains the name of the active search group
* 'all' > Is the hard-coded name for the 'All' group
* 'main_lines' > Is the hard-coded name for the 'Lines' group
* active_search_involved_fields > Contains an array of dictionaries with the fields used with the searchbox content
* 'type' > Can be 'text' or 'number'
* 'field' > The field name
* 'oper' > The operator used
Preview:
~~~~~~~~

View File

@ -372,38 +372,37 @@ ul.auto-toc {
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="id2">Installation</a></li>
<li><a class="reference internal" href="#configuration" id="id3">Configuration</a><ul>
<li><a class="reference internal" href="#widget-options" id="id4">Widget options:</a></li>
<li><a class="reference internal" href="#default-context" id="id5">Default context:</a></li>
<li><a class="reference internal" href="#examples" id="id6">Examples:</a></li>
<li><a class="reference internal" href="#installation" id="id1">Installation</a></li>
<li><a class="reference internal" href="#configuration" id="id2">Configuration</a><ul>
<li><a class="reference internal" href="#widget-options" id="id3">Widget options:</a></li>
<li><a class="reference internal" href="#default-context" id="id4">Default context:</a></li>
<li><a class="reference internal" href="#examples" id="id5">Examples:</a></li>
</ul>
</li>
<li><a class="reference internal" href="#usage" id="id7">Usage</a><ul>
<li><a class="reference internal" href="#id1" id="id8">Default context:</a></li>
<li><a class="reference internal" href="#preview" id="id9">Preview:</a></li>
<li><a class="reference internal" href="#usage" id="id6">Usage</a><ul>
<li><a class="reference internal" href="#preview" id="id7">Preview:</a></li>
</ul>
</li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id10">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id11">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id12">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id13">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id14">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id15">Maintainers</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id8">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id9">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id10">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id11">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id12">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id13">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#id2">Installation</a></h1>
<h1><a class="toc-backref" href="#id1">Installation</a></h1>
<p>Its advisable to install web_widget_numeric_step to have a better usability on touch screens.</p>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id3">Configuration</a></h1>
<h1><a class="toc-backref" href="#id2">Configuration</a></h1>
<p>Create or edit a new view and use the new widget called one2many_product_picker.
You need to define the view fields. The view must be of <tt class="docutils literal">form</tt> type.</p>
<div class="section" id="widget-options">
<h2><a class="toc-backref" href="#id4">Widget options:</a></h2>
<h2><a class="toc-backref" href="#id3">Widget options:</a></h2>
<ul>
<li><p class="first">product_per_page &gt; Integer -&gt; Used to control the load more behaviour (16 by default)</p>
</li>
@ -474,7 +473,7 @@ options=&quot;{'search': [{'name': _('Starts With'), 'domain': [('name', '=like'
</pre>
</div>
<div class="section" id="default-context">
<h2><a class="toc-backref" href="#id5">Default context:</a></h2>
<h2><a class="toc-backref" href="#id4">Default context:</a></h2>
<p>The widget sends a defaults context with the search_read request:</p>
<blockquote>
<ul>
@ -499,7 +498,7 @@ options=&quot;{'search': [{'name': _('Starts With'), 'domain': [('name', '=like'
</blockquote>
</div>
<div class="section" id="examples">
<h2><a class="toc-backref" href="#id6">Examples:</a></h2>
<h2><a class="toc-backref" href="#id5">Examples:</a></h2>
<p>This is an example that uses the sale.order.line fields:</p>
<pre class="code xml literal-block">
<span class="nt">&lt;field</span>
@ -559,105 +558,23 @@ options=&quot;{'search': [{'name': _('Starts With'), 'domain': [('name', '=like'
</div>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id7">Usage</a></h1>
<p>You need to define the view fields. The view must be of <tt class="docutils literal">form</tt> type.
This is an example that uses the sale.order.line fields:</p>
<pre class="code xml literal-block">
<span class="nt">&lt;field</span>
<span class="na">name=</span><span class="s">&quot;order_line&quot;</span>
<span class="na">attrs=</span><span class="s">&quot;{'readonly': [('state', 'in', ('done','cancel'))]}&quot;</span>
<span class="na">nolabel=</span><span class="s">&quot;1&quot;</span>
<span class="na">mode=</span><span class="s">&quot;form&quot;</span>
<span class="na">widget=</span><span class="s">&quot;one2many_product_picker&quot;</span>
<span class="na">options=</span><span class="s">&quot;{'search': [{'name': 'Test', 'domain': [['name', 'ilike', '$search']]}] ,'edit_discount': True, 'show_discount': True, 'groups': [{'name': 'desk', 'string': _('Desks'), 'domain': [('name', 'ilike', '%desk%')], 'order': [{'name': 'id', 'asc': true}]}, {'name': 'chair', 'string': _('Chairs'), 'domain': [('name', 'ilike', '%chair%')]}]}&quot;</span>
<span class="nt">&gt;</span>
<span class="nt">&lt;form&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;state&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;display_type&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;currency_id&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;discount&quot;</span> <span class="na">widget=</span><span class="s">&quot;numeric_step&quot;</span> <span class="na">options=</span><span class="s">&quot;{'max': 100}&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span><span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;price_unit&quot;</span> <span class="na">widget=</span><span class="s">&quot;numeric_step&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span><span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;name&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;product_id&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;order_id&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span><span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;product_uom_qty&quot;</span> <span class="na">class=</span><span class="s">&quot;mb-1&quot;</span> <span class="na">widget=</span><span class="s">&quot;numeric_step&quot;</span> <span class="na">context=</span><span class="s">&quot;{
'partner_id': parent.partner_id,
'quantity': product_uom_qty,
'pricelist': parent.pricelist_id,
'uom': product_uom,
'company_id': parent.company_id
}&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;product_uom&quot;</span> <span class="na">force_save=</span><span class="s">&quot;1&quot;</span> <span class="na">attrs=</span><span class="s">&quot;{
'readonly': [('state', 'in', ('sale','done', 'cancel'))],
'required': [('display_type', '=', False)],
}&quot;</span> <span class="na">context=</span><span class="s">&quot;{'company_id': parent.company_id}&quot;</span> <span class="na">class=</span><span class="s">&quot;mb-2&quot;</span> <span class="na">options=</span><span class="s">&quot;{'no_open': True, 'no_create': True, 'no_edit': True}&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/form&gt;</span>
<span class="nt">&lt;/field&gt;</span>
</pre>
<p>Other example for purchase.order.line fields:</p>
<pre class="code xml literal-block">
<span class="nt">&lt;field</span>
<span class="na">name=</span><span class="s">&quot;order_line&quot;</span>
<span class="na">attrs=</span><span class="s">&quot;{'readonly': [('state', 'in', ('done','cancel'))]}&quot;</span>
<span class="na">nolabel=</span><span class="s">&quot;1&quot;</span>
<span class="na">widget=</span><span class="s">&quot;one2many_product_picker&quot;</span>
<span class="na">mode=</span><span class="s">&quot;form&quot;</span>
<span class="na">options=</span><span class="s">&quot;{'search': [{'name': _('Name'), 'domain': [['name', 'ilike', '$search']]}, {'name': _('Price'), 'domain': [['list_price', '=', $number_search]]}], 'field_map': {'product_uom_qty': 'product_qty'}, 'groups': [{'name': _('Desk'), 'domain': [['name', 'ilike', 'desk']], 'order': {'name': 'id', 'asc': true}}, {'name': _('Chairs'), 'domain': [['name', 'ilike', 'chair']]}]}&quot;</span>
<span class="nt">&gt;</span>
<span class="nt">&lt;form&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;name&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;product_id&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;price_unit&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;currency_id&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;order_id&quot;</span> <span class="na">invisible=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;date_planned&quot;</span> <span class="na">class=</span><span class="s">&quot;mb-1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;product_qty&quot;</span> <span class="na">class=</span><span class="s">&quot;mb-1&quot;</span> <span class="na">widget=</span><span class="s">&quot;numeric_step&quot;</span> <span class="na">required=</span><span class="s">&quot;1&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;field</span> <span class="na">name=</span><span class="s">&quot;product_uom&quot;</span> <span class="na">class=</span><span class="s">&quot;mb-2&quot;</span> <span class="na">options=</span><span class="s">&quot;{'no_open': True, 'no_create': True, 'no_edit': True}&quot;</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/form&gt;</span>
<span class="nt">&lt;/field&gt;</span>
</pre>
<p>** In this example we dont use field_map option because the default match with the sale.order.line field names.</p>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id8">Default context:</a></h2>
<p>The widget sends a defaults context with the search_read request:</p>
<blockquote>
<ul>
<li><p class="first">active_search_group_name &gt; Contains the name of the active search group</p>
<blockquote>
<ul class="simple">
<li>all &gt; Is the hard-coded name for the All group</li>
<li>main_lines &gt; Is the hard-coded name for the Lines group</li>
</ul>
</blockquote>
</li>
<li><p class="first">active_search_involved_fields &gt; Contains an array of dictionaries with the fields used with the searchbox content</p>
<blockquote>
<ul class="simple">
<li>type &gt; Can be text or number</li>
<li>field &gt; The field name</li>
<li>oper &gt; The operator used</li>
</ul>
</blockquote>
</li>
</ul>
</blockquote>
</div>
<h1><a class="toc-backref" href="#id6">Usage</a></h1>
<div class="section" id="preview">
<h2><a class="toc-backref" href="#id9">Preview:</a></h2>
<h2><a class="toc-backref" href="#id7">Preview:</a></h2>
<blockquote>
<img alt="https://raw.githubusercontent.com/OCA/web/12.0/web_widget_one2many_product_picker/static/img/product_picker.gif" src="https://raw.githubusercontent.com/OCA/web/12.0/web_widget_one2many_product_picker/static/img/product_picker.gif" />
</blockquote>
</div>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id10">Known issues / Roadmap</a></h1>
<h1><a class="toc-backref" href="#id8">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Translations in the xml options attribute of the field that use the widget cant be exported automatically to be translated</li>
<li>The product card animations can be improved. Currently the card is recreated, so we lost some elements to apply correct effects.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id11">Bug Tracker</a></h1>
<h1><a class="toc-backref" href="#id9">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
@ -665,15 +582,15 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id12">Credits</a></h1>
<h1><a class="toc-backref" href="#id10">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id13">Authors</a></h2>
<h2><a class="toc-backref" href="#id11">Authors</a></h2>
<ul class="simple">
<li>Tecnativa</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id14">Contributors</a></h2>
<h2><a class="toc-backref" href="#id12">Contributors</a></h2>
<ul>
<li><p class="first"><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:</p>
<blockquote>
@ -687,7 +604,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id15">Maintainers</a></h2>
<h2><a class="toc-backref" href="#id13">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose

View File

@ -3,6 +3,8 @@
odoo.define("web_widget_one2many_product_picker.tools", function (
require
) {
"use strict";
var field_utils = require("web.field_utils");
/**
@ -10,18 +12,21 @@ odoo.define("web_widget_one2many_product_picker.tools", function (
*
* @param {Number} price
* @param {Number} discount
* @returns {Number}
*/
function priceReduce (price, discount) {
return price * (1.0 - discount / 100.0);
};
}
/**
* Print formatted price using the 'currency_field'
* info in 'data'.
*
* @param {Number} value
* @param {Object} field_info,
* @param {String} currency_field
* @param {Object} data
* @returns {String}
*/
function monetary (value, field_info, currency_field, data) {
return field_utils.format.monetary(
@ -32,7 +37,7 @@ odoo.define("web_widget_one2many_product_picker.tools", function (
currency_field: currency_field,
field_digits: true,
});
};
}
return {
monetary: monetary,

View File

@ -1,12 +1,16 @@
// 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.ProductPickerQuickCreateForm", function (require) {
odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateForm", function (
require
) {
"use strict";
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;
var ProductPickerQuickCreateFormView = require(
"web_widget_one2many_product_picker.ProductPickerQuickCreateFormView"
).ProductPickerQuickCreateFormView;
var qweb = core.qweb;
@ -42,6 +46,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateForm", f
this.id = this.state && this.state.id;
this.editContext = {};
},
/**
* @override
*/
@ -65,8 +70,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateForm", f
var refinedContext = _.extend(
{},
this.main_state.getContext(),
this.nodeContext,
);
this.nodeContext);
_.extend(refinedContext, this.editContext);
this.formView = new ProductPickerQuickCreateFormView(fieldsView, {
context: refinedContext,

View File

@ -1,6 +1,8 @@
// 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.ProductPickerQuickCreateFormView", function (require) {
odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView", function (
require
) {
"use strict";
/**
@ -17,28 +19,34 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
BasicModel.include({
_applyOnChange: function (values, record, viewType) {
if ('ignore_onchanges' in record.context) {
for (var field_name of record.context['ignore_onchanges']) {
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'];
delete record.context.ignore_onchanges;
}
return this._super(values, record, viewType);
},
});
var ProductPickerQuickCreateFormRenderer = QuickCreateFormView.prototype.config.Renderer.extend(
var ProductPickerQuickCreateFormRenderer =
QuickCreateFormView.prototype.config.Renderer.extend(
{
/**
* @override
*/
start: function () {
this.$el.addClass("oe_one2many_product_picker_form_view o_xxs_form_view");
this.$el.addClass(
"oe_one2many_product_picker_form_view o_xxs_form_view");
return this._super.apply(this, arguments);
},
}
);
var ProductPickerQuickCreateFormController = QuickCreateFormView.prototype.config.Controller.extend(
var ProductPickerQuickCreateFormController =
QuickCreateFormView.prototype.config.Controller.extend(
{
events: _.extend({}, QuickCreateFormView.prototype.events, {
"click .oe_record_add": "_onClickAdd",
@ -81,9 +89,11 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
}
}
}
this.$el.find(".oe_one2many_product_picker_form_buttons").remove();
this.$el.find(
".oe_one2many_product_picker_form_buttons").remove();
this.$el.find(".o_form_view").append(
qweb.render("One2ManyProductPicker.QuickCreate.FormButtons", {
qweb.render(
"One2ManyProductPicker.QuickCreate.FormButtons", {
state: state,
})
);
@ -93,7 +103,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
* @private
*/
_disableQuickCreate: function () {
this._disabled = true; // ensures that the record won't be created twice
// Ensures that the record won't be created twice
this._disabled = true;
this.$el.addClass("o_disabled");
this.$("input:not(:disabled)")
.addClass("o_temporarily_disabled")
@ -104,7 +116,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
* @private
*/
_enableQuickCreate: function () {
this._disabled = false; // allows to create again
// Allows to create again
this._disabled = false;
this.$el.removeClass("o_disabled");
this.$("input.o_temporarily_disabled")
.removeClass("o_temporarily_disabled")
@ -114,6 +128,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
/**
* @private
* @param {Array[String]} fields_changed
* @returns {Boolean}
*/
_needReloadCard: function (fields_changed) {
for (var index in fields_changed) {
@ -145,20 +160,23 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
}
var reload_values = {
compareValue: new_value,
}
};
var record = this.model.get(this.handle);
if (!('base_record_id' in record.context)) {
var 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;
reload_values.baseRecordID = record.id;
reload_values.baseRecordResID = record.ref;
reload_values.baseRecordCompareValue = old_value;
} else {
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;
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;
}
this.trigger_up("reload_view", reload_values);
@ -170,7 +188,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
if (this.model.isPureVirtual(this.handle)) {
this.model.unsetDirty(this.handle);
}
this.model.updateRecordContext(this.handle, {has_changes_confirmed: false});
this.model.updateRecordContext(this.handle, {
has_changes_confirmed: false,
});
this.trigger_up("quick_record_updated", {
changes: ev.data.changes,
});
@ -184,7 +204,8 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
*/
_add: function () {
if (this._disabled) {
// don't do anything if we are already creating a record
// Don't do anything if we are already creating a record
return $.Deferred();
}
var self = this;
@ -211,7 +232,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
*/
_onClickAdd: function (ev) {
ev.stopPropagation();
this.model.updateRecordContext(this.handle, {has_changes_confirmed: true});
this.model.updateRecordContext(this.handle, {
has_changes_confirmed: true,
});
this._add();
},
@ -221,7 +244,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
*/
_onClickRemove: function (ev) {
ev.stopPropagation();
this.trigger_up("list_record_remove", {id: this.renderer.state.id});
this.trigger_up("list_record_remove", {
id: this.renderer.state.id,
});
},
/**
@ -230,7 +255,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
*/
_onClickChange: function (ev) {
ev.stopPropagation();
this.model.updateRecordContext(this.handle, {has_changes_confirmed: true});
this.model.updateRecordContext(this.handle, {
has_changes_confirmed: true,
});
var record = this.model.get(this.handle);
this.trigger_up("update_quick_record", {
id: record.id,
@ -239,6 +266,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickCreateFormView
this.model.unsetDirty(this.handle);
this._updateButtons();
},
/**
* @private
* @param {MouseEvent} ev

View File

@ -1,11 +1,15 @@
// 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.ProductPickerQuickModifPriceForm", function (require) {
odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm", function (
require
) {
"use strict";
var core = require("web.core");
var Widget = require("web.Widget");
var ProductPickerQuickModifPriceFormView = require("web_widget_one2many_product_picker.ProductPickerQuickModifPriceFormView").ProductPickerQuickModifPriceFormView;
var ProductPickerQuickModifPriceFormView = require(
"web_widget_one2many_product_picker.ProductPickerQuickModifPriceFormView"
).ProductPickerQuickModifPriceFormView;
var qweb = core.qweb;
@ -101,7 +105,8 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
*/
_generateFormArch: function () {
var wanted_field_states = this._getWantedFieldState();
var template = "<templates><t t-name='One2ManyProductPicker.QuickModifPrice.Form'>";
var template =
"<templates><t t-name='One2ManyProductPicker.QuickModifPrice.Form'>";
template += this.basicFieldParams.field.views.form.arch;
template += "</t></templates>";
qweb.add_template(template);
@ -115,7 +120,8 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
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")) : {};
var modifiers =
$field.attr("modifiers") ? JSON.parse($field.attr("modifiers")) : {};
modifiers.invisible = false;
modifiers.readonly = wanted_field_states[field_name];
$field.attr("modifiers", JSON.stringify(modifiers));

View File

@ -1,6 +1,8 @@
// 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.ProductPickerQuickModifPriceFormView", function (require) {
odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceFormView", function (
require
) {
"use strict";
/**
@ -14,14 +16,16 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
var qweb = core.qweb;
var 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");
this.$el.addClass(
"oe_one2many_product_picker_form_view o_xxs_form_view");
return this._super.apply(this, arguments).then(function () {
self._appendPrice();
self._appendButtons();
@ -32,9 +36,11 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
* @private
*/
_appendButtons: function () {
this.$el.find(".oe_one2many_product_picker_form_buttons").remove();
this.$el.find(
".oe_one2many_product_picker_form_buttons").remove();
this.$el.append(
qweb.render("One2ManyProductPicker.QuickModifPrice.FormButtons", {
qweb.render(
"One2ManyProductPicker.QuickModifPrice.FormButtons", {
mode: this.mode,
})
);
@ -48,12 +54,12 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
this.$el.append(
qweb.render("One2ManyProductPicker.QuickModifPrice.Price")
);
}
},
}
);
var ProductPickerQuickModifPriceFormController = QuickCreateFormView.prototype.config.Controller.extend(
var ProductPickerQuickModifPriceFormController =
QuickCreateFormView.prototype.config.Controller.extend(
{
events: _.extend({}, QuickCreateFormView.prototype.events, {
"click .oe_record_change": "_onClickChange",
@ -84,7 +90,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
/**
* @override
*/
_onFieldChanged: function (ev) {
_onFieldChanged: function () {
this._super.apply(this, arguments);
this._updatePrice();
},
@ -94,7 +100,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
*/
_updatePrice: function () {
var record = this.model.get(this.handle);
var price_reduce = tools.priceReduce(record.data[this.fieldMap.price_unit], record.data[this.fieldMap.discount]);
var price_reduce = tools.priceReduce(
record.data[this.fieldMap.price_unit],
record.data[this.fieldMap.discount]);
this.renderer.$el.find(".oe_price").html(
tools.monetary(
price_reduce,
@ -109,7 +117,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
* @private
*/
_disableQuickCreate: function () {
this._disabled = true; // ensures that the record won't be created twice
// Ensures that the record won't be created twice
this._disabled = true;
this.$el.addClass("o_disabled");
this.$("input:not(:disabled)")
.addClass("o_temporarily_disabled")
@ -120,7 +130,9 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
* @private
*/
_enableQuickCreate: function () {
this._disabled = false; // allows to create again
// Allows to create again
this._disabled = false;
this.$el.removeClass("o_disabled");
this.$("input.o_temporarily_disabled")
.removeClass("o_temporarily_disabled")
@ -134,8 +146,11 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
_onClickChange: function (ev) {
var self = this;
ev.stopPropagation();
this.model.updateRecordContext(this.handle, {has_changes_confirmed: true});
this.model.updateRecordContext(this.handle, {
has_changes_confirmed: true,
});
var is_virtual = this.model.isPureVirtual(this.handle);
// If is a 'pure virtual' record, save it in the selected list
if (is_virtual) {
if (this.model.isDirty(this.handle)) {
@ -148,6 +163,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
}).then(function () {
self._enableQuickCreate();
var record = self.model.get(self.handle);
self.model.unsetDirty(self.handle);
self.trigger_up("create_quick_record", {
id: record.id,
});
@ -157,6 +173,7 @@ odoo.define("web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm
this.getParent().destroy();
}
} else {
// If is a "normal" record, update it
var record = this.model.get(this.handle);
this.trigger_up("update_quick_record", {

View File

@ -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("web_widget_one2many_product_picker.One2ManyProductPickerRecord", function (
@ -9,12 +10,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
var Widget = require("web.Widget");
var Domain = require("web.Domain");
var widgetRegistry = require("web.widget_registry");
var core = require("web.core");
var tools = require("web_widget_one2many_product_picker.tools");
var ProductPickerQuickModifPriceForm = require(
"web_widget_one2many_product_picker.ProductPickerQuickModifPriceForm");
var qweb = core.qweb;
var _t = core._t;
/* This represent a record (a card) */
var One2ManyProductPickerRecord = Widget.extend({
@ -74,7 +75,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @override
*/
update: function (record) {
// detach the widgets because the record will empty its $el, which
// Detach the widgets because the record will empty its $el, which
// will remove all event handlers on its descendants, and we want
// to keep those handlers alive as we will re-use these widgets
_.invoke(_.pluck(this.subWidgets, "$el"), "detach");
@ -100,8 +102,9 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* Generates the URL for the given product using the selected field
*
* @private
* @param {string} field
* @returns {string}
* @param {Number} product_id
* @param {String} field_name
* @returns {String}
*/
_getImageUrl: function (product_id, field_name) {
return _.str.sprintf(
@ -128,8 +131,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
/**
* @private
* @param {string} d a stringified domain
* @returns {boolean} the domain evaluted with the current values
* @param {String} d a stringified domain
* @returns {Boolean} the domain evaluted with the current values
*/
_computeDomain: function (d) {
return new Domain(d).compute(
@ -160,6 +163,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @returns {Object}
*/
_getQWebContext: function () {
// Using directly the 'model record' instead of the state because
// the state it's a parsed version of this record that doesn't
// contains the '_virtual' attribute.
@ -186,7 +190,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
*/
_getInternalVirtualRecordContext: function () {
var context = {};
context["default_" + this.options.basicFieldParams.relation_field] = this.options.basicFieldParams.state.id || null;
context["default_" + this.options.basicFieldParams.relation_field] =
this.options.basicFieldParams.state.id || null;
return context;
},
@ -199,7 +204,10 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
*/
_getInternalVirtualRecordData: function () {
var data = {};
data[this.options.fieldMap.product] = {operation: 'ADD', id: this.recordSearch.id};
data[this.options.fieldMap.product] = {
operation: 'ADD',
id: this.recordSearch.id,
};
return data;
},
@ -211,9 +219,11 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
*/
_generateVirtualState: function (data, context) {
var model = this.options.basicFieldParams.model;
var scontext = _.extend({}, this._getInternalVirtualRecordContext(), context);
var scontext = _.extend(
{}, this._getInternalVirtualRecordContext(), context);
var sdata = _.extend({}, this._getInternalVirtualRecordData(), data);
return model.createVirtualRecord(this.options.basicFieldParams.value.id, {
return model.createVirtualRecord(
this.options.basicFieldParams.value.id, {
data: sdata,
context: scontext,
});
@ -225,7 +235,10 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
_render: function () {
this.defs = [];
this._replaceElement(
qweb.render("One2ManyProductPicker.FlipCard", this._getQWebContext())
qweb.render(
"One2ManyProductPicker.FlipCard",
this._getQWebContext()
)
);
this.$card = this.$(".oe_flip_card");
this.$front = this.$(".oe_flip_card_front");
@ -241,6 +254,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* any, or directly by the formatted value
*
* @private
* @param {jQueryElement} $container
*/
_processWidgetFields: function ($container) {
var self = this;
@ -257,13 +271,21 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
// even if it is not specified in the view.
if (field_widget || self.fields[field_name].type === "many2many") {
var widget = self.subWidgets[field_name];
if (!widget) {
if (widget) {
// a widget already exists for that field, so reset it
// with the new state
widget.reset(self.state);
$field.replaceWith(widget.$el);
} else {
// the widget doesn't exist yet, so instanciate it
var Widget = self.fieldsInfo[field_name].Widget;
if (Widget) {
widget = self._processWidget($field, field_name, Widget);
self.subWidgets[field_name] = widget;
} else if (config.debug) {
// the widget is not implemented
$field.replaceWith(
$("<span>", {
@ -274,11 +296,6 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
})
);
}
} else {
// a widget already exists for that field, so reset it
// with the new state
widget.reset(self.state);
$field.replaceWith(widget.$el);
}
}
});
@ -294,6 +311,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @returns {Widget} the widget instance
*/
_processWidget: function ($field, field_name, Widget) {
// some field's attrs might be record dependent (they start with
// 't-att-') and should thus be evaluated, which is done by qweb
// we here replace those attrs in the dict of attrs of the state
@ -301,7 +319,6 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
// field's widgets point of view
// that dict being shared between records, we don't modify it
// in place
var self = this;
var attrs = Object.create(null);
_.each(this.fieldsInfo[field_name], function (value, key) {
if (_.str.startsWith(key, "t-att-")) {
@ -310,8 +327,14 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
}
attrs[key] = value;
});
var options = _.extend({}, this.options, {attrs: attrs, data: this.state.data});
var widget = new Widget(this, field_name, this.getParent().state, options);
var options = _.extend({}, this.options, {
attrs: attrs,
data: this.state.data,
});
var widget = new Widget(
this, field_name,
this.getParent().state,
options);
var def = widget.replace($field);
if (def.state() === "pending") {
this.defs.push(def);
@ -329,8 +352,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
var self = this;
$container.find("widget").each(function () {
var $field = $(this);
var Widget = widgetRegistry.get($field.attr("name"));
var widget = new Widget(self, {
var FieldWidget = widgetRegistry.get($field.attr("name"));
var widget = new FieldWidget(self, {
fieldsInfo: self.fieldsInfo,
fields: self.fields,
main_state: self.getParent().state,
@ -346,8 +369,9 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
self.widgets.push(widget);
var def = widget
._widgetRenderAndInsert(function () {})
.then(function () {
._widgetRenderAndInsert(function () {
// Do nothing
}).then(function () {
widget.$el.addClass("o_widget");
$field.replaceWith(widget.$el);
});
@ -384,7 +408,9 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
var to_find = [];
if (!_.isEmpty(fields)) {
to_find = _.map(fields, function(field){ return _.str.sprintf("[data-field=%s]", [field]); });
to_find = _.map(fields, function (field) {
return _.str.sprintf("[data-field=%s]", [field]);
});
} else {
to_find = ["[data-field]"];
}
@ -401,11 +427,13 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
var field_map = this.options.fieldMap;
if (state_data) {
var has_discount = state_data[field_map.discount] > 0.0;
this.$el.find(".original_price,.discount_price").toggleClass("d-none", !has_discount);
this.$el.find(".original_price,.discount_price")
.toggleClass("d-none", !has_discount);
if (has_discount) {
this.$el.find(".price_unit").html(this._calcPriceReduced());
} else {
this.$el.find(".price_unit").html(this._getMonetaryFieldValue("price_unit"));
this.$el.find(".price_unit").html(
this._getMonetaryFieldValue("price_unit"));
}
}
}
@ -420,7 +448,9 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
var field_map = this.options.fieldMap;
var state_data = this.state.data;
if (state_data && state_data[field_map.discount]) {
price_reduce = tools.priceReduce(state_data[field_map.price_unit], state_data[field_map.discount]);
price_reduce = tools.priceReduce(
state_data[field_map.price_unit],
state_data[field_map.discount]);
}
return price_reduce && tools.monetary(
price_reduce,
@ -451,7 +481,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
canEditDiscount: this.options.editDiscount,
currencyField: this.options.currencyField,
});
this.$modifPricePopup = $(qweb.render("One2ManyProductPicker.QuickModifPricePopup"));
this.$modifPricePopup = $(
qweb.render("One2ManyProductPicker.QuickModifPricePopup"));
this.$modifPricePopup.appendTo($(".o_main_content"));
modif_price_form.attachTo(this.$modifPricePopup);
},
@ -463,12 +494,15 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @param {ClickEvent} evt
*/
_onClickFlipCard: function (evt) {
// Avoid clicks on form elements
if (['INPUT', 'BUTTON', 'A'].indexOf(evt.target.tagName) !== -1) {
return;
}
if (!this._clickFlipCardDelayed) {
this._clickFlipCardDelayed = setTimeout(this._onClickDelayedFlipCard.bind(this, evt), this._click_card_delayed_time);
this._clickFlipCardDelayed = setTimeout(
this._onClickDelayedFlipCard.bind(this, evt),
this._click_card_delayed_time);
}
++this._clickFlipCardCount;
if (this._clickFlipCardCount >= 2) {
@ -481,9 +515,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
/**
* @private
* @param {MouseEvent} evt
*/
_onClickDelayedFlipCard: function (evt) {
_onClickDelayedFlipCard: function () {
this._clickFlipCardDelayed = false;
this._clickFlipCardCount = 0;
@ -504,7 +537,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
$actived_card.removeClass("active");
$actived_card.find('.oe_flip_card_front').removeClass("d-none");
self.$card.addClass("active");
setTimeout(() => {
setTimeout(function () {
self.$('.oe_flip_card_front').addClass("d-none");
}, 200);
});
@ -517,7 +550,10 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
*/
_onDblClickDelayedFlipCard: function (evt) {
var $target = $(evt.target);
if ($target.hasClass('badge_price') || $target.parents('.badge_price').length) {
if (
$target.hasClass('badge_price') ||
$target.parents('.badge_price').length
) {
this._openPriceModifier();
} else {
var $currentTarget = $(evt.currentTarget);
@ -540,7 +576,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
var $actived_card = this.$el.parent().find(".active");
if ($actived_card[0] !== $currentTarget[0]) {
$actived_card.removeClass("active");
$actived_card.find('.oe_flip_card_front').removeClass("d-none");
$actived_card.find('.oe_flip_card_front')
.removeClass("d-none");
}
var offset = $currentTarget.offset();
$currentTarget.css({
@ -562,9 +599,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
/**
* @private
* @param {CustomEvent} evt
*/
_onRestoreFlipCard: function (evt) {
_onRestoreFlipCard: function () {
this.$(".oe_flip_card").removeClass("active");
this.$('.oe_flip_card_front').removeClass("d-none");
},
@ -584,8 +620,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRecord", fu
* @private
* @param {CustomEvent} evt
*/
_onQuickRecordUpdated: function (ev) {
this._processDynamicFields(Object.keys(ev.data.changes));
_onQuickRecordUpdated: function (evt) {
this._processDynamicFields(Object.keys(evt.data.changes));
this.trigger_up("update_subtotal");
},
});

View File

@ -1,11 +1,14 @@
// 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.One2ManyProductPickerRenderer", function (require) {
odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer", function (
require
) {
"use strict";
var core = require("web.core");
var BasicRenderer = require("web.BasicRenderer");
var One2ManyProductPickerRecord = require("web_widget_one2many_product_picker.One2ManyProductPickerRecord");
var One2ManyProductPickerRecord = require(
"web_widget_one2many_product_picker.One2ManyProductPickerRecord");
var qweb = core.qweb;
@ -14,7 +17,6 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
className: 'oe_one2many_product_picker_view',
events: {
//'scroll': '_lazyOnScrollView',
'click #productPickerLoadMore': '_onClickLoadMore',
},
@ -30,13 +32,16 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
this.recordOptions = _.extend({}, params.record_options, {
viewType: 'One2ManyProductPicker',
});
// Workaraound: Odoo initilize this class so we need do this to
// Workaround: Odoo initilize this class so we need do this to
// 'receive' more arguments.
this.options = parent.options;
this.mode = parent.mode;
this.search_data = parent._searchRecords;
this.last_search_data_count = parent._lastSearchRecordsCount;
this._lazyOnScrollView = _.debounce(this._onScrollView.bind(this), this.DELAY_GET_RECORDS);
this._lazyOnScrollView = _.debounce(
this._onScrollView.bind(this),
this.DELAY_GET_RECORDS);
},
/**
@ -67,12 +72,11 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
* @override
*/
start: function () {
//this.$el.addClass("row");
return this._super.apply(this, arguments);
},
/**
* @param {Object} searchState
* @param {Object} search_data
*/
updateSearchData: function (search_data, count) {
this.search_data = search_data;
@ -99,7 +103,10 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
return this._super.apply(this, arguments);
}
var old_state = _.clone(this.state.data);
return this._super(state, _.extend({}, params, {noRender: true})).then(function() {
return this._super(
state,
_.extend({}, params, {noRender: true})
).then(function () {
self._updateStateRecords(old_state);
});
},
@ -112,8 +119,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
_removeRecords: function (states) {
var defs = [];
var to_destroy = [];
for (var index in states) {
var state = states[index];
for (var index_state in states) {
var state = states[index_state];
for (var e = this.widgets.length-1; e>=0; --e) {
var widget = this.widgets[e];
if (widget && widget.state.id === state.id) {
@ -126,22 +133,32 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
// If doesn't exists other records with the same product, we need
// create a 'pure virtual' record again.
for (var index in to_destroy) {
var widget_destroyed = to_destroy[index];
var widget_product_id = widget_destroyed.state.data[this.options.field_map.product].data.id;
for (var index_destroy in to_destroy) {
var widget_destroyed = to_destroy[index_destroy];
var widget_product_id = widget_destroyed.state
.data[this.options.field_map.product].data.id;
var found = false;
for (var e = this.widgets.length-1; e>=0; --e) {
var widget = this.widgets[e];
if (widget.state.data[this.options.field_map.product].data.id === widget_product_id) {
for (var eb = this.widgets.length-1; eb>=0; --eb) {
var widget = this.widgets[eb];
if (
widget.state.data[this.options.field_map.product].data.id === widget_product_id
) {
found = true;
break;
}
}
if (!found) {
var search_record = _.find(this.search_data, {id: widget_product_id})
var search_record = _.find(this.search_data, {id: widget_product_id});
var new_search_record = _.extend({}, search_record, {__id: state.id});
var search_record_index = widget_destroyed.$el.index();
defs.push(this.appendSearchRecords([new_search_record], false, true, search_record_index));
defs.push(
this.appendSearchRecords(
[new_search_record],
false,
true,
search_record_index
)
);
}
}
@ -155,9 +172,11 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
* Thanks to this we don't need re-render 'pure virtual' records.
*
* @private
* @param {Object} old_states
* @returns {Deferred}
*/
_updateStateRecords: function (old_states) {
// States to remove
var states_to_destroy = [];
for (var index in old_states) {
@ -187,6 +206,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
for (var e = this.widgets.length-1; e>=0; --e) {
var widget = this.widgets[e];
if (!widget) {
// Already processed widget (deleted)
continue;
}
@ -194,17 +214,25 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
widget.recreate(state);
exists = true;
break;
} else if (widget.recordSearch.id === state.data[this.options.field_map.product].data.id) {
} else if (
widget.recordSearch.id === state.data[this.options.field_map.product].data.id
) {
// Is a new record
search_record_index = widget.$el.index();
search_record = widget.recordSearch;
}
// Remove "pure virtual" records that have the same product that the new record
if (widget.is_virtual && widget.state.data[this.options.field_map.product].data.id === state.data[this.options.field_map.product].data.id) {
if (
widget.is_virtual &&
widget.state.data[this.options.field_map.product].data.id === state.data[this.options.field_map.product].data.id
) {
to_destroy.push(widget);
delete this.widgets[e];
}
}
// Need add a new one?
if (!exists && search_record_index !== -1) {
var new_search_record = _.extend({}, search_record, {__id: state.id});
@ -248,6 +276,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
*
* @private
* @param {Array[Object]} results
* @returns {Array[Object]}
*/
_processSearchRecords: function (results) {
var field_name = this.options.field_map.product;
@ -256,7 +285,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
var record_search = results[index];
var state_data_found = false;
for (var state_record of this.state.data) {
for (var index_data in this.state.data) {
var state_record = this.state.data[index_data];
var field = state_record.data[field_name];
if (
(typeof field === "object" && field.data.id === record_search.id) ||
@ -321,7 +351,8 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
*/
_appendSearchRecords: function (search_records, no_process_records, position) {
var self = this;
var processed_records = no_process_records?search_records:this._processSearchRecords(search_records);
var processed_records =
no_process_records?search_records:this._processSearchRecords(search_records);
_.each(processed_records, function (search_record) {
var state_data = self._getRecordDataById(search_record.__id);
var ProductPickerRecord = new One2ManyProductPickerRecord(
@ -330,6 +361,7 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
self._getRecordOptions(search_record)
);
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) {
@ -338,10 +370,12 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
self.defsVirtualState.push(defVirtualState);
}
}
// At this point the widget will use the existing state (line) or
// the search data. Using search data instead of waiting for
// simulated state gives a low FCP time.
var def = ProductPickerRecord.appendTo(self.$recordsContainer).then(function(){
var def = ProductPickerRecord.appendTo(self.$recordsContainer)
.then(function () {
if (typeof position !== "undefined") {
var $elm = self.$el.find("> div > div:nth("+position+")");
ProductPickerRecord.$el.insertAfter($elm);
@ -415,11 +449,11 @@ odoo.define("web_widget_one2many_product_picker.One2ManyProductPickerRenderer",
/**
* @private
*/
_onClickLoadMore: function (evt) {
_onClickLoadMore: function () {
this.$btnLoadMore.attr("disabled", true);
this.trigger_up("load_more");
this._loadMoreWorking = true;
}
},
});

View File

@ -6,6 +6,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function (require)
var BasicModel = require("web.BasicModel");
BasicModel.include({
/**
* @param {Number/String} handle
* @param {Object} context
@ -62,7 +63,7 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function (require)
var list = this.localData[listID];
var context = _.extend({}, this._getContext(list), options.context);
var position = (options && options.position) || 'top';
var position = options?options.position:'top';
var params = {
context: context,
fields: list.fields,
@ -75,10 +76,15 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function (require)
};
return $.Deferred(function (d) {
self._makeDefaultRecord(list.model, params).then(function (recordID) {
self._makeDefaultRecord(list.model, params)
.then(function (recordID) {
self.setPureVirtual(recordID, true);
if (options.data) {
self._applyChangeNoWarnings(recordID, options.data, params).then(function(){
self._applyChangeNoWarnings(
recordID,
options.data,
params
).then(function () {
d.resolve(self.get(recordID));
});
} else {
@ -92,9 +98,9 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function (require)
* Cloned '_applyChange' but without warning messages
*
* @private
* @param {Object} record
* @param {Object} fields
* @param {String} viewType
* @param {Number} recordID
* @param {Object} changes
* @param {Object} options
* @returns {Deferred}
*/
_applyChangeNoWarnings: function (recordID, changes, options) {
@ -112,11 +118,16 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function (require)
initialData[elem.id] = $.extend(true, {}, _.pick(elem, 'data', '_changes'));
});
// apply changes to local data
// Apply changes to local data
for (var fieldName in changes) {
field = record.fields[fieldName];
if (field && (field.type === 'one2many' || field.type === 'many2many')) {
defs.push(this._applyX2ManyChange(record, fieldName, changes[fieldName], options.viewType, options.allowWarning));
defs.push(this._applyX2ManyChange(
record,
fieldName,
changes[fieldName],
options.viewType,
options.allowWarning));
} else if (field && (field.type === 'many2one' || field.type === 'reference')) {
defs.push(this._applyX2OneChange(record, fieldName, changes[fieldName]));
} else {
@ -129,12 +140,25 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function (require)
}
return $.when.apply($, defs).then(function () {
var onChangeFields = []; // the fields that have changed and that have an on_change
// The fields that have changed and that have an on_change
var onChangeFields = [];
for (var fieldName in changes) {
field = record.fields[fieldName];
if (field && field.onChange) {
var isX2Many = field.type === 'one2many' || field.type === 'many2many';
if (!isX2Many || (self._isX2ManyValid(record._changes[fieldName] || record.data[fieldName]))) {
var isX2Many = (
field.type === 'one2many' ||
field.type === 'many2many'
);
if (
!isX2Many ||
(
self._isX2ManyValid(
record._changes[fieldName] ||
record.data[fieldName]
)
)
) {
onChangeFields.push(fieldName);
}
}
@ -144,12 +168,15 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function (require)
self._performOnChangeNoWarnings(record, onChangeFields, options.viewType)
.then(function (result) {
delete record._warning;
onchangeDef.resolve(_.keys(changes).concat(Object.keys(result && result.value || {})));
onchangeDef.resolve(
_.keys(changes).concat(
Object.keys((result && result.value) || {})));
}).fail(function () {
self._visitChildren(record, function (elem) {
_.extend(elem, initialData[elem.id]);
});
// safe fix for stable version, for opw-2267444
// Safe fix for stable version, for opw-2267444
if (!options.force_fail) {
onchangeDef.resolve({});
} else {
@ -161,12 +188,16 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function (require)
}
return onchangeDef.then(function (fieldNames) {
_.each(fieldNames, function (name) {
if (record._changes && record._changes[name] === record.data[name]) {
if (
record._changes &&
record._changes[name] === record.data[name]
) {
delete record._changes[name];
record._isDirty = !_.isEmpty(record._changes);
}
});
return self._fetchSpecialData(record).then(function (fieldNames2) {
// Return the names of the fields that changed (onchange or
// associated special data change)
return _.union(fieldNames, fieldNames2);
@ -196,7 +227,8 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function (require)
};
if (fields.length === 1) {
fields = fields[0];
// if only one field changed, add its context to the RPC context
// If only one field changed, add its context to the RPC context
options.fieldName = fields;
}
var context = this._getContext(record, options);
@ -206,10 +238,10 @@ odoo.define("web_widget_one2many_product_picker.BasicModel", function (require)
model: record.model,
method: 'onchange',
args: [idList, currentData, fields, onchangeSpec, context],
})
.then(function (result) {
}).then(function (result) {
if (!record._changes) {
// if the _changes key does not exist anymore, it means that
// If the _changes key does not exist anymore, it means that
// it was removed by discarding the changes after the rpc
// to onchange. So, in that case, the proper response is to
// ignore the onchange.

View File

@ -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("web_widget_one2many_product_picker.BasicView", function (require) {
@ -9,21 +10,31 @@ odoo.define("web_widget_one2many_product_picker.BasicView", function (require) {
var _t = core._t;
// py.js _ -> _t() call
// Add ref to _() -> _t() call
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({
/**
* @override
*/
_processField: function (viewType, field, attrs) {
/* We need process 'options' attribute to handle translations and special replacements */
if (attrs.widget === "one2many_product_picker" && !_.isObject(attrs.options)) {
/**
* We need process 'options' attribute to handle translations and
* special replacements
*/
if (
attrs.widget === "one2many_product_picker" &&
!_.isObject(attrs.options)
) {
attrs.options = attrs.options ? pyUtils.py_eval(attrs.options, {
_: PY_t,
// Hack: This allow use $number_search out of an string
number_search: '$number_search',
}) : {};
}

View File

@ -8,7 +8,8 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
var core = require("web.core");
var field_registry = require("web.field_registry");
var FieldOne2Many = require("web.relational_fields").FieldOne2Many;
var One2ManyProductPickerRenderer = require("web_widget_one2many_product_picker.One2ManyProductPickerRenderer");
var One2ManyProductPickerRenderer = require(
"web_widget_one2many_product_picker.One2ManyProductPickerRenderer");
var tools = require("web_widget_one2many_product_picker.tools");
var _t = core._t;
@ -36,7 +37,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
_auto_search_delay: 450,
// product.product fields
// Model product.product fields
search_read_fields: [
"id",
"display_name",
@ -45,9 +46,12 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
/**
* @override
*/
init: function (parent, name, record, options) {
init: function (parent, name, record) {
this._super.apply(this, arguments);
this.state = record; // This is the parent state
// This is the parent state
this.state = record;
// Use jquery 'extend' to have a 'deep' merge.
this.options = $.extend(true, this._getDefaultOptions(), this.attrs.options);
if (!this.options.search) {
@ -58,6 +62,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
if (!(this.options.search[0] instanceof Array)) {
this._searchCategoryNames = _.map(this.options.search, "name");
}
// FIXME: Choose a better way to get the active controller or model objects
this.parent_controller = parent.getParent();
if (this.view) {
@ -72,6 +77,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
if (!this.view) {
return $.when();
}
// Uses to work with searchs, so we can mix properties with the user values.
this._searchContext = {
domain: this.mode === "readonly" ? this._getLinesDomain() : false,
@ -96,9 +102,13 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
var prices = [];
var field_map = this.options.field_map;
var records = this.parent_controller.model.get(this.state.id).data[this.name].data;
if (this.options.show_discounts) {
if (this.options.show_discount) {
prices = _.map(records, function (line) {
return line.data[field_map.product_uom_qty] * tools.priceReduce(line.data[field_map.price_unit], line.data[field_map.discount]);
return line.data[field_map.product_uom_qty] *
tools.priceReduce(
line.data[field_map.price_unit],
line.data[field_map.discount]
);
});
} else {
prices = _.map(records, function (line) {
@ -198,7 +208,9 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
* @override
*/
_renderButtons: function () {
if (!this.isReadonly) {
if (this.isReadonly) {
return this._super.apply(this, arguments);
}
this.$buttons = $(
qweb.render("One2ManyProductPicker.ControlPanelButtons", {
search_category_names: this._searchCategoryNames,
@ -215,9 +227,6 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
this.$badgeLines = this.$btnLines.find(".badge");
this.updateBadgeLines();
this.$groups.appendTo(this.$buttons);
} else {
return this._super.apply(this, arguments);
}
},
/**
@ -226,6 +235,7 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
_render: function () {
var self = this;
var def = this._super.apply(this, arguments);
// Parent implementation can return 'undefined' :(
return (
def &&
@ -329,7 +339,10 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
self._lastSearchRecordsCount = results.length;
self._searchOffset = offset + limit;
if (self.renderer) {
self.renderer.updateSearchData(self._searchRecords, self._lastSearchRecordsCount);
self.renderer.updateSearchData(
self._searchRecords,
self._lastSearchRecordsCount
);
}
d.resolve(results);
});
@ -341,7 +354,6 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
* @param {MouseEvent} evt
*/
_onClickSearchGroup: function (evt) {
var self = this;
var $btn = $(evt.target);
var groupIndex = Number($btn.data("group")) || 0;
this._activeSearchGroup = this.searchGroups[groupIndex];
@ -430,29 +442,36 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
search_domain = search_domain[this._searchMode].domain;
}
var involved_fields = [];
// Iterate domain triplets and logic operators
for (var index in search_domain) {
var domain = _.clone(search_domain[index]);
var domain_cloned = _.clone(search_domain[index]);
// Is a triplet
if (domain instanceof Array) {
if (domain_cloned instanceof Array) {
// Replace right leaf with the current value of the search input
if (domain[2] === "$number_search") {
domain[2] = Number(this._searchContext.text);
if (domain_cloned[2] === "$number_search") {
domain_cloned[2] = Number(this._searchContext.text);
involved_fields.push({
type: 'number',
field: domain[0],
oper: domain[1],
field: domain_cloned[0],
oper: domain_cloned[1],
});
} else if (typeof(domain[2]) === "string" && domain[2].includes("$search")) {
domain[2] = domain[2].replace(/\$search/, this._searchContext.text);
} else if (
typeof domain_cloned[2] === "string" &&
domain_cloned[2].includes("$search")
) {
domain_cloned[2] = domain_cloned[2]
.replace(/\$search/, this._searchContext.text);
involved_fields.push({
type: 'text',
field: domain[0],
oper: domain[1],
field: domain_cloned[0],
oper: domain_cloned[1],
});
}
}
sdomain.push(domain);
sdomain.push(domain_cloned);
}
this._searchContext.involvedFields = involved_fields;
}
@ -471,7 +490,8 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
return [];
}
var field_name = this.options.field_map.product;
var lines = this.parent_controller.model.get(this.state.id).data[this.name].data;
var lines = this.parent_controller.model.get(this.state.id)
.data[this.name].data;
var ids = _.map(lines, function (line) {
return line.data[field_name].data.id;
});
@ -483,13 +503,12 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
* that the search results. Use directy in-memory values.
*/
showLines: function () {
var self = this;
this._clearSearchInput();
this.$btnLines.parent().find(".active").removeClass("active");
this.$btnLines.addClass("active");
this._activeSearchGroup = {
'name': 'main_lines',
}
};
this._searchContext.domain = this._getLinesDomain();
this._searchContext.order = false;
this.doRenderSearchRecords();
@ -526,9 +545,13 @@ odoo.define("web_widget_one2many_product_picker.FieldOne2ManyProductPicker", fun
* @param {DropdownEvent} evt
*/
_onShowSearchDropdown: function (evt) {
// Workaround: This "ensures" a correct dropdown position
var offset = $(evt.currentTarget).find(".dropdown-toggle").parent().height();
_.defer(function() { $(evt.currentTarget).find(".dropdown-menu").css("transform", "translate3d(0px, " + offset + "px, 0px)"); });
_.defer(function () {
$(evt.currentTarget).find(".dropdown-menu")
.css("transform", "translate3d(0px, " + offset + "px, 0px)");
});
},
/**

View File

@ -1,3 +1,4 @@
/* global QUnit */
// 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.widget_tests', function (require) {
@ -38,7 +39,7 @@ odoo.define('web_widget_one2many_product_picker.widget_tests', function (require
},
records: [
{id: 1, line_ids: [1, 2], currency_id: 1, display_name: "FT01"},
]
],
},
line: {
fields: {
@ -53,7 +54,7 @@ odoo.define('web_widget_one2many_product_picker.widget_tests', function (require
records: [
{id: 1, name: "Large Cabinet", product_id: 1, product_uom: 1, product_uom_qty: 3, price_unit: 9.99, price_reduce: 9.00, foo_id: 1},
{id: 2, name: "Cabinet with Doors", product_id: 2, product_uom: 1, product_uom_qty: 8, price_unit: 42.99, price_reduce: 40.00, foo_id: 1},
]
],
},
product: {
fields: {
@ -94,7 +95,7 @@ odoo.define('web_widget_one2many_product_picker.widget_tests', function (require
],
},
};
}
},
}, function () {
QUnit.test('Load widget', function (assert) {
assert.expect(4);