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		if (!DolibarrApiAccess::$user->rights->commande->lire) {
162			throw new RestException(401);
163		}
164
165		$obj_ret = array();
166
167		// case of external user, $thirdparty_ids param is ignored and replaced by user's socid
168		$socids = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : $thirdparty_ids;
169
170		// If the internal user must only see his customers, force searching by him
171		$search_sale = 0;
172		if (!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) {
173			$search_sale = DolibarrApiAccess::$user->id;
174		}
175
176		$sql = "SELECT t.rowid";
177		if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) {
178			$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)
179		}
180		$sql .= " FROM ".MAIN_DB_PREFIX."commande as t";
181
182		if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) {
183			$sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale
184		}
185
186		$sql .= ' WHERE t.entity IN ('.getEntity('commande').')';
187		if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) {
188			$sql .= " AND t.fk_soc = sc.fk_soc";
189		}
190		if ($socids) {
191			$sql .= " AND t.fk_soc IN (".$this->db->sanitize($socids).")";
192		}
193		if ($search_sale > 0) {
194			$sql .= " AND t.rowid = sc.fk_soc"; // Join for the needed table to filter by sale
195		}
196		// Insert sale filter
197		if ($search_sale > 0) {
198			$sql .= " AND sc.fk_user = ".((int) $search_sale);
199		}
200		// Add sql filters
201		if ($sqlfilters) {
202			if (!DolibarrApi::_checkFilters($sqlfilters)) {
203				throw new RestException(503, 'Error when validating parameter sqlfilters '.$sqlfilters);
204			}
205			$regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';
206			$sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")";
207		}
208
209		$sql .= $this->db->order($sortfield, $sortorder);
210		if ($limit) {
211			if ($page < 0) {
212				$page = 0;
213			}
214			$offset = $limit * $page;
215
216			$sql .= $this->db->plimit($limit + 1, $offset);
217		}
218
219		dol_syslog("API Rest request");
220		$result = $this->db->query($sql);
221
222		if ($result) {
223			$num = $this->db->num_rows($result);
224			$min = min($num, ($limit <= 0 ? $num : $limit));
225			$i = 0;
226			while ($i < $min) {
227				$obj = $this->db->fetch_object($result);
228				$commande_static = new Commande($this->db);
229				if ($commande_static->fetch($obj->rowid)) {
230					// Add external contacts ids
231					$commande_static->contacts_ids = $commande_static->liste_contact(-1, 'external', 1);
232					$obj_ret[] = $this->_cleanObjectDatas($commande_static);
233				}
234				$i++;
235			}
236		} else {
237			throw new RestException(503, 'Error when retrieve commande list : '.$this->db->lasterror());
238		}
239		if (!count($obj_ret)) {
240			throw new RestException(404, 'No order found');
241		}
242		return $obj_ret;
243	}
244
245	/**
246	 * Create a sale order
247	 *
248	 * Exemple: { "socid": 2, "date": 1595196000, "type": 0, "lines": [{ "fk_product": 2, "qty": 1 }] }
249	 *
250	 * @param   array   $request_data   Request data
251	 * @return  int     ID of order
252	 */
253	public function post($request_data = null)
254	{
255		if (!DolibarrApiAccess::$user->rights->commande->creer) {
256			throw new RestException(401, "Insuffisant rights");
257		}
258		// Check mandatory fields
259		$result = $this->_validate($request_data);
260
261		foreach ($request_data as $field => $value) {
262			$this->commande->$field = $value;
263		}
264		/*if (isset($request_data["lines"])) {
265		  $lines = array();
266		  foreach ($request_data["lines"] as $line) {
267			array_push($lines, (object) $line);
268		  }
269		  $this->commande->lines = $lines;
270		}*/
271
272		if ($this->commande->create(DolibarrApiAccess::$user) < 0) {
273			throw new RestException(500, "Error creating order", array_merge(array($this->commande->error), $this->commande->errors));
274		}
275
276		return $this->commande->id;
277	}
278
279	/**
280	 * Get lines of an order
281	 *
282	 * @param int   $id             Id of order
283	 *
284	 * @url	GET {id}/lines
285	 *
286	 * @return int
287	 */
288	public function getLines($id)
289	{
290		if (!DolibarrApiAccess::$user->rights->commande->lire) {
291			throw new RestException(401);
292		}
293
294		$result = $this->commande->fetch($id);
295		if (!$result) {
296			throw new RestException(404, 'Order not found');
297		}
298
299		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
300			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
301		}
302		$this->commande->getLinesArray();
303		$result = array();
304		foreach ($this->commande->lines as $line) {
305			array_push($result, $this->_cleanObjectDatas($line));
306		}
307		return $result;
308	}
309
310	/**
311	 * Add a line to given order
312	 *
313	 * @param int   $id             Id of order to update
314	 * @param array $request_data   OrderLine data
315	 *
316	 * @url	POST {id}/lines
317	 *
318	 * @return int
319	 */
320	public function postLine($id, $request_data = null)
321	{
322		if (!DolibarrApiAccess::$user->rights->commande->creer) {
323			throw new RestException(401);
324		}
325
326		$result = $this->commande->fetch($id);
327		if (!$result) {
328			throw new RestException(404, 'Order not found');
329		}
330
331		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
332			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
333		}
334
335		$request_data = (object) $request_data;
336
337		$request_data->desc = checkVal($request_data->desc, 'restricthtml');
338		$request_data->label = checkVal($request_data->label);
339
340		$updateRes = $this->commande->addline(
341			$request_data->desc,
342			$request_data->subprice,
343			$request_data->qty,
344			$request_data->tva_tx,
345			$request_data->localtax1_tx,
346			$request_data->localtax2_tx,
347			$request_data->fk_product,
348			$request_data->remise_percent,
349			$request_data->info_bits,
350			$request_data->fk_remise_except,
351			$request_data->price_base_type ? $request_data->price_base_type : 'HT',
352			$request_data->subprice,
353			$request_data->date_start,
354			$request_data->date_end,
355			$request_data->product_type,
356			$request_data->rang,
357			$request_data->special_code,
358			$request_data->fk_parent_line,
359			$request_data->fk_fournprice,
360			$request_data->pa_ht,
361			$request_data->label,
362			$request_data->array_options,
363			$request_data->fk_unit,
364			$request_data->origin,
365			$request_data->origin_id,
366			$request_data->multicurrency_subprice,
367			$request_data->ref_ext
368		);
369
370		if ($updateRes > 0) {
371			return $updateRes;
372		} else {
373			throw new RestException(400, $this->commande->error);
374		}
375	}
376
377	/**
378	 * Update a line to given order
379	 *
380	 * @param int   $id             Id of order to update
381	 * @param int   $lineid         Id of line to update
382	 * @param array $request_data   OrderLine data
383	 *
384	 * @url	PUT {id}/lines/{lineid}
385	 *
386	 * @return array|bool
387	 */
388	public function putLine($id, $lineid, $request_data = null)
389	{
390		if (!DolibarrApiAccess::$user->rights->commande->creer) {
391			throw new RestException(401);
392		}
393
394		$result = $this->commande->fetch($id);
395		if (!$result) {
396			throw new RestException(404, 'Order not found');
397		}
398
399		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
400			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
401		}
402
403		$request_data = (object) $request_data;
404
405		$request_data->desc = checkVal($request_data->desc, 'restricthtml');
406		$request_data->label = checkVal($request_data->label);
407
408		$updateRes = $this->commande->updateline(
409			$lineid,
410			$request_data->desc,
411			$request_data->subprice,
412			$request_data->qty,
413			$request_data->remise_percent,
414			$request_data->tva_tx,
415			$request_data->localtax1_tx,
416			$request_data->localtax2_tx,
417			$request_data->price_base_type ? $request_data->price_base_type : 'HT',
418			$request_data->info_bits,
419			$request_data->date_start,
420			$request_data->date_end,
421			$request_data->product_type,
422			$request_data->fk_parent_line,
423			0,
424			$request_data->fk_fournprice,
425			$request_data->pa_ht,
426			$request_data->label,
427			$request_data->special_code,
428			$request_data->array_options,
429			$request_data->fk_unit,
430			$request_data->multicurrency_subprice,
431			0,
432			$request_data->ref_ext
433		);
434
435		if ($updateRes > 0) {
436			$result = $this->get($id);
437			unset($result->line);
438			return $this->_cleanObjectDatas($result);
439		}
440		return false;
441	}
442
443	/**
444	 * Delete a line to given order
445	 *
446	 *
447	 * @param int   $id             Id of order to update
448	 * @param int   $lineid         Id of line to delete
449	 *
450	 * @url	DELETE {id}/lines/{lineid}
451	 *
452	 * @return int
453	 *
454	 * @throws RestException 401
455	 * @throws RestException 404
456	 */
457	public function deleteLine($id, $lineid)
458	{
459		if (!DolibarrApiAccess::$user->rights->commande->creer) {
460			throw new RestException(401);
461		}
462
463		$result = $this->commande->fetch($id);
464		if (!$result) {
465			throw new RestException(404, 'Order not found');
466		}
467
468		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
469			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
470		}
471
472		// TODO Check the lineid $lineid is a line of ojbect
473
474		$updateRes = $this->commande->deleteline(DolibarrApiAccess::$user, $lineid);
475		if ($updateRes > 0) {
476			return $this->get($id);
477		} else {
478			throw new RestException(405, $this->commande->error);
479		}
480	}
481
482	/**
483	 * Get contacts of given order
484	 *
485	 * Return an array with contact informations
486	 *
487	 * @param int    $id   ID of order
488	 * @param string $type Type of the contact (BILLING, SHIPPING, CUSTOMER)
489	 *
490	 * @url	GET {id}/contacts
491	 *
492	 * @return 	array data without useless information
493	 *
494	 * @throws 	RestException
495	 */
496	public function getContacts($id, $type = '')
497	{
498		if (!DolibarrApiAccess::$user->rights->commande->lire) {
499			throw new RestException(401);
500		}
501
502		$result = $this->commande->fetch($id);
503		if (!$result) {
504			throw new RestException(404, 'Order not found');
505		}
506
507		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
508			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
509		}
510
511		$contacts = $this->commande->liste_contact(-1, 'external', 0, $type);
512
513		return $this->_cleanObjectDatas($contacts);
514	}
515
516	/**
517	 * Add a contact type of given order
518	 *
519	 * @param int    $id             Id of order to update
520	 * @param int    $contactid      Id of contact to add
521	 * @param string $type           Type of the contact (BILLING, SHIPPING, CUSTOMER)
522	 *
523	 * @url	POST {id}/contact/{contactid}/{type}
524	 *
525	 * @return int
526	 *
527	 * @throws RestException 401
528	 * @throws RestException 404
529	 */
530	public function postContact($id, $contactid, $type)
531	{
532		if (!DolibarrApiAccess::$user->rights->commande->creer) {
533			throw new RestException(401);
534		}
535
536		$result = $this->commande->fetch($id);
537		if (!$result) {
538			throw new RestException(404, 'Order not found');
539		}
540
541		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
542			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
543		}
544
545		$result = $this->commande->add_contact($contactid, $type, 'external');
546
547		if ($result < 0) {
548			throw new RestException(500, 'Error when added the contact');
549		}
550
551		if ($result == 0) {
552			throw new RestException(304, 'contact already added');
553		}
554
555		return array(
556			'success' => array(
557				'code' => 200,
558				'message' => 'Contact linked to the order'
559			)
560		);
561	}
562
563	/**
564	 * Unlink a contact type of given order
565	 *
566	 * @param int    $id             Id of order to update
567	 * @param int    $contactid      Id of contact
568	 * @param string $type           Type of the contact (BILLING, SHIPPING, CUSTOMER).
569	 *
570	 * @url	DELETE {id}/contact/{contactid}/{type}
571	 *
572	 * @return int
573	 *
574	 * @throws RestException 401
575	 * @throws RestException 404
576	 * @throws RestException 500
577	 */
578	public function deleteContact($id, $contactid, $type)
579	{
580		if (!DolibarrApiAccess::$user->rights->commande->creer) {
581			throw new RestException(401);
582		}
583
584		$result = $this->commande->fetch($id);
585		if (!$result) {
586			throw new RestException(404, 'Order not found');
587		}
588
589		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
590			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
591		}
592
593		 $contacts = $this->commande->liste_contact();
594
595		foreach ($contacts as $contact) {
596			if ($contact['id'] == $contactid && $contact['code'] == $type) {
597				$result = $this->commande->delete_contact($contact['rowid']);
598
599				if (!$result) {
600					throw new RestException(500, 'Error when deleted the contact');
601				}
602			}
603		}
604
605		return array(
606			'success' => array(
607				'code' => 200,
608				'message' => 'Contact unlinked from order'
609			)
610		);
611	}
612
613	/**
614	 * Update order general fields (won't touch lines of order)
615	 *
616	 * @param int   $id             Id of order to update
617	 * @param array $request_data   Datas
618	 *
619	 * @return int
620	 */
621	public function put($id, $request_data = null)
622	{
623		if (!DolibarrApiAccess::$user->rights->commande->creer) {
624			throw new RestException(401);
625		}
626
627		$result = $this->commande->fetch($id);
628		if (!$result) {
629			throw new RestException(404, 'Order not found');
630		}
631
632		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
633			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
634		}
635		foreach ($request_data as $field => $value) {
636			if ($field == 'id') {
637				continue;
638			}
639			$this->commande->$field = $value;
640		}
641
642		// Update availability
643		if (!empty($this->commande->availability_id)) {
644			if ($this->commande->availability($this->commande->availability_id) < 0) {
645				throw new RestException(400, 'Error while updating availability');
646			}
647		}
648
649		if ($this->commande->update(DolibarrApiAccess::$user) > 0) {
650			return $this->get($id);
651		} else {
652			throw new RestException(500, $this->commande->error);
653		}
654	}
655
656	/**
657	 * Delete order
658	 *
659	 * @param   int     $id         Order ID
660	 * @return  array
661	 */
662	public function delete($id)
663	{
664		if (!DolibarrApiAccess::$user->rights->commande->supprimer) {
665			throw new RestException(401);
666		}
667		$result = $this->commande->fetch($id);
668		if (!$result) {
669			throw new RestException(404, 'Order not found');
670		}
671
672		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
673			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
674		}
675
676		if (!$this->commande->delete(DolibarrApiAccess::$user)) {
677			throw new RestException(500, 'Error when deleting order : '.$this->commande->error);
678		}
679
680		return array(
681			'success' => array(
682				'code' => 200,
683				'message' => 'Order deleted'
684			)
685		);
686	}
687
688	/**
689	 * Validate an order
690	 *
691	 * If you get a bad value for param notrigger check, provide this in body
692	 * {
693	 *   "idwarehouse": 0,
694	 *   "notrigger": 0
695	 * }
696	 *
697	 * @param   int $id             Order ID
698	 * @param   int $idwarehouse    Warehouse ID
699	 * @param   int $notrigger      1=Does not execute triggers, 0= execute triggers
700	 *
701	 * @url POST    {id}/validate
702	 *
703	 * @throws RestException 304
704	 * @throws RestException 401
705	 * @throws RestException 404
706	 * @throws RestException 500
707	 *
708	 * @return  array
709	 */
710	public function validate($id, $idwarehouse = 0, $notrigger = 0)
711	{
712		if (!DolibarrApiAccess::$user->rights->commande->creer) {
713			throw new RestException(401);
714		}
715		$result = $this->commande->fetch($id);
716		if (!$result) {
717			throw new RestException(404, 'Order not found');
718		}
719
720		$result = $this->commande->fetch_thirdparty(); // do not check result, as failure is not fatal (used only for mail notification substitutes)
721
722		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
723			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
724		}
725
726		$result = $this->commande->valid(DolibarrApiAccess::$user, $idwarehouse, $notrigger);
727		if ($result == 0) {
728			throw new RestException(304, 'Error nothing done. May be object is already validated');
729		}
730		if ($result < 0) {
731			throw new RestException(500, 'Error when validating Order: '.$this->commande->error);
732		}
733		$result = $this->commande->fetch($id);
734
735		$this->commande->fetchObjectLinked();
736
737		return $this->_cleanObjectDatas($this->commande);
738	}
739
740	/**
741	 *  Tag the order as validated (opened)
742	 *
743	 *  Function used when order is reopend after being closed.
744	 *
745	 * @param int   $id       Id of the order
746	 *
747	 * @url     POST {id}/reopen
748	 *
749	 * @return int
750	 *
751	 * @throws RestException 304
752	 * @throws RestException 400
753	 * @throws RestException 401
754	 * @throws RestException 404
755	 * @throws RestException 405
756	 */
757	public function reopen($id)
758	{
759
760		if (!DolibarrApiAccess::$user->rights->commande->creer) {
761			throw new RestException(401);
762		}
763		if (empty($id)) {
764			throw new RestException(400, 'Order ID is mandatory');
765		}
766		$result = $this->commande->fetch($id);
767		if (!$result) {
768			throw new RestException(404, 'Order not found');
769		}
770
771		$result = $this->commande->set_reopen(DolibarrApiAccess::$user);
772		if ($result < 0) {
773			throw new RestException(405, $this->commande->error);
774		} elseif ($result == 0) {
775			throw new RestException(304);
776		}
777
778		return $result;
779	}
780
781	/**
782	 * Classify the order as invoiced. Could be also called setbilled
783	 *
784	 * @param int   $id           Id of the order
785	 *
786	 * @url     POST {id}/setinvoiced
787	 *
788	 * @return int
789	 *
790	 * @throws RestException 400
791	 * @throws RestException 401
792	 * @throws RestException 404
793	 * @throws RestException 405
794	 */
795	public function setinvoiced($id)
796	{
797
798		if (!DolibarrApiAccess::$user->rights->commande->creer) {
799			throw new RestException(401);
800		}
801		if (empty($id)) {
802			throw new RestException(400, 'Order ID is mandatory');
803		}
804		$result = $this->commande->fetch($id);
805		if (!$result) {
806			throw new RestException(404, 'Order not found');
807		}
808
809		$result = $this->commande->classifyBilled(DolibarrApiAccess::$user);
810		if ($result < 0) {
811			throw new RestException(400, $this->commande->error);
812		}
813
814		$result = $this->commande->fetch($id);
815		if (!$result) {
816			throw new RestException(404, 'Order not found');
817		}
818
819		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
820			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
821		}
822
823		$this->commande->fetchObjectLinked();
824
825		return $this->_cleanObjectDatas($this->commande);
826	}
827
828	/**
829	 * Close an order (Classify it as "Delivered")
830	 *
831	 * @param   int     $id             Order ID
832	 * @param   int     $notrigger      Disabled triggers
833	 *
834	 * @url POST    {id}/close
835	 *
836	 * @return  int
837	 */
838	public function close($id, $notrigger = 0)
839	{
840		if (!DolibarrApiAccess::$user->rights->commande->creer) {
841			throw new RestException(401);
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		$result = $this->commande->cloture(DolibarrApiAccess::$user, $notrigger);
853		if ($result == 0) {
854			throw new RestException(304, 'Error nothing done. May be object is already closed');
855		}
856		if ($result < 0) {
857			throw new RestException(500, 'Error when closing Order: '.$this->commande->error);
858		}
859
860		$result = $this->commande->fetch($id);
861		if (!$result) {
862			throw new RestException(404, 'Order not found');
863		}
864
865		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
866			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
867		}
868
869		$this->commande->fetchObjectLinked();
870
871		return $this->_cleanObjectDatas($this->commande);
872	}
873
874	/**
875	 * Set an order to draft
876	 *
877	 * @param   int     $id             Order ID
878	 * @param   int 	$idwarehouse    Warehouse ID to use for stock change (Used only if option STOCK_CALCULATE_ON_VALIDATE_ORDER is on)
879	 *
880	 * @url POST    {id}/settodraft
881	 *
882	 * @return  array
883	 */
884	public function settodraft($id, $idwarehouse = -1)
885	{
886		if (!DolibarrApiAccess::$user->rights->commande->creer) {
887			throw new RestException(401);
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		$result = $this->commande->setDraft(DolibarrApiAccess::$user, $idwarehouse);
899		if ($result == 0) {
900			throw new RestException(304, 'Nothing done. May be object is already closed');
901		}
902		if ($result < 0) {
903			throw new RestException(500, 'Error when closing Order: '.$this->commande->error);
904		}
905
906		$result = $this->commande->fetch($id);
907		if (!$result) {
908			throw new RestException(404, 'Order not found');
909		}
910
911		if (!DolibarrApi::_checkAccessToResource('commande', $this->commande->id)) {
912			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
913		}
914
915		$this->commande->fetchObjectLinked();
916
917		return $this->_cleanObjectDatas($this->commande);
918	}
919
920
921	/**
922	 * Create an order using an existing proposal.
923	 *
924	 *
925	 * @param int   $proposalid       Id of the proposal
926	 *
927	 * @url     POST /createfromproposal/{proposalid}
928	 *
929	 * @return int
930	 * @throws RestException 400
931	 * @throws RestException 401
932	 * @throws RestException 404
933	 * @throws RestException 405
934	 */
935	public function createOrderFromProposal($proposalid)
936	{
937
938		require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
939
940		if (!DolibarrApiAccess::$user->rights->propal->lire) {
941			throw new RestException(401);
942		}
943		if (!DolibarrApiAccess::$user->rights->commande->creer) {
944			throw new RestException(401);
945		}
946		if (empty($proposalid)) {
947			throw new RestException(400, 'Proposal ID is mandatory');
948		}
949
950		$propal = new Propal($this->db);
951		$result = $propal->fetch($proposalid);
952		if (!$result) {
953			throw new RestException(404, 'Proposal not found');
954		}
955
956		$result = $this->commande->createFromProposal($propal, DolibarrApiAccess::$user);
957		if ($result < 0) {
958			throw new RestException(405, $this->commande->error);
959		}
960		$this->commande->fetchObjectLinked();
961
962		return $this->_cleanObjectDatas($this->commande);
963	}
964
965
966	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
967	/**
968	 * Clean sensible object datas
969	 *
970	 * @param   Object  $object     Object to clean
971	 * @return  Object              Object with cleaned properties
972	 */
973	protected function _cleanObjectDatas($object)
974	{
975		// phpcs:enable
976		$object = parent::_cleanObjectDatas($object);
977
978		unset($object->note);
979		unset($object->address);
980		unset($object->barcode_type);
981		unset($object->barcode_type_code);
982		unset($object->barcode_type_label);
983		unset($object->barcode_type_coder);
984
985		return $object;
986	}
987
988	/**
989	 * Validate fields before create or update object
990	 *
991	 * @param   array           $data   Array with data to verify
992	 * @return  array
993	 * @throws  RestException
994	 */
995	private function _validate($data)
996	{
997		$commande = array();
998		foreach (Orders::$FIELDS as $field) {
999			if (!isset($data[$field])) {
1000				throw new RestException(400, $field." field missing");
1001			}
1002			$commande[$field] = $data[$field];
1003		}
1004		return $commande;
1005	}
1006}
1007