浏览代码

NEW: Introduce option SUPPLIER_ORDER_DOUBLE_APPROVAL to allow 2
approvals to make a supplier order approved. Activating this option
introduce a new permission to the second level approval.

Laurent Destailleur 10 年之前
父节点
当前提交
d8db32a99c

+ 55 - 18
htdocs/admin/supplier_order.php

@@ -169,29 +169,57 @@ else if ($action == 'set_SUPPLIER_ORDER_OTHER')
 {
     $freetext = GETPOST('SUPPLIER_ORDER_FREE_TEXT');	// No alpha here, we want exact string
 	$doubleapproval = GETPOST('SUPPLIER_ORDER_DOUBLE_APPROVAL');
-	$doubleapprovalgroup = GETPOST('SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP') > 0 ? GETPOST('SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP') : '';
-	
+	//$doubleapprovalgroup = GETPOST('SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP') > 0 ? GETPOST('SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP') : '';
+
     $res1 = dolibarr_set_const($db, "SUPPLIER_ORDER_FREE_TEXT",$freetext,'chaine',0,'',$conf->entity);
     $res2 = dolibarr_set_const($db, "SUPPLIER_ORDER_DOUBLE_APPROVAL",$doubleapproval,'chaine',0,'',$conf->entity);
-    if (isset($_POST["SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP"]))
+    /*if (isset($_POST["SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP"]))
     {
     	$res3 = dolibarr_set_const($db, "SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP",$doubleapprovalgroup,'chaine',0,'',$conf->entity);
     }
     else
     {
     	$res3=1;
-    }
-    
-    if (! $res1 > 0 || ! $res2 > 0 || ! $res3 > 0) $error++;
+    }*/
+
+    // TODO We add/delete permission until permission can have a condition on a global var
+    $r_id = 1190;
+    $entity = $conf->entity;
+    $r_desc='Permission for second approval';
+    $r_modul='fournisseur';
+    $r_type='w';
+    $r_perms='commande';
+    $r_subperms='approve2';
+    $r_def=0;
+
+    if ($conf->global->SUPPLIER_ORDER_DOUBLE_APPROVAL)
+    {
+    	$sql = "INSERT INTO ".MAIN_DB_PREFIX."rights_def";
+    	$sql.= " (id, entity, libelle, module, type, bydefault, perms, subperms)";
+    	$sql.= " VALUES ";
+    	$sql.= "(".$r_id.",".$entity.",'".$db->escape($r_desc)."','".$r_modul."','".$r_type."',".$r_def.",'".$r_perms."','".$r_subperms."')";
 
-	if (! $error)
-	{
-		setEventMessage($langs->trans("SetupSaved"));
-	}
-	else
-	{
-		setEventMessage($langs->trans("Error"),'errors');
-	}
+    	$resqlinsert=$db->query($sql,1);
+    	if (! $resqlinsert)
+    	{
+    		if ($db->errno() != "DB_ERROR_RECORD_ALREADY_EXISTS")
+    		{
+    			setEventMessage($db->lasterror(),'errors');
+    			$error++;
+    		}
+    	}
+    }
+    else
+    {
+    	$sql = "DELETE FROM	".MAIN_DB_PREFIX."rights_def";
+    	$sql.= " WHERE id = ".$r_id;
+       	$resqldelete=$db->query($sql,1);
+    	if (! $resqldelete)
+    	{
+   			setEventMessage($db->lasterror(),'errors');
+   			$error++;
+    	}
+    }
 }
 
 
@@ -467,16 +495,25 @@ print '<td>'.$langs->trans("Parameter").'</td>';
 print '<td align="center" width="60">'.$langs->trans("Value").'</td>';
 print '<td width="80">&nbsp;</td>';
 print "</tr>\n";
-
+$var=false;
 if ($conf->global->MAIN_FEATURES_LEVEL > 0)
 {
 	print '<tr '.$bc[$var].'><td>';
-	print $langs->trans("UseDoubleApproval").'</td><td>';
-	print $form->selectyesno('SUPPLIER_ORDER_DOUBLE_APPROVAL', $conf->global->SUPPLIER_ORDER_DOUBLE_APPROVAL);
-	print '<br>'.$form->select_dolgroups($conf->global->SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP,'SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP', 1);
+	print $langs->trans("UseDoubleApproval").'<br>';
+	print $langs->trans("IfSetToYesDontForgetPermission");
+	print '</td><td>';
+	print $form->selectyesno('SUPPLIER_ORDER_DOUBLE_APPROVAL', $conf->global->SUPPLIER_ORDER_DOUBLE_APPROVAL, 1);
+	print '</td><td align="right">';
+	print '<input type="submit" class="button" value="'.$langs->trans("Modify").'">';
+	print "</td></tr>\n";
+	$var=!$var;
+	/*print '<tr '.$bc[$var].'><td>';
+	print $langs->trans("GroupOfUserForSecondApproval").'</td><td>';
+	print $form->select_dolgroups($conf->global->SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP,'SUPPLIER_ORDER_DOUBLE_APPROVAL_GROUP', 1);
 	print '</td><td align="right">';
 	print '<input type="submit" class="button" value="'.$langs->trans("Modify").'">';
 	print "</td></tr>\n";
+	$var=!$var;*/
 }
 
 print '<tr '.$bc[$var].'><td colspan="2">';

+ 5 - 5
htdocs/core/class/html.form.class.php

@@ -395,12 +395,12 @@ class Form
         $htmltext=str_replace("\n","",$htmltext);
 
         $htmltext=str_replace('"',"&quot;",$htmltext);
-        if ($tooltipon == 2 || $tooltipon == 3) $paramfortooltipimg=' class="classfortooltip inline-block'.($extracss?' '.$extracss:'').'" title="'.($noencodehtmltext?$htmltext:dol_escape_htmltag($htmltext,1)).'"'; // Attribut to put on td img tag to store tooltip
+        if ($tooltipon == 2 || $tooltipon == 3) $paramfortooltipimg=' class="classfortooltip inline-block'.($extracss?' '.$extracss:'').'" style="padding: 0px;" title="'.($noencodehtmltext?$htmltext:dol_escape_htmltag($htmltext,1)).'"'; // Attribut to put on td img tag to store tooltip
         else $paramfortooltipimg =($extracss?' class="'.$extracss.'"':''); // Attribut to put on td text tag
-        if ($tooltipon == 1 || $tooltipon == 3) $paramfortooltiptd=' class="classfortooltip inline-block'.($extracss?' '.$extracss:'').'" title="'.($noencodehtmltext?$htmltext:dol_escape_htmltag($htmltext,1)).'"'; // Attribut to put on td tag to store tooltip
+        if ($tooltipon == 1 || $tooltipon == 3) $paramfortooltiptd=' class="classfortooltip inline-block'.($extracss?' '.$extracss:'').'" style="padding: 0px;" title="'.($noencodehtmltext?$htmltext:dol_escape_htmltag($htmltext,1)).'"'; // Attribut to put on td tag to store tooltip
         else $paramfortooltiptd =($extracss?' class="'.$extracss.'"':''); // Attribut to put on td text tag
         $s="";
-        if (empty($notabs)) $s.='<table class="nobordernopadding" summary=""><tr>';
+        if (empty($notabs)) $s.='<table class="nobordernopadding" summary=""><tr style="height: auto;">';
         elseif ($notabs == 2) $s.='<div class="inline-block nowrap">';
         if ($direction < 0) {
             $s.='<'.$tag.$paramfortooltipimg;
@@ -412,8 +412,8 @@ class Form
         // Use another method to help avoid having a space in value in order to use this value with jquery
         // TODO add this in css
         //if ($text != '') $s.='<'.$tag.$paramfortooltiptd.'>'.(($direction < 0)?'&nbsp;':'').$text.(($direction > 0)?'&nbsp;':'').'</'.$tag.'>';
-        $paramfortooltiptd.= (($direction < 0)?' class="inline-block" style="padding-left: 3px !important;"':'');
-        $paramfortooltiptd.= (($direction > 0)?' class="inline-block" style="padding-right: 3px !important;"':'');
+        $paramfortooltiptd.= (($direction < 0)?' class="inline-block" style="padding: 0px; padding-left: 3px !important;"':'');
+        $paramfortooltiptd.= (($direction > 0)?' class="inline-block" style="padding: 0px; padding-right: 3px !important;"':'');
         if ((string) $text != '') $s.='<'.$tag.$paramfortooltiptd.'>'.$text.'</'.$tag.'>';
         if ($direction > 0) {
             $s.='<'.$tag.$paramfortooltipimg;

+ 12 - 4
htdocs/core/class/html.formcompany.class.php

@@ -455,7 +455,7 @@ class FormCompany
 		{
 			$out.= '<div id="particulier2" class="visible">';
 			$out.= '<select class="flat" name="forme_juridique_code" id="legal_form">';
-			if ($country_codeid) $out.= '<option value="0">&nbsp;</option>';
+			if ($country_codeid) $out.= '<option value="0">&nbsp;</option>';	// When country_codeid is set, we force to add an empty line because it does not appears from select. When not set, we already get the empty line from select.
 
 			$num = $this->db->num_rows($resql);
 			if ($num)
@@ -465,13 +465,21 @@ class FormCompany
 				while ($i < $num)
 				{
 					$obj = $this->db->fetch_object($resql);
-					$labelcountry=(($langs->trans("Country".$obj->country_code)!="Country".$obj->country_code) ? $langs->trans("Country".$obj->country_code) : $obj->country);
-					$labeljs=(($langs->trans("JuridicalStatus".$obj->code)!="JuridicalStatus".$obj->code) ? $langs->trans("JuridicalStatus".$obj->code) : ($obj->label!='-'?$obj->label:''));	// $obj->label is already in output charset (converted by database driver)
-					$arraydata[$obj->code]=array('code'=>$obj->code, 'label'=>$labeljs, 'label_sort'=>$labelcountry.'_'.$labeljs, 'country_code'=>$obj->country_code, 'country'=>$labelcountry);
+
+					if ($obj->code)		// We exclude empty line, we will add it later
+					{
+						$labelcountry=(($langs->trans("Country".$obj->country_code)!="Country".$obj->country_code) ? $langs->trans("Country".$obj->country_code) : $obj->country);
+						$labeljs=(($langs->trans("JuridicalStatus".$obj->code)!="JuridicalStatus".$obj->code) ? $langs->trans("JuridicalStatus".$obj->code) : ($obj->label!='-'?$obj->label:''));	// $obj->label is already in output charset (converted by database driver)
+						$arraydata[$obj->code]=array('code'=>$obj->code, 'label'=>$labeljs, 'label_sort'=>$labelcountry.'_'.$labeljs, 'country_code'=>$obj->country_code, 'country'=>$labelcountry);
+					}
 					$i++;
 				}
 
 				$arraydata=dol_sort_array($arraydata, 'label_sort', 'ASC');
+				if (empty($country_codeid))	// Introduce empty value (if $country_codeid not empty, empty value was already added)
+				{
+					$arraydata[0]=array('code'=>0, 'label'=>'', 'label_sort'=>'_', 'country_code'=>'', 'country'=>'');
+				}
 
 				foreach($arraydata as $key => $val)
 				{

+ 64 - 22
htdocs/fourn/class/fournisseur.commande.class.php

@@ -65,6 +65,7 @@ class CommandeFournisseur extends CommonOrder
     var $date;
     var $date_valid;
     var $date_approve;
+    var $date_approve2;		// Used when SUPPLIER_ORDER_DOUBLE_APPROVAL is set
     var $date_commande;
 	var $date_livraison;	// Date livraison souhaitee
     var $total_ht;
@@ -85,12 +86,13 @@ class CommandeFournisseur extends CommonOrder
     var $user_author_id;
     var $user_valid_id;
     var $user_approve_id;
+    var $user_approve_id2;	// Used when SUPPLIER_ORDER_DOUBLE_APPROVAL is set
 
 	//Incorterms
 	var $fk_incoterms;
 	var $location_incoterms;
 	var $libelle_incoterms;  //Used into tooltip
-	
+
     var $extraparams=array();
 
 	/**
@@ -110,7 +112,7 @@ class CommandeFournisseur extends CommonOrder
     function __construct($db)
     {
     	global $conf;
-    	
+
         $this->db = $db;
         $this->products = array();
 
@@ -144,8 +146,8 @@ class CommandeFournisseur extends CommonOrder
 
         $sql = "SELECT c.rowid, c.ref, ref_supplier, c.fk_soc, c.fk_statut, c.amount_ht, c.total_ht, c.total_ttc, c.tva,";
         $sql.= " c.localtax1, c.localtax2, ";
-        $sql.= " c.date_creation, c.date_valid, c.date_approve,";
-        $sql.= " c.fk_user_author, c.fk_user_valid, c.fk_user_approve,";
+        $sql.= " c.date_creation, c.date_valid, c.date_approve, c.date_approve2,";
+        $sql.= " c.fk_user_author, c.fk_user_valid, c.fk_user_approve, c.fk_user_approve2,";
         $sql.= " c.date_commande as date_commande, c.date_livraison as date_livraison, c.fk_cond_reglement, c.fk_mode_reglement, c.fk_projet as fk_project, c.remise_percent, c.source, c.fk_input_method,";
         $sql.= " c.fk_account,";
         $sql.= " c.note_private, c.note_public, c.model_pdf, c.extraparams,";
@@ -184,6 +186,7 @@ class CommandeFournisseur extends CommonOrder
             $this->user_author_id		= $obj->fk_user_author;
             $this->user_valid_id		= $obj->fk_user_valid;
             $this->user_approve_id		= $obj->fk_user_approve;
+            $this->user_approve_id2		= $obj->fk_user_approve2;
             $this->total_ht				= $obj->total_ht;
             $this->total_tva			= $obj->tva;
             $this->total_localtax1		= $obj->localtax1;
@@ -192,7 +195,8 @@ class CommandeFournisseur extends CommonOrder
             $this->date					= $this->db->jdate($obj->date_creation);
             $this->date_valid			= $this->db->jdate($obj->date_valid);
             $this->date_approve			= $this->db->jdate($obj->date_approve);
-            $this->date_commande		= $this->db->jdate($obj->date_commande); // date a laquelle la commande a ete transmise
+            $this->date_approve2		= $this->db->jdate($obj->date_approve2);
+            $this->date_commande		= $this->db->jdate($obj->date_commande); // date we make the order to supplier
 			$this->date_livraison       = $this->db->jdate($obj->date_livraison);
             $this->remise_percent		= $obj->remise_percent;
             $this->methode_commande_id	= $obj->fk_input_method;
@@ -216,9 +220,9 @@ class CommandeFournisseur extends CommonOrder
 
 			//Incoterms
 			$this->fk_incoterms = $obj->fk_incoterms;
-			$this->location_incoterms = $obj->location_incoterms;									
+			$this->location_incoterms = $obj->location_incoterms;
 			$this->libelle_incoterms = $obj->libelle_incoterms;
-				
+
             $this->extraparams			= (array) json_decode($obj->extraparams, true);
 
             $this->db->free($resql);
@@ -645,9 +649,10 @@ class CommandeFournisseur extends CommonOrder
      *
      *	@param	User	$user			Object user
      *	@param	int		$idwarehouse	Id of warhouse for stock change
+     *  @param	int		$secondlevel	0=Standard approval, 1=Second level approval (used when option SUPPLIER_ORDER_DOUBLE_APPROVAL is set)
      *	@return	int						<0 if KO, >0 if OK
      */
-    function approve($user, $idwarehouse=0)
+    function approve($user, $idwarehouse=0, $secondlevel=0)
     {
         global $langs,$conf;
 		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
@@ -658,6 +663,8 @@ class CommandeFournisseur extends CommonOrder
 
         if ($user->rights->fournisseur->commande->approuver)
         {
+        	$now = dol_now();
+
             $this->db->begin();
 
 			// Definition du nom de modele de numerotation de commande
@@ -675,11 +682,26 @@ class CommandeFournisseur extends CommonOrder
             }
             $this->newref = $num;
 
+            // Do we have to change status now ? (If double approval is required and first approval, we keep status to 1 = validated)
+			$movetoapprovestatus=true;
+
             $sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
 			$sql.= " SET ref='".$this->db->escape($num)."',";
-            $sql.= " fk_statut = 2,";
-            $sql.= " date_approve='".$this->db->idate(dol_now())."',";
-            $sql.= " fk_user_approve = ".$user->id;
+			if (empty($secondlevel))	// standard or first level approval
+			{
+	            $sql.= " date_approve='".$this->db->idate($now)."',";
+    	        $sql.= " fk_user_approve = ".$user->id;
+    	        if (! empty($conf->global->SUPPLIER_ORDER_DOUBLE_APPROVAL)) $movetoapprovestatus=false;
+			}
+			else	// request a second level approval
+			{
+            	$sql.= " date_approve2='".$this->db->idate($now)."',";
+            	$sql.= " fk_user_approve2 = ".$user->id;
+    	        if (empty($this->user_approve_id)) $movetoapprovestatus=false;		// first level approval not done
+			}
+			// If double approval is required and first approval, we keep status to 1 = validated
+			if ($movetoapprovestatus) $sql.= ", fk_statut = 2";
+			else $sql.= ", fk_statut = 1";
             $sql.= " WHERE rowid = ".$this->id;
             $sql.= " AND fk_statut = 1";
 
@@ -697,7 +719,7 @@ class CommandeFournisseur extends CommonOrder
 	            }
 
                 // If stock is incremented on validate order, we must increment it
-                if (! $error && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER))
+                if (! $error && $movetoapprovestatus && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER))
                 {
                     require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
                     $langs->load("agenda");
@@ -729,6 +751,20 @@ class CommandeFournisseur extends CommonOrder
 
                 if (! $error)
                 {
+                	$this->ref=$newref;
+                	if ($movetoapprovestatus) $this->statut = 2;
+					else $this->statut = 1;
+           			if (empty($secondlevel))	// standard or first level approval
+					{
+			            $this->date_approve = $now;
+		    	        $this->user_approve_id = $user->id;
+					}
+					else	// request a second level approval
+					{
+			            $this->date_approve2 = $now;
+		    	        $this->user_approve_id2 = $user->id;
+					}
+
                     $this->db->commit();
                     return 1;
                 }
@@ -1088,6 +1124,10 @@ class CommandeFournisseur extends CommonOrder
         $this->date_creation      = '';
         $this->date_validation    = '';
         $this->ref_supplier       = '';
+        $this->user_approve_id    = '';
+        $this->user_approve_id2   = '';
+        $this->date_approve       = '';
+        $this->date_approve2      = '';
 
         // Create clone
         $result=$this->create($user);
@@ -1625,14 +1665,14 @@ class CommandeFournisseur extends CommonOrder
 
     /**
 	 * Return array of dispathed lines waiting to be approved for this order
-	 * 
+	 *
 	 * @param	int		$status		Filter on stats (-1 = no filter, 0 = lines draft to be approved, 1 = approved lines)
 	 * @return	array				Array of lines
      */
     function getDispachedLines($status=-1)
     {
     	$ret = array();
-    	
+
     	// List of already dispatched lines
 		$sql = "SELECT p.ref, p.label,";
 		$sql.= " e.rowid as warehouse_id, e.label as entrepot,";
@@ -1655,16 +1695,16 @@ class CommandeFournisseur extends CommonOrder
 			{
 				$objp = $this->db->fetch_object($resql);
 				if ($objp) $ret[]=array('id'=>$objp->dispatchedlineid, 'productid'=>$objp->fk_product, 'warehouseid'=>$objp->warehouse_id);
-				
+
 				$i++;
 			}
 		}
 		else dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
-		
+
 		return $ret;
     }
-    
-    
+
+
     /**
      * 	Set a delivery in database for this supplier order
      *
@@ -1677,10 +1717,10 @@ class CommandeFournisseur extends CommonOrder
     function Livraison($user, $date, $type, $comment)
     {
     	global $conf;
-    	
+
         $result = 0;
 		$error = 0;
-		
+
         dol_syslog(get_class($this)."::Livraison");
 
         if ($user->rights->fournisseur->commande->receptionner)
@@ -1709,7 +1749,7 @@ class CommandeFournisseur extends CommonOrder
                 dol_syslog(get_class($this)."::Livraison Error -2", LOG_ERR);
                 $result = -2;
             }
-	    	
+
             if (! $error)
             {
                 $this->db->begin();
@@ -2094,6 +2134,8 @@ class CommandeFournisseur extends CommonOrder
         $this->mode_reglement_code = 'CHQ';
         $this->note_public='This is a comment (public)';
         $this->note_private='This is a comment (private)';
+        $this->statut=0;
+
         // Lines
         $nbp = 5;
         $xnbp = 0;
@@ -2350,7 +2392,7 @@ class CommandeFournisseurLigne extends CommonOrderLine
 {
 	public $element='commande_fournisseurdet';
 	public $table_element='commande_fournisseurdet';
-	
+
 	/**
 	 * Unit price without taxes
 	 * @var float

+ 84 - 28
htdocs/fourn/commande/card.php

@@ -200,14 +200,29 @@ if (empty($reshook))
 	        else if ($object->statut == 7) $newstatus=3;	// Canceled->Process running
 	        else if ($object->statut == 9) $newstatus=1;	// Refused->Validated
 
+	        $db->begin();
+
 	        $result = $object->setStatus($user, $newstatus);
 	        if ($result > 0)
 	        {
+	        	if ($newstatus == 0)
+	        	{
+		        	$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande_fournisseur';
+	        		$sql.= ' SET fk_user_approve = null, fk_user_approve2 = null, date_approve = null, date_approve2 = null';
+	        		$sql.= ' WHERE rowid = '.$object->id;
+
+	        		$resql=$db->query($sql);
+	        	}
+
+        		$db->commit();
+
 	            header('Location: '.$_SERVER["PHP_SELF"].'?id='.$object->id);
 	            exit;
 	        }
 	        else
-	        {
+			{
+				$db->rollback();
+
 	        	setEventMessage($object->error, 'errors');
 	        }
 	    }
@@ -570,6 +585,7 @@ if (empty($reshook))
 		}
 	}
 
+	// Validate
 	if ($action == 'confirm_valid' && $confirm == 'yes' &&
 	    ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->fournisseur->commande->creer))
 	    || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->fournisseur->supplier_order_advance->validate)))
@@ -605,11 +621,11 @@ if (empty($reshook))
 	    // If we have permission, and if we don't need to provide the idwarehouse, we go directly on approved step
 	    if ($user->rights->fournisseur->commande->approuver && ! (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER) && $object->hasProductsOrServices(1)))
 	    {
-	        $action='confirm_approve';
+	        $action='confirm_approve';	// can make standard or first level approval also if permission is set
 	    }
 	}
 
-	if ($action == 'confirm_approve' && $confirm == 'yes' && $user->rights->fournisseur->commande->approuver)
+	if (($action == 'confirm_approve' || $action == 'confirm_approve2') && $confirm == 'yes' && $user->rights->fournisseur->commande->approuver)
 	{
 	    $idwarehouse=GETPOST('idwarehouse', 'int');
 
@@ -624,7 +640,7 @@ if (empty($reshook))
 		}
 
 	    // Check parameters
-	    if (! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER) && $qualified_for_stock_change)
+	    if (! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER) && $qualified_for_stock_change)	// warning name of option should be STOCK_CALCULATE_ON_SUPPLIER_APPROVE_ORDER
 	    {
 	        if (! $idwarehouse || $idwarehouse == -1)
 	        {
@@ -636,7 +652,7 @@ if (empty($reshook))
 
 	    if (! $error)
 	    {
-	        $result	= $object->approve($user, $idwarehouse);
+	        $result	= $object->approve($user, $idwarehouse, ($action=='confirm_approve2'?1:0));
 	        if ($result > 0)
 	        {
 	            if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
@@ -1663,9 +1679,9 @@ elseif (! empty($object->id))
 	}
 
 	/*
-	 * Confirmation de l'approbation
+	 * Confirm approval
 	 */
-	if ($action	== 'approve')
+	if ($action	== 'approve' || $action	== 'approve2')
 	{
         $qualified_for_stock_change=0;
 	    if (empty($conf->global->STOCK_SUPPORTS_SERVICES))
@@ -1699,7 +1715,7 @@ elseif (! empty($object->id))
 			$text.=$notify->confirmMessage('ORDER_SUPPLIER_APPROVE', $object->socid, $object);
 		}
 
-		$formconfirm = $form->formconfirm($_SERVER['PHP_SELF']."?id=".$object->id, $langs->trans("ApproveThisOrder"), $text, "confirm_approve", $formquestion, 1, 1, 240);
+		$formconfirm = $form->formconfirm($_SERVER['PHP_SELF']."?id=".$object->id, $langs->trans("ApproveThisOrder"), $text, "confirm_".$action, $formquestion, 1, 1, 240);
 	}
 
 	/*
@@ -2589,13 +2605,51 @@ elseif (! empty($object->id))
 				{
 					if ($user->rights->fournisseur->commande->approuver)
 					{
-						print '<a class="butAction"	href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=approve">'.$langs->trans("ApproveOrder").'</a>';
+						if (! empty($conf->global->SUPPLIER_ORDER_DOUBLE_APPROVAL) && ! empty($object->user_approve_id))
+						{
+							print '<a class="butActionRefused" href="#" title="'.dol_escape_htmltag($langs->trans("FirstApprovalAlreadyDone")).'">'.$langs->trans("ApproveOrder").'</a>';
+						}
+						else
+						{
+							print '<a class="butAction"	href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=approve">'.$langs->trans("ApproveOrder").'</a>';
+						}
+					}
+					else
+					{
+						print '<a class="butActionRefused" href="#" title="'.dol_escape_htmltag($langs->trans("NotAllowed")).'">'.$langs->trans("ApproveOrder").'</a>';
+					}
+				}
+
+				// Second approval (if option SUPPLIER_ORDER_DOUBLE_APPROVAL is set)
+				if ($object->statut == 1)
+				{
+					if ($user->rights->fournisseur->commande->approve2)
+					{
+						if (! empty($conf->global->SUPPLIER_ORDER_DOUBLE_APPROVAL) && ! empty($object->user_approve_id2))
+						{
+							print '<a class="butActionRefused" href="#" title="'.dol_escape_htmltag($langs->trans("SecondApprovalAlreadyDone")).'">'.$langs->trans("Approve2Order").'</a>';
+						}
+						else
+						{
+							print '<a class="butAction"	href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=approve2">'.$langs->trans("Approve2Order").'</a>';
+						}
+					}
+					else
+					{
+						print '<a class="butActionRefused" href="#" title="'.dol_escape_htmltag($langs->trans("NotAllowed")).'">'.$langs->trans("Approve2Order").'</a>';
+					}
+				}
+
+				// Refuse
+				if ($object->statut == 1)
+				{
+					if ($user->rights->fournisseur->commande->approuver || $user->rights->fournisseur->commande->approve2)
+					{
 						print '<a class="butAction"	href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=refuse">'.$langs->trans("RefuseOrder").'</a>';
 					}
 					else
 					{
-						print '<a class="butActionRefused" href="#">'.$langs->trans("ApproveOrder").'</a>';
-						print '<a class="butActionRefused" href="#">'.$langs->trans("RefuseOrder").'</a>';
+						print '<a class="butActionRefused" href="#" title="'.dol_escape_htmltag($langs->trans("NotAllowed")).'">'.$langs->trans("RefuseOrder").'</a>';
 					}
 				}
 
@@ -2625,7 +2679,7 @@ elseif (! empty($object->id))
 				}
 
 				// Create bill
-				if (! empty($conf->fournisseur->enabled) && $object->statut >= 2)  // 2 means accepted
+				if (! empty($conf->fournisseur->enabled) && ($object->statut >= 2 && $object->statut != 9))  // 2 means accepted
 				{
 					if ($user->rights->fournisseur->facture->creer)
 					{
@@ -2645,6 +2699,12 @@ elseif (! empty($object->id))
 					print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=webservice&amp;mode=init">'.$langs->trans('CreateRemoteOrder').'</a>';
 				}
 
+				// Clone
+				if ($user->rights->fournisseur->commande->creer)
+				{
+					print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&amp;socid='.$object->socid.'&amp;action=clone&amp;object=order">'.$langs->trans("ToClone").'</a>';
+				}
+
 				// Cancel
 				if ($object->statut == 2)
 				{
@@ -2654,12 +2714,6 @@ elseif (! empty($object->id))
 					}
 				}
 
-				// Clone
-				if ($user->rights->fournisseur->commande->creer)
-				{
-					print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&amp;socid='.$object->socid.'&amp;action=clone&amp;object=order">'.$langs->trans("ToClone").'</a>';
-				}
-
 				// Delete
 				if ($user->rights->fournisseur->commande->supprimer)
 				{
@@ -2696,23 +2750,17 @@ elseif (! empty($object->id))
 		print '</div><div class="fichehalfright"><div class="ficheaddleft">';
 
 
-        // List of actions on element
-        include_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php';
-        $formactions=new FormActions($db);
-        $somethingshown=$formactions->showactions($object,'order_supplier',$socid);
-
-
 		if ($user->rights->fournisseur->commande->commander && $object->statut == 2)
 		{
 			/*
 			 * Commander (action=commande)
 			 */
-			print '<br>';
 			print '<form name="commande" action="card.php?id='.$object->id.'&amp;action=commande" method="post">';
 			print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">';
 			print '<input type="hidden"	name="action" value="commande">';
+			print_fiche_titre($langs->trans("ToOrder"),'','');
 			print '<table class="border" width="100%">';
-			print '<tr class="liste_titre"><td colspan="2">'.$langs->trans("ToOrder").'</td></tr>';
+			//print '<tr class="liste_titre"><td colspan="2">'.$langs->trans("ToOrder").'</td></tr>';
 			print '<tr><td>'.$langs->trans("OrderDate").'</td><td>';
 			$date_com = dol_mktime(0, 0, 0, GETPOST('remonth'), GETPOST('reday'), GETPOST('reyear'));
 			print $form->select_date($date_com,'','','','',"commande");
@@ -2726,6 +2774,7 @@ elseif (! empty($object->id))
 			print '<tr><td align="center" colspan="2"><input type="submit" class="button" value="'.$langs->trans("ToOrder").'"></td></tr>';
 			print '</table>';
 			print '</form>';
+			print "<br>";
 		}
 
 		if ($user->rights->fournisseur->commande->receptionner	&& ($object->statut == 3 || $object->statut == 4))
@@ -2733,12 +2782,12 @@ elseif (! empty($object->id))
 			/*
 			 * Receptionner (action=livraison)
 			 */
-			print '<br>';
 			print '<form action="card.php?id='.$object->id.'" method="post">';
 			print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">';
 			print '<input type="hidden"	name="action" value="livraison">';
+			print_fiche_titre($langs->trans("Receive"),'','');
 			print '<table class="border" width="100%">';
-			print '<tr class="liste_titre"><td colspan="2">'.$langs->trans("Receive").'</td></tr>';
+			//print '<tr class="liste_titre"><td colspan="2">'.$langs->trans("Receive").'</td></tr>';
 			print '<tr><td>'.$langs->trans("DeliveryDate").'</td><td>';
 			print $form->select_date('','','','','',"commande");
 			print "</td></tr>\n";
@@ -2758,8 +2807,15 @@ elseif (! empty($object->id))
 			print '<tr><td align="center" colspan="2"><input type="submit" class="button" value="'.$langs->trans("Receive").'"></td></tr>';
 			print "</table>\n";
 			print "</form>\n";
+			print "<br>";
 		}
 
+        // List of actions on element
+        include_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php';
+        $formactions=new FormActions($db);
+        $somethingshown=$formactions->showactions($object,'order_supplier',$socid);
+
+
 		// List of actions on element
 		/* Hidden because" available into "Log" tab
 		print '<br>';

+ 2 - 0
htdocs/install/mysql/migration/3.7.0-3.8.0.sql

@@ -385,6 +385,8 @@ ALTER TABLE llx_commande ADD COLUMN fk_incoterms integer;
 ALTER TABLE llx_commande ADD COLUMN location_incoterms varchar(255);
 ALTER TABLE llx_commande_fournisseur ADD COLUMN fk_incoterms integer;
 ALTER TABLE llx_commande_fournisseur ADD COLUMN location_incoterms varchar(255);
+ALTER TABLE llx_commande_fournisseur ADD COLUMN date_approve2 datetime after date_approve;
+ALTER TABLE llx_commande_fournisseur ADD COLUMN fk_user_approve2 integer after fk_user_approve;
 ALTER TABLE llx_facture ADD COLUMN fk_incoterms integer;
 ALTER TABLE llx_facture ADD COLUMN location_incoterms varchar(255);
 ALTER TABLE llx_facture_fourn ADD COLUMN fk_incoterms integer;

+ 2 - 0
htdocs/install/mysql/tables/llx_commande_fournisseur.sql

@@ -36,11 +36,13 @@ create table llx_commande_fournisseur
   date_creation			datetime,                      -- date de creation 
   date_valid			datetime,                      -- date de validation
   date_approve			datetime,                      -- date de approve
+  date_approve2			datetime,                      -- date de approve 2 (when double approving is accivated)
   date_commande			date,                          -- date de la commande
   fk_user_author		integer,                       -- user making creation
   fk_user_modif         integer,                       -- user making last change
   fk_user_valid			integer,                       -- user validating
   fk_user_approve		integer,                       -- user approving
+  fk_user_approve2		integer,                       -- user approving 2 (when double approving is accivated)
   source				smallint NOT NULL,			-- not used, except by setting this to 42 for orders coming for replenishment and 0 in other case ?
   fk_statut				smallint  default 0,
   amount_ht				real      default 0,

+ 1 - 0
htdocs/langs/en_US/admin.lang

@@ -1565,6 +1565,7 @@ SuppliersSetup=Supplier module setup
 SuppliersCommandModel=Complete template of supplier order (logo...)
 SuppliersInvoiceModel=Complete template of supplier invoice (logo...)
 SuppliersInvoiceNumberingModel=Supplier invoices numbering models
+IfSetToYesDontForgetPermission=If set to yes, don't forget to provide permissions to groups or users allowed for the second approval
 ##### GeoIPMaxmind #####
 GeoIPMaxmindSetup=GeoIP Maxmind module setup
 PathToGeoIPMaxmindCountryDataFile=Path to file containing Maxmind ip to country translation.<br>Examples:<br>/usr/local/share/GeoIP/GeoIP.dat<br>/usr/share/GeoIP/GeoIP.dat

+ 3 - 1
htdocs/langs/en_US/orders.lang

@@ -64,7 +64,8 @@ ShipProduct=Ship product
 Discount=Discount
 CreateOrder=Create Order
 RefuseOrder=Refuse order
-ApproveOrder=Accept order
+ApproveOrder=Approve order
+Approve2Order=Approve order (second level)
 ValidateOrder=Validate order
 UnvalidateOrder=Unvalidate order
 DeleteOrder=Delete order
@@ -120,6 +121,7 @@ PaymentOrderRef=Payment of order %s
 CloneOrder=Clone order
 ConfirmCloneOrder=Are you sure you want to clone this order <b>%s</b> ?
 DispatchSupplierOrder=Receiving supplier order %s
+FirstApprovalAlreadyDone=First approval already done
 ##### Types de contacts #####
 TypeContact_commande_internal_SALESREPFOLL=Representative following-up customer order
 TypeContact_commande_internal_SHIPPING=Representative following-up shipping

+ 2 - 1
htdocs/societe/soc.php

@@ -1587,8 +1587,9 @@ else
             if ($user->admin) print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"),1);
             print '</td></tr>';
 
+            // Juridical type
             print '<tr><td><label for="legal_form">'.$langs->trans('JuridicalStatus').'</label></td><td colspan="3">';
-            print $formcompany->select_juridicalstatus($object->forme_juridique_code,$object->country_code);
+            print $formcompany->select_juridicalstatus($object->forme_juridique_code,$object->country_code,'',0);
             print '</td></tr>';
 
             // Capital