1<?php
2/* Copyright (C) 2003      Rodolphe Quiedeville  <rodolphe@quiedeville.org>
3 * Copyright (C) 2005-2014 Regis Houssin         <regis.houssin@inodbox.com>
4 * Copyright (C) 2006-2007 Laurent Destailleur   <eldy@users.sourceforge.net>
5 * Copyright (C) 2007      Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
6 * Copyright (C) 2011-2018 Philippe Grand	     <philippe.grand@atoo-net.com>
7 * Copyright (C) 2013      Florian Henry	     <florian.henry@open-concept.pro>
8 * Copyright (C) 2014-2015 Marcos García         <marcosgdf@gmail.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23
24/**
25 *  \file       htdocs/delivery/class/delivery.class.php
26 *  \ingroup    delivery
27 *  \brief      Delivery Order Management Class File
28 */
29
30require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
31require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
32require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
33require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
34if (!empty($conf->propal->enabled)) {
35	require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
36}
37if (!empty($conf->commande->enabled)) {
38	require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
39}
40
41
42/**
43 *  Class to manage receptions
44 */
45class Delivery extends CommonObject
46{
47	use CommonIncoterm;
48
49	/**
50	 * @var string ID to identify managed object
51	 */
52	public $element = "delivery";
53
54	/**
55	 * @var string Field with ID of parent key if this field has a parent
56	 */
57	public $fk_element = "fk_delivery";
58
59	/**
60	 * @var string Name of table without prefix where object is stored
61	 */
62	public $table_element = "delivery";
63
64	/**
65	 * @var string    Name of subtable line
66	 */
67	public $table_element_line = "deliverydet";
68
69	/**
70	 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
71	 */
72	public $picto = 'sending';
73
74	/**
75	 * @var int draft status
76	 */
77	public $draft;
78
79	/**
80	 * @var int thirdparty id
81	 */
82	public $socid;
83
84	/**
85	 * @var string ref custome
86	 */
87	public $ref_customer;
88
89	/**
90	 * @var integer|string Date really received
91	 */
92	public $date_delivery;
93
94	/**
95	 * @var integer|string date_creation
96	 */
97	public $date_creation;
98
99	/**
100	 * @var integer|string date_valid
101	 */
102	public $date_valid;
103
104	/**
105	 * @var string model pdf
106	 */
107	public $model_pdf;
108
109	public $lines = array();
110
111
112	/**
113	 * Constructor
114	 *
115	 * @param	DoliDB	$db		Database handler
116	 */
117	public function __construct($db)
118	{
119		$this->db = $db;
120
121		// List of short language codes for status
122		$this->statuts[-1] = 'StatusDeliveryCanceled';
123		$this->statuts[0]  = 'StatusDeliveryDraft';
124		$this->statuts[1]  = 'StatusDeliveryValidated';
125	}
126
127	/**
128	 *  Create delivery receipt in database
129	 *
130	 *  @param 	User	$user       Objet du user qui cree
131	 *  @return int         		<0 si erreur, id delivery cree si ok
132	 */
133	public function create($user)
134	{
135		global $conf;
136
137		dol_syslog("Delivery::create");
138
139		if (empty($this->model_pdf)) {
140			$this->model_pdf = $conf->global->DELIVERY_ADDON_PDF;
141		}
142
143		$error = 0;
144
145		$now = dol_now();
146
147		/* Delivery note as draft On positionne en mode draft le bon de livraison */
148		$this->draft = 1;
149
150		$this->user = $user;
151
152		$this->db->begin();
153
154		$sql = "INSERT INTO ".MAIN_DB_PREFIX."delivery (";
155		$sql .= "ref";
156		$sql .= ", entity";
157		$sql .= ", fk_soc";
158		$sql .= ", ref_customer";
159		$sql .= ", date_creation";
160		$sql .= ", fk_user_author";
161		$sql .= ", date_delivery";
162		$sql .= ", fk_address";
163		$sql .= ", note_private";
164		$sql .= ", note_public";
165		$sql .= ", model_pdf";
166		$sql .= ", fk_incoterms, location_incoterms";
167		$sql .= ") VALUES (";
168		$sql .= "'(PROV)'";
169		$sql .= ", ".$conf->entity;
170		$sql .= ", ".$this->socid;
171		$sql .= ", '".$this->db->escape($this->ref_customer)."'";
172		$sql .= ", '".$this->db->idate($now)."'";
173		$sql .= ", ".$user->id;
174		$sql .= ", ".($this->date_delivery ? "'".$this->db->idate($this->date_delivery)."'" : "null");
175		$sql .= ", ".($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
176		$sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
177		$sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
178		$sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
179		$sql .= ", ".(int) $this->fk_incoterms;
180		$sql .= ", '".$this->db->escape($this->location_incoterms)."'";
181		$sql .= ")";
182
183		dol_syslog("Delivery::create", LOG_DEBUG);
184		$resql = $this->db->query($sql);
185		if ($resql) {
186			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."delivery");
187
188			$numref = "(PROV".$this->id.")";
189
190			$sql = "UPDATE ".MAIN_DB_PREFIX."delivery ";
191			$sql .= "SET ref = '".$this->db->escape($numref)."'";
192			$sql .= " WHERE rowid = ".$this->id;
193
194			dol_syslog("Delivery::create", LOG_DEBUG);
195			$resql = $this->db->query($sql);
196			if ($resql) {
197				if (!$conf->expedition_bon->enabled) {
198					$commande = new Commande($this->db);
199					$commande->id = $this->commande_id;
200					$commande->fetch_lines();
201				}
202
203
204				/*
205				 *  Inserting products into the database
206				 */
207				$num = count($this->lines);
208				for ($i = 0; $i < $num; $i++) {
209					$origin_id = $this->lines[$i]->origin_line_id;
210					if (!$origin_id) {
211						$origin_id = $this->lines[$i]->commande_ligne_id; // For backward compatibility
212					}
213
214					if (!$this->create_line($origin_id, $this->lines[$i]->qty, $this->lines[$i]->fk_product, $this->lines[$i]->description)) {
215						$error++;
216					}
217				}
218
219				if (!$error && $this->id && $this->origin_id) {
220					$ret = $this->add_object_linked();
221					if (!$ret) {
222						$error++;
223					}
224
225					if (!$conf->expedition_bon->enabled) {
226						// TODO standardize status uniformiser les statuts
227						$ret = $this->setStatut(2, $this->origin_id, $this->origin);
228						if (!$ret) {
229							$error++;
230						}
231					}
232				}
233
234				if (!$error) {
235					$this->db->commit();
236					return $this->id;
237				} else {
238					$error++;
239					$this->error = $this->db->lasterror()." - sql=".$this->db->lastqueryerror;
240					$this->db->rollback();
241					return -3;
242				}
243			} else {
244				$error++;
245				$this->error = $this->db->lasterror()." - sql=".$this->db->lastqueryerror;
246				$this->db->rollback();
247				return -2;
248			}
249		} else {
250			$error++;
251			$this->error = $this->db->lasterror()." - sql=".$this->db->lastqueryerror;
252			$this->db->rollback();
253			return -1;
254		}
255	}
256
257	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
258	/**
259	 *	Create a line
260	 *
261	 *	@param	string	$origin_id				Id of order
262	 *	@param	string	$qty					Quantity
263	 *	@param	string	$fk_product				Id of predefined product
264	 *	@param	string	$description			Description
265	 *	@return	int								<0 if KO, >0 if OK
266	 */
267	public function create_line($origin_id, $qty, $fk_product, $description)
268	{
269		// phpcs:enable
270		$error = 0;
271		$idprod = $fk_product;
272		$j = 0;
273
274		$sql = "INSERT INTO ".MAIN_DB_PREFIX."deliverydet (fk_delivery, fk_origin_line,";
275		$sql .= " fk_product, description, qty)";
276		$sql .= " VALUES (".$this->id.",".((int) $origin_id).",";
277		$sql .= " ".($idprod > 0 ? ((int) $idprod) : "null").",";
278		$sql .= " ".($description ? "'".$this->db->escape($description)."'" : "null").",";
279		$sql .= (price2num($qty, 'MS')).")";
280
281		dol_syslog(get_class($this)."::create_line", LOG_DEBUG);
282		if (!$this->db->query($sql)) {
283			$error++;
284		}
285
286		if ($error == 0) {
287			return 1;
288		}
289	}
290
291	/**
292	 * 	Load a delivery receipt
293	 *
294	 * 	@param	int		$id			Id of object to load
295	 * 	@return	integer
296	 */
297	public function fetch($id)
298	{
299		global $conf;
300
301		$sql = "SELECT l.rowid, l.fk_soc, l.date_creation, l.date_valid, l.ref, l.ref_customer, l.fk_user_author,";
302		$sql .= " l.total_ht, l.fk_statut, l.fk_user_valid, l.note_private, l.note_public";
303		$sql .= ", l.date_delivery, l.fk_address, l.model_pdf";
304		$sql .= ", el.fk_source as origin_id, el.sourcetype as origin";
305		$sql .= ', l.fk_incoterms, l.location_incoterms';
306		$sql .= ", i.libelle as label_incoterms";
307		$sql .= " FROM ".MAIN_DB_PREFIX."delivery as l";
308		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = l.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
309		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON l.fk_incoterms = i.rowid';
310		$sql .= " WHERE l.rowid = ".((int) $id);
311
312		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
313		$result = $this->db->query($sql);
314		if ($result) {
315			if ($this->db->num_rows($result)) {
316				$obj = $this->db->fetch_object($result);
317
318				$this->id                   = $obj->rowid;
319				$this->date_delivery        = $this->db->jdate($obj->date_delivery);
320				$this->date_creation        = $this->db->jdate($obj->date_creation);
321				$this->date_valid           = $this->db->jdate($obj->date_valid);
322				$this->ref                  = $obj->ref;
323				$this->ref_customer         = $obj->ref_customer;
324				$this->socid                = $obj->fk_soc;
325				$this->statut               = $obj->fk_statut;
326				$this->user_author_id       = $obj->fk_user_author;
327				$this->user_valid_id        = $obj->fk_user_valid;
328				$this->fk_delivery_address  = $obj->fk_address;
329				$this->note                 = $obj->note_private; //TODO deprecated
330				$this->note_private         = $obj->note_private;
331				$this->note_public          = $obj->note_public;
332				$this->model_pdf            = $obj->model_pdf;
333				$this->modelpdf             = $obj->model_pdf; // deprecated
334				$this->origin               = $obj->origin; // May be 'shipping'
335				$this->origin_id            = $obj->origin_id; // May be id of shipping
336
337				//Incoterms
338				$this->fk_incoterms = $obj->fk_incoterms;
339				$this->location_incoterms = $obj->location_incoterms;
340				$this->label_incoterms = $obj->label_incoterms;
341				$this->db->free($result);
342
343				if ($this->statut == 0) {
344					$this->draft = 1;
345				}
346
347				// Retrieve all extrafields
348				// fetch optionals attributes and labels
349				$this->fetch_optionals();
350
351				// Load lines
352				$result = $this->fetch_lines();
353				if ($result < 0) {
354					return -3;
355				}
356
357				return 1;
358			} else {
359				$this->error = 'Delivery with id '.$id.' not found sql='.$sql;
360				dol_syslog(get_class($this).'::fetch Error '.$this->error, LOG_ERR);
361				return -2;
362			}
363		} else {
364			$this->error = $this->db->error();
365			return -1;
366		}
367	}
368
369	/**
370	 *  Validate object and update stock if option enabled
371	 *
372	 *  @param  User    $user       Object user that validate
373	 *  @param  int     $notrigger  1=Does not execute triggers, 0= execute triggers
374	 *  @return int
375	 */
376	public function valid($user, $notrigger = 0)
377	{
378		global $conf, $langs;
379		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
380
381		dol_syslog(get_class($this)."::valid begin");
382
383		$this->db->begin();
384
385		$error = 0;
386
387		if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->expedition->delivery->creer))
388		|| (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->expedition->delivery_advance->validate))) {
389			if (!empty($conf->global->DELIVERY_ADDON_NUMBER)) {
390				// Setting the command numbering module name
391				$modName = $conf->global->DELIVERY_ADDON_NUMBER;
392
393				if (is_readable(DOL_DOCUMENT_ROOT.'/core/modules/delivery/'.$modName.'.php')) {
394					require_once DOL_DOCUMENT_ROOT.'/core/modules/delivery/'.$modName.'.php';
395
396					$now = dol_now();
397
398					// Retrieving the new reference
399					$objMod = new $modName($this->db);
400					$soc = new Societe($this->db);
401					$soc->fetch($this->socid);
402
403					if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
404						$numref = $objMod->delivery_get_num($soc, $this);
405					} else {
406						$numref = $this->ref;
407					}
408					$this->newref = dol_sanitizeFileName($numref);
409
410					// Test if is not already in valid status. If so, we stop to avoid decrementing the stock twice.
411					$sql = "SELECT ref";
412					$sql .= " FROM ".MAIN_DB_PREFIX."delivery";
413					$sql .= " WHERE ref = '".$this->db->escape($numref)."'";
414					$sql .= " AND fk_statut <> 0";
415					$sql .= " AND entity = ".((int) $conf->entity);
416
417					$resql = $this->db->query($sql);
418					if ($resql) {
419						$num = $this->db->num_rows($resql);
420						if ($num > 0) {
421							return 0;
422						}
423					}
424
425					$sql = "UPDATE ".MAIN_DB_PREFIX."delivery SET";
426					$sql .= " ref='".$this->db->escape($numref)."'";
427					$sql .= ", fk_statut = 1";
428					$sql .= ", date_valid = '".$this->db->idate($now)."'";
429					$sql .= ", fk_user_valid = ".$user->id;
430					$sql .= " WHERE rowid = ".$this->id;
431					$sql .= " AND fk_statut = 0";
432
433					$resql = $this->db->query($sql);
434					if (!$resql) {
435						dol_print_error($this->db);
436						$this->error = $this->db->lasterror();
437						$error++;
438					}
439
440					if (!$error && !$notrigger) {
441						// Call trigger
442						$result = $this->call_trigger('DELIVERY_VALIDATE', $user);
443						if ($result < 0) {
444							$error++;
445						}
446						// End call triggers
447					}
448
449					if (!$error) {
450						$this->oldref = $this->ref;
451
452						// Rename directory if dir was a temporary ref
453						if (preg_match('/^[\(]?PROV/i', $this->ref)) {
454							// Now we rename also files into index
455							$sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'expedition/receipt/".$this->db->escape($this->newref)."'";
456							$sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/receipt/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
457							$resql = $this->db->query($sql);
458							if (!$resql) {
459								$error++; $this->error = $this->db->lasterror();
460							}
461
462							// We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
463							$oldref = dol_sanitizeFileName($this->ref);
464							$newref = dol_sanitizeFileName($numref);
465							$dirsource = $conf->expedition->dir_output.'/receipt/'.$oldref;
466							$dirdest = $conf->expedition->dir_output.'/receipt/'.$newref;
467							if (!$error && file_exists($dirsource)) {
468								dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
469
470								if (@rename($dirsource, $dirdest)) {
471									dol_syslog("Rename ok");
472									// Rename docs starting with $oldref with $newref
473									$listoffiles = dol_dir_list($conf->expedition->dir_output.'/receipt/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
474									foreach ($listoffiles as $fileentry) {
475										$dirsource = $fileentry['name'];
476										$dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
477										$dirsource = $fileentry['path'].'/'.$dirsource;
478										$dirdest = $fileentry['path'].'/'.$dirdest;
479										@rename($dirsource, $dirdest);
480									}
481								}
482							}
483						}
484
485						// Set new ref and current status
486						if (!$error) {
487							$this->ref = $numref;
488							$this->statut = 1;
489						}
490
491						dol_syslog(get_class($this)."::valid ok");
492					}
493
494					if (!$error) {
495						$this->db->commit();
496						return 1;
497					} else {
498						$this->db->rollback();
499						return -1;
500					}
501				}
502			}
503		} else {
504			$this->error = "Non autorise";
505			dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
506			return -1;
507		}
508	}
509
510	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
511	/**
512	 * 	Creating the delivery slip from an existing shipment
513	 *
514	 *	@param	User	$user            User who creates
515	 *	@param  int		$sending_id      Id of the expedition that serves as a model
516	 *	@return	integer
517	 */
518	public function create_from_sending($user, $sending_id)
519	{
520		// phpcs:enable
521		$expedition = new Expedition($this->db);
522		$result = $expedition->fetch($sending_id);
523
524		$this->lines = array();
525
526		$num = count($expedition->lines);
527		for ($i = 0; $i < $num; $i++) {
528			$line = new DeliveryLine($this->db);
529			$line->origin_line_id    = $expedition->lines[$i]->origin_line_id;
530			$line->label             = $expedition->lines[$i]->label;
531			$line->description       = $expedition->lines[$i]->description;
532			$line->qty               = $expedition->lines[$i]->qty_shipped;
533			$line->fk_product        = $expedition->lines[$i]->fk_product;
534
535			$this->lines[$i] = $line;
536		}
537
538		$this->origin               = $expedition->element;
539		$this->origin_id            = $expedition->id;
540		$this->note_private         = $expedition->note_private;
541		$this->note_public          = $expedition->note_public;
542		$this->fk_project           = $expedition->fk_project;
543		$this->date_delivery        = $expedition->date_delivery;
544		$this->fk_delivery_address  = $expedition->fk_delivery_address;
545		$this->socid                = $expedition->socid;
546		$this->ref_customer         = $expedition->ref_customer;
547
548		//Incoterms
549		$this->fk_incoterms = $expedition->fk_incoterms;
550		$this->location_incoterms = $expedition->location_incoterms;
551
552		return $this->create($user);
553	}
554
555	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
556	/**
557	 * Update a livraison line (only extrafields)
558	 *
559	 * @param 	int		$id					Id of line (livraison line)
560	 * @param	array		$array_options		extrafields array
561	 * @return	int							<0 if KO, >0 if OK
562	 */
563	public function update_line($id, $array_options = 0)
564	{
565		// phpcs:enable
566		global $conf;
567		$error = 0;
568
569		if ($id > 0 && !$error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
570			$line = new DeliveryLine($this->db);
571			$line->array_options = $array_options;
572			$line->id = $id;
573			$result = $line->insertExtraFields();
574
575			if ($result < 0) {
576				$this->error[] = $line->error;
577				$error++;
578			}
579		}
580
581		if (!$error) {
582			return 1;
583		} else {
584			return -1;
585		}
586	}
587
588
589	/**
590	 * 	Add line
591	 *
592	 *	@param	int		$origin_id		Origin id
593	 *	@param	int		$qty			Qty
594	 *	@return	void
595	 */
596	public function addline($origin_id, $qty)
597	{
598		$num = count($this->lines);
599		$line = new DeliveryLine($this->db);
600
601		$line->origin_id = $origin_id;
602		$line->qty = $qty;
603
604		$this->lines[$num] = $line;
605	}
606
607	/**
608	 *	Delete line
609	 *
610	 *	@param	int		$lineid		Line id
611	 *	@return	integer|null
612	 */
613	public function deleteline($lineid)
614	{
615		if ($this->statut == 0) {
616			$sql = "DELETE FROM ".MAIN_DB_PREFIX."commandedet";
617			$sql .= " WHERE rowid = ".((int) $lineid);
618
619			if ($this->db->query($sql)) {
620				$this->update_price();
621
622				return 1;
623			} else {
624				return 0;
625			}
626		}
627	}
628
629	/**
630	 * Delete object
631	 *
632	 * @return	integer
633	 */
634	public function delete()
635	{
636		global $conf, $langs, $user;
637
638		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
639		$this->db->begin();
640
641		$error = 0;
642
643		$sql = "DELETE FROM ".MAIN_DB_PREFIX."deliverydet";
644		$sql .= " WHERE fk_delivery = ".((int) $this->id);
645		if ($this->db->query($sql)) {
646			// Delete linked object
647			$res = $this->deleteObjectLinked();
648			if ($res < 0) {
649				$error++;
650			}
651
652			if (!$error) {
653				$sql = "DELETE FROM ".MAIN_DB_PREFIX."delivery";
654				$sql .= " WHERE rowid = ".$this->id;
655				if ($this->db->query($sql)) {
656					$this->db->commit();
657
658					// Deleting pdf folder's draft On efface le repertoire de pdf provisoire
659					$ref = dol_sanitizeFileName($this->ref);
660					if (!empty($conf->expedition->dir_output)) {
661						$dir = $conf->expedition->dir_output.'/receipt/'.$ref;
662						$file = $dir.'/'.$ref.'.pdf';
663						if (file_exists($file)) {
664							if (!dol_delete_file($file)) {
665								return 0;
666							}
667						}
668						if (file_exists($dir)) {
669							if (!dol_delete_dir($dir)) {
670								$this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
671								return 0;
672							}
673						}
674					}
675
676					// Call trigger
677					$result = $this->call_trigger('DELIVERY_DELETE', $user);
678					if ($result < 0) {
679						$this->db->rollback();
680						return -4;
681					}
682					// End call triggers
683
684					return 1;
685				} else {
686					$this->error = $this->db->lasterror()." - sql=$sql";
687					$this->db->rollback();
688					return -3;
689				}
690			} else {
691				$this->error = $this->db->lasterror()." - sql=$sql";
692				$this->db->rollback();
693				return -2;
694			}
695		} else {
696			$this->error = $this->db->lasterror()." - sql=$sql";
697			$this->db->rollback();
698			return -1;
699		}
700	}
701
702	/**
703	 *	Return clicable name (with picto eventually)
704	 *
705	 *	@param	int		$withpicto					0=No picto, 1=Include picto into link, 2=Only picto
706	 *  @param  int     $save_lastsearch_value		-1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
707	 *	@return	string								Chaine avec URL
708	 */
709	public function getNomUrl($withpicto = 0, $save_lastsearch_value = -1)
710	{
711		global $langs;
712
713		$result = '';
714
715		$label = img_picto('', $this->picto).' <u>'.$langs->trans("ShowReceiving").'</u>:<br>';
716		$label .= '<b>'.$langs->trans("Status").'</b>: '.$this->ref;
717
718		$url = DOL_URL_ROOT.'/delivery/card.php?id='.$this->id;
719
720		//if ($option !== 'nolink')
721		//{
722			// Add param to save lastsearch_values or not
723			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
724		if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
725			$add_save_lastsearch_values = 1;
726		}
727		if ($add_save_lastsearch_values) {
728			$url .= '&save_lastsearch_values=1';
729		}
730		//}
731
732
733		$linkstart = '<a href="'.$url.'" title="'.dol_escape_htmltag($label, 1).'" class="classfortooltip">';
734		$linkend = '</a>';
735
736		if ($withpicto) {
737			$result .= ($linkstart.img_object($label, $this->picto, 'class="classfortooltip"').$linkend);
738		}
739		if ($withpicto && $withpicto != 2) {
740			$result .= ' ';
741		}
742		$result .= $linkstart.$this->ref.$linkend;
743		return $result;
744	}
745
746	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
747	/**
748	 *	Load lines
749	 *
750	 *	@return	void
751	 */
752	public function fetch_lines()
753	{
754		// phpcs:enable
755		$this->lines = array();
756
757		$sql = "SELECT ld.rowid, ld.fk_product, ld.description, ld.subprice, ld.total_ht, ld.qty as qty_shipped, ld.fk_origin_line, ";
758		$sql .= " cd.qty as qty_asked, cd.label as custom_label, cd.fk_unit,";
759		$sql .= " p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc,";
760		$sql .= " p.weight, p.weight_units,  p.width, p.width_units, p.length, p.length_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units, p.tobatch as product_tobatch";
761		$sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd, ".MAIN_DB_PREFIX."deliverydet as ld";
762		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p on p.rowid = ld.fk_product";
763		$sql .= " WHERE ld.fk_origin_line = cd.rowid";
764		$sql .= " AND ld.fk_delivery = ".((int) $this->id);
765
766		dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
767		$resql = $this->db->query($sql);
768		if ($resql) {
769			$num = $this->db->num_rows($resql);
770			$i = 0;
771			while ($i < $num) {
772				$line = new DeliveryLine($this->db);
773
774				$obj = $this->db->fetch_object($resql);
775
776				$line->id = $obj->rowid;
777				$line->label = $obj->custom_label;
778				$line->description		= $obj->description;
779				$line->fk_product = $obj->fk_product;
780				$line->qty_asked = $obj->qty_asked;
781				$line->qty_shipped		= $obj->qty_shipped;
782
783				$line->ref = $obj->product_ref; // deprecated
784				$line->libelle = $obj->product_label; // deprecated
785				$line->product_label	= $obj->product_label; // Product label
786				$line->product_ref = $obj->product_ref; // Product ref
787				$line->product_desc		= $obj->product_desc; // Product description
788				$line->product_type		= $obj->fk_product_type;
789				$line->fk_origin_line = $obj->fk_origin_line;
790
791				$line->price = $obj->price;
792				$line->total_ht = $obj->total_ht;
793
794				// units
795				$line->weight         	= $obj->weight;
796				$line->weight_units   	= $obj->weight_units;
797				$line->width         	= $obj->width;
798				$line->width_units   	= $obj->width_units;
799				$line->height         	= $obj->height;
800				$line->height_units   	= $obj->height_units;
801				$line->length         	= $obj->length;
802				$line->length_units   	= $obj->length_units;
803				$line->surface        	= $obj->surface;
804				$line->surface_units = $obj->surface_units;
805				$line->volume         	= $obj->volume;
806				$line->volume_units   	= $obj->volume_units;
807
808				$line->fk_unit = $obj->fk_unit;
809				$line->fetch_optionals();
810
811				$this->lines[$i] = $line;
812
813				$i++;
814			}
815			$this->db->free($resql);
816		}
817
818		return $this->lines;
819	}
820
821
822	/**
823	 *  Retourne le libelle du statut d'une expedition
824	 *
825	 *  @param	int			$mode		Mode
826	 *  @return string      			Label
827	 */
828	public function getLibStatut($mode = 0)
829	{
830		return $this->LibStatut($this->statut, $mode);
831	}
832
833	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
834	/**
835	 *	Renvoi le libelle d'un statut donne
836	 *
837	 *  @param	int		$status     	Id status
838	 *  @param  int		$mode          	0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
839	 *  @return string					Label
840	 */
841	public function LibStatut($status, $mode)
842	{
843		// phpcs:enable
844		global $langs;
845
846		if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
847			global $langs;
848			//$langs->load("mymodule");
849			$this->labelStatus[-1] = $langs->trans('StatusDeliveryCanceled');
850			$this->labelStatus[0] = $langs->trans('StatusDeliveryDraft');
851			$this->labelStatus[1] = $langs->trans('StatusDeliveryValidated');
852			$this->labelStatusShort[-1] = $langs->trans('StatusDeliveryCanceled');
853			$this->labelStatusShort[0] = $langs->trans('StatusDeliveryDraft');
854			$this->labelStatusShort[1] = $langs->trans('StatusDeliveryValidated');
855		}
856
857		$statusType = 'status0';
858		if ($status == -1) {
859			$statusType = 'status5';
860		}
861		if ($status == 1) {
862			$statusType = 'status4';
863		}
864
865		return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
866	}
867
868
869	/**
870	 *  Initialise an instance with random values.
871	 *  Used to build previews or test instances.
872	 *	id must be 0 if object instance is a specimen.
873	 *
874	 *  @return	void
875	 */
876	public function initAsSpecimen()
877	{
878		global $user, $langs, $conf;
879
880		$now = dol_now();
881
882		// Load array of products prodids
883		$num_prods = 0;
884		$prodids = array();
885		$sql = "SELECT rowid";
886		$sql .= " FROM ".MAIN_DB_PREFIX."product";
887		$sql .= " WHERE entity IN (".getEntity('product').")";
888		$sql .= " AND tosell = 1";
889		$sql .= $this->db->plimit(100);
890
891		$resql = $this->db->query($sql);
892		if ($resql) {
893			$num_prods = $this->db->num_rows($resql);
894			$i = 0;
895			while ($i < $num_prods) {
896				$i++;
897				$row = $this->db->fetch_row($resql);
898				$prodids[$i] = $row[0];
899			}
900		}
901
902		// Initialise parametres
903		$this->id = 0;
904		$this->ref = 'SPECIMEN';
905		$this->specimen = 1;
906		$this->socid = 1;
907		$this->date_delivery = $now;
908		$this->note_public = 'Public note';
909		$this->note_private = 'Private note';
910
911		$i = 0;
912		$line = new DeliveryLine($this->db);
913		$line->fk_product     = $prodids[0];
914		$line->qty_asked      = 10;
915		$line->qty_shipped    = 9;
916		$line->ref            = 'REFPROD';
917		$line->label          = 'Specimen';
918		$line->description    = 'Description';
919		$line->price          = 100;
920		$line->total_ht       = 100;
921
922		$this->lines[$i] = $line;
923	}
924
925	/**
926	 *  Renvoie la quantite de produit restante a livrer pour une commande
927	 *
928	 *  @return     array		Product remaining to be delivered
929	 *  TODO use new function
930	 */
931	public function getRemainingDelivered()
932	{
933		global $langs;
934
935		// Get the linked object
936		$this->fetchObjectLinked('', '', $this->id, $this->element);
937		//var_dump($this->linkedObjectsIds);
938		// Get the product ref and qty in source
939		$sqlSourceLine = "SELECT st.rowid, st.description, st.qty";
940		$sqlSourceLine .= ", p.ref, p.label";
941		$sqlSourceLine .= " FROM ".MAIN_DB_PREFIX.$this->linkedObjectsIds[0]['type']."det as st";
942		$sqlSourceLine .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON st.fk_product = p.rowid";
943		$sqlSourceLine .= " WHERE fk_".$this->linked_object[0]['type']." = ".((int) $this->linked_object[0]['linkid']);
944
945		$resultSourceLine = $this->db->query($sqlSourceLine);
946		if ($resultSourceLine) {
947			$num_lines = $this->db->num_rows($resultSourceLine);
948			$i = 0;
949			$resultArray = array();
950			while ($i < $num_lines) {
951				$objSourceLine = $this->db->fetch_object($resultSourceLine);
952
953				// Get lines of sources alread delivered
954				$sql = "SELECT ld.fk_origin_line, sum(ld.qty) as qty";
955				$sql .= " FROM ".MAIN_DB_PREFIX."deliverydet as ld, ".MAIN_DB_PREFIX."delivery as l,";
956				$sql .= " ".MAIN_DB_PREFIX.$this->linked_object[0]['type']." as c";
957				$sql .= ", ".MAIN_DB_PREFIX.$this->linked_object[0]['type']."det as cd";
958				$sql .= " WHERE ld.fk_delivery = l.rowid";
959				$sql .= " AND ld.fk_origin_line = cd.rowid";
960				$sql .= " AND cd.fk_".$this->linked_object[0]['type']." = c.rowid";
961				$sql .= " AND cd.fk_".$this->linked_object[0]['type']." = ".((int) $this->linked_object[0]['linkid']);
962				$sql .= " AND ld.fk_origin_line = ".((int) $objSourceLine->rowid);
963				$sql .= " GROUP BY ld.fk_origin_line";
964
965				$result = $this->db->query($sql);
966				$row = $this->db->fetch_row($result);
967
968				if ($objSourceLine->qty - $row[1] > 0) {
969					if ($row[0] == $objSourceLine->rowid) {
970						$array[$i]['qty'] = $objSourceLine->qty - $row[1];
971					} else {
972						$array[$i]['qty'] = $objSourceLine->qty;
973					}
974
975					$array[$i]['ref'] = $objSourceLine->ref;
976					$array[$i]['label'] = $objSourceLine->label ? $objSourceLine->label : $objSourceLine->description;
977				} elseif ($objSourceLine->qty - $row[1] < 0) {
978					$array[$i]['qty'] = $objSourceLine->qty - $row[1]." Erreur livraison !";
979					$array[$i]['ref'] = $objSourceLine->ref;
980					$array[$i]['label'] = $objSourceLine->label ? $objSourceLine->label : $objSourceLine->description;
981				}
982
983					$i++;
984			}
985			return $array;
986		} else {
987			$this->error = $this->db->error()." - sql=$sqlSourceLine";
988			return -1;
989		}
990	}
991
992	/**
993	 *	Set the planned delivery date
994	 *
995	 *	@param      User			$user        		Objet utilisateur qui modifie
996	 *	@param      integer 		$delivery_date     Delivery date
997	 *	@return     int         						<0 if KO, >0 if OK
998	 */
999	public function setDeliveryDate($user, $delivery_date)
1000	{
1001		if ($user->rights->expedition->creer) {
1002			$sql = "UPDATE ".MAIN_DB_PREFIX."delivery";
1003			$sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
1004			$sql .= " WHERE rowid = ".$this->id;
1005
1006			dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
1007			$resql = $this->db->query($sql);
1008			if ($resql) {
1009				$this->date_delivery = $delivery_date;
1010				return 1;
1011			} else {
1012				$this->error = $this->db->error();
1013				return -1;
1014			}
1015		} else {
1016			return -2;
1017		}
1018	}
1019
1020	/**
1021	 *	Create object on disk
1022	 *
1023	 *	@param     string		$modele			force le modele a utiliser ('' to not force)
1024	 * 	@param     Translate	$outputlangs	Object langs to use for output
1025	 *  @param     int			$hidedetails    Hide details of lines
1026	 *  @param     int			$hidedesc       Hide description
1027	 *  @param     int			$hideref        Hide ref
1028	 *  @return    int             				0 if KO, 1 if OK
1029	 */
1030	public function generateDocument($modele, $outputlangs = '', $hidedetails = 0, $hidedesc = 0, $hideref = 0)
1031	{
1032		global $conf, $user, $langs;
1033
1034		$langs->load("deliveries");
1035		$outputlangs->load("products");
1036
1037		if (!dol_strlen($modele)) {
1038			$modele = 'typhon';
1039
1040			if ($this->model_pdf) {
1041				$modele = $this->model_pdf;
1042			} elseif (!empty($conf->global->DELIVERY_ADDON_PDF)) {
1043				$modele = $conf->global->DELIVERY_ADDON_PDF;
1044			}
1045		}
1046
1047		$modelpath = "core/modules/delivery/doc/";
1048
1049		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
1050	}
1051
1052	/**
1053	 * Function used to replace a thirdparty id with another one.
1054	 *
1055	 * @param DoliDB $db Database handler
1056	 * @param int $origin_id Old thirdparty id
1057	 * @param int $dest_id New thirdparty id
1058	 * @return bool
1059	 */
1060	public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
1061	{
1062		$tables = array(
1063			'delivery'
1064		);
1065
1066		return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
1067	}
1068}
1069
1070
1071
1072/**
1073 *  Management class of delivery note lines
1074 */
1075class DeliveryLine extends CommonObjectLine
1076{
1077	/**
1078	 * @var DoliDB Database handler.
1079	 */
1080	public $db;
1081
1082	/**
1083	 * @var string ID to identify managed object
1084	 */
1085	public $element = 'deliverydet';
1086
1087	/**
1088	 * @var string Name of table without prefix where object is stored
1089	 */
1090	public $table_element = 'deliverydet';
1091
1092	// From llx_expeditiondet
1093	public $qty;
1094	public $qty_asked;
1095	public $qty_shipped;
1096	public $price;
1097	public $fk_product;
1098	public $origin_id;
1099
1100	/**
1101	 * @var string delivery note lines label
1102	 */
1103	public $label;
1104
1105	/**
1106	 * @var string product description
1107	 */
1108	public $description;
1109
1110	/**
1111	 * @deprecated
1112	 * @see $product_ref
1113	 */
1114	public $ref;
1115	/**
1116	 * @deprecated
1117	 * @see product_label;
1118	 */
1119	public $libelle;
1120
1121	public $origin_line_id;
1122
1123	public $product_ref;
1124	public $product_label;
1125
1126	/**
1127	 *	Constructor
1128	 *
1129	 *	@param	DoliDB	$db		Database handler
1130	 */
1131	public function __construct($db)
1132	{
1133		$this->db = $db;
1134	}
1135}
1136