Odoo MTO with Stock Check – Customize MTO to Consider Available Inventory

Odoo MTO with Stock Check – Customize MTO to Consider Available Inventory

Here’s your improved and properly formatted version:


Odoo MTO with Stock Check – Customize MTO to Consider Available Inventory

Overview

By default, the Make to Order (MTO) route in Odoo does not consider available stock — it creates a manufacturing order (MO) or purchase order for the full sales order quantity.

If you want to consume existing inventory first and manufacture only the shortfall, you’ll need to customize the procurement rule logic or use a custom route.


Example Scenario

You have 100 pcs of finished goods (tables) in stock.
You create a Sales Order for 500 pcs.
The product is configured with the MTO + Manufacture route.

🔹 Expected Result:
MO should be created only for 400 pcs, because 100 pcs are already available.


Goal

  • Stock available: 100 pcs
  • Sales Order quantity: 500 pcs
  • MO generated: 400 pcs

Implementation Steps

Step 1: Create the Module Skeleton

./odoo-bin scaffold mto_stock_check addons

Directory structure:

mto_stock_check/
├── __init__.py
├── __manifest__.py
└── models/
    ├── __init__.py
    └── stock_rule.py

Step 2: Custom Logic in stock_rule.py


from odoo import models, api, _
from odoo.exceptions import UserError
import logging

_logger = logging.getLogger(__name__)

class StockRule(models.Model):
	_inherit = 'stock.rule'

	def is_root_level_product(self, product_id):
		# Check if this product is used as a component in any BoM
		component_in_bom = self.env['mrp.bom.line'].search_count([
			('product_id', '=', product_id.id)
		])
		return component_in_bom == 0

	@api.multi
	def _run_manufacture(self, product_id, product_qty, product_uom, location_id, name, origin, values):
		# check product_id is in bom root level or not
		available_qty= 0
		if self.is_root_level_product(product_id): 
			available_qty = product_id.qty_available

		# Ensure product_id is a record
		if not isinstance(product_id, models.BaseModel):
			product_id = self.env['product.product'].browse(product_id)
		
		# Calculate quantity to manufacture (ignore BOM component stock)
		if available_qty:
			adjusted_qty = max(0, product_qty - available_qty)
		else:
			adjusted_qty = product_qty

		# If no manufacturing needed, return
		if adjusted_qty <= 0:
			return True

		# Get BoM - use same logic as original
		bom = self._get_matching_bom(product_id, values)
		if not bom:
			msg = _('No Bill of Material found for product: %s') % product_id.display_name
			_logger.error(msg)
			raise UserError(msg)

		# Create manufacturing order for the adjusted quantity
		Production = self.env['mrp.production'].sudo().with_context(
			force_company=values['company_id'].id
		)
		
		mo_vals = self._prepare_mo_vals(
			product_id, 
			adjusted_qty, 
			product_uom, 
			location_id, 
			name, 
			origin, 
			values, 
			bom
		)
		
		production = Production.create(mo_vals)

		# Link to orderpoint/origin if exists
		orderpoint = values.get('orderpoint_id')
		if orderpoint:
			production.message_post_with_view(
				'mail.message_origin_link',
				values={'self': production, 'origin': orderpoint},
				subtype_id=self.env.ref('mail.mt_note').id
			)
			
		origin_production = values.get('move_dest_ids') and values['move_dest_ids'][0].raw_material_production_id
		if origin_production:
			production.message_post_with_view(
				'mail.message_origin_link',
				values={'self': production, 'origin': origin_production},
				subtype_id=self.env.ref('mail.mt_note').id
			)
			
		return True

Step 3: __init__.py in the models/ Folder

from . import stock_rule

Step 4: __manifest__.py in the Root Module Folder

{
    'name': "MTO with Stock Check",
    'summary': "MTO route that considers available stock before triggering MO",
    'author': "My Company",
    'website': "https://genkiware.com",
    'category': 'Manufacturing',
    'version': '1.0',
    'depends': ['mrp', 'stock'],
    'data': [],
    'installable': True,
}

There are no reviews yet.

Leave a Reply

Your email address will not be published. Required fields are marked *


The reCAPTCHA verification period has expired. Please reload the page.



Start typing and press Enter to search

slot gacor dana slot deposit dana