dolibarr  18.0.0
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-2022 Ferran Marcet <fmarcet@2byte.es>
13  * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
14  * Copyright (C) 2018-2022 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 (isModEnabled("propal")) {
41  require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
42 }
43 if (isModEnabled('commande')) {
44  require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
45 }
46 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionlinebatch.class.php';
47 
48 
52 class Expedition extends CommonObject
53 {
54  use CommonIncoterm;
55 
59  public $element = "shipping";
60 
64  public $fk_element = "fk_expedition";
65 
69  public $table_element = "expedition";
70 
74  public $table_element_line = "expeditiondet";
75 
80  public $ismultientitymanaged = 1;
81 
85  public $picto = 'dolly';
86 
87 
91  public $fields = array();
92 
96  public $user_author_id;
97 
101  public $fk_user_author;
102 
103  public $socid;
104 
110  public $ref_client;
111 
115  public $ref_customer;
116 
117  public $brouillon;
118 
122  public $entrepot_id;
123 
127  public $tracking_number;
128 
132  public $tracking_url;
133  public $billed;
134 
138  public $model_pdf;
139 
140  public $trueWeight;
141  public $weight_units;
142  public $trueWidth;
143  public $width_units;
144  public $trueHeight;
145  public $height_units;
146  public $trueDepth;
147  public $depth_units;
148  // A denormalized value
149  public $trueSize;
150 
154  public $date_delivery;
155 
160  public $date;
161 
167 
172  public $date_shipping;
173 
177  public $date_creation;
178 
182  public $date_valid;
183 
184  public $meths;
185  public $listmeths; // List of carriers
186 
190  public $commande_id;
191 
195  public $commande;
196 
200  public $lines = array();
201 
202  // Multicurrency
206  public $fk_multicurrency;
207 
211  public $multicurrency_code;
212  public $multicurrency_tx;
213  public $multicurrency_total_ht;
214  public $multicurrency_total_tva;
215  public $multicurrency_total_ttc;
216 
220  const STATUS_DRAFT = 0;
221 
225  const STATUS_VALIDATED = 1;
226 
230  const STATUS_CLOSED = 2;
231 
235  const STATUS_CANCELED = -1;
236 
237 
243  public function __construct($db)
244  {
245  global $conf;
246 
247  $this->db = $db;
248 
249  // List of long language codes for status
250  $this->statuts = array();
251  $this->statuts[-1] = 'StatusSendingCanceled';
252  $this->statuts[0] = 'StatusSendingDraft';
253  $this->statuts[1] = 'StatusSendingValidated';
254  $this->statuts[2] = 'StatusSendingProcessed';
255 
256  // List of short language codes for status
257  $this->statuts_short = array();
258  $this->statuts_short[-1] = 'StatusSendingCanceledShort';
259  $this->statuts_short[0] = 'StatusSendingDraftShort';
260  $this->statuts_short[1] = 'StatusSendingValidatedShort';
261  $this->statuts_short[2] = 'StatusSendingProcessedShort';
262  }
263 
270  public function getNextNumRef($soc)
271  {
272  global $langs, $conf;
273  $langs->load("sendings");
274 
275  if (!empty($conf->global->EXPEDITION_ADDON_NUMBER)) {
276  $mybool = false;
277 
278  $file = $conf->global->EXPEDITION_ADDON_NUMBER.".php";
279  $classname = $conf->global->EXPEDITION_ADDON_NUMBER;
280 
281  // Include file with class
282  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
283 
284  foreach ($dirmodels as $reldir) {
285  $dir = dol_buildpath($reldir."core/modules/expedition/");
286 
287  // Load file with numbering class (if found)
288  $mybool |= @include_once $dir.$file;
289  }
290 
291  if (!$mybool) {
292  dol_print_error('', "Failed to include file ".$file);
293  return '';
294  }
295 
296  $obj = new $classname();
297  $numref = "";
298  $numref = $obj->getNextValue($soc, $this);
299 
300  if ($numref != "") {
301  return $numref;
302  } else {
303  dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
304  return "";
305  }
306  } else {
307  print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
308  return "";
309  }
310  }
311 
319  public function create($user, $notrigger = 0)
320  {
321  global $conf, $hookmanager;
322 
323  $now = dol_now();
324 
325  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
326  $error = 0;
327 
328  // Clean parameters
329  $this->brouillon = 1;
330  $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
331  if (empty($this->fk_project)) {
332  $this->fk_project = 0;
333  }
334 
335  $this->user = $user;
336 
337 
338  $this->db->begin();
339 
340  $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
341  $sql .= "ref";
342  $sql .= ", entity";
343  $sql .= ", ref_customer";
344  $sql .= ", ref_ext";
345  $sql .= ", date_creation";
346  $sql .= ", fk_user_author";
347  $sql .= ", date_expedition";
348  $sql .= ", date_delivery";
349  $sql .= ", fk_soc";
350  $sql .= ", fk_projet";
351  $sql .= ", fk_address";
352  $sql .= ", fk_shipping_method";
353  $sql .= ", tracking_number";
354  $sql .= ", weight";
355  $sql .= ", size";
356  $sql .= ", width";
357  $sql .= ", height";
358  $sql .= ", weight_units";
359  $sql .= ", size_units";
360  $sql .= ", note_private";
361  $sql .= ", note_public";
362  $sql .= ", model_pdf";
363  $sql .= ", fk_incoterms, location_incoterms";
364  $sql .= ") VALUES (";
365  $sql .= "'(PROV)'";
366  $sql .= ", ".((int) $conf->entity);
367  $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
368  $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
369  $sql .= ", '".$this->db->idate($now)."'";
370  $sql .= ", ".((int) $user->id);
371  $sql .= ", ".($this->date_expedition > 0 ? "'".$this->db->idate($this->date_expedition)."'" : "null");
372  $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
373  $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
374  $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
375  $sql .= ", ".($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
376  $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
377  $sql .= ", '".$this->db->escape($this->tracking_number)."'";
378  $sql .= ", ".(is_numeric($this->weight) ? $this->weight : 'NULL');
379  $sql .= ", ".(is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
380  $sql .= ", ".(is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
381  $sql .= ", ".(is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
382  $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
383  $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
384  $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
385  $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
386  $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
387  $sql .= ", ".(int) $this->fk_incoterms;
388  $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
389  $sql .= ")";
390 
391  dol_syslog(get_class($this)."::create", LOG_DEBUG);
392  $resql = $this->db->query($sql);
393  if ($resql) {
394  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
395 
396  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
397  $sql .= " SET ref = '(PROV".$this->id.")'";
398  $sql .= " WHERE rowid = ".((int) $this->id);
399 
400  dol_syslog(get_class($this)."::create", LOG_DEBUG);
401  if ($this->db->query($sql)) {
402  // Insert of lines
403  $num = count($this->lines);
404  for ($i = 0; $i < $num; $i++) {
405  if (empty($this->lines[$i]->product_type) || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
406  if (!isset($this->lines[$i]->detail_batch)) { // no batch management
407  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) {
408  $error++;
409  }
410  } else { // with batch management
411  if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
412  $error++;
413  }
414  }
415  }
416  }
417 
418  if (!$error && $this->id && $this->origin_id) {
419  $ret = $this->add_object_linked();
420  if (!$ret) {
421  $error++;
422  }
423  }
424 
425  // Actions on extra fields
426  if (!$error) {
427  $result = $this->insertExtraFields();
428  if ($result < 0) {
429  $error++;
430  }
431  }
432 
433  if (!$error && !$notrigger) {
434  // Call trigger
435  $result = $this->call_trigger('SHIPPING_CREATE', $user);
436  if ($result < 0) {
437  $error++;
438  }
439  // End call triggers
440 
441  if (!$error) {
442  $this->db->commit();
443  return $this->id;
444  } else {
445  foreach ($this->errors as $errmsg) {
446  dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
447  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
448  }
449  $this->db->rollback();
450  return -1 * $error;
451  }
452  } else {
453  $error++;
454  $this->db->rollback();
455  return -3;
456  }
457  } else {
458  $error++;
459  $this->error = $this->db->lasterror()." - sql=$sql";
460  $this->db->rollback();
461  return -2;
462  }
463  } else {
464  $error++;
465  $this->error = $this->db->error()." - sql=$sql";
466  $this->db->rollback();
467  return -1;
468  }
469  }
470 
471  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
482  public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = null)
483  {
484  //phpcs:enable
485  global $user;
486 
487  $expeditionline = new ExpeditionLigne($this->db);
488  $expeditionline->fk_expedition = $this->id;
489  $expeditionline->entrepot_id = $entrepot_id;
490  $expeditionline->fk_origin_line = $origin_line_id;
491  $expeditionline->qty = $qty;
492  $expeditionline->rang = $rang;
493  $expeditionline->array_options = $array_options;
494 
495  if (($lineId = $expeditionline->insert($user)) < 0) {
496  $this->errors[] = $expeditionline->error;
497  }
498  return $lineId;
499  }
500 
501 
502  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
510  public function create_line_batch($line_ext, $array_options = 0)
511  {
512  // phpcs:enable
513  $error = 0;
514  $stockLocationQty = array(); // associated array with batch qty in stock location
515 
516  $tab = $line_ext->detail_batch;
517  // create stockLocation Qty array
518  foreach ($tab as $detbatch) {
519  if (!empty($detbatch->entrepot_id)) {
520  if (empty($stockLocationQty[$detbatch->entrepot_id])) {
521  $stockLocationQty[$detbatch->entrepot_id] = 0;
522  }
523  $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
524  }
525  }
526  // create shipment lines
527  foreach ($stockLocationQty as $stockLocation => $qty) {
528  $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
529  if ($line_id < 0) {
530  $error++;
531  } else {
532  // create shipment batch lines for stockLocation
533  foreach ($tab as $detbatch) {
534  if ($detbatch->entrepot_id == $stockLocation) {
535  if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch
536  $this->errors = $detbatch->errors;
537  $error++;
538  }
539  }
540  }
541  }
542  }
543 
544  if (!$error) {
545  return 1;
546  } else {
547  return -1;
548  }
549  }
550 
560  public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
561  {
562  global $conf;
563 
564  // Check parameters
565  if (empty($id) && empty($ref) && empty($ref_ext)) {
566  return -1;
567  }
568 
569  $sql = "SELECT e.rowid, e.entity, e.ref, e.fk_soc as socid, e.date_creation, e.ref_customer, e.ref_ext, e.fk_user_author, e.fk_statut, e.fk_projet as fk_project, e.billed";
570  $sql .= ", e.date_valid";
571  $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
572  $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
573  $sql .= ", e.fk_shipping_method, e.tracking_number";
574  $sql .= ", e.note_private, e.note_public";
575  $sql .= ', e.fk_incoterms, e.location_incoterms';
576  $sql .= ', i.libelle as label_incoterms';
577  $sql .= ', s.libelle as shipping_method';
578  $sql .= ", el.fk_source as origin_id, el.sourcetype as origin";
579  $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
580  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
581  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
582  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
583  $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
584  if ($id) {
585  $sql .= " AND e.rowid = ".((int) $id);
586  }
587  if ($ref) {
588  $sql .= " AND e.ref='".$this->db->escape($ref)."'";
589  }
590  if ($ref_ext) {
591  $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
592  }
593 
594  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
595  $result = $this->db->query($sql);
596  if ($result) {
597  if ($this->db->num_rows($result)) {
598  $obj = $this->db->fetch_object($result);
599 
600  $this->id = $obj->rowid;
601  $this->entity = $obj->entity;
602  $this->ref = $obj->ref;
603  $this->socid = $obj->socid;
604  $this->ref_customer = $obj->ref_customer;
605  $this->ref_ext = $obj->ref_ext;
606  $this->status = $obj->fk_statut;
607  $this->statut = $this->status; // Deprecated
608  $this->user_author_id = $obj->fk_user_author;
609  $this->fk_user_author = $obj->fk_user_author;
610  $this->date_creation = $this->db->jdate($obj->date_creation);
611  $this->date_valid = $this->db->jdate($obj->date_valid);
612  $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
613  $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
614  $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
615  $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planed
616  $this->fk_delivery_address = $obj->fk_address;
617  $this->model_pdf = $obj->model_pdf;
618  $this->modelpdf = $obj->model_pdf; // deprecated
619  $this->shipping_method_id = $obj->fk_shipping_method;
620  $this->shipping_method = $obj->shipping_method;
621  $this->tracking_number = $obj->tracking_number;
622  $this->origin = ($obj->origin ? $obj->origin : 'commande'); // For compatibility
623  $this->origin_id = $obj->origin_id;
624  $this->billed = $obj->billed;
625  $this->fk_project = $obj->fk_project;
626 
627  $this->trueWeight = $obj->weight;
628  $this->weight_units = $obj->weight_units;
629 
630  $this->trueWidth = $obj->width;
631  $this->width_units = $obj->size_units;
632  $this->trueHeight = $obj->height;
633  $this->height_units = $obj->size_units;
634  $this->trueDepth = $obj->size;
635  $this->depth_units = $obj->size_units;
636 
637  $this->note_public = $obj->note_public;
638  $this->note_private = $obj->note_private;
639 
640  // A denormalized value
641  $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
642  $this->size_units = $obj->size_units;
643 
644  //Incoterms
645  $this->fk_incoterms = $obj->fk_incoterms;
646  $this->location_incoterms = $obj->location_incoterms;
647  $this->label_incoterms = $obj->label_incoterms;
648 
649  $this->db->free($result);
650 
651  if ($this->statut == self::STATUS_DRAFT) {
652  $this->brouillon = 1;
653  }
654 
655  // Tracking url
656  $this->getUrlTrackingStatus($obj->tracking_number);
657 
658  // Thirdparty
659  $result = $this->fetch_thirdparty(); // TODO Remove this
660 
661  // Retrieve extrafields
662  $this->fetch_optionals();
663 
664  // Fix Get multicurrency param for transmited
665  if (isModEnabled('multicurrency')) {
666  if (!empty($this->multicurrency_code)) {
667  $this->multicurrency_code = $this->thirdparty->multicurrency_code;
668  }
669  if (!empty($conf->global->MULTICURRENCY_USE_ORIGIN_TX) && !empty($this->thirdparty->multicurrency_tx)) {
670  $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
671  }
672  }
673 
674  /*
675  * Lines
676  */
677  $result = $this->fetch_lines();
678  if ($result < 0) {
679  return -3;
680  }
681 
682  return 1;
683  } else {
684  dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
685  $this->error = 'Delivery with id '.$id.' not found';
686  return 0;
687  }
688  } else {
689  $this->error = $this->db->error();
690  return -1;
691  }
692  }
693 
701  public function valid($user, $notrigger = 0)
702  {
703  global $conf, $langs;
704 
705  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
706 
707  dol_syslog(get_class($this)."::valid");
708 
709  // Protection
710  if ($this->statut) {
711  dol_syslog(get_class($this)."::valid not in draft status", LOG_WARNING);
712  return 0;
713  }
714 
715  if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->expedition->creer))
716  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->expedition->shipping_advance->validate)))) {
717  $this->error = 'Permission denied';
718  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
719  return -1;
720  }
721 
722  $this->db->begin();
723 
724  $error = 0;
725 
726  // Define new ref
727  $soc = new Societe($this->db);
728  $soc->fetch($this->socid);
729 
730  // Class of company linked to order
731  $result = $soc->set_as_client();
732 
733  // Define new ref
734  if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
735  $numref = $this->getNextNumRef($soc);
736  } else {
737  $numref = "EXP".$this->id;
738  }
739  $this->newref = dol_sanitizeFileName($numref);
740 
741  $now = dol_now();
742 
743  // Validate
744  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
745  $sql .= " ref='".$this->db->escape($numref)."'";
746  $sql .= ", fk_statut = 1";
747  $sql .= ", date_valid = '".$this->db->idate($now)."'";
748  $sql .= ", fk_user_valid = ".$user->id;
749  $sql .= " WHERE rowid = ".((int) $this->id);
750 
751  dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
752  $resql = $this->db->query($sql);
753  if (!$resql) {
754  $this->error = $this->db->lasterror();
755  $error++;
756  }
757 
758  // If stock increment is done on sending (recommanded choice)
759  if (!$error && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)) {
760  $result = $this->manageStockMvtOnEvt($user);
761  if ($result < 0) {
762  return -2;
763  }
764  }
765 
766  // Change status of order to "shipment in process"
767  $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
768  if (!$ret) {
769  $error++;
770  }
771 
772  if (!$error && !$notrigger) {
773  // Call trigger
774  $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
775  if ($result < 0) {
776  $error++;
777  }
778  // End call triggers
779  }
780 
781  if (!$error) {
782  $this->oldref = $this->ref;
783 
784  // Rename directory if dir was a temporary ref
785  if (preg_match('/^[\(]?PROV/i', $this->ref)) {
786  // Now we rename also files into index
787  $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)."'";
788  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
789  $resql = $this->db->query($sql);
790  if (!$resql) {
791  $error++; $this->error = $this->db->lasterror();
792  }
793 
794  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
795  $oldref = dol_sanitizeFileName($this->ref);
796  $newref = dol_sanitizeFileName($numref);
797  $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
798  $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
799  if (!$error && file_exists($dirsource)) {
800  dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
801 
802  if (@rename($dirsource, $dirdest)) {
803  dol_syslog("Rename ok");
804  // Rename docs starting with $oldref with $newref
805  $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
806  foreach ($listoffiles as $fileentry) {
807  $dirsource = $fileentry['name'];
808  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
809  $dirsource = $fileentry['path'].'/'.$dirsource;
810  $dirdest = $fileentry['path'].'/'.$dirdest;
811  @rename($dirsource, $dirdest);
812  }
813  }
814  }
815  }
816  }
817 
818  // Set new ref and current status
819  if (!$error) {
820  $this->ref = $numref;
821  $this->statut = self::STATUS_VALIDATED;
822  }
823 
824  if (!$error) {
825  $this->db->commit();
826  return 1;
827  } else {
828  $this->db->rollback();
829  return -1 * $error;
830  }
831  }
832 
833 
834  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
841  public function create_delivery($user)
842  {
843  // phpcs:enable
844  global $conf;
845 
846  if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
847  if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
848  // Expedition validee
849  include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
850  $delivery = new Delivery($this->db);
851  $result = $delivery->create_from_sending($user, $this->id);
852  if ($result > 0) {
853  return $result;
854  } else {
855  $this->error = $delivery->error;
856  return $result;
857  }
858  } else {
859  return 0;
860  }
861  } else {
862  return 0;
863  }
864  }
865 
878  public function addline($entrepot_id, $id, $qty, $array_options = 0)
879  {
880  global $conf, $langs;
881 
882  $num = count($this->lines);
883  $line = new ExpeditionLigne($this->db);
884 
885  $line->entrepot_id = $entrepot_id;
886  $line->origin_line_id = $id;
887  $line->fk_origin_line = $id;
888  $line->qty = $qty;
889 
890  $orderline = new OrderLine($this->db);
891  $orderline->fetch($id);
892 
893  // Copy the rang of the order line to the expedition line
894  $line->rang = $orderline->rang;
895  $line->product_type = $orderline->product_type;
896 
897  if (isModEnabled('stock') && !empty($orderline->fk_product)) {
898  $fk_product = $orderline->fk_product;
899 
900  if (!($entrepot_id > 0) && empty($conf->global->STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS)) {
901  $langs->load("errors");
902  $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
903  return -1;
904  }
905 
906  if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT)) {
907  $product = new Product($this->db);
908  $product->fetch($fk_product);
909 
910  // Check must be done for stock of product into warehouse if $entrepot_id defined
911  if ($entrepot_id > 0) {
912  $product->load_stock('warehouseopen');
913  $product_stock = $product->stock_warehouse[$entrepot_id]->real;
914  } else {
915  $product_stock = $product->stock_reel;
916  }
917 
918  $product_type = $product->type;
919  if ($product_type == 0 || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
920  $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
921  // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
922  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.
923  if ($product_stock < $qty) {
924  $langs->load("errors");
925  $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
926  $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
927 
928  $this->db->rollback();
929  return -3;
930  }
931  }
932  }
933  }
934  }
935 
936  // If product need a batch number, we should not have called this function but addline_batch instead.
937  // If this happen, we may have a bug in card.php page
938  if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
939  $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; //
940  return -4;
941  }
942 
943  // extrafields
944  if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
945  $line->array_options = $array_options;
946  }
947 
948  $this->lines[$num] = $line;
949 
950  return 1;
951  }
952 
953  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
961  public function addline_batch($dbatch, $array_options = 0)
962  {
963  // phpcs:enable
964  global $conf, $langs;
965 
966  $num = count($this->lines);
967  if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
968  $line = new ExpeditionLigne($this->db);
969  $tab = array();
970  foreach ($dbatch['detail'] as $key => $value) {
971  if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
972  // $value['q']=qty to move
973  // $value['id_batch']=id into llx_product_batch of record to move
974  //var_dump($value);
975 
976  $linebatch = new ExpeditionLineBatch($this->db);
977  $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
978  if ($ret < 0) {
979  $this->error = $linebatch->error;
980  return -1;
981  }
982  $linebatch->qty = $value['q'];
983  if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
984  $linebatch->batch = null;
985  }
986  $tab[] = $linebatch;
987 
988  if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
989  require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
990  $prod_batch = new Productbatch($this->db);
991  $prod_batch->fetch($value['id_batch']);
992 
993  if ($prod_batch->qty < $linebatch->qty) {
994  $langs->load("errors");
995  $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
996  dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
997  $this->db->rollback();
998  return -1;
999  }
1000  }
1001 
1002  //var_dump($linebatch);
1003  }
1004  }
1005  $line->entrepot_id = $linebatch->entrepot_id;
1006  $line->origin_line_id = $dbatch['ix_l']; // deprecated
1007  $line->fk_origin_line = $dbatch['ix_l'];
1008  $line->qty = $dbatch['qty'];
1009  $line->detail_batch = $tab;
1010 
1011  // extrafields
1012  if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1013  $line->array_options = $array_options;
1014  }
1015 
1016  //var_dump($line);
1017  $this->lines[$num] = $line;
1018  return 1;
1019  }
1020  }
1021 
1029  public function update($user = null, $notrigger = 0)
1030  {
1031  global $conf;
1032  $error = 0;
1033 
1034  // Clean parameters
1035 
1036  if (isset($this->ref)) {
1037  $this->ref = trim($this->ref);
1038  }
1039  if (isset($this->entity)) {
1040  $this->entity = (int) $this->entity;
1041  }
1042  if (isset($this->ref_customer)) {
1043  $this->ref_customer = trim($this->ref_customer);
1044  }
1045  if (isset($this->socid)) {
1046  $this->socid = (int) $this->socid;
1047  }
1048  if (isset($this->fk_user_author)) {
1049  $this->fk_user_author = (int) $this->fk_user_author;
1050  }
1051  if (isset($this->fk_user_valid)) {
1052  $this->fk_user_valid = (int) $this->fk_user_valid;
1053  }
1054  if (isset($this->fk_delivery_address)) {
1055  $this->fk_delivery_address = (int) $this->fk_delivery_address;
1056  }
1057  if (isset($this->shipping_method_id)) {
1058  $this->shipping_method_id = (int) $this->shipping_method_id;
1059  }
1060  if (isset($this->tracking_number)) {
1061  $this->tracking_number = trim($this->tracking_number);
1062  }
1063  if (isset($this->statut)) {
1064  $this->statut = (int) $this->statut;
1065  }
1066  if (isset($this->trueDepth)) {
1067  $this->trueDepth = trim($this->trueDepth);
1068  }
1069  if (isset($this->trueWidth)) {
1070  $this->trueWidth = trim($this->trueWidth);
1071  }
1072  if (isset($this->trueHeight)) {
1073  $this->trueHeight = trim($this->trueHeight);
1074  }
1075  if (isset($this->size_units)) {
1076  $this->size_units = trim($this->size_units);
1077  }
1078  if (isset($this->weight_units)) {
1079  $this->weight_units = trim($this->weight_units);
1080  }
1081  if (isset($this->trueWeight)) {
1082  $this->weight = trim($this->trueWeight);
1083  }
1084  if (isset($this->note_private)) {
1085  $this->note_private = trim($this->note_private);
1086  }
1087  if (isset($this->note_public)) {
1088  $this->note_public = trim($this->note_public);
1089  }
1090  if (isset($this->model_pdf)) {
1091  $this->model_pdf = trim($this->model_pdf);
1092  }
1093 
1094 
1095 
1096  // Check parameters
1097  // Put here code to add control on parameters values
1098 
1099  // Update request
1100  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1101 
1102  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1103  $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1104  $sql .= " ref_customer=".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1105  $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1106  $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1107  $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1108  $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1109  $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1110  $sql .= " date_expedition=".(dol_strlen($this->date_expedition) != 0 ? "'".$this->db->idate($this->date_expedition)."'" : 'null').",";
1111  $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1112  $sql .= " fk_address=".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1113  $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1114  $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1115  $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1116  $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1117  $sql .= " height=".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1118  $sql .= " width=".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1119  $sql .= " size_units=".(isset($this->size_units) ? $this->size_units : "null").",";
1120  $sql .= " size=".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1121  $sql .= " weight_units=".(isset($this->weight_units) ? $this->weight_units : "null").",";
1122  $sql .= " weight=".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1123  $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1124  $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1125  $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1126  $sql .= " entity=".$conf->entity;
1127 
1128  $sql .= " WHERE rowid=".((int) $this->id);
1129 
1130  $this->db->begin();
1131 
1132  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1133  $resql = $this->db->query($sql);
1134  if (!$resql) {
1135  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1136  }
1137 
1138  if (!$error && !$notrigger) {
1139  // Call trigger
1140  $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1141  if ($result < 0) {
1142  $error++;
1143  }
1144  // End call triggers
1145  }
1146 
1147  // Commit or rollback
1148  if ($error) {
1149  foreach ($this->errors as $errmsg) {
1150  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1151  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1152  }
1153  $this->db->rollback();
1154  return -1 * $error;
1155  } else {
1156  $this->db->commit();
1157  return 1;
1158  }
1159  }
1160 
1161 
1169  public function cancel($notrigger = 0, $also_update_stock = false)
1170  {
1171  global $conf, $langs, $user;
1172 
1173  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1174 
1175  $error = 0;
1176  $this->error = '';
1177 
1178  $this->db->begin();
1179 
1180  // Add a protection to refuse deleting if shipment has at least one delivery
1181  $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1182  if (count($this->linkedObjectsIds) > 0) {
1183  $this->error = 'ErrorThereIsSomeDeliveries';
1184  $error++;
1185  }
1186 
1187  if (!$error && !$notrigger) {
1188  // Call trigger
1189  $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1190  if ($result < 0) {
1191  $error++;
1192  }
1193  // End call triggers
1194  }
1195 
1196  // Stock control
1197  if (!$error && isModEnabled('stock') &&
1198  (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
1199  ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1200  require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1201 
1202  $langs->load("agenda");
1203 
1204  // Loop on each product line to add a stock movement and delete features
1205  $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1206  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1207  $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1208  $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1209  $sql .= " AND cd.rowid = ed.fk_origin_line";
1210 
1211  dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1212  $resql = $this->db->query($sql);
1213  if ($resql) {
1214  $cpt = $this->db->num_rows($resql);
1215 
1216  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1217 
1218  for ($i = 0; $i < $cpt; $i++) {
1219  dol_syslog(get_class($this)."::delete movement index ".$i);
1220  $obj = $this->db->fetch_object($resql);
1221 
1222  $mouvS = new MouvementStock($this->db);
1223  // we do not log origin because it will be deleted
1224  $mouvS->origin = null;
1225  // get lot/serial
1226  $lotArray = null;
1227  if (isModEnabled('productbatch')) {
1228  $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1229  if (!is_array($lotArray)) {
1230  $error++;
1231  $this->errors[] = "Error ".$this->db->lasterror();
1232  }
1233  }
1234 
1235  if (empty($lotArray)) {
1236  // no lot/serial
1237  // We increment stock of product (and sub-products)
1238  // We use warehouse selected for each line
1239  $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
1240  if ($result < 0) {
1241  $error++;
1242  $this->errors = array_merge($this->errors, $mouvS->errors);
1243  break;
1244  }
1245  } else {
1246  // We increment stock of batches
1247  // We use warehouse selected for each line
1248  foreach ($lotArray as $lot) {
1249  $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
1250  if ($result < 0) {
1251  $error++;
1252  $this->errors = array_merge($this->errors, $mouvS->errors);
1253  break;
1254  }
1255  }
1256  if ($error) {
1257  break; // break for loop incase of error
1258  }
1259  }
1260  }
1261  } else {
1262  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1263  }
1264  }
1265 
1266  // delete batch expedition line
1267  if (!$error && isModEnabled('productbatch')) {
1268  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1269  if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1270  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1271  }
1272  }
1273 
1274 
1275  if (!$error) {
1276  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1277  $sql .= " WHERE fk_expedition = ".((int) $this->id);
1278 
1279  if ($this->db->query($sql)) {
1280  // Delete linked object
1281  $res = $this->deleteObjectLinked();
1282  if ($res < 0) {
1283  $error++;
1284  }
1285 
1286  // No delete expedition
1287  if (!$error) {
1288  $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1289  $sql .= " WHERE rowid = ".((int) $this->id);
1290 
1291  if ($this->db->query($sql)) {
1292  if (!empty($this->origin) && $this->origin_id > 0) {
1293  $this->fetch_origin();
1294  $origin = $this->origin;
1295  if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1296  // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1297  $this->$origin->loadExpeditions();
1298  //var_dump($this->$origin->expeditions);exit;
1299  if (count($this->$origin->expeditions) <= 0) {
1300  $this->$origin->setStatut(Commande::STATUS_VALIDATED);
1301  }
1302  }
1303  }
1304 
1305  if (!$error) {
1306  $this->db->commit();
1307 
1308  // We delete PDFs
1309  $ref = dol_sanitizeFileName($this->ref);
1310  if (!empty($conf->expedition->dir_output)) {
1311  $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1312  $file = $dir.'/'.$ref.'.pdf';
1313  if (file_exists($file)) {
1314  if (!dol_delete_file($file)) {
1315  return 0;
1316  }
1317  }
1318  if (file_exists($dir)) {
1319  if (!dol_delete_dir_recursive($dir)) {
1320  $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1321  return 0;
1322  }
1323  }
1324  }
1325 
1326  return 1;
1327  } else {
1328  $this->db->rollback();
1329  return -1;
1330  }
1331  } else {
1332  $this->error = $this->db->lasterror()." - sql=$sql";
1333  $this->db->rollback();
1334  return -3;
1335  }
1336  } else {
1337  $this->error = $this->db->lasterror()." - sql=$sql";
1338  $this->db->rollback();
1339  return -2;
1340  }//*/
1341  } else {
1342  $this->error = $this->db->lasterror()." - sql=$sql";
1343  $this->db->rollback();
1344  return -1;
1345  }
1346  } else {
1347  $this->db->rollback();
1348  return -1;
1349  }
1350  }
1351 
1360  public function delete($notrigger = 0, $also_update_stock = false)
1361  {
1362  global $conf, $langs, $user;
1363 
1364  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1365 
1366  $error = 0;
1367  $this->error = '';
1368 
1369  $this->db->begin();
1370 
1371  // Add a protection to refuse deleting if shipment has at least one delivery
1372  $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1373  if (count($this->linkedObjectsIds) > 0) {
1374  $this->error = 'ErrorThereIsSomeDeliveries';
1375  $error++;
1376  }
1377 
1378  if (!$error && !$notrigger) {
1379  // Call trigger
1380  $result = $this->call_trigger('SHIPPING_DELETE', $user);
1381  if ($result < 0) {
1382  $error++;
1383  }
1384  // End call triggers
1385  }
1386 
1387  // Stock control
1388  if (!$error && isModEnabled('stock') &&
1389  (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
1390  ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1391  require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1392 
1393  $langs->load("agenda");
1394 
1395  // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1396  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
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 = ".((int) $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  $cpt = $this->db->num_rows($resql);
1409  for ($i = 0; $i < $cpt; $i++) {
1410  dol_syslog(get_class($this)."::delete movement index ".$i);
1411  $obj = $this->db->fetch_object($resql);
1412 
1413  $mouvS = new MouvementStock($this->db);
1414  // we do not log origin because it will be deleted
1415  $mouvS->origin = null;
1416  // get lot/serial
1417  $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1418  if (!is_array($lotArray)) {
1419  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1420  }
1421  if (empty($lotArray)) {
1422  // no lot/serial
1423  // We increment stock of product (and sub-products)
1424  // We use warehouse selected for each line
1425  $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
1426  if ($result < 0) {
1427  $error++;
1428  $this->errors = array_merge($this->errors, $mouvS->errors);
1429  break;
1430  }
1431  } else {
1432  // We increment stock of batches
1433  // We use warehouse selected for each line
1434  foreach ($lotArray as $lot) {
1435  $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
1436  if ($result < 0) {
1437  $error++;
1438  $this->errors = array_merge($this->errors, $mouvS->errors);
1439  break;
1440  }
1441  }
1442  if ($error) {
1443  break; // break for loop incase of error
1444  }
1445  }
1446  }
1447  } else {
1448  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1449  }
1450  }
1451 
1452  // delete batch expedition line
1453  if (!$error) {
1454  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1455  if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1456  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1457  }
1458  }
1459 
1460  if (!$error) {
1461  $main = MAIN_DB_PREFIX.'expeditiondet';
1462  $ef = $main."_extrafields";
1463  $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
1464 
1465  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1466  $sql .= " WHERE fk_expedition = ".((int) $this->id);
1467 
1468  if ($this->db->query($sqlef) && $this->db->query($sql)) {
1469  // Delete linked object
1470  $res = $this->deleteObjectLinked();
1471  if ($res < 0) {
1472  $error++;
1473  }
1474 
1475  // delete extrafields
1476  $res = $this->deleteExtraFields();
1477  if ($res < 0) {
1478  $error++;
1479  }
1480 
1481  if (!$error) {
1482  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1483  $sql .= " WHERE rowid = ".((int) $this->id);
1484 
1485  if ($this->db->query($sql)) {
1486  if (!empty($this->origin) && $this->origin_id > 0) {
1487  $this->fetch_origin();
1488  $origin = $this->origin;
1489  if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1490  // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1491  $this->$origin->loadExpeditions();
1492  //var_dump($this->$origin->expeditions);exit;
1493  if (count($this->$origin->expeditions) <= 0) {
1494  $this->$origin->setStatut(Commande::STATUS_VALIDATED);
1495  }
1496  }
1497  }
1498 
1499  if (!$error) {
1500  $this->db->commit();
1501 
1502  // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1503  $this->deleteEcmFiles();
1504 
1505  // We delete PDFs
1506  $ref = dol_sanitizeFileName($this->ref);
1507  if (!empty($conf->expedition->dir_output)) {
1508  $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1509  $file = $dir.'/'.$ref.'.pdf';
1510  if (file_exists($file)) {
1511  if (!dol_delete_file($file)) {
1512  return 0;
1513  }
1514  }
1515  if (file_exists($dir)) {
1516  if (!dol_delete_dir_recursive($dir)) {
1517  $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1518  return 0;
1519  }
1520  }
1521  }
1522 
1523  return 1;
1524  } else {
1525  $this->db->rollback();
1526  return -1;
1527  }
1528  } else {
1529  $this->error = $this->db->lasterror()." - sql=$sql";
1530  $this->db->rollback();
1531  return -3;
1532  }
1533  } else {
1534  $this->error = $this->db->lasterror()." - sql=$sql";
1535  $this->db->rollback();
1536  return -2;
1537  }
1538  } else {
1539  $this->error = $this->db->lasterror()." - sql=$sql";
1540  $this->db->rollback();
1541  return -1;
1542  }
1543  } else {
1544  $this->db->rollback();
1545  return -1;
1546  }
1547  }
1548 
1549  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1555  public function fetch_lines()
1556  {
1557  // phpcs:enable
1558  global $conf, $mysoc;
1559 
1560  $this->lines = array();
1561 
1562  // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1563  // TODO: See if we can restore a common fetch_lines (one line = one record)
1564 
1565  $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";
1566  $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1567  $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";
1568  $sql .= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc, cd.rang";
1569  $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_origin_line, ed.fk_entrepot";
1570  $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type";
1571  $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";
1572  $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1573  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1574  $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1575  $sql .= " AND ed.fk_origin_line = cd.rowid";
1576  $sql .= " ORDER BY cd.rang, ed.fk_origin_line"; // We need after a break on fk_origin_line but when there is no break on fk_origin_line, cd.rang is same so we can add it as first order criteria.
1577 
1578  dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1579  $resql = $this->db->query($sql);
1580  if ($resql) {
1581  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1582 
1583  $num = $this->db->num_rows($resql);
1584  $i = 0;
1585  $lineindex = 0;
1586  $originline = 0;
1587 
1588  $this->total_ht = 0;
1589  $this->total_tva = 0;
1590  $this->total_ttc = 0;
1591  $this->total_localtax1 = 0;
1592  $this->total_localtax2 = 0;
1593 
1594  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1595 
1596  while ($i < $num) {
1597  $obj = $this->db->fetch_object($resql);
1598 
1599  if ($originline > 0 && $originline == $obj->fk_origin_line) {
1600  $line->entrepot_id = 0; // entrepod_id in details_entrepot
1601  $line->qty_shipped += $obj->qty_shipped;
1602  } else {
1603  $line = new ExpeditionLigne($this->db);
1604  $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
1605  $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
1606  }
1607 
1608  $detail_entrepot = new stdClass();
1609  $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1610  $detail_entrepot->qty_shipped = $obj->qty_shipped;
1611  $detail_entrepot->line_id = $obj->line_id;
1612  $line->details_entrepot[] = $detail_entrepot;
1613 
1614  $line->line_id = $obj->line_id;
1615  $line->rowid = $obj->line_id; // TODO deprecated
1616  $line->id = $obj->line_id;
1617 
1618  $line->fk_origin = 'orderline';
1619  $line->fk_origin_line = $obj->fk_origin_line;
1620  $line->origin_line_id = $obj->fk_origin_line; // TODO deprecated
1621 
1622  $line->fk_expedition = $this->id; // id of parent
1623 
1624  $line->product_type = $obj->product_type;
1625  $line->fk_product = $obj->fk_product;
1626  $line->fk_product_type = $obj->fk_product_type;
1627  $line->ref = $obj->product_ref; // TODO deprecated
1628  $line->product_ref = $obj->product_ref;
1629  $line->product_label = $obj->product_label;
1630  $line->libelle = $obj->product_label; // TODO deprecated
1631  $line->product_tosell = $obj->product_tosell;
1632  $line->product_tobuy = $obj->product_tobuy;
1633  $line->product_tobatch = $obj->product_tobatch;
1634  $line->label = $obj->custom_label;
1635  $line->description = $obj->description;
1636  $line->qty_asked = $obj->qty_asked;
1637  $line->rang = $obj->rang;
1638  $line->weight = $obj->weight;
1639  $line->weight_units = $obj->weight_units;
1640  $line->length = $obj->length;
1641  $line->length_units = $obj->length_units;
1642  $line->surface = $obj->surface;
1643  $line->surface_units = $obj->surface_units;
1644  $line->volume = $obj->volume;
1645  $line->volume_units = $obj->volume_units;
1646  $line->fk_unit = $obj->fk_unit;
1647 
1648  $line->pa_ht = $obj->pa_ht;
1649 
1650  // Local taxes
1651  $localtax_array = array(0=>$obj->localtax1_type, 1=>$obj->localtax1_tx, 2=>$obj->localtax2_type, 3=>$obj->localtax2_tx);
1652  $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1653  $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1654 
1655  // For invoicing
1656  $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
1657  $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1658  $line->qty = $line->qty_shipped;
1659  $line->total_ht = $tabprice[0];
1660  $line->total_localtax1 = $tabprice[9];
1661  $line->total_localtax2 = $tabprice[10];
1662  $line->total_ttc = $tabprice[2];
1663  $line->total_tva = $tabprice[1];
1664  $line->vat_src_code = $obj->vat_src_code;
1665  $line->tva_tx = $obj->tva_tx;
1666  $line->localtax1_tx = $obj->localtax1_tx;
1667  $line->localtax2_tx = $obj->localtax2_tx;
1668  $line->info_bits = $obj->info_bits;
1669  $line->price = $obj->price;
1670  $line->subprice = $obj->subprice;
1671  $line->remise_percent = $obj->remise_percent;
1672 
1673  $this->total_ht += $tabprice[0];
1674  $this->total_tva += $tabprice[1];
1675  $this->total_ttc += $tabprice[2];
1676  $this->total_localtax1 += $tabprice[9];
1677  $this->total_localtax2 += $tabprice[10];
1678 
1679  // Multicurrency
1680  $this->fk_multicurrency = $obj->fk_multicurrency;
1681  $this->multicurrency_code = $obj->multicurrency_code;
1682  $this->multicurrency_subprice = $obj->multicurrency_subprice;
1683  $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1684  $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1685  $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1686 
1687  if ($originline != $obj->fk_origin_line) {
1688  $line->detail_batch = array();
1689  }
1690 
1691  // Detail of batch
1692  if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1693  $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1694 
1695  if (is_array($newdetailbatch)) {
1696  if ($originline != $obj->fk_origin_line) {
1697  $line->detail_batch = $newdetailbatch;
1698  } else {
1699  $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1700  }
1701  }
1702  }
1703 
1704  $line->fetch_optionals();
1705 
1706  if ($originline != $obj->fk_origin_line) {
1707  $this->lines[$lineindex] = $line;
1708  $lineindex++;
1709  } else {
1710  $line->total_ht += $tabprice[0];
1711  $line->total_localtax1 += $tabprice[9];
1712  $line->total_localtax2 += $tabprice[10];
1713  $line->total_ttc += $tabprice[2];
1714  $line->total_tva += $tabprice[1];
1715  }
1716 
1717  $i++;
1718  $originline = $obj->fk_origin_line;
1719  }
1720  $this->db->free($resql);
1721  return 1;
1722  } else {
1723  $this->error = $this->db->error();
1724  return -3;
1725  }
1726  }
1727 
1735  public function deleteline($user, $lineid)
1736  {
1737  global $user;
1738 
1739  if ($this->statut == self::STATUS_DRAFT) {
1740  $this->db->begin();
1741 
1742  $line = new ExpeditionLigne($this->db);
1743 
1744  // For triggers
1745  $line->fetch($lineid);
1746 
1747  if ($line->delete($user) > 0) {
1748  //$this->update_price(1);
1749 
1750  $this->db->commit();
1751  return 1;
1752  } else {
1753  $this->db->rollback();
1754  return -1;
1755  }
1756  } else {
1757  $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1758  return -2;
1759  }
1760  }
1761 
1762 
1770  public function getTooltipContentArray($params)
1771  {
1772  global $conf, $langs;
1773 
1774  $langs->load('sendings');
1775 
1776  $nofetch = !empty($params['nofetch']);
1777 
1778  $datas = array();
1779  $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
1780  if (isset($this->statut)) {
1781  $datas['picto'] .= ' '.$this->getLibStatut(5);
1782  }
1783  $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1784  $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1785  if (!$nofetch) {
1786  $langs->load('companies');
1787  if (empty($this->thirdparty)) {
1788  $this->fetch_thirdparty();
1789  }
1790  $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1791  }
1792 
1793  return $datas;
1794  }
1795 
1807  public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1808  {
1809  global $langs, $conf, $hookmanager;
1810 
1811  $result = '';
1812  $params = [
1813  'id' => $this->id,
1814  'objecttype' => $this->element,
1815  'option' => $option,
1816  'nofetch' => 1,
1817  ];
1818  $classfortooltip = 'classfortooltip';
1819  $dataparams = '';
1820  if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1821  $classfortooltip = 'classforajaxtooltip';
1822  $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1823  $label = '';
1824  } else {
1825  $label = implode($this->getTooltipContentArray($params));
1826  }
1827 
1828  $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1829 
1830  if ($short) {
1831  return $url;
1832  }
1833 
1834  if ($option !== 'nolink') {
1835  // Add param to save lastsearch_values or not
1836  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1837  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1838  $add_save_lastsearch_values = 1;
1839  }
1840  if ($add_save_lastsearch_values) {
1841  $url .= '&save_lastsearch_values=1';
1842  }
1843  }
1844 
1845  $linkclose = '';
1846  if (empty($notooltip)) {
1847  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1848  $label = $langs->trans("Shipment");
1849  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1850  }
1851  $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1852  $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1853  }
1854 
1855  $linkstart = '<a href="'.$url.'"';
1856  $linkstart .= $linkclose.'>';
1857  $linkend = '</a>';
1858 
1859  $result .= $linkstart;
1860  if ($withpicto) {
1861  $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : $dataparams.' class="'.(($withpicto != 2) ? 'paddingright ' : '').$classfortooltip.'"'), 0, 0, $notooltip ? 0 : 1);
1862  }
1863  if ($withpicto != 2) {
1864  $result .= $this->ref;
1865  }
1866  $result .= $linkend;
1867  global $action;
1868  $hookmanager->initHooks(array($this->element . 'dao'));
1869  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1870  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1871  if ($reshook > 0) {
1872  $result = $hookmanager->resPrint;
1873  } else {
1874  $result .= $hookmanager->resPrint;
1875  }
1876  return $result;
1877  }
1878 
1885  public function getLibStatut($mode = 0)
1886  {
1887  return $this->LibStatut($this->statut, $mode);
1888  }
1889 
1890  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1898  public function LibStatut($status, $mode)
1899  {
1900  // phpcs:enable
1901  global $langs;
1902 
1903  $labelStatus = $langs->transnoentitiesnoconv($this->statuts[$status]);
1904  $labelStatusShort = $langs->transnoentitiesnoconv($this->statuts_short[$status]);
1905 
1906  $statusType = 'status'.$status;
1907  if ($status == self::STATUS_VALIDATED) {
1908  $statusType = 'status4';
1909  }
1910  if ($status == self::STATUS_CLOSED) {
1911  $statusType = 'status6';
1912  }
1913  if ($status == self::STATUS_CANCELED) {
1914  $statusType = 'status9';
1915  }
1916 
1917  return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
1918  }
1919 
1927  public function initAsSpecimen()
1928  {
1929  global $langs;
1930 
1931  $now = dol_now();
1932 
1933  dol_syslog(get_class($this)."::initAsSpecimen");
1934 
1935  $order = new Commande($this->db);
1936  $order->initAsSpecimen();
1937 
1938  // Initialise parametres
1939  $this->id = 0;
1940  $this->ref = 'SPECIMEN';
1941  $this->specimen = 1;
1942  $this->statut = self::STATUS_VALIDATED;
1943  $this->livraison_id = 0;
1944  $this->date = $now;
1945  $this->date_creation = $now;
1946  $this->date_valid = $now;
1947  $this->date_delivery = $now + 24 * 3600;
1948  $this->date_expedition = $now + 24 * 3600;
1949 
1950  $this->entrepot_id = 0;
1951  $this->fk_delivery_address = 0;
1952  $this->socid = 1;
1953 
1954  $this->commande_id = 0;
1955  $this->commande = $order;
1956 
1957  $this->origin_id = 1;
1958  $this->origin = 'commande';
1959 
1960  $this->note_private = 'Private note';
1961  $this->note_public = 'Public note';
1962 
1963  $nbp = 5;
1964  $xnbp = 0;
1965  while ($xnbp < $nbp) {
1966  $line = new ExpeditionLigne($this->db);
1967  $line->desc = $langs->trans("Description")." ".$xnbp;
1968  $line->libelle = $langs->trans("Description")." ".$xnbp; // deprecated
1969  $line->label = $langs->trans("Description")." ".$xnbp;
1970  $line->qty = 10;
1971  $line->qty_asked = 5;
1972  $line->qty_shipped = 4;
1973  $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
1974 
1975  $this->lines[] = $line;
1976  $xnbp++;
1977  }
1978  }
1979 
1980  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1989  public function set_date_livraison($user, $delivery_date)
1990  {
1991  // phpcs:enable
1992  return $this->setDeliveryDate($user, $delivery_date);
1993  }
1994 
2002  public function setDeliveryDate($user, $delivery_date)
2003  {
2004  if ($user->rights->expedition->creer) {
2005  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2006  $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2007  $sql .= " WHERE rowid = ".((int) $this->id);
2008 
2009  dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2010  $resql = $this->db->query($sql);
2011  if ($resql) {
2012  $this->date_delivery = $delivery_date;
2013  return 1;
2014  } else {
2015  $this->error = $this->db->error();
2016  return -1;
2017  }
2018  } else {
2019  return -2;
2020  }
2021  }
2022 
2023  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2029  public function fetch_delivery_methods()
2030  {
2031  // phpcs:enable
2032  global $langs;
2033  $this->meths = array();
2034 
2035  $sql = "SELECT em.rowid, em.code, em.libelle as label";
2036  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2037  $sql .= " WHERE em.active = 1";
2038  $sql .= " ORDER BY em.libelle ASC";
2039 
2040  $resql = $this->db->query($sql);
2041  if ($resql) {
2042  while ($obj = $this->db->fetch_object($resql)) {
2043  $label = $langs->trans('SendingMethod'.$obj->code);
2044  $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2045  }
2046  }
2047  }
2048 
2049  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2056  public function list_delivery_methods($id = '')
2057  {
2058  // phpcs:enable
2059  global $langs;
2060 
2061  $this->listmeths = array();
2062  $i = 0;
2063 
2064  $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2065  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2066  if ($id != '') {
2067  $sql .= " WHERE em.rowid=".((int) $id);
2068  }
2069 
2070  $resql = $this->db->query($sql);
2071  if ($resql) {
2072  while ($obj = $this->db->fetch_object($resql)) {
2073  $this->listmeths[$i]['rowid'] = $obj->rowid;
2074  $this->listmeths[$i]['code'] = $obj->code;
2075  $label = $langs->trans('SendingMethod'.$obj->code);
2076  $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2077  $this->listmeths[$i]['description'] = $obj->description;
2078  $this->listmeths[$i]['tracking'] = $obj->tracking;
2079  $this->listmeths[$i]['active'] = $obj->active;
2080  $i++;
2081  }
2082  }
2083  }
2084 
2091  public function getUrlTrackingStatus($value = '')
2092  {
2093  if (!empty($this->shipping_method_id)) {
2094  $sql = "SELECT em.code, em.tracking";
2095  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2096  $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2097 
2098  $resql = $this->db->query($sql);
2099  if ($resql) {
2100  if ($obj = $this->db->fetch_object($resql)) {
2101  $tracking = $obj->tracking;
2102  }
2103  }
2104  }
2105 
2106  if (!empty($tracking) && !empty($value)) {
2107  $url = str_replace('{TRACKID}', $value, $tracking);
2108  $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">'.($value ? $value : 'url').'</a>', $url, $url);
2109  } else {
2110  $this->tracking_url = $value;
2111  }
2112  }
2113 
2119  public function setClosed()
2120  {
2121  global $conf, $langs, $user;
2122 
2123  $error = 0;
2124 
2125  // Protection. This avoid to move stock later when we should not
2126  if ($this->statut == self::STATUS_CLOSED) {
2127  return 0;
2128  }
2129 
2130  $this->db->begin();
2131 
2132  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED;
2133  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2134 
2135  $resql = $this->db->query($sql);
2136  if ($resql) {
2137  // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2138  if ($this->origin == 'commande' && $this->origin_id > 0) {
2139  $order = new Commande($this->db);
2140  $order->fetch($this->origin_id);
2141 
2142  $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2143 
2144  $shipments_match_order = 1;
2145  foreach ($order->lines as $line) {
2146  $lineid = $line->id;
2147  $qty = $line->qty;
2148  if (($line->product_type == 0 || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) && $order->expeditions[$lineid] != $qty) {
2149  $shipments_match_order = 0;
2150  $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';
2151  dol_syslog($text);
2152  break;
2153  }
2154  }
2155  if ($shipments_match_order) {
2156  dol_syslog("Qty for the ".count($order->lines)." lines of the origin order is same than qty for lines in the shipment we close (shipments_match_order is true), with new status Expedition::STATUS_CLOSED=".self::STATUS_CLOSED.', so we close order');
2157  // We close the order
2158  $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2159  }
2160  }
2161 
2162  $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2163  $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2164 
2165  // If stock increment is done on closing
2166  if (!$error && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
2167  $result = $this->manageStockMvtOnEvt($user);
2168  if ($result<0) {
2169  $error++;
2170  }
2171  }
2172 
2173  // Call trigger
2174  if (!$error) {
2175  $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2176  if ($result < 0) {
2177  $error++;
2178  }
2179  }
2180  } else {
2181  dol_print_error($this->db);
2182  $error++;
2183  }
2184 
2185  if (!$error) {
2186  $this->db->commit();
2187  return 1;
2188  } else {
2189  $this->statut = self::STATUS_VALIDATED;
2190  $this->status = self::STATUS_VALIDATED;
2191 
2192  $this->db->rollback();
2193  return -1;
2194  }
2195  }
2196 
2204  private function manageStockMvtOnEvt($user)
2205  {
2206  global $langs;
2207 
2208  $error=0;
2209 
2210  require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2211 
2212  $langs->load("agenda");
2213 
2214  // Loop on each product line to add a stock movement
2215  $sql = "SELECT cd.fk_product, cd.subprice,";
2216  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2217  $sql .= " e.ref,";
2218  $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2219  $sql .= " cd.rowid as cdid, ed.rowid as edid";
2220  $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2221  $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2222  $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2223  $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2224  $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2225  $sql .= " AND cd.rowid = ed.fk_origin_line";
2226 
2227  dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2228  $resql = $this->db->query($sql);
2229  if ($resql) {
2230  $cpt = $this->db->num_rows($resql);
2231  for ($i = 0; $i < $cpt; $i++) {
2232  $obj = $this->db->fetch_object($resql);
2233  if (empty($obj->edbrowid)) {
2234  $qty = $obj->qty;
2235  } else {
2236  $qty = $obj->edbqty;
2237  }
2238  if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2239  continue;
2240  }
2241  dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2242 
2243  $mouvS = new MouvementStock($this->db);
2244  $mouvS->origin = &$this;
2245  $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2246 
2247  if (empty($obj->edbrowid)) {
2248  // line without batch detail
2249 
2250  // 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
2251  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentClassifyClosedInDolibarr", $obj->ref));
2252  if ($result < 0) {
2253  $this->error = $mouvS->error;
2254  $this->errors = $mouvS->errors;
2255  $error++;
2256  break;
2257  }
2258  } else {
2259  // line with batch detail
2260 
2261  // 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
2262  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentClassifyClosedInDolibarr", $obj->ref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
2263  if ($result < 0) {
2264  $this->error = $mouvS->error;
2265  $this->errors = $mouvS->errors;
2266  $error++;
2267  break;
2268  }
2269  }
2270 
2271  // If some stock lines are now 0, we can remove entry into llx_product_stock, but only if there is no child lines into llx_product_batch (detail of batch, because we can imagine
2272  // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2273  $sqldelete = "DELETE FROM ".MAIN_DB_PREFIX."product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM ".MAIN_DB_PREFIX."product_batch as pb)";
2274  $resqldelete = $this->db->query($sqldelete);
2275  // We do not test error, it can fails if there is child in batch details
2276  }
2277  } else {
2278  $this->error = $this->db->lasterror();
2279  $this->errors[] = $this->db->lasterror();
2280  $error ++;
2281  }
2282 
2283  return $error;
2284  }
2285 
2291  public function setBilled()
2292  {
2293  global $user;
2294  $error = 0;
2295 
2296  $this->db->begin();
2297 
2298  $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut=2, billed=1'; // TODO Update only billed
2299  $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2300 
2301  $resql = $this->db->query($sql);
2302  if ($resql) {
2303  $this->statut = self::STATUS_CLOSED;
2304  $this->billed = 1;
2305 
2306  // Call trigger
2307  $result = $this->call_trigger('SHIPPING_BILLED', $user);
2308  if ($result < 0) {
2309  $error++;
2310  }
2311  } else {
2312  $error++;
2313  $this->errors[] = $this->db->lasterror;
2314  }
2315 
2316  if (empty($error)) {
2317  $this->db->commit();
2318  return 1;
2319  } else {
2320  $this->statut = self::STATUS_VALIDATED;
2321  $this->billed = 0;
2322  $this->db->rollback();
2323  return -1;
2324  }
2325  }
2326 
2334  public function setDraft($user, $notrigger = 0)
2335  {
2336  // Protection
2337  if ($this->statut <= self::STATUS_DRAFT) {
2338  return 0;
2339  }
2340 
2341  return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2342  }
2343 
2349  public function reOpen()
2350  {
2351  global $conf, $langs, $user;
2352 
2353  $error = 0;
2354 
2355  // Protection. This avoid to move stock later when we should not
2356  if ($this->statut == self::STATUS_VALIDATED) {
2357  return 0;
2358  }
2359 
2360  $this->db->begin();
2361 
2362  $oldbilled = $this->billed;
2363 
2364  $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2365  $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2366 
2367  $resql = $this->db->query($sql);
2368  if ($resql) {
2369  $this->statut = self::STATUS_VALIDATED;
2370  $this->billed = 0;
2371 
2372  // If stock increment is done on closing
2373  if (!$error && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
2374  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2375 
2376  $langs->load("agenda");
2377 
2378  // Loop on each product line to add a stock movement
2379  // TODO possibilite d'expedier a partir d'une propale ou autre origine
2380  $sql = "SELECT cd.fk_product, cd.subprice,";
2381  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2382  $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2383  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2384  $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2385  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2386  $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2387  $sql .= " AND cd.rowid = ed.fk_origin_line";
2388 
2389  dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2390  $resql = $this->db->query($sql);
2391  if ($resql) {
2392  $cpt = $this->db->num_rows($resql);
2393  for ($i = 0; $i < $cpt; $i++) {
2394  $obj = $this->db->fetch_object($resql);
2395  if (empty($obj->edbrowid)) {
2396  $qty = $obj->qty;
2397  } else {
2398  $qty = $obj->edbqty;
2399  }
2400  if ($qty <= 0) {
2401  continue;
2402  }
2403  dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2404 
2405  //var_dump($this->lines[$i]);
2406  $mouvS = new MouvementStock($this->db);
2407  $mouvS->origin = &$this;
2408  $mouvS->setOrigin($this->element, $this->id);
2409 
2410  if (empty($obj->edbrowid)) {
2411  // line without batch detail
2412 
2413  // 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
2414  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2415  if ($result < 0) {
2416  $this->error = $mouvS->error;
2417  $this->errors = $mouvS->errors;
2418  $error++;
2419  break;
2420  }
2421  } else {
2422  // line with batch detail
2423 
2424  // 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
2425  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
2426  if ($result < 0) {
2427  $this->error = $mouvS->error;
2428  $this->errors = $mouvS->errors;
2429  $error++;
2430  break;
2431  }
2432  }
2433  }
2434  } else {
2435  $this->error = $this->db->lasterror();
2436  $error++;
2437  }
2438  }
2439 
2440  if (!$error) {
2441  // Call trigger
2442  $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2443  if ($result < 0) {
2444  $error++;
2445  }
2446  }
2447  } else {
2448  $error++;
2449  $this->errors[] = $this->db->lasterror();
2450  }
2451 
2452  if (!$error) {
2453  $this->db->commit();
2454  return 1;
2455  } else {
2456  $this->statut = self::STATUS_CLOSED;
2457  $this->billed = $oldbilled;
2458  $this->db->rollback();
2459  return -1;
2460  }
2461  }
2462 
2474  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2475  {
2476  global $conf;
2477 
2478  $outputlangs->load("products");
2479 
2480  if (!dol_strlen($modele)) {
2481  $modele = 'rouget';
2482 
2483  if (!empty($this->model_pdf)) {
2484  $modele = $this->model_pdf;
2485  } elseif (!empty($this->modelpdf)) { // deprecated
2486  $modele = $this->modelpdf;
2487  } elseif (!empty($conf->global->EXPEDITION_ADDON_PDF)) {
2488  $modele = $conf->global->EXPEDITION_ADDON_PDF;
2489  }
2490  }
2491 
2492  $modelpath = "core/modules/expedition/doc/";
2493 
2494  $this->fetch_origin();
2495 
2496  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2497  }
2498 
2507  public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2508  {
2509  $tables = array(
2510  'expedition'
2511  );
2512 
2513  return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2514  }
2515 }
2516 
2517 
2522 {
2526  public $element = 'expeditiondet';
2527 
2531  public $table_element = 'expeditiondet';
2532 
2533 
2540  public $line_id; // deprecated
2541 
2547 
2553  public $fk_origin; // Example: 'orderline'
2554 
2558  public $fk_origin_line;
2559 
2563  public $fk_expedition;
2564 
2568  public $db;
2569 
2573  public $qty;
2574 
2578  public $qty_shipped;
2579 
2583  public $fk_product;
2584 
2585  // detail of lot and qty = array(id in llx_expeditiondet_batch, fk_expeditiondet, batch, qty, fk_origin_stock)
2586  // We can use this to know warehouse planned to be used for each lot.
2587  public $detail_batch;
2588 
2589  // detail of warehouses and qty
2590  // We can use this to know warehouse when there is no lot.
2591  public $details_entrepot;
2592 
2593 
2597  public $entrepot_id;
2598 
2599 
2603  public $qty_asked;
2604 
2609  public $ref;
2610 
2614  public $product_ref;
2615 
2620  public $libelle;
2621 
2625  public $product_label;
2626 
2632  public $desc;
2633 
2637  public $product_desc;
2638 
2643  public $product_type = 0;
2644 
2648  public $rang;
2649 
2653  public $weight;
2654  public $weight_units;
2655 
2659  public $length;
2660  public $length_units;
2661 
2665  public $surface;
2666  public $surface_units;
2667 
2671  public $volume;
2672  public $volume_units;
2673 
2674  // Invoicing
2675  public $remise_percent;
2676  public $tva_tx;
2677 
2681  public $total_ht;
2682 
2686  public $total_ttc;
2687 
2691  public $total_tva;
2692 
2696  public $total_localtax1;
2697 
2701  public $total_localtax2;
2702 
2703 
2709  public function __construct($db)
2710  {
2711  $this->db = $db;
2712  }
2713 
2720  public function fetch($rowid)
2721  {
2722  $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_origin_line, ed.qty, ed.rang';
2723  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
2724  $sql .= ' WHERE ed.rowid = '.((int) $rowid);
2725  $result = $this->db->query($sql);
2726  if ($result) {
2727  $objp = $this->db->fetch_object($result);
2728  $this->id = $objp->rowid;
2729  $this->fk_expedition = $objp->fk_expedition;
2730  $this->entrepot_id = $objp->fk_entrepot;
2731  $this->fk_origin_line = $objp->fk_origin_line;
2732  $this->qty = $objp->qty;
2733  $this->rang = $objp->rang;
2734 
2735  $this->db->free($result);
2736 
2737  return 1;
2738  } else {
2739  $this->errors[] = $this->db->lasterror();
2740  $this->error = $this->db->lasterror();
2741  return -1;
2742  }
2743  }
2744 
2752  public function insert($user, $notrigger = 0)
2753  {
2754  global $langs, $conf;
2755 
2756  $error = 0;
2757 
2758  // Check parameters
2759  if (empty($this->fk_expedition) || empty($this->fk_origin_line) || !is_numeric($this->qty)) {
2760  $this->error = 'ErrorMandatoryParametersNotProvided';
2761  return -1;
2762  }
2763 
2764  $this->db->begin();
2765 
2766  if (empty($this->rang)) {
2767  $this->rang = 0;
2768  }
2769 
2770  // Rank to use
2771  $ranktouse = $this->rang;
2772  if ($ranktouse == -1) {
2773  $rangmax = $this->line_max($this->fk_expedition);
2774  $ranktouse = $rangmax + 1;
2775  }
2776 
2777  $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
2778  $sql .= "fk_expedition";
2779  $sql .= ", fk_entrepot";
2780  $sql .= ", fk_origin_line";
2781  $sql .= ", qty";
2782  $sql .= ", rang";
2783  $sql .= ") VALUES (";
2784  $sql .= $this->fk_expedition;
2785  $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
2786  $sql .= ", ".((int) $this->fk_origin_line);
2787  $sql .= ", ".price2num($this->qty, 'MS');
2788  $sql .= ", ".((int) $ranktouse);
2789  $sql .= ")";
2790 
2791  dol_syslog(get_class($this)."::insert", LOG_DEBUG);
2792  $resql = $this->db->query($sql);
2793  if ($resql) {
2794  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
2795 
2796  if (!$error) {
2797  $result = $this->insertExtraFields();
2798  if ($result < 0) {
2799  $error++;
2800  }
2801  }
2802 
2803  if (!$error && !$notrigger) {
2804  // Call trigger
2805  $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
2806  if ($result < 0) {
2807  $error++;
2808  }
2809  // End call triggers
2810  }
2811 
2812  if ($error) {
2813  foreach ($this->errors as $errmsg) {
2814  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2815  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2816  }
2817  }
2818  } else {
2819  $error++;
2820  }
2821 
2822  if ($error) {
2823  $this->db->rollback();
2824  return -1;
2825  } else {
2826  $this->db->commit();
2827  return $this->id;
2828  }
2829  }
2830 
2838  public function delete($user = null, $notrigger = 0)
2839  {
2840  global $conf;
2841 
2842  $error = 0;
2843 
2844  $this->db->begin();
2845 
2846  // delete batch expedition line
2847  if (isModEnabled('productbatch')) {
2848  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
2849  $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
2850 
2851  if (!$this->db->query($sql)) {
2852  $this->errors[] = $this->db->lasterror()." - sql=$sql";
2853  $error++;
2854  }
2855  }
2856 
2857  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
2858  $sql .= " WHERE rowid = ".((int) $this->id);
2859 
2860  if (!$error && $this->db->query($sql)) {
2861  // Remove extrafields
2862  if (!$error) {
2863  $result = $this->deleteExtraFields();
2864  if ($result < 0) {
2865  $this->errors[] = $this->error;
2866  $error++;
2867  }
2868  }
2869  if (!$error && !$notrigger) {
2870  // Call trigger
2871  $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
2872  if ($result < 0) {
2873  $this->errors[] = $this->error;
2874  $error++;
2875  }
2876  // End call triggers
2877  }
2878  } else {
2879  $this->errors[] = $this->db->lasterror()." - sql=$sql";
2880  $error++;
2881  }
2882 
2883  if (!$error) {
2884  $this->db->commit();
2885  return 1;
2886  } else {
2887  foreach ($this->errors as $errmsg) {
2888  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2889  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2890  }
2891  $this->db->rollback();
2892  return -1 * $error;
2893  }
2894  }
2895 
2903  public function update($user = null, $notrigger = 0)
2904  {
2905  global $conf;
2906 
2907  $error = 0;
2908 
2909  dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
2910 
2911  $this->db->begin();
2912 
2913  // Clean parameters
2914  if (empty($this->qty)) {
2915  $this->qty = 0;
2916  }
2917  $qty = price2num($this->qty);
2918  $remainingQty = 0;
2919  $batch = null;
2920  $batch_id = null;
2921  $expedition_batch_id = null;
2922  if (is_array($this->detail_batch)) { // array of ExpeditionLineBatch
2923  if (count($this->detail_batch) > 1) {
2924  dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
2925  $this->errors[] = 'ErrorBadParameters';
2926  $error++;
2927  } else {
2928  $batch = $this->detail_batch[0]->batch;
2929  $batch_id = $this->detail_batch[0]->fk_origin_stock;
2930  $expedition_batch_id = $this->detail_batch[0]->id;
2931  if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id) {
2932  dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
2933  $this->errors[] = 'ErrorBadParameters';
2934  $error++;
2935  }
2936  $qty = price2num($this->detail_batch[0]->qty);
2937  }
2938  } elseif (!empty($this->detail_batch)) {
2939  $batch = $this->detail_batch->batch;
2940  $batch_id = $this->detail_batch->fk_origin_stock;
2941  $expedition_batch_id = $this->detail_batch->id;
2942  if ($this->entrepot_id != $this->detail_batch->entrepot_id) {
2943  dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
2944  $this->errors[] = 'ErrorBadParameters';
2945  $error++;
2946  }
2947  $qty = price2num($this->detail_batch->qty);
2948  }
2949 
2950  // check parameters
2951  if (!isset($this->id) || !isset($this->entrepot_id)) {
2952  dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
2953  $this->errors[] = 'ErrorMandatoryParametersNotProvided';
2954  $error++;
2955  return -1;
2956  }
2957 
2958  // update lot
2959 
2960  if (!empty($batch) && isModEnabled('productbatch')) {
2961  dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id, batch=$batch");
2962 
2963  if (empty($batch_id) || empty($this->fk_product)) {
2964  dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
2965  $this->errors[] = 'ErrorMandatoryParametersNotProvided';
2966  $error++;
2967  }
2968 
2969  // fetch remaining lot qty
2970  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
2971 
2972  if (!$error && ($lotArray = $shipmentlinebatch->fetchAll($this->id)) < 0) {
2973  $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
2974  $error++;
2975  } else {
2976  // caculate new total line qty
2977  foreach ($lotArray as $lot) {
2978  if ($expedition_batch_id != $lot->id) {
2979  $remainingQty += $lot->qty;
2980  }
2981  }
2982  $qty += $remainingQty;
2983 
2984  //fetch lot details
2985 
2986  // fetch from product_lot
2987  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
2988  $lot = new Productlot($this->db);
2989  if ($lot->fetch(0, $this->fk_product, $batch) < 0) {
2990  $this->errors[] = $lot->errors;
2991  $error++;
2992  }
2993  if (!$error && !empty($expedition_batch_id)) {
2994  // delete lot expedition line
2995  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
2996  $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
2997  $sql .= " AND rowid = ".((int) $expedition_batch_id);
2998 
2999  if (!$this->db->query($sql)) {
3000  $this->errors[] = $this->db->lasterror()." - sql=$sql";
3001  $error++;
3002  }
3003  }
3004  if (!$error && $this->detail_batch->qty > 0) {
3005  // create lot expedition line
3006  if (isset($lot->id)) {
3007  $shipmentLot = new ExpeditionLineBatch($this->db);
3008  $shipmentLot->batch = $lot->batch;
3009  $shipmentLot->eatby = $lot->eatby;
3010  $shipmentLot->sellby = $lot->sellby;
3011  $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id;
3012  $shipmentLot->qty = $this->detail_batch->qty;
3013  $shipmentLot->fk_origin_stock = $batch_id;
3014  if ($shipmentLot->create($this->id) < 0) {
3015  $this->errors = $shipmentLot->errors;
3016  $error++;
3017  }
3018  }
3019  }
3020  }
3021  }
3022  if (!$error) {
3023  // update line
3024  $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3025  $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
3026  $sql .= " , qty = ".((float) price2num($qty, 'MS'));
3027  $sql .= " WHERE rowid = ".((int) $this->id);
3028 
3029  if (!$this->db->query($sql)) {
3030  $this->errors[] = $this->db->lasterror()." - sql=$sql";
3031  $error++;
3032  }
3033  }
3034 
3035  if (!$error) {
3036  if (!$error) {
3037  $result = $this->insertExtraFields();
3038  if ($result < 0) {
3039  $this->errors[] = $this->error;
3040  $error++;
3041  }
3042  }
3043  }
3044 
3045  if (!$error && !$notrigger) {
3046  // Call trigger
3047  $result = $this->call_trigger('LINESHIPPING_MODIFY', $user);
3048  if ($result < 0) {
3049  $this->errors[] = $this->error;
3050  $error++;
3051  }
3052  // End call triggers
3053  }
3054  if (!$error) {
3055  $this->db->commit();
3056  return 1;
3057  } else {
3058  foreach ($this->errors as $errmsg) {
3059  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3060  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3061  }
3062  $this->db->rollback();
3063  return -1 * $error;
3064  }
3065  }
3066 }
Expedition\$date_expedition
$date_expedition
Definition: expedition.class.php:166
ExpeditionLigne\insert
insert($user, $notrigger=0)
Insert line into database.
Definition: expedition.class.php:2752
CommonObject\setStatusCommon
setStatusCommon($user, $status, $notrigger=0, $triggercode='')
Set to a status.
Definition: commonobject.class.php:9852
Societe
Class to manage third parties objects (customers, suppliers, prospects...)
Definition: societe.class.php:51
Expedition\setClosed
setClosed()
Classify the shipping as closed.
Definition: expedition.class.php:2119
Productbatch
Manage record for batch number management.
Definition: productbatch.class.php:32
Expedition\update
update($user=null, $notrigger=0)
Update database.
Definition: expedition.class.php:1029
Expedition\list_delivery_methods
list_delivery_methods($id='')
Fetch all deliveries method and return an array.
Definition: expedition.class.php:2056
dol_sanitizeFileName
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
Definition: functions.lib.php:1323
Expedition
Class to manage shipments.
Definition: expedition.class.php:52
Expedition\addline_batch
addline_batch($dbatch, $array_options=0)
Add a shipment line with batch record.
Definition: expedition.class.php:961
Expedition\setDeliveryDate
setDeliveryDate($user, $delivery_date)
Set the planned delivery date.
Definition: expedition.class.php:2002
Expedition\__construct
__construct($db)
Constructor.
Definition: expedition.class.php:243
Expedition\reOpen
reOpen()
Classify the shipping as validated/opened.
Definition: expedition.class.php:2349
dol_delete_dir_recursive
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
Definition: files.lib.php:1485
DoliDB
Class to manage Dolibarr database access.
Definition: DoliDB.class.php:30
CommonObject\fetchObjectLinked
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).
Definition: commonobject.class.php:3858
dol_print_error
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
Definition: functions.lib.php:5107
ExpeditionLigne
Classe to manage lines of shipment.
Definition: expedition.class.php:2521
Expedition\STATUS_DRAFT
const STATUS_DRAFT
Draft status.
Definition: expedition.class.php:220
dol_buildpath
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
Definition: functions.lib.php:1158
dol_dir_list
dol_dir_list($path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition: files.lib.php:62
Expedition\create_line_batch
create_line_batch($line_ext, $array_options=0)
Create the detail of the expedition line.
Definition: expedition.class.php:510
Commande\STATUS_SHIPMENTONPROCESS
const STATUS_SHIPMENTONPROCESS
Shipment on process.
Definition: commande.class.php:402
CommonIncoterm
trait CommonIncoterm
Superclass for incoterm classes.
Definition: commonincoterm.class.php:29
Expedition\setDraft
setDraft($user, $notrigger=0)
Set draft status.
Definition: expedition.class.php:2334
CommonObject\deleteObjectLinked
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid='', $f_user=null, $notrigger=0)
Delete all links between an object $this.
Definition: commonobject.class.php:4170
Expedition\addline
addline($entrepot_id, $id, $qty, $array_options=0)
Add an expedition line.
Definition: expedition.class.php:878
ExpeditionLineBatch
CRUD class for batch number management within shipment.
Definition: expeditionlinebatch.class.php:29
CommonObject\setStatut
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='fk_statut')
Set status of an object.
Definition: commonobject.class.php:4306
Expedition\$date
$date
Definition: expedition.class.php:160
Expedition\deleteline
deleteline($user, $lineid)
Delete detail line.
Definition: expedition.class.php:1735
CommonObjectLine
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Definition: commonobjectline.class.php:32
CommonObject
Parent class of all other business classes (invoices, contracts, proposals, orders,...
Definition: commonobject.class.php:45
Expedition\generateDocument
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
Definition: expedition.class.php:2474
Expedition\STATUS_CLOSED
const STATUS_CLOSED
Closed status.
Definition: expedition.class.php:230
CommonObject\fetch_origin
fetch_origin()
Read linked origin object.
Definition: commonobject.class.php:1827
price2num
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
Definition: functions.lib.php:5955
Productlot
Class with list of lots and properties.
Definition: productlot.class.php:37
CommonObject\fetch_thirdparty
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
Definition: commonobject.class.php:1634
img_picto
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
Definition: functions.lib.php:4135
Expedition\setBilled
setBilled()
Classify the shipping as invoiced (used when WORKFLOW_BILL_ON_SHIPMENT is on)
Definition: expedition.class.php:2291
Expedition\getUrlTrackingStatus
getUrlTrackingStatus($value='')
Forge an set tracking url.
Definition: expedition.class.php:2091
Commande\STATUS_VALIDATED
const STATUS_VALIDATED
Validated status.
Definition: commande.class.php:398
CommonObject\commonGenerateDocument
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
Definition: commonobject.class.php:5361
calcul_price_total
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
dol_delete_file
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
Definition: files.lib.php:1334
Expedition\valid
valid($user, $notrigger=0)
Validate object and update stock if option enabled.
Definition: expedition.class.php:701
OrderLine
Class to manage order lines.
Definition: commande.class.php:4216
Expedition\fetch_lines
fetch_lines()
Load lines.
Definition: expedition.class.php:1555
get_localtax
get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
Definition: functions.lib.php:6136
Expedition\initAsSpecimen
initAsSpecimen()
Initialise an instance with random values.
Definition: expedition.class.php:1927
MouvementStock
Class to manage stock movements.
Definition: mouvementstock.class.php:31
CommonObject\insertExtraFields
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
Definition: commonobject.class.php:6137
ExpeditionLigne\update
update($user=null, $notrigger=0)
Update a line in database.
Definition: expedition.class.php:2903
Expedition\create
create($user, $notrigger=0)
Create expedition en base.
Definition: expedition.class.php:319
ExpeditionLigne\fetch
fetch($rowid)
Load line expedition.
Definition: expedition.class.php:2720
Commande
Class to manage customers orders.
Definition: commande.class.php:47
Expedition\replaceThirdparty
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
Definition: expedition.class.php:2507
CommonObject\deleteEcmFiles
deleteEcmFiles($mode=0)
Delete related files of object in database.
Definition: commonobject.class.php:10135
Expedition\manageStockMvtOnEvt
manageStockMvtOnEvt($user)
Manage Stock MVt onb Close or valid Shipment.
Definition: expedition.class.php:2204
dol_syslog
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
Definition: functions.lib.php:1741
Expedition\getTooltipContentArray
getTooltipContentArray($params)
getTooltipContentArray
Definition: expedition.class.php:1770
CommonObject\line_max
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
Definition: commonobject.class.php:3317
Expedition\getNomUrl
getNomUrl($withpicto=0, $option='', $max=0, $short=0, $notooltip=0, $save_lastsearch_value=-1)
Return clicable link of object (with eventually picto)
Definition: expedition.class.php:1807
Expedition\LibStatut
LibStatut($status, $mode)
Return label of a status.
Definition: expedition.class.php:1898
CommonObject\fetch_optionals
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
Definition: commonobject.class.php:5986
Expedition\fetch
fetch($id, $ref='', $ref_ext='', $notused='')
Get object and lines from database.
Definition: expedition.class.php:560
$sql
if(isModEnabled('facture') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $sql
Social contributions to pay.
Definition: index.php:746
dol_strlen
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
Definition: functions.lib.php:3997
Expedition\cancel
cancel($notrigger=0, $also_update_stock=false)
Cancel shipment.
Definition: expedition.class.php:1169
Expedition\set_date_livraison
set_date_livraison($user, $delivery_date)
Set delivery date.
Definition: expedition.class.php:1989
Expedition\getNextNumRef
getNextNumRef($soc)
Return next expedition ref.
Definition: expedition.class.php:270
Expedition\create_delivery
create_delivery($user)
Create a delivery receipt from a shipment.
Definition: expedition.class.php:841
ExpeditionLigne\$libelle
$libelle
Definition: expedition.class.php:2620
isModEnabled
isModEnabled($module)
Is Dolibarr module enabled.
Definition: functions.lib.php:207
ref
$object ref
Definition: info.php:78
getDolGlobalString
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
Definition: functions.lib.php:142
Expedition\STATUS_CANCELED
const STATUS_CANCELED
Canceled status.
Definition: expedition.class.php:235
Expedition\STATUS_VALIDATED
const STATUS_VALIDATED
Validated status.
Definition: expedition.class.php:225
CommonObject\deleteExtraFields
deleteExtraFields()
Delete all extra fields values for the current object.
Definition: commonobject.class.php:6097
Product
Class to manage products or services.
Definition: product.class.php:46
CommonObject\add_object_linked
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
Definition: commonobject.class.php:3760
dolGetStatus
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
Definition: functions.lib.php:10967
ExpeditionLigne\__construct
__construct($db)
Constructor.
Definition: expedition.class.php:2709
img_object
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
Definition: functions.lib.php:4473
ExpeditionLigne\$ref
$ref
Definition: expedition.class.php:2609
dol_now
dol_now($mode='auto')
Return date for now.
Definition: functions.lib.php:3056
Expedition\create_line
create_line($entrepot_id, $origin_line_id, $qty, $rang=0, $array_options=null)
Create a expedition line.
Definition: expedition.class.php:482
Expedition\getLibStatut
getLibStatut($mode=0)
Return status label.
Definition: expedition.class.php:1885
Delivery
Class to manage receptions.
Definition: delivery.class.php:46
CommonObject\call_trigger
call_trigger($triggerName, $user)
Call trigger based on this instance.
Definition: commonobject.class.php:5770
user
$conf db user
Definition: repair.php:124
ExpeditionLigne\$origin_line_id
$origin_line_id
Definition: expedition.class.php:2546
getDolGlobalInt
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
Definition: functions.lib.php:156
CommonObject\commonReplaceThirdparty
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
Definition: commonobject.class.php:8504
Expedition\fetch_delivery_methods
fetch_delivery_methods()
Fetch deliveries method and return an array.
Definition: expedition.class.php:2029