[REF] stock: products on hand action

Instead of hardcoding a domain in the action, we add a field that will
be used to apply the domain. The goal is to be able to define a static
domain in the xml so we'll be able to default on the filter while still
being able to remove it.

Let's consider this usecase:

     Physical
    |        \
   WH    Subcontracting
    |         |
  Stock    5 units
    |
 10 units

With the hardcoded domain, we have two problems
- the right part of the tree isn't visible
- the user can add a quant in subcontracting but it will disappear at
  the creation since it isn't part of the hardcoded domain.

The complicated part of the domain is the `_get_domain_locations` call.
It will use multiple context keys, the current company and get the
hierarchy of internal locations below warehouses to see what it should
include or not.

To be able to remove a domain, it should be declared in the xml and not
hardcoded in the action. Since the domain is complex, we can't express
it. So we add a computed field "on_hand" and its search method is
overiden to call `_get_domain_locations`.

task-2061825

closes odoo/odoo#40403

Signed-off-by: Simon Lejeune (sle) <sle@openerp.com>
This commit is contained in:
Simon Lejeune
2019-12-04 10:59:59 +00:00
parent 53658052ae
commit 1ae66862d5
3 changed files with 18 additions and 7 deletions
+2 -3
View File
@@ -481,8 +481,7 @@ class Product(models.Model):
# Be aware that the exact same function exists in product.template
def action_open_quants(self):
location_domain = self._get_domain_locations()[0]
domain = expression.AND([[('product_id', 'in', self.ids)], location_domain])
domain = [('product_id', 'in', self.ids)]
hide_location = not self.user_has_groups('stock.group_stock_multi_locations')
hide_lot = all([product.tracking == 'none' for product in self])
self = self.with_context(hide_location=hide_location, hide_lot=hide_lot)
@@ -507,7 +506,7 @@ class Product(models.Model):
else:
self = self.with_context(product_tmpl_id=self.product_tmpl_id.id)
ctx = dict(self.env.context)
ctx.update({'no_at_date': True})
ctx.update({'no_at_date': True, 'search_default_on_hand': True})
return self.env['stock.quant'].with_context(ctx)._get_quants_action(domain)
def action_update_quantity_on_hand(self):
+15 -4
View File
@@ -51,15 +51,13 @@ class StockQuant(models.Model):
'product.product', 'Product',
domain=lambda self: self._domain_product_id(),
ondelete='restrict', readonly=True, required=True, index=True, check_company=True)
# so user can filter on template in webclient
product_tmpl_id = fields.Many2one(
'product.template', string='Product Template',
related='product_id.product_tmpl_id', readonly=False)
product_uom_id = fields.Many2one(
'uom.uom', 'Unit of Measure',
readonly=True, related='product_id.uom_id')
company_id = fields.Many2one(related='location_id.company_id',
string='Company', store=True, readonly=True)
company_id = fields.Many2one(related='location_id.company_id', string='Company', store=True, readonly=True)
location_id = fields.Many2one(
'stock.location', 'Location',
domain=lambda self: self._domain_location_id(),
@@ -93,6 +91,7 @@ class StockQuant(models.Model):
compute='_compute_available_quantity')
in_date = fields.Datetime('Incoming Date', readonly=True)
tracking = fields.Selection(related='product_id.tracking', readonly=True)
on_hand = fields.Boolean('On Hand', store=False, search='_search_on_hand')
@api.depends('quantity', 'reserved_quantity')
def _compute_available_quantity(self):
@@ -128,6 +127,18 @@ class StockQuant(models.Model):
move = quant.env['stock.move'].with_context(inventory_mode=False).create(move_vals)
move._action_done()
def _search_on_hand(self, operator, value):
"""Handle the "on_hand" filter, indirectly calling `_get_domain_locations`."""
if operator not in ['=', '!='] or not isinstance(value, bool):
raise UserError(_('Operation not supported'))
domain_loc = self.env['product.product']._get_domain_locations()[0]
quant_ids = [l['id'] for l in self.env['stock.quant'].search_read(domain_loc, ['id'])]
if (operator == '!=' and value is True) or (operator == '=' and value is False):
domain_operator = 'not in'
else:
domain_operator = 'in'
return [('id', domain_operator, quant_ids)]
@api.model
def create(self, vals):
""" Override to handle the "inventory mode" and create a quant as
@@ -612,7 +623,7 @@ class StockQuant(models.Model):
'view_mode': 'list',
'res_model': 'stock.quant',
'type': 'ir.actions.act_window',
'context': dict(self.env.context),
'context': self.env.context,
'domain': domain or [],
'help': """
<p class="o_view_nocontent_empty_folder">No Stock On Hand</p>
+1
View File
@@ -15,6 +15,7 @@
<group expand='0' string='Filters'>
<filter name='internal_loc' string="Internal Locations" domain="[('location_id.usage','=', 'internal')]"/>
<filter name='transit_loc' string="Transit Locations" domain="[('location_id.usage' ,'=', 'transit')]"/>
<filter name="on_hand" string="On Hand" domain="[('on_hand', '=', True)]"/>
<separator/>
<filter name="negative" string="Negative Stock" domain="[('quantity', '&lt;', 0.0)]"/>
<filter name="positive" string="Positive Stock" domain="[('quantity', '&gt;', 0.0)]"/>