3

I have added a lot quantity field in Manufacturing picking operations. It is reflecting the lot quantity perfectly with respect to location. Now the problem is I do not want to show the lots having no product i.e. 0 quantity. How do I achieve this?

Here is the screenshot where I want to make changes.enter image description here

Here is the model code (stock_move_line.py):

class StockMoveLine(models.Model):
    _name = "stock.move.line"
    _description = "Product Moves (Stock Move Line)"
    _rec_name = "product_id"
    _order = "result_package_id desc, id"
    lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number123')
    x_lot_qty = fields.Float(compute="fetch_lot_wrt_location", 
                       name="Lot Quantity", 
                       store=True,
                       readonly=True, 
                       digits=(12,3))
    
    @api.onchange('lot_id','location_id')
    def fetch_lot_wrt_location(self):
        self.x_lot_qty = 0.0
        for row in self:
            quants = self.env['stock.quant'].search([('lot_id.id','=',row.lot_id.id)])
            for quant in quants:
                if row.location_id.display_name == quant.location_id.display_name:
                    row.x_lot_qty = quant.quantity
    

Here are the views (stock_move_views.xml):

<field name="lot_id" 
            attrs="{'readonly': ['&amp;', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True)]}" 
            invisible="not context.get('show_lots_m2o')" 
            domain="[('product_id', '=', parent.product_id)]" 
            groups="stock.group_production_lot" 
            context="{'default_product_id': parent.product_id, 'active_picking_id': picking_id}"/>
<field name="x_lot_qty" 
            attrs="{'readonly': ['&amp;', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True)]}" 
            invisible="not context.get('show_lots_m2o')" 
            domain="[('product_id', '=', parent.product_id)]" 
            groups="stock.group_production_lot" 
            context="{'default_product_id': parent.product_id, 'active_picking_id': picking_id}"/>

I did applied filter via domain attribute but it is not working. How can I restrict the lot numbers with quantity 0.

UPDATE: I tried following solution: I created a new dynamic selection field and tried populating the lots which are not empty with respect to location, below is the code:

@api.onchange('location_id')
def _fetch_non_empty_lots(self):
    lots = []
    for row in self:
        quants = self.env['stock.quant'].search([('location_id.id','=',row.location_id.id), ('product_id.id', '=', row.product_id.id), ('quantity', '>', 0)])
        for quant in quants:
            lots.append(quant)
            
    return lots

x_lot_id = fields.Selection(selection="_fetch_non_empty_lots", name="Non Empty Lots")

But now when I select a new location, I get following error:

Odoo Server Error
Traceback (most recent call last):
  File "C:\virtual_odoo12\Scripts\odoo\http.py", line 656, in _handle_exception
    return super(JsonRequest, self)._handle_exception(exception)
  File "C:\virtual_odoo12\Scripts\odoo\http.py", line 314, in _handle_exception
    raise pycompat.reraise(type(exception), exception, sys.exc_info()[2])
  File "C:\virtual_odoo12\Scripts\odoo\tools\pycompat.py", line 87, in reraise
    raise value
  File "C:\virtual_odoo12\Scripts\odoo\http.py", line 698, in dispatch
    result = self._call_function(**self.params)
  File "C:\virtual_odoo12\Scripts\odoo\http.py", line 346, in _call_function
    return checked_call(self.db, *args, **kwargs)
  File "C:\virtual_odoo12\Scripts\odoo\service\model.py", line 97, in wrapper
    return f(dbname, *args, **kwargs)
  File "C:\virtual_odoo12\Scripts\odoo\http.py", line 339, in checked_call
    result = self.endpoint(*a, **kw)
  File "C:\virtual_odoo12\Scripts\odoo\http.py", line 941, in __call__
    return self.method(*args, **kw)
  File "C:\virtual_odoo12\Scripts\odoo\http.py", line 519, in response_wrap
    response = f(*args, **kw)
  File "c:\virtual_odoo12\scripts\addons\web\controllers\main.py", line 962, in call_kw
    return self._call_kw(model, method, args, kwargs)
  File "c:\virtual_odoo12\scripts\addons\web\controllers\main.py", line 954, in _call_kw
    return call_kw(request.env[model], method, args, kwargs)
  File "C:\virtual_odoo12\Scripts\odoo\api.py", line 759, in call_kw
    return _call_kw_multi(method, model, args, kwargs)
  File "C:\virtual_odoo12\Scripts\odoo\api.py", line 746, in _call_kw_multi
    result = method(recs, *args, **kwargs)
  File "C:\virtual_odoo12\Scripts\odoo\models.py", line 5524, in onchange
    record._onchange_eval(name, field_onchange[name], result)
  File "C:\virtual_odoo12\Scripts\odoo\models.py", line 5369, in _onchange_eval
    process(method_res)
  File "C:\virtual_odoo12\Scripts\odoo\models.py", line 5355, in process
    if res.get('value'):
