Add support for disassembly production

Closes #14091
This commit is contained in:
Cédric Krier committed 2025-06-19 17:39:38 +02:00
1 parent 29e3785f8d
commit 0b078acacd
6 files changed
+44 -18

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Add support for disassembly production
Version 7.6.0 - 2025-04-28
--------------------------
+15 -12
View File
@@ -22,6 +22,8 @@ class BOM(DeactivableMixin, ModelSQL, ModelView):
code_readonly = fields.Function(
fields.Boolean("Code Readonly"), 'get_code_readonly')
inputs = fields.One2Many('production.bom.input', 'bom', "Input Materials")
input_products = fields.Many2Many(
'production.bom.input', 'bom', 'product', "Input Products")
outputs = fields.One2Many(
'production.bom.output', 'bom', "Output Materials")
output_products = fields.Many2Many('production.bom.output',
@@ -71,18 +73,18 @@ class BOM(DeactivableMixin, ModelSQL, ModelView):
('code', operator, code_value, *extra),
]
def compute_factor(self, product, quantity, unit):
'''
Compute factor for an output product
'''
Uom = Pool().get('product.uom')
output_quantity = 0
for output in self.outputs:
if output.product == product:
output_quantity += Uom.compute_qty(
output.unit, output.quantity, unit, round=False)
if output_quantity:
return quantity / output_quantity
def compute_factor(self, product, quantity, unit, type='outputs'):
"Compute factor for a product from the type"
pool = Pool()
Uom = pool.get('product.uom')
assert type in {'inputs', 'outputs'}, f"Invalid {type}"
total = 0
for line in getattr(self, type):
if line.product == product:
total += Uom.compute_qty(
line.unit, line.quantity, unit, round=False)
if total:
return quantity / total
else:
return 0
@@ -108,6 +110,7 @@ class BOM(DeactivableMixin, ModelSQL, ModelView):
else:
default = default.copy()
default.setdefault('code', None)
default.setdefault('input_products', None)
default.setdefault('output_products', None)
return super().copy(records, default=default)
+22 -5
View File
@@ -70,9 +70,18 @@ class Production(ShipmentAssignMixin, Workflow, ModelSQL, ModelView):
'readonly': (~Eval('state').in_(['request', 'draft'])
| Eval('inputs', [-1]) | Eval('outputs', [-1])),
})
type = fields.Selection([
('assembly', "Assembly"),
('disassembly', "Disassembly"),
], "Type", required=True,
states={
'readonly': ~Eval('state').in_(['request', 'draft']),
})
product = fields.Many2One('product.product', 'Product',
domain=[
('producible', '=', True),
If(Eval('type') == 'assembly',
('producible', '=', True),
()),
],
states={
'readonly': ~Eval('state').in_(['request', 'draft']),
@@ -83,7 +92,10 @@ class Production(ShipmentAssignMixin, Workflow, ModelSQL, ModelView):
depends={'company'})
bom = fields.Many2One('production.bom', 'BOM',
domain=[
('output_products', '=', Eval('product', 0)),
If(Eval('type') == 'disassembly',
('input_products', '=', Eval('product', -1)),
('output_products', '=', Eval('product', -1)),
),
],
states={
'readonly': (~Eval('state').in_(['request', 'draft'])
@@ -305,6 +317,10 @@ class Production(ShipmentAssignMixin, Workflow, ModelSQL, ModelView):
warehouse = Location(warehouse_id)
return warehouse.production_location.id
@classmethod
def default_type(cls):
return 'assembly'
@staticmethod
def default_company():
return Transaction().context.get('company')
@@ -312,7 +328,7 @@ class Production(ShipmentAssignMixin, Workflow, ModelSQL, ModelView):
@fields.depends('product', 'bom')
def compute_lead_time(self, pattern=None):
pattern = pattern.copy() if pattern is not None else {}
if self.product:
if self.product and self.product.producible:
pattern.setdefault('bom', self.bom.id if self.bom else None)
for line in self.product.production_lead_times:
if line.match(pattern):
@@ -384,14 +400,15 @@ class Production(ShipmentAssignMixin, Workflow, ModelSQL, ModelView):
return move
@fields.depends(
'bom', 'product', 'unit', 'quantity', 'inputs', 'outputs',
'type', 'bom', 'product', 'unit', 'quantity', 'inputs', 'outputs',
methods=['_move'])
def explode_bom(self):
if not (self.bom and self.product and self.unit):
return
factor = self.bom.compute_factor(
self.product, self.quantity or 0, self.unit)
self.product, self.quantity or 0, self.unit,
type='inputs' if self.type == 'disassembly' else 'outputs')
inputs = []
for input_ in self.bom.inputs:
quantity = input_.compute_quantity(factor)
+1 -1
View File
@@ -20,7 +20,7 @@ class ProductionTestCase(CompanyTestMixin, ModuleTestCase):
LeadTime = pool.get('production.lead_time')
date = datetime.date(2016, 11, 26)
product = Product()
product = Product(producible=True)
product.production_lead_times = []
production = Production()
production.planned_date = date
@@ -22,6 +22,10 @@ this repository contains the full copyright notices and license terms. -->
<label name="warehouse"/>
<field name="warehouse"/>
<label name="type"/>
<field name="type"/>
<newline/>
<label name="product"/>
<field name="product"/>
<label name="bom"/>
@@ -5,6 +5,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="company" expand="1" optional="1"/>
<field name="number" expand="1"/>
<field name="reference" expand="1" optional="1"/>
<field name="type" optional="1"/>
<field name="product" expand="2"/>
<field name="quantity" symbol="unit"/>
<field name="planned_start_date" optional="0"/>