This question has been flagged
2 Replies
4987 Views

Hi there, I am trying to solve an issue where I cannot find any documentation, I hope I can explain it properly.
Odoo 11 Community:

Products with their BOM's for each consumable. Consumables are logged with Consecutive Lot numbers. Now sending out a Manufacturing order to several Work Centers, each one has to to choose the lot number to use for each Consumable. But if one Lot number is not in Stock to produce the requested number, I can still choose it and produce, even though it is supposed to pick the next lot number of the same Consumable (at least thats what I want to achieve)

Product A --> BOM Steel Plate 0.7 (amount used) 

Steel Plate Amount:1 LOT: 1234

Steel Plate Amount: 1 LOT:1235

Technically I need 1 complete Steel Plate and 0.4 from the second. In the Work Order I can choose twice the first one without any complaint form the system and produce Product A.

Causing me later in my Inventory Steel Plate 1 LOT: 1234   a negative Value of - 0.4 


Is there any way to configure the System to jump automatically to the next LOT?

Avatar
Discard
Best Answer

Odoo can be configured so Work Orders check Lot conditions based on your needs. 

Automatically suggesting which Lot to used based on user defined conditions would require a custom module.  Since this is done for Manufacturing Orders, you can review that code already available.


Rules in my example:

1. Force users to select the first available Lot with Stock remaining.

2. Force users to only use each Lot once per Work Order.

3. Force users to only use Lots with Stock remaining (prevent negative stock).

4. Force users to use as much of the Lot as the Work Order needs.


