dolibarr  18.0.6
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 = 'Shipment 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  } elseif (!empty($this->ref)) {
737  $numref = $this->ref;
738  } else {
739  $numref = "EXP".$this->id;
740  }
741  $this->newref = dol_sanitizeFileName($numref);
742 
743  $now = dol_now();
744 
745  // Validate
746  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
747  $sql .= " ref='".$this->db->escape($numref)."'";
748  $sql .= ", fk_statut = 1";
749  $sql .= ", date_valid = '".$this->db->idate($now)."'";
750  $sql .= ", fk_user_valid = ".$user->id;
751  $sql .= " WHERE rowid = ".((int) $this->id);
752 
753  dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
754  $resql = $this->db->query($sql);
755  if (!$resql) {
756  $this->error = $this->db->lasterror();
757  $error++;
758  }
759 
760  // If stock increment is done on sending (recommanded choice)
761  if (!$error && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)) {
762  $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
763  if ($result < 0) {
764  return -2;
765  }
766  }
767 
768  // Change status of order to "shipment in process"
769  $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
770  if (!$ret) {
771  $error++;
772  }
773 
774  if (!$error && !$notrigger) {
775  // Call trigger
776  $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
777  if ($result < 0) {
778  $error++;
779  }
780  // End call triggers
781  }
782 
783  if (!$error) {
784  $this->oldref = $this->ref;
785 
786  // Rename directory if dir was a temporary ref
787  if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
788  // Now we rename also files into index
789  $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)."'";
790  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
791  $resql = $this->db->query($sql);
792  if (!$resql) {
793  $error++; $this->error = $this->db->lasterror();
794  }
795  $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
796  $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
797  $resql = $this->db->query($sql);
798  if (!$resql) {
799  $error++; $this->error = $this->db->lasterror();
800  }
801 
802  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
803  $oldref = dol_sanitizeFileName($this->ref);
804  $newref = dol_sanitizeFileName($numref);
805  $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
806  $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
807  if (!$error && file_exists($dirsource)) {
808  dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
809 
810  if (@rename($dirsource, $dirdest)) {
811  dol_syslog("Rename ok");
812  // Rename docs starting with $oldref with $newref
813  $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
814  foreach ($listoffiles as $fileentry) {
815  $dirsource = $fileentry['name'];
816  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
817  $dirsource = $fileentry['path'].'/'.$dirsource;
818  $dirdest = $fileentry['path'].'/'.$dirdest;
819  @rename($dirsource, $dirdest);
820  }
821  }
822  }
823  }
824  }
825 
826  // Set new ref and current status
827  if (!$error) {
828  $this->ref = $numref;
829  $this->statut = self::STATUS_VALIDATED;
830  }
831 
832  if (!$error) {
833  $this->db->commit();
834  return 1;
835  } else {
836  $this->db->rollback();
837  return -1 * $error;
838  }
839  }
840 
841 
842  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
849  public function create_delivery($user)
850  {
851  // phpcs:enable
852  global $conf;
853 
854  if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
855  if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
856  // Expedition validee
857  include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
858  $delivery = new Delivery($this->db);
859  $result = $delivery->create_from_sending($user, $this->id);
860  if ($result > 0) {
861  return $result;
862  } else {
863  $this->error = $delivery->error;
864  return $result;
865  }
866  } else {
867  return 0;
868  }
869  } else {
870  return 0;
871  }
872  }
873 
886  public function addline($entrepot_id, $id, $qty, $array_options = 0)
887  {
888  global $conf, $langs;
889 
890  $num = count($this->lines);
891  $line = new ExpeditionLigne($this->db);
892 
893  $line->entrepot_id = $entrepot_id;
894  $line->origin_line_id = $id;
895  $line->fk_origin_line = $id;
896  $line->qty = $qty;
897 
898  $orderline = new OrderLine($this->db);
899  $orderline->fetch($id);
900 
901  // Copy the rang of the order line to the expedition line
902  $line->rang = $orderline->rang;
903  $line->product_type = $orderline->product_type;
904 
905  if (isModEnabled('stock') && !empty($orderline->fk_product)) {
906  $fk_product = $orderline->fk_product;
907 
908  if (!($entrepot_id > 0) && empty($conf->global->STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS)) {
909  $langs->load("errors");
910  $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
911  return -1;
912  }
913 
914  if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT)) {
915  $product = new Product($this->db);
916  $product->fetch($fk_product);
917 
918  // Check must be done for stock of product into warehouse if $entrepot_id defined
919  if ($entrepot_id > 0) {
920  $product->load_stock('warehouseopen');
921  $product_stock = $product->stock_warehouse[$entrepot_id]->real;
922  } else {
923  $product_stock = $product->stock_reel;
924  }
925 
926  $product_type = $product->type;
927  if ($product_type == 0 || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
928  $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
929  // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
930  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.
931  if ($product_stock < $qty) {
932  $langs->load("errors");
933  $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
934  $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
935 
936  $this->db->rollback();
937  return -3;
938  }
939  }
940  }
941  }
942  }
943 
944  // If product need a batch number, we should not have called this function but addline_batch instead.
945  // If this happen, we may have a bug in card.php page
946  if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
947  $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; //
948  return -4;
949  }
950 
951  // extrafields
952  if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
953  $line->array_options = $array_options;
954  }
955 
956  $this->lines[$num] = $line;
957 
958  return 1;
959  }
960 
961  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
969  public function addline_batch($dbatch, $array_options = 0)
970  {
971  // phpcs:enable
972  global $conf, $langs;
973 
974  $num = count($this->lines);
975  if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
976  $line = new ExpeditionLigne($this->db);
977  $tab = array();
978  foreach ($dbatch['detail'] as $key => $value) {
979  if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
980  // $value['q']=qty to move
981  // $value['id_batch']=id into llx_product_batch of record to move
982  //var_dump($value);
983 
984  $linebatch = new ExpeditionLineBatch($this->db);
985  $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
986  if ($ret < 0) {
987  $this->error = $linebatch->error;
988  return -1;
989  }
990  $linebatch->qty = $value['q'];
991  if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
992  $linebatch->batch = null;
993  }
994  $tab[] = $linebatch;
995 
996  if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
997  require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
998  $prod_batch = new Productbatch($this->db);
999  $prod_batch->fetch($value['id_batch']);
1000 
1001  if ($prod_batch->qty < $linebatch->qty) {
1002  $langs->load("errors");
1003  $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1004  dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1005  $this->db->rollback();
1006  return -1;
1007  }
1008  }
1009 
1010  //var_dump($linebatch);
1011  }
1012  }
1013  $line->entrepot_id = $linebatch->entrepot_id;
1014  $line->origin_line_id = $dbatch['ix_l']; // deprecated
1015  $line->fk_origin_line = $dbatch['ix_l'];
1016  $line->qty = $dbatch['qty'];
1017  $line->detail_batch = $tab;
1018 
1019  // extrafields
1020  if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1021  $line->array_options = $array_options;
1022  }
1023 
1024  //var_dump($line);
1025  $this->lines[$num] = $line;
1026  return 1;
1027  }
1028  }
1029 
1037  public function update($user = null, $notrigger = 0)
1038  {
1039  global $conf;
1040  $error = 0;
1041 
1042  // Clean parameters
1043 
1044  if (isset($this->ref)) {
1045  $this->ref = trim($this->ref);
1046  }
1047  if (isset($this->entity)) {
1048  $this->entity = (int) $this->entity;
1049  }
1050  if (isset($this->ref_customer)) {
1051  $this->ref_customer = trim($this->ref_customer);
1052  }
1053  if (isset($this->socid)) {
1054  $this->socid = (int) $this->socid;
1055  }
1056  if (isset($this->fk_user_author)) {
1057  $this->fk_user_author = (int) $this->fk_user_author;
1058  }
1059  if (isset($this->fk_user_valid)) {
1060  $this->fk_user_valid = (int) $this->fk_user_valid;
1061  }
1062  if (isset($this->fk_delivery_address)) {
1063  $this->fk_delivery_address = (int) $this->fk_delivery_address;
1064  }
1065  if (isset($this->shipping_method_id)) {
1066  $this->shipping_method_id = (int) $this->shipping_method_id;
1067  }
1068  if (isset($this->tracking_number)) {
1069  $this->tracking_number = trim($this->tracking_number);
1070  }
1071  if (isset($this->statut)) {
1072  $this->statut = (int) $this->statut;
1073  }
1074  if (isset($this->trueDepth)) {
1075  $this->trueDepth = trim($this->trueDepth);
1076  }
1077  if (isset($this->trueWidth)) {
1078  $this->trueWidth = trim($this->trueWidth);
1079  }
1080  if (isset($this->trueHeight)) {
1081  $this->trueHeight = trim($this->trueHeight);
1082  }
1083  if (isset($this->size_units)) {
1084  $this->size_units = trim($this->size_units);
1085  }
1086  if (isset($this->weight_units)) {
1087  $this->weight_units = trim($this->weight_units);
1088  }
1089  if (isset($this->trueWeight)) {
1090  $this->weight = trim($this->trueWeight);
1091  }
1092  if (isset($this->note_private)) {
1093  $this->note_private = trim($this->note_private);
1094  }
1095  if (isset($this->note_public)) {
1096  $this->note_public = trim($this->note_public);
1097  }
1098  if (isset($this->model_pdf)) {
1099  $this->model_pdf = trim($this->model_pdf);
1100  }
1101 
1102  // Check parameters
1103  // Put here code to add control on parameters values
1104 
1105  // Update request
1106  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1107  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1108  $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1109  $sql .= " ref_customer=".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1110  $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1111  $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1112  $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1113  $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1114  $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1115  $sql .= " date_expedition=".(dol_strlen($this->date_expedition) != 0 ? "'".$this->db->idate($this->date_expedition)."'" : 'null').",";
1116  $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1117  $sql .= " fk_address=".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1118  $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1119  $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1120  $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1121  $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1122  $sql .= " height=".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1123  $sql .= " width=".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1124  $sql .= " size_units=".(isset($this->size_units) ? $this->size_units : "null").",";
1125  $sql .= " size=".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1126  $sql .= " weight_units=".(isset($this->weight_units) ? $this->weight_units : "null").",";
1127  $sql .= " weight=".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1128  $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1129  $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1130  $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1131  $sql .= " entity=".$conf->entity;
1132  $sql .= " WHERE rowid=".((int) $this->id);
1133 
1134  $this->db->begin();
1135 
1136  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1137  $resql = $this->db->query($sql);
1138  if (!$resql) {
1139  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1140  }
1141 
1142  if (!$error && !$notrigger) {
1143  // Call trigger
1144  $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1145  if ($result < 0) {
1146  $error++;
1147  }
1148  // End call triggers
1149  }
1150 
1151  // Commit or rollback
1152  if ($error) {
1153  foreach ($this->errors as $errmsg) {
1154  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1155  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1156  }
1157  $this->db->rollback();
1158  return -1 * $error;
1159  } else {
1160  $this->db->commit();
1161  return 1;
1162  }
1163  }
1164 
1165 
1173  public function cancel($notrigger = 0, $also_update_stock = false)
1174  {
1175  global $conf, $langs, $user;
1176 
1177  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1178 
1179  $error = 0;
1180  $this->error = '';
1181 
1182  $this->db->begin();
1183 
1184  // Add a protection to refuse deleting if shipment has at least one delivery
1185  $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1186  if (count($this->linkedObjectsIds) > 0) {
1187  $this->error = 'ErrorThereIsSomeDeliveries';
1188  $error++;
1189  }
1190 
1191  if (!$error && !$notrigger) {
1192  // Call trigger
1193  $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1194  if ($result < 0) {
1195  $error++;
1196  }
1197  // End call triggers
1198  }
1199 
1200  // Stock control
1201  if (!$error && isModEnabled('stock') &&
1202  (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
1203  ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1204  require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1205 
1206  $langs->load("agenda");
1207 
1208  // Loop on each product line to add a stock movement and delete features
1209  $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1210  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1211  $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1212  $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1213  $sql .= " AND cd.rowid = ed.fk_origin_line";
1214 
1215  dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1216  $resql = $this->db->query($sql);
1217  if ($resql) {
1218  $cpt = $this->db->num_rows($resql);
1219 
1220  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1221 
1222  for ($i = 0; $i < $cpt; $i++) {
1223  dol_syslog(get_class($this)."::delete movement index ".$i);
1224  $obj = $this->db->fetch_object($resql);
1225 
1226  $mouvS = new MouvementStock($this->db);
1227  // we do not log origin because it will be deleted
1228  $mouvS->origin = null;
1229  // get lot/serial
1230  $lotArray = null;
1231  if (isModEnabled('productbatch')) {
1232  $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1233  if (!is_array($lotArray)) {
1234  $error++;
1235  $this->errors[] = "Error ".$this->db->lasterror();
1236  }
1237  }
1238 
1239  if (empty($lotArray)) {
1240  // no lot/serial
1241  // We increment stock of product (and sub-products)
1242  // We use warehouse selected for each line
1243  $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
1244  if ($result < 0) {
1245  $error++;
1246  $this->errors = array_merge($this->errors, $mouvS->errors);
1247  break;
1248  }
1249  } else {
1250  // We increment stock of batches
1251  // We use warehouse selected for each line
1252  foreach ($lotArray as $lot) {
1253  $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
1254  if ($result < 0) {
1255  $error++;
1256  $this->errors = array_merge($this->errors, $mouvS->errors);
1257  break;
1258  }
1259  }
1260  if ($error) {
1261  break; // break for loop incase of error
1262  }
1263  }
1264  }
1265  } else {
1266  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1267  }
1268  }
1269 
1270  // delete batch expedition line
1271  if (!$error && isModEnabled('productbatch')) {
1272  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1273  if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1274  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1275  }
1276  }
1277 
1278 
1279  if (!$error) {
1280  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1281  $sql .= " WHERE fk_expedition = ".((int) $this->id);
1282 
1283  if ($this->db->query($sql)) {
1284  // Delete linked object
1285  $res = $this->deleteObjectLinked();
1286  if ($res < 0) {
1287  $error++;
1288  }
1289 
1290  // No delete expedition
1291  if (!$error) {
1292  $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1293  $sql .= " WHERE rowid = ".((int) $this->id);
1294 
1295  if ($this->db->query($sql)) {
1296  if (!empty($this->origin) && $this->origin_id > 0) {
1297  $this->fetch_origin();
1298  $origin = $this->origin;
1299  if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1300  // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1301  $this->$origin->loadExpeditions();
1302  //var_dump($this->$origin->expeditions);exit;
1303  if (count($this->$origin->expeditions) <= 0) {
1304  $this->$origin->setStatut(Commande::STATUS_VALIDATED);
1305  }
1306  }
1307  }
1308 
1309  if (!$error) {
1310  $this->db->commit();
1311 
1312  // We delete PDFs
1313  $ref = dol_sanitizeFileName($this->ref);
1314  if (!empty($conf->expedition->dir_output)) {
1315  $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1316  $file = $dir.'/'.$ref.'.pdf';
1317  if (file_exists($file)) {
1318  if (!dol_delete_file($file)) {
1319  return 0;
1320  }
1321  }
1322  if (file_exists($dir)) {
1323  if (!dol_delete_dir_recursive($dir)) {
1324  $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1325  return 0;
1326  }
1327  }
1328  }
1329 
1330  return 1;
1331  } else {
1332  $this->db->rollback();
1333  return -1;
1334  }
1335  } else {
1336  $this->error = $this->db->lasterror()." - sql=$sql";
1337  $this->db->rollback();
1338  return -3;
1339  }
1340  } else {
1341  $this->error = $this->db->lasterror()." - sql=$sql";
1342  $this->db->rollback();
1343  return -2;
1344  }//*/
1345  } else {
1346  $this->error = $this->db->lasterror()." - sql=$sql";
1347  $this->db->rollback();
1348  return -1;
1349  }
1350  } else {
1351  $this->db->rollback();
1352  return -1;
1353  }
1354  }
1355 
1364  public function delete($notrigger = 0, $also_update_stock = false)
1365  {
1366  global $conf, $langs, $user;
1367 
1368  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1369 
1370  $error = 0;
1371  $this->error = '';
1372 
1373  $this->db->begin();
1374 
1375  // Add a protection to refuse deleting if shipment has at least one delivery
1376  $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1377  if (count($this->linkedObjectsIds) > 0) {
1378  $this->error = 'ErrorThereIsSomeDeliveries';
1379  $error++;
1380  }
1381 
1382  if (!$error && !$notrigger) {
1383  // Call trigger
1384  $result = $this->call_trigger('SHIPPING_DELETE', $user);
1385  if ($result < 0) {
1386  $error++;
1387  }
1388  // End call triggers
1389  }
1390 
1391  // Stock control
1392  if (!$error && isModEnabled('stock') &&
1393  (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
1394  ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1395  require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1396 
1397  $langs->load("agenda");
1398 
1399  // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1400  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1401 
1402  // Loop on each product line to add a stock movement
1403  $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1404  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1405  $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1406  $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1407  $sql .= " AND cd.rowid = ed.fk_origin_line";
1408 
1409  dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1410  $resql = $this->db->query($sql);
1411  if ($resql) {
1412  $cpt = $this->db->num_rows($resql);
1413  for ($i = 0; $i < $cpt; $i++) {
1414  dol_syslog(get_class($this)."::delete movement index ".$i);
1415  $obj = $this->db->fetch_object($resql);
1416 
1417  $mouvS = new MouvementStock($this->db);
1418  // we do not log origin because it will be deleted
1419  $mouvS->origin = null;
1420  // get lot/serial
1421  $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1422  if (!is_array($lotArray)) {
1423  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1424  }
1425  if (empty($lotArray)) {
1426  // no lot/serial
1427  // We increment stock of product (and sub-products)
1428  // We use warehouse selected for each line
1429  $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
1430  if ($result < 0) {
1431  $error++;
1432  $this->errors = array_merge($this->errors, $mouvS->errors);
1433  break;
1434  }
1435  } else {
1436  // We increment stock of batches
1437  // We use warehouse selected for each line
1438  foreach ($lotArray as $lot) {
1439  $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
1440  if ($result < 0) {
1441  $error++;
1442  $this->errors = array_merge($this->errors, $mouvS->errors);
1443  break;
1444  }
1445  }
1446  if ($error) {
1447  break; // break for loop incase of error
1448  }
1449  }
1450  }
1451  } else {
1452  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1453  }
1454  }
1455 
1456  // delete batch expedition line
1457  if (!$error) {
1458  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1459  if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1460  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1461  }
1462  }
1463 
1464  if (!$error) {
1465  $main = MAIN_DB_PREFIX.'expeditiondet';
1466  $ef = $main."_extrafields";
1467  $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
1468 
1469  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1470  $sql .= " WHERE fk_expedition = ".((int) $this->id);
1471 
1472  if ($this->db->query($sqlef) && $this->db->query($sql)) {
1473  // Delete linked object
1474  $res = $this->deleteObjectLinked();
1475  if ($res < 0) {
1476  $error++;
1477  }
1478 
1479  // delete extrafields
1480  $res = $this->deleteExtraFields();
1481  if ($res < 0) {
1482  $error++;
1483  }
1484 
1485  if (!$error) {
1486  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1487  $sql .= " WHERE rowid = ".((int) $this->id);
1488 
1489  if ($this->db->query($sql)) {
1490  if (!empty($this->origin) && $this->origin_id > 0) {
1491  $this->fetch_origin();
1492  $origin = $this->origin;
1493  if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1494  // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1495  $this->$origin->loadExpeditions();
1496  //var_dump($this->$origin->expeditions);exit;
1497  if (count($this->$origin->expeditions) <= 0) {
1498  $this->$origin->setStatut(Commande::STATUS_VALIDATED);
1499  }
1500  }
1501  }
1502 
1503  if (!$error) {
1504  $this->db->commit();
1505 
1506  // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1507  $this->deleteEcmFiles();
1508 
1509  // We delete PDFs
1510  $ref = dol_sanitizeFileName($this->ref);
1511  if (!empty($conf->expedition->dir_output)) {
1512  $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1513  $file = $dir.'/'.$ref.'.pdf';
1514  if (file_exists($file)) {
1515  if (!dol_delete_file($file)) {
1516  return 0;
1517  }
1518  }
1519  if (file_exists($dir)) {
1520  if (!dol_delete_dir_recursive($dir)) {
1521  $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1522  return 0;
1523  }
1524  }
1525  }
1526 
1527  return 1;
1528  } else {
1529  $this->db->rollback();
1530  return -1;
1531  }
1532  } else {
1533  $this->error = $this->db->lasterror()." - sql=$sql";
1534  $this->db->rollback();
1535  return -3;
1536  }
1537  } else {
1538  $this->error = $this->db->lasterror()." - sql=$sql";
1539  $this->db->rollback();
1540  return -2;
1541  }
1542  } else {
1543  $this->error = $this->db->lasterror()." - sql=$sql";
1544  $this->db->rollback();
1545  return -1;
1546  }
1547  } else {
1548  $this->db->rollback();
1549  return -1;
1550  }
1551  }
1552 
1553  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1559  public function fetch_lines()
1560  {
1561  // phpcs:enable
1562  global $conf, $mysoc;
1563 
1564  $this->lines = array();
1565 
1566  // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1567  // TODO: See if we can restore a common fetch_lines (one line = one record)
1568 
1569  $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";
1570  $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1571  $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";
1572  $sql .= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc, cd.rang";
1573  $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_origin_line, ed.fk_entrepot";
1574  $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type";
1575  $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";
1576  $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1577  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1578  $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1579  $sql .= " AND ed.fk_origin_line = cd.rowid";
1580  $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.
1581 
1582  dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1583  $resql = $this->db->query($sql);
1584  if ($resql) {
1585  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1586 
1587  $num = $this->db->num_rows($resql);
1588  $i = 0;
1589  $lineindex = 0;
1590  $originline = 0;
1591 
1592  $this->total_ht = 0;
1593  $this->total_tva = 0;
1594  $this->total_ttc = 0;
1595  $this->total_localtax1 = 0;
1596  $this->total_localtax2 = 0;
1597 
1598  $this->multicurrency_total_ht = 0;
1599  $this->multicurrency_total_tva = 0;
1600  $this->multicurrency_total_ttc = 0;
1601 
1602  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1603 
1604  while ($i < $num) {
1605  $obj = $this->db->fetch_object($resql);
1606 
1607 
1608  if ($originline > 0 && $originline == $obj->fk_origin_line) {
1609  $line->entrepot_id = 0; // entrepod_id in details_entrepot
1610  $line->qty_shipped += $obj->qty_shipped;
1611  } else {
1612  $line = new ExpeditionLigne($this->db); // new group to start
1613  $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
1614  $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
1615  }
1616 
1617  $detail_entrepot = new stdClass();
1618  $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1619  $detail_entrepot->qty_shipped = $obj->qty_shipped;
1620  $detail_entrepot->line_id = $obj->line_id;
1621  $line->details_entrepot[] = $detail_entrepot;
1622 
1623  $line->line_id = $obj->line_id;
1624  $line->rowid = $obj->line_id; // TODO deprecated
1625  $line->id = $obj->line_id;
1626 
1627  $line->fk_origin = 'orderline';
1628  $line->fk_origin_line = $obj->fk_origin_line;
1629  $line->origin_line_id = $obj->fk_origin_line; // TODO deprecated
1630 
1631  $line->fk_expedition = $this->id; // id of parent
1632 
1633  $line->product_type = $obj->product_type;
1634  $line->fk_product = $obj->fk_product;
1635  $line->fk_product_type = $obj->fk_product_type;
1636  $line->ref = $obj->product_ref; // TODO deprecated
1637  $line->product_ref = $obj->product_ref;
1638  $line->product_label = $obj->product_label;
1639  $line->libelle = $obj->product_label; // TODO deprecated
1640  $line->product_tosell = $obj->product_tosell;
1641  $line->product_tobuy = $obj->product_tobuy;
1642  $line->product_tobatch = $obj->product_tobatch;
1643  $line->label = $obj->custom_label;
1644  $line->description = $obj->description;
1645  $line->qty_asked = $obj->qty_asked;
1646  $line->rang = $obj->rang;
1647  $line->weight = $obj->weight;
1648  $line->weight_units = $obj->weight_units;
1649  $line->length = $obj->length;
1650  $line->length_units = $obj->length_units;
1651  $line->surface = $obj->surface;
1652  $line->surface_units = $obj->surface_units;
1653  $line->volume = $obj->volume;
1654  $line->volume_units = $obj->volume_units;
1655  $line->fk_unit = $obj->fk_unit;
1656 
1657  $line->pa_ht = $obj->pa_ht;
1658 
1659  // Local taxes
1660  $localtax_array = array(0=>$obj->localtax1_type, 1=>$obj->localtax1_tx, 2=>$obj->localtax2_type, 3=>$obj->localtax2_tx);
1661  $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1662  $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1663 
1664  // For invoicing
1665  $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
1666  $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1667  $line->qty = $line->qty_shipped;
1668  $line->total_ht = $tabprice[0];
1669  $line->total_localtax1 = $tabprice[9];
1670  $line->total_localtax2 = $tabprice[10];
1671  $line->total_ttc = $tabprice[2];
1672  $line->total_tva = $tabprice[1];
1673  $line->vat_src_code = $obj->vat_src_code;
1674  $line->tva_tx = $obj->tva_tx;
1675  $line->localtax1_tx = $obj->localtax1_tx;
1676  $line->localtax2_tx = $obj->localtax2_tx;
1677  $line->info_bits = $obj->info_bits;
1678  $line->price = $obj->price;
1679  $line->subprice = $obj->subprice;
1680  $line->remise_percent = $obj->remise_percent;
1681 
1682  $this->total_ht += $tabprice[0];
1683  $this->total_tva += $tabprice[1];
1684  $this->total_ttc += $tabprice[2];
1685  $this->total_localtax1 += $tabprice[9];
1686  $this->total_localtax2 += $tabprice[10];
1687 
1688  // Multicurrency
1689  $this->fk_multicurrency = $obj->fk_multicurrency;
1690  $this->multicurrency_code = $obj->multicurrency_code;
1691  $line->multicurrency_subprice = $obj->multicurrency_subprice;
1692  $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1693  $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1694  $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1695 
1696  $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
1697  $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
1698  $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
1699 
1700  if ($originline != $obj->fk_origin_line) {
1701  $line->detail_batch = array();
1702  }
1703 
1704  // Detail of batch
1705  if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1706  $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1707 
1708  if (is_array($newdetailbatch)) {
1709  if ($originline != $obj->fk_origin_line) {
1710  $line->detail_batch = $newdetailbatch;
1711  } else {
1712  $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1713  }
1714  }
1715  }
1716 
1717  $line->fetch_optionals();
1718 
1719  if ($originline != $obj->fk_origin_line) {
1720  $this->lines[$lineindex] = $line;
1721  $lineindex++;
1722  } else {
1723  $line->total_ht += $tabprice[0];
1724  $line->total_localtax1 += $tabprice[9];
1725  $line->total_localtax2 += $tabprice[10];
1726  $line->total_ttc += $tabprice[2];
1727  $line->total_tva += $tabprice[1];
1728  }
1729 
1730  $i++;
1731  $originline = $obj->fk_origin_line;
1732  }
1733  $this->db->free($resql);
1734  return 1;
1735  } else {
1736  $this->error = $this->db->error();
1737  return -3;
1738  }
1739  }
1740 
1748  public function deleteline($user, $lineid)
1749  {
1750  global $user;
1751 
1752  if ($this->statut == self::STATUS_DRAFT) {
1753  $this->db->begin();
1754 
1755  $line = new ExpeditionLigne($this->db);
1756 
1757  // For triggers
1758  $line->fetch($lineid);
1759 
1760  if ($line->delete($user) > 0) {
1761  //$this->update_price(1);
1762 
1763  $this->db->commit();
1764  return 1;
1765  } else {
1766  $this->db->rollback();
1767  return -1;
1768  }
1769  } else {
1770  $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1771  return -2;
1772  }
1773  }
1774 
1775 
1783  public function getTooltipContentArray($params)
1784  {
1785  global $conf, $langs;
1786 
1787  $langs->load('sendings');
1788 
1789  $nofetch = !empty($params['nofetch']);
1790 
1791  $datas = array();
1792  $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
1793  if (isset($this->statut)) {
1794  $datas['picto'] .= ' '.$this->getLibStatut(5);
1795  }
1796  $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1797  $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1798  if (!$nofetch) {
1799  $langs->load('companies');
1800  if (empty($this->thirdparty)) {
1801  $this->fetch_thirdparty();
1802  }
1803  $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1804  }
1805 
1806  return $datas;
1807  }
1808 
1820  public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1821  {
1822  global $langs, $conf, $hookmanager;
1823 
1824  $result = '';
1825  $params = [
1826  'id' => $this->id,
1827  'objecttype' => $this->element,
1828  'option' => $option,
1829  'nofetch' => 1,
1830  ];
1831  $classfortooltip = 'classfortooltip';
1832  $dataparams = '';
1833  if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1834  $classfortooltip = 'classforajaxtooltip';
1835  $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1836  $label = '';
1837  } else {
1838  $label = implode($this->getTooltipContentArray($params));
1839  }
1840 
1841  $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1842 
1843  if ($short) {
1844  return $url;
1845  }
1846 
1847  if ($option !== 'nolink') {
1848  // Add param to save lastsearch_values or not
1849  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1850  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1851  $add_save_lastsearch_values = 1;
1852  }
1853  if ($add_save_lastsearch_values) {
1854  $url .= '&save_lastsearch_values=1';
1855  }
1856  }
1857 
1858  $linkclose = '';
1859  if (empty($notooltip)) {
1860  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1861  $label = $langs->trans("Shipment");
1862  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1863  }
1864  $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1865  $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1866  }
1867 
1868  $linkstart = '<a href="'.$url.'"';
1869  $linkstart .= $linkclose.'>';
1870  $linkend = '</a>';
1871 
1872  $result .= $linkstart;
1873  if ($withpicto) {
1874  $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1875  }
1876  if ($withpicto != 2) {
1877  $result .= $this->ref;
1878  }
1879  $result .= $linkend;
1880  global $action;
1881  $hookmanager->initHooks(array($this->element . 'dao'));
1882  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1883  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1884  if ($reshook > 0) {
1885  $result = $hookmanager->resPrint;
1886  } else {
1887  $result .= $hookmanager->resPrint;
1888  }
1889  return $result;
1890  }
1891 
1898  public function getLibStatut($mode = 0)
1899  {
1900  return $this->LibStatut($this->statut, $mode);
1901  }
1902 
1903  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1911  public function LibStatut($status, $mode)
1912  {
1913  // phpcs:enable
1914  global $langs;
1915 
1916  $labelStatus = $langs->transnoentitiesnoconv($this->statuts[$status]);
1917  $labelStatusShort = $langs->transnoentitiesnoconv($this->statuts_short[$status]);
1918 
1919  $statusType = 'status'.$status;
1920  if ($status == self::STATUS_VALIDATED) {
1921  $statusType = 'status4';
1922  }
1923  if ($status == self::STATUS_CLOSED) {
1924  $statusType = 'status6';
1925  }
1926  if ($status == self::STATUS_CANCELED) {
1927  $statusType = 'status9';
1928  }
1929 
1930  return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
1931  }
1932 
1940  public function initAsSpecimen()
1941  {
1942  global $langs;
1943 
1944  $now = dol_now();
1945 
1946  dol_syslog(get_class($this)."::initAsSpecimen");
1947 
1948  $order = new Commande($this->db);
1949  $order->initAsSpecimen();
1950 
1951  // Initialise parametres
1952  $this->id = 0;
1953  $this->ref = 'SPECIMEN';
1954  $this->specimen = 1;
1955  $this->statut = self::STATUS_VALIDATED;
1956  $this->livraison_id = 0;
1957  $this->date = $now;
1958  $this->date_creation = $now;
1959  $this->date_valid = $now;
1960  $this->date_delivery = $now + 24 * 3600;
1961  $this->date_expedition = $now + 24 * 3600;
1962 
1963  $this->entrepot_id = 0;
1964  $this->fk_delivery_address = 0;
1965  $this->socid = 1;
1966 
1967  $this->commande_id = 0;
1968  $this->commande = $order;
1969 
1970  $this->origin_id = 1;
1971  $this->origin = 'commande';
1972 
1973  $this->note_private = 'Private note';
1974  $this->note_public = 'Public note';
1975 
1976  $nbp = 5;
1977  $xnbp = 0;
1978  while ($xnbp < $nbp) {
1979  $line = new ExpeditionLigne($this->db);
1980  $line->desc = $langs->trans("Description")." ".$xnbp;
1981  $line->libelle = $langs->trans("Description")." ".$xnbp; // deprecated
1982  $line->label = $langs->trans("Description")." ".$xnbp;
1983  $line->qty = 10;
1984  $line->qty_asked = 5;
1985  $line->qty_shipped = 4;
1986  $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
1987 
1988  $this->lines[] = $line;
1989  $xnbp++;
1990  }
1991  }
1992 
1993  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2002  public function set_date_livraison($user, $delivery_date)
2003  {
2004  // phpcs:enable
2005  return $this->setDeliveryDate($user, $delivery_date);
2006  }
2007 
2015  public function setDeliveryDate($user, $delivery_date)
2016  {
2017  if ($user->rights->expedition->creer) {
2018  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2019  $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2020  $sql .= " WHERE rowid = ".((int) $this->id);
2021 
2022  dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2023  $resql = $this->db->query($sql);
2024  if ($resql) {
2025  $this->date_delivery = $delivery_date;
2026  return 1;
2027  } else {
2028  $this->error = $this->db->error();
2029  return -1;
2030  }
2031  } else {
2032  return -2;
2033  }
2034  }
2035 
2036  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2042  public function fetch_delivery_methods()
2043  {
2044  // phpcs:enable
2045  global $langs;
2046  $this->meths = array();
2047 
2048  $sql = "SELECT em.rowid, em.code, em.libelle as label";
2049  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2050  $sql .= " WHERE em.active = 1";
2051  $sql .= " ORDER BY em.libelle ASC";
2052 
2053  $resql = $this->db->query($sql);
2054  if ($resql) {
2055  while ($obj = $this->db->fetch_object($resql)) {
2056  $label = $langs->trans('SendingMethod'.$obj->code);
2057  $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2058  }
2059  }
2060  }
2061 
2062  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2069  public function list_delivery_methods($id = '')
2070  {
2071  // phpcs:enable
2072  global $langs;
2073 
2074  $this->listmeths = array();
2075  $i = 0;
2076 
2077  $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2078  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2079  if ($id != '') {
2080  $sql .= " WHERE em.rowid=".((int) $id);
2081  }
2082 
2083  $resql = $this->db->query($sql);
2084  if ($resql) {
2085  while ($obj = $this->db->fetch_object($resql)) {
2086  $this->listmeths[$i]['rowid'] = $obj->rowid;
2087  $this->listmeths[$i]['code'] = $obj->code;
2088  $label = $langs->trans('SendingMethod'.$obj->code);
2089  $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2090  $this->listmeths[$i]['description'] = $obj->description;
2091  $this->listmeths[$i]['tracking'] = $obj->tracking;
2092  $this->listmeths[$i]['active'] = $obj->active;
2093  $i++;
2094  }
2095  }
2096  }
2097 
2104  public function getUrlTrackingStatus($value = '')
2105  {
2106  if (!empty($this->shipping_method_id)) {
2107  $sql = "SELECT em.code, em.tracking";
2108  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2109  $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2110 
2111  $resql = $this->db->query($sql);
2112  if ($resql) {
2113  if ($obj = $this->db->fetch_object($resql)) {
2114  $tracking = $obj->tracking;
2115  }
2116  }
2117  }
2118 
2119  if (!empty($tracking) && !empty($value)) {
2120  $url = str_replace('{TRACKID}', $value, $tracking);
2121  $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">'.($value ? $value : 'url').'</a>', $url, $url);
2122  } else {
2123  $this->tracking_url = $value;
2124  }
2125  }
2126 
2132  public function setClosed()
2133  {
2134  global $conf, $langs, $user;
2135 
2136  $error = 0;
2137 
2138  // Protection. This avoid to move stock later when we should not
2139  if ($this->statut == self::STATUS_CLOSED) {
2140  return 0;
2141  }
2142 
2143  $this->db->begin();
2144 
2145  $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED;
2146  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2147 
2148  $resql = $this->db->query($sql);
2149  if ($resql) {
2150  // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2151  if ($this->origin == 'commande' && $this->origin_id > 0) {
2152  $order = new Commande($this->db);
2153  $order->fetch($this->origin_id);
2154 
2155  $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2156 
2157  $shipments_match_order = 1;
2158  foreach ($order->lines as $line) {
2159  $lineid = $line->id;
2160  $qty = $line->qty;
2161  if (($line->product_type == 0 || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) && $order->expeditions[$lineid] != $qty) {
2162  $shipments_match_order = 0;
2163  $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';
2164  dol_syslog($text);
2165  break;
2166  }
2167  }
2168  if ($shipments_match_order) {
2169  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');
2170  // We close the order
2171  $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2172  }
2173  }
2174 
2175  $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2176  $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2177 
2178  // If stock increment is done on closing
2179  if (!$error && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
2180  $result = $this->manageStockMvtOnEvt($user);
2181  if ($result<0) {
2182  $error++;
2183  }
2184  }
2185 
2186  // Call trigger
2187  if (!$error) {
2188  $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2189  if ($result < 0) {
2190  $error++;
2191  }
2192  }
2193  } else {
2194  dol_print_error($this->db);
2195  $error++;
2196  }
2197 
2198  if (!$error) {
2199  $this->db->commit();
2200  return 1;
2201  } else {
2202  $this->statut = self::STATUS_VALIDATED;
2203  $this->status = self::STATUS_VALIDATED;
2204 
2205  $this->db->rollback();
2206  return -1;
2207  }
2208  }
2209 
2219  private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2220  {
2221  global $langs;
2222 
2223  $error=0;
2224 
2225  require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2226 
2227  $langs->load("agenda");
2228 
2229  // Loop on each product line to add a stock movement
2230  $sql = "SELECT cd.fk_product, cd.subprice,";
2231  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2232  $sql .= " e.ref,";
2233  $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2234  $sql .= " cd.rowid as cdid, ed.rowid as edid";
2235  $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2236  $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2237  $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2238  $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2239  $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2240  $sql .= " AND cd.rowid = ed.fk_origin_line";
2241 
2242  dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2243  $resql = $this->db->query($sql);
2244  if ($resql) {
2245  $cpt = $this->db->num_rows($resql);
2246  for ($i = 0; $i < $cpt; $i++) {
2247  $obj = $this->db->fetch_object($resql);
2248  if (empty($obj->edbrowid)) {
2249  $qty = $obj->qty;
2250  } else {
2251  $qty = $obj->edbqty;
2252  }
2253  if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2254  continue;
2255  }
2256  dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2257 
2258  $mouvS = new MouvementStock($this->db);
2259  $mouvS->origin = &$this;
2260  $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2261 
2262  if (empty($obj->edbrowid)) {
2263  // line without batch detail
2264 
2265  // 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
2266  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
2267  if ($result < 0) {
2268  $this->error = $mouvS->error;
2269  $this->errors = $mouvS->errors;
2270  $error++;
2271  break;
2272  }
2273  } else {
2274  // line with batch detail
2275 
2276  // 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
2277  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
2278  if ($result < 0) {
2279  $this->error = $mouvS->error;
2280  $this->errors = $mouvS->errors;
2281  $error++;
2282  break;
2283  }
2284  }
2285 
2286  // 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
2287  // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2288  $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)";
2289  $resqldelete = $this->db->query($sqldelete);
2290  // We do not test error, it can fails if there is child in batch details
2291  }
2292  } else {
2293  $this->error = $this->db->lasterror();
2294  $this->errors[] = $this->db->lasterror();
2295  $error ++;
2296  }
2297 
2298  return $error;
2299  }
2300 
2306  public function setBilled()
2307  {
2308  global $user;
2309  $error = 0;
2310 
2311  $this->db->begin();
2312 
2313  $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut=2, billed=1'; // TODO Update only billed
2314  $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2315 
2316  $resql = $this->db->query($sql);
2317  if ($resql) {
2318  $this->statut = self::STATUS_CLOSED;
2319  $this->billed = 1;
2320 
2321  // Call trigger
2322  $result = $this->call_trigger('SHIPPING_BILLED', $user);
2323  if ($result < 0) {
2324  $error++;
2325  }
2326  } else {
2327  $error++;
2328  $this->errors[] = $this->db->lasterror;
2329  }
2330 
2331  if (empty($error)) {
2332  $this->db->commit();
2333  return 1;
2334  } else {
2335  $this->statut = self::STATUS_VALIDATED;
2336  $this->billed = 0;
2337  $this->db->rollback();
2338  return -1;
2339  }
2340  }
2341 
2349  public function setDraft($user, $notrigger = 0)
2350  {
2351  // Protection
2352  if ($this->statut <= self::STATUS_DRAFT) {
2353  return 0;
2354  }
2355 
2356  return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2357  }
2358 
2364  public function reOpen()
2365  {
2366  global $conf, $langs, $user;
2367 
2368  $error = 0;
2369 
2370  // Protection. This avoid to move stock later when we should not
2371  if ($this->statut == self::STATUS_VALIDATED) {
2372  return 0;
2373  }
2374 
2375  $this->db->begin();
2376 
2377  $oldbilled = $this->billed;
2378 
2379  $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2380  $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2381 
2382  $resql = $this->db->query($sql);
2383  if ($resql) {
2384  $this->statut = self::STATUS_VALIDATED;
2385  $this->billed = 0;
2386 
2387  // If stock increment is done on closing
2388  if (!$error && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
2389  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2390 
2391  $langs->load("agenda");
2392 
2393  // Loop on each product line to add a stock movement
2394  // TODO possibilite d'expedier a partir d'une propale ou autre origine
2395  $sql = "SELECT cd.fk_product, cd.subprice,";
2396  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2397  $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2398  $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2399  $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2400  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2401  $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2402  $sql .= " AND cd.rowid = ed.fk_origin_line";
2403 
2404  dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2405  $resql = $this->db->query($sql);
2406  if ($resql) {
2407  $cpt = $this->db->num_rows($resql);
2408  for ($i = 0; $i < $cpt; $i++) {
2409  $obj = $this->db->fetch_object($resql);
2410  if (empty($obj->edbrowid)) {
2411  $qty = $obj->qty;
2412  } else {
2413  $qty = $obj->edbqty;
2414  }
2415  if ($qty <= 0) {
2416  continue;
2417  }
2418  dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2419 
2420  //var_dump($this->lines[$i]);
2421  $mouvS = new MouvementStock($this->db);
2422  $mouvS->origin = &$this;
2423  $mouvS->setOrigin($this->element, $this->id);
2424 
2425  if (empty($obj->edbrowid)) {
2426  // line without batch detail
2427 
2428  // 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
2429  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2430  if ($result < 0) {
2431  $this->error = $mouvS->error;
2432  $this->errors = $mouvS->errors;
2433  $error++;
2434  break;
2435  }
2436  } else {
2437  // line with batch detail
2438 
2439  // 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
2440  $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);
2441  if ($result < 0) {
2442  $this->error = $mouvS->error;
2443  $this->errors = $mouvS->errors;
2444  $error++;
2445  break;
2446  }
2447  }
2448  }
2449  } else {
2450  $this->error = $this->db->lasterror();
2451  $error++;
2452  }
2453  }
2454 
2455  if (!$error) {
2456  // Call trigger
2457  $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2458  if ($result < 0) {
2459  $error++;
2460  }
2461  }
2462  } else {
2463  $error++;
2464  $this->errors[] = $this->db->lasterror();
2465  }
2466 
2467  if (!$error) {
2468  $this->db->commit();
2469  return 1;
2470  } else {
2471  $this->statut = self::STATUS_CLOSED;
2472  $this->billed = $oldbilled;
2473  $this->db->rollback();
2474  return -1;
2475  }
2476  }
2477 
2489  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2490  {
2491  global $conf;
2492 
2493  $outputlangs->load("products");
2494 
2495  if (!dol_strlen($modele)) {
2496  $modele = 'rouget';
2497 
2498  if (!empty($this->model_pdf)) {
2499  $modele = $this->model_pdf;
2500  } elseif (!empty($this->modelpdf)) { // deprecated
2501  $modele = $this->modelpdf;
2502  } elseif (!empty($conf->global->EXPEDITION_ADDON_PDF)) {
2503  $modele = $conf->global->EXPEDITION_ADDON_PDF;
2504  }
2505  }
2506 
2507  $modelpath = "core/modules/expedition/doc/";
2508 
2509  $this->fetch_origin();
2510 
2511  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2512  }
2513 
2522  public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2523  {
2524  $tables = array(
2525  'expedition'
2526  );
2527 
2528  return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2529  }
2530 }
2531 
2532 
2537 {
2541  public $element = 'expeditiondet';
2542 
2546  public $table_element = 'expeditiondet';
2547 
2548 
2555  public $line_id; // deprecated
2556 
2562 
2568  public $fk_origin; // Example: 'orderline'
2569 
2573  public $fk_origin_line;
2574 
2578  public $fk_expedition;
2579 
2583  public $db;
2584 
2588  public $qty;
2589 
2593  public $qty_shipped;
2594 
2598  public $fk_product;
2599 
2600  // detail of lot and qty = array(id in llx_expeditiondet_batch, fk_expeditiondet, batch, qty, fk_origin_stock)
2601  // We can use this to know warehouse planned to be used for each lot.
2602  public $detail_batch;
2603 
2604  // detail of warehouses and qty
2605  // We can use this to know warehouse when there is no lot.
2606  public $details_entrepot;
2607 
2608 
2612  public $entrepot_id;
2613 
2614 
2618  public $qty_asked;
2619 
2624  public $ref;
2625 
2629  public $product_ref;
2630 
2635  public $libelle;
2636 
2640  public $product_label;
2641 
2647  public $desc;
2648 
2652  public $product_desc;
2653 
2658  public $product_type = 0;
2659 
2663  public $rang;
2664 
2668  public $weight;
2669  public $weight_units;
2670 
2674  public $length;
2675  public $length_units;
2676 
2680  public $surface;
2681  public $surface_units;
2682 
2686  public $volume;
2687  public $volume_units;
2688 
2689  // Invoicing
2690  public $remise_percent;
2691  public $tva_tx;
2692 
2696  public $total_ht;
2697 
2701  public $total_ttc;
2702 
2706  public $total_tva;
2707 
2711  public $total_localtax1;
2712 
2716  public $total_localtax2;
2717 
2718 
2724  public function __construct($db)
2725  {
2726  $this->db = $db;
2727  }
2728 
2735  public function fetch($rowid)
2736  {
2737  $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_origin_line, ed.qty, ed.rang';
2738  $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
2739  $sql .= ' WHERE ed.rowid = '.((int) $rowid);
2740  $result = $this->db->query($sql);
2741  if ($result) {
2742  $objp = $this->db->fetch_object($result);
2743  $this->id = $objp->rowid;
2744  $this->fk_expedition = $objp->fk_expedition;
2745  $this->entrepot_id = $objp->fk_entrepot;
2746  $this->fk_origin_line = $objp->fk_origin_line;
2747  $this->qty = $objp->qty;
2748  $this->rang = $objp->rang;
2749 
2750  $this->db->free($result);
2751 
2752  return 1;
2753  } else {
2754  $this->errors[] = $this->db->lasterror();
2755  $this->error = $this->db->lasterror();
2756  return -1;
2757  }
2758  }
2759 
2767  public function insert($user, $notrigger = 0)
2768  {
2769  global $langs, $conf;
2770 
2771  $error = 0;
2772 
2773  // Check parameters
2774  if (empty($this->fk_expedition) || empty($this->fk_origin_line) || !is_numeric($this->qty)) {
2775  $this->error = 'ErrorMandatoryParametersNotProvided';
2776  return -1;
2777  }
2778 
2779  $this->db->begin();
2780 
2781  if (empty($this->rang)) {
2782  $this->rang = 0;
2783  }
2784 
2785  // Rank to use
2786  $ranktouse = $this->rang;
2787  if ($ranktouse == -1) {
2788  $rangmax = $this->line_max($this->fk_expedition);
2789  $ranktouse = $rangmax + 1;
2790  }
2791 
2792  $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
2793  $sql .= "fk_expedition";
2794  $sql .= ", fk_entrepot";
2795  $sql .= ", fk_origin_line";
2796  $sql .= ", qty";
2797  $sql .= ", rang";
2798  $sql .= ") VALUES (";
2799  $sql .= $this->fk_expedition;
2800  $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
2801  $sql .= ", ".((int) $this->fk_origin_line);
2802  $sql .= ", ".price2num($this->qty, 'MS');
2803  $sql .= ", ".((int) $ranktouse);
2804  $sql .= ")";
2805 
2806  dol_syslog(get_class($this)."::insert", LOG_DEBUG);
2807  $resql = $this->db->query($sql);
2808  if ($resql) {
2809  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
2810 
2811  if (!$error) {
2812  $result = $this->insertExtraFields();
2813  if ($result < 0) {
2814  $error++;
2815  }
2816  }
2817 
2818  if (!$error && !$notrigger) {
2819  // Call trigger
2820  $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
2821  if ($result < 0) {
2822  $error++;
2823  }
2824  // End call triggers
2825  }
2826 
2827  if ($error) {
2828  foreach ($this->errors as $errmsg) {
2829  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2830  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2831  }
2832  }
2833  } else {
2834  $error++;
2835  }
2836 
2837  if ($error) {
2838  $this->db->rollback();
2839  return -1;
2840  } else {
2841  $this->db->commit();
2842  return $this->id;
2843  }
2844  }
2845 
2853  public function delete($user = null, $notrigger = 0)
2854  {
2855  $error = 0;
2856 
2857  $this->db->begin();
2858 
2859  // delete batch expedition line
2860  if (isModEnabled('productbatch')) {
2861  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
2862  $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
2863 
2864  if (!$this->db->query($sql)) {
2865  $this->errors[] = $this->db->lasterror()." - sql=$sql";
2866  $error++;
2867  }
2868  }
2869 
2870  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
2871  $sql .= " WHERE rowid = ".((int) $this->id);
2872 
2873  if (!$error && $this->db->query($sql)) {
2874  // Remove extrafields
2875  if (!$error) {
2876  $result = $this->deleteExtraFields();
2877  if ($result < 0) {
2878  $this->errors[] = $this->error;
2879  $error++;
2880  }
2881  }
2882  if (!$error && !$notrigger) {
2883  // Call trigger
2884  $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
2885  if ($result < 0) {
2886  $this->errors[] = $this->error;
2887  $error++;
2888  }
2889  // End call triggers
2890  }
2891  } else {
2892  $this->errors[] = $this->db->lasterror()." - sql=$sql";
2893  $error++;
2894  }
2895 
2896  if (!$error) {
2897  $this->db->commit();
2898  return 1;
2899  } else {
2900  foreach ($this->errors as $errmsg) {
2901  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2902  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2903  }
2904  $this->db->rollback();
2905  return -1 * $error;
2906  }
2907  }
2908 
2916  public function update($user = null, $notrigger = 0)
2917  {
2918  $error = 0;
2919 
2920  dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
2921 
2922  $this->db->begin();
2923 
2924  // Clean parameters
2925  if (empty($this->qty)) {
2926  $this->qty = 0;
2927  }
2928  $qty = price2num($this->qty);
2929  $remainingQty = 0;
2930  $batch = null;
2931  $batch_id = null;
2932  $expedition_batch_id = null;
2933  if (is_array($this->detail_batch)) { // array of ExpeditionLineBatch
2934  if (count($this->detail_batch) > 1) {
2935  dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
2936  $this->errors[] = 'ErrorBadParameters';
2937  $error++;
2938  } else {
2939  $batch = $this->detail_batch[0]->batch;
2940  $batch_id = $this->detail_batch[0]->fk_origin_stock;
2941  $expedition_batch_id = $this->detail_batch[0]->id;
2942  if ($this->entrepot_id != $this->detail_batch[0]->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[0]->qty);
2948  }
2949  } elseif (!empty($this->detail_batch)) {
2950  $batch = $this->detail_batch->batch;
2951  $batch_id = $this->detail_batch->fk_origin_stock;
2952  $expedition_batch_id = $this->detail_batch->id;
2953  if ($this->entrepot_id != $this->detail_batch->entrepot_id) {
2954  dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
2955  $this->errors[] = 'ErrorBadParameters';
2956  $error++;
2957  }
2958  $qty = price2num($this->detail_batch->qty);
2959  }
2960 
2961  // check parameters
2962  if (!isset($this->id) || !isset($this->entrepot_id)) {
2963  dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
2964  $this->errors[] = 'ErrorMandatoryParametersNotProvided';
2965  $error++;
2966  return -1;
2967  }
2968 
2969  // update lot
2970 
2971  if (!empty($batch) && isModEnabled('productbatch')) {
2972  dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id, batch=$batch");
2973 
2974  if (empty($batch_id) || empty($this->fk_product)) {
2975  dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
2976  $this->errors[] = 'ErrorMandatoryParametersNotProvided';
2977  $error++;
2978  }
2979 
2980  // fetch remaining lot qty
2981  $shipmentlinebatch = new ExpeditionLineBatch($this->db);
2982 
2983  if (!$error && ($lotArray = $shipmentlinebatch->fetchAll($this->id)) < 0) {
2984  $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
2985  $error++;
2986  } else {
2987  // caculate new total line qty
2988  foreach ($lotArray as $lot) {
2989  if ($expedition_batch_id != $lot->id) {
2990  $remainingQty += $lot->qty;
2991  }
2992  }
2993  $qty += $remainingQty;
2994 
2995  //fetch lot details
2996 
2997  // fetch from product_lot
2998  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
2999  $lot = new Productlot($this->db);
3000  if ($lot->fetch(0, $this->fk_product, $batch) < 0) {
3001  $this->errors[] = $lot->errors;
3002  $error++;
3003  }
3004  if (!$error && !empty($expedition_batch_id)) {
3005  // delete lot expedition line
3006  $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
3007  $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
3008  $sql .= " AND rowid = ".((int) $expedition_batch_id);
3009 
3010  if (!$this->db->query($sql)) {
3011  $this->errors[] = $this->db->lasterror()." - sql=$sql";
3012  $error++;
3013  }
3014  }
3015  if (!$error && $this->detail_batch->qty > 0) {
3016  // create lot expedition line
3017  if (isset($lot->id)) {
3018  $shipmentLot = new ExpeditionLineBatch($this->db);
3019  $shipmentLot->batch = $lot->batch;
3020  $shipmentLot->eatby = $lot->eatby;
3021  $shipmentLot->sellby = $lot->sellby;
3022  $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id;
3023  $shipmentLot->qty = $this->detail_batch->qty;
3024  $shipmentLot->fk_origin_stock = $batch_id;
3025  if ($shipmentLot->create($this->id) < 0) {
3026  $this->errors = $shipmentLot->errors;
3027  $error++;
3028  }
3029  }
3030  }
3031  }
3032  }
3033  if (!$error) {
3034  // update line
3035  $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3036  $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
3037  $sql .= " , qty = ".((float) price2num($qty, 'MS'));
3038  $sql .= " WHERE rowid = ".((int) $this->id);
3039 
3040  if (!$this->db->query($sql)) {
3041  $this->errors[] = $this->db->lasterror()." - sql=$sql";
3042  $error++;
3043  }
3044  }
3045 
3046  if (!$error) {
3047  if (!$error) {
3048  $result = $this->insertExtraFields();
3049  if ($result < 0) {
3050  $this->errors[] = $this->error;
3051  $error++;
3052  }
3053  }
3054  }
3055 
3056  if (!$error && !$notrigger) {
3057  // Call trigger
3058  $result = $this->call_trigger('LINESHIPPING_MODIFY', $user);
3059  if ($result < 0) {
3060  $this->errors[] = $this->error;
3061  $error++;
3062  }
3063  // End call triggers
3064  }
3065  if (!$error) {
3066  $this->db->commit();
3067  return 1;
3068  } else {
3069  foreach ($this->errors as $errmsg) {
3070  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3071  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3072  }
3073  $this->db->rollback();
3074  return -1 * $error;
3075  }
3076  }
3077 }
$object ref
Definition: info.php:78
Class to manage customers orders.
const STATUS_SHIPMENTONPROCESS
Shipment on process.
const STATUS_VALIDATED
Validated status.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
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...
deleteEcmFiles($mode=0)
Delete related files of object in database.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid='', $f_user=null, $notrigger=0)
Delete all links between an object $this.
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='fk_statut')
Set status of an object.
deleteExtraFields()
Delete all extra fields values for the current object.
setStatusCommon($user, $status, $notrigger=0, $triggercode='')
Set to a status.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
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).
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
fetch_origin()
Read linked origin object.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
call_trigger($triggerName, $user)
Call trigger based on this instance.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage receptions.
Class to manage Dolibarr database access.
Class to manage shipments.
getNomUrl($withpicto=0, $option='', $max=0, $short=0, $notooltip=0, $save_lastsearch_value=-1)
Return clicable link of object (with eventually picto)
create_delivery($user)
Create a delivery receipt from a shipment.
deleteline($user, $lineid)
Delete detail line.
setDraft($user, $notrigger=0)
Set draft status.
getUrlTrackingStatus($value='')
Forge an set tracking url.
setClosed()
Classify the shipping as closed.
__construct($db)
Constructor.
fetch_lines()
Load lines.
list_delivery_methods($id='')
Fetch all deliveries method and return an array.
create($user, $notrigger=0)
Create expedition en base.
LibStatut($status, $mode)
Return label of a status.
setBilled()
Classify the shipping as invoiced (used when WORKFLOW_BILL_ON_SHIPMENT is on)
addline_batch($dbatch, $array_options=0)
Add a shipment line with batch record.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
getTooltipContentArray($params)
getTooltipContentArray
setDeliveryDate($user, $delivery_date)
Set the planned delivery date.
const STATUS_DRAFT
Draft status.
const STATUS_CANCELED
Canceled status.
getLibStatut($mode=0)
Return status label.
set_date_livraison($user, $delivery_date)
Set delivery date.
initAsSpecimen()
Initialise an instance with random values.
getNextNumRef($soc)
Return next expedition ref.
const STATUS_CLOSED
Closed status.
const STATUS_VALIDATED
Validated status.
manageStockMvtOnEvt($user, $labelmovement='ShipmentClassifyClosedInDolibarr')
Manage Stock MVt onb Close or valid Shipment.
create_line_batch($line_ext, $array_options=0)
Create the detail of the expedition line.
valid($user, $notrigger=0)
Validate object and update stock if option enabled.
update($user=null, $notrigger=0)
Update database.
cancel($notrigger=0, $also_update_stock=false)
Cancel shipment.
fetch_delivery_methods()
Fetch deliveries method and return an array.
addline($entrepot_id, $id, $qty, $array_options=0)
Add an expedition line.
fetch($id, $ref='', $ref_ext='', $notused='')
Get object and lines from database.
reOpen()
Classify the shipping as validated/opened.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
create_line($entrepot_id, $origin_line_id, $qty, $rang=0, $array_options=null)
Create a expedition line.
Classe to manage lines of shipment.
fetch($rowid)
Load line expedition.
__construct($db)
Constructor.
insert($user, $notrigger=0)
Insert line into database.
update($user=null, $notrigger=0)
Update a line in database.
CRUD class for batch number management within shipment.
Class to manage stock movements.
Class to manage order lines.
Class to manage products or services.
Manage record for batch number management.
Class with list of lots and properties.
Class to manage third parties objects (customers, suppliers, prospects...)
trait CommonIncoterm
Superclass for incoterm classes.
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_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:1507
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:1356
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
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
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
$conf db user
Definition: repair.php:124