From 35dd492d19ae8076e13512e33f09d1a66d55a46e Mon Sep 17 00:00:00 2001
From: Laurent Destailleur <eldy@destailleur.fr>
Date: Thu, 22 Sep 2016 12:53:44 +0200
Subject: [PATCH] NEW Add REST api for commercial proposals

---
 htdocs/api/index.php                          |  26 +-
 .../comm/propal/class/api_proposals.class.php | 504 ++++++++++++++++++
 htdocs/comm/propal/class/propal.class.php     | 178 ++++---
 htdocs/commande/class/api_orders.class.php    |  17 +-
 htdocs/commande/class/commande.class.php      |   4 +-
 5 files changed, 628 insertions(+), 101 deletions(-)
 create mode 100644 htdocs/comm/propal/class/api_proposals.class.php

diff --git a/htdocs/api/index.php b/htdocs/api/index.php
index bfb9fca54b8..549e164abde 100644
--- a/htdocs/api/index.php
+++ b/htdocs/api/index.php
@@ -82,21 +82,27 @@ foreach ($modulesdir as $dir)
         {
             if (is_readable($dir.$file) && preg_match("/^mod(.*)\.class\.php$/i",$file,$reg))
             {
-                $module = $part = strtolower($reg[1]);
-
-                if ($module == 'agenda') {
-                    $part = 'comm/action';
+                $module = strtolower($reg[1]);
+                $moduledirforclass = $module;
+                $moduleforperm = $module;
+                
+                if ($module == 'propale') {
+                    $moduledirforclass = 'comm/propal';
+                    $moduleforperm='propal';
+				}
+                elseif ($module == 'agenda') {
+                    $moduledirforclass = 'comm/action';
 				}
-                if ($module == 'categorie') {
-                    $part = 'categories';
+                elseif ($module == 'categorie') {
+                    $moduledirforclass = 'categories';
 				}
-                if ($module == 'facture') {
-                    $part = 'compta/facture';
+                elseif ($module == 'facture') {
+                    $moduledirforclass = 'compta/facture';
 				}
 
                 // Defined if module is enabled
                 $enabled=true;
-                if (empty($conf->$module->enabled)) $enabled=false;
+                if (empty($conf->$moduleforperm->enabled)) $enabled=false;
 
                 if ($enabled)
                 {
@@ -108,7 +114,7 @@ foreach ($modulesdir as $dir)
                      * @todo : take care of externals module!
                      * @todo : use getElementProperties() function ?
                      */
-                    $dir_part = DOL_DOCUMENT_ROOT.'/'.$part.'/class/';
+                    $dir_part = DOL_DOCUMENT_ROOT.'/'.$moduledirforclass.'/class/';
 
                     $handle_part=@opendir(dol_osencode($dir_part));
                     if (is_resource($handle_part))
diff --git a/htdocs/comm/propal/class/api_proposals.class.php b/htdocs/comm/propal/class/api_proposals.class.php
new file mode 100644
index 00000000000..9a33c4d7cbc
--- /dev/null
+++ b/htdocs/comm/propal/class/api_proposals.class.php
@@ -0,0 +1,504 @@
+<?php
+/* Copyright (C) 2015   Jean-François Ferry     <jfefe@aternatik.fr>
+ * Copyright (C) 2016   Laurent Destailleur     <eldy@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+ use Luracast\Restler\RestException;
+
+ require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
+
+/**
+ * API class for orders
+ *
+ * @access protected 
+ * @class  DolibarrApiAccess {@requires user,external}
+ */
+class Proposals extends DolibarrApi
+{
+
+    /**
+     * @var array   $FIELDS     Mandatory fields, checked when create and update object 
+     */
+    static $FIELDS = array(
+        'socid'
+    );
+
+    /**
+     * @var propal $propal {@type propal}
+     */
+    public $propal;
+
+    /**
+     * Constructor
+     */
+    function __construct()
+    {
+		global $db, $conf;
+		$this->db = $db;
+        $this->propal = new Propal($this->db);
+    }
+
+    /**
+     * Get properties of a commercial proposal object
+     *
+     * Return an array with commercial proposal informations
+     * 
+     * @param       int         $id         ID of commercial proposal
+     * @return 	array|mixed data without useless information
+	 *
+     * @throws 	RestException
+     */
+    function get($id)
+    {		
+		if(! DolibarrApiAccess::$user->rights->propal->lire) {
+			throw new RestException(401);
+		}
+			
+        $result = $this->propal->fetch($id);
+        if( ! $result ) {
+            throw new RestException(404, 'Commercial Proposal not found');
+        }
+		
+		if( ! DolibarrApi::_checkAccessToResource('propal',$this->propal->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+        
+        $this->propal->fetchObjectLinked();
+		return $this->_cleanObjectDatas($this->propal);
+    }
+
+    /**
+     * List commercial proposals
+     * 
+     * Get a list of commercial proposals
+     * 
+     * @param string	$sortfield	        Sort field
+     * @param string	$sortorder	        Sort order
+     * @param int		$limit		        Limit for list
+     * @param int		$page		        Page number
+     * @param string   	$thirdparty_ids	    Thirdparty ids to filter commercial proposal of. Example: '1' or '1,2,3'          {@pattern /^[0-9,]*$/i}
+     *
+     * @return  array   Array of order objects
+     */
+    function index($sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0, $thirdparty_ids = '') {
+        global $db, $conf;
+        
+        $obj_ret = array();
+        // case of external user, $thirdpartyid param is ignored and replaced by user's socid
+        $socids = DolibarrApiAccess::$user->societe_id ? DolibarrApiAccess::$user->societe_id : $thirdparty_ids;
+            
+        // If the internal user must only see his customers, force searching by him
+        if (! DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) $search_sale = DolibarrApiAccess::$user->id;
+
+        $sql = "SELECT s.rowid";
+        if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) $sql .= ", sc.fk_soc, sc.fk_user"; // We need these fields in order to filter by sale (including the case where the user can only see his prospects)
+        $sql.= " FROM ".MAIN_DB_PREFIX."propal as s";
+        
+        if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale
+
+        $sql.= ' WHERE s.entity IN ('.getEntity('propal', 1).')';
+        if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) $sql.= " AND s.fk_soc = sc.fk_soc";
+        if ($socids) $sql.= " AND s.fk_soc IN (".$socids.")";
+        if ($search_sale > 0) $sql.= " AND s.rowid = sc.fk_soc";		// Join for the needed table to filter by sale
+        
+        // Insert sale filter
+        if ($search_sale > 0)
+        {
+            $sql .= " AND sc.fk_user = ".$search_sale;
+        }
+        
+        $nbtotalofrecords = 0;
+        if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST))
+        {
+            $result = $db->query($sql);
+            $nbtotalofrecords = $db->num_rows($result);
+        }
+
+        $sql.= $db->order($sortfield, $sortorder);
+        if ($limit)	{
+            if ($page < 0)
+            {
+                $page = 0;
+            }
+            $offset = $limit * $page;
+
+            $sql.= $db->plimit($limit + 1, $offset);
+        }
+
+        $result = $db->query($sql);
+        
+        if ($result)
+        {
+            $num = $db->num_rows($result);
+            while ($i < $num)
+            {
+                $obj = $db->fetch_object($result);
+                $propal_static = new Propal($db);
+                if($propal_static->fetch($obj->rowid)) {
+                    $obj_ret[] = parent::_cleanObjectDatas($propal_static);
+                }
+                $i++;
+            }
+        }
+        else {
+            throw new RestException(503, 'Error when retrieve propal list');
+        }
+        if( ! count($obj_ret)) {
+            throw new RestException(404, 'No order found');
+        }
+		return $obj_ret;
+    }
+
+    /**
+     * Create commercial proposal object
+     *
+     * @param   array   $request_data   Request data
+     * @return  int     ID of propal
+     */
+    function post($request_data = NULL)
+    {
+      if(! DolibarrApiAccess::$user->rights->propal->creer) {
+			  throw new RestException(401, "Insuffisant rights");
+		  }
+        // Check mandatory fields
+        $result = $this->_validate($request_data);
+
+        foreach($request_data as $field => $value) {
+            $this->propal->$field = $value;
+        }
+        /*if (isset($request_data["lines"])) {
+          $lines = array();
+          foreach ($request_data["lines"] as $line) {
+            array_push($lines, (object) $line);
+          }
+          $this->propal->lines = $lines;
+        }*/
+        if ($this->propal->create(DolibarrApiAccess::$user) <= 0) {
+            $errormsg = $this->propal->error;
+            throw new RestException(500, $errormsg ? $errormsg : "Error while creating order");
+        }
+        
+        return $this->propal->id;
+    }
+
+    /**
+     * Get lines of a commercial proposal
+     *
+     * @param int   $id             Id of commercial proposal
+     * 
+     * @url	GET {id}/lines
+     * 
+     * @return int 
+     */
+    function getLines($id) {
+      if(! DolibarrApiAccess::$user->rights->propal->lire) {
+		  	throw new RestException(401);
+		  }
+        
+      $result = $this->propal->fetch($id);
+      if( ! $result ) {
+         throw new RestException(404, 'Commercial Proposal not found');
+      }
+		
+		  if( ! DolibarrApi::_checkAccessToResource('propal',$this->propal->id)) {
+			  throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+      }
+      $this->propal->getLinesArray();
+      $result = array();
+      foreach ($this->propal->lines as $line) {
+        array_push($result,$this->_cleanObjectDatas($line));
+      }
+      return $result;
+    }
+
+    /**
+     * Add a line to given commercial proposal
+     *
+     * @param int   $id             Id of commercial proposal to update
+     * @param array $request_data   Commercial proposal line data   
+     * 
+     * @url	POST {id}/lines
+     * 
+     * @return int 
+     */
+    function postLine($id, $request_data = NULL) {
+      if(! DolibarrApiAccess::$user->rights->propal->creer) {
+		  	throw new RestException(401);
+		  }
+        
+      $result = $this->propal->fetch($id);
+      if( ! $result ) {
+         throw new RestException(404, 'Commercial Proposal not found');
+      }
+		
+		  if( ! DolibarrApi::_checkAccessToResource('propal',$this->propal->id)) {
+			  throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+      }
+			$request_data = (object) $request_data;
+      $updateRes = $this->propal->addline(
+                        $request_data->desc,
+                        $request_data->subprice,
+                        $request_data->qty,
+                        $request_data->tva_tx,
+                        $request_data->localtax1_tx,
+                        $request_data->localtax2_tx,
+                        $request_data->fk_product,
+                        $request_data->remise_percent,
+                        $request_data->info_bits,
+                        $request_data->fk_remise_except,
+                        'HT',
+                        0,
+                        $request_data->date_start,
+                        $request_data->date_end,
+                        $request_data->product_type,
+                        $request_data->rang,
+                        $request_data->special_code,
+                        $fk_parent_line,
+                        $request_data->fk_fournprice,
+                        $request_data->pa_ht,
+                        $request_data->label,
+                        $request_data->array_options,
+                        $request_data->fk_unit,
+                        $this->element,
+                        $request_data->id
+      );
+
+      if ($updateRes > 0) {
+        return $this->get($id)->line->rowid;
+
+      }
+      return false;
+    }
+
+    /**
+     * Update a line of given commercial proposal
+     *
+     * @param int   $id             Id of commercial proposal to update
+     * @param int   $lineid         Id of line to update
+     * @param array $request_data   Commercial proposal line data   
+     * 
+     * @url	PUT {id}/lines/{lineid}
+     * 
+     * @return object 
+     */
+    function putLine($id, $lineid, $request_data = NULL) {
+      if(! DolibarrApiAccess::$user->rights->propal->creer) {
+		  	throw new RestException(401);
+		  }
+        
+      $result = $this->propal->fetch($id);
+      if( ! $result ) {
+         throw new RestException(404, 'Proposal not found');
+      }
+		
+		  if( ! DolibarrApi::_checkAccessToResource('propal',$this->propal->id)) {
+			  throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+      }
+			$request_data = (object) $request_data;
+      $updateRes = $this->propal->updateline(
+                        $lineid,
+                        $request_data->desc,
+                        $request_data->subprice,
+                        $request_data->qty,
+                        $request_data->remise_percent,
+                        $request_data->tva_tx,
+                        $request_data->localtax1_tx,
+                        $request_data->localtax2_tx,
+                        'HT',
+                        $request_data->info_bits,
+                        $request_data->date_start,
+                        $request_data->date_end,
+                        $request_data->product_type,
+                        $request_data->fk_parent_line,
+                        0,
+                        $request_data->fk_fournprice,
+                        $request_data->pa_ht,
+                        $request_data->label,
+                        $request_data->special_code,
+                        $request_data->array_options,
+                        $request_data->fk_unit
+      );
+
+      if ($updateRes > 0) {
+        $result = $this->get($id);
+        unset($result->line);
+        return $this->_cleanObjectDatas($result);
+      }
+      return false;
+    }
+
+    /**
+     * Delete a line of given commercial proposal
+     *
+     *
+     * @param int   $id             Id of commercial proposal to update
+     * @param int   $lineid         Id of line to delete
+     * 
+     * @url	DELETE {id}/lines/{lineid}
+     * 
+     * @return int 
+     */
+    function delLine($id, $lineid) {
+      if(! DolibarrApiAccess::$user->rights->propal->creer) {
+		  	throw new RestException(401);
+		  }
+        
+      $result = $this->propal->fetch($id);
+      if( ! $result ) {
+         throw new RestException(404, 'Proposal not found');
+      }
+		
+		  if( ! DolibarrApi::_checkAccessToResource('propal',$this->propal->id)) {
+			  throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+      }
+			$request_data = (object) $request_data;
+      $updateRes = $this->propal->deleteline($lineid);
+      if ($updateRes == 1) {
+        return $this->get($id);
+      }
+      return false;
+    }
+
+    /**
+     * Update commercial proposal general fields (won't touch lines of commercial proposal)
+     *
+     * @param int   $id             Id of commercial proposal to update
+     * @param array $request_data   Datas   
+     * 
+     * @return int 
+     */
+    function put($id, $request_data = NULL) {
+      if(! DolibarrApiAccess::$user->rights->propal->creer) {
+		  	throw new RestException(401);
+		  }
+        
+        $result = $this->propal->fetch($id);
+        if( ! $result ) {
+            throw new RestException(404, 'Proposal not found');
+        }
+		
+		if( ! DolibarrApi::_checkAccessToResource('propal',$this->propal->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+        foreach($request_data as $field => $value) {
+            $this->propal->$field = $value;
+        }
+        
+        if($this->propal->update($id, DolibarrApiAccess::$user,1,'','','update'))
+            return $this->get($id);
+        
+        return false;
+    }
+    
+    /**
+     * Delete commercial proposal
+     *
+     * @param   int     $id         Commercial proposal ID
+     * 
+     * @return  array
+     */
+    function delete($id)
+    {
+        if(! DolibarrApiAccess::$user->rights->propal->supprimer) {
+			throw new RestException(401);
+		}
+        $result = $this->propal->fetch($id);
+        if( ! $result ) {
+            throw new RestException(404, 'Commercial Proposal not found');
+        }
+		
+		if( ! DolibarrApi::_checkAccessToResource('propal',$this->propal->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+        
+        if( ! $this->propal->delete(DolibarrApiAccess::$user)) {
+            throw new RestException(500, 'Error when delete Commercial Proposal : '.$this->propal->error);
+        }
+        
+        return array(
+            'success' => array(
+                'code' => 200,
+                'message' => 'Commercial Proposal deleted'
+            )
+        );
+        
+    }
+    
+    /**
+     * Validate a commercial proposal
+     * 
+     * @param   int     $id             Commercial proposal ID
+     * @param   int     $notrigger      Use {}
+     * 
+     * @url POST    {id}/validate
+     *  
+     * @return  array
+     * FIXME An error 403 is returned if the request has an empty body.
+     * Error message: "Forbidden: Content type `text/plain` is not supported."
+     * Workaround: send this in the body
+     * {
+     * "notrigger": 0
+     * }
+     */
+    function validate($id, $notrigger=0)
+    {
+        if(! DolibarrApiAccess::$user->rights->propal->creer) {
+			throw new RestException(401);
+		}
+        $result = $this->propal->fetch($id);
+        if( ! $result ) {
+            throw new RestException(404, 'Commercial Proposal not found');
+        }
+		
+		if( ! DolibarrApi::_checkAccessToResource('propal',$this->propal->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+        
+        $result = $this->propal->valid(DolibarrApiAccess::$user, $notrigger);
+        if ($result == 0) {
+            throw new RestException(500, 'Error nothing done. May be object is already validated');
+        }
+        if ($result < 0) {
+            throw new RestException(500, 'Error when validating Commercial Proposal: '.$this->propal->error);
+        }
+        
+        return array(
+            'success' => array(
+                'code' => 200,
+                'message' => 'Commercial Proposal validated'
+            )
+        );
+    }
+    
+    /**
+     * Validate fields before create or update object
+     * 
+     * @param   array           $data   Array with data to verify
+     * @return  array           
+     * @throws  RestException
+     */
+    function _validate($data)
+    {
+        $propal = array();
+        foreach (Orders::$FIELDS as $field) {
+            if (!isset($data[$field]))
+                throw new RestException(400, "$field field missing");
+            $propal[$field] = $data[$field];
+            
+        }
+        return $propal;
+    }
+}
diff --git a/htdocs/comm/propal/class/propal.class.php b/htdocs/comm/propal/class/propal.class.php
index fb24e82f68d..f3eccc2a9be 100644
--- a/htdocs/comm/propal/class/propal.class.php
+++ b/htdocs/comm/propal/class/propal.class.php
@@ -1504,8 +1504,8 @@ class Propal extends CommonObject
      *  Set status to validated
      *
      *  @param	User	$user       Object user that validate
-     *  @param	int		$notrigger	1=Does not execute triggers, 0= execuete triggers
-     *  @return int         		<0 if KO, >=0 if OK
+     *  @param	int		$notrigger	1=Does not execute triggers, 0=execute triggers
+     *  @return int         		<0 if KO, 0=Nothing done, >=0 if OK
      */
     function valid($user, $notrigger=0)
     {
@@ -1514,98 +1514,110 @@ class Propal extends CommonObject
     	global $conf;
 
         $error=0;
-        $now=dol_now();
 
-        if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->propal->creer))
-       	|| (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->propal->propal_advance->validate)))
+        // Protection
+        if ($this->statut == self::STATUS_VALIDATED)
         {
-            $this->db->begin();
+            dol_syslog(get_class($this)."::valid action abandonned: already validated", LOG_WARNING);
+            return 0;
+        }
+        
+        if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->propal->creer))
+       	|| (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->propal->propal_advance->validate))))
+        {
+            $this->error='ErrorPermissionDenied';
+            dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
+            return -1;
+        }
 
-            // Numbering module definition
-            $soc = new Societe($this->db);
-            $soc->fetch($this->socid);
+        $now=dol_now();
+            
+        $this->db->begin();
 
-            // Define new ref
-            if (! $error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) // empty should not happened, but when it occurs, the test save life
-            {
-            	$num = $this->getNextNumRef($soc);
-            }
-            else
-            {
-            	$num = $this->ref;
-            }
-            $this->newref = $num;
+        // Numbering module definition
+        $soc = new Societe($this->db);
+        $soc->fetch($this->socid);
 
-            $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
-            $sql.= " SET ref = '".$num."',";
-            $sql.= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".$user->id;
-            $sql.= " WHERE rowid = ".$this->id." AND fk_statut = ".self::STATUS_DRAFT;
+        // Define new ref
+        if (! $error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) // empty should not happened, but when it occurs, the test save life
+        {
+        	$num = $this->getNextNumRef($soc);
+        }
+        else
+        {
+        	$num = $this->ref;
+        }
+        $this->newref = $num;
 
-            dol_syslog(get_class($this)."::valid", LOG_DEBUG);
-			$resql=$this->db->query($sql);
-			if (! $resql)
-			{
-				dol_print_error($this->db);
-				$error++;
-			}
+        $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
+        $sql.= " SET ref = '".$num."',";
+        $sql.= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".$user->id;
+        $sql.= " WHERE rowid = ".$this->id." AND fk_statut = ".self::STATUS_DRAFT;
 
-   			// Trigger calls
-			if (! $error && ! $notrigger)
-			{
-                // Call trigger
-                $result=$this->call_trigger('PROPAL_VALIDATE',$user);
-                if ($result < 0) { $error++; }
-                // End call triggers
-            }
+        dol_syslog(get_class($this)."::valid", LOG_DEBUG);
+		$resql=$this->db->query($sql);
+		if (! $resql)
+		{
+			dol_print_error($this->db);
+			$error++;
+		}
 
-            if (! $error)
-            {
-            	$this->oldref = $this->ref;
+		// Trigger calls
+		if (! $error && ! $notrigger)
+		{
+            // Call trigger
+            $result=$this->call_trigger('PROPAL_VALIDATE',$user);
+            if ($result < 0) { $error++; }
+            // End call triggers
+        }
 
-            	// Rename directory if dir was a temporary ref
-            	if (preg_match('/^[\(]?PROV/i', $this->ref))
-            	{
-            		// Rename of propal directory ($this->ref = old ref, $num = new ref)
-            		// to  not lose the linked files
-            		$oldref = dol_sanitizeFileName($this->ref);
-            		$newref = dol_sanitizeFileName($num);
-            		$dirsource = $conf->propal->dir_output.'/'.$oldref;
-            		$dirdest = $conf->propal->dir_output.'/'.$newref;
-
-            		if (file_exists($dirsource))
-            		{
-            			dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
-            			if (@rename($dirsource, $dirdest))
-            			{
-            				dol_syslog("Rename ok");
-            				// Rename docs starting with $oldref with $newref
-            				$listoffiles=dol_dir_list($conf->propal->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref,'/'));
-            				foreach($listoffiles as $fileentry)
-            				{
-            					$dirsource=$fileentry['name'];
-            					$dirdest=preg_replace('/^'.preg_quote($oldref,'/').'/',$newref, $dirsource);
-            					$dirsource=$fileentry['path'].'/'.$dirsource;
-            					$dirdest=$fileentry['path'].'/'.$dirdest;
-            					@rename($dirsource, $dirdest);
-            				}
-            			}
-            		}
-            	}
+        if (! $error)
+        {
+        	$this->oldref = $this->ref;
+
+        	// Rename directory if dir was a temporary ref
+        	if (preg_match('/^[\(]?PROV/i', $this->ref))
+        	{
+        		// Rename of propal directory ($this->ref = old ref, $num = new ref)
+        		// to  not lose the linked files
+        		$oldref = dol_sanitizeFileName($this->ref);
+        		$newref = dol_sanitizeFileName($num);
+        		$dirsource = $conf->propal->dir_output.'/'.$oldref;
+        		$dirdest = $conf->propal->dir_output.'/'.$newref;
+
+        		if (file_exists($dirsource))
+        		{
+        			dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
+        			if (@rename($dirsource, $dirdest))
+        			{
+        				dol_syslog("Rename ok");
+        				// Rename docs starting with $oldref with $newref
+        				$listoffiles=dol_dir_list($conf->propal->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref,'/'));
+        				foreach($listoffiles as $fileentry)
+        				{
+        					$dirsource=$fileentry['name'];
+        					$dirdest=preg_replace('/^'.preg_quote($oldref,'/').'/',$newref, $dirsource);
+        					$dirsource=$fileentry['path'].'/'.$dirsource;
+        					$dirdest=$fileentry['path'].'/'.$dirdest;
+        					@rename($dirsource, $dirdest);
+        				}
+        			}
+        		}
+        	}
 
-            	$this->ref=$num;
-            	$this->brouillon=0;
-            	$this->statut = self::STATUS_VALIDATED;
-            	$this->user_valid_id=$user->id;
-            	$this->datev=$now;
+        	$this->ref=$num;
+        	$this->brouillon=0;
+        	$this->statut = self::STATUS_VALIDATED;
+        	$this->user_valid_id=$user->id;
+        	$this->datev=$now;
 
-            	$this->db->commit();
-            	return 1;
-            }
-            else
-			{
-            	$this->db->rollback();
-            	return -1;
-            }
+        	$this->db->commit();
+        	return 1;
+        }
+        else
+		{
+        	$this->db->rollback();
+        	return -1;
         }
     }
 