You create an Automated Action (install Automated Action Rules if it isn't already installed) and script your tests so that you are unable to validate the Work Order unless all tests pass.

Example:



Code:

# setup empty list to store which lots we plan to consume
lots_used = []
# loop through all Lots we plan to consume, which also triggers the rules for the first Lot
for line in record.active_move_line_ids: 
# only run the rules when we enter a Lot and a Qty
if line.qty_done and line.qty_done > 0 and line.lot_id:   
# Search for the first available Lot with Stock remaining
lots_available = env['stock.production.lot'].search([('product_id','=',line.product_id.id),('product_qty','>',0)])
    # If we are entering a Lot that isn't the first available, and we haven't planned to consume it on this Work Order
if lots_available[0].name != line.lot_id.name and lots_available[0].name not in lots_used:     
raise Warning("Lot " + lots_available[0].name + " should be used before Lot " + line.lot_id.name + ".")
# If we have already planned to consume this Lot
if line.lot_id.name in lots_used:     
raise Warning("Lot " + line.lot_id.name + " has already been selected to consume in this Work Order." \
                    + "Please choose another Lot.")
# If this Lot has no remaining Stock:
    if line.lot_id.product_qty == 0.0:
      raise Warning("Lot " + line.lot_id.name + " has been completely consumed. Please choose another Lot.")
# If we are not planning to consume as much as the Work Order suggests:
    if line.lot_id.product_qty < line.qty_done:
      raise Warning("Lot " + line.lot_id.name + " can NOT be used.  It has only " + str(line.lot_id.product_qty) \
                    + " " + line.product_uom_id.name + " remaining and you need " + str(line.qty_done) + ".")
# if all rules pass, add this Lot to the list we are using to keep track of which Lots we plan to consume
    lots_used.append(line.lot_id.name)

Avatar
Discard
Author Best Answer

Hi Ray 

thanks for your suggestions, before going ahead I have to say that I am new to Odoo and a newbie to Pyhton

I entered the Dev Mode and went on to Automated Actions and created the new one following your instructions. I have to admit that i copied your script straight forward into it. The action as such does not prompt any "force" to select a specific LOT.

below I'll post the error directly from the automation module


Traceback (most recent call last):
  File "/opt/odoo/odoo11/odoo/tools/safe_eval.py", line 350, in safe_eval
    return unsafe_eval(c, globals_dict, locals_dict)
  File "", line 14, in <module>
AttributeError: 'NoneType' object has no attribute 'active_move_line_ids'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/opt/odoo/odoo11/odoo/http.py", line 650, in _handle_exception
    return super(JsonRequest, self)._handle_exception(exception)
  File "/opt/odoo/odoo11/odoo/http.py", line 310, in _handle_exception
    raise pycompat.reraise(type(exception), exception, sys.exc_info()[2])
  File "/opt/odoo/odoo11/odoo/tools/pycompat.py", line 87, in reraise
    raise value
  File "/opt/odoo/odoo11/odoo/http.py", line 692, in dispatch
    result = self._call_function(**self.params)
  File "/opt/odoo/odoo11/odoo/http.py", line 342, in _call_function
    return checked_call(self.db, *args, **kwargs)
  File "/opt/odoo/odoo11/odoo/service/model.py", line 97, in wrapper
    return f(dbname, *args, **kwargs)
  File "/opt/odoo/odoo11/odoo/http.py", line 335, in checked_call
    result = self.endpoint(*a, **kw)
  File "/opt/odoo/odoo11/odoo/http.py", line 936, in __call__
    return self.method(*args, **kw)
  File "/opt/odoo/odoo11/odoo/http.py", line 515, in response_wrap
    response = f(*args, **kw)
  File "/opt/odoo/odoo11/addons/web/controllers/main.py", line 938, in call_button
    action = self._call_kw(model, method, args, {})
  File "/opt/odoo/odoo11/addons/web/controllers/main.py", line 926, in _call_kw
    return call_kw(request.env[model], method, args, kwargs)
  File "/opt/odoo/odoo11/odoo/api.py", line 689, in call_kw
    return call_kw_multi(method, model, args, kwargs)
  File "/opt/odoo/odoo11/odoo/api.py", line 680, in call_kw_multi
    result = method(recs, *args, **kwargs)
  File "/opt/odoo/odoo11/odoo/addons/base/ir/ir_cron.py", line 76, in method_direct_trigger
    self.sudo(user=cron.user_id.id).ir_actions_server_id.run()
  File "/opt/odoo/odoo11/odoo/addons/base/ir/ir_actions.py", line 554, in run
    res = func(action, eval_context=eval_context)
  File "/opt/odoo/odoo11/addons/website/models/ir_actions.py", line 57, in run_action_code_multi
    res = super(ServerAction, self).run_action_code_multi(action, eval_context)
  File "/opt/odoo/odoo11/odoo/addons/base/ir/ir_actions.py", line 430, in run_action_code_multi
    safe_eval(action.sudo().code.strip(), eval_context, mode="exec", nocopy=True)  # nocopy allows to return 'action'
  File "/opt/odoo/odoo11/odoo/tools/safe_eval.py", line 373, in safe_eval
    pycompat.reraise(ValueError, ValueError('%s: "%s" while evaluating\n%r' % (ustr(type(e)), ustr(e), expr)), exc_info[2])
  File "/opt/odoo/odoo11/odoo/tools/pycompat.py", line 86, in reraise
    raise value.with_traceback(tb)
  File "/opt/odoo/odoo11/odoo/tools/safe_eval.py", line 350, in safe_eval
    return unsafe_eval(c, globals_dict, locals_dict)
  File "", line 14, in <module>
ValueError: <class 'AttributeError'>: "'NoneType' object has no attribute 'active_move_line_ids'" while evaluating
'# Available variables:\n#  - env: Odoo Environment on which the action is triggered\n#  - model: Odoo Model of the record on which the action is triggered; is a void recordset\n#  - record: record on which the action is triggered; may be be void\n#  - records: recordset of all records on which the action is triggered in multi-mode; may be void\n#  - time, datetime, dateutil, timezone: useful Python libraries\n#  - log: log(message, level=\'info\'): logging function to record debug information in ir.logging table\n#  - Warning: Warning Exception to use with raise\n# To return an action, assign: action = {...}\n\n\nlots_used = []\n# loop through all Lots we plan to consume, which also triggers the rules for the first Lot\nfor line in record.active_move_line_ids:  \n  # only run the rules when we enter a Lot and a Qty\n  if line.qty_done and line.qty_done > 0 and line.lot_id:    \n    # Search for the first available Lot with Stock remaining\n    lots_available = env[\'stock.production.lot\'].search([(\'product_id\',\'=\',line.product_id.id),(\'product_qty\',\'>\',0)])\n    # If we are entering a Lot that isn\'t the first available, and we haven\'t planned to consume it on this Work Order\n    if lots_available[0].name != line.lot_id.name and lots_available[0].name not in lots_used:      \n      raise Warning("Lot " + lots_available[0].name + " should be used before Lot " + line.lot_id.name + ".")\n    # If we have already planned to consume this Lot\n    if line.lot_id.name in lots_used:      \n      raise Warning("Lot " + line.lot_id.name + " has already been selected to consume in this Work Order." \\\n                    + "Please choose another Lot.")\n    # If this Lot has no remaining Stock:\n    if line.lot_id.product_qty == 0.0:\n      raise Warning("Lot " + line.lot_id.name + " has been completely consumed. Please choose another Lot.")\n    # If we are not planning to consume as much as the Work Order suggests:\n    if line.lot_id.product_qty < line.qty_done:\n      raise Warning("Lot " + line.lot_id.name + " can NOT be used.  It has only " + str(line.lot_id.product_qty) \\\n                    + " " + line.product_uom_id.name + " remaining and you need " + str(line.qty_done) + ".")\n    # if all rules pass, add this Lot to the list we are using to keep track of which Lots we plan to consume\n    lots_used.append(line.lot_id.name)'

Avatar
Discard

What is the MODEL of your Automated Action?

Author

I used the action within MRP.WORKCENTER // Prio 5 // interval every Minute (for test purpose, but changed it to other values as well) // set to Active

Author

Made a mistake in first place and moved it now to the correct location. I placed it in planned action rather than automated. but still the script throws back an error and I am not able to execute.

Traceback (most recent call last):

File "/opt/odoo/odoo11/odoo/tools/safe_eval.py", line 350, in safe_eval

return unsafe_eval(c, globals_dict, locals_dict)

File "", line 13, in <module>

AttributeError: 'mrp.workcenter' object has no attribute 'active_move_line_ids'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):

