Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMP] stock: Add hybrid procure method 'mts_else_mto' #32642

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion addons/sale_stock/tests/test_sale_stock.py
Expand Up @@ -497,4 +497,4 @@ 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])
28 changes: 24 additions & 4 deletions addons/stock/models/stock_rule.py
Expand Up @@ -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_else_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',
Expand Down Expand Up @@ -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_else_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)
Expand Down Expand Up @@ -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_else_mto', we need to get the quantity forcasted to know if
we have to apply MTS or MTO on the moves of the procurement.
:param product: The product of the move
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newline

: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_else_mto':
qty_available = product.with_context(location=location.id).virtual_available
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't you batch virtual_available? read it for all products where the rule is mts_then_mto and pass the values there

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)
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion addons/stock/models/stock_warehouse.py
Expand Up @@ -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_else_mto',
'company_id': self.company_id.id,
'action': 'pull',
'auto': 'manual',
Expand Down
99 changes: 99 additions & 0 deletions addons/stock/tests/test_move2.py
Expand Up @@ -2049,3 +2049,102 @@ def test_push_rule_on_move_1(self):

pushed_move = move1.move_dest_ids
self.assertEqual(pushed_move.location_dest_id.id, push_location.id)

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_else_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)

pg = self.env['procurement.group'].create({'name': 'Test-pg-mtso-mto'})

self.env['procurement.group'].run([
pg.Procurement(
product_a,
5.0,
product_a.uom_id,
final_location,
'test_mtso_mto',
'test_mtso_mto',
warehouse.company_id,
{
'warehouse_id': warehouse,
'group_id': pg
}
)
])

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

# 3 pickings shloud be created.
picking_ids = self.env['stock.picking'].search([('group_id', '=', pg.id)])
self.assertEquals(len(picking_ids), 3)
for picking in 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')

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_else_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_else_mto'
rule = self.env['procurement.group']._get_rule(product_a, final_location, values)
rule.procure_method = 'mts_else_mto'

pg = self.env['procurement.group'].create({'name': 'Test-pg-mtso-mts'})

self.env['procurement.group'].run([
pg.Procurement(
product_a,
4.0,
product_a.uom_id,
final_location,
'test_mtso_mts',
'test_mtso_mts',
warehouse.company_id,
{
'warehouse_id': warehouse,
'group_id': pg
}
)
])

# A picking shloud be created with its move having MTS as procure method.
picking_ids = self.env['stock.picking'].search([('group_id', '=', pg.id)])
self.assertEquals(len(picking_ids), 1)
picking = 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)