How to forbid Manufacturing Order when Real stock does not satisfy?

Hi

For this MO there is missing two stock items for product 10226 when producing 1 unit. When this happens, we want to forbid (or atleast warn) the MO to be created. Right now we can create MO, but since it cannot be produced in reality I want to stop before creating MO.

Is there an option for this? So Dolibarr checks stock levels before allowing to create MO?

There is no such option in 19.0.2.

But just checking the stock level is not enough in my opinion.
It would also have to be checked for S/N == 1, S/N <> 0 (NULL) and whether the S/N is available in the warehouse.
Further tests if required

But it’s not that difficult to program

For each line in the mrp

  1. get warehouse for selected S/N
  2. get qty on stock for selected product
  3. check for mismatch
  4. display on screen and increment $error

Hello, here’s my proposed “solution”. As I’ve been working for a UK client for the past two weeks on the very topic of BOM and MO, I felt “brave” enough to try something in this regard. :sunglasses:

Disclaimer: The only drawback is that the solution I’m providing is a bit “raw”. I mean that you’ll need to modify a Dolibarr PHP file, so it will be lost in your next update (i.e., you’ll have to apply the same change again). However, I’m going to propose it in the official Github repository so they can add it to the next version (@eldy ).

What I’ve done is very simple: add the following code to the file: /htdocs/mrp/mo_card.php above the lines (#228):

// Create MO with Childs
	if ($action == 'add' && empty($id) && !empty($TBomLineId)) {

you must paste this:


if (!function_exists('f_query')) {
	function f_query($sql, $index_field = '')
	{
		global $db;

		$elements = array();
		$resql=$db->query($sql);
		if ($resql) {
			if ($db->num_rows($resql)) {
				while ($row = $db->fetch_array($resql)) {
					if (is_array($row)) {
						if (!empty($index_field) && isset($row[$index_field])) {
							$elements[$row[$index_field]] = $row;
						} else {
							$elements[] = $row;
						}
					}
				}
			}
			$db->free($resql);
			return $elements;
		} else {
			return "Error ".$db->lasterror();
			dol_syslog(get_class($this)."::query ERROR: ".$db->lasterror(), LOG_ERR);
		}
	}
}	
if (!function_exists('_is_there_enough_stock')) {
	function _is_there_enough_stock(){
		// Get BOM id and quantity to produce
		if (empty($_POST['fk_bom']) || empty($_POST['qty'])) return;
		$fk_bom = GETPOST('fk_bom', 'alpha');
		$qty    = GETPOST('qty', 'alpha');

		// Get BOM quantity produced
		$sql = "SELECT rowid,qty
					FROM ".MAIN_DB_PREFIX."bom_bom
					WHERE rowid=".$fk_bom;
		$elements = f_query($sql, 'rowid');
		if (!is_array($elements) || !isset($elements[$fk_bom])) return;
		$bom = $elements[$fk_bom];

		// Get BOM subproducts (not services) and stocks
		$sql = "SELECT ".MAIN_DB_PREFIX."bom_bomline.qty,".MAIN_DB_PREFIX."bom_bomline.fk_product,
					".MAIN_DB_PREFIX."bom_bomline.qty_frozen,
					".MAIN_DB_PREFIX."product.ref,".MAIN_DB_PREFIX."product.label ,
					".MAIN_DB_PREFIX."product.stock, ".MAIN_DB_PREFIX."product.fk_product_type  
					FROM ".MAIN_DB_PREFIX."bom_bomline JOIN ".MAIN_DB_PREFIX."product 
					ON ".MAIN_DB_PREFIX."bom_bomline.fk_product=".MAIN_DB_PREFIX."product.rowid 
					WHERE ".MAIN_DB_PREFIX."bom_bomline.fk_bom=".$fk_bom." 
					AND ".MAIN_DB_PREFIX."product.fk_product_type='0'";
		$bomlines = f_query($sql, 'fk_product');
		if (!is_array($bomlines) || count($bomlines)==0) return;

		// check stocks depending of quantity
		$without_stock = array();
		$qty_to_produce = floatval($qty);
		$qty_per_BOM    = floatval($bom['qty']);
		foreach ($bomlines as $prod) {
			if (empty($prod['qty_frozen'])) {
				$qty_needed = $qty_to_produce * (floatval($prod['qty']) / $qty_per_BOM);
			} else {
				$qty_needed = floatval($prod['qty']);
			}
			if ($qty_needed > floatval($prod['stock'])) {
				$without_stock[] = "Missing ".round($qty_needed - floatval($prod['stock']),1)
									." units stock of ".$prod['label']." [".$prod['ref']."]. ";
			}
		}
		
		// if it's missing stock for any product
		if (count($without_stock)) {
			setEventMessage(implode('<br />', $without_stock),  'errors');
			header("Location: ".dol_buildpath('/mrp/mo_card.php?action=create&fk_bom='.((int) $fk_bom), 1).'&qty='.$qty);
			exit;
		}
	}
}
if ($action == 'add') {
	_is_there_enough_stock();
}

It took me about 3 hours, but it finally worked correctly:

  1. When you try to create a new MO, this stock check will be executed
  2. And if any of the required products doesn’t have sufficient stock, the system will return you to the MO creation page, displaying an error message that lists the missing stock units of the products in short supply.

Example of use on my dev instance:
imagen

If you have any questions, let me know. Don’t worry about this “patch”… it only makes two database queries and performs some calculations comparing units to be consumed and physical stock, showing an error message if necessary and interrupting the MO creation before it even begins.

Best regards.

By the way, I’ve added now this improvement as an activatable option in my Stocktransfers module, available on Dolistore. This year, I’ve already added 4 functionalities that I’ve called “bonus” features, which are not directly related to the module itself, but are connected to products and stock.

An “easy” and RELIABLE way to add modifications to Dolibarr without losing them during updates is to add them to a third-party module to be executed by the various “hooks” that exist throughout Dolibarr. In any list and/or object card in Dolibarr, there are one or more hooks that allow us developers to “intervene” at that moment to add or modify information on the screen, or even interfere with or alter native Dolibarr actions, or add new ones when certain events occur.

If you ask me, it’s a beautiful way to allow people to customize the ERP to the fullest extent, according to their needs.