dolibarr  13.0.2
expedition.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2003-2008 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3  * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
4  * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
5  * Copyright (C) 2006-2012 Laurent Destailleur <eldy@users.sourceforge.net>
6  * Copyright (C) 2011-2020 Juanjo Menent <jmenent@2byte.es>
7  * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
8  * Copyright (C) 2014 Cedric GROSS <c.gross@kreiz-it.fr>
9  * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
10  * Copyright (C) 2014-2017 Francis Appels <francis.appels@yahoo.com>
11  * Copyright (C) 2015 Claudio Aschieri <c.aschieri@19.coop>
12  * Copyright (C) 2016 Ferran Marcet <fmarcet@2byte.es>
13  * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
14  * Copyright (C) 2018-2020 Frédéric France <frederic.france@netlogic.fr>
15  * Copyright (C) 2020 Lenin Rivas <lenin@leninrivas.com>
16  *
17  * This program is free software; you can redistribute it and/or modify
18  * it under the terms of the GNU General Public License as published by
19  * the Free Software Foundation; either version 3 of the License, or
20  * (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25  * GNU General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public License
28  * along with this program. If not, see <https://www.gnu.org/licenses/>.
29  */
30 
37 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
38 require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
39 require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
40 if (!empty($conf->propal->enabled)) require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
41 if (!empty($conf->commande->enabled)) require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
42 if (!empty($conf->productbatch->enabled)) require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionbatch.class.php';
43 
44 
48 class Expedition extends CommonObject
49 {
50  use CommonIncoterm;
51 
55  public $element = "shipping";
56 
60  public $fk_element = "fk_expedition";
61 
65  public $table_element = "expedition";
66 
70  public $table_element_line = "expeditiondet";
71 
76  public $ismultientitymanaged = 1;
77 
81  public $picto = 'dolly';
82 
83  public $socid;
84 
90  public $ref_client;
91 
95  public $ref_customer;
96 
101  public $ref_int;
102 
103  public $brouillon;
104 
108  public $entrepot_id;
109 
113  public $tracking_number;
114 
118  public $tracking_url;
119  public $billed;
120 
124  public $model_pdf;
125 
126  public $trueWeight;
127  public $weight_units;
128  public $trueWidth;
129  public $width_units;
130  public $trueHeight;
131  public $height_units;
132  public $trueDepth;
133  public $depth_units;
134  // A denormalized value
135  public $trueSize;
136 
140  public $date_delivery;
141 
146  public $date;
147 
153 
158  public $date_shipping;
159 
163  public $date_creation;
164 
168  public $date_valid;
169 
170  public $meths;
171  public $listmeths; // List of carriers
172 
173  public $lines = array();
174 
175 
179  const STATUS_DRAFT = 0;
180 
184  const STATUS_VALIDATED = 1;
185 
189  const STATUS_CLOSED = 2;
190 
194  const STATUS_CANCELED = -1;
195 
196 
202  public function __construct($db)
203  {
204  global $conf;
205 
206  $this->db = $db;
207 
208  // List of long language codes for status
209  $this->statuts = array();
210  $this->statuts[-1] = 'StatusSendingCanceled';
211  $this->statuts[0] = 'StatusSendingDraft';
212  $this->statuts[1] = 'StatusSendingValidated';
213  $this->statuts[2] = 'StatusSendingProcessed';
214 
215  // List of short language codes for status
216  $this->statutshorts = array();
217  $this->statutshorts[-1] = 'StatusSendingCanceledShort';
218  $this->statutshorts[0] = 'StatusSendingDraftShort';
219  $this->statutshorts[1] = 'StatusSendingValidatedShort';
220  $this->statutshorts[2] = 'StatusSendingProcessedShort';
221  }
222 
229  public function getNextNumRef($soc)
230  {
231  global $langs, $conf;
232  $langs->load("sendings");
233 
234  if (!empty($conf->global->EXPEDITION_ADDON_NUMBER))
235  {
236  $mybool = false;
237 
238  $file = $conf->global->EXPEDITION_ADDON_NUMBER.".php";
239  $classname = $conf->global->EXPEDITION_ADDON_NUMBER;
240 
241  // Include file with class
242  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
243 
244  foreach ($dirmodels as $reldir) {
245  $dir = dol_buildpath($reldir."core/modules/expedition/");
246 
247  // Load file with numbering class (if found)
248  $mybool |= @include_once $dir.$file;
249  }
250 
251  if (!$mybool)
252  {
253  dol_print_error('', "Failed to include file ".$file);
254  return '';
255  }
256 
257  $obj = new $classname();
258  $numref = "";
259  $numref = $obj->getNextValue($soc, $this);
260 
261  if ($numref != "")
262  {
263  return $numref;
264  } else {
265  dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
266  return "";
267  }
268  } else {
269  print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
270  return "";
271  }
272  }
273 
281  public function create($user, $notrigger = 0)
282  {
283  global $conf, $hookmanager;
284 
285  $now = dol_now();
286 
287  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
288  $error = 0;
289 
290  // Clean parameters
291  $this->brouillon = 1;
292  $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
293  if (empty($this->fk_project)) $this->fk_project = 0;
294 
295  $this->user = $user;
296 
297 
298  $this->db->begin();
299 
300  $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
301 
302  $sql .= "ref";
303  $sql .= ", entity";
304  $sql .= ", ref_customer";
305  $sql .= ", ref_int";
306  $sql .= ", ref_ext";
307  $sql .= ", date_creation";
308  $sql .= ", fk_user_author";
309  $sql .= ", date_expedition";
310  $sql .= ", date_delivery";
311  $sql .= ", fk_soc";
312  $sql .= ", fk_projet";
313  $sql .= ", fk_address";
314  $sql .= ", fk_shipping_method";
315  $sql .= ", tracking_number";
316  $sql .= ", weight";
317  $sql .= ", size";
318  $sql .= ", width";
319  $sql .= ", height";
320  $sql .= ", weight_units";
321  $sql .= ", size_units";
322  $sql .= ", note_private";
323  $sql .= ", note_public";
324  $sql .= ", model_pdf";
325  $sql .= ", fk_incoterms, location_incoterms";
326  $sql .= ") VALUES (";
327  $sql .= "'(PROV)'";
328  $sql .= ", ".$conf->entity;
329  $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
330  $sql .= ", ".($this->ref_int ? "'".$this->db->escape($this->ref_int)."'" : "null");
331  $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
332  $sql .= ", '".$this->db->idate($now)."'";
333  $sql .= ", ".$user->id;
334  $sql .= ", ".($this->date_expedition > 0 ? "'".$this->db->idate($this->date_expedition)."'" : "null");
335  $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
336  $sql .= ", ".$this->socid;
337  $sql .= ", ".$this->fk_project;
338  $sql .= ", ".($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
339  $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : "null");
340  $sql .= ", '".$this->db->escape($this->tracking_number)."'";
341  $sql .= ", ".(is_numeric($this->weight) ? $this->weight : 'NULL');
342  $sql .= ", ".(is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
343  $sql .= ", ".(is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
344  $sql .= ", ".(is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
345  $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
346  $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
347  $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
348  $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
349  $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
350  $sql .= ", ".(int) $this->fk_incoterms;
351  $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
352  $sql .= ")";
353 
354  dol_syslog(get_class($this)."::create", LOG_DEBUG);
355  $resql = $this->db->query($sql);
356  if ($resql)
357  {
358  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
359 
360  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
361  $sql .= " SET ref = '(PROV".$this->id.")'";
362  $sql .= " WHERE rowid = ".$this->id;
363 
364  dol_syslog(get_class($this)."::create", LOG_DEBUG);
365  if ($this->db->query($sql))
366  {
367  // Insert of lines
368  $num = count($this->lines);
369  for ($i = 0; $i < $num; $i++)
370  {
371  if (!isset($this->lines[$i]->detail_batch))
372  { // no batch management
373  if (!$this->create_line($this->lines[$i]->entrepot_id, $this->lines[$i]->origin_line_id, $this->lines[$i]->qty, $this->lines[$i]->rang, $this->lines[$i]->array_options) > 0)
374  {
375  $error++;
376  }
377  } else { // with batch management
378  if (!$this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) > 0)
379  {
380  $error++;
381  }
382  }
383  }
384 
385  if (!$error && $this->id && $this->origin_id)
386  {
387  $ret = $this->add_object_linked();
388  if (!$ret)
389  {
390  $error++;
391  }
392  }
393 
394  // Actions on extra fields
395  if (!$error)
396  {
397  $result = $this->insertExtraFields();
398  if ($result < 0)
399  {
400  $error++;
401  }
402  }
403 
404  if (!$error && !$notrigger)
405  {
406  // Call trigger
407  $result = $this->call_trigger('SHIPPING_CREATE', $user);
408  if ($result < 0) { $error++; }
409  // End call triggers
410 
411  if (!$error)
412  {
413  $this->db->commit();
414  return $this->id;
415  } else {
416  foreach ($this->errors as $errmsg)
417  {
418  dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
419  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
420  }
421  $this->db->rollback();
422  return -1 * $error;
423  }
424  } else {
425  $error++;
426  $this->error = $this->db->lasterror()." - sql=$sql";
427  $this->db->rollback();
428  return -3;
429  }
430  } else {
431  $error++;
432  $this->error = $this->db->lasterror()." - sql=$sql";
433  $this->db->rollback();
434  return -2;
435  }
436  } else {
437  $error++;
438  $this->error = $this->db->error()." - sql=$sql";
439  $this->db->rollback();
440  return -1;
441  }
442  }
443 
444  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
455  public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = 0)
456  {
457  //phpcs:enable
458  global $user;
459 
460  $expeditionline = new ExpeditionLigne($this->db);
461  $expeditionline->fk_expedition = $this->id;
462  $expeditionline->entrepot_id = $entrepot_id;
463  $expeditionline->fk_origin_line = $origin_line_id;
464  $expeditionline->qty = $qty;
465  $expeditionline->rang = $rang;
466  $expeditionline->array_options = $array_options;
467 
468  if (($lineId = $expeditionline->insert($user)) < 0)
469  {
470  $this->errors[] = $expeditionline->error;
471  }
472  return $lineId;
473  }
474 
475 
476  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
484  public function create_line_batch($line_ext, $array_options = 0)
485  {
486  // phpcs:enable
487  $error = 0;
488  $stockLocationQty = array(); // associated array with batch qty in stock location
489 
490  $tab = $line_ext->detail_batch;
491  // create stockLocation Qty array
492  foreach ($tab as $detbatch)
493  {
494  if ($detbatch->entrepot_id)
495  {
496  $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
497  }
498  }
499  // create shipment lines
500  foreach ($stockLocationQty as $stockLocation => $qty)
501  {
502  $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
503  if ($line_id < 0) {
504  $error++;
505  } else {
506  // create shipment batch lines for stockLocation
507  foreach ($tab as $detbatch)
508  {
509  if ($detbatch->entrepot_id == $stockLocation) {
510  if (!($detbatch->create($line_id) > 0)) // Create an expeditionlinebatch
511  {
512  $error++;
513  }
514  }
515  }
516  }
517  }
518 
519  if (!$error) return 1;
520  else return -1;
521  }
522 
532  public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
533  {
534  global $conf;
535 
536  // Check parameters
537  if (empty($id) && empty($ref) && empty($ref_ext)) return -1;
538 
539  $sql = "SELECT e.rowid, e.entity, e.ref, e.fk_soc as socid, e.date_creation, e.ref_customer, e.ref_ext, e.ref_int, e.fk_user_author, e.fk_statut, e.fk_projet as fk_project, e.billed";
540  $sql .= ", e.date_valid";
541  $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
542  $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
543  $sql .= ", e.fk_shipping_method, e.tracking_number";
544  $sql .= ", e.note_private, e.note_public";
545  $sql .= ', e.fk_incoterms, e.location_incoterms';
546  $sql .= ', i.libelle as label_incoterms';
547  $sql .= ', s.libelle as shipping_method';
548  $sql .= ", el.fk_source as origin_id, el.sourcetype as origin";
549  $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
550  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
551  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
552  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
553  $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
554  if ($id) $sql .= " AND e.rowid=".$id;
555  if ($ref) $sql .= " AND e.ref='".$this->db->escape($ref)."'";
556  if ($ref_ext) $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
557  if ($notused) $sql .= " AND e.ref_int='".$this->db->escape($notused)."'";
558 
559  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
560  $result = $this->db->query($sql);
561  if ($result)
562  {
563  if ($this->db->num_rows($result))
564  {
565  $obj = $this->db->fetch_object($result);
566 
567  $this->id = $obj->rowid;
568  $this->entity = $obj->entity;
569  $this->ref = $obj->ref;
570  $this->socid = $obj->socid;
571  $this->ref_customer = $obj->ref_customer;
572  $this->ref_ext = $obj->ref_ext;
573  $this->ref_int = $obj->ref_int;
574  $this->statut = $obj->fk_statut;
575  $this->user_author_id = $obj->fk_user_author;
576  $this->date_creation = $this->db->jdate($obj->date_creation);
577  $this->date_valid = $this->db->jdate($obj->date_valid);
578  $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
579  $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
580  $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
581  $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planed
582  $this->fk_delivery_address = $obj->fk_address;
583  $this->model_pdf = $obj->model_pdf;
584  $this->modelpdf = $obj->model_pdf;
585  $this->shipping_method_id = $obj->fk_shipping_method;
586  $this->shipping_method = $obj->shipping_method;
587  $this->tracking_number = $obj->tracking_number;
588  $this->origin = ($obj->origin ? $obj->origin : 'commande'); // For compatibility
589  $this->origin_id = $obj->origin_id;
590  $this->billed = $obj->billed;
591  $this->fk_project = $obj->fk_project;
592 
593  $this->trueWeight = $obj->weight;
594  $this->weight_units = $obj->weight_units;
595 
596  $this->trueWidth = $obj->width;
597  $this->width_units = $obj->size_units;
598  $this->trueHeight = $obj->height;
599  $this->height_units = $obj->size_units;
600  $this->trueDepth = $obj->size;
601  $this->depth_units = $obj->size_units;
602 
603  $this->note_public = $obj->note_public;
604  $this->note_private = $obj->note_private;
605 
606  // A denormalized value
607  $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
608  $this->size_units = $obj->size_units;
609 
610  //Incoterms
611  $this->fk_incoterms = $obj->fk_incoterms;
612  $this->location_incoterms = $obj->location_incoterms;
613  $this->label_incoterms = $obj->label_incoterms;
614 
615  $this->db->free($result);
616 
617  if ($this->statut == self::STATUS_DRAFT) $this->brouillon = 1;
618 
619  // Tracking url
620  $this->getUrlTrackingStatus($obj->tracking_number);
621 
622  // Thirdparty
623  $result = $this->fetch_thirdparty(); // TODO Remove this
624 
625  // Retrieve extrafields
626  $this->fetch_optionals();
627 
628  // Fix Get multicurrency param for transmited
629  if (!empty($conf->multicurrency->enabled))
630  {
631  if (!empty($this->multicurrency_code)) $this->multicurrency_code = $this->thirdparty->multicurrency_code;
632  if (!empty($conf->global->MULTICURRENCY_USE_ORIGIN_TX) && !empty($this->thirdparty->multicurrency_tx)) $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
633  }
634 
635  /*
636  * Lines
637  */
638  $result = $this->fetch_lines();
639  if ($result < 0)
640  {
641  return -3;
642  }
643 
644  return 1;
645  } else {
646  dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
647  $this->error = 'Delivery with id '.$id.' not found';
648  return 0;
649  }
650  } else {
651  $this->error = $this->db->error();
652  return -1;
653  }
654  }
655 
663  public function valid($user, $notrigger = 0)
664  {
665  global $conf, $langs;
666 
667  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
668 
669  dol_syslog(get_class($this)."::valid");
670 
671  // Protection
672  if ($this->statut)
673  {
674  dol_syslog(get_class($this)."::valid no draft status", LOG_WARNING);
675  return 0;
676  }
677 
678  if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->expedition->creer))
679  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->expedition->shipping_advance->validate))))
680  {
681  $this->error = 'Permission denied';
682  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
683  return -1;
684  }
685 
686  $this->db->begin();
687 
688  $error = 0;
689 
690  // Define new ref
691  $soc = new Societe($this->db);
692  $soc->fetch($this->socid);
693 
694  // Class of company linked to order
695  $result = $soc->set_as_client();
696 
697  // Define new ref
698  if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) // empty should not happened, but when it occurs, the test save life
699  {
700  $numref = $this->getNextNumRef($soc);
701  } else {
702  $numref = "EXP".$this->id;
703  }
704  $this->newref = dol_sanitizeFileName($numref);
705 
706  $now = dol_now();
707 
708  // Validate
709  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
710  $sql .= " ref='".$this->db->escape($numref)."'";
711  $sql .= ", fk_statut = 1";
712  $sql .= ", date_valid = '".$this->db->idate($now)."'";
713  $sql .= ", fk_user_valid = ".$user->id;
714  $sql .= " WHERE rowid = ".$this->id;
715 
716  dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
717  $resql = $this->db->query($sql);
718  if (!$resql)
719  {
720  $this->error = $this->db->lasterror();
721  $error++;
722  }
723 
724  // If stock increment is done on sending (recommanded choice)
725  if (!$error && !empty($conf->stock->enabled) && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT))
726  {
727  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
728 
729  $langs->load("agenda");
730 
731  // Loop on each product line to add a stock movement
732  $sql = "SELECT cd.fk_product, cd.subprice,";
733  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
734  $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
735  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
736  $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
737  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
738  $sql .= " WHERE ed.fk_expedition = ".$this->id;
739  $sql .= " AND cd.rowid = ed.fk_origin_line";
740 
741  dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
742  $resql = $this->db->query($sql);
743  if ($resql)
744  {
745  $cpt = $this->db->num_rows($resql);
746  for ($i = 0; $i < $cpt; $i++)
747  {
748  $obj = $this->db->fetch_object($resql);
749  if (empty($obj->edbrowid))
750  {
751  $qty = $obj->qty;
752  } else {
753  $qty = $obj->edbqty;
754  }
755  if ($qty <= 0) continue;
756  dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
757 
758  //var_dump($this->lines[$i]);
759  $mouvS = new MouvementStock($this->db);
760  $mouvS->origin = &$this;
761 
762  if (empty($obj->edbrowid))
763  {
764  // line without batch detail
765 
766  // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record.
767  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr", $numref));
768  if ($result < 0) {
769  $error++;
770  $this->error = $mouvS->error;
771  $this->errors = array_merge($this->errors, $mouvS->errors);
772  break;
773  }
774  } else {
775  // line with batch detail
776 
777  // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record.
778  // Note: ->fk_origin_stock = id into table llx_product_batch (may be rename into llx_product_stock_batch in another version)
779  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr", $numref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
780  if ($result < 0) {
781  $error++;
782  $this->error = $mouvS->error;
783  $this->errors = array_merge($this->errors, $mouvS->errors);
784  break;
785  }
786  }
787  }
788  } else {
789  $this->db->rollback();
790  $this->error = $this->db->error();
791  return -2;
792  }
793  }
794 
795  // Change status of order to "shipment in process"
796  $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
797 
798  if (!$ret)
799  {
800  $error++;
801  }
802 
803  if (!$error && !$notrigger)
804  {
805  // Call trigger
806  $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
807  if ($result < 0) { $error++; }
808  // End call triggers
809  }
810 
811  if (!$error)
812  {
813  $this->oldref = $this->ref;
814 
815  // Rename directory if dir was a temporary ref
816  if (preg_match('/^[\(]?PROV/i', $this->ref))
817  {
818  // Now we rename also files into index
819  $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
820  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
821  $resql = $this->db->query($sql);
822  if (!$resql) { $error++; $this->error = $this->db->lasterror(); }
823 
824  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
825  $oldref = dol_sanitizeFileName($this->ref);
826  $newref = dol_sanitizeFileName($numref);
827  $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
828  $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
829  if (!$error && file_exists($dirsource))
830  {
831  dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
832 
833  if (@rename($dirsource, $dirdest))
834  {
835  dol_syslog("Rename ok");
836  // Rename docs starting with $oldref with $newref
837  $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
838  foreach ($listoffiles as $fileentry)
839  {
840  $dirsource = $fileentry['name'];
841  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
842  $dirsource = $fileentry['path'].'/'.$dirsource;
843  $dirdest = $fileentry['path'].'/'.$dirdest;
844  @rename($dirsource, $dirdest);
845  }
846  }
847  }
848  }
849  }
850 
851  // Set new ref and current status
852  if (!$error)
853  {
854  $this->ref = $numref;
855  $this->statut = self::STATUS_VALIDATED;
856  }
857 
858  if (!$error)
859  {
860  $this->db->commit();
861  return 1;
862  } else {
863  $this->db->rollback();
864  return -1 * $error;
865  }
866  }
867 
868 
869  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
876  public function create_delivery($user)
877  {
878  // phpcs:enable
879  global $conf;
880 
881  if ($conf->delivery_note->enabled)
882  {
883  if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED)
884  {
885  // Expedition validee
886  include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
887  $delivery = new Delivery($this->db);
888  $result = $delivery->create_from_sending($user, $this->id);
889  if ($result > 0)
890  {
891  return $result;
892  } else {
893  $this->error = $delivery->error;
894  return $result;
895  }
896  } else return 0;
897  } else return 0;
898  }
899 
911  public function addline($entrepot_id, $id, $qty, $array_options = 0)
912  {
913  global $conf, $langs;
914 
915  $num = count($this->lines);
916  $line = new ExpeditionLigne($this->db);
917 
918  $line->entrepot_id = $entrepot_id;
919  $line->origin_line_id = $id;
920  $line->fk_origin_line = $id;
921  $line->qty = $qty;
922 
923  $orderline = new OrderLine($this->db);
924  $orderline->fetch($id);
925 
926  // Copy the rang of the order line to the expedition line
927  $line->rang = $orderline->rang;
928 
929  if (!empty($conf->stock->enabled) && !empty($orderline->fk_product))
930  {
931  $fk_product = $orderline->fk_product;
932 
933  if (!($entrepot_id > 0) && empty($conf->global->STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS)) {
934  $langs->load("errors");
935  $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
936  return -1;
937  }
938 
939  if ($conf->global->STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT) {
940  $product = new Product($this->db);
941  $product->fetch($fk_product);
942 
943  // Check must be done for stock of product into warehouse if $entrepot_id defined
944  if ($entrepot_id > 0) {
945  $product->load_stock('warehouseopen');
946  $product_stock = $product->stock_warehouse[$entrepot_id]->real;
947  } else {
948  $product_stock = $product->stock_reel;
949  }
950 
951  $product_type = $product->type;
952  if ($product_type == 0 || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
953  $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
954  // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
955  if (!$isavirtualproduct || empty($conf->global->PRODUIT_SOUSPRODUITS) || ($isavirtualproduct && empty($conf->global->STOCK_EXCLUDE_VIRTUAL_PRODUCTS))) { // If STOCK_EXCLUDE_VIRTUAL_PRODUCTS is set, we do not manage stock for kits/virtual products.
956  if ($product_stock < $qty) {
957  $langs->load("errors");
958  $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
959  $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
960 
961  $this->db->rollback();
962  return -3;
963  }
964  }
965  }
966  }
967  }
968 
969  // If product need a batch number, we should not have called this function but addline_batch instead.
970  if (!empty($conf->productbatch->enabled) && !empty($orderline->fk_product) && !empty($orderline->product_tobatch))
971  {
972  $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH';
973  return -4;
974  }
975 
976  // extrafields
977  if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options) > 0) // For avoid conflicts if trigger used
978  $line->array_options = $array_options;
979 
980  $this->lines[$num] = $line;
981  }
982 
983  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
991  public function addline_batch($dbatch, $array_options = 0)
992  {
993  // phpcs:enable
994  global $conf, $langs;
995 
996  $num = count($this->lines);
997  if ($dbatch['qty'] > 0)
998  {
999  $line = new ExpeditionLigne($this->db);
1000  $tab = array();
1001  foreach ($dbatch['detail'] as $key=>$value)
1002  {
1003  if ($value['q'] > 0)
1004  {
1005  // $value['q']=qty to move
1006  // $value['id_batch']=id into llx_product_batch of record to move
1007  //var_dump($value);
1008 
1009  $linebatch = new ExpeditionLineBatch($this->db);
1010  $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1011  if ($ret < 0)
1012  {
1013  $this->error = $linebatch->error;
1014  return -1;
1015  }
1016  $linebatch->qty = $value['q'];
1017  $tab[] = $linebatch;
1018 
1019  if ($conf->global->STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT)
1020  {
1021  require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1022  $prod_batch = new Productbatch($this->db);
1023  $prod_batch->fetch($value['id_batch']);
1024 
1025  if ($prod_batch->qty < $linebatch->qty)
1026  {
1027  $langs->load("errors");
1028  $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1029  dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1030  $this->db->rollback();
1031  return -1;
1032  }
1033  }
1034 
1035  //var_dump($linebatch);
1036  }
1037  }
1038  $line->entrepot_id = $linebatch->entrepot_id;
1039  $line->origin_line_id = $dbatch['ix_l']; // deprecated
1040  $line->fk_origin_line = $dbatch['ix_l'];
1041  $line->qty = $dbatch['qty'];
1042  $line->detail_batch = $tab;
1043 
1044  // extrafields
1045  if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options) > 0) // For avoid conflicts if trigger used
1046  $line->array_options = $array_options;
1047 
1048  //var_dump($line);
1049  $this->lines[$num] = $line;
1050  return 1;
1051  }
1052  }
1053 
1061  public function update($user = null, $notrigger = 0)
1062  {
1063  global $conf;
1064  $error = 0;
1065 
1066  // Clean parameters
1067 
1068  if (isset($this->ref)) $this->ref = trim($this->ref);
1069  if (isset($this->entity)) $this->entity = (int) $this->entity;
1070  if (isset($this->ref_customer)) $this->ref_customer = trim($this->ref_customer);
1071  if (isset($this->socid)) $this->socid = (int) $this->socid;
1072  if (isset($this->fk_user_author)) $this->fk_user_author = (int) $this->fk_user_author;
1073  if (isset($this->fk_user_valid)) $this->fk_user_valid = (int) $this->fk_user_valid;
1074  if (isset($this->fk_delivery_address)) $this->fk_delivery_address = (int) $this->fk_delivery_address;
1075  if (isset($this->shipping_method_id)) $this->shipping_method_id = (int) $this->shipping_method_id;
1076  if (isset($this->tracking_number)) $this->tracking_number = trim($this->tracking_number);
1077  if (isset($this->statut)) $this->statut = (int) $this->statut;
1078  if (isset($this->trueDepth)) $this->trueDepth = trim($this->trueDepth);
1079  if (isset($this->trueWidth)) $this->trueWidth = trim($this->trueWidth);
1080  if (isset($this->trueHeight)) $this->trueHeight = trim($this->trueHeight);
1081  if (isset($this->size_units)) $this->size_units = trim($this->size_units);
1082  if (isset($this->weight_units)) $this->weight_units = trim($this->weight_units);
1083  if (isset($this->trueWeight)) $this->weight = trim($this->trueWeight);
1084  if (isset($this->note_private)) $this->note_private = trim($this->note_private);
1085  if (isset($this->note_public)) $this->note_public = trim($this->note_public);
1086  if (isset($this->model_pdf)) $this->model_pdf = trim($this->model_pdf);
1087 
1088 
1089 
1090  // Check parameters
1091  // Put here code to add control on parameters values
1092 
1093  // Update request
1094  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1095 
1096  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1097  $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1098  $sql .= " ref_customer=".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1099  $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1100  $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1101  $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1102  $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1103  $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1104  $sql .= " date_expedition=".(dol_strlen($this->date_expedition) != 0 ? "'".$this->db->idate($this->date_expedition)."'" : 'null').",";
1105  $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1106  $sql .= " fk_address=".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1107  $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1108  $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1109  $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1110  $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1111  $sql .= " height=".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1112  $sql .= " width=".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1113  $sql .= " size_units=".(isset($this->size_units) ? $this->size_units : "null").",";
1114  $sql .= " size=".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1115  $sql .= " weight_units=".(isset($this->weight_units) ? $this->weight_units : "null").",";
1116  $sql .= " weight=".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1117  $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1118  $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1119  $sql .= " model_pdf=".(isset($this->modelpdf) ? "'".$this->db->escape($this->modelpdf)."'" : "null").",";
1120  $sql .= " entity=".$conf->entity;
1121 
1122  $sql .= " WHERE rowid=".$this->id;
1123 
1124  $this->db->begin();
1125 
1126  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1127  $resql = $this->db->query($sql);
1128  if (!$resql) { $error++; $this->errors[] = "Error ".$this->db->lasterror(); }
1129 
1130  if (!$error && !$notrigger) {
1131  // Call trigger
1132  $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1133  if ($result < 0) { $error++; }
1134  // End call triggers
1135  }
1136 
1137  // Commit or rollback
1138  if ($error)
1139  {
1140  foreach ($this->errors as $errmsg)
1141  {
1142  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1143  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1144  }
1145  $this->db->rollback();
1146  return -1 * $error;
1147  } else {
1148  $this->db->commit();
1149  return 1;
1150  }
1151  }
1152 
1153 
1161  public function cancel($notrigger = 0, $also_update_stock = false)
1162  {
1163  global $conf, $langs, $user;
1164 
1165  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1166  require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionbatch.class.php';
1167 
1168  $error = 0;
1169  $this->error = '';
1170 
1171  $this->db->begin();
1172 
1173  // Add a protection to refuse deleting if shipment has at least one delivery
1174  $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1175  if (count($this->linkedObjectsIds) > 0) {
1176  $this->error = 'ErrorThereIsSomeDeliveries';
1177  $error++;
1178  }
1179 
1180  if (!$error && !$notrigger) {
1181  // Call trigger
1182  $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1183  if ($result < 0) { $error++; }
1184  // End call triggers
1185  }
1186 
1187  // Stock control
1188  if (!$error && $conf->stock->enabled &&
1189  (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
1190  ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock)))
1191  {
1192  require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1193 
1194  $langs->load("agenda");
1195 
1196  // Loop on each product line to add a stock movement and delete features
1197  $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1198  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1199  $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1200  $sql .= " WHERE ed.fk_expedition = ".$this->id;
1201  $sql .= " AND cd.rowid = ed.fk_origin_line";
1202 
1203  dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1204  $resql = $this->db->query($sql);
1205  if ($resql)
1206  {
1207  $cpt = $this->db->num_rows($resql);
1208  for ($i = 0; $i < $cpt; $i++)
1209  {
1210  dol_syslog(get_class($this)."::delete movement index ".$i);
1211  $obj = $this->db->fetch_object($resql);
1212 
1213  $mouvS = new MouvementStock($this->db);
1214  // we do not log origin because it will be deleted
1215  $mouvS->origin = null;
1216  // get lot/serial
1217  $lotArray = null;
1218  if ($conf->productbatch->enabled)
1219  {
1220  $lotArray = ExpeditionLineBatch::fetchAll($this->db, $obj->expeditiondet_id);
1221  if (!is_array($lotArray))
1222  {
1223  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1224  }
1225  }
1226  if (empty($lotArray)) {
1227  // no lot/serial
1228  // We increment stock of product (and sub-products)
1229  // We use warehouse selected for each line
1230  $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref)); // Price is set to 0, because we don't want to see WAP changed
1231  if ($result < 0)
1232  {
1233  $error++; $this->errors = $this->errors + $mouvS->errors;
1234  break;
1235  }
1236  } else {
1237  // We increment stock of batches
1238  // We use warehouse selected for each line
1239  foreach ($lotArray as $lot)
1240  {
1241  $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch); // Price is set to 0, because we don't want to see WAP changed
1242  if ($result < 0)
1243  {
1244  $error++; $this->errors = $this->errors + $mouvS->errors;
1245  break;
1246  }
1247  }
1248  if ($error) break; // break for loop incase of error
1249  }
1250  }
1251  } else {
1252  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1253  }
1254  }
1255 
1256  // delete batch expedition line
1257  if (!$error && $conf->productbatch->enabled)
1258  {
1259  if (ExpeditionLineBatch::deletefromexp($this->db, $this->id) < 0)
1260  {
1261  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1262  }
1263  }
1264 
1265 
1266  if (!$error)
1267  {
1268  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1269  $sql .= " WHERE fk_expedition = ".$this->id;
1270 
1271  if ($this->db->query($sql))
1272  {
1273  // Delete linked object
1274  $res = $this->deleteObjectLinked();
1275  if ($res < 0) $error++;
1276 
1277  // No delete expedition
1278  if (!$error)
1279  {
1280  $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1281  $sql .= " WHERE rowid = ".$this->id;
1282 
1283  if ($this->db->query($sql))
1284  {
1285  if (!empty($this->origin) && $this->origin_id > 0)
1286  {
1287  $this->fetch_origin();
1288  $origin = $this->origin;
1289  if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) // If order source of shipment is "shipment in progress"
1290  {
1291  // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1292  $this->$origin->loadExpeditions();
1293  //var_dump($this->$origin->expeditions);exit;
1294  if (count($this->$origin->expeditions) <= 0)
1295  {
1296  $this->$origin->setStatut(Commande::STATUS_VALIDATED);
1297  }
1298  }
1299  }
1300 
1301  if (!$error)
1302  {
1303  $this->db->commit();
1304 
1305  // We delete PDFs
1306  $ref = dol_sanitizeFileName($this->ref);
1307  if (!empty($conf->expedition->dir_output))
1308  {
1309  $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1310  $file = $dir.'/'.$ref.'.pdf';
1311  if (file_exists($file))
1312  {
1313  if (!dol_delete_file($file))
1314  {
1315  return 0;
1316  }
1317  }
1318  if (file_exists($dir))
1319  {
1320  if (!dol_delete_dir_recursive($dir))
1321  {
1322  $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1323  return 0;
1324  }
1325  }
1326  }
1327 
1328  return 1;
1329  } else {
1330  $this->db->rollback();
1331  return -1;
1332  }
1333  } else {
1334  $this->error = $this->db->lasterror()." - sql=$sql";
1335  $this->db->rollback();
1336  return -3;
1337  }
1338  } else {
1339  $this->error = $this->db->lasterror()." - sql=$sql";
1340  $this->db->rollback();
1341  return -2;
1342  }//*/
1343  } else {
1344  $this->error = $this->db->lasterror()." - sql=$sql";
1345  $this->db->rollback();
1346  return -1;
1347  }
1348  } else {
1349  $this->db->rollback();
1350  return -1;
1351  }
1352  }
1353 
1362  public function delete($notrigger = 0, $also_update_stock = false)
1363  {
1364  global $conf, $langs, $user;
1365 
1366  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1367  require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionbatch.class.php';
1368 
1369  $error = 0;
1370  $this->error = '';
1371 
1372  $this->db->begin();
1373 
1374  // Add a protection to refuse deleting if shipment has at least one delivery
1375  $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1376  if (count($this->linkedObjectsIds) > 0)
1377  {
1378  $this->error = 'ErrorThereIsSomeDeliveries';
1379  $error++;
1380  }
1381 
1382  if (!$error && !$notrigger) {
1383  // Call trigger
1384  $result = $this->call_trigger('SHIPPING_DELETE', $user);
1385  if ($result < 0) { $error++; }
1386  // End call triggers
1387  }
1388 
1389  // Stock control
1390  if (!$error && $conf->stock->enabled &&
1391  (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
1392  ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock)))
1393  {
1394  require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1395 
1396  $langs->load("agenda");
1397 
1398  // Loop on each product line to add a stock movement
1399  $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1400  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1401  $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1402  $sql .= " WHERE ed.fk_expedition = ".$this->id;
1403  $sql .= " AND cd.rowid = ed.fk_origin_line";
1404 
1405  dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1406  $resql = $this->db->query($sql);
1407  if ($resql)
1408  {
1409  $cpt = $this->db->num_rows($resql);
1410  for ($i = 0; $i < $cpt; $i++)
1411  {
1412  dol_syslog(get_class($this)."::delete movement index ".$i);
1413  $obj = $this->db->fetch_object($resql);
1414 
1415  $mouvS = new MouvementStock($this->db);
1416  // we do not log origin because it will be deleted
1417  $mouvS->origin = null;
1418  // get lot/serial
1419  $lotArray = null;
1420  if ($conf->productbatch->enabled)
1421  {
1422  $lotArray = ExpeditionLineBatch::fetchAll($this->db, $obj->expeditiondet_id);
1423  if (!is_array($lotArray))
1424  {
1425  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1426  }
1427  }
1428  if (empty($lotArray)) {
1429  // no lot/serial
1430  // We increment stock of product (and sub-products)
1431  // We use warehouse selected for each line
1432  $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref)); // Price is set to 0, because we don't want to see WAP changed
1433  if ($result < 0)
1434  {
1435  $error++; $this->errors = $this->errors + $mouvS->errors;
1436  break;
1437  }
1438  } else {
1439  // We increment stock of batches
1440  // We use warehouse selected for each line
1441  foreach ($lotArray as $lot)
1442  {
1443  $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch); // Price is set to 0, because we don't want to see WAP changed
1444  if ($result < 0)
1445  {
1446  $error++; $this->errors = $this->errors + $mouvS->errors;
1447  break;
1448  }
1449  }
1450  if ($error) break; // break for loop incase of error
1451  }
1452  }
1453  } else {
1454  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1455  }
1456  }
1457 
1458  // delete batch expedition line
1459  if (!$error && $conf->productbatch->enabled)
1460  {
1461  if (ExpeditionLineBatch::deletefromexp($this->db, $this->id) < 0)
1462  {
1463  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1464  }
1465  }
1466 
1467  if (!$error)
1468  {
1469  $main = MAIN_DB_PREFIX.'expeditiondet';
1470  $ef = $main."_extrafields";
1471  $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".$this->id.")";
1472 
1473  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1474  $sql .= " WHERE fk_expedition = ".$this->id;
1475 
1476  if ($this->db->query($sqlef) && $this->db->query($sql))
1477  {
1478  // Delete linked object
1479  $res = $this->deleteObjectLinked();
1480  if ($res < 0) $error++;
1481 
1482  // delete extrafields
1483  $res = $this->deleteExtraFields();
1484  if ($res < 0) $error++;
1485 
1486  if (!$error)
1487  {
1488  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1489  $sql .= " WHERE rowid = ".$this->id;
1490 
1491  if ($this->db->query($sql))
1492  {
1493  if (!empty($this->origin) && $this->origin_id > 0)
1494  {
1495  $this->fetch_origin();
1496  $origin = $this->origin;
1497  if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) // If order source of shipment is "shipment in progress"
1498  {
1499  // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1500  $this->$origin->loadExpeditions();
1501  //var_dump($this->$origin->expeditions);exit;
1502  if (count($this->$origin->expeditions) <= 0)
1503  {
1504  $this->$origin->setStatut(Commande::STATUS_VALIDATED);
1505  }
1506  }
1507  }
1508 
1509  if (!$error)
1510  {
1511  $this->db->commit();
1512 
1513  // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1514  $this->deleteEcmFiles();
1515 
1516  // We delete PDFs
1517  $ref = dol_sanitizeFileName($this->ref);
1518  if (!empty($conf->expedition->dir_output))
1519  {
1520  $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1521  $file = $dir.'/'.$ref.'.pdf';
1522  if (file_exists($file))
1523  {
1524  if (!dol_delete_file($file))
1525  {
1526  return 0;
1527  }
1528  }
1529  if (file_exists($dir))
1530  {
1531  if (!dol_delete_dir_recursive($dir))
1532  {
1533  $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1534  return 0;
1535  }
1536  }
1537  }
1538 
1539  return 1;
1540  } else {
1541  $this->db->rollback();
1542  return -1;
1543  }
1544  } else {
1545  $this->error = $this->db->lasterror()." - sql=$sql";
1546  $this->db->rollback();
1547  return -3;
1548  }
1549  } else {
1550  $this->error = $this->db->lasterror()." - sql=$sql";
1551  $this->db->rollback();
1552  return -2;
1553  }
1554  } else {
1555  $this->error = $this->db->lasterror()." - sql=$sql";
1556  $this->db->rollback();
1557  return -1;
1558  }
1559  } else {
1560  $this->db->rollback();
1561  return -1;
1562  }
1563  }
1564 
1565  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1571  public function fetch_lines()
1572  {
1573  // phpcs:enable
1574  global $conf, $mysoc;
1575  // TODO: recuperer les champs du document associe a part
1576  $this->lines = array();
1577 
1578  $sql = "SELECT cd.rowid, cd.fk_product, cd.label as custom_label, cd.description, cd.qty as qty_asked, cd.product_type, cd.fk_unit";
1579  $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1580  $sql .= ", cd.vat_src_code, cd.tva_tx, cd.localtax1_tx, cd.localtax2_tx, cd.localtax1_type, cd.localtax2_type, cd.info_bits, cd.price, cd.subprice, cd.remise_percent,cd.buy_price_ht as pa_ht";
1581  $sql .= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc, cd.rang";
1582  $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_origin_line, ed.fk_entrepot";
1583  $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type";
1584  $sql .= ", p.weight, p.weight_units, p.length, p.length_units, p.surface, p.surface_units, p.volume, p.volume_units, p.tosell as product_tosell, p.tobuy as product_tobuy, p.tobatch as product_tobatch";
1585  $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1586  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1587  $sql .= " WHERE ed.fk_expedition = ".$this->id;
1588  $sql .= " AND ed.fk_origin_line = cd.rowid";
1589  $sql .= " ORDER BY cd.rang, ed.fk_origin_line";
1590 
1591  dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1592  $resql = $this->db->query($sql);
1593  if ($resql)
1594  {
1595  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1596 
1597  $num = $this->db->num_rows($resql);
1598  $i = 0;
1599  $lineindex = 0;
1600  $originline = 0;
1601 
1602  $this->total_ht = 0;
1603  $this->total_tva = 0;
1604  $this->total_ttc = 0;
1605  $this->total_localtax1 = 0;
1606  $this->total_localtax2 = 0;
1607 
1608  $line = new ExpeditionLigne($this->db);
1609 
1610  while ($i < $num)
1611  {
1612  $obj = $this->db->fetch_object($resql);
1613 
1614  if ($originline == $obj->fk_origin_line) {
1615  $line->entrepot_id = 0; // entrepod_id in details_entrepot
1616  $line->qty_shipped += $obj->qty_shipped;
1617  } else {
1618  $line = new ExpeditionLigne($this->db);
1619  $line->entrepot_id = $obj->fk_entrepot;
1620  $line->qty_shipped = $obj->qty_shipped;
1621  }
1622 
1623  $detail_entrepot = new stdClass;
1624  $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1625  $detail_entrepot->qty_shipped = $obj->qty_shipped;
1626  $detail_entrepot->line_id = $obj->line_id;
1627  $line->details_entrepot[] = $detail_entrepot;
1628 
1629  $line->line_id = $obj->line_id;
1630  $line->rowid = $obj->line_id; // TODO deprecated
1631  $line->id = $obj->line_id;
1632 
1633  $line->fk_origin = 'orderline';
1634  $line->fk_origin_line = $obj->fk_origin_line;
1635  $line->origin_line_id = $obj->fk_origin_line; // TODO deprecated
1636 
1637  $line->fk_expedition = $this->id; // id of parent
1638 
1639  $line->product_type = $obj->product_type;
1640  $line->fk_product = $obj->fk_product;
1641  $line->fk_product_type = $obj->fk_product_type;
1642  $line->ref = $obj->product_ref; // TODO deprecated
1643  $line->product_ref = $obj->product_ref;
1644  $line->product_label = $obj->product_label;
1645  $line->libelle = $obj->product_label; // TODO deprecated
1646  $line->product_tosell = $obj->product_tosell;
1647  $line->product_tobuy = $obj->product_tobuy;
1648  $line->product_tobatch = $obj->product_tobatch;
1649  $line->label = $obj->custom_label;
1650  $line->description = $obj->description;
1651  $line->qty_asked = $obj->qty_asked;
1652  $line->rang = $obj->rang;
1653  $line->weight = $obj->weight;
1654  $line->weight_units = $obj->weight_units;
1655  $line->length = $obj->length;
1656  $line->length_units = $obj->length_units;
1657  $line->surface = $obj->surface;
1658  $line->surface_units = $obj->surface_units;
1659  $line->volume = $obj->volume;
1660  $line->volume_units = $obj->volume_units;
1661  $line->fk_unit = $obj->fk_unit;
1662 
1663  $line->pa_ht = $obj->pa_ht;
1664 
1665  // Local taxes
1666  $localtax_array = array(0=>$obj->localtax1_type, 1=>$obj->localtax1_tx, 2=>$obj->localtax2_type, 3=>$obj->localtax2_tx);
1667  $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1668  $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1669 
1670  // For invoicing
1671  $tabprice = calcul_price_total($obj->qty_shipped, $obj->subprice, $obj->remise_percent, $obj->tva_tx, $localtax1_tx, $localtax2_tx, 0, 'HT', $obj->info_bits, $obj->fk_product_type, $mysoc, $localtax_array); // We force type to 0
1672  $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1673  $line->qty = $line->qty_shipped;
1674  $line->total_ht = $tabprice[0];
1675  $line->total_localtax1 = $tabprice[9];
1676  $line->total_localtax2 = $tabprice[10];
1677  $line->total_ttc = $tabprice[2];
1678  $line->total_tva = $tabprice[1];
1679  $line->vat_src_code = $obj->vat_src_code;
1680  $line->tva_tx = $obj->tva_tx;
1681  $line->localtax1_tx = $obj->localtax1_tx;
1682  $line->localtax2_tx = $obj->localtax2_tx;
1683  $line->info_bits = $obj->info_bits;
1684  $line->price = $obj->price;
1685  $line->subprice = $obj->subprice;
1686  $line->remise_percent = $obj->remise_percent;
1687 
1688  $this->total_ht += $tabprice[0];
1689  $this->total_tva += $tabprice[1];
1690  $this->total_ttc += $tabprice[2];
1691  $this->total_localtax1 += $tabprice[9];
1692  $this->total_localtax2 += $tabprice[10];
1693 
1694  // Multicurrency
1695  $this->fk_multicurrency = $obj->fk_multicurrency;
1696  $this->multicurrency_code = $obj->multicurrency_code;
1697  $this->multicurrency_subprice = $obj->multicurrency_subprice;
1698  $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1699  $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1700  $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1701 
1702  if ($originline != $obj->fk_origin_line)
1703  {
1704  $line->detail_batch = array();
1705  }
1706 
1707  // Detail of batch
1708  if (!empty($conf->productbatch->enabled) && $obj->line_id > 0 && $obj->product_tobatch > 0)
1709  {
1710  require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionbatch.class.php';
1711 
1712  $newdetailbatch = ExpeditionLineBatch::fetchAll($this->db, $obj->line_id, $obj->fk_product);
1713  if (is_array($newdetailbatch))
1714  {
1715  if ($originline != $obj->fk_origin_line)
1716  {
1717  $line->detail_batch = $newdetailbatch;
1718  } else {
1719  $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1720  }
1721  }
1722  }
1723 
1724  if ($originline != $obj->fk_origin_line)
1725  {
1726  $this->lines[$lineindex] = $line;
1727  $lineindex++;
1728  } else {
1729  $line->total_ht += $tabprice[0];
1730  $line->total_localtax1 += $tabprice[9];
1731  $line->total_localtax2 += $tabprice[10];
1732  $line->total_ttc += $tabprice[2];
1733  $line->total_tva += $tabprice[1];
1734  }
1735  $line->fetch_optionals();
1736  $i++;
1737  $originline = $obj->fk_origin_line;
1738  }
1739  $this->db->free($resql);
1740  return 1;
1741  } else {
1742  $this->error = $this->db->error();
1743  return -3;
1744  }
1745  }
1746 
1754  public function deleteline($user, $lineid)
1755  {
1756  global $user;
1757 
1758  if ($this->statut == self::STATUS_DRAFT)
1759  {
1760  $this->db->begin();
1761 
1762  $line = new ExpeditionLigne($this->db);
1763 
1764  // For triggers
1765  $line->fetch($lineid);
1766 
1767  if ($line->delete($user) > 0)
1768  {
1769  //$this->update_price(1);
1770 
1771  $this->db->commit();
1772  return 1;
1773  } else {
1774  $this->db->rollback();
1775  return -1;
1776  }
1777  } else {
1778  $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1779  return -2;
1780  }
1781  }
1782 
1783 
1795  public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1796  {
1797  global $langs, $conf;
1798 
1799  $result = '';
1800  $label = '<u>'.$langs->trans("Shipment").'</u>';
1801  $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1802  $label .= '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1803 
1804  $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1805 
1806  if ($short) return $url;
1807 
1808  if ($option !== 'nolink')
1809  {
1810  // Add param to save lastsearch_values or not
1811  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1812  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1;
1813  if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1';
1814  }
1815 
1816  $linkclose = '';
1817  if (empty($notooltip))
1818  {
1819  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
1820  {
1821  $label = $langs->trans("Shipment");
1822  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1823  }
1824  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1825  $linkclose .= ' class="classfortooltip"';
1826  }
1827 
1828  $linkstart = '<a href="'.$url.'"';
1829  $linkstart .= $linkclose.'>';
1830  $linkend = '</a>';
1831 
1832  $result .= $linkstart;
1833  if ($withpicto) $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
1834  if ($withpicto != 2) $result .= $this->ref;
1835  $result .= $linkend;
1836 
1837  return $result;
1838  }
1839 
1846  public function getLibStatut($mode = 0)
1847  {
1848  return $this->LibStatut($this->statut, $mode);
1849  }
1850 
1851  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1859  public function LibStatut($status, $mode)
1860  {
1861  // phpcs:enable
1862  global $langs;
1863 
1864  $labelStatus = $langs->trans($this->statuts[$status]);
1865  $labelStatusShort = $langs->trans($this->statutshorts[$status]);
1866 
1867  $statusType = 'status'.$status;
1868  if ($status == self::STATUS_VALIDATED) $statusType = 'status4';
1869  if ($status == self::STATUS_CLOSED) $statusType = 'status6';
1870  if ($status == self::STATUS_CANCELED) $statusType = 'status9';
1871 
1872  return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
1873  }
1874 
1882  public function initAsSpecimen()
1883  {
1884  global $langs;
1885 
1886  $now = dol_now();
1887 
1888  dol_syslog(get_class($this)."::initAsSpecimen");
1889 
1890  // Load array of products prodids
1891  $num_prods = 0;
1892  $prodids = array();
1893  $sql = "SELECT rowid";
1894  $sql .= " FROM ".MAIN_DB_PREFIX."product";
1895  $sql .= " WHERE entity IN (".getEntity('product').")";
1896  $resql = $this->db->query($sql);
1897  if ($resql)
1898  {
1899  $num_prods = $this->db->num_rows($resql);
1900  $i = 0;
1901  while ($i < $num_prods)
1902  {
1903  $i++;
1904  $row = $this->db->fetch_row($resql);
1905  $prodids[$i] = $row[0];
1906  }
1907  }
1908 
1909  $order = new Commande($this->db);
1910  $order->initAsSpecimen();
1911 
1912  // Initialise parametres
1913  $this->id = 0;
1914  $this->ref = 'SPECIMEN';
1915  $this->specimen = 1;
1916  $this->statut = self::STATUS_VALIDATED;
1917  $this->livraison_id = 0;
1918  $this->date = $now;
1919  $this->date_creation = $now;
1920  $this->date_valid = $now;
1921  $this->date_delivery = $now;
1922  $this->date_expedition = $now + 24 * 3600;
1923 
1924  $this->entrepot_id = 0;
1925  $this->fk_delivery_address = 0;
1926  $this->socid = 1;
1927 
1928  $this->commande_id = 0;
1929  $this->commande = $order;
1930 
1931  $this->origin_id = 1;
1932  $this->origin = 'commande';
1933 
1934  $this->note_private = 'Private note';
1935  $this->note_public = 'Public note';
1936 
1937  $nbp = 5;
1938  $xnbp = 0;
1939  while ($xnbp < $nbp)
1940  {
1941  $line = new ExpeditionLigne($this->db);
1942  $line->desc = $langs->trans("Description")." ".$xnbp;
1943  $line->libelle = $langs->trans("Description")." ".$xnbp; // deprecated
1944  $line->label = $langs->trans("Description")." ".$xnbp;
1945  $line->qty = 10;
1946  $line->qty_asked = 5;
1947  $line->qty_shipped = 4;
1948  $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
1949 
1950  $this->lines[] = $line;
1951  $xnbp++;
1952  }
1953  }
1954 
1955  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1964  public function set_date_livraison($user, $delivery_date)
1965  {
1966  // phpcs:enable
1967  return $this->setDeliveryDate($user, $delivery_date);
1968  }
1969 
1977  public function setDeliveryDate($user, $delivery_date)
1978  {
1979  if ($user->rights->expedition->creer)
1980  {
1981  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
1982  $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
1983  $sql .= " WHERE rowid = ".$this->id;
1984 
1985  dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
1986  $resql = $this->db->query($sql);
1987  if ($resql)
1988  {
1989  $this->date_delivery = $delivery_date;
1990  return 1;
1991  } else {
1992  $this->error = $this->db->error();
1993  return -1;
1994  }
1995  } else {
1996  return -2;
1997  }
1998  }
1999 
2000  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2006  public function fetch_delivery_methods()
2007  {
2008  // phpcs:enable
2009  global $langs;
2010  $this->meths = array();
2011 
2012  $sql = "SELECT em.rowid, em.code, em.libelle as label";
2013  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2014  $sql .= " WHERE em.active = 1";
2015  $sql .= " ORDER BY em.libelle ASC";
2016 
2017  $resql = $this->db->query($sql);
2018  if ($resql)
2019  {
2020  while ($obj = $this->db->fetch_object($resql))
2021  {
2022  $label = $langs->trans('SendingMethod'.$obj->code);
2023  $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2024  }
2025  }
2026  }
2027 
2028  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2035  public function list_delivery_methods($id = '')
2036  {
2037  // phpcs:enable
2038  global $langs;
2039 
2040  $this->listmeths = array();
2041  $i = 0;
2042 
2043  $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2044  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2045  if ($id != '') $sql .= " WHERE em.rowid=".$id;
2046 
2047  $resql = $this->db->query($sql);
2048  if ($resql)
2049  {
2050  while ($obj = $this->db->fetch_object($resql))
2051  {
2052  $this->listmeths[$i]['rowid'] = $obj->rowid;
2053  $this->listmeths[$i]['code'] = $obj->code;
2054  $label = $langs->trans('SendingMethod'.$obj->code);
2055  $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2056  $this->listmeths[$i]['description'] = $obj->description;
2057  $this->listmeths[$i]['tracking'] = $obj->tracking;
2058  $this->listmeths[$i]['active'] = $obj->active;
2059  $i++;
2060  }
2061  }
2062  }
2063 
2064  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2072  public function update_delivery_method($id = '')
2073  {
2074  // phpcs:enable
2075  if ($id == '')
2076  {
2077  $sql = "INSERT INTO ".MAIN_DB_PREFIX."c_shipment_mode (code, libelle, description, tracking)";
2078  $sql .= " VALUES ('".$this->db->escape($this->update['code'])."','".$this->db->escape($this->update['libelle'])."','".$this->db->escape($this->update['description'])."','".$this->db->escape($this->update['tracking'])."')";
2079  $resql = $this->db->query($sql);
2080  } else {
2081  $sql = "UPDATE ".MAIN_DB_PREFIX."c_shipment_mode SET";
2082  $sql .= " code='".$this->db->escape($this->update['code'])."'";
2083  $sql .= ",libelle='".$this->db->escape($this->update['libelle'])."'";
2084  $sql .= ",description='".$this->db->escape($this->update['description'])."'";
2085  $sql .= ",tracking='".$this->db->escape($this->update['tracking'])."'";
2086  $sql .= " WHERE rowid=".$id;
2087  $resql = $this->db->query($sql);
2088  }
2089  if ($resql < 0) dol_print_error($this->db, '');
2090  }
2091 
2092  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2099  public function activ_delivery_method($id)
2100  {
2101  // phpcs:enable
2102  $sql = 'UPDATE '.MAIN_DB_PREFIX.'c_shipment_mode SET active=1';
2103  $sql .= ' WHERE rowid='.$id;
2104 
2105  $resql = $this->db->query($sql);
2106  }
2107 
2108  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2116  public function disable_delivery_method($id)
2117  {
2118  // phpcs:enable
2119  $sql = 'UPDATE '.MAIN_DB_PREFIX.'c_shipment_mode SET active=0';
2120  $sql .= ' WHERE rowid='.$id;
2121 
2122  $resql = $this->db->query($sql);
2123  }
2124 
2125 
2132  public function getUrlTrackingStatus($value = '')
2133  {
2134  if (!empty($this->shipping_method_id))
2135  {
2136  $sql = "SELECT em.code, em.tracking";
2137  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2138  $sql .= " WHERE em.rowid = ".$this->shipping_method_id;
2139 
2140  $resql = $this->db->query($sql);
2141  if ($resql)
2142  {
2143  if ($obj = $this->db->fetch_object($resql))
2144  {
2145  $tracking = $obj->tracking;
2146  }
2147  }
2148  }
2149 
2150  if (!empty($tracking) && !empty($value))
2151  {
2152  $url = str_replace('{TRACKID}', $value, $tracking);
2153  $this->tracking_url = sprintf('<a target="_blank" href="%s">'.($value ? $value : 'url').'</a>', $url, $url);
2154  } else {
2155  $this->tracking_url = $value;
2156  }
2157  }
2158 
2164  public function setClosed()
2165  {
2166  global $conf, $langs, $user;
2167 
2168  $error = 0;
2169 
2170  // Protection. This avoid to move stock later when we should not
2171  if ($this->statut == self::STATUS_CLOSED)
2172  {
2173  return 0;
2174  }
2175 
2176  $this->db->begin();
2177 
2178  $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut='.self::STATUS_CLOSED;
2179  $sql .= ' WHERE rowid = '.$this->id.' AND fk_statut > 0';
2180 
2181  $resql = $this->db->query($sql);
2182  if ($resql)
2183  {
2184  // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2185  if ($this->origin == 'commande' && $this->origin_id > 0)
2186  {
2187  $order = new Commande($this->db);
2188  $order->fetch($this->origin_id);
2189 
2190  $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2191 
2192  $shipments_match_order = 1;
2193  foreach ($order->lines as $line)
2194  {
2195  $lineid = $line->id;
2196  $qty = $line->qty;
2197  if (($line->product_type == 0 || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) && $order->expeditions[$lineid] != $qty)
2198  {
2199  $shipments_match_order = 0;
2200  $text = 'Qty for order line id '.$lineid.' is '.$qty.'. However in the shipments with status Expedition::STATUS_CLOSED='.self::STATUS_CLOSED.' we have qty = '.$order->expeditions[$lineid].', so we can t close order';
2201  dol_syslog($text);
2202  break;
2203  }
2204  }
2205  if ($shipments_match_order)
2206  {
2207  dol_syslog("Qty for the ".count($order->lines)." lines of order have same value for shipments with status Expedition::STATUS_CLOSED=".self::STATUS_CLOSED.', so we close order');
2208  $order->cloture($user);
2209  }
2210  }
2211 
2212  $this->statut = self::STATUS_CLOSED;
2213 
2214 
2215  // If stock increment is done on closing
2216  if (!$error && !empty($conf->stock->enabled) && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE))
2217  {
2218  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2219 
2220  $langs->load("agenda");
2221 
2222  // Loop on each product line to add a stock movement
2223  // TODO possibilite d'expedier a partir d'une propale ou autre origine ?
2224  $sql = "SELECT cd.fk_product, cd.subprice,";
2225  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2226  $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2227  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2228  $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2229  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2230  $sql .= " WHERE ed.fk_expedition = ".$this->id;
2231  $sql .= " AND cd.rowid = ed.fk_origin_line";
2232 
2233  dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2234  $resql = $this->db->query($sql);
2235  if ($resql)
2236  {
2237  $cpt = $this->db->num_rows($resql);
2238  for ($i = 0; $i < $cpt; $i++)
2239  {
2240  $obj = $this->db->fetch_object($resql);
2241  if (empty($obj->edbrowid))
2242  {
2243  $qty = $obj->qty;
2244  } else {
2245  $qty = $obj->edbqty;
2246  }
2247  if ($qty <= 0) continue;
2248  dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2249 
2250  $mouvS = new MouvementStock($this->db);
2251  $mouvS->origin = &$this;
2252 
2253  if (empty($obj->edbrowid))
2254  {
2255  // line without batch detail
2256 
2257  // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record
2258  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentClassifyClosedInDolibarr", $numref));
2259  if ($result < 0) {
2260  $this->error = $mouvS->error;
2261  $this->errors = $mouvS->errors;
2262  $error++; break;
2263  }
2264  } else {
2265  // line with batch detail
2266 
2267  // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record
2268  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentClassifyClosedInDolibarr", $numref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
2269  if ($result < 0) {
2270  $this->error = $mouvS->error;
2271  $this->errors = $mouvS->errors;
2272  $error++; break;
2273  }
2274  }
2275  }
2276  } else {
2277  $this->error = $this->db->lasterror();
2278  $error++;
2279  }
2280  }
2281 
2282  // Call trigger
2283  if (!$error)
2284  {
2285  $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2286  if ($result < 0) {
2287  $error++;
2288  }
2289  }
2290  } else {
2291  dol_print_error($this->db);
2292  $error++;
2293  }
2294 
2295  if (!$error)
2296  {
2297  $this->db->commit();
2298  return 1;
2299  } else {
2300  $this->statut = self::STATUS_VALIDATED;
2301  $this->db->rollback();
2302  return -1;
2303  }
2304  }
2305 
2306  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2312  public function set_billed()
2313  {
2314  // phpcs:enable
2315  global $user;
2316  $error = 0;
2317 
2318  $this->db->begin();
2319 
2320  $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut=2, billed=1'; // TODO Update only billed
2321  $sql .= ' WHERE rowid = '.$this->id.' AND fk_statut > 0';
2322 
2323  $resql = $this->db->query($sql);
2324  if ($resql)
2325  {
2326  $this->statut = self::STATUS_CLOSED;
2327  $this->billed = 1;
2328 
2329  // Call trigger
2330  $result = $this->call_trigger('SHIPPING_BILLED', $user);
2331  if ($result < 0) {
2332  $error++;
2333  }
2334  } else {
2335  $error++;
2336  $this->errors[] = $this->db->lasterror;
2337  }
2338 
2339  if (empty($error)) {
2340  $this->db->commit();
2341  return 1;
2342  } else {
2343  $this->statut = self::STATUS_VALIDATED;
2344  $this->billed = 0;
2345  $this->db->rollback();
2346  return -1;
2347  }
2348  }
2349 
2355  public function reOpen()
2356  {
2357  global $conf, $langs, $user;
2358 
2359  $error = 0;
2360 
2361  // Protection. This avoid to move stock later when we should not
2362  if ($this->statut == self::STATUS_VALIDATED)
2363  {
2364  return 0;
2365  }
2366 
2367  $this->db->begin();
2368 
2369  $oldbilled = $this->billed;
2370 
2371  $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut=1';
2372  $sql .= ' WHERE rowid = '.$this->id.' AND fk_statut > 0';
2373 
2374  $resql = $this->db->query($sql);
2375  if ($resql)
2376  {
2377  $this->statut = self::STATUS_VALIDATED;
2378  $this->billed = 0;
2379 
2380  // If stock increment is done on closing
2381  if (!$error && !empty($conf->stock->enabled) && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE))
2382  {
2383  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2384 
2385  $langs->load("agenda");
2386 
2387  // Loop on each product line to add a stock movement
2388  // TODO possibilite d'expedier a partir d'une propale ou autre origine
2389  $sql = "SELECT cd.fk_product, cd.subprice,";
2390  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2391  $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2392  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2393  $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2394  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2395  $sql .= " WHERE ed.fk_expedition = ".$this->id;
2396  $sql .= " AND cd.rowid = ed.fk_origin_line";
2397 
2398  dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2399  $resql = $this->db->query($sql);
2400  if ($resql)
2401  {
2402  $cpt = $this->db->num_rows($resql);
2403  for ($i = 0; $i < $cpt; $i++)
2404  {
2405  $obj = $this->db->fetch_object($resql);
2406  if (empty($obj->edbrowid))
2407  {
2408  $qty = $obj->qty;
2409  } else {
2410  $qty = $obj->edbqty;
2411  }
2412  if ($qty <= 0) continue;
2413  dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2414 
2415  //var_dump($this->lines[$i]);
2416  $mouvS = new MouvementStock($this->db);
2417  $mouvS->origin = &$this;
2418 
2419  if (empty($obj->edbrowid))
2420  {
2421  // line without batch detail
2422 
2423  // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record
2424  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $numref));
2425  if ($result < 0) {
2426  $this->error = $mouvS->error;
2427  $this->errors = $mouvS->errors;
2428  $error++; break;
2429  }
2430  } else {
2431  // line with batch detail
2432 
2433  // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record
2434  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $numref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
2435  if ($result < 0) {
2436  $this->error = $mouvS->error;
2437  $this->errors = $mouvS->errors;
2438  $error++; break;
2439  }
2440  }
2441  }
2442  } else {
2443  $this->error = $this->db->lasterror();
2444  $error++;
2445  }
2446  }
2447 
2448  if (!$error) {
2449  // Call trigger
2450  $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2451  if ($result < 0) {
2452  $error++;
2453  }
2454  }
2455  } else {
2456  $error++;
2457  $this->errors[] = $this->db->lasterror();
2458  }
2459 
2460  if (!$error)
2461  {
2462  $this->db->commit();
2463  return 1;
2464  } else {
2465  $this->statut = self::STATUS_CLOSED;
2466  $this->billed = $oldbilled;
2467  $this->db->rollback();
2468  return -1;
2469  }
2470  }
2471 
2483  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2484  {
2485  global $conf;
2486 
2487  $outputlangs->load("products");
2488 
2489  if (!dol_strlen($modele)) {
2490  $modele = 'rouget';
2491 
2492  if (!empty($this->model_pdf)) {
2493  $modele = $this->model_pdf;
2494  } elseif (!empty($this->modelpdf)) { // deprecated
2495  $modele = $this->modelpdf;
2496  } elseif (!empty($conf->global->EXPEDITION_ADDON_PDF)) {
2497  $modele = $conf->global->EXPEDITION_ADDON_PDF;
2498  }
2499  }
2500 
2501  $modelpath = "core/modules/expedition/doc/";
2502 
2503  $this->fetch_origin();
2504 
2505  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2506  }
2507 
2516  public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
2517  {
2518  $tables = array(
2519  'expedition'
2520  );
2521 
2522  return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
2523  }
2524 }
2525 
2526 
2531 {
2535  public $element = 'expeditiondet';
2536 
2540  public $table_element = 'expeditiondet';
2541 
2547 
2551  public $fk_origin_line;
2552 
2556  public $fk_expedition;
2557 
2561  public $db;
2562 
2566  public $qty;
2567 
2571  public $qty_shipped;
2572 
2576  public $fk_product;
2577  public $detail_batch;
2578 
2582  public $entrepot_id;
2583 
2584 
2588  public $qty_asked;
2589 
2594  public $ref;
2595 
2599  public $product_ref;
2600 
2605  public $libelle;
2606 
2610  public $product_label;
2611 
2617  public $desc;
2618 
2622  public $product_desc;
2623 
2627  public $rang;
2628 
2632  public $weight;
2633  public $weight_units;
2634 
2638  public $length;
2639  public $length_units;
2640 
2644  public $surface;
2645  public $surface_units;
2646 
2650  public $volume;
2651  public $volume_units;
2652 
2653  // Invoicing
2654  public $remise_percent;
2655  public $tva_tx;
2656 
2660  public $total_ht;
2661 
2665  public $total_ttc;
2666 
2670  public $total_tva;
2671 
2675  public $total_localtax1;
2676 
2680  public $total_localtax2;
2681 
2682 
2688  public function __construct($db)
2689  {
2690  $this->db = $db;
2691  }
2692 
2699  public function fetch($rowid)
2700  {
2701  $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_origin_line, ed.qty, ed.rang';
2702  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
2703  $sql .= ' WHERE ed.rowid = '.$rowid;
2704  $result = $this->db->query($sql);
2705  if ($result)
2706  {
2707  $objp = $this->db->fetch_object($result);
2708  $this->id = $objp->rowid;
2709  $this->fk_expedition = $objp->fk_expedition;
2710  $this->entrepot_id = $objp->fk_entrepot;
2711  $this->fk_origin_line = $objp->fk_origin_line;
2712  $this->qty = $objp->qty;
2713  $this->rang = $objp->rang;
2714 
2715  $this->db->free($result);
2716 
2717  return 1;
2718  } else {
2719  $this->errors[] = $this->db->lasterror();
2720  $this->error = $this->db->lasterror();
2721  return -1;
2722  }
2723  }
2724 
2732  public function insert($user, $notrigger = 0)
2733  {
2734  global $langs, $conf;
2735 
2736  $error = 0;
2737 
2738  // Check parameters
2739  if (empty($this->fk_expedition) || empty($this->fk_origin_line) || !is_numeric($this->qty))
2740  {
2741  $this->error = 'ErrorMandatoryParametersNotProvided';
2742  return -1;
2743  }
2744 
2745  $this->db->begin();
2746 
2747  if (empty($this->rang)) $this->rang = 0;
2748 
2749  // Rank to use
2750  $ranktouse = $this->rang;
2751  if ($ranktouse == -1)
2752  {
2753  $rangmax = $this->line_max($this->fk_expedition);
2754  $ranktouse = $rangmax + 1;
2755  }
2756 
2757  $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
2758  $sql .= "fk_expedition";
2759  $sql .= ", fk_entrepot";
2760  $sql .= ", fk_origin_line";
2761  $sql .= ", qty";
2762  $sql .= ", rang";
2763  $sql .= ") VALUES (";
2764  $sql .= $this->fk_expedition;
2765  $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
2766  $sql .= ", ".$this->fk_origin_line;
2767  $sql .= ", ".$this->qty;
2768  $sql .= ", ".$ranktouse;
2769  $sql .= ")";
2770 
2771  dol_syslog(get_class($this)."::insert", LOG_DEBUG);
2772  $resql = $this->db->query($sql);
2773  if ($resql)
2774  {
2775  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
2776 
2777  if (!$error)
2778  {
2779  $result = $this->insertExtraFields();
2780  if ($result < 0)
2781  {
2782  $error++;
2783  }
2784  }
2785 
2786  if (!$error && !$notrigger)
2787  {
2788  // Call trigger
2789  $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
2790  if ($result < 0)
2791  {
2792  $error++;
2793  }
2794  // End call triggers
2795  }
2796 
2797  if (!$error) {
2798  $this->db->commit();
2799  return $this->id;
2800  }
2801 
2802  foreach ($this->errors as $errmsg)
2803  {
2804  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2805  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2806  }
2807 
2808  $this->db->rollback();
2809  return -1 * $error;
2810  } else {
2811  $error++;
2812  }
2813  }
2814 
2822  public function delete($user = null, $notrigger = 0)
2823  {
2824  global $conf;
2825 
2826  $error = 0;
2827 
2828  $this->db->begin();
2829 
2830  // delete batch expedition line
2831  if ($conf->productbatch->enabled)
2832  {
2833  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
2834  $sql .= " WHERE fk_expeditiondet = ".$this->id;
2835 
2836  if (!$this->db->query($sql))
2837  {
2838  $this->errors[] = $this->db->lasterror()." - sql=$sql";
2839  $error++;
2840  }
2841  }
2842 
2843  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
2844  $sql .= " WHERE rowid = ".$this->id;
2845 
2846  if (!$error && $this->db->query($sql))
2847  {
2848  // Remove extrafields
2849  if (!$error)
2850  {
2851  $result = $this->deleteExtraFields();
2852  if ($result < 0)
2853  {
2854  $this->errors[] = $this->error;
2855  $error++;
2856  }
2857  }
2858  if (!$error && !$notrigger)
2859  {
2860  // Call trigger
2861  $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
2862  if ($result < 0)
2863  {
2864  $this->errors[] = $this->error;
2865  $error++;
2866  }
2867  // End call triggers
2868  }
2869  } else {
2870  $this->errors[] = $this->db->lasterror()." - sql=$sql";
2871  $error++;
2872  }
2873 
2874  if (!$error) {
2875  $this->db->commit();
2876  return 1;
2877  } else {
2878  foreach ($this->errors as $errmsg)
2879  {
2880  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2881  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2882  }
2883  $this->db->rollback();
2884  return -1 * $error;
2885  }
2886  }
2887 
2895  public function update($user = null, $notrigger = 0)
2896  {
2897  global $conf;
2898 
2899  $error = 0;
2900 
2901  dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
2902 
2903  $this->db->begin();
2904 
2905  // Clean parameters
2906  if (empty($this->qty)) $this->qty = 0;
2907  $qty = price2num($this->qty);
2908  $remainingQty = 0;
2909  $batch = null;
2910  $batch_id = null;
2911  $expedition_batch_id = null;
2912  if (is_array($this->detail_batch)) // array of ExpeditionLineBatch
2913  {
2914  if (count($this->detail_batch) > 1)
2915  {
2916  dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
2917  $this->errors[] = 'ErrorBadParameters';
2918  $error++;
2919  } else {
2920  $batch = $this->detail_batch[0]->batch;
2921  $batch_id = $this->detail_batch[0]->fk_origin_stock;
2922  $expedition_batch_id = $this->detail_batch[0]->id;
2923  if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id)
2924  {
2925  dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
2926  $this->errors[] = 'ErrorBadParameters';
2927  $error++;
2928  }
2929  $qty = price2num($this->detail_batch[0]->qty);
2930  }
2931  } elseif (!empty($this->detail_batch))
2932  {
2933  $batch = $this->detail_batch->batch;
2934  $batch_id = $this->detail_batch->fk_origin_stock;
2935  $expedition_batch_id = $this->detail_batch->id;
2936  if ($this->entrepot_id != $this->detail_batch->entrepot_id)
2937  {
2938  dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
2939  $this->errors[] = 'ErrorBadParameters';
2940  $error++;
2941  }
2942  $qty = price2num($this->detail_batch->qty);
2943  }
2944 
2945  // check parameters
2946  if (!isset($this->id) || !isset($this->entrepot_id))
2947  {
2948  dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
2949  $this->errors[] = 'ErrorMandatoryParametersNotProvided';
2950  $error++;
2951  return -1;
2952  }
2953 
2954  // update lot
2955 
2956  if (!empty($batch) && $conf->productbatch->enabled)
2957  {
2958  dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id, batch=$batch");
2959 
2960  if (empty($batch_id) || empty($this->fk_product)) {
2961  dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
2962  $this->errors[] = 'ErrorMandatoryParametersNotProvided';
2963  $error++;
2964  }
2965 
2966  // fetch remaining lot qty
2967  require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionbatch.class.php';
2968  if (!$error && ($lotArray = ExpeditionLineBatch::fetchAll($this->db, $this->id)) < 0)
2969  {
2970  $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
2971  $error++;
2972  } else {
2973  // caculate new total line qty
2974  foreach ($lotArray as $lot)
2975  {
2976  if ($expedition_batch_id != $lot->id)
2977  {
2978  $remainingQty += $lot->qty;
2979  }
2980  }
2981  $qty += $remainingQty;
2982 
2983  //fetch lot details
2984 
2985  // fetch from product_lot
2986  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
2987  $lot = new Productlot($this->db);
2988  if ($lot->fetch(0, $this->fk_product, $batch) < 0)
2989  {
2990  $this->errors[] = $lot->errors;
2991  $error++;
2992  }
2993  if (!$error && !empty($expedition_batch_id))
2994  {
2995  // delete lot expedition line
2996  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
2997  $sql .= " WHERE fk_expeditiondet = ".$this->id;
2998  $sql .= " AND rowid = ".$expedition_batch_id;
2999 
3000  if (!$this->db->query($sql))
3001  {
3002  $this->errors[] = $this->db->lasterror()." - sql=$sql";
3003  $error++;
3004  }
3005  }
3006  if (!$error && $this->detail_batch->qty > 0)
3007  {
3008  // create lot expedition line
3009  if (isset($lot->id))
3010  {
3011  $shipmentLot = new ExpeditionLineBatch($this->db);
3012  $shipmentLot->batch = $lot->batch;
3013  $shipmentLot->eatby = $lot->eatby;
3014  $shipmentLot->sellby = $lot->sellby;
3015  $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id;
3016  $shipmentLot->qty = $this->detail_batch->qty;
3017  $shipmentLot->fk_origin_stock = $batch_id;
3018  if ($shipmentLot->create($this->id) < 0)
3019  {
3020  $this->errors[] = $shipmentLot->errors;
3021  $error++;
3022  }
3023  }
3024  }
3025  }
3026  }
3027  if (!$error)
3028  {
3029  // update line
3030  $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3031  $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
3032  $sql .= " , qty = ".$qty;
3033  $sql .= " WHERE rowid = ".$this->id;
3034 
3035  if (!$this->db->query($sql))
3036  {
3037  $this->errors[] = $this->db->lasterror()." - sql=$sql";
3038  $error++;
3039  }
3040  }
3041 
3042  if (!$error)
3043  {
3044  if (!$error)
3045  {
3046  $result = $this->insertExtraFields();
3047  if ($result < 0)
3048  {
3049  $this->errors[] = $this->error;
3050  $error++;
3051  }
3052  }
3053  }
3054 
3055  if (!$error && !$notrigger)
3056  {
3057  // Call trigger
3058  $result = $this->call_trigger('LINESHIPPING_UPDATE', $user);
3059  if ($result < 0)
3060  {
3061  $this->errors[] = $this->error;
3062  $error++;
3063  }
3064  // End call triggers
3065  }
3066  if (!$error) {
3067  $this->db->commit();
3068  return 1;
3069  } else {
3070  foreach ($this->errors as $errmsg)
3071  {
3072  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3073  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3074  }
3075  $this->db->rollback();
3076  return -1 * $error;
3077  }
3078  }
3079 }
update($user=null, $notrigger=0)
Update database.
Class to manage receptions.
list_delivery_methods($id= '')
Fetch all deliveries method and return an array.
Class to manage stock movements.
addline_batch($dbatch, $array_options=0)
Add a shipment line with batch record.
setDeliveryDate($user, $delivery_date)
Set the planned delivery date.
fetch_delivery_methods()
Fetch deliveries method and return an array.
__construct($db)
Constructor.
reOpen()
Classify the shipping as validated/opened.
Class with list of lots and properties.
Classe to manage lines of shipment.
disable_delivery_method($id)
DesActivate delivery method.
if(!empty($arrayfields['u.datec']['checked'])) print_liste_field_titre("DateCreationShort"u if(!empty($arrayfields['u.tms']['checked'])) print_liste_field_titre("DateModificationShort"u if(!empty($arrayfields['u.statut']['checked'])) print_liste_field_titre("Status"u statut
Definition: list.php:632
CRUD class for batch number management within shipment.
const STATUS_DRAFT
Draft status.
static replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
Class to manage products or services.
dol_now($mode= 'auto')
Return date for now.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller= '', $localtaxes_array= '', $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code= '')
Calculate totals (net, vat, ...) of a line.
Definition: price.lib.php:86
Class to manage Dolibarr database access.
create_line_batch($line_ext, $array_options=0)
Create the detail (eat-by date) of the expedition line.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetch_origin()
Read linked origin object.
addline($entrepot_id, $id, $qty, $array_options=0)
Add an expedition line.
static fetchAll($db, $id_line_expdet, $fk_product=0)
Retrieve all batch number detailed information of a shipment line.
const STATUS_SHIPMENTONPROCESS
Shipment on process.
deleteline($user, $lineid)
Delete detail line.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this-&gt;socid or $this-&gt;fk_soc, into this-&gt;thirdparty.
set_billed()
Classify the shipping as invoiced (used when WORKFLOW_BILL_ON_SHIPMENT is on)
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller Note: This function applies same rules than get_default_tva.
update($user=null, $notrigger=0)
Update a line in database.
const STATUS_CLOSED
Closed status.
$conf db
API class for accounts.
Definition: inc.php:54
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage order lines.
fetch_lines()
Load lines.
insertExtraFields($trigger= '', $userused=null)
Add/Update all extra fields values for the current object.
fetch($rowid)
Load line expedition.
Class to manage third parties objects (customers, suppliers, prospects...)
valid($user, $notrigger=0)
Validate object and update stock if option enabled.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
fetch($id, $ref= '', $ref_ext= '', $notused= '')
Get object and lines from database.
dol_strlen($string, $stringencoding= 'UTF-8')
Make a strlen call.
price2num($amount, $rounding= '', $option=0)
Function that return a number with universal decimal format (decimal separator is &#39;...
$conf db user
Definition: repair.php:109
initAsSpecimen()
Initialise an instance with random values.
deleteEcmFiles($mode=0)
Delete related files of object in database.
Class to manage shipments.
static commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
Class to manage customers orders.
create($user, $notrigger=0)
Create expedition en base.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename= '', $restricttologhandler= '', $logcontext=null)
Write log message into outputs.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories) ...
Definition: files.lib.php:1286
fetchObjectLinked($sourceid=null, $sourcetype= '', $targetid=null, $targettype= '', $clause= 'OR', $alsosametype=1, $orderby= 'sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only). ...
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1)
Remove a file or several files with a mask.
Definition: files.lib.php:1144
img_object($titlealt, $picto, $moreatt= '', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
__construct($db)
Constructor.
deleteExtraFields()
Delete all extra fields values for the current object.
dol_sanitizeFileName($str, $newstr= '_', $unaccent=1)
Clean a string to use it as a file name.
dol_dir_list($path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0)
Scan a directory and return a list of files/directories.
Definition: files.lib.php:60
getUrlTrackingStatus($value= '')
Forge an set tracking url.
create_delivery($user)
Create a delivery receipt from a shipment.
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this-&gt;array_options This method is in most cases call...
deleteObjectLinked($sourceid=null, $sourcetype= '', $targetid=null, $targettype= '', $rowid= '')
Delete all links between an object $this.
const STATUS_VALIDATED
Validated status.
print $_SERVER["PHP_SELF"]
Edit parameters.
LibStatut($status, $mode)
Return label of a status.
update_delivery_method($id= '')
Update/create delivery method.
const STATUS_VALIDATED
Validated status.
Manage record for batch number management.
trait CommonIncoterm
Superclass for incoterm classes.
cancel($notrigger=0, $also_update_stock=false)
Cancel shipment.
print
Draft customers invoices.
Definition: index.php:89
getNomUrl($withpicto=0, $option= '', $max=0, $short=0, $notooltip=0, $save_lastsearch_value=-1)
Return clicable link of object (with eventually picto)
call_trigger($triggerName, $user)
Call trigger based on this instance.
set_date_livraison($user, $delivery_date)
Set delivery date.
getNextNumRef($soc)
Return next contract ref.
setStatut($status, $elementId=null, $elementType= '', $trigkey= '')
Set status of an object.
const STATUS_CANCELED
Canceled status.
if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) if(!empty($conf->don->enabled)&&$user->rights->don->lire) if(!empty($conf->tax->enabled)&&$user->rights->tax->charges->lire) if(!empty($conf->facture->enabled)&&!empty($conf->commande->enabled)&&$user->rights->commande->lire &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) $resql
Social contributions to pay.
Definition: index.php:1232
dol_print_error($db= '', $error= '', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getLibStatut($mode=0)
Return status label.
dolGetStatus($statusLabel= '', $statusLabelShort= '', $html= '', $statusType= 'status0', $displayMode=0, $url= '', $params=array())
Output the badge of a status.
static deletefromexp($db, $id_expedition)
Delete batch record attach to a shipment.
activ_delivery_method($id)
Activate delivery method.
add_object_linked($origin=null, $origin_id=null)
Add objects linked in llx_element_element.
Parent class of all other business classes (invoices, contracts, proposals, orders, ...)
insert($user, $notrigger=0)
Insert line into database.
setClosed()
Classify the shipping as closed.
create_line($entrepot_id, $origin_line_id, $qty, $rang=0, $array_options=0)
Create a expedition line.