File "/opt/odoo/odoo11/odoo/http.py", line 650, in _handle_exception

return super(JsonRequest, self)._handle_exception(exception)

File "/opt/odoo/odoo11/odoo/http.py", line 310, in _handle_exception

raise pycompat.reraise(type(exception), exception, sys.exc_info()[2])

File "/opt/odoo/odoo11/odoo/tools/pycompat.py", line 87, in reraise

raise value

File "/opt/odoo/odoo11/odoo/http.py", line 692, in dispatch

result = self._call_function(**self.params)

File "/opt/odoo/odoo11/odoo/http.py", line 342, in _call_function

return checked_call(self.db, *args, **kwargs)

File "/opt/odoo/odoo11/odoo/service/model.py", line 97, in wrapper

return f(dbname, *args, **kwargs)

File "/opt/odoo/odoo11/odoo/http.py", line 335, in checked_call

result = self.endpoint(*a, **kw)

File "/opt/odoo/odoo11/odoo/http.py", line 936, in __call__

return self.method(*args, **kw)

File "/opt/odoo/odoo11/odoo/http.py", line 515, in response_wrap

response = f(*args, **kw)

File "/opt/odoo/odoo11/addons/web/controllers/main.py", line 938, in call_button

action = self._call_kw(model, method, args, {})

File "/opt/odoo/odoo11/addons/web/controllers/main.py", line 926, in _call_kw

return call_kw(request.env[model], method, args, kwargs)

File "/opt/odoo/odoo11/odoo/api.py", line 689, in call_kw

return call_kw_multi(method, model, args, kwargs)

File "/opt/odoo/odoo11/odoo/api.py", line 680, in call_kw_multi

result = method(recs, *args, **kwargs)

File "/opt/odoo/odoo11/addons/mrp/models/mrp_workorder.py", line 410, in record_production

self.button_finish()

File "/opt/odoo/odoo11/addons/mrp/models/mrp_workorder.py", line 451, in button_finish

self.end_all()

File "/opt/odoo/odoo11/addons/mrp/models/mrp_workorder.py", line 489, in end_all

