Files
tryton/modules/production/bom.py
T

280 lines
9.4 KiB
Python
Raw Normal View History

2014-12-02 12:55:39 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2018-03-26 14:11:26 +02:00
from trytond.model import ModelView, ModelSQL, DeactivableMixin, fields, Unique
2012-05-13 23:00:54 +02:00
from trytond.wizard import Wizard, StateView, Button
from trytond.pyson import Eval
from trytond.pool import Pool
2018-03-26 14:11:26 +02:00
class BOM(DeactivableMixin, ModelSQL, ModelView):
2012-05-13 23:00:54 +02:00
"Bill of Material"
2012-09-24 22:12:50 +02:00
__name__ = 'production.bom'
2012-05-13 23:00:54 +02:00
name = fields.Char('Name', required=True, translate=True)
inputs = fields.One2Many('production.bom.input', 'bom', 'Inputs')
outputs = fields.One2Many('production.bom.output', 'bom', 'Outputs')
output_products = fields.Many2Many('production.bom.output',
'bom', 'product', 'Output Products')
2012-09-24 22:12:50 +02:00
def compute_factor(self, product, quantity, uom):
2012-05-13 23:00:54 +02:00
'''
Compute factor for an output product
'''
2012-09-24 22:12:50 +02:00
Uom = Pool().get('product.uom')
for output in self.outputs:
2012-05-13 23:00:54 +02:00
if output.product == product:
if not output.quantity:
return 0.0
2012-09-24 22:12:50 +02:00
quantity = Uom.compute_qty(uom, quantity,
2012-05-13 23:00:54 +02:00
output.uom, round=False)
return quantity / output.quantity
2022-03-06 13:36:25 +01:00
else:
return 0.0
2012-05-13 23:00:54 +02:00
2012-09-24 22:12:50 +02:00
@classmethod
def copy(cls, records, default=None):
2012-05-13 23:00:54 +02:00
if default is None:
default = {}
else:
default = default.copy()
2018-09-01 01:02:03 +02:00
default.setdefault('output_products', None)
2012-09-24 22:12:50 +02:00
return super(BOM, cls).copy(records, default=default)
2012-05-13 23:00:54 +02:00
class BOMInput(ModelSQL, ModelView):
"Bill of Material Input"
2012-09-24 22:12:50 +02:00
__name__ = 'production.bom.input'
2012-05-13 23:00:54 +02:00
bom = fields.Many2One('production.bom', 'BOM', required=True,
select=1, ondelete='CASCADE')
product = fields.Many2One('product.product', 'Product', required=True)
2012-05-13 23:00:54 +02:00
uom_category = fields.Function(fields.Many2One(
2014-01-26 14:29:24 +01:00
'product.uom.category', 'Uom Category'), 'on_change_with_uom_category')
2012-05-13 23:00:54 +02:00
uom = fields.Many2One('product.uom', 'Uom', required=True,
domain=[
('category', '=', Eval('uom_category')),
], depends=['uom_category'])
2014-01-26 14:29:24 +01:00
unit_digits = fields.Function(fields.Integer('Unit Digits'),
'on_change_with_unit_digits')
2012-05-13 23:00:54 +02:00
quantity = fields.Float('Quantity', required=True,
domain=['OR',
('quantity', '>=', 0),
('quantity', '=', None),
],
2012-05-13 23:00:54 +02:00
digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits'])
2012-09-24 22:12:50 +02:00
@classmethod
def __setup__(cls):
super(BOMInput, cls).__setup__()
cls.product.domain = [('type', 'in', cls.get_product_types())]
2021-03-07 11:13:53 +01:00
cls.__access__.add('bom')
t = cls.__table__()
2012-09-24 22:12:50 +02:00
cls._sql_constraints = [
('product_bom_uniq', Unique(t, t.product, t.bom),
2018-12-29 14:20:29 +01:00
'production.msg_product_bom_unique'),
2012-09-24 22:12:50 +02:00
]
2012-05-13 23:00:54 +02:00
@classmethod
def get_product_types(cls):
return ['goods', 'assets']
2014-01-26 14:29:24 +01:00
@fields.depends('product', 'uom')
2012-09-24 22:12:50 +02:00
def on_change_product(self):
if self.product:
category = self.product.default_uom.category
if not self.uom or self.uom.category != category:
2014-10-29 20:40:02 +01:00
self.uom = self.product.default_uom
self.unit_digits = self.product.default_uom.digits
2012-05-13 23:00:54 +02:00
else:
2014-10-29 20:40:02 +01:00
self.uom = None
2012-05-13 23:00:54 +02:00
2014-01-26 14:29:24 +01:00
@fields.depends('product')
2012-09-24 22:12:50 +02:00
def on_change_with_uom_category(self, name=None):
if self.product:
return self.product.default_uom.category.id
2012-05-13 23:00:54 +02:00
2014-01-26 14:29:24 +01:00
@fields.depends('uom')
2012-09-24 22:12:50 +02:00
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
2012-05-13 23:00:54 +02:00
return 2
2017-07-26 23:43:23 +02:00
def get_rec_name(self, name):
return self.product.rec_name
@classmethod
def search_rec_name(cls, name, clause):
2017-09-27 18:37:30 +02:00
return [('product.rec_name',) + tuple(clause[1:])]
2017-07-26 23:43:23 +02:00
@classmethod
def validate(cls, boms):
super(BOMInput, cls).validate(boms)
for bom in boms:
bom.check_bom_recursion()
2012-09-24 22:12:50 +02:00
def check_bom_recursion(self):
2012-05-13 23:00:54 +02:00
'''
Check BOM recursion
'''
self.product.check_bom_recursion()
2012-05-13 23:00:54 +02:00
2012-09-24 22:12:50 +02:00
def compute_quantity(self, factor):
2017-02-02 12:12:19 +01:00
return self.uom.ceil(self.quantity * factor)
2012-05-13 23:00:54 +02:00
class BOMOutput(BOMInput):
"Bill of Material Output"
2012-09-24 22:12:50 +02:00
__name__ = 'production.bom.output'
_table = 'production_bom_output' # Needed to override BOMInput._table
2012-05-13 23:00:54 +02:00
2017-02-02 12:12:19 +01:00
def compute_quantity(self, factor):
return self.uom.floor(self.quantity * factor)
@classmethod
def delete(cls, outputs):
pool = Pool()
ProductBOM = pool.get('product.product-production.bom')
bom_products = [b for o in outputs for b in o.product.boms]
super(BOMOutput, cls).delete(outputs)
# Validate that output_products domain on bom is still valid
ProductBOM._validate(bom_products, ['bom'])
2012-05-13 23:00:54 +02:00
class BOMTree(ModelView):
'BOM Tree'
2012-09-24 22:12:50 +02:00
__name__ = 'production.bom.tree'
2012-05-13 23:00:54 +02:00
product = fields.Many2One('product.product', 'Product')
quantity = fields.Float('Quantity', digits=(16, Eval('unit_digits', 2)),
depends=['unit_digits'])
uom = fields.Many2One('product.uom', 'Uom')
unit_digits = fields.Integer('Unit Digits')
childs = fields.One2Many('production.bom.tree', None, 'Childs')
2012-09-24 22:12:50 +02:00
@classmethod
def tree(cls, product, quantity, uom, bom=None):
Input = Pool().get('production.bom.input')
2012-05-13 23:00:54 +02:00
result = []
if bom is None:
if not product.boms:
return result
bom = product.boms[0].bom
2012-09-24 22:12:50 +02:00
factor = bom.compute_factor(product, quantity, uom)
for input_ in bom.inputs:
quantity = Input.compute_quantity(input_, factor)
childs = cls.tree(input_.product, quantity, input_.uom)
2012-05-13 23:00:54 +02:00
values = {
2012-09-24 22:12:50 +02:00
'product': input_.product.id,
'product.': {
'rec_name': input_.product.rec_name,
},
2012-05-13 23:00:54 +02:00
'quantity': quantity,
2012-09-24 22:12:50 +02:00
'uom': input_.uom.id,
'uom.': {
'rec_name': input_.uom.rec_name,
},
2012-09-24 22:12:50 +02:00
'unit_digits': input_.uom.digits,
2012-05-13 23:00:54 +02:00
'childs': childs,
}
result.append(values)
return result
class OpenBOMTreeStart(ModelView):
'Open BOM Tree'
2012-09-24 22:12:50 +02:00
__name__ = 'production.bom.tree.open.start'
2012-05-13 23:00:54 +02:00
quantity = fields.Float('Quantity', required=True,
digits=(16, Eval('unit_digits', 2)), depends=['unit_digits'])
uom = fields.Many2One('product.uom', 'Unit', required=True,
domain=[
('category', '=', Eval('category')),
], depends=['category'])
2014-01-26 14:29:24 +01:00
unit_digits = fields.Integer('Unit Digits', readonly=True)
2012-05-13 23:00:54 +02:00
category = fields.Many2One('product.uom.category', 'Category',
readonly=True)
bom = fields.Many2One('product.product-production.bom',
'BOM', required=True, domain=[
('product', '=', Eval('product')),
], depends=['product'])
product = fields.Many2One('product.product', 'Product', readonly=True)
2014-01-26 14:29:24 +01:00
@fields.depends('uom')
2012-09-24 22:12:50 +02:00
def on_change_with_unit_digits(self):
if self.uom:
return self.uom.digits
2012-05-13 23:00:54 +02:00
return 2
class OpenBOMTreeTree(ModelView):
'Open BOM Tree'
2012-09-24 22:12:50 +02:00
__name__ = 'production.bom.tree.open.tree'
2012-05-13 23:00:54 +02:00
2015-12-15 09:54:49 +01:00
bom_tree = fields.One2Many('production.bom.tree', None, 'BOM Tree',
readonly=True)
2012-05-13 23:00:54 +02:00
2012-09-24 22:12:50 +02:00
@classmethod
def tree(cls, bom, product, quantity, uom):
2012-05-13 23:00:54 +02:00
pool = Pool()
2012-09-24 22:12:50 +02:00
Tree = pool.get('production.bom.tree')
2012-05-13 23:00:54 +02:00
2012-09-24 22:12:50 +02:00
childs = Tree.tree(product, quantity, uom, bom=bom)
2012-05-13 23:00:54 +02:00
bom_tree = [{
2012-09-24 22:12:50 +02:00
'product': product.id,
'product.': {
'rec_name': product.rec_name,
},
2012-09-24 22:12:50 +02:00
'quantity': quantity,
'uom': uom.id,
'uom.': {
'rec_name': uom.rec_name,
},
2012-09-24 22:12:50 +02:00
'unit_digits': uom.digits,
'childs': childs,
}]
2012-05-13 23:00:54 +02:00
return {
'bom_tree': bom_tree,
2012-09-24 22:12:50 +02:00
}
2012-05-13 23:00:54 +02:00
class OpenBOMTree(Wizard):
'Open BOM Tree'
2012-09-24 22:12:50 +02:00
__name__ = 'production.bom.tree.open'
2012-05-13 23:00:54 +02:00
start = StateView('production.bom.tree.open.start',
'production.bom_tree_open_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
2015-03-10 18:42:41 +01:00
Button('OK', 'tree', 'tryton-ok', True),
2012-05-13 23:00:54 +02:00
])
tree = StateView('production.bom.tree.open.tree',
'production.bom_tree_open_tree_view_form', [
2018-08-19 20:02:58 +02:00
Button('Change', 'start', 'tryton-back'),
2012-05-13 23:00:54 +02:00
Button('Close', 'end', 'tryton-close'),
])
2012-09-24 22:12:50 +02:00
def default_start(self, fields):
2012-05-13 23:00:54 +02:00
defaults = {}
product = self.record
2012-05-13 23:00:54 +02:00
defaults['category'] = product.default_uom.category.id
2012-09-24 22:12:50 +02:00
if getattr(self.start, 'uom', None):
defaults['uom'] = self.start.uom.id
defaults['unit_digits'] = self.start.unit_digits
2012-05-13 23:00:54 +02:00
else:
defaults['uom'] = product.default_uom.id
defaults['unit_digits'] = product.default_uom.digits
defaults['product'] = product.id
2012-09-24 22:12:50 +02:00
if getattr(self.start, 'bom', None):
defaults['bom'] = self.start.bom.id
2012-05-13 23:00:54 +02:00
elif product.boms:
defaults['bom'] = product.boms[0].id
2012-09-24 22:12:50 +02:00
defaults['quantity'] = getattr(self.start, 'quantity', None)
2012-05-13 23:00:54 +02:00
return defaults
2012-09-24 22:12:50 +02:00
def default_tree(self, fields):
2012-05-13 23:00:54 +02:00
pool = Pool()
2012-09-24 22:12:50 +02:00
BomTree = pool.get('production.bom.tree.open.tree')
return BomTree.tree(self.start.bom.bom, self.start.product,
self.start.quantity, self.start.uom)