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