I have a need to setup a simplified manufacturing process.
I want users to select a BOM, and a Barcode and click produce to complete a manufacturing order.
The bom determines the quantity, the barcode should set the serial and the location of the good determines which components lots are being consumed. I already got some help to setup a button that manufactures which works fine without lots on components and when i use .action_generate_serial() for the serial. I'm fairly capable, but not sure where to look to find the instructions to find how to add lot assignment on components and how to use my own lot (see Lot_id). All code for Models.py and Views.xml attached.
from odoo import models, fields, api, _,exceptions
class automated_manufacturing(models.Model):
"""
Pseudo:
get bom
select size of production
now scan the barcode to confirm the BOM matches the Barcode.
now scan the serial number and manufacture.
Add to notebook using domain to limit the view. - explore adding reference to keep filter simple or do it based on day
now remove from any planned manufacturing orders for today. if possible.
NB)think of how scalable this is.
Maybes:
2) Check levels of Goods in Airlock
when it comes to filtering MO's use this video to easily filter = https://www.youtube.com/watch?v=Ru6mS2ds-EA
"""
_name = "am.am"
_description = 'Primary base Model for Manufacturing Automation'
_inherit = ["mail.thread"]
#_inherits = {'mrp.production': 'related_manufacturing'}
''' These are status and reference Calculations
status is progressed with buttons see also view.
reference is calculated using create function.
'''
state = fields.Selection([('waiting', 'Waiting'), ('working', 'Working'), ('complete', 'Complete'), ('cancel', 'Cancelled')], default='waiting', string="status", tracking=True)
reference = fields.Char(string='Manufacturing Reference', readonly=True, copy=False, default=lambda self: _('New'))
''' name value and description are not really needed just used to keep it fleshed
bom_selected allows bomb selection and Check_wip and check_wip_bool return message and True/False
to allow manufacturing.
'''
name = fields.Char()
value = fields.Integer()
description = fields.Text()
BOM_selected = fields.Many2one("mrp.bom", tracking=True, read=['state'])
check_wip = fields.Char(string="Products in WIP:", compute='_compute_bom_check', readonly=True)
check_wip_bool = fields.Text(compute='_compute_bom_check' )
package_qty = fields.Char(compute='_Package_type', string="Packaged_qty", readonly=True)
"""
now scan the barcode to confirm the BOM matches the Barcode.
now scan the serial number and manufacture.
Add to notebook using domain to limit the view. - explore adding reference to keep filter simple or do it based on day
now remove from any planned manufacturing orders for today. if possible.
"""
Confirm_BOM = fields.Char()
Serial_Number = fields.Char()
#related_manufacturing = fields.Many2one("mrp.production")
# check products in wip and reply
#if in wip then select what type of package
#then move to manu orders duplication with set amount of consumption and list
def manufacture_value(self):
company = self.env.company.id
if not self.Confirm_BOM:
raise exceptions.UserError('BOM Barcode Missing!')
#turn barcode into product_id
self._cr.execute("""Select
pp.id,
pp.barcode,
pp.product_tmpl_id,
pt.uom_id,
bom.id as bom_id
from
product_product pp
inner join
product_template pt
on
pp.product_tmpl_id = pt.id
inner join
mrp_bom bom
on
pt.id = bom.product_tmpl_id
where
pp.barcode = %s
and
bom.id = %s;
""",
(self.Confirm_BOM,self.BOM_selected.id))
WIP_check = self._cr.fetchall()
if len(WIP_check)>1:
print(len(WIP_check))
raise exceptions.UserError('Duplicate Barcode. Please ensure products have unique Barcode!')
elif len(WIP_check)<1:
raise exceptions.UserError('No barcode or BOM found!')
else:
for pp_id, pp_barcode, pp_product_tmpl_id, pt_uom_id,bom_id in WIP_check:
WIP_check = pp_id
product_uom_id = pt_uom_id
#Source location
#turn barcode into product_id
self._cr.execute("""
select
id,
name
from
stock_location sl
where
sl.name like %s
""",
('WIP',))
Source_Location = self._cr.fetchall()
if len(Source_Location) > 1:
print(len(Source_Location))
raise exceptions.UserError('Duplicate Location. Please ensure only one location contains WIP!')
elif len(Source_Location) < 1:
raise exceptions.UserError('No location called WIP!')
else:
for wip_id,wip_name in Source_Location:
Source_Location = wip_id
wip_name = wip_name
# WHILE TESTING ONLY
Serial_Number = int(self.Serial_Number)
Serial_Number +=1
self.Serial_Number = Serial_Number
Lot_id = self.env['stock.production.lot'].create({
'name': self.Serial_Number,
'product_id': WIP_check,
'company_id': company,
})
## https://www.odoo.com/forum/help-1/how-to-create-manufacturing-order-using-api-182874
bom_description = self.get_bom_description(WIP_check)
print('str(self.package_qty)',str(self.package_qty))
manufacturing_order = self.env['mrp.production'].create([{
'product_id': WIP_check,
'product_qty': str(self.package_qty),
'product_uom_id': bom_description['product_uom_id'][0],
'qty_producing': self.package_qty,
'product_uom_qty': str(self.package_qty),
'bom_id': bom_description['bom_id'][0],
'origin': self.reference,
'location_src_id': Source_Location,
'move_finished_ids': [
[0, '', {
'product_id': move_finished_id[2]['product_id'][0],
'product_uom': move_finished_id[2]['product_uom'][0],
'product_uom_qty': str(int(move_finished_id[2]['product_uom_qty'])*int(self.package_qty)),
'location_id': move_finished_id[2]['location_id'][0],
'location_dest_id': move_finished_id[2]['location_dest_id'][0],
'name': move_finished_id[2]['name'],
'byproduct_id': False,
'quantity_done': str(int(move_finished_id[2]['product_uom_qty']) * int(self.package_qty)),
}]
for move_finished_id in bom_description['move_finished_ids'][1:]
],
'move_raw_ids': [
[0, '', {
'product_id': move_raw_id[2]['product_id'][0],
'bom_line_id': move_raw_id[2]['bom_line_id'][0],
'product_uom': move_raw_id[2]['product_uom'][0],
'product_uom_qty': str(int(move_raw_id[2]['product_uom_qty'])*int(self.package_qty)),
'location_id': Source_Location,
'location_dest_id':Source_Location,
'name': move_raw_id[2]['name'],
'quantity_done': str(int(move_raw_id[2]['product_uom_qty']) * int(self.package_qty)),
}]
for move_raw_id in bom_description['move_raw_ids'][1:]
]
}])
manufacturing_order.action_confirm()
manufacturing_order.action_generate_serial()
manufacturing_order.action_assign()
#manufacturing_order.button_mark_done()
def get_bom_description(self, product_id):
onchange_res = self.env['mrp.production'].onchange(
{
'product_id': product_id
},
'product_id',
{
'company_id': '1',
'product_id': '1',
'product_qty': '1',
'product_uom_id': '1',
'bom_id': '1',
'move_finished_ids': '1',
'move_finished_ids.product_id': '1',
'move_finished_ids.product_uom': '1',
'move_finished_ids.product_uom_qty': '1',
'move_finished_ids.location_id': '1',
'move_finished_ids.location_dest_id': '1',
'move_finished_ids.name': '',
'move_raw_ids': '1',
'move_raw_ids.product_id': '1',
'move_raw_ids.name': '',
'move_raw_ids.bom_line_id': '',
'move_raw_ids.location_id': '1',
'move_raw_ids.location_dest_id': '1',
'move_raw_ids.product_uom_qty': '1',
'move_raw_ids.product_uom': '1',
}
)
return onchange_res['value']
def working_button(self):
if self.check_wip_bool:
self.state = "working"
else:
if self.BOM_selected:
self.state = "waiting"
raise exceptions.UserError('Products missing in WIP Cannot Advance')
else:
self.state = "waiting"
raise exceptions.UserError('Select a BOM to continue')
def complete_button(self):
self.state = "complete"
def cancel_button(self):
self.state= "cancel"
@api.model
def create(self, vals):
if not vals.get('description'):
vals['description']= 'New Manufacturing Automation'
if vals.get('reference', _('New')) == _('New'):
vals['reference'] = self.env['ir.sequence'].next_by_code('am.am') or _('New')
res = super(automated_manufacturing, self).create(vals)
return res
@api.onchange('BOM_selected')
def _Package_type(self):
for record in self:
if not record.BOM_selected.id:
bom_select_id = 0
else:
bom_select_id = record.BOM_selected.id
record._cr.execute("""
select
bom.id,
bom.product_tmpl_id,
pt.id,
pt.name,
pt.default_code
from
mrp_bom bom
inner join
product_template pt
on
bom.product_tmpl_id = pt.id
where
bom.id = %s
""", (bom_select_id,))
selected = record._cr.fetchall()
if not selected:
record.package_qty = ''
print('record.BOM_selected', selected)
for bom_id, bom_product_tmpl_id, pt_id, pt_name, pt_default_code in selected:
if 'n95' in str(pt_name).lower():
record.package_qty = 12
else:
record.package_qty = 40
@api.onchange('BOM_selected')
def _compute_bom_check(self):
for record in self:
# get list o products for each BOM.
bom_lines= []
company = self.env.company.id
# self.env.cr.execute("some_sql", params)
record._cr.execute("""SELECT
bom.id,
bom.product_tmpl_id,
bomprod.bom_id,
bomprod.product_id,
prod.id,
prod.product_tmpl_id,
prod.default_code,
pt.name
FROM
mrp_bom bom
inner join
mrp_bom_line bomprod on bom.id = bomprod.bom_id
inner join
product_product prod on bomprod.product_id = prod.id
inner join
product_template pt on prod.product_tmpl_id = pt.id
where
bom.company_id = %s
""", (company,))
BOM_components = record._cr.fetchall()
# BOM PArents - get the list of products produced by the bom
if not record.BOM_selected:
BOM_Parent = 0
else:
BOM_Parent =record.BOM_selected.id
# Select product
record._cr.execute("""select pp.id,
pp.default_code,
sl.complete_name,
sl.id as location_id
from
stock_quant qty
inner join
stock_location sl
on
qty.location_id = sl.id
inner join
product_product pp
on
qty.product_id = pp.id
where sl.name like %s
""", ('WIP',))
WIP_check = record._cr.fetchall()
# Psuedo - index the parts to the parents Key:list = Parent:component and then compare in WIP to these.
# remove product where match to remove key and then if key is gone that product can be built based on units in WIP
BOM_Dict = dict()
for cbom_id, cbom_product_tmpl_id, cbomprod_bom_id, cbomprod_product_id, cprod_id, cprod_product_tmpl_id, cprod_default_code,cpt_name in BOM_components:
if BOM_Parent == cbom_id:
if not BOM_Dict or BOM_Parent not in BOM_Dict.keys():
#i am empty therforegenerate me.
BOM_Dict[BOM_Parent] = [(cprod_id, cprod_default_code, cpt_name)]
else:
# this product is used by this parent append to dict
BOM_Dict[BOM_Parent].append((cprod_id, cprod_default_code, cpt_name))
# remove values from bom where the products are in WIP
for wipp_id,wipp_default_code,wipp_complete_name,wipsl_id in WIP_check:
for keys in BOM_Dict.keys():
for values in BOM_Dict[keys]:
id,default_code,name = values
if id == wipp_id:
BOM_Dict[keys].remove(values)
# now that weve lets find empty lists as these BOMs can be manufactured.
message = []
for keys in BOM_Dict.keys():
if len(BOM_Dict[keys]) == 0:
record.check_wip = str('Accepted: All Products found in WIP')
record.check_wip_bool = True
else:
record.check_wip = str('Denied, The following products are missing from WIP:\n' + str(BOM_Dict[keys]))
record.check_wip_bool = False
print(record.check_wip, record.check_wip_bool)
VIEWS.xml
Start Manufacturing Run
am.am
tree,form
auto.manufacturing.tree
am.am
tree
auto.manufacturing.form
am.am
action="am_tree_action"/>