1<?php
2/* Copyright (C) 2015   Jean-François Ferry     <jfefe@aternatik.fr>
3 * Copyright (C) 2016	Laurent Destailleur		<eldy@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use Luracast\Restler\RestException;
20
21require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
22
23/**
24 * API class for orders
25 *
26 * @access protected
27 * @class  DolibarrApiAccess {@requires user,external}
28 */
29class Orders extends DolibarrApi
30{
31
32	/**
33	 * @var array   $FIELDS     Mandatory fields, checked when create and update object
34	 */
35	static $FIELDS = array(
36		'socid',
37		'date'
38	);
39
40	/**
41	 * @var Commande $commande {@type Commande}
42	 */
43	public $commande;
44
45	/**
46	 * Constructor
47	 */
48	public function __construct()
49	{
50		global $db, $conf;
51		$this->db = $db;
52		$this->commande = new Commande($this->db);
53	}
54
55	/**
56	 * Get properties of an order object by id
57	 *
58	 * Return an array with order informations
59	 *
60	 * @param       int         $id            ID of order
61	 * @param       int         $contact_list  0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id
62	 * @return 	array|mixed data without useless information
63	 *
64	 * @throws 	RestException
65	 */
66	public function get($id, $contact_list = 1)
67	{
68		return $this->_fetch($id, '', '', $contact_list);
69	}
70
71	/**
72	 * Get properties of an order object by ref
73	 *
74	 * Return an array with order informations
75	 *
76	 * @param       string		$ref			Ref of object
77	 * @param       int         $contact_list  0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id
78	 * @return 	array|mixed data without useless information
79	 *
80	 * @url GET    ref/{ref}
81	 *
82	 * @throws 	RestException
83	 */
84	public function getByRef($ref, $contact_list = 1)
85	{
86		return $this->_fetch('', $ref, '', $contact_list);
87	}
88
89	/**
90	 * Get properties of an order object by ref_ext
91	 *
92	 * Return an array with order informations
93	 *
94	 * @param       string		$ref_ext			External reference of object
95	 * @param       int         $contact_list  0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id
96	 * @return 	array|mixed data without useless information
97	 *
98	 * @url GET    ref_ext/{ref_ext}
99	 *
100	 * @throws 	RestException
101	 */
102	public function getByRefExt($ref_ext, $contact_list = 1)
103	{
104		return $this->_fetch('', '', $ref_ext, $contact_list);
105	}
106
107	/**
108	 * Get properties of an order object
109	 *
110	 * Return an array with order informations
111	 *
112	 * @param       int         $id            ID of order
113	 * @param		string		$ref			Ref of object
114	 * @param		string		$ref_ext		External reference of object
115	 * @param       int         $contact_list  0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id
116	 * @return 	array|mixed data without useless information
117	 *
118	 * @throws 	RestException
119	 */
120	private function _fetch($id, $ref = '', $ref_ext = '', $contact_list = 1)
121	{
122		if (!DolibarrApiAccess::$user->rights->commande->lire) {
123			throw new RestException(401);
124		}
125
126		$result = $this->commande->fetch($id, $ref, $ref_ext);
127		if (!$result) {
128			throw new RestException(404, 'Order not found');
129		}
130
131		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
132			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
133		}
134
135		// Add external contacts ids
136		$this->commande->contacts_ids = $this->commande->liste_contact(-1, 'external', $contact_list);
137		$this->commande->fetchObjectLinked();
138		return $this->_cleanObjectDatas($this->commande);
139	}
140
141	/**
142	 * List orders
143	 *
144	 * Get a list of orders
145	 *
146	 * @param string	       $sortfield	        Sort field
147	 * @param string	       $sortorder	        Sort order
148	 * @param int		       $limit		        Limit for list
149	 * @param int		       $page		        Page number
150	 * @param string   	       $thirdparty_ids	    Thirdparty ids to filter orders of (example '1' or '1,2,3') {@pattern /^[0-9,]*$/i}
151	 * @param string           $sqlfilters          Other criteria to filter answers separated by a comma. Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')"
152	 * @return  array                               Array of order objects
153	 *
154	 * @throws RestException 404 Not found
155	 * @throws RestException 503 Error
156	 */
157	public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $thirdparty_ids = '', $sqlfilters = '')
158	{
159		global $db, $conf;
160
161		$obj_ret = array();
162
163		// case of external user, $thirdparty_ids param is ignored and replaced by user's socid
164		$socids = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : $thirdparty_ids;
165
166		// If the internal user must only see his customers, force searching by him
167		$search_sale = 0;
168		if (!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) $search_sale = DolibarrApiAccess::$user->id;
169
170		$sql = "SELECT t.rowid";
171		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)
172		$sql .= " FROM ".MAIN_DB_PREFIX."commande as t";
173
174		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
175
176		$sql .= ' WHERE t.entity IN ('.getEntity('commande').')';
177		if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) $sql .= " AND t.fk_soc = sc.fk_soc";
178		if ($socids) $sql .= " AND t.fk_soc IN (".$socids.")";
179		if ($search_sale > 0) $sql .= " AND t.rowid = sc.fk_soc"; // Join for the needed table to filter by sale
180		// Insert sale filter
181		if ($search_sale > 0)
182		{
183			$sql .= " AND sc.fk_user = ".$search_sale;
184		}
185		// Add sql filters
186		if ($sqlfilters)
187		{
188			if (!DolibarrApi::_checkFilters($sqlfilters))
189			{
190				throw new RestException(503, 'Error when validating parameter sqlfilters '.$sqlfilters);
191			}
192			$regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^:\(\)]+)\)';
193			$sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")";
194		}
195
196		$sql .= $this->db->order($sortfield, $sortorder);
197		if ($limit) {
198			if ($page < 0)
199			{
200				$page = 0;
201			}
202			$offset = $limit * $page;
203
204			$sql .= $this->db->plimit($limit + 1, $offset);
205		}
206
207		dol_syslog("API Rest request");
208		$result = $this->db->query($sql);
209
210		if ($result)
211		{
212			$num = $this->db->num_rows($result);
213			$min = min($num, ($limit <= 0 ? $num : $limit));
214			$i = 0;
215			while ($i < $min)
216			{
217				$obj = $this->db->fetch_object($result);
218				$commande_static = new Commande($this->db);
219				if ($commande_static->fetch($obj->rowid)) {
220					// Add external contacts ids
221					$commande_static->contacts_ids = $commande_static->liste_contact(-1, 'external', 1);
222					$obj_ret[] = $this->_cleanObjectDatas($commande_static);
223				}
224				$i++;
225			}
226		} else {
227			throw new RestException(503, 'Error when retrieve commande list : '.$this->db->lasterror());
228		}
229		if (!count($obj_ret)) {
230			throw new RestException(404, 'No order found');
231		}
232		return $obj_ret;
233	}
234
235	/**
236	 * Create a sale order
237	 *
238	 * Exemple: { "socid": 2, "date": 1595196000, "type": 0, "lines": [{ "fk_product": 2, "qty": 1 }] }
239	 *
240	 * @param   array   $request_data   Request data
241	 * @return  int     ID of order
242	 */
243	public function post($request_data = null)
244	{
245		if (!DolibarrApiAccess::$user->rights->commande->creer) {
246			throw new RestException(401, "Insuffisant rights");
247		}
248		// Check mandatory fields
249		$result = $this->_validate($request_data);
250
251		foreach ($request_data as $field => $value) {
252			$this->commande->$field = $value;
253		}
254		/*if (isset($request_data["lines"])) {
255          $lines = array();
256          foreach ($request_data["lines"] as $line) {
257            array_push($lines, (object) $line);
258          }
259          $this->commande->lines = $lines;
260        }*/
261
262		if ($this->commande->create(DolibarrApiAccess::$user) < 0) {
263			throw new RestException(500, "Error creating order", array_merge(array($this->commande->error), $this->commande->errors));
264		}
265
266		return $this->commande->id;
267	}
268
269	/**
270	 * Get lines of an order
271	 *
272	 * @param int   $id             Id of order
273	 *
274	 * @url	GET {id}/lines
275	 *
276	 * @return int
277	 */
278	public function getLines($id)
279	{
280		if (!DolibarrApiAccess::$user->rights->commande->lire) {
281			throw new RestException(401);
282		}
283
284		$result = $this->commande->fetch($id);
285		if (!$result) {
286			throw new RestException(404, 'Order not found');
287		}
288
289		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
290			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
291		}
292		$this->commande->getLinesArray();
293		$result = array();
294		foreach ($this->commande->lines as $line) {
295			array_push($result, $this->_cleanObjectDatas($line));
296		}
297		return $result;
298	}
299
300	/**
301	 * Add a line to given order
302	 *
303	 * @param int   $id             Id of order to update
304	 * @param array $request_data   OrderLine data
305	 *
306	 * @url	POST {id}/lines
307	 *
308	 * @return int
309	 */
310	public function postLine($id, $request_data = null)
311	{
312		if (!DolibarrApiAccess::$user->rights->commande->creer) {
313			throw new RestException(401);
314		}
315
316		$result = $this->commande->fetch($id);
317		if (!$result) {
318			throw new RestException(404, 'Order not found');
319		}
320
321		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
322			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
323		}
324		$request_data = (object) $request_data;
325		$updateRes = $this->commande->addline(
326						$request_data->desc,
327						$request_data->subprice,
328						$request_data->qty,
329						$request_data->tva_tx,
330						$request_data->localtax1_tx,
331						$request_data->localtax2_tx,
332						$request_data->fk_product,
333						$request_data->remise_percent,
334						$request_data->info_bits,
335						$request_data->fk_remise_except,
336						'HT',
337						0,
338						$request_data->date_start,
339						$request_data->date_end,
340						$request_data->product_type,
341						$request_data->rang,
342						$request_data->special_code,
343						$request_data->fk_parent_line,
344						$request_data->fk_fournprice,
345						$request_data->pa_ht,
346						$request_data->label,
347						$request_data->array_options,
348						$request_data->fk_unit,
349						$request_data->origin,
350						$request_data->origin_id,
351						$request_data->multicurrency_subprice,
352						$request_data->ref_ext
353		);
354
355		if ($updateRes > 0) {
356			return $updateRes;
357		} else {
358			throw new RestException(400, $this->commande->error);
359		}
360	}
361
362	/**
363	 * Update a line to given order
364	 *
365	 * @param int   $id             Id of order to update
366	 * @param int   $lineid         Id of line to update
367	 * @param array $request_data   OrderLine data
368	 *
369	 * @url	PUT {id}/lines/{lineid}
370	 *
371	 * @return array|bool
372	 */
373	public function putLine($id, $lineid, $request_data = null)
374	{
375		if (!DolibarrApiAccess::$user->rights->commande->creer) {
376			throw new RestException(401);
377		}
378
379		$result = $this->commande->fetch($id);
380		if (!$result) {
381			throw new RestException(404, 'Order not found');
382		}
383
384		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
385			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
386		}
387		$request_data = (object) $request_data;
388		$updateRes = $this->commande->updateline(
389			$lineid,
390			$request_data->desc,
391			$request_data->subprice,
392			$request_data->qty,
393			$request_data->remise_percent,
394			$request_data->tva_tx,
395			$request_data->localtax1_tx,
396			$request_data->localtax2_tx,
397			'HT',
398			$request_data->info_bits,
399			$request_data->date_start,
400			$request_data->date_end,
401			$request_data->product_type,
402			$request_data->fk_parent_line,
403			0,
404			$request_data->fk_fournprice,
405			$request_data->pa_ht,
406			$request_data->label,
407			$request_data->special_code,
408			$request_data->array_options,
409			$request_data->fk_unit,
410	  		$request_data->multicurrency_subprice,
411			0,
412	  		$request_data->ref_ext
413		);
414
415		if ($updateRes > 0) {
416			$result = $this->get($id);
417			unset($result->line);
418			return $this->_cleanObjectDatas($result);
419		}
420		return false;
421	}
422
423	/**
424	 * Delete a line to given order
425	 *
426	 *
427	 * @param int   $id             Id of order to update
428	 * @param int   $lineid         Id of line to delete
429	 *
430	 * @url	DELETE {id}/lines/{lineid}
431	 *
432	 * @return int
433	 *
434	 * @throws RestException 401
435	 * @throws RestException 404
436	 */
437	public function deleteLine($id, $lineid)
438	{
439		if (!DolibarrApiAccess::$user->rights->commande->creer) {
440			throw new RestException(401);
441		}
442
443		$result = $this->commande->fetch($id);
444		if (!$result) {
445			throw new RestException(404, 'Order not found');
446		}
447
448		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
449			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
450		}
451
452		// TODO Check the lineid $lineid is a line of ojbect
453
454		$updateRes = $this->commande->deleteline(DolibarrApiAccess::$user, $lineid);
455		if ($updateRes > 0) {
456			return $this->get($id);
457		} else {
458			throw new RestException(405, $this->commande->error);
459		}
460	}
461
462	/**
463	 * Get contacts of given order
464	 *
465	 * Return an array with contact informations
466	 *
467	 * @param int    $id   ID of order
468	 * @param string $type Type of the contact (BILLING, SHIPPING, CUSTOMER)
469	 *
470	 * @url	GET {id}/contacts
471	 *
472	 * @return 	array data without useless information
473	 *
474	 * @throws 	RestException
475	 */
476	public function getContacts($id, $type = '')
477	{
478		if (!DolibarrApiAccess::$user->rights->commande->lire) {
479			throw new RestException(401);
480		}
481
482		$result = $this->commande->fetch($id);
483		if (!$result) {
484			throw new RestException(404, 'Order not found');
485		}
486
487		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
488			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
489		}
490
491		$contacts = $this->commande->liste_contact(-1, 'external', 0, $type);
492
493		return $this->_cleanObjectDatas($contacts);
494	}
495
496	/**
497	 * Add a contact type of given order
498	 *
499	 * @param int    $id             Id of order to update
500	 * @param int    $contactid      Id of contact to add
501	 * @param string $type           Type of the contact (BILLING, SHIPPING, CUSTOMER)
502	 *
503	 * @url	POST {id}/contact/{contactid}/{type}
504	 *
505	 * @return int
506	 *
507	 * @throws RestException 401
508	 * @throws RestException 404
509	 */
510	public function postContact($id, $contactid, $type)
511	{
512		if (!DolibarrApiAccess::$user->rights->commande->creer) {
513			throw new RestException(401);
514		}
515
516		$result = $this->commande->fetch($id);
517		if (!$result) {
518			throw new RestException(404, 'Order not found');
519		}
520
521		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
522			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
523		}
524
525		$result = $this->commande->add_contact($contactid, $type, 'external');
526
527		if ($result < 0) {
528			throw new RestException(500, 'Error when added the contact');
529		}
530
531		if ($result == 0) {
532			throw new RestException(304, 'contact already added');
533		}
534
535		return array(
536			'success' => array(
537				'code' => 200,
538				'message' => 'Contact linked to the order'
539			)
540		);
541	}
542
543	/**
544	 * Unlink a contact type of given order
545	 *
546	 * @param int    $id             Id of order to update
547	 * @param int    $contactid      Id of contact
548	 * @param string $type           Type of the contact (BILLING, SHIPPING, CUSTOMER).
549	 *
550	 * @url	DELETE {id}/contact/{contactid}/{type}
551	 *
552	 * @return int
553	 *
554	 * @throws RestException 401
555	 * @throws RestException 404
556	 * @throws RestException 500
557	 */
558	public function deleteContact($id, $contactid, $type)
559	{
560		if (!DolibarrApiAccess::$user->rights->commande->creer) {
561			throw new RestException(401);
562		}
563
564		$result = $this->commande->fetch($id);
565		if (!$result) {
566			throw new RestException(404, 'Order not found');
567		}
568
569		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
570			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
571		}
572
573		 $contacts = $this->commande->liste_contact();
574
575		foreach ($contacts as $contact) {
576			if ($contact['id'] == $contactid && $contact['code'] == $type) {
577				$result = $this->commande->delete_contact($contact['rowid']);
578
579				if (!$result) {
580					throw new RestException(500, 'Error when deleted the contact');
581				}
582			}
583		}
584
585		return array(
586			'success' => array(
587				'code' => 200,
588				'message' => 'Contact unlinked from order'
589			)
590		);
591	}
592
593	/**
594	 * Update order general fields (won't touch lines of order)
595	 *
596	 * @param int   $id             Id of order to update
597	 * @param array $request_data   Datas
598	 *
599	 * @return int
600	 */
601	public function put($id, $request_data = null)
602	{
603		if (!DolibarrApiAccess::$user->rights->commande->creer) {
604			throw new RestException(401);
605		}
606
607		$result = $this->commande->fetch($id);
608		if (!$result) {
609			throw new RestException(404, 'Order not found');
610		}
611
612		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
613			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
614		}
615		foreach ($request_data as $field => $value) {
616			if ($field == 'id') continue;
617			$this->commande->$field = $value;
618		}
619
620		// Update availability
621		if (!empty($this->commande->availability_id)) {
622			if ($this->commande->availability($this->commande->availability_id) < 0)
623			throw new RestException(400, 'Error while updating availability');
624		}
625
626		if ($this->commande->update(DolibarrApiAccess::$user) > 0)
627		{
628			return $this->get($id);
629		} else {
630			throw new RestException(500, $this->commande->error);
631		}
632	}
633
634	/**
635	 * Delete order
636	 *
637	 * @param   int     $id         Order ID
638	 * @return  array
639	 */
640	public function delete($id)
641	{
642		if (!DolibarrApiAccess::$user->rights->commande->supprimer) {
643			throw new RestException(401);
644		}
645		$result = $this->commande->fetch($id);
646		if (!$result) {
647			throw new RestException(404, 'Order not found');
648		}
649
650		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
651			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
652		}
653
654		if (!$this->commande->delete(DolibarrApiAccess::$user)) {
655			throw new RestException(500, 'Error when deleting order : '.$this->commande->error);
656		}
657
658		return array(
659			'success' => array(
660				'code' => 200,
661				'message' => 'Order deleted'
662			)
663		);
664	}
665
666	/**
667	 * Validate an order
668	 *
669	 * If you get a bad value for param notrigger check, provide this in body
670	 * {
671	 *   "idwarehouse": 0,
672	 *   "notrigger": 0
673	 * }
674	 *
675	 * @param   int $id             Order ID
676	 * @param   int $idwarehouse    Warehouse ID
677	 * @param   int $notrigger      1=Does not execute triggers, 0= execute triggers
678	 *
679	 * @url POST    {id}/validate
680	 *
681	 * @throws RestException 304
682	 * @throws RestException 401
683	 * @throws RestException 404
684	 * @throws RestException 500
685	 *
686	 * @return  array
687	 */
688	public function validate($id, $idwarehouse = 0, $notrigger = 0)
689	{
690		if (!DolibarrApiAccess::$user->rights->commande->creer) {
691			throw new RestException(401);
692		}
693		$result = $this->commande->fetch($id);
694		if (!$result) {
695			throw new RestException(404, 'Order not found');
696		}
697
698		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
699			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
700		}
701
702		$result = $this->commande->valid(DolibarrApiAccess::$user, $idwarehouse, $notrigger);
703		if ($result == 0) {
704			throw new RestException(304, 'Error nothing done. May be object is already validated');
705		}
706		if ($result < 0) {
707			throw new RestException(500, 'Error when validating Order: '.$this->commande->error);
708		}
709		$result = $this->commande->fetch($id);
710		if (!$result) {
711			throw new RestException(404, 'Order not found');
712		}
713
714		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
715			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
716		}
717
718		$this->commande->fetchObjectLinked();
719
720		return $this->_cleanObjectDatas($this->commande);
721	}
722
723	/**
724	 *  Tag the order as validated (opened)
725	 *
726	 *  Function used when order is reopend after being closed.
727	 *
728	 * @param int   $id       Id of the order
729	 *
730	 * @url     POST {id}/reopen
731	 *
732	 * @return int
733	 *
734	 * @throws RestException 304
735	 * @throws RestException 400
736	 * @throws RestException 401
737	 * @throws RestException 404
738	 * @throws RestException 405
739	 */
740	public function reopen($id)
741	{
742
743		if (!DolibarrApiAccess::$user->rights->commande->creer) {
744			throw new RestException(401);
745		}
746		if (empty($id)) {
747			throw new RestException(400, 'Order ID is mandatory');
748		}
749		$result = $this->commande->fetch($id);
750		if (!$result) {
751			throw new RestException(404, 'Order not found');
752		}
753
754		$result = $this->commande->set_reopen(DolibarrApiAccess::$user);
755		if ($result < 0) {
756			throw new RestException(405, $this->commande->error);
757		} elseif ($result == 0) {
758			throw new RestException(304);
759		}
760
761		return $result;
762	}
763
764	/**
765	 * Classify the order as invoiced. Could be also called setbilled
766	 *
767	 * @param int   $id           Id of the order
768	 *
769	 * @url     POST {id}/setinvoiced
770	 *
771	 * @return int
772	 *
773	 * @throws RestException 400
774	 * @throws RestException 401
775	 * @throws RestException 404
776	 * @throws RestException 405
777	 */
778	public function setinvoiced($id)
779	{
780
781		if (!DolibarrApiAccess::$user->rights->commande->creer) {
782			throw new RestException(401);
783		}
784		if (empty($id)) {
785			throw new RestException(400, 'Order ID is mandatory');
786		}
787		$result = $this->commande->fetch($id);
788		if (!$result) {
789			throw new RestException(404, 'Order not found');
790		}
791
792		$result = $this->commande->classifyBilled(DolibarrApiAccess::$user);
793		if ($result < 0) {
794			throw new RestException(400, $this->commande->error);
795		}
796
797		$result = $this->commande->fetch($id);
798		if (!$result) {
799			throw new RestException(404, 'Order not found');
800		}
801
802		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
803			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
804		}
805
806		$this->commande->fetchObjectLinked();
807
808		return $this->_cleanObjectDatas($this->commande);
809	}
810
811	/**
812	 * Close an order (Classify it as "Delivered")
813	 *
814	 * @param   int     $id             Order ID
815	 * @param   int     $notrigger      Disabled triggers
816	 *
817	 * @url POST    {id}/close
818	 *
819	 * @return  int
820	 */
821	public function close($id, $notrigger = 0)
822	{
823		if (!DolibarrApiAccess::$user->rights->commande->creer) {
824			throw new RestException(401);
825		}
826		$result = $this->commande->fetch($id);
827		if (!$result) {
828			throw new RestException(404, 'Order not found');
829		}
830
831		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
832			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
833		}
834
835		$result = $this->commande->cloture(DolibarrApiAccess::$user, $notrigger);
836		if ($result == 0) {
837			throw new RestException(304, 'Error nothing done. May be object is already closed');
838		}
839		if ($result < 0) {
840			throw new RestException(500, 'Error when closing Order: '.$this->commande->error);
841		}
842
843		$result = $this->commande->fetch($id);
844		if (!$result) {
845			throw new RestException(404, 'Order not found');
846		}
847
848		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
849			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
850		}
851
852		$this->commande->fetchObjectLinked();
853
854		return $this->_cleanObjectDatas($this->commande);
855	}
856
857	/**
858	 * Set an order to draft
859	 *
860	 * @param   int     $id             Order ID
861	 * @param   int 	$idwarehouse    Warehouse ID to use for stock change (Used only if option STOCK_CALCULATE_ON_VALIDATE_ORDER is on)
862	 *
863	 * @url POST    {id}/settodraft
864	 *
865	 * @return  array
866	 */
867	public function settodraft($id, $idwarehouse = -1)
868	{
869		if (!DolibarrApiAccess::$user->rights->commande->creer) {
870			throw new RestException(401);
871		}
872		$result = $this->commande->fetch($id);
873		if (!$result) {
874			throw new RestException(404, 'Order not found');
875		}
876
877		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
878			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
879		}
880
881		$result = $this->commande->setDraft(DolibarrApiAccess::$user, $idwarehouse);
882		if ($result == 0) {
883			throw new RestException(304, 'Nothing done. May be object is already closed');
884		}
885		if ($result < 0) {
886			throw new RestException(500, 'Error when closing Order: '.$this->commande->error);
887		}
888
889		$result = $this->commande->fetch($id);
890		if (!$result) {
891			throw new RestException(404, 'Order not found');
892		}
893
894		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
895			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
896		}
897
898		$this->commande->fetchObjectLinked();
899
900		return $this->_cleanObjectDatas($this->commande);
901	}
902
903
904	/**
905	 * Create an order using an existing proposal.
906	 *
907	 *
908	 * @param int   $proposalid       Id of the proposal
909	 *
910	 * @url     POST /createfromproposal/{proposalid}
911	 *
912	 * @return int
913	 * @throws RestException 400
914	 * @throws RestException 401
915	 * @throws RestException 404
916	 * @throws RestException 405
917	 */
918	public function createOrderFromProposal($proposalid)
919	{
920
921		require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
922
923		if (!DolibarrApiAccess::$user->rights->propal->lire) {
924			throw new RestException(401);
925		}
926		if (!DolibarrApiAccess::$user->rights->commande->creer) {
927			throw new RestException(401);
928		}
929		if (empty($proposalid)) {
930			throw new RestException(400, 'Proposal ID is mandatory');
931		}
932
933		$propal = new Propal($this->db);
934		$result = $propal->fetch($proposalid);
935		if (!$result) {
936			throw new RestException(404, 'Proposal not found');
937		}
938
939		$result = $this->commande->createFromProposal($propal, DolibarrApiAccess::$user);
940		if ($result < 0) {
941			throw new RestException(405, $this->commande->error);
942		}
943		$this->commande->fetchObjectLinked();
944
945		return $this->_cleanObjectDatas($this->commande);
946	}
947
948
949	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
950	/**
951	 * Clean sensible object datas
952	 *
953	 * @param   Object  $object     Object to clean
954	 * @return  Object              Object with cleaned properties
955	 */
956	protected function _cleanObjectDatas($object)
957	{
958		// phpcs:enable
959		$object = parent::_cleanObjectDatas($object);
960
961		unset($object->note);
962		unset($object->address);
963		unset($object->barcode_type);
964		unset($object->barcode_type_code);
965		unset($object->barcode_type_label);
966		unset($object->barcode_type_coder);
967
968		return $object;
969	}
970
971	/**
972	 * Validate fields before create or update object
973	 *
974	 * @param   array           $data   Array with data to verify
975	 * @return  array
976	 * @throws  RestException
977	 */
978	private function _validate($data)
979	{
980		$commande = array();
981		foreach (Orders::$FIELDS as $field) {
982			if (!isset($data[$field]))
983				throw new RestException(400, $field." field missing");
984			$commande[$field] = $data[$field];
985		}
986		return $commande;
987	}
988}
989