diff --git a/htdocs/commande/class/api_orders.class.php b/htdocs/commande/class/api_orders.class.php
index 72d01c33bc8..ad3c17d1187 100644
--- a/htdocs/commande/class/api_orders.class.php
+++ b/htdocs/commande/class/api_orders.class.php
@@ -449,10 +449,11 @@ class Orders extends DolibarrApi
      * Error message: "Forbidden: Content type `text/plain` is not supported."
      * Workaround: send this in the body
      * {
-     *   "idwarehouse": 0
+     *   "idwarehouse": 0,
+     *   "notrigger": 0
      * }
      */
-    function validate($id, $idwarehouse=0)
+    function validate($id, $idwarehouse=0, $notrigger=0)
     {
         if(! DolibarrApiAccess::$user->rights->commande->creer) {
 			throw new RestException(401);
@@ -466,10 +467,14 @@ class Orders extends DolibarrApi
 			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
 		}
         
-        if( ! $this->commande->valid(DolibarrApiAccess::$user, $idwarehouse)) {
-            throw new RestException(500, 'Error when validate order');
-        }
-        
+		$result = $this->commande->valid(DolibarrApiAccess::$user, $idwarehouse, $notrigger);
+		if ($result == 0) {
+		    throw new RestException(500, 'Error nothing done. May be object is already validated');
+		}
+		if ($result < 0) {
+		    throw new RestException(500, 'Error when validating Order: '.$this->commande->error);
+		}
+		
         return array(
             'success' => array(
                 'code' => 200,
diff --git a/htdocs/commande/class/commande.class.php b/htdocs/commande/class/commande.class.php
index 108fcf90d84..c0d4ab66a81 100644
--- a/htdocs/commande/class/commande.class.php
+++ b/htdocs/commande/class/commande.class.php
@@ -260,7 +260,7 @@ class Commande extends CommonOrder
      *	@param		User	$user     		User making status change
      *	@param		int		$idwarehouse	Id of warehouse to use for stock decrease
      *  @param		int		$notrigger		1=Does not execute triggers, 0= execuete triggers
-     *	@return  	int						<=0 if OK, >0 if KO
+     *	@return  	int						<=0 if OK, 0=Nothing done, >0 if KO
      */
     function valid($user, $idwarehouse=0, $notrigger=0)
     {
@@ -272,7 +272,7 @@ class Commande extends CommonOrder
         // Protection
         if ($this->statut == self::STATUS_VALIDATED)
         {
-            dol_syslog(get_class($this)."::valid action abandonned: no draft status", LOG_WARNING);
+            dol_syslog(get_class($this)."::valid action abandonned: already validated", LOG_WARNING);
             return 0;
         }
 
-- 
GitLab