Skip to content
Permalink
Browse files

[IMP] stock: Add hybrid procure method 'mts_then_mto'

In some occasions, products can be available in a location, but are still ignored when the MTO is set on those products.
(e.g.: When a product is returned)

To avoid this, we add a `procure_method` named `mts_then_mto`, allowing procurement to reserve the products if enough of them are available in his source location.
In order to achieve this,  we apply the MTS or the MTO `procure_method` on the moves from the procurement depending on whether or not there are enough quantities of the needed products in the source location ('All or nothing' logic).

TaskID: 1831347
  • Loading branch information...
arbaes committed Apr 10, 2019
1 parent 5eaa4f2 commit 224edbc8dbd1bc0985a12fee80c8a9c474ab74cc
@@ -497,4 +497,96 @@ def test_07_forced_qties(self):
})
so1.picking_ids.button_validate()
self.assertEqual(so1.picking_ids.state, 'done')
self.assertEqual(so1.order_line.mapped('qty_delivered'), [1, 1, 1])
self.assertEqual(so1.order_line.mapped('qty_delivered'), [1, 1, 1])

def test_mtso_mto(self):
""" Make a sale order for 5 products when there are only 4 in stock then
check that MTO is applied on the moves when the rule is set to 'mts_then_mto'
"""
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1)
warehouse.delivery_steps = 'pick_pack_ship'
partner_demo_customer = self.env.ref('base.res_partner_1')
final_location = partner_demo_customer.property_stock_customer
product_a = self.env['product.product'].create({
'name': 'ProductA',
'type': 'product',
})

self.env['stock.quant']._update_available_quantity(product_a, warehouse.wh_output_stock_loc_id, 4.0)

# We set quantities in the stock location to avoid warnings
# triggered by '_onchange_product_id_check_availability'
self.env['stock.quant']._update_available_quantity(product_a, warehouse.lot_stock_id, 4.0)
values = {'warehouse_id': warehouse}

# We alter one rule and we set it to 'mts_then_mto'
rule = self.env['procurement.group']._get_rule(product_a, final_location, values)
rule.procure_method = 'mts_then_mto'

# We create an SO, then validate it to trigger a procurement
f = Form(self.env['sale.order'])
f.partner_id = partner_demo_customer
f.warehouse_id = warehouse
with f.order_line.new() as line:
line.product_id = product_a
line.product_uom_qty = 5.0

so = f.save()
so.action_confirm()

qty_available = self.env['stock.quant']._get_available_quantity(product_a, warehouse.wh_output_stock_loc_id)

# 3 pickings shloud be created.
self.assertEquals(len(so.picking_ids), 3)
for picking in so.picking_ids:
# Only the picking from Stock to Pack should be MTS
if picking.location_id == warehouse.lot_stock_id:
self.assertEquals(picking.move_lines.procure_method, 'make_to_stock')
else:
self.assertEquals(picking.move_lines.procure_method, 'make_to_order')

# TODO: To update if we change the 'all or nothing' mechanism
self.assertEquals(len(picking.move_lines), 1)
self.assertEquals(picking.move_lines.product_uom_qty, 5, 'The quantity of the move should be the same as on the SO')
self.assertEqual(qty_available, 4, 'The 4 products should still be available')

def test_mtso_mts(self):
""" Make a sale order for 4 products when there are 4 in stock then
check that MTS is applied on the moves when the rule is set to 'mts_then_mto'
"""
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1)
warehouse.delivery_steps = 'pick_pack_ship'
partner_demo_customer = self.env.ref('base.res_partner_1')
final_location = partner_demo_customer.property_stock_customer
product_a = self.env['product.product'].create({
'name': 'ProductA',
'type': 'product',
})

self.env['stock.quant']._update_available_quantity(product_a, warehouse.wh_output_stock_loc_id, 4.0)
values = {'warehouse_id': warehouse}

# We alter one rule and we set it to 'mts_then_mto'
rule = self.env['procurement.group']._get_rule(product_a, final_location, values)
rule.procure_method = 'mts_then_mto'

# We create an SO, then validate it to trigger a procurement
f = Form(self.env['sale.order'])
f.partner_id = partner_demo_customer
f.warehouse_id = warehouse
with f.order_line.new() as line:
line.product_id = product_a
line.product_uom_qty = 4.0

so = f.save()
so.action_confirm()

# A picking shloud be created with its move having MTS as procure method.
self.assertEquals(len(so.picking_ids), 1)
picking = so.picking_ids
self.assertEquals(picking.move_lines.procure_method, 'make_to_stock')
self.assertEquals(len(picking.move_lines), 1)
self.assertEquals(picking.move_lines.product_uom_qty, 4)

qty_available = self.env['stock.quant']._get_available_quantity(product_a, warehouse.wh_output_stock_loc_id)
self.assertEqual(qty_available, 0, 'The 4 products should be reserved')
@@ -44,10 +44,13 @@ class StockRule(models.Model):
route_id = fields.Many2one('stock.location.route', 'Route', required=True, ondelete='cascade')
procure_method = fields.Selection([
('make_to_stock', 'Take From Stock'),
('make_to_order', 'Trigger Another Rule')], string='Move Supply Method',
('make_to_order', 'Trigger Another Rule'),
('mts_then_mto', 'Take From Stock, if Unavailable, Trigger Another Rule')], string='Move Supply Method',
default='make_to_stock', required=True,
help="""Create Procurement: A procurement will be created in the source location and the system will try to find a rule to resolve it. The available stock will be ignored.
Take from Stock: The products will be taken from the available stock.""")
help="Take From Stock: the products will be taken from the available stock of the source location.\n"
"Trigger Another Rule: the system will try to find a stock rule to bring the products in the source location. The available stock will be ignored.\n"
"Take From Stock, if Unavailable, Trigger Another Rule: the products will be taken from the available stock of the source location."
"If there is no stock available, the system will try to find a rule to bring the products in the source location.")
route_sequence = fields.Integer('Route Sequence', related='route_id.sequence', store=True, readonly=False)
picking_type_id = fields.Many2one(
'stock.picking.type', 'Operation Type',
@@ -109,6 +112,8 @@ def _get_message_dict(self):
suffix = ""
if self.procure_method == 'make_to_order' and self.location_src_id:
suffix = _("<br>A need is created in <b>%s</b> and a rule will be triggered to fulfill it.") % (source)
if self.procure_method == 'mts_then_mto' and self.location_src_id:
suffix = _("<br>If the products are not available in <b>%s</b>, a rule will be triggered to bring products in this location.") % source
message_dict = {
'pull': _('When products are needed in <b>%s</b>, <br/> <b>%s</b> are created from <b>%s</b> to fulfill the need.') % (destination, operation, source) + suffix,
'push': _('When products arrive in <b>%s</b>, <br/> <b>%s</b> are created to send them in <b>%s</b>.') % (source, operation, destination)
@@ -167,6 +172,21 @@ def _push_prepare_move_copy_values(self, move_to_copy, new_date):
}
return new_move_vals

def _get_procure_method(self, product, qty_needed, location):
""" When the procure_method of the rule is 'mts_then_mto', we need to get the quantity available to know if
we have to apply MTS or MTO on the moves of the procurement.
:param product: The product of the move
:param qty_needed: The amount needed of that product
:param location: The location source of the product
:return: The procure_method to apply on the move
"""
if self.procure_method == 'mts_then_mto':
qty_available = product.with_context(location=location.id).virtual_available
if float_compare(qty_needed, qty_available, precision_rounding=product.uom_id.rounding) <= 0:
return 'make_to_stock'
return 'make_to_order'
return self.procure_method

@api.model
def _run_pull(self, procurements):
moves_values_by_company = defaultdict(list)
@@ -219,7 +239,7 @@ def _get_stock_move_values(self, product_id, product_qty, product_uom, location_
'location_dest_id': location_id.id,
'move_dest_ids': values.get('move_dest_ids', False) and [(4, x.id) for x in values['move_dest_ids']] or [],
'rule_id': self.id,
'procure_method': self.procure_method,
'procure_method': self._get_procure_method(product_id, product_qty, self.location_src_id),
'origin': origin,
'picking_type_id': self.picking_type_id.id,
'group_id': group_id,
@@ -335,7 +335,7 @@ def _get_global_route_rules_values(self):
'depends': ['delivery_steps'],
'create_values': {
'active': True,
'procure_method': 'make_to_order',
'procure_method': 'mts_then_mto',
'company_id': self.company_id.id,
'action': 'pull',
'auto': 'manual',

0 comments on commit 224edbc

Please sign in to comment.
You can’t perform that action at this time.