return self.end_previous(doall=True)

File "/opt/odoo/odoo11/addons/mrp/models/mrp_workorder.py", line 476, in end_previous

timeline.write({'date_end': enddate})

File "/opt/odoo/odoo11/odoo/models.py", line 3089, in write

self._write(old_vals)

File "/opt/odoo/odoo11/odoo/models.py", line 3317, in _write

self.recompute()

File "/opt/odoo/odoo11/odoo/models.py", line 4914, in recompute

target._write(dict(vals))

File "/opt/odoo/odoo11/addons/base_automation/models/base_automation.py", line 237, in _write

action._process(action._filter_post(pre[action]))

File "/opt/odoo/odoo11/addons/base_automation/models/base_automation.py", line 184, in _process

self.action_server_id.with_context(**ctx).run()

File "/opt/odoo/odoo11/odoo/addons/base/ir/ir_actions.py", line 554, in run

res = func(action, eval_context=eval_context)

File "/opt/odoo/odoo11/odoo/addons/base/ir/ir_actions.py", line 430, in run_action_code_multi

safe_eval(action.sudo().code.strip(), eval_context, mode="exec", nocopy=True) # nocopy allows to return 'action'

File "/opt/odoo/odoo11/odoo/tools/safe_eval.py", line 373, in safe_eval

pycompat.reraise(ValueError, ValueError('%s: "%s" while evaluating\n%r' % (ustr(type(e)), ustr(e), expr)), exc_info[2])

File "/opt/odoo/odoo11/odoo/tools/pycompat.py", line 86, in reraise

raise value.with_traceback(tb)

File "/opt/odoo/odoo11/odoo/tools/safe_eval.py", line 350, in safe_eval

return unsafe_eval(c, globals_dict, locals_dict)

File "", line 13, in <module>

ValueError: <class 'AttributeError'>: "'mrp.workcenter' object has no attribute 'active_move_line_ids'" while evaluating

'# Available variables:\n# - env: Odoo Environment on which the action is triggered\n# - model: Odoo Model of the record on which the action is triggered; is a void recordset\n# - record: record on which the action is triggered; may be be void\n# - records: recordset of all records on which the action is triggered in multi-mode; may be void\n# - time, datetime, dateutil, timezone: useful Python libraries\n# - log: log(message, level=\'info\'): logging function to record debug information in ir.logging table\n# - Warning: Warning Exception to use with raise\n# To return an action, assign: action = {...}\n\nlots_used = []\n# loop through all Lots we plan to consume, which also triggers the rules for the first Lot\nfor line in record.active_move_line_ids: \n # only run the rules when we enter a Lot and a Qty\n if line.qty_done and line.qty_done > 0 and line.lot_id: \n # Search for the first available Lot with Stock remaining\n lots_available = env[\'stock.production.lot\'].search([(\'product_id\',\'=\',line.product_id.id),(\'product_qty\',\'>\',0)])\n # If we are entering a Lot that isn\'t the first available, and we haven\'t planned to consume it on this Work Order\n if lots_available[0].name != line.lot_id.name and lots_available[0].name not in lots_used: \n raise Warning("Lot " + lots_available[0].name + " should be used before Lot " + line.lot_id.name + ".")\n # If we have already planned to consume this Lot\n if line.lot_id.name in lots_used: \n raise Warning("Lot " + line.lot_id.name + " has already been selected to consume in this Work Order." \\\n + "Please choose another Lot.")\n # If this Lot has no remaining Stock:\n if line.lot_id.product_qty == 0.0:\n raise Warning("Lot " + line.lot_id.name + " has been completely consumed. Please choose another Lot.")\n # If we are not planning to consume as much as the Work Order suggests:\n if line.lot_id.product_qty < line.qty_done:\n raise Warning("Lot " + line.lot_id.name + " can NOT be used. It has only " + str(line.lot_id.product_qty) \\\n + " " + line.product_uom_id.name + " remaining and you need " + str(line.qty_done) + ".")\n # if all rules pass, add this Lot to the list we are using to keep track of which Lots we plan to consume\n lots_used.append(line.lot_id.name)'