Skip to content

Commit

Permalink
[IMP] mrp: add production lines
Browse files Browse the repository at this point in the history
To record a production via a manufacturing order, the user can either
use the produce wizard or the workorders views. Those two objects was
technically different but act more or less the same. This commit aims to
merge the similar behaviors in common code

We introduce two new abstract models :
 1. Abstract workorder to share workorders and the produce wizard
 2. Abstract workorder lines to share active_move_line on workorder and
    product_produce_line on the wizard. Those abstract line keep the information
    about the quantities and lot number to put on component move lines and
    finished product move lines

Task : 1891864
  • Loading branch information
Whenrow committed Feb 1, 2019
1 parent 0abfaed commit 5ef4666
Show file tree
Hide file tree
Showing 20 changed files with 554 additions and 506 deletions.
11 changes: 7 additions & 4 deletions addons/mrp/data/mrp_demo.xml
Expand Up @@ -696,14 +696,17 @@
<value model="mrp.product.produce" eval="dict(
obj().with_context(active_id=ref('mrp.mrp_production_laptop_cust')).default_get(list(obj().fields_get())),
**{
'product_qty': obj().env.ref('mrp.mrp_production_laptop_cust').product_qty,
'lot_id': ref('mrp.lot_product_27_0'),
'qty_producing': obj().env.ref('mrp.mrp_production_laptop_cust').product_qty,
'final_lot_id': ref('mrp.lot_product_27_0'),
}
)"/>
</function>

<function model="mrp.product.produce" name="_onchange_product_qty">
<value model="mrp.product.produce" search="[('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)]"/>
<function model="mrp.product.produce.line" name="create">
<value model="mrp.product.produce.line" eval="[dict(item,
**{
'product_produce_id': obj().env['mrp.product.produce'].search([('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)]).id
}) for item in obj().env['mrp.product.produce'].search([('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)])._update_workorder_lines()['to_create']]"/>
</function>

<function model="mrp.product.produce" name="do_produce">
Expand Down
1 change: 1 addition & 0 deletions addons/mrp/models/__init__.py
Expand Up @@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import mrp_document
from . import mrp_abstract_workorder
from . import res_config_settings
from . import mrp_bom
from . import mrp_routing
Expand Down
354 changes: 354 additions & 0 deletions addons/mrp/models/mrp_abstract_workorder.py

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion addons/mrp/models/mrp_production.py
Expand Up @@ -509,6 +509,7 @@ def _get_move_raw_values(self, bom_line, line_data):
data = {
'sequence': bom_line.sequence,
'name': self.name,
'reference': self.name,
'date': self.date_planned_start,
'date_expected': self.date_planned_start,
'bom_line_id': bom_line.id,
Expand Down Expand Up @@ -715,6 +716,7 @@ def _workorders_create(self, bom, bom_data):
'name': operation.name,
'production_id': self.id,
'workcenter_id': operation.workcenter_id.id,
'product_uom_id': self.product_uom_id.id,
'operation_id': operation.id,
'duration_expected': duration_expected,
'state': len(workorders) == 0 and 'ready' or 'pending',
Expand All @@ -733,7 +735,7 @@ def _workorders_create(self, bom, bom_data):
moves_raw.mapped('move_line_ids').write({'workorder_id': workorder.id})
(moves_finished + moves_raw).write({'workorder_id': workorder.id})

workorder._generate_lot_ids()
workorder.generate_wo_lines()
return workorders

def _check_lots(self):
Expand Down
228 changes: 39 additions & 189 deletions addons/mrp/models/mrp_workorder.py

Large diffs are not rendered by default.

59 changes: 0 additions & 59 deletions addons/mrp/models/stock_move.py
Expand Up @@ -225,65 +225,6 @@ def _generate_move_phantom(self, bom_line, product_qty, quantity_done):
return move
return self.env['stock.move']

def _generate_consumed_move_line(self, qty_to_add, final_lot, lot=False):
if lot:
move_lines = self.move_line_ids.filtered(lambda ml: ml.lot_id == lot and not ml.lot_produced_id)
else:
move_lines = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_produced_id)

# Sanity check: if the product is a serial number and `lot` is already present in the other
# consumed move lines, raise.
if lot and self.product_id.tracking == 'serial' and lot in self.move_line_ids.filtered(lambda ml: ml.qty_done).mapped('lot_id'):
raise UserError(_('You cannot consume the same serial number twice. Please correct the serial numbers encoded.'))

for ml in move_lines:
rounding = ml.product_uom_id.rounding
if float_compare(qty_to_add, 0, precision_rounding=rounding) <= 0:
break
quantity_to_process = min(qty_to_add, ml.product_uom_qty - ml.qty_done)
qty_to_add -= quantity_to_process

new_quantity_done = (ml.qty_done + quantity_to_process)
if float_compare(new_quantity_done, ml.product_uom_qty, precision_rounding=rounding) >= 0:
ml.write({'qty_done': new_quantity_done, 'lot_produced_id': final_lot.id})
else:
new_qty_reserved = ml.product_uom_qty - new_quantity_done
default = {'product_uom_qty': new_quantity_done,
'qty_done': new_quantity_done,
'lot_produced_id': final_lot.id}
ml.copy(default=default)
ml.with_context(bypass_reservation_update=True).write({'product_uom_qty': new_qty_reserved, 'qty_done': 0})

if float_compare(qty_to_add, 0, precision_rounding=self.product_uom.rounding) > 0:
# Search for a sub-location where the product is available. This might not be perfectly
# correct if the quantity available is spread in several sub-locations, but at least
# we should be closer to the reality. Anyway, no reservation is made, so it is still
# possible to change it afterwards.
quants = self.env['stock.quant']._gather(self.product_id, self.location_id, lot_id=lot, strict=False)
available_quantity = self.product_id.uom_id._compute_quantity(
self.env['stock.quant']._get_available_quantity(
self.product_id, self.location_id, lot_id=lot, strict=False
), self.product_uom
)
location_id = False
if float_compare(qty_to_add, available_quantity, precision_rounding=self.product_uom.rounding) < 0:
location_id = quants.filtered(lambda r: r.quantity > 0)[-1:].location_id

vals = {
'move_id': self.id,
'product_id': self.product_id.id,
'location_id': location_id.id if location_id else self.location_id.id,
'production_id': self.raw_material_production_id.id,
'location_dest_id': self.location_dest_id.id,
'product_uom_qty': 0,
'product_uom_id': self.product_uom.id,
'qty_done': qty_to_add,
'lot_produced_id': final_lot.id,
}
if lot:
vals.update({'lot_id': lot.id})
self.env['stock.move.line'].create(vals)

def _get_upstream_documents_and_responsibles(self, visited):
if self.created_production_id and self.created_production_id.state not in ('done', 'cancel'):
return [(self.created_production_id, self.created_production_id.user_id, visited)]
Expand Down
2 changes: 2 additions & 0 deletions addons/mrp/security/ir.model.access.csv
Expand Up @@ -28,6 +28,8 @@ access_product_supplierinfo_user,product.supplierinfo user,product.model_product
access_res_partner,res.partner,base.model_res_partner,mrp.group_mrp_user,1,0,0,0
access_mrp_workorder_mrp_user,mrp.workorder.user,model_mrp_workorder,mrp.group_mrp_user,1,1,1,1
access_mrp_workorder_mrp_manager,mrp.workorder,model_mrp_workorder,mrp.group_mrp_manager,1,1,1,1
access_mrp_workorder_line_mrp_user,mrp.workorder.user,model_mrp_workorder_line,mrp.group_mrp_user,1,1,1,1
access_mrp_workorder_line_mrp_manager,mrp.workorder,model_mrp_workorder_line,mrp.group_mrp_manager,1,1,1,1
access_resource_calendar_leaves_user,mrp.resource.calendar.leaves.user,resource.model_resource_calendar_leaves,mrp.group_mrp_user,1,1,1,1
access_resource_calendar_leaves_manager,mrp.resource.calendar.leaves.manager,resource.model_resource_calendar_leaves,mrp.group_mrp_manager,1,0,0,0
access_resource_calendar_attendance_mrp_user,mrp.resource.calendar.attendance.mrp.user,resource.model_resource_calendar_attendance,mrp.group_mrp_user,1,1,1,1
Expand Down
46 changes: 23 additions & 23 deletions addons/mrp/tests/test_order.py
Expand Up @@ -143,7 +143,7 @@ def test_basic(self):
'active_id': man_order.id,
'active_ids': [man_order.id],
}))
produce_form.product_qty = 1.0
produce_form.qty_producing = 1.0
produce_wizard = produce_form.save()
produce_wizard.do_produce()

Expand Down Expand Up @@ -412,7 +412,7 @@ def test_multiple_post_inventory(self):
# produce one item, call `post_inventory`
context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id}
produce_form = Form(self.env['mrp.product.produce'].with_context(context))
produce_form.product_qty = 1.00
produce_form.qty_producing = 1.00
custom_laptop_produce = produce_form.save()
custom_laptop_produce.do_produce()
mo_custom_laptop.post_inventory()
Expand All @@ -425,7 +425,7 @@ def test_multiple_post_inventory(self):
# produce the second item, call `post_inventory`
context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id}
produce_form = Form(self.env['mrp.product.produce'].with_context(context))
produce_form.product_qty = 1.00
produce_form.qty_producing = 1.00
custom_laptop_produce = produce_form.save()
custom_laptop_produce.do_produce()
mo_custom_laptop.post_inventory()
Expand Down Expand Up @@ -458,7 +458,7 @@ def test_rounding(self):
'active_id': production.id,
'active_ids': [production.id],
}))
produce_form.product_qty = 8
produce_form.qty_producing = 8
produce_wizard = produce_form.save()
produce_wizard.do_produce()
self.assertEqual(production.move_raw_ids[0].quantity_done, 16, 'Should use half-up rounding when producing')
Expand All @@ -480,10 +480,9 @@ def test_product_produce_1(self):
'active_ids': [mo.id],
}))
product_produce = produce_form.save()
self.assertEqual(len(product_produce.workorder_line_ids), 2, 'You should have produce lines even the consumed products are not tracked.')
product_produce.do_produce()

self.assertEqual(len(product_produce.produce_line_ids), 2, 'You should have produce lines even the consumed products are not tracked.')

def test_product_produce_2(self):
""" Check that line are created when the consumed products are
tracked by serial and the lot proposed are correct. """
Expand Down Expand Up @@ -511,9 +510,9 @@ def test_product_produce_2(self):
}))
product_produce = produce_form.save()

self.assertEqual(len(product_produce.produce_line_ids), 3, 'You should have 3 produce lines. One for each serial to consume')
product_produce.product_qty = 1
produce_line_1 = product_produce.produce_line_ids[0]
self.assertEqual(len(product_produce.workorder_line_ids), 3, 'You should have 3 produce lines. One for each serial to consume')
product_produce.qty_producing = 1
produce_line_1 = product_produce.workorder_line_ids[0]
produce_line_1.qty_done = 1
remaining_lot = (lot_p1_1 | lot_p1_2) - produce_line_1.lot_id
product_produce.do_produce()
Expand All @@ -523,8 +522,8 @@ def test_product_produce_2(self):
'active_ids': [mo.id],
}))
product_produce = produce_form.save()
self.assertEqual(len(product_produce.produce_line_ids), 2, 'You should have 2 produce lines since one has already be consumed.')
for line in product_produce.produce_line_ids.filtered(lambda x: x.lot_id):
self.assertEqual(len(product_produce.workorder_line_ids), 2, 'You should have 2 produce lines since one has already be consumed.')
for line in product_produce.workorder_line_ids.filtered(lambda x: x.lot_id):
self.assertEqual(line.lot_id, remaining_lot, 'Wrong lot proposed.')

def test_product_produce_3(self):
Expand Down Expand Up @@ -560,15 +559,15 @@ def test_product_produce_3(self):
'active_id': mo.id,
'active_ids': [mo.id],
}))
produce_form.product_qty = 1.0
produce_form.qty_producing = 1.0
product_produce = produce_form.save()
product_produce.lot_id = final_product_lot.id
product_produce.final_lot_id = final_product_lot.id
# product 1 lot 1 shelf1
# product 1 lot 1 shelf2
# product 1 lot 2
self.assertEqual(len(product_produce.produce_line_ids), 4, 'You should have 4 produce lines. lot 1 shelf_1, lot 1 shelf_2, lot2 and for product which have tracking None')
self.assertEqual(len(product_produce.workorder_line_ids), 4, 'You should have 4 produce lines. lot 1 shelf_1, lot 1 shelf_2, lot2 and for product which have tracking None')

for produce_line in product_produce.produce_line_ids:
for produce_line in product_produce.workorder_line_ids:
produce_line.qty_done = produce_line.qty_to_consume + 1
product_produce.do_produce()

Expand Down Expand Up @@ -614,9 +613,10 @@ def test_product_produce_4(self):
'active_id': mo.id,
'active_ids': [mo.id],
}).create({
'product_qty': 1.0,
'qty_producing': 1.0,
})
product_produce._onchange_product_qty()
line_values = product_produce._update_workorder_lines()
product_produce.workorder_line_ids |= product_produce.workorder_line_ids.create(line_values['to_create'])
product_produce.do_produce()

ml_p1 = mo.move_raw_ids.filtered(lambda x: x.product_id == p1).mapped('move_line_ids')
Expand Down Expand Up @@ -658,11 +658,11 @@ def test_product_produce_5(self):
'active_id': mo.id,
'active_ids': [mo.id],
}).create({
'product_qty': 5.0,
'qty_producing': 5.0,
})
produce_wizard._onchange_product_qty()
produce_wizard._onchange_qty_producing()

for produce_line in produce_wizard.produce_line_ids:
for produce_line in produce_wizard.workorder_line_ids:
produce_line.qty_done = produce_line.qty_to_consume
produce_wizard.do_produce()

Expand Down Expand Up @@ -705,11 +705,11 @@ def test_product_produce_uom(self):
'active_id': mo.id,
'active_ids': [mo.id],
}))
produce_form.lot_id = final_product_lot
produce_form.final_lot_id = final_product_lot
product_produce = produce_form.save()
self.assertEqual(product_produce.product_qty, 1)
self.assertEqual(product_produce.qty_producing, 1)
self.assertEqual(product_produce.product_uom_id, unit, 'Should be 1 unit since the tracking is serial.')
product_produce.lot_id = final_product_lot.id
product_produce.final_lot_id = final_product_lot.id

product_produce.do_produce()
move_line_raw = mo.move_raw_ids.mapped('move_line_ids').filtered(lambda m: m.qty_done)
Expand Down
4 changes: 2 additions & 2 deletions addons/mrp/tests/test_procurement.py
Expand Up @@ -70,7 +70,7 @@ def test_procurement(self):
'active_id': produce_product_4.id,
'active_ids': [produce_product_4.id],
}))
produce_form.product_qty = produce_product_4.product_qty
produce_form.qty_producing = produce_product_4.product_qty
product_produce = produce_form.save()
product_produce.do_produce()
produce_product_4.post_inventory()
Expand All @@ -96,7 +96,7 @@ def test_procurement(self):
'active_id': production_product_6.id,
'active_ids': [production_product_6.id],
}))
produce_form.product_qty = production_product_6.product_qty
produce_form.qty_producing = production_product_6.product_qty
product_produce = produce_form.save()
product_produce.do_produce()
production_product_6.post_inventory()
Expand Down
5 changes: 2 additions & 3 deletions addons/mrp/tests/test_traceability.py
Expand Up @@ -70,10 +70,10 @@ def test_tracking_types_on_mo(self):
}))

if finished_product.tracking != 'serial':
produce_form.product_qty = 1
produce_form.qty_producing = 1

if finished_product.tracking != 'none':
produce_form.lot_id = self.env['stock.production.lot'].create({'name': 'Serial or Lot finished', 'product_id': finished_product.id})
produce_form.final_lot_id = self.env['stock.production.lot'].create({'name': 'Serial or Lot finished', 'product_id': finished_product.id})
produce_wizard = produce_form.save()

produce_wizard.do_produce()
Expand Down Expand Up @@ -113,4 +113,3 @@ def test_tracking_types_on_mo(self):
unfoldable,
'Parts with tracking type "%s", should have be unfoldable : %s' % (tracking, unfoldable)
)

0 comments on commit 5ef4666

Please sign in to comment.