AttributeError: 'list' object has no attribute 'get'

UPDATE 2: Instead of modifying the existing dropdown, I created a dynamic dropdown. When location is selected, the drop down is populated with lot numbers which have quantity greater than 0. However, I am facing a basic issue/error:

AttributeError: 'list' object has no attribute 'get'

Here is the model selection field and code to populate it:

@api.onchange('location_id')
def _fetch_non_empty_lots(self):
        quants = self.env['stock.quant'].search([('location_id.id','=',self.location_id.id), 
                        ('product_id.id', '=', self.product_id.id), 
                        ('quantity', '>', 0)])
        return {str(quant.lot_id.id) :str.title(quant.lot_id.display_name) for quant in quants} 

## declare field    

x_lot_id = fields.Selection(selection="_fetch_non_empty_lots", name="Non Empty Lots")

When the list is empty, there is no error. When there is non-empty lot then I am getting the eror. Ineed some assistance.

user1584253
  • 975
  • 2
  • 18
  • 55

4 Answers4

1

Odoo defines a product quantity field in the stock production lot model, you can use that field in the domain to filter lots, and you need to set store to True to make it searchable. If you need to define a new field then use the same logic and define it in the lot model to use in the lot_id field domain.

You got that error because Odoo expects a dictionary and it got a list instead.

The method may return a dictionary for changing field domains and pop up a warning message, like in the old API.

In your two last examples, you set the selection attribute to a string but the selection attribute specifies the possible values for this field. It is given as either a list of pairs (value, string), or a model method, or a method name. You can find an example in website_customer module.

Edit: (return a domain from onchange method)

The method may return a dictionary for changing field domains and pop up a warning message, like in the old API::

return {
    'domain': {'other_id': [('partner_id', '=', partner_id)]},
    'warning': {'title': "Warning", 'message': "What is this?"},
}
Kenly
  • 24,317
  • 7
  • 44
  • 60
  • Can you please elaborate how I use the code because I followed the same logic as link i.e. [(quant.lot_id.display_name, str.title(quant.lot_id.display_name)) for quant in quants] but getting same error "AttributeError: 'list' object has no attribute 'get'" – user1584253 Dec 03 '20 at 19:05
  • You got that error because you returned a list and Odoo expects a dictionary. You need to remove the `onchange` decorator from the method that computes the selection value. – Kenly Dec 03 '20 at 20:48
  • When I removed the onchange decorator, every time the selection method return empty value – user1584253 Dec 07 '20 at 20:23
  • however I changed return statement to {str(quant.lot_id.id) :str.title(quant.lot_id.display_name) for quant in quants} but still getting empty data in return. I debugged with pdb, the function is called 4 times when I change location.At the 3rd function call I see data in self (stock.move.line) there my function works but when 4th time function is called, it returns no lots since self contains nothing – user1584253 Dec 07 '20 at 20:25
  • Of course `self` is an empty recordset, you can't use field values, they are not available yet. – Kenly Dec 07 '20 at 22:47
  • 1
    I get stock.move.line with corresponding data in the row with the 3rd function call. How do I get data other than self object than ? – user1584253 Dec 08 '20 at 05:35
  • What did you change in your code, I checked the last one without the ``onchange`` decorator and the selection value is a function reference – Kenly Dec 08 '20 at 13:55
  • I have edited my question, you can see the latest changes of my code in "UPDATE 2" section. I have used @api.onchange('location_id') to get current selected location – user1584253 Dec 08 '20 at 17:02
  • I have gone through the odoo.com community and found that I am unable to change the selections from onchange function. Instead I have to create the many2one instead of selection field and return the domain in the onchange. https://www.odoo.com/fr_FR/forum/aide-1/change-selection-field-with-onchange-api-102465 – user1584253 Dec 08 '20 at 17:07
  • But I am confused with the last statement how to return the domain in the onchange – user1584253 Dec 08 '20 at 17:08
  • Check my edit, I added the official source code documentation to return a domain from ``onchange`` method. – Kenly Dec 08 '20 at 18:39
  • I tried that and added this code on change of location_id {'domain': {'lot_id': [('product_id', '=', 'parent.product_id'),('product_qty', '>', 0)]}} ... but getting empty lots in dropdown – user1584253 Dec 08 '20 at 18:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/225683/discussion-between-user1584253-and-kenly). – user1584253 Dec 08 '20 at 18:58
  • That I have copied from the view (xml) file, and this is to restrict the lot number related to the product only. If I remove it, all lots will be displayed irrespective of the product selected – user1584253 Dec 09 '20 at 05:49
0

I think it should be like that?

lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number123' 
  domain="[('product_id', '=', parent.product_id),('product_qty', '>', 0)]" )
Paxmees
  • 1,540
  • 1
  • 15
  • 28
  • It's not working, the field x_lot_qty .. It is calculating the quantity of product in lot number considering the location also ... I did something like this lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number1234', domain="[('product_id', '=', parent.product_id),('x_lot_qty', '>', 0)]") – user1584253 Nov 25 '20 at 07:36
  • `x_lot_qty` is on the `stock.move.line` but you search from `stock.production.lot` – Paxmees Nov 25 '20 at 07:49
  • yes, because lot_id is not available in stock_move_line .. and my objective is to show only those lots which have product quantity greater than 0 with respect to location – user1584253 Nov 25 '20 at 07:56
0

What I understand is you want to show only those lots which have more than 0 products, in your lots drop down

You can try this in your XML

<field name="lot_id" 
            attrs="{'readonly': ['&amp;', ('package_level_id', '!=', False), ('parent.picking_type_entire_packs', '=', True)]}" 
            invisible="not context.get('show_lots_m2o')" 
            domain="[('product_qty', '>', 0)]"     //View this
            groups="stock.group_production_lot" 
            context="{'default_product_id': parent.product_id, 'active_picking_id': picking_id}"/>
Adam Strauss
  • 1,889
  • 2
  • 15
  • 45
  • It's not working ... one filter should be there every time ('product_id', '=', parent.product_id) .. otherwise it will show all products lot, then product_qty is not working since it contains total product quantity not by lot numbers therefore, I created a custom computed field named x_lot_qty so it sees which location is selected in "from" column and what product is selected then it shows the quantity. So we need to consider location also – user1584253 Nov 26 '20 at 07:18
  • I tried domain="[('product_id', '=', parent.product_id),('xlot_qty', '>', 0)]" but getting this error "ValueError: Invalid field 'xlot_qty' in leaf "', 0) on stock_production_lot (ctx: )>"" – user1584253 Nov 26 '20 at 07:25
  • You're trying `('xlot_qty', '>', 0)` but I'm saying `('product_qty', '>', 0)` try this my dear fellow :) – Adam Strauss Nov 26 '20 at 07:45
  • Hello @AdamStrauss, it not work because the field 'product_qty' of the stock.production.lot is the computed field and non-store field. – Dipen Shah Nov 26 '20 at 11:41
  • Oh got it, then what is the solution any guidance :) – Adam Strauss Nov 26 '20 at 11:43
  • I tried creating a dynamic dropdown that when location is selected then non empty lot numbers should be populated in the dropdown. But getting some error. I have updated my question, please have a look – user1584253 Nov 27 '20 at 07:43
0

add this on your stock.move.line model:

@api.onchange('parent.product_id')
def _onchange_product_id(self):
    quants = self.env['stock.quant'].search([('product_id', '=', self.parent.product_id.id), ('quantity', '>', 0)])
    return {
        'domain': {
            'lot_id': [
                ('id', 'in', quants.lot_id.ids)
            ],
        },
    }
SDBot
  • 784
  • 1
  • 4
  • 10
  • It is not working .. I changed the decorator parameter to @api.onchange('lot_id', 'location_id') ... and added location filter in stock.quant .. but no luck – user1584253 Jan 26 '21 at 18:27
  • No, don't change the onchange decorator params to 'lot_id' and 'location_id'. Can you make sure the onchange is being called? – SDBot Jan 27 '21 at 01:20
  • Yes, when I change the decorator to location_id then it is called. Please see the screenshot, the product is already selected. However, when location changes then lots having quantity greater than 0 should be filtered only – user1584253 Jan 27 '21 at 09:34
  • the location_id is that 'From' field?, if so then change to @api.onchange('location_id'), if the function is called, then you need to check the result of 'quants.lot_id.ids', if 'quants.lot_id.ids' is not correct then problem is the search query. – SDBot Jan 27 '21 at 09:57
  • I have put additional filter in your query ... ('location_id.display_name','=', self.location_id.display_name) ... it give correct result .. but doesn't populate the dropdown – user1584253 Jan 27 '21 at 15:55
  • doesn't populate dropdown? you mean the dropdown is entirely empty? – SDBot Jan 27 '21 at 16:01
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/227908/discussion-between-sdbot-and-user1584253). – SDBot Jan 27 '21 at 16:03
  • try remove the onchange method, if the lot_id started to populate, then the domain returned by the onchange method is incorrect. But if the lot_id remain empty even after you removed the onchange method then either you have domain applied domain filter somewhere else, which you need to remove then try again. – SDBot Jan 27 '21 at 16:24