From 8a00109baf27c38243ee776a411a24f616d3dd15 Mon Sep 17 00:00:00 2001
From: Laurent Destailleur <eldy@destailleur.fr>
Date: Sun, 1 Mar 2015 16:04:34 +0100
Subject: [PATCH] Fix: Several bugs int replenishment feature.

---
 htdocs/commande/list.php                      | 13 ++--
 htdocs/fourn/commande/dispatch.php            |  4 +-
 htdocs/install/mysql/migration/repair.sql     |  1 +
 htdocs/langs/en_US/stocks.lang                |  2 +-
 htdocs/product/class/product.class.php        | 15 ++--
 .../product/stock/lib/replenishment.lib.php   | 74 ++++++++++++-------
 htdocs/product/stock/replenish.php            | 74 ++++++++-----------
 htdocs/product/stock/replenishorders.php      | 32 ++++----
 8 files changed, 117 insertions(+), 98 deletions(-)

diff --git a/htdocs/commande/list.php b/htdocs/commande/list.php
index 0de3c54e1ba..59df993b686 100644
--- a/htdocs/commande/list.php
+++ b/htdocs/commande/list.php
@@ -361,19 +361,22 @@ if ($resql)
                     // stock order and stock order_supplier
                     $stock_order=0;
                     $stock_order_supplier=0;
-                    if (! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)) {
-                        if (! empty($conf->commande->enabled)) {
+                    if (! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT))
+                    {
+                        if (! empty($conf->commande->enabled))
+                        {
                             if (empty($productstat_cache[$generic_commande->lines[$lig]->fk_product]['stats_order_customer'])) {
-                                $generic_product->load_stats_commande(0,'1,2',true);
+                                $generic_product->load_stats_commande(0,'1,2');
                                 $productstat_cache[$generic_commande->lines[$lig]->fk_product]['stats_order_customer'] = $generic_product->stats_commande['qty'];
                             } else {
                                 $generic_product->stats_commande['qty'] = $productstat_cache[$generic_commande->lines[$lig]->fk_product]['stats_order_customer'];
                             }
                             $stock_order=$generic_product->stats_commande['qty'];
                         }
-                        if (! empty($conf->fournisseur->enabled)) {
+                        if (! empty($conf->fournisseur->enabled))
+                        {
                             if (empty($productstat_cache[$generic_commande->lines[$lig]->fk_product]['stats_order_supplier'])) {
-                                $generic_product->load_stats_commande_fournisseur(0,'3',true);
+                                $generic_product->load_stats_commande_fournisseur(0,'3');
                                 $productstat_cache[$generic_commande->lines[$lig]->fk_product]['stats_order_supplier'] = $generic_product->stats_commande_fournisseur['qty'];
                             } else {
                                 $generic_product->stats_commande_fournisseur['qty'] = $productstat_cache[$generic_commande->lines[$lig]->fk_product]['stats_order_supplier'];
diff --git a/htdocs/fourn/commande/dispatch.php b/htdocs/fourn/commande/dispatch.php
index c88886de649..9874f809673 100644
--- a/htdocs/fourn/commande/dispatch.php
+++ b/htdocs/fourn/commande/dispatch.php
@@ -387,11 +387,11 @@ if ($id > 0 || ! empty($ref))
 							print '<td align="right">';
 							if (count($listwarehouses)>1)
 							{
-								print $form->selectarray("entrepot".$suffix, $listwarehouses, '', 1, 0, 0, '', 0, 0, $disabled);
+								print $form->selectarray("entrepot".$suffix, $listwarehouses, GETPOST("entrepot".$suffix), 1, 0, 0, '', 0, 0, $disabled);
 							}
 							elseif  (count($listwarehouses)==1)
 							{
-								print $form->selectarray("entrepot".$suffix, $listwarehouses, '', 0, 0, 0, '', 0, 0, $disabled);
+								print $form->selectarray("entrepot".$suffix, $listwarehouses, GETPOST("entrepot".$suffix), 0, 0, 0, '', 0, 0, $disabled);
 							}
 							else
 							{
diff --git a/htdocs/install/mysql/migration/repair.sql b/htdocs/install/mysql/migration/repair.sql
index 9e1ca7bb6f9..c0aeda5b671 100755
--- a/htdocs/install/mysql/migration/repair.sql
+++ b/htdocs/install/mysql/migration/repair.sql
@@ -212,5 +212,6 @@ update llx_facturedet set product_type = 1 where product_type = 2;
 --update llx_commandedet as d set d.product_type = 1 where d.fk_product = 22 and d.product_type = 0;
 --update llx_facturedet as d set d.product_type = 1 where d.fk_product = 22 and d.product_type = 0;
 
+delete from llx_commande_fournisseur_dispatch where fk_commandefourndet = 0 or fk_commandefourndet IS NULL;
 
 
diff --git a/htdocs/langs/en_US/stocks.lang b/htdocs/langs/en_US/stocks.lang
index 0e3e7dd6c27..fdd6408accb 100644
--- a/htdocs/langs/en_US/stocks.lang
+++ b/htdocs/langs/en_US/stocks.lang
@@ -108,7 +108,7 @@ WarehouseForStockDecrease=The warehouse <b>%s</b> will be used for stock decreas
 WarehouseForStockIncrease=The warehouse <b>%s</b> will be used for stock increase
 ForThisWarehouse=For this warehouse
 ReplenishmentStatusDesc=This is list of all product with a stock lower than desired stock (or lower than alert value if checkbox "alert only" is checked), and suggest you to create supplier orders to fill the difference.
-ReplenishmentOrdersDesc=This is list of all opened supplier orders
+ReplenishmentOrdersDesc=This is list of all opened supplier orders including predefined products. Only opened orders with predefined products, so that may affect stocks, are visible here.
 Replenishments=Replenishments
 NbOfProductBeforePeriod=Quantity of product %s in stock before selected period (< %s)
 NbOfProductAfterPeriod=Quantity of product %s in stock after selected period (> %s)
diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php
index 0778046c431..e26f9102544 100644
--- a/htdocs/product/class/product.class.php
+++ b/htdocs/product/class/product.class.php
@@ -3024,7 +3024,9 @@ class Product extends CommonObject
 				}
 			}
 			$this->db->free($result);
-			$this->load_virtual_stock();
+
+			$this->load_virtual_stock();		// This also load stats_commande_fournisseur, ...
+
 			return 1;
 		}
 		else
@@ -3048,18 +3050,21 @@ class Product extends CommonObject
         $stock_sending_client=0;
         $stock_reception_fournisseur=0;
 
-        if (! empty($conf->commande->enabled)) {
+        if (! empty($conf->commande->enabled))
+        {
             $result=$this->load_stats_commande(0,'1,2');
             if ($result < 0) dol_print_error($db,$this->error);
             $stock_commande_client=$this->stats_commande['qty'];
         }
-        if (! empty($conf->expedition->enabled)) {
+        if (! empty($conf->expedition->enabled))
+        {
             $result=$this->load_stats_sending(0,'1,2');
             if ($result < 0) dol_print_error($db,$this->error);
             $stock_sending_client=$this->stats_expedition['qty'];
         }
-        if (! empty($conf->fournisseur->enabled)) {
-            $result=$this->load_stats_commande_fournisseur(0,'3,4');
+        if (! empty($conf->fournisseur->enabled))
+        {
+            $result=$this->load_stats_commande_fournisseur(0,'1,2,3,4');
             if ($result < 0) dol_print_error($db,$this->error);
             $stock_commande_fournisseur=$this->stats_commande_fournisseur['qty'];
 
diff --git a/htdocs/product/stock/lib/replenishment.lib.php b/htdocs/product/stock/lib/replenishment.lib.php
index 50a0d85e25c..1ef993c479d 100644
--- a/htdocs/product/stock/lib/replenishment.lib.php
+++ b/htdocs/product/stock/lib/replenishment.lib.php
@@ -25,56 +25,80 @@
 require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.commande.class.php';
 
 /**
- * dispatched
+ * Check if there is still some dispatching of stock to do.
  *
- * @param 	int	$order_id	Id of order
- * @return	boolean
+ * @param 	int		$order_id		Id of order to check
+ * @return	boolean					True = There is some dispatching to do, False = All dispatching is done (may be we receive more) or is not required
  */
-function dispatched($order_id)
+function dolDispatchToDo($order_id)
 {
     global $db;
-    $sql = 'SELECT fk_product, SUM(qty) FROM ' . MAIN_DB_PREFIX . 'commande_fournisseur_dispatch';
-    $sql .= ' WHERE fk_commande = ' . $order_id . ' GROUP BY fk_product';
-    $sql .= ' ORDER by fk_product';
-    $resql = $db->query($sql);
+
     $dispatched = array();
     $ordered = array();
-    if($resql && $db->num_rows($resql))
+
+    # Count nb of quantity dispatched per product
+    $sql = 'SELECT fk_product, SUM(qty) FROM ' . MAIN_DB_PREFIX . 'commande_fournisseur_dispatch';
+    $sql.= ' WHERE fk_commande = ' . $order_id;
+    $sql.= ' GROUP BY fk_product';
+    $sql.= ' ORDER by fk_product';
+    $resql = $db->query($sql);
+    if ($resql && $db->num_rows($resql))
     {
-        while($res = $db->fetch_object($resql))
-            $dispatched[] = $res;
+        while ($obj = $db->fetch_object($resql))
+            $dispatched[$obj->fk_product] = $obj;
     }
+
+    # Count nb of quantity to dispatch per product
     $sql = 'SELECT fk_product, SUM(qty) FROM ' . MAIN_DB_PREFIX . 'commande_fournisseurdet';
-    $sql .= ' WHERE fk_commande = ' . $order_id . ' GROUP BY fk_product';
-    $sql .= ' ORDER by fk_product';
+    $sql.= ' WHERE fk_commande = ' . $order_id;
+    $sql.= ' AND fk_product > 0';
+    if (empty($conf->global->STOCK_SUPPORTS_SERVICES)) $sql.= ' AND product_type = 0';
+    $sql.= ' GROUP BY fk_product';
+    $sql.= ' ORDER by fk_product';
     $resql = $db->query($sql);
-    if($resql && $db->num_rows($resql)) {
-        while($res = $db->fetch_object($resql))
-            $ordered[] = $res;
+    if ($resql && $db->num_rows($resql))
+    {
+        while ($obj = $db->fetch_object($resql))
+            $ordered[$obj->fk_product] = $obj;
     }
-    return $dispatched == $ordered;
+
+    $todispatch=0;
+    foreach ($ordered as $key => $val)
+    {
+		if ($ordered[$key] > $dispatched[$key]) $todispatch++;
+    }
+
+    return ($todispatch ? true : false);
+    //return true;
 }
 
 /**
  * dispatchedOrders
  *
- * @return Ambigous <string, multitype:NULL >
+ * @return string		Array of id of orders wit all dispathing already done or not required
  */
 function dispatchedOrders()
 {
     global $db;
+
     $sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'commande_fournisseur';
     $resql = $db->query($sql);
-    $res = array();
-    if ($resql && $db->num_rows($resql) > 0) {
-        while ($obj = $db->fetch_object($resql)) {
-            if (dispatched($obj->rowid)) {
-                $res[] = $obj->rowid;
+    $resarray = array();
+    if ($resql && $db->num_rows($resql) > 0)
+    {
+        while ($obj = $db->fetch_object($resql))
+        {
+            if (! dolDispatchToDo($obj->rowid))
+            {
+                $resarray[] = $obj->rowid;
             }
         }
     }
-    if ($res) {
-        $res = '(' . implode(',', $res) . ')';
+
+    if (count($resarray))
+    {
+        $res = '(' . implode(',', $resarray) . ')';
     } else {
         //hack to make sure ordered SQL request won't syntax error
         $res = '(0)';
diff --git a/htdocs/product/stock/replenish.php b/htdocs/product/stock/replenish.php
index 35835709ab8..efb008df4ff 100644
--- a/htdocs/product/stock/replenish.php
+++ b/htdocs/product/stock/replenish.php
@@ -336,6 +336,17 @@ $head[1][0] = DOL_URL_ROOT.'/product/stock/replenishorders.php';
 $head[1][1] = $langs->trans("ReplenishmentOrders");
 $head[1][2] = 'replenishorders';
 
+
+
+print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST" name="formulaire">'.
+	'<input type="hidden" name="token" value="' .$_SESSION['newtoken'] . '">'.
+	'<input type="hidden" name="sortfield" value="' . $sortfield . '">'.
+	'<input type="hidden" name="sortorder" value="' . $sortorder . '">'.
+	'<input type="hidden" name="type" value="' . $type . '">'.
+	'<input type="hidden" name="linecount" value="' . $num . '">'.
+	'<input type="hidden" name="action" value="order">'.
+	'<input type="hidden" name="mode" value="' . $mode . '">';
+
 dol_fiche_head($head, 'replenish', $langs->trans('Replenishment'), 0, 'stock');
 
 print $langs->trans("ReplenishmentStatusDesc").'<br>'."\n";
@@ -386,16 +397,7 @@ if ($sref || $snom || $sall || $salert || GETPOST('search', 'alpha')) {
 	);
 }
 
-print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST" name="formulaire">'.
-	'<input type="hidden" name="token" value="' .$_SESSION['newtoken'] . '">'.
-	'<input type="hidden" name="sortfield" value="' . $sortfield . '">'.
-	'<input type="hidden" name="sortorder" value="' . $sortorder . '">'.
-	'<input type="hidden" name="type" value="' . $type . '">'.
-	'<input type="hidden" name="linecount" value="' . $num . '">'.
-	'<input type="hidden" name="action" value="order">'.
-	'<input type="hidden" name="mode" value="' . $mode . '">'.
-
-	'<table class="liste" width="100%">';
+print '<table class="liste" width="100%">';
 
 $param = (isset($type)? '&type=' . $type : '');
 $param .= '&fourn_id=' . $fourn_id . '&snom='. $snom . '&salert=' . $salert;
@@ -467,7 +469,6 @@ while ($i < ($limit ? min($num, $limit) : $num))
 				if (!empty($objtp->label)) $objp->label = $objtp->label;
 			}
 		}
-		$form = new Form($db);
 		$var =! $var;
 
 		if ($usevirtualstock)
@@ -480,7 +481,13 @@ while ($i < ($limit ? min($num, $limit) : $num))
 			$stock = $prod->stock_reel;
 		}
 
-		$ordered = $prod->stats_commande_fournisseur['qty']-$prod->stats_reception['qty'];
+		// Force call prod->load_stats_xxx to choose status to count (otherwise it is loaded by load_stock function)
+		$result=$prod->load_stats_commande_fournisseur(0,'1,2,3,4');
+		$result=$prod->load_stats_reception(0,'4');
+
+		//print $prod->stats_commande_fournisseur['qty'].'<br>'."\n";
+		//print $prod->stats_reception['qty'];
+		$ordered = $prod->stats_commande_fournisseur['qty'] - $prod->stats_reception['qty'];
 
 		$warning='';
 		if ($objp->alertstock && ($stock < $objp->alertstock))
@@ -555,12 +562,6 @@ while ($i < ($limit ? min($num, $limit) : $num))
 print '</table>';
 
 
-$value=$langs->trans("CreateOrders");
-print '<div class="center"><input class="button" type="submit" name="valid" value="'.$value.'"></div>';
-
-
-print '</form>';
-
 if ($num > $conf->liste_limit)
 {
 	if ($sref || $snom || $sall || $salert || GETPOST('search', 'alpha'))
@@ -569,36 +570,16 @@ if ($num > $conf->liste_limit)
 		$filters .= '&sall=' . $sall;
 		$filters .= '&salert=' . $salert;
 		$filters .= '&mode=' . $mode;
-		print_barre_liste(
-			'',
-			$page,
-			'replenish.php',
-			$filters,
-			$sortfield,
-			$sortorder,
-			'',
-			$num,
-			0,
-			''
-		);
-	} else {
+		print_barre_liste('', $page, 'replenish.php', $filters, $sortfield, $sortorder, '', $num, 0, '');
+	}
+	else
+	{
 		$filters = '&sref=' . $sref . '&snom=' . $snom;
 		$filters .= '&fourn_id=' . $fourn_id;
 		$filters .= (isset($type)? '&type=' . $type : '');
 		$filters .= '&salert=' . $salert;
 		$filters .= '&mode=' . $mode;
-		print_barre_liste(
-			'',
-			$page,
-			'replenish.php',
-			$filters,
-			$sortfield,
-			$sortorder,
-			'',
-			$num,
-			0,
-			''
-		);
+		print_barre_liste('', $page, 'replenish.php', $filters, $sortfield, $sortorder, '', $num, 0, '');
 	}
 }
 
@@ -607,6 +588,13 @@ $db->free($resql);
 dol_fiche_end();
 
 
+$value=$langs->trans("CreateOrders");
+print '<div class="center"><input class="button" type="submit" name="valid" value="'.$value.'"></div>';
+
+
+print '</form>';
+
+
 // TODO Replace this with jquery
 print '
 <script type="text/javascript">
diff --git a/htdocs/product/stock/replenishorders.php b/htdocs/product/stock/replenishorders.php
index 71fe1407a2d..db450a09f6e 100644
--- a/htdocs/product/stock/replenishorders.php
+++ b/htdocs/product/stock/replenishorders.php
@@ -45,6 +45,8 @@ $result=restrictedArea($user,'produit|service');
  * View
  */
 
+$form = new Form($db);
+
 $helpurl = 'EN:Module_Stocks_En|FR:Module_Stock|ES:M&oacute;dulo_Stocks';
 $texte = $langs->trans('ReplenishmentOrders');
 
@@ -90,7 +92,7 @@ $sql.= ' AND cf.entity = ' . $conf->entity;
 if ($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER) {
     $sql .= ' AND cf.fk_statut < 3';
 } elseif ($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER) {
-    $sql .= ' AND cf.fk_statut < 6';
+    $sql .= ' AND cf.fk_statut < 6';	// We want alos status 5, we will keep them visible if dispatching is not yet finished (tested with function dolDispatchToDo).
 } else {
     $sql .= ' AND cf.fk_statut < 5';
 }
@@ -143,6 +145,8 @@ $sql .= ' GROUP BY cf.rowid, cf.ref, cf.date_creation, cf.fk_statut';
 $sql .= ', cf.total_ttc, cf.fk_user_author, u.login, s.rowid, s.nom';
 $sql .= $db->order($sortfield, $sortorder);
 $sql .= $db->plimit($conf->liste_limit+1, $offset);
+//print $sql;
+
 $resql = $db->query($sql);
 if ($resql)
 {
@@ -151,20 +155,11 @@ if ($resql)
 
 	print $langs->trans("ReplenishmentOrdersDesc").'<br><br>';
 
-    print_barre_liste(
-    		'',
-    		$page,
-    		$_SERVER["PHP_SELF"],
-    		'',
-    		$sortfield,
-    		$sortorder,
-    		'',
-    		$num,
-    		0,
-    		''
-    );
-    print '<form action="'.$_SERVER["PHP_SELF"].'" method="GET">'.
-         '<table class="noborder" width="100%">'.
+    print_barre_liste('', $page, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, '', $num, 0, '');
+
+    print '<form action="'.$_SERVER["PHP_SELF"].'" method="GET">';
+
+    print '<table class="noborder" width="100%">'.
          '<tr class="liste_titre">';
     print_liste_field_titre(
     		$langs->trans('Ref'),
@@ -226,8 +221,8 @@ if ($resql)
     		$sortfield,
     		$sortorder
     );
-    $form = new Form($db);
     print '</tr>'.
+
          '<tr class="liste_titre">'.
          '<td class="liste_titre">'.
          '<input type="text" class="flat" name="search_ref" value="' . $sref . '">'.
@@ -258,7 +253,10 @@ if ($resql)
     {
         $obj = $db->fetch_object($resql);
         $var = !$var;
-        if (!dispatched($obj->rowid) && (!$sproduct || in_array($sproduct, getProducts($obj->rowid))))
+
+        $showline = dolDispatchToDo($obj->rowid) && (!$sproduct || in_array($sproduct, getProducts($obj->rowid)));
+
+        if ($showline)
         {
             $href = DOL_URL_ROOT . '/fourn/commande/card.php?id=' . $obj->rowid;
             print '<tr ' . $bc[$var] . '>'.
-- 
GitLab