summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
Diffstat (limited to 'addons')
-rw-r--r--addons/mrp/models/mrp_production.py20
-rw-r--r--addons/mrp/models/stock_move.py4
-rw-r--r--addons/mrp/models/stock_rule.py26
-rw-r--r--addons/sale_mrp/tests/test_sale_mrp_flow.py168
-rw-r--r--addons/stock/models/stock_move.py25
5 files changed, 205 insertions, 38 deletions
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 10ac3e19112..c6ca7dd25d0 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -514,24 +514,6 @@ class MrpProduction(models.Model):
return self.location_src_id
@api.multi
- def _adjust_procure_method(self):
- try:
- mto_route = self.env['stock.warehouse']._find_global_route('stock.route_warehouse0_mto', _('Make To Order'))
- except:
- mto_route = False
- for move in self.move_raw_ids:
- product = move.product_id
- routes = product.route_ids + product.route_from_categ_ids + move.warehouse_id.route_ids
- # TODO: optimize with read_group?
- pull = self.env['stock.rule'].search([('route_id', 'in', [x.id for x in routes]), ('location_src_id', '=', move.location_id.id),
- ('location_id', '=', move.location_dest_id.id), ('action', '!=', 'push')], limit=1)
- if pull and (pull.procure_method == 'make_to_order'):
- move.procure_method = pull.procure_method
- elif not pull: # If there is no make_to_stock rule either
- if mto_route and mto_route.id in [x.id for x in routes]:
- move.procure_method = 'make_to_order'
-
- @api.multi
def _update_raw_move(self, bom_line, line_data):
""" :returns update_move, old_quantity, new_quantity """
quantity = line_data['qty']
@@ -581,7 +563,7 @@ class MrpProduction(models.Model):
'unit_factor': move_raw.product_uom_qty / production.product_qty
})
production._generate_finished_moves()
- production._adjust_procure_method()
+ production.move_raw_ids._adjust_procure_method()
(production.move_raw_ids | production.move_finished_ids)._action_confirm()
return True
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index 096074b8157..2234d34f5f4 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -201,7 +201,9 @@ class StockMove(models.Model):
def _generate_move_phantom(self, bom_line, product_qty, quantity_done):
if bom_line.product_id.type in ['product', 'consu']:
- return self.copy(default=self._prepare_phantom_move_values(bom_line, product_qty, quantity_done))
+ move = self.copy(default=self._prepare_phantom_move_values(bom_line, product_qty, quantity_done))
+ move._adjust_procure_method()
+ return move
return self.env['stock.move']
def _generate_consumed_move_line(self, qty_to_add, final_lot, lot=False):
diff --git a/addons/mrp/models/stock_rule.py b/addons/mrp/models/stock_rule.py
index 8b8a252d793..d66d1af3298 100644
--- a/addons/mrp/models/stock_rule.py
+++ b/addons/mrp/models/stock_rule.py
@@ -90,3 +90,29 @@ class StockRule(models.Model):
new_move_vals = super(StockRule, self)._push_prepare_move_copy_values(move_to_copy, new_date)
new_move_vals['production_id'] = False
return new_move_vals
+
+class ProcurementGroup(models.Model):
+ _inherit = 'procurement.group'
+
+ @api.model
+ def run(self, product_id, product_qty, product_uom, location_id, name, origin, values):
+ """ If 'run' is called on a kit, this override is made in order to call
+ the original 'run' method with the values of the components of that kit.
+ """
+ bom_kit = self.env['mrp.bom']._bom_find(product=product_id, bom_type='phantom')
+ if bom_kit:
+ order_qty = product_uom._compute_quantity(product_qty, bom_kit.product_uom_id, round=False)
+ qty_to_produce = ( order_qty / bom_kit.product_qty)
+ boms, bom_sub_lines = bom_kit.explode(product_id, qty_to_produce)
+ for bom_line, bom_line_data in bom_sub_lines:
+ procurement_uom = bom_line.product_uom_id
+ quant_uom = bom_line.product_id.uom_id
+ component_qty = bom_line_data['qty']
+ get_param = self.env['ir.config_parameter'].sudo().get_param
+ if procurement_uom.id != quant_uom.id and get_param('stock.propagate_uom') != '1':
+ component_qty = bom_line.product_uom_id._compute_quantity(bom_line_data['qty'], quant_uom, rounding_method='HALF-UP')
+ procurement_uom = quant_uom
+ super(ProcurementGroup, self).run(bom_line.product_id, component_qty, procurement_uom, location_id, name, origin, values)
+ return True
+ else:
+ return super(ProcurementGroup, self).run(product_id, product_qty, product_uom, location_id, name, origin, values)
diff --git a/addons/sale_mrp/tests/test_sale_mrp_flow.py b/addons/sale_mrp/tests/test_sale_mrp_flow.py
index 136d69d14ac..5b23d94dfb1 100644
--- a/addons/sale_mrp/tests/test_sale_mrp_flow.py
+++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py
@@ -5,6 +5,7 @@ from datetime import datetime
from odoo.tests import common, Form
from odoo.exceptions import UserError
+from odoo.tools import mute_logger
class TestSaleMrpFlow(common.TransactionCase):
@@ -25,19 +26,6 @@ class TestSaleMrpFlow(common.TransactionCase):
self.stock_location = self.env.ref('stock.stock_location_stock')
self.warehouse = self.env.ref('stock.warehouse0')
- def test_00_sale_mrp_flow(self):
- """ Test sale to mrp flow with diffrent unit of measure."""
- def create_product(name, uom_id, routes=()):
- p = Form(self.env['product.product'])
- p.name = name
- p.type = 'product'
- p.uom_id = uom_id
- p.uom_po_id = uom_id
- p.route_ids.clear()
- for r in routes:
- p.route_ids.add(r)
- return p.save()
-
self.uom_kg = self.env['uom.uom'].search([('category_id', '=', self.categ_kgm.id), ('uom_type', '=', 'reference')], limit=1)
self.uom_kg.write({
'name': 'Test-KG',
@@ -51,7 +39,7 @@ class TestSaleMrpFlow(common.TransactionCase):
self.uom_unit = self.env['uom.uom'].search([('category_id', '=', self.categ_unit.id), ('uom_type', '=', 'reference')], limit=1)
self.uom_unit.write({
'name': 'Test-Unit',
- 'rounding': 1.0})
+ 'rounding': 0.01})
self.uom_dozen = self.UoM.create({
'name': 'Test-DozenA',
'category_id': self.categ_unit.id,
@@ -59,14 +47,29 @@ class TestSaleMrpFlow(common.TransactionCase):
'uom_type': 'bigger',
'rounding': 0.001})
+ def create_product(self, name, uom_id, routes=()):
+ p = Form(self.env['product.product'])
+ p.name = name
+ p.type = 'product'
+ p.uom_id = uom_id
+ p.uom_po_id = uom_id
+ p.route_ids.clear()
+ for r in routes:
+ p.route_ids.add(r)
+ return p.save()
+
+ def test_00_sale_mrp_flow(self):
+ """ Test sale to mrp flow with diffrent unit of measure."""
+
+
# Create product A, B, C, D.
# --------------------------
route_manufacture = self.warehouse.manufacture_pull_id.route_id
route_mto = self.warehouse.mto_pull_id.route_id
- product_a = create_product('Product A', self.uom_unit, routes=[route_manufacture, route_mto])
- product_c = create_product('Product C', self.uom_kg)
- product_b = create_product('Product B', self.uom_dozen, routes=[route_manufacture, route_mto])
- product_d = create_product('Product D', self.uom_unit, routes=[route_manufacture, route_mto])
+ product_a = self.create_product('Product A', self.uom_unit, routes=[route_manufacture, route_mto])
+ product_c = self.create_product('Product C', self.uom_kg)
+ product_b = self.create_product('Product B', self.uom_dozen, routes=[route_manufacture, route_mto])
+ product_d = self.create_product('Product D', self.uom_unit, routes=[route_manufacture, route_mto])
# ------------------------------------------------------------------------------------------
# Bill of materials for product A, B, D.
@@ -489,3 +492,132 @@ class TestSaleMrpFlow(common.TransactionCase):
# Check that the cost of Good Sold entries are equal to 2* (2 * 20 + 1 * 10) = 100
self.assertEqual(aml_expense.debit, 100, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_output.credit, 100, "Cost of Good Sold entry missing or mismatching")
+
+ @mute_logger('odoo.tests.common.onchange')
+ def test_10_sale_mrp_kits_routes(self):
+
+ # Create a kit 'kit_1' :
+ # -----------------------
+ #
+ # kit_1 --|- component_shelf1 x3
+ # |- component_shelf2 x2
+
+ kit_1 = self.create_product('Kit1', self.uom_unit)
+ component_shelf1 = self.create_product('Comp Shelf1', self.uom_unit)
+ component_shelf2 = self.create_product('Comp Shelf2', self.uom_unit)
+
+ with Form(self.env['mrp.bom']) as bom:
+ bom.product_tmpl_id = kit_1.product_tmpl_id
+ bom.product_qty = 1
+ bom.product_uom_id = self.uom_unit
+ bom.type = 'phantom'
+ with bom.bom_line_ids.new() as line:
+ line.product_id = component_shelf1
+ line.product_qty = 3
+ line.product_uom_id = self.uom_unit
+ with bom.bom_line_ids.new() as line:
+ line.product_id = component_shelf2
+ line.product_qty = 2
+ line.product_uom_id = self.uom_unit
+
+ # Creating 2 specific routes for each of the components of the kit
+ route_shelf1 = self.env['stock.location.route'].create({
+ 'name': 'Shelf1 -> Customer',
+ 'product_selectable': True,
+ 'rule_ids': [(0, 0, {
+ 'name': 'Shelf1 -> Customer',
+ 'action': 'pull',
+ 'picking_type_id': self.ref('stock.picking_type_in'),
+ 'location_src_id': self.ref('stock.stock_location_components'),
+ 'location_id': self.ref('stock.stock_location_customers'),
+ })],
+ })
+
+ route_shelf2 = self.env['stock.location.route'].create({
+ 'name': 'Shelf2 -> Customer',
+ 'product_selectable': True,
+ 'rule_ids': [(0, 0, {
+ 'name': 'Shelf2 -> Customer',
+ 'action': 'pull',
+ 'picking_type_id': self.ref('stock.picking_type_in'),
+ 'location_src_id': self.ref('stock.stock_location_14'),
+ 'location_id': self.ref('stock.stock_location_customers'),
+ })],
+ })
+
+ component_shelf1.write({
+ 'route_ids': [(4, route_shelf1.id)]})
+ component_shelf2.write({
+ 'route_ids': [(4, route_shelf2.id)]})
+
+ # Creating a sale order for 5 kits and confirming it
+ order_form = Form(self.env['sale.order'])
+ order_form.partner_id = self.env.ref('base.res_partner_2')
+ with order_form.order_line.new() as line:
+ line.product_id = kit_1
+ line.product_uom = self.uom_unit
+ line.product_uom_qty = 5
+ order = order_form.save()
+ order.action_confirm()
+
+ # Now we check that the routes of the components were applied, in order to make sure the routes set
+ # on the kit itself are ignored
+ self.assertEquals(len(order.picking_ids), 2)
+ self.assertEquals(len(order.picking_ids[0].move_lines), 1)
+ self.assertEquals(len(order.picking_ids[1].move_lines), 1)
+ moves = order.picking_ids.mapped('move_lines')
+ move_shelf1 = moves.filtered(lambda m: m.product_id == component_shelf1)
+ move_shelf2 = moves.filtered(lambda m: m.product_id == component_shelf2)
+ self.assertEquals(move_shelf1.location_id.id, self.ref('stock.stock_location_components'))
+ self.assertEquals(move_shelf1.location_dest_id.id, self.ref('stock.stock_location_customers'))
+ self.assertEquals(move_shelf2.location_id.id, self.ref('stock.stock_location_14'))
+ self.assertEquals(move_shelf2.location_dest_id.id, self.ref('stock.stock_location_customers'))
+
+ @mute_logger('odoo.tests.common.onchange')
+ def test_11_sale_mrp_explode_kits_uom_quantities(self):
+
+ # Create a kit 'kit_1' :
+ # -----------------------
+ #
+ # 2x Dozens kit_1 --|- component_unit x6 Units
+ # |- component_kg x7 Kg
+
+ kit_1 = self.create_product('Kit1', self.uom_unit)
+ component_unit = self.create_product('Comp Unit', self.uom_unit)
+ component_kg = self.create_product('Comp Kg', self.uom_kg)
+
+ with Form(self.env['mrp.bom']) as bom:
+ bom.product_tmpl_id = kit_1.product_tmpl_id
+ bom.product_qty = 2
+ bom.product_uom_id = self.uom_dozen
+ bom.type = 'phantom'
+ with bom.bom_line_ids.new() as line:
+ line.product_id = component_unit
+ line.product_qty = 6
+ line.product_uom_id = self.uom_unit
+ with bom.bom_line_ids.new() as line:
+ line.product_id = component_kg
+ line.product_qty = 7
+ line.product_uom_id = self.uom_kg
+
+ # Creating a sale order for 3 Units of kit_1 and confirming it
+ order_form = Form(self.env['sale.order'])
+ order_form.partner_id = self.env.ref('base.res_partner_2')
+ with order_form.order_line.new() as line:
+ line.product_id = kit_1
+ line.product_uom = self.uom_unit
+ line.product_uom_qty = 2
+ order = order_form.save()
+ order.action_confirm()
+
+ # Now we check that the routes of the components were applied, in order to make sure the routes set
+ # on the kit itself are ignored
+ self.assertEquals(len(order.picking_ids), 1)
+ self.assertEquals(len(order.picking_ids[0].move_lines), 2)
+
+ # Finally, we check the quantities for each component on the picking
+ move_component_unit = order.picking_ids[0].move_lines.filtered(lambda m: m.product_id == component_unit)
+ move_component_kg = order.picking_ids[0].move_lines - move_component_unit
+ self.assertEquals(move_component_unit.product_uom_qty, 0.5)
+ self.assertEquals(move_component_kg.product_uom_qty, 0.583)
+
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 4c02adf6400..9260b906289 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -1268,3 +1268,28 @@ class StockMove(models.Model):
vals = self._prepare_move_line_vals(quantity=0)
vals['qty_done'] = qty
ml = self.env['stock.move.line'].create(vals)
+
+ def _adjust_procure_method(self):
+ """ This method will try to apply the procure method MTO on some moves if
+ a compatible MTO route is found. Else the procure method will be set to MTS
+ """
+ try:
+ mto_route = self.env['stock.warehouse']._find_global_route('stock.route_warehouse0_mto',
+ 'Make To Order')
+ except ValueError:
+ mto_route = False
+ for move in self:
+ product_id = move.product_id
+ domain = [
+ ('location_src_id', '=', move.location_id.id),
+ ('location_id', '=', move.location_dest_id.id),
+ ('action', '!=', 'push')
+ ]
+ rules = self.env['procurement.group']._search_rule(False, product_id, move.warehouse_id, domain)
+ routes = product_id.route_ids + product_id.route_from_categ_ids + move.warehouse_id.route_ids
+ if rules and (rules.procure_method == 'make_to_order'):
+ move.procure_method = rules.procure_method
+ elif mto_route and mto_route.id in [x.id for x in routes]:
+ move.procure_method = 'make_to_order'
+ else:
+ move.procure_method = 'make_to_stock'