dolibarr 20.0.0
expedition.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003-2008 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
5 * Copyright (C) 2006-2012 Laurent Destailleur <eldy@users.sourceforge.net>
6 * Copyright (C) 2011-2020 Juanjo Menent <jmenent@2byte.es>
7 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
8 * Copyright (C) 2014 Cedric GROSS <c.gross@kreiz-it.fr>
9 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
10 * Copyright (C) 2014-2017 Francis Appels <francis.appels@yahoo.com>
11 * Copyright (C) 2015 Claudio Aschieri <c.aschieri@19.coop>
12 * Copyright (C) 2016-2024 Ferran Marcet <fmarcet@2byte.es>
13 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2018-2024 Frédéric France <frederic.france@free.fr>
15 * Copyright (C) 2020 Lenin Rivas <lenin@leninrivas.com>
16 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 3 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program. If not, see <https://www.gnu.org/licenses/>.
30 */
31
38require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
39require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
40require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
41if (isModEnabled("propal")) {
42 require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
43}
44if (isModEnabled('order')) {
45 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
46}
47require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionlinebatch.class.php';
48
49
54{
56
60 public $element = "shipping";
61
65 public $fk_element = "fk_expedition";
66
70 public $table_element = "expedition";
71
75 public $table_element_line = "expeditiondet";
76
80 public $picto = 'dolly';
81
82
86 public $fields = array();
87
91 public $user_author_id;
92
96 public $fk_user_author;
97
98 public $socid;
99
105 public $ref_client;
106
110 public $ref_customer;
111
115 public $entrepot_id;
116
120 public $tracking_number;
121
125 public $tracking_url;
126 public $billed;
127
131 public $model_pdf;
132
133 public $trueWeight;
134 public $weight_units;
135 public $trueWidth;
136 public $width_units;
137 public $trueHeight;
138 public $height_units;
139 public $trueDepth;
140 public $depth_units;
141 // A denormalized value
142 public $trueSize;
143
144 public $livraison_id;
145
149 public $multicurrency_subprice;
150
151 public $size_units;
152
153 public $sizeH;
154
155 public $sizeS;
156
157 public $sizeW;
158
159 public $weight;
160
164 public $date_delivery;
165
170 public $date;
171
177
182 public $date_shipping;
183
187 public $date_creation;
188
192 public $date_valid;
193
194 public $meths;
195 public $listmeths; // List of carriers
196
200 public $commande_id;
201
205 public $commande;
206
210 public $lines = array();
211
212 // Multicurrency
216 public $fk_multicurrency;
217
221 public $multicurrency_code;
222 public $multicurrency_tx;
223 public $multicurrency_total_ht;
224 public $multicurrency_total_tva;
225 public $multicurrency_total_ttc;
226
230 public $signed_status = 0;
231
235 const STATUS_DRAFT = 0;
236
244
251 const STATUS_CLOSED = 2;
252
256 const STATUS_CANCELED = -1;
257
266
267
272
276 const STATUS_SIGNED = 1;
277
278
279
285 public function __construct($db)
286 {
287 global $conf;
288
289 $this->db = $db;
290
291 $this->ismultientitymanaged = 1;
292 $this->isextrafieldmanaged = 1;
293
294 // List of long language codes for status
295 $this->labelStatus = array();
296 $this->labelStatus[-1] = 'StatusSendingCanceled';
297 $this->labelStatus[0] = 'StatusSendingDraft';
298 $this->labelStatus[1] = 'StatusSendingValidated';
299 $this->labelStatus[2] = 'StatusSendingProcessed';
300
301 // List of short language codes for status
302 $this->labelStatusShort = array();
303 $this->labelStatusShort[-1] = 'StatusSendingCanceledShort';
304 $this->labelStatusShort[0] = 'StatusSendingDraftShort';
305 $this->labelStatusShort[1] = 'StatusSendingValidatedShort';
306 $this->labelStatusShort[2] = 'StatusSendingProcessedShort';
307 }
308
315 public function getNextNumRef($soc)
316 {
317 global $langs, $conf;
318 $langs->load("sendings");
319
320 if (getDolGlobalString('EXPEDITION_ADDON_NUMBER')) {
321 $mybool = false;
322
323 $file = getDolGlobalString('EXPEDITION_ADDON_NUMBER') . ".php";
324 $classname = getDolGlobalString('EXPEDITION_ADDON_NUMBER');
325
326 // Include file with class
327 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
328
329 foreach ($dirmodels as $reldir) {
330 $dir = dol_buildpath($reldir."core/modules/expedition/");
331
332 // Load file with numbering class (if found)
333 $mybool = ((bool) @include_once $dir.$file) || $mybool;
334 }
335
336 if (!$mybool) {
337 dol_print_error(null, "Failed to include file ".$file);
338 return '';
339 }
340
341 $obj = new $classname();
342 $numref = "";
343 $numref = $obj->getNextValue($soc, $this);
344
345 if ($numref != "") {
346 return $numref;
347 } else {
348 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
349 return "";
350 }
351 } else {
352 print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
353 return "";
354 }
355 }
356
364 public function create($user, $notrigger = 0)
365 {
366 global $conf, $hookmanager;
367
368 $now = dol_now();
369
370 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
371 $error = 0;
372
373 // Clean parameters
374 $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
375 if (empty($this->fk_project)) {
376 $this->fk_project = 0;
377 }
378 if (empty($this->date_shipping) && !empty($this->date_expedition)) {
379 $this->date_shipping = $this->date_expedition;
380 }
381
382 $this->user = $user;
383
384 $this->db->begin();
385
386 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
387 $sql .= "ref";
388 $sql .= ", entity";
389 $sql .= ", ref_customer";
390 $sql .= ", ref_ext";
391 $sql .= ", date_creation";
392 $sql .= ", fk_user_author";
393 $sql .= ", date_expedition";
394 $sql .= ", date_delivery";
395 $sql .= ", fk_soc";
396 $sql .= ", fk_projet";
397 $sql .= ", fk_address";
398 $sql .= ", fk_shipping_method";
399 $sql .= ", tracking_number";
400 $sql .= ", weight";
401 $sql .= ", size";
402 $sql .= ", width";
403 $sql .= ", height";
404 $sql .= ", weight_units";
405 $sql .= ", size_units";
406 $sql .= ", note_private";
407 $sql .= ", note_public";
408 $sql .= ", model_pdf";
409 $sql .= ", fk_incoterms, location_incoterms";
410 $sql .= ") VALUES (";
411 $sql .= "'(PROV)'";
412 $sql .= ", ".((int) $conf->entity);
413 $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
414 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
415 $sql .= ", '".$this->db->idate($now)."'";
416 $sql .= ", ".((int) $user->id);
417 $sql .= ", ".($this->date_shipping > 0 ? "'".$this->db->idate($this->date_shipping)."'" : "null");
418 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
419 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
420 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
421 $sql .= ", ".($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
422 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
423 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
424 $sql .= ", ".(is_numeric($this->weight) ? $this->weight : 'NULL');
425 $sql .= ", ".(is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
426 $sql .= ", ".(is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
427 $sql .= ", ".(is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
428 $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
429 $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
430 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
431 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
432 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
433 $sql .= ", ".(int) $this->fk_incoterms;
434 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
435 $sql .= ")";
436
437 dol_syslog(get_class($this)."::create", LOG_DEBUG);
438 $resql = $this->db->query($sql);
439 if ($resql) {
440 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
441
442 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
443 $sql .= " SET ref = '(PROV".$this->id.")'";
444 $sql .= " WHERE rowid = ".((int) $this->id);
445
446 dol_syslog(get_class($this)."::create", LOG_DEBUG);
447 if ($this->db->query($sql)) {
448 // Insert of lines
449 $num = count($this->lines);
450 for ($i = 0; $i < $num; $i++) {
451 if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
452 if (!isset($this->lines[$i]->detail_batch)) { // no batch management
453 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) {
454 $error++;
455 }
456 } else { // with batch management
457 if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
458 $error++;
459 }
460 }
461 }
462 }
463
464 if (!$error && $this->id && $this->origin_id) {
465 $ret = $this->add_object_linked();
466 if (!$ret) {
467 $error++;
468 }
469 }
470
471 // Actions on extra fields
472 if (!$error) {
473 $result = $this->insertExtraFields();
474 if ($result < 0) {
475 $error++;
476 }
477 }
478
479 if (!$error && !$notrigger) {
480 // Call trigger
481 $result = $this->call_trigger('SHIPPING_CREATE', $user);
482 if ($result < 0) {
483 $error++;
484 }
485 // End call triggers
486
487 if (!$error) {
488 $this->db->commit();
489 return $this->id;
490 } else {
491 foreach ($this->errors as $errmsg) {
492 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
493 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
494 }
495 $this->db->rollback();
496 return -1 * $error;
497 }
498 } else {
499 $error++;
500 $this->db->rollback();
501 return -3;
502 }
503 } else {
504 $error++;
505 $this->error = $this->db->lasterror()." - sql=$sql";
506 $this->db->rollback();
507 return -2;
508 }
509 } else {
510 $error++;
511 $this->error = $this->db->error()." - sql=$sql";
512 $this->db->rollback();
513 return -1;
514 }
515 }
516
517 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
528 public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [])
529 {
530 //phpcs:enable
531 global $user;
532
533 $expeditionline = new ExpeditionLigne($this->db);
534 $expeditionline->fk_expedition = $this->id;
535 $expeditionline->entrepot_id = $entrepot_id;
536 $expeditionline->fk_elementdet = $origin_line_id;
537 $expeditionline->element_type = $this->origin;
538 $expeditionline->qty = $qty;
539 $expeditionline->rang = $rang;
540 $expeditionline->array_options = $array_options;
541
542 if (($lineId = $expeditionline->insert($user)) < 0) {
543 $this->errors[] = $expeditionline->error;
544 }
545 return $lineId;
546 }
547
548
549 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
557 public function create_line_batch($line_ext, $array_options = [])
558 {
559 // phpcs:enable
560 $error = 0;
561 $stockLocationQty = array(); // associated array with batch qty in stock location
562
563 $tab = $line_ext->detail_batch;
564 // create stockLocation Qty array
565 foreach ($tab as $detbatch) {
566 if (!empty($detbatch->entrepot_id)) {
567 if (empty($stockLocationQty[$detbatch->entrepot_id])) {
568 $stockLocationQty[$detbatch->entrepot_id] = 0;
569 }
570 $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
571 }
572 }
573 // create shipment lines
574 foreach ($stockLocationQty as $stockLocation => $qty) {
575 $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
576 if ($line_id < 0) {
577 $error++;
578 } else {
579 // create shipment batch lines for stockLocation
580 foreach ($tab as $detbatch) {
581 if ($detbatch->entrepot_id == $stockLocation) {
582 if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch
583 $this->errors = $detbatch->errors;
584 $error++;
585 }
586 }
587 }
588 }
589 }
590
591 if (!$error) {
592 return 1;
593 } else {
594 return -1;
595 }
596 }
597
607 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
608 {
609 global $conf;
610
611 // Check parameters
612 if (empty($id) && empty($ref) && empty($ref_ext)) {
613 return -1;
614 }
615
616 $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";
617 $sql .= ", e.date_valid";
618 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
619 $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
620 $sql .= ", e.fk_shipping_method, e.tracking_number";
621 $sql .= ", e.note_private, e.note_public";
622 $sql .= ', e.fk_incoterms, e.location_incoterms';
623 $sql .= ', e.signed_status';
624 $sql .= ', i.libelle as label_incoterms';
625 $sql .= ', s.libelle as shipping_method';
626 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin_type";
627 $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
628 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
629 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
630 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
631 $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
632 if ($id) {
633 $sql .= " AND e.rowid = ".((int) $id);
634 }
635 if ($ref) {
636 $sql .= " AND e.ref='".$this->db->escape($ref)."'";
637 }
638 if ($ref_ext) {
639 $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
640 }
641
642 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
643 $result = $this->db->query($sql);
644 if ($result) {
645 if ($this->db->num_rows($result)) {
646 $obj = $this->db->fetch_object($result);
647
648 $this->id = $obj->rowid;
649 $this->entity = $obj->entity;
650 $this->ref = $obj->ref;
651 $this->socid = $obj->socid;
652 $this->ref_customer = $obj->ref_customer;
653 $this->ref_ext = $obj->ref_ext;
654 $this->status = $obj->fk_statut;
655 $this->statut = $this->status; // Deprecated
656 $this->user_author_id = $obj->fk_user_author;
657 $this->fk_user_author = $obj->fk_user_author;
658 $this->date_creation = $this->db->jdate($obj->date_creation);
659 $this->date_valid = $this->db->jdate($obj->date_valid);
660 $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
661 $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
662 $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
663 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
664 $this->fk_delivery_address = $obj->fk_address;
665 $this->model_pdf = $obj->model_pdf;
666 $this->shipping_method_id = $obj->fk_shipping_method;
667 $this->shipping_method = $obj->shipping_method;
668 $this->tracking_number = $obj->tracking_number;
669 $this->origin = ($obj->origin_type ? $obj->origin_type : 'commande'); // For compatibility
670 $this->origin_type = ($obj->origin_type ? $obj->origin_type : 'commande');
671 $this->origin_id = $obj->origin_id;
672 $this->billed = $obj->billed;
673 $this->fk_project = $obj->fk_project;
674 $this->signed_status = $obj->signed_status;
675 $this->trueWeight = $obj->weight;
676 $this->weight_units = $obj->weight_units;
677
678 $this->trueWidth = $obj->width;
679 $this->width_units = $obj->size_units;
680 $this->trueHeight = $obj->height;
681 $this->height_units = $obj->size_units;
682 $this->trueDepth = $obj->size;
683 $this->depth_units = $obj->size_units;
684
685 $this->note_public = $obj->note_public;
686 $this->note_private = $obj->note_private;
687
688 // A denormalized value
689 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
690 $this->size_units = $obj->size_units;
691
692 //Incoterms
693 $this->fk_incoterms = $obj->fk_incoterms;
694 $this->location_incoterms = $obj->location_incoterms;
695 $this->label_incoterms = $obj->label_incoterms;
696
697 $this->db->free($result);
698
699 // Tracking url
700 $this->getUrlTrackingStatus($obj->tracking_number);
701
702 // Thirdparty
703 $result = $this->fetch_thirdparty(); // TODO Remove this
704
705 // Retrieve extrafields
706 $this->fetch_optionals();
707
708 // Fix Get multicurrency param for transmitted
709 if (isModEnabled('multicurrency')) {
710 if (!empty($this->multicurrency_code)) {
711 $this->multicurrency_code = $this->thirdparty->multicurrency_code;
712 }
713 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($this->thirdparty->multicurrency_tx)) {
714 $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
715 }
716 }
717
718 /*
719 * Lines
720 */
721 $result = $this->fetch_lines();
722 if ($result < 0) {
723 return -3;
724 }
725
726 return 1;
727 } else {
728 dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
729 $this->error = 'Shipment with id '.$id.' not found';
730 return 0;
731 }
732 } else {
733 $this->error = $this->db->error();
734 return -1;
735 }
736 }
737
745 public function valid($user, $notrigger = 0)
746 {
747 global $conf;
748
749 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
750
751 dol_syslog(get_class($this)."::valid");
752
753 // Protection
754 if ($this->status) {
755 dol_syslog(get_class($this)."::valid not in draft status", LOG_WARNING);
756 return 0;
757 }
758
759 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'creer'))
760 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'shipping_advance', 'validate')))) {
761 $this->error = 'Permission denied';
762 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
763 return -1;
764 }
765
766 $this->db->begin();
767
768 $error = 0;
769
770 // Define new ref
771 $soc = new Societe($this->db);
772 $soc->fetch($this->socid);
773
774 // Class of company linked to order
775 $result = $soc->setAsCustomer();
776
777 // Define new ref
778 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
779 $numref = $this->getNextNumRef($soc);
780 } elseif (!empty($this->ref)) {
781 $numref = $this->ref;
782 } else {
783 $numref = "EXP".$this->id;
784 }
785 $this->newref = dol_sanitizeFileName($numref);
786
787 $now = dol_now();
788
789 // Validate
790 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
791 $sql .= " ref='".$this->db->escape($numref)."'";
792 $sql .= ", fk_statut = 1";
793 $sql .= ", date_valid = '".$this->db->idate($now)."'";
794 $sql .= ", fk_user_valid = ".$user->id;
795 $sql .= " WHERE rowid = ".((int) $this->id);
796
797 dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
798 $resql = $this->db->query($sql);
799 if (!$resql) {
800 $this->error = $this->db->lasterror();
801 $error++;
802 }
803
804 // If stock increment is done on sending (recommended choice)
805 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
806 $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
807 if ($result < 0) {
808 return -2;
809 }
810 }
811
812 // Change status of order to "shipment in process"
813 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
814 if (!$ret) {
815 $error++;
816 }
817
818 if (!$error && !$notrigger) {
819 // Call trigger
820 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
821 if ($result < 0) {
822 $error++;
823 }
824 // End call triggers
825 }
826
827 if (!$error) {
828 $this->oldref = $this->ref;
829
830 // Rename directory if dir was a temporary ref
831 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
832 // Now we rename also files into index
833 $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)."'";
834 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
835 $resql = $this->db->query($sql);
836 if (!$resql) {
837 $error++;
838 $this->error = $this->db->lasterror();
839 }
840 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
841 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
842 $resql = $this->db->query($sql);
843 if (!$resql) {
844 $error++;
845 $this->error = $this->db->lasterror();
846 }
847
848 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
849 $oldref = dol_sanitizeFileName($this->ref);
850 $newref = dol_sanitizeFileName($numref);
851 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
852 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
853 if (!$error && file_exists($dirsource)) {
854 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
855
856 if (@rename($dirsource, $dirdest)) {
857 dol_syslog("Rename ok");
858 // Rename docs starting with $oldref with $newref
859 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
860 foreach ($listoffiles as $fileentry) {
861 $dirsource = $fileentry['name'];
862 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
863 $dirsource = $fileentry['path'].'/'.$dirsource;
864 $dirdest = $fileentry['path'].'/'.$dirdest;
865 @rename($dirsource, $dirdest);
866 }
867 }
868 }
869 }
870 }
871
872 // Set new ref and current status
873 if (!$error) {
874 $this->ref = $numref;
877 }
878
879 if (!$error) {
880 $this->db->commit();
881 return 1;
882 } else {
883 $this->db->rollback();
884 return -1 * $error;
885 }
886 }
887
888
889 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
896 public function create_delivery($user)
897 {
898 // phpcs:enable
899 global $conf;
900
901 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
902 if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
903 // Expedition validee
904 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
905 $delivery = new Delivery($this->db);
906 $result = $delivery->create_from_sending($user, $this->id);
907 if ($result > 0) {
908 return $result;
909 } else {
910 $this->error = $delivery->error;
911 return $result;
912 }
913 } else {
914 return 0;
915 }
916 } else {
917 return 0;
918 }
919 }
920
933 public function addline($entrepot_id, $id, $qty, $array_options = [])
934 {
935 global $conf, $langs;
936
937 $num = count($this->lines);
938 $line = new ExpeditionLigne($this->db);
939
940 $line->entrepot_id = $entrepot_id;
941 $line->origin_line_id = $id;
942 $line->fk_elementdet = $id;
943 $line->element_type = 'order';
944 $line->qty = $qty;
945
946 $orderline = new OrderLine($this->db);
947 $orderline->fetch($id);
948
949 // Copy the rang of the order line to the expedition line
950 $line->rang = $orderline->rang;
951 $line->product_type = $orderline->product_type;
952
953 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
954 $fk_product = $orderline->fk_product;
955
956 if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS') && !(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') && $line->product_type == Product::TYPE_SERVICE)) {
957 $langs->load("errors");
958 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
959 return -1;
960 }
961
962 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
963 $product = new Product($this->db);
964 $product->fetch($fk_product);
965
966 // Check must be done for stock of product into warehouse if $entrepot_id defined
967 if ($entrepot_id > 0) {
968 $product->load_stock('warehouseopen');
969 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
970 } else {
971 $product_stock = $product->stock_reel;
972 }
973
974 $product_type = $product->type;
975 if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
976 $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
977 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
978 if (!$isavirtualproduct || !getDolGlobalString('PRODUIT_SOUSPRODUITS') || ($isavirtualproduct && !getDolGlobalString('STOCK_EXCLUDE_VIRTUAL_PRODUCTS'))) { // If STOCK_EXCLUDE_VIRTUAL_PRODUCTS is set, we do not manage stock for kits/virtual products.
979 if ($product_stock < $qty) {
980 $langs->load("errors");
981 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
982 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
983
984 $this->db->rollback();
985 return -3;
986 }
987 }
988 }
989 }
990 }
991
992 // If product need a batch number, we should not have called this function but addline_batch instead.
993 // If this happen, we may have a bug in card.php page
994 if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
995 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; //
996 return -4;
997 }
998
999 // extrafields
1000 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1001 $line->array_options = $array_options;
1002 }
1003
1004 $this->lines[$num] = $line;
1005
1006 return 1;
1007 }
1008
1009 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1017 public function addline_batch($dbatch, $array_options = [])
1018 {
1019 // phpcs:enable
1020 global $conf, $langs;
1021
1022 $num = count($this->lines);
1023 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1024 $line = new ExpeditionLigne($this->db);
1025 $tab = array();
1026 foreach ($dbatch['detail'] as $key => $value) {
1027 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1028 // $value['q']=qty to move
1029 // $value['id_batch']=id into llx_product_batch of record to move
1030 //var_dump($value);
1031
1032 $linebatch = new ExpeditionLineBatch($this->db);
1033 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1034 if ($ret < 0) {
1035 $this->setErrorsFromObject($linebatch);
1036 return -1;
1037 }
1038 $linebatch->qty = $value['q'];
1039 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1040 $linebatch->batch = null;
1041 }
1042 $tab[] = $linebatch;
1043
1044 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1045 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1046 $prod_batch = new Productbatch($this->db);
1047 $prod_batch->fetch($value['id_batch']);
1048
1049 if ($prod_batch->qty < $linebatch->qty) {
1050 $langs->load("errors");
1051 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1052 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1053 $this->db->rollback();
1054 return -1;
1055 }
1056 }
1057
1058 //var_dump($linebatch);
1059 }
1060 }
1061 $line->entrepot_id = $linebatch->entrepot_id;
1062 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1063 $line->fk_elementdet = $dbatch['ix_l'];
1064 $line->qty = $dbatch['qty'];
1065 $line->detail_batch = $tab;
1066
1067 // extrafields
1068 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1069 $line->array_options = $array_options;
1070 }
1071
1072 //var_dump($line);
1073 $this->lines[$num] = $line;
1074 return 1;
1075 }
1076 return 0;
1077 }
1078
1086 public function update($user = null, $notrigger = 0)
1087 {
1088 global $conf;
1089 $error = 0;
1090
1091 // Clean parameters
1092
1093 if (isset($this->ref)) {
1094 $this->ref = trim($this->ref);
1095 }
1096 if (isset($this->entity)) {
1097 $this->entity = (int) $this->entity;
1098 }
1099 if (isset($this->ref_customer)) {
1100 $this->ref_customer = trim($this->ref_customer);
1101 }
1102 if (isset($this->socid)) {
1103 $this->socid = (int) $this->socid;
1104 }
1105 if (isset($this->fk_user_author)) {
1106 $this->fk_user_author = (int) $this->fk_user_author;
1107 }
1108 if (isset($this->fk_user_valid)) {
1109 $this->fk_user_valid = (int) $this->fk_user_valid;
1110 }
1111 if (isset($this->fk_delivery_address)) {
1112 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1113 }
1114 if (isset($this->shipping_method_id)) {
1115 $this->shipping_method_id = (int) $this->shipping_method_id;
1116 }
1117 if (isset($this->tracking_number)) {
1118 $this->tracking_number = trim($this->tracking_number);
1119 }
1120 if (isset($this->statut)) {
1121 $this->statut = (int) $this->statut;
1122 }
1123 if (isset($this->trueDepth)) {
1124 $this->trueDepth = trim($this->trueDepth);
1125 }
1126 if (isset($this->trueWidth)) {
1127 $this->trueWidth = trim($this->trueWidth);
1128 }
1129 if (isset($this->trueHeight)) {
1130 $this->trueHeight = trim($this->trueHeight);
1131 }
1132 if (isset($this->size_units)) {
1133 $this->size_units = trim($this->size_units);
1134 }
1135 if (isset($this->weight_units)) {
1136 $this->weight_units = trim($this->weight_units);
1137 }
1138 if (isset($this->trueWeight)) {
1139 $this->weight = trim((string) $this->trueWeight);
1140 }
1141 if (isset($this->note_private)) {
1142 $this->note_private = trim($this->note_private);
1143 }
1144 if (isset($this->note_public)) {
1145 $this->note_public = trim($this->note_public);
1146 }
1147 if (isset($this->model_pdf)) {
1148 $this->model_pdf = trim($this->model_pdf);
1149 }
1150
1151 // Check parameters
1152 // Put here code to add control on parameters values
1153
1154 // Update request
1155 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1156 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1157 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1158 $sql .= " ref_customer=".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1159 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1160 $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1161 $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1162 $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1163 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1164 $sql .= " date_expedition=".(dol_strlen($this->date_expedition) != 0 ? "'".$this->db->idate($this->date_expedition)."'" : 'null').",";
1165 $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1166 $sql .= " fk_address=".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1167 $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1168 $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1169 $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1170 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1171 $sql .= " height=".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1172 $sql .= " width=".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1173 $sql .= " size_units=".(isset($this->size_units) ? $this->size_units : "null").",";
1174 $sql .= " size=".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1175 $sql .= " weight_units=".(isset($this->weight_units) ? $this->weight_units : "null").",";
1176 $sql .= " weight=".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1177 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1178 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1179 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1180 $sql .= " entity=".$conf->entity;
1181 $sql .= " WHERE rowid=".((int) $this->id);
1182
1183 $this->db->begin();
1184
1185 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1186 $resql = $this->db->query($sql);
1187 if (!$resql) {
1188 $error++;
1189 $this->errors[] = "Error ".$this->db->lasterror();
1190 }
1191
1192 if (!$error && !$notrigger) {
1193 // Call trigger
1194 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1195 if ($result < 0) {
1196 $error++;
1197 }
1198 // End call triggers
1199 }
1200
1201 // Commit or rollback
1202 if ($error) {
1203 foreach ($this->errors as $errmsg) {
1204 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1205 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1206 }
1207 $this->db->rollback();
1208 return -1 * $error;
1209 } else {
1210 $this->db->commit();
1211 return 1;
1212 }
1213 }
1214
1215
1223 public function cancel($notrigger = 0, $also_update_stock = false)
1224 {
1225 global $conf, $langs, $user;
1226
1227 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1228
1229 $error = 0;
1230 $this->error = '';
1231
1232 $this->db->begin();
1233
1234 // Add a protection to refuse deleting if shipment has at least one delivery
1235 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1236 if (count($this->linkedObjectsIds) > 0) {
1237 $this->error = 'ErrorThereIsSomeDeliveries';
1238 $error++;
1239 }
1240
1241 if (!$error && !$notrigger) {
1242 // Call trigger
1243 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1244 if ($result < 0) {
1245 $error++;
1246 }
1247 // End call triggers
1248 }
1249
1250 // Stock control
1251 if (!$error && isModEnabled('stock') &&
1252 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1253 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1254 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1255
1256 $langs->load("agenda");
1257
1258 // Loop on each product line to add a stock movement and delete features
1259 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1260 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1261 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1262 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1263 $sql .= " AND cd.rowid = ed.fk_elementdet";
1264
1265 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1266 $resql = $this->db->query($sql);
1267 if ($resql) {
1268 $cpt = $this->db->num_rows($resql);
1269
1270 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1271
1272 for ($i = 0; $i < $cpt; $i++) {
1273 dol_syslog(get_class($this)."::delete movement index ".$i);
1274 $obj = $this->db->fetch_object($resql);
1275
1276 $mouvS = new MouvementStock($this->db);
1277 // we do not log origin because it will be deleted
1278 $mouvS->origin = '';
1279 // get lot/serial
1280 $lotArray = null;
1281 if (isModEnabled('productbatch')) {
1282 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1283 if (!is_array($lotArray)) {
1284 $error++;
1285 $this->errors[] = "Error ".$this->db->lasterror();
1286 }
1287 }
1288
1289 if (empty($lotArray)) {
1290 // no lot/serial
1291 // We increment stock of product (and sub-products)
1292 // We use warehouse selected for each line
1293 $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
1294 if ($result < 0) {
1295 $error++;
1296 $this->errors = array_merge($this->errors, $mouvS->errors);
1297 break;
1298 }
1299 } else {
1300 // We increment stock of batches
1301 // We use warehouse selected for each line
1302 foreach ($lotArray as $lot) {
1303 $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
1304 if ($result < 0) {
1305 $error++;
1306 $this->errors = array_merge($this->errors, $mouvS->errors);
1307 break;
1308 }
1309 }
1310 if ($error) {
1311 break; // break for loop in case of error
1312 }
1313 }
1314 }
1315 } else {
1316 $error++;
1317 $this->errors[] = "Error ".$this->db->lasterror();
1318 }
1319 }
1320
1321 // delete batch expedition line
1322 if (!$error && isModEnabled('productbatch')) {
1323 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1324 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1325 $error++;
1326 $this->errors[] = "Error ".$this->db->lasterror();
1327 }
1328 }
1329
1330
1331 if (!$error) {
1332 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1333 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1334
1335 if ($this->db->query($sql)) {
1336 // Delete linked object
1337 $res = $this->deleteObjectLinked();
1338 if ($res < 0) {
1339 $error++;
1340 }
1341
1342 // No delete expedition
1343 if (!$error) {
1344 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1345 $sql .= " WHERE rowid = ".((int) $this->id);
1346
1347 if ($this->db->query($sql)) {
1348 if (!empty($this->origin) && $this->origin_id > 0) {
1349 $this->fetch_origin();
1350 if ($this->origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1351 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1352 $this->origin_object->loadExpeditions();
1353 //var_dump($this->$origin->expeditions);exit;
1354 if (count($this->origin_object->expeditions) <= 0) {
1355 $this->origin_object->setStatut(Commande::STATUS_VALIDATED);
1356 }
1357 }
1358 }
1359
1360 if (!$error) {
1361 $this->db->commit();
1362
1363 // We delete PDFs
1364 $ref = dol_sanitizeFileName($this->ref);
1365 if (!empty($conf->expedition->dir_output)) {
1366 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1367 $file = $dir.'/'.$ref.'.pdf';
1368 if (file_exists($file)) {
1369 if (!dol_delete_file($file)) {
1370 return 0;
1371 }
1372 }
1373 if (file_exists($dir)) {
1374 if (!dol_delete_dir_recursive($dir)) {
1375 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1376 return 0;
1377 }
1378 }
1379 }
1380
1381 return 1;
1382 } else {
1383 $this->db->rollback();
1384 return -1;
1385 }
1386 } else {
1387 $this->error = $this->db->lasterror()." - sql=$sql";
1388 $this->db->rollback();
1389 return -3;
1390 }
1391 } else {
1392 $this->error = $this->db->lasterror()." - sql=$sql";
1393 $this->db->rollback();
1394 return -2;
1395 }//*/
1396 } else {
1397 $this->error = $this->db->lasterror()." - sql=$sql";
1398 $this->db->rollback();
1399 return -1;
1400 }
1401 } else {
1402 $this->db->rollback();
1403 return -1;
1404 }
1405 }
1406
1416 public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1417 {
1418 global $conf, $langs;
1419
1420 if (empty($user)) {
1421 global $user;
1422 }
1423
1424 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1425
1426 $error = 0;
1427 $this->error = '';
1428
1429 $this->db->begin();
1430
1431 // Add a protection to refuse deleting if shipment has at least one delivery
1432 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1433 if (count($this->linkedObjectsIds) > 0) {
1434 $this->error = 'ErrorThereIsSomeDeliveries';
1435 $error++;
1436 }
1437
1438 if (!$error && !$notrigger) {
1439 // Call trigger
1440 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1441 if ($result < 0) {
1442 $error++;
1443 }
1444 // End call triggers
1445 }
1446
1447 // Stock control
1448 if (!$error && isModEnabled('stock') &&
1449 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1450 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1451 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1452
1453 $langs->load("agenda");
1454
1455 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1456 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1457
1458 // Loop on each product line to add a stock movement
1459 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1460 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1461 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1462 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1463 $sql .= " AND cd.rowid = ed.fk_elementdet";
1464
1465 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1466 $resql = $this->db->query($sql);
1467 if ($resql) {
1468 $cpt = $this->db->num_rows($resql);
1469 for ($i = 0; $i < $cpt; $i++) {
1470 dol_syslog(get_class($this)."::delete movement index ".$i);
1471 $obj = $this->db->fetch_object($resql);
1472
1473 $mouvS = new MouvementStock($this->db);
1474 // we do not log origin because it will be deleted
1475 $mouvS->origin = '';
1476 // get lot/serial
1477 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1478 if (!is_array($lotArray)) {
1479 $error++;
1480 $this->errors[] = "Error ".$this->db->lasterror();
1481 }
1482 if (empty($lotArray)) {
1483 // no lot/serial
1484 // We increment stock of product (and sub-products)
1485 // We use warehouse selected for each line
1486 $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
1487 if ($result < 0) {
1488 $error++;
1489 $this->errors = array_merge($this->errors, $mouvS->errors);
1490 break;
1491 }
1492 } else {
1493 // We increment stock of batches
1494 // We use warehouse selected for each line
1495 foreach ($lotArray as $lot) {
1496 $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
1497 if ($result < 0) {
1498 $error++;
1499 $this->errors = array_merge($this->errors, $mouvS->errors);
1500 break;
1501 }
1502 }
1503 if ($error) {
1504 break; // break for loop in case of error
1505 }
1506 }
1507 }
1508 } else {
1509 $error++;
1510 $this->errors[] = "Error ".$this->db->lasterror();
1511 }
1512 }
1513
1514 // delete batch expedition line
1515 if (!$error) {
1516 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1517 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1518 $error++;
1519 $this->errors[] = "Error ".$this->db->lasterror();
1520 }
1521 }
1522
1523 if (!$error) {
1524 $main = MAIN_DB_PREFIX.'expeditiondet';
1525 $ef = $main."_extrafields";
1526 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
1527
1528 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1529 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1530
1531 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1532 // Delete linked object
1533 $res = $this->deleteObjectLinked();
1534 if ($res < 0) {
1535 $error++;
1536 }
1537
1538 // delete extrafields
1539 $res = $this->deleteExtraFields();
1540 if ($res < 0) {
1541 $error++;
1542 }
1543
1544 if (!$error) {
1545 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1546 $sql .= " WHERE rowid = ".((int) $this->id);
1547
1548 if ($this->db->query($sql)) {
1549 if (!empty($this->origin) && $this->origin_id > 0) {
1550 $this->fetch_origin();
1551 if ($this->origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1552 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1553 $this->origin_object->loadExpeditions();
1554 //var_dump($this->$origin->expeditions);exit;
1555 if (count($this->origin_object->expeditions) <= 0) {
1556 $this->origin_object->setStatut(Commande::STATUS_VALIDATED);
1557 }
1558 }
1559 }
1560
1561 if (!$error) {
1562 $this->db->commit();
1563
1564 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1565 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1566 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1567
1568 // We delete PDFs
1569 $ref = dol_sanitizeFileName($this->ref);
1570 if (!empty($conf->expedition->dir_output)) {
1571 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1572 $file = $dir.'/'.$ref.'.pdf';
1573 if (file_exists($file)) {
1574 if (!dol_delete_file($file)) {
1575 return 0;
1576 }
1577 }
1578 if (file_exists($dir)) {
1579 if (!dol_delete_dir_recursive($dir)) {
1580 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1581 return 0;
1582 }
1583 }
1584 }
1585
1586 return 1;
1587 } else {
1588 $this->db->rollback();
1589 return -1;
1590 }
1591 } else {
1592 $this->error = $this->db->lasterror()." - sql=$sql";
1593 $this->db->rollback();
1594 return -3;
1595 }
1596 } else {
1597 $this->error = $this->db->lasterror()." - sql=$sql";
1598 $this->db->rollback();
1599 return -2;
1600 }
1601 } else {
1602 $this->error = $this->db->lasterror()." - sql=$sql";
1603 $this->db->rollback();
1604 return -1;
1605 }
1606 } else {
1607 $this->db->rollback();
1608 return -1;
1609 }
1610 }
1611
1612 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1618 public function fetch_lines()
1619 {
1620 // phpcs:enable
1621 global $mysoc;
1622
1623 $this->lines = array();
1624
1625 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1626 // TODO: See if we can restore a common fetch_lines (one line = one record)
1627
1628 $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";
1629 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1630 $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
1631 $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";
1632 $sql .= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc, cd.rang, cd.date_start, cd.date_end";
1633 $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot";
1634 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
1635 $sql .= ", p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_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";
1636 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1637 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1638 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1639 $sql .= " AND ed.fk_elementdet = cd.rowid";
1640 $sql .= " ORDER BY cd.rang, ed.fk_elementdet"; // We need after a break on fk_elementdet but when there is no break on fk_elementdet, cd.rang is same so we can add it as first order criteria.
1641
1642 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1643 $resql = $this->db->query($sql);
1644 if ($resql) {
1645 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1646
1647 $num = $this->db->num_rows($resql);
1648 $i = 0;
1649 $lineindex = 0;
1650 $originline = 0;
1651
1652 $this->total_ht = 0;
1653 $this->total_tva = 0;
1654 $this->total_ttc = 0;
1655 $this->total_localtax1 = 0;
1656 $this->total_localtax2 = 0;
1657
1658 $this->multicurrency_total_ht = 0;
1659 $this->multicurrency_total_tva = 0;
1660 $this->multicurrency_total_ttc = 0;
1661
1662 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1663
1664 while ($i < $num) {
1665 $obj = $this->db->fetch_object($resql);
1666
1667
1668 if ($originline > 0 && $originline == $obj->fk_elementdet) {
1669 '@phan-var-force ExpeditionLigne $line'; // $line from previous loop
1670 $line->entrepot_id = 0; // entrepod_id in details_entrepot
1671 $line->qty_shipped += $obj->qty_shipped;
1672 } else {
1673 $line = new ExpeditionLigne($this->db); // new group to start
1674 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
1675 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
1676 }
1677
1678 $detail_entrepot = new stdClass();
1679 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1680 $detail_entrepot->qty_shipped = $obj->qty_shipped;
1681 $detail_entrepot->line_id = $obj->line_id;
1682 $line->details_entrepot[] = $detail_entrepot;
1683
1684 $line->line_id = $obj->line_id; // TODO deprecated
1685 $line->rowid = $obj->line_id; // TODO deprecated
1686 $line->id = $obj->line_id;
1687
1688 $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
1689
1690 $line->fk_element = $obj->fk_element;
1691 $line->origin_id = $obj->fk_element;
1692 $line->fk_elementdet = $obj->fk_elementdet;
1693 $line->origin_line_id = $obj->fk_elementdet;
1694 $line->element_type = $obj->element_type;
1695
1696 $line->fk_expedition = $this->id; // id of parent
1697
1698 $line->product_type = $obj->product_type;
1699 $line->fk_product = $obj->fk_product;
1700 $line->fk_product_type = $obj->fk_product_type;
1701 $line->ref = $obj->product_ref; // TODO deprecated
1702 $line->product_ref = $obj->product_ref;
1703 $line->product_label = $obj->product_label;
1704 $line->libelle = $obj->product_label; // TODO deprecated
1705 $line->product_barcode = $obj->product_barcode; // Barcode number product
1706 $line->product_tosell = $obj->product_tosell;
1707 $line->product_tobuy = $obj->product_tobuy;
1708 $line->product_tobatch = $obj->product_tobatch;
1709 $line->fk_fournprice = $obj->fk_fournprice;
1710 $line->label = $obj->custom_label;
1711 $line->description = $obj->description;
1712 $line->qty_asked = $obj->qty_asked;
1713 $line->rang = $obj->rang;
1714 $line->weight = $obj->weight;
1715 $line->weight_units = $obj->weight_units;
1716 $line->length = $obj->length;
1717 $line->length_units = $obj->length_units;
1718 $line->width = $obj->width;
1719 $line->width_units = $obj->width_units;
1720 $line->height = $obj->height;
1721 $line->height_units = $obj->height_units;
1722 $line->surface = $obj->surface;
1723 $line->surface_units = $obj->surface_units;
1724 $line->volume = $obj->volume;
1725 $line->volume_units = $obj->volume_units;
1726 $line->fk_unit = $obj->fk_unit;
1727
1728 $line->pa_ht = $obj->pa_ht;
1729
1730 // Local taxes
1731 $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
1732 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1733 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1734
1735 // For invoicing
1736 $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
1737 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1738 $line->qty = $line->qty_shipped;
1739 $line->total_ht = $tabprice[0];
1740 $line->total_localtax1 = $tabprice[9];
1741 $line->total_localtax2 = $tabprice[10];
1742 $line->total_ttc = $tabprice[2];
1743 $line->total_tva = $tabprice[1];
1744 $line->vat_src_code = $obj->vat_src_code;
1745 $line->tva_tx = $obj->tva_tx;
1746 $line->localtax1_tx = $obj->localtax1_tx;
1747 $line->localtax2_tx = $obj->localtax2_tx;
1748 $line->info_bits = $obj->info_bits;
1749 $line->price = $obj->price;
1750 $line->subprice = $obj->subprice;
1751 $line->fk_remise_except = $obj->fk_remise_except;
1752 $line->remise_percent = $obj->remise_percent;
1753
1754 $this->total_ht += $tabprice[0];
1755 $this->total_tva += $tabprice[1];
1756 $this->total_ttc += $tabprice[2];
1757 $this->total_localtax1 += $tabprice[9];
1758 $this->total_localtax2 += $tabprice[10];
1759
1760 $line->date_start = $this->db->jdate($obj->date_start);
1761 $line->date_end = $this->db->jdate($obj->date_end);
1762
1763 // Multicurrency
1764 $this->fk_multicurrency = $obj->fk_multicurrency;
1765 $this->multicurrency_code = $obj->multicurrency_code;
1766 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1767 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1768 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1769 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1770
1771 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
1772 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
1773 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
1774
1775 if ($originline != $obj->fk_elementdet) {
1776 $line->detail_batch = array();
1777 }
1778
1779 // Detail of batch
1780 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1781 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1782
1783 if (is_array($newdetailbatch)) {
1784 if ($originline != $obj->fk_elementdet) {
1785 $line->detail_batch = $newdetailbatch;
1786 } else {
1787 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1788 }
1789 }
1790 }
1791
1792 $line->fetch_optionals();
1793
1794 if ($originline != $obj->fk_elementdet) {
1795 $this->lines[$lineindex] = $line;
1796 $lineindex++;
1797 } else {
1798 $line->total_ht += $tabprice[0];
1799 $line->total_localtax1 += $tabprice[9];
1800 $line->total_localtax2 += $tabprice[10];
1801 $line->total_ttc += $tabprice[2];
1802 $line->total_tva += $tabprice[1];
1803 }
1804
1805 $i++;
1806 $originline = $obj->fk_elementdet;
1807 }
1808 $this->db->free($resql);
1809 return 1;
1810 } else {
1811 $this->error = $this->db->error();
1812 return -3;
1813 }
1814 }
1815
1823 public function deleteLine($user, $lineid)
1824 {
1825 global $user;
1826
1827 if ($this->statut == self::STATUS_DRAFT) {
1828 $this->db->begin();
1829
1830 $line = new ExpeditionLigne($this->db);
1831
1832 // For triggers
1833 $line->fetch($lineid);
1834
1835 if ($line->delete($user) > 0) {
1836 //$this->update_price(1);
1837
1838 $this->db->commit();
1839 return 1;
1840 } else {
1841 $this->db->rollback();
1842 return -1;
1843 }
1844 } else {
1845 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1846 return -2;
1847 }
1848 }
1849
1850
1858 public function getTooltipContentArray($params)
1859 {
1860 global $conf, $langs;
1861
1862 $langs->load('sendings');
1863
1864 $nofetch = !empty($params['nofetch']);
1865
1866 $datas = array();
1867 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
1868 if (isset($this->statut)) {
1869 $datas['picto'] .= ' '.$this->getLibStatut(5);
1870 }
1871 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1872 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1873 if (!$nofetch) {
1874 $langs->load('companies');
1875 if (empty($this->thirdparty)) {
1876 $this->fetch_thirdparty();
1877 }
1878 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1879 }
1880
1881 return $datas;
1882 }
1883
1895 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1896 {
1897 global $langs, $hookmanager;
1898
1899 $result = '';
1900 $params = [
1901 'id' => $this->id,
1902 'objecttype' => $this->element,
1903 'option' => $option,
1904 'nofetch' => 1,
1905 ];
1906 $classfortooltip = 'classfortooltip';
1907 $dataparams = '';
1908 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1909 $classfortooltip = 'classforajaxtooltip';
1910 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1911 $label = '';
1912 } else {
1913 $label = implode($this->getTooltipContentArray($params));
1914 }
1915
1916 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1917
1918 if ($short) {
1919 return $url;
1920 }
1921
1922 if ($option !== 'nolink') {
1923 // Add param to save lastsearch_values or not
1924 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1925 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1926 $add_save_lastsearch_values = 1;
1927 }
1928 if ($add_save_lastsearch_values) {
1929 $url .= '&save_lastsearch_values=1';
1930 }
1931 }
1932
1933 $linkclose = '';
1934 if (empty($notooltip)) {
1935 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1936 $label = $langs->trans("Shipment");
1937 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1938 }
1939 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1940 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1941 }
1942
1943 $linkstart = '<a href="'.$url.'"';
1944 $linkstart .= $linkclose.'>';
1945 $linkend = '</a>';
1946
1947 $result .= $linkstart;
1948 if ($withpicto) {
1949 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1950 }
1951 if ($withpicto != 2) {
1952 $result .= $this->ref;
1953 }
1954 $result .= $linkend;
1955 global $action;
1956 $hookmanager->initHooks(array($this->element . 'dao'));
1957 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1958 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1959 if ($reshook > 0) {
1960 $result = $hookmanager->resPrint;
1961 } else {
1962 $result .= $hookmanager->resPrint;
1963 }
1964 return $result;
1965 }
1966
1973 public function getLibStatut($mode = 0)
1974 {
1975 return $this->LibStatut($this->statut, $mode);
1976 }
1977
1978 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1986 public function LibStatut($status, $mode)
1987 {
1988 // phpcs:enable
1989 global $langs;
1990
1991 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
1992 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
1993
1994 $statusType = 'status'.$status;
1995 if ($status == self::STATUS_VALIDATED) {
1996 $statusType = 'status4';
1997 }
1998 if ($status == self::STATUS_CLOSED) {
1999 $statusType = 'status6';
2000 }
2001 if ($status == self::STATUS_CANCELED) {
2002 $statusType = 'status9';
2003 }
2004
2005 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
2006 }
2007
2015 public function getKanbanView($option = '', $arraydata = null)
2016 {
2017 global $langs, $conf;
2018
2019 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2020
2021 $return = '<div class="box-flex-item box-flex-grow-zero">';
2022 $return .= '<div class="info-box info-box-sm">';
2023 $return .= '<div class="info-box-icon bg-infobox-action">';
2024 $return .= img_picto('', 'order');
2025 $return .= '</div>';
2026 $return .= '<div class="info-box-content">';
2027 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
2028 if ($selected >= 0) {
2029 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2030 }
2031 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
2032 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
2033 }
2034 if (property_exists($this, 'total_ht')) {
2035 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'</div>';
2036 }
2037 if (method_exists($this, 'getLibStatut')) {
2038 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2039 }
2040 $return .= '</div>';
2041 $return .= '</div>';
2042 $return .= '</div>';
2043
2044 return $return;
2045 }
2046
2054 public function initAsSpecimen()
2055 {
2056 global $langs;
2057
2058 $now = dol_now();
2059
2060 dol_syslog(get_class($this)."::initAsSpecimen");
2061
2062 $order = new Commande($this->db);
2063 $order->initAsSpecimen();
2064
2065 // Initialise parameters
2066 $this->id = 0;
2067 $this->ref = 'SPECIMEN';
2068 $this->specimen = 1;
2070 $this->livraison_id = 0;
2071 $this->date = $now;
2072 $this->date_creation = $now;
2073 $this->date_valid = $now;
2074 $this->date_delivery = $now + 24 * 3600;
2075 $this->date_expedition = $now + 24 * 3600;
2076
2077 $this->entrepot_id = 0;
2078 $this->fk_delivery_address = 0;
2079 $this->socid = 1;
2080
2081 $this->commande_id = 0;
2082 $this->commande = $order;
2083
2084 $this->origin_id = 1;
2085 $this->origin = 'commande';
2086
2087 $this->note_private = 'Private note';
2088 $this->note_public = 'Public note';
2089
2090 $nbp = 5;
2091 $xnbp = 0;
2092 while ($xnbp < $nbp) {
2093 $line = new ExpeditionLigne($this->db);
2094 $line->product_desc = $langs->trans("Description")." ".$xnbp;
2095 $line->product_label = $langs->trans("Description")." ".$xnbp;
2096 $line->qty = 10;
2097 $line->qty_asked = 5;
2098 $line->qty_shipped = 4;
2099 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2100
2101 $this->lines[] = $line;
2102 $xnbp++;
2103 }
2104
2105 return 1;
2106 }
2107
2108 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2117 public function set_date_livraison($user, $delivery_date)
2118 {
2119 // phpcs:enable
2120 return $this->setDeliveryDate($user, $delivery_date);
2121 }
2122
2130 public function setDeliveryDate($user, $delivery_date)
2131 {
2132 if ($user->hasRight('expedition', 'creer')) {
2133 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2134 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2135 $sql .= " WHERE rowid = ".((int) $this->id);
2136
2137 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2138 $resql = $this->db->query($sql);
2139 if ($resql) {
2140 $this->date_delivery = $delivery_date;
2141 return 1;
2142 } else {
2143 $this->error = $this->db->error();
2144 return -1;
2145 }
2146 } else {
2147 return -2;
2148 }
2149 }
2150
2151 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2157 public function fetch_delivery_methods()
2158 {
2159 // phpcs:enable
2160 global $langs;
2161 $this->meths = array();
2162
2163 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2164 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2165 $sql .= " WHERE em.active = 1";
2166 $sql .= " ORDER BY em.libelle ASC";
2167
2168 $resql = $this->db->query($sql);
2169 if ($resql) {
2170 while ($obj = $this->db->fetch_object($resql)) {
2171 $label = $langs->trans('SendingMethod'.$obj->code);
2172 $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2173 }
2174 }
2175 }
2176
2177 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2184 public function list_delivery_methods($id = 0)
2185 {
2186 // phpcs:enable
2187 global $langs;
2188
2189 $this->listmeths = array();
2190 $i = 0;
2191
2192 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2193 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2194 if (!empty($id)) {
2195 $sql .= " WHERE em.rowid=".((int) $id);
2196 }
2197
2198 $resql = $this->db->query($sql);
2199 if ($resql) {
2200 while ($obj = $this->db->fetch_object($resql)) {
2201 $this->listmeths[$i]['rowid'] = $obj->rowid;
2202 $this->listmeths[$i]['code'] = $obj->code;
2203 $label = $langs->trans('SendingMethod'.$obj->code);
2204 $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2205 $this->listmeths[$i]['description'] = $obj->description;
2206 $this->listmeths[$i]['tracking'] = $obj->tracking;
2207 $this->listmeths[$i]['active'] = $obj->active;
2208 $i++;
2209 }
2210 }
2211 }
2212
2219 public function getUrlTrackingStatus($value = '')
2220 {
2221 if (!empty($this->shipping_method_id)) {
2222 $sql = "SELECT em.code, em.tracking";
2223 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2224 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2225
2226 $resql = $this->db->query($sql);
2227 if ($resql) {
2228 if ($obj = $this->db->fetch_object($resql)) {
2229 $tracking = $obj->tracking;
2230 }
2231 }
2232 }
2233
2234 if (!empty($tracking) && !empty($value)) {
2235 $url = str_replace('{TRACKID}', $value, $tracking);
2236 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
2237 } else {
2238 $this->tracking_url = $value;
2239 }
2240 }
2241
2247 public function setClosed()
2248 {
2249 global $user;
2250
2251 $error = 0;
2252
2253 // Protection. This avoid to move stock later when we should not
2254 if ($this->statut == self::STATUS_CLOSED) {
2255 return 0;
2256 }
2257
2258 $this->db->begin();
2259
2260 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED;
2261 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2262
2263 $resql = $this->db->query($sql);
2264 if ($resql) {
2265 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2266 if ($this->origin == 'commande' && $this->origin_id > 0) {
2267 $order = new Commande($this->db);
2268 $order->fetch($this->origin_id);
2269
2270 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2271
2272 $shipments_match_order = 1;
2273 foreach ($order->lines as $line) {
2274 $lineid = $line->id;
2275 $qty = $line->qty;
2276 if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2277 $shipments_match_order = 0;
2278 $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';
2279 dol_syslog($text);
2280 break;
2281 }
2282 }
2283 if ($shipments_match_order) {
2284 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');
2285 // We close the order
2286 $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2287 }
2288 }
2289
2290 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2291 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2292
2293 // If stock increment is done on closing
2294 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2295 $result = $this->manageStockMvtOnEvt($user);
2296 if ($result < 0) {
2297 $error++;
2298 }
2299 }
2300
2301 // Call trigger
2302 if (!$error) {
2303 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2304 if ($result < 0) {
2305 $error++;
2306 }
2307 }
2308 } else {
2309 dol_print_error($this->db);
2310 $error++;
2311 }
2312
2313 if (!$error) {
2314 $this->db->commit();
2315 return 1;
2316 } else {
2319
2320 $this->db->rollback();
2321 return -1;
2322 }
2323 }
2324
2334 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2335 {
2336 global $langs;
2337
2338 $error = 0;
2339
2340 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2341
2342 $langs->load("agenda");
2343
2344 // Loop on each product line to add a stock movement
2345 $sql = "SELECT cd.fk_product, cd.subprice,";
2346 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2347 $sql .= " e.ref,";
2348 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2349 $sql .= " cd.rowid as cdid, ed.rowid as edid";
2350 $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2351 $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2352 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2353 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2354 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2355 $sql .= " AND cd.rowid = ed.fk_elementdet";
2356
2357 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2358 $resql = $this->db->query($sql);
2359 if ($resql) {
2360 $cpt = $this->db->num_rows($resql);
2361 for ($i = 0; $i < $cpt; $i++) {
2362 $obj = $this->db->fetch_object($resql);
2363 if (empty($obj->edbrowid)) {
2364 $qty = $obj->qty;
2365 } else {
2366 $qty = $obj->edbqty;
2367 }
2368 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2369 continue;
2370 }
2371 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2372
2373 $mouvS = new MouvementStock($this->db);
2374 $mouvS->origin = &$this;
2375 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2376
2377 if (empty($obj->edbrowid)) {
2378 // line without batch detail
2379
2380 // 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
2381 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
2382 if ($result < 0) {
2383 $this->error = $mouvS->error;
2384 $this->errors = $mouvS->errors;
2385 $error++;
2386 break;
2387 }
2388 } else {
2389 // line with batch detail
2390
2391 // 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
2392 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
2393 if ($result < 0) {
2394 $this->error = $mouvS->error;
2395 $this->errors = $mouvS->errors;
2396 $error++;
2397 break;
2398 }
2399 }
2400
2401 // 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
2402 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2403 $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)";
2404 $resqldelete = $this->db->query($sqldelete);
2405 // We do not test error, it can fails if there is child in batch details
2406 }
2407 } else {
2408 $this->error = $this->db->lasterror();
2409 $this->errors[] = $this->db->lasterror();
2410 $error++;
2411 }
2412
2413 return $error;
2414 }
2415
2421 public function setBilled()
2422 {
2423 global $user;
2424 $error = 0;
2425
2426 $this->db->begin();
2427
2428 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET billed = 1';
2429 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2430
2431 $resql = $this->db->query($sql);
2432 if ($resql) {
2433 $this->billed = 1;
2434
2435 // Call trigger
2436 $result = $this->call_trigger('SHIPPING_BILLED', $user);
2437 if ($result < 0) {
2438 $this->billed = 0;
2439 $error++;
2440 }
2441 } else {
2442 $error++;
2443 $this->errors[] = $this->db->lasterror;
2444 }
2445
2446 if (empty($error)) {
2447 $this->db->commit();
2448 return 1;
2449 } else {
2450 $this->db->rollback();
2451 return -1;
2452 }
2453 }
2454
2462 public function setDraft($user, $notrigger = 0)
2463 {
2464 // Protection
2465 if ($this->statut <= self::STATUS_DRAFT) {
2466 return 0;
2467 }
2468
2469 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2470 }
2471
2477 public function reOpen()
2478 {
2479 global $langs, $user;
2480
2481 $error = 0;
2482
2483 // Protection. This avoid to move stock later when we should not
2484 if ($this->statut == self::STATUS_VALIDATED) {
2485 return 0;
2486 }
2487
2488 $this->db->begin();
2489
2490 $oldbilled = $this->billed;
2491
2492 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2493 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2494
2495 $resql = $this->db->query($sql);
2496 if ($resql) {
2499 $this->billed = 0;
2500
2501 // If stock increment is done on closing
2502 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2503 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2504
2505 $langs->load("agenda");
2506
2507 // Loop on each product line to add a stock movement
2508 // TODO possibilite d'expedier a partir d'une propale ou autre origine
2509 $sql = "SELECT cd.fk_product, cd.subprice,";
2510 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2511 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2512 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2513 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2514 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2515 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2516 $sql .= " AND cd.rowid = ed.fk_elementdet";
2517
2518 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2519 $resql = $this->db->query($sql);
2520 if ($resql) {
2521 $cpt = $this->db->num_rows($resql);
2522 for ($i = 0; $i < $cpt; $i++) {
2523 $obj = $this->db->fetch_object($resql);
2524 if (empty($obj->edbrowid)) {
2525 $qty = $obj->qty;
2526 } else {
2527 $qty = $obj->edbqty;
2528 }
2529 if ($qty <= 0) {
2530 continue;
2531 }
2532 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2533
2534 //var_dump($this->lines[$i]);
2535 $mouvS = new MouvementStock($this->db);
2536 $mouvS->origin = &$this;
2537 $mouvS->setOrigin($this->element, $this->id);
2538
2539 if (empty($obj->edbrowid)) {
2540 // line without batch detail
2541
2542 // 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
2543 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2544 if ($result < 0) {
2545 $this->error = $mouvS->error;
2546 $this->errors = $mouvS->errors;
2547 $error++;
2548 break;
2549 }
2550 } else {
2551 // line with batch detail
2552
2553 // 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
2554 $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);
2555 if ($result < 0) {
2556 $this->error = $mouvS->error;
2557 $this->errors = $mouvS->errors;
2558 $error++;
2559 break;
2560 }
2561 }
2562 }
2563 } else {
2564 $this->error = $this->db->lasterror();
2565 $error++;
2566 }
2567 }
2568
2569 if (!$error) {
2570 // Call trigger
2571 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2572 if ($result < 0) {
2573 $error++;
2574 }
2575 }
2576 } else {
2577 $error++;
2578 $this->errors[] = $this->db->lasterror();
2579 }
2580
2581 if (!$error) {
2582 $this->db->commit();
2583 return 1;
2584 } else {
2585 $this->statut = self::STATUS_CLOSED;
2586 $this->status = self::STATUS_CLOSED;
2587 $this->billed = $oldbilled;
2588 $this->db->rollback();
2589 return -1;
2590 }
2591 }
2592
2604 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2605 {
2606 $outputlangs->load("products");
2607
2608 if (!dol_strlen($modele)) {
2609 $modele = 'rouget';
2610
2611 if (!empty($this->model_pdf)) {
2612 $modele = $this->model_pdf;
2613 } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
2614 $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
2615 }
2616 }
2617
2618 $modelpath = "core/modules/expedition/doc/";
2619
2620 $this->fetch_origin();
2621
2622 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2623 }
2624
2633 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2634 {
2635 $tables = array(
2636 'expedition'
2637 );
2638
2639 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2640 }
2641}
2642
2643
2648{
2652 public $element = 'expeditiondet';
2653
2657 public $table_element = 'expeditiondet';
2658
2662 public $parent_element = 'expedition';
2663
2667 public $fk_parent_attribute = 'fk_expedition';
2668
2675 public $line_id; // deprecated
2676
2680 public $fk_element;
2681
2685 public $origin_id;
2686
2690 public $fk_elementdet;
2691
2695 public $origin_line_id;
2696
2700 public $element_type;
2701
2702
2709 public $fk_origin; // Example: 'orderline'
2710
2714 public $fk_expedition;
2715
2719 public $db;
2720
2724 public $qty;
2725
2729 public $qty_shipped;
2730
2734 public $fk_product;
2735
2736 // detail of lot and qty = array(id in llx_expeditiondet_batch, fk_expeditiondet, batch, qty, fk_origin_stock)
2737 // We can use this to know warehouse planned to be used for each lot.
2738 public $detail_batch;
2739
2740 // detail of warehouses and qty
2741 // We can use this to know warehouse when there is no lot.
2742 public $details_entrepot;
2743
2744
2748 public $entrepot_id;
2749
2750
2754 public $qty_asked;
2755
2760 public $ref;
2761
2765 public $product_ref;
2766
2771 public $libelle;
2772
2776 public $product_label;
2777
2783 public $desc;
2784
2788 public $product_desc;
2789
2794 public $product_type = 0;
2795
2799 public $rang;
2800
2804 public $weight;
2805 public $weight_units;
2806
2810 public $length;
2811 public $length_units;
2812
2816 public $width;
2817 public $width_units;
2818
2822 public $height;
2823 public $height_units;
2824
2828 public $surface;
2829 public $surface_units;
2830
2834 public $volume;
2835 public $volume_units;
2836
2837 // Invoicing
2838 public $remise_percent;
2839 public $tva_tx;
2840
2844 public $total_ht;
2845
2849 public $total_ttc;
2850
2854 public $total_tva;
2855
2859 public $total_localtax1;
2860
2864 public $total_localtax2;
2865
2866
2872 public function __construct($db)
2873 {
2874 $this->db = $db;
2875 }
2876
2883 public function fetch($rowid)
2884 {
2885 $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_elementdet, ed.element_type, ed.qty, ed.rang';
2886 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
2887 $sql .= ' WHERE ed.rowid = '.((int) $rowid);
2888 $result = $this->db->query($sql);
2889 if ($result) {
2890 $objp = $this->db->fetch_object($result);
2891 $this->id = $objp->rowid;
2892 $this->fk_expedition = $objp->fk_expedition;
2893 $this->entrepot_id = $objp->fk_entrepot;
2894 $this->fk_elementdet = $objp->fk_elementdet;
2895 $this->element_type = $objp->element_type;
2896 $this->qty = $objp->qty;
2897 $this->rang = $objp->rang;
2898
2899 $this->db->free($result);
2900
2901 return 1;
2902 } else {
2903 $this->errors[] = $this->db->lasterror();
2904 $this->error = $this->db->lasterror();
2905 return -1;
2906 }
2907 }
2908
2916 public function insert($user, $notrigger = 0)
2917 {
2918 $error = 0;
2919
2920 // Check parameters
2921 if (empty($this->fk_expedition) || empty($this->fk_elementdet) || !is_numeric($this->qty)) {
2922 $this->error = 'ErrorMandatoryParametersNotProvided';
2923 return -1;
2924 }
2925
2926 $this->db->begin();
2927
2928 if (empty($this->rang)) {
2929 $this->rang = 0;
2930 }
2931
2932 // Rank to use
2933 $ranktouse = $this->rang;
2934 if ($ranktouse == -1) {
2935 $rangmax = $this->line_max($this->fk_expedition);
2936 $ranktouse = $rangmax + 1;
2937 }
2938
2939 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
2940 $sql .= "fk_expedition";
2941 $sql .= ", fk_entrepot";
2942 $sql .= ", fk_elementdet";
2943 $sql .= ", element_type";
2944 $sql .= ", qty";
2945 $sql .= ", rang";
2946 $sql .= ") VALUES (";
2947 $sql .= $this->fk_expedition;
2948 $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
2949 $sql .= ", ".((int) $this->fk_elementdet);
2950 $sql .= ", '".(empty($this->element_type) ? 'order' : $this->db->escape($this->element_type))."'";
2951 $sql .= ", ".price2num($this->qty, 'MS');
2952 $sql .= ", ".((int) $ranktouse);
2953 $sql .= ")";
2954
2955 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
2956 $resql = $this->db->query($sql);
2957 if ($resql) {
2958 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
2959
2960 if (!$error) {
2961 $result = $this->insertExtraFields();
2962 if ($result < 0) {
2963 $error++;
2964 }
2965 }
2966
2967 if (!$error && !$notrigger) {
2968 // Call trigger
2969 $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
2970 if ($result < 0) {
2971 $error++;
2972 }
2973 // End call triggers
2974 }
2975
2976 if ($error) {
2977 foreach ($this->errors as $errmsg) {
2978 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2979 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2980 }
2981 }
2982 } else {
2983 $error++;
2984 }
2985
2986 if ($error) {
2987 $this->db->rollback();
2988 return -1;
2989 } else {
2990 $this->db->commit();
2991 return $this->id;
2992 }
2993 }
2994
3002 public function delete($user = null, $notrigger = 0)
3003 {
3004 $error = 0;
3005
3006 $this->db->begin();
3007
3008 // delete batch expedition line
3009 if (isModEnabled('productbatch')) {
3010 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
3011 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
3012
3013 if (!$this->db->query($sql)) {
3014 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3015 $error++;
3016 }
3017 }
3018
3019 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
3020 $sql .= " WHERE rowid = ".((int) $this->id);
3021
3022 if (!$error && $this->db->query($sql)) {
3023 // Remove extrafields
3024 if (!$error) {
3025 $result = $this->deleteExtraFields();
3026 if ($result < 0) {
3027 $this->errors[] = $this->error;
3028 $error++;
3029 }
3030 }
3031 if (!$error && !$notrigger) {
3032 // Call trigger
3033 $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
3034 if ($result < 0) {
3035 $this->errors[] = $this->error;
3036 $error++;
3037 }
3038 // End call triggers
3039 }
3040 } else {
3041 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3042 $error++;
3043 }
3044
3045 if (!$error) {
3046 $this->db->commit();
3047 return 1;
3048 } else {
3049 foreach ($this->errors as $errmsg) {
3050 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
3051 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3052 }
3053 $this->db->rollback();
3054 return -1 * $error;
3055 }
3056 }
3057
3065 public function update($user = null, $notrigger = 0)
3066 {
3067 $error = 0;
3068
3069 dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
3070
3071 $this->db->begin();
3072
3073 // Clean parameters
3074 if (empty($this->qty)) {
3075 $this->qty = 0;
3076 }
3077 $qty = price2num($this->qty);
3078 $remainingQty = 0;
3079 $batch = null;
3080 $batch_id = null;
3081 $expedition_batch_id = null;
3082 if (is_array($this->detail_batch)) { // array of ExpeditionLineBatch
3083 if (count($this->detail_batch) > 1) {
3084 dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
3085 $this->errors[] = 'ErrorBadParameters';
3086 $error++;
3087 } else {
3088 $batch = $this->detail_batch[0]->batch;
3089 $batch_id = $this->detail_batch[0]->fk_origin_stock;
3090 $expedition_batch_id = $this->detail_batch[0]->id;
3091 if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id) {
3092 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
3093 $this->errors[] = 'ErrorBadParameters';
3094 $error++;
3095 }
3096 $qty = price2num($this->detail_batch[0]->qty);
3097 }
3098 } elseif (!empty($this->detail_batch)) {
3099 $batch = $this->detail_batch->batch;
3100 $batch_id = $this->detail_batch->fk_origin_stock;
3101 $expedition_batch_id = $this->detail_batch->id;
3102 if ($this->entrepot_id != $this->detail_batch->entrepot_id) {
3103 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
3104 $this->errors[] = 'ErrorBadParameters';
3105 $error++;
3106 }
3107 $qty = price2num($this->detail_batch->qty);
3108 }
3109
3110 // check parameters
3111 if (!isset($this->id) || !isset($this->entrepot_id)) {
3112 dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
3113 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
3114 $error++;
3115 return -1;
3116 }
3117
3118 // update lot
3119
3120 if (!empty($batch) && isModEnabled('productbatch')) {
3121 $batch_id_str = $batch_id ?? 'null';
3122 dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id_str, batch=$batch");
3123
3124 if (empty($batch_id) || empty($this->fk_product)) {
3125 dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
3126 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
3127 $error++;
3128 }
3129
3130 // fetch remaining lot qty
3131 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
3132
3133 if (!$error && ($lotArray = $shipmentlinebatch->fetchAll($this->id)) < 0) {
3134 $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
3135 $error++;
3136 } else {
3137 // calculate new total line qty
3138 foreach ($lotArray as $lot) {
3139 if ($expedition_batch_id != $lot->id) {
3140 $remainingQty += $lot->qty;
3141 }
3142 }
3143 $qty += $remainingQty;
3144
3145 //fetch lot details
3146
3147 // fetch from product_lot
3148 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
3149 $lot = new Productlot($this->db);
3150 if ($lot->fetch(0, $this->fk_product, $batch) < 0) {
3151 $this->errors[] = $lot->errors;
3152 $error++;
3153 }
3154 if (!$error && !empty($expedition_batch_id)) {
3155 // delete lot expedition line
3156 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
3157 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
3158 $sql .= " AND rowid = ".((int) $expedition_batch_id);
3159
3160 if (!$this->db->query($sql)) {
3161 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3162 $error++;
3163 }
3164 }
3165 if (!$error && $this->detail_batch->qty > 0) {
3166 // create lot expedition line
3167 if (isset($lot->id)) {
3168 $shipmentLot = new ExpeditionLineBatch($this->db);
3169 $shipmentLot->batch = $lot->batch;
3170 $shipmentLot->eatby = $lot->eatby;
3171 $shipmentLot->sellby = $lot->sellby;
3172 $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id;
3173 $shipmentLot->qty = $this->detail_batch->qty;
3174 $shipmentLot->fk_origin_stock = $batch_id;
3175 if ($shipmentLot->create($this->id) < 0) {
3176 $this->errors = $shipmentLot->errors;
3177 $error++;
3178 }
3179 }
3180 }
3181 }
3182 }
3183 if (!$error) {
3184 // update line
3185 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3186 $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
3187 $sql .= " , qty = ".((float) price2num($qty, 'MS'));
3188 $sql .= " WHERE rowid = ".((int) $this->id);
3189
3190 if (!$this->db->query($sql)) {
3191 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3192 $error++;
3193 }
3194 }
3195
3196 if (!$error) {
3197 $result = $this->insertExtraFields();
3198 if ($result < 0) {
3199 $this->errors[] = $this->error;
3200 $error++;
3201 }
3202 }
3203
3204 if (!$error && !$notrigger) {
3205 // Call trigger
3206 $result = $this->call_trigger('LINESHIPPING_MODIFY', $user);
3207 if ($result < 0) {
3208 $this->errors[] = $this->error;
3209 $error++;
3210 }
3211 // End call triggers
3212 }
3213 if (!$error) {
3214 $this->db->commit();
3215 return 1;
3216 } else {
3217 foreach ($this->errors as $errmsg) {
3218 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3219 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3220 }
3221 $this->db->rollback();
3222 return -1 * $error;
3223 }
3224 }
3225}
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition security.php:636
$object ref
Definition info.php:79
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=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
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.
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).
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
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.
const STATUS_NO_SIGNATURE
No signature.
setDraft($user, $notrigger=0)
Set draft status.
const STATUS_SHIPMENT_IN_PROGRESS
Expedition in progress -> package exit the warehouse and is now in the truck or into the hand of the ...
getUrlTrackingStatus($value='')
Forge an set tracking url.
setClosed()
Classify the shipping as closed (this records also the stock movement)
__construct($db)
Constructor.
fetch_lines()
Load lines.
create($user, $notrigger=0)
Create expedition en base.
LibStatut($status, $mode)
Return label of a status.
setBilled()
Classify the shipping as invoiced (used for example by trigger when WORKFLOW_SHIPPING_CLASSIFY_BILLED...
addline($entrepot_id, $id, $qty, $array_options=[])
Add an expedition line.
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.
create_line($entrepot_id, $origin_line_id, $qty, $rang=0, $array_options=[])
Create a expedition line.
getKanbanView($option='', $arraydata=null)
Return clicable link of object (with eventually picto)
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.
addline_batch($dbatch, $array_options=[])
Add a shipment line with batch record.
getNextNumRef($soc)
Return next expedition ref.
const STATUS_CLOSED
Closed status -> parcel was received by customer / end of process prev status : validated or shipment...
const STATUS_VALIDATED
Validated status -> parcel is ready to be sent prev status : draft next status : closed or shipment_i...
create_line_batch($line_ext, $array_options=[])
Create the detail of the expedition line.
manageStockMvtOnEvt($user, $labelmovement='ShipmentClassifyClosedInDolibarr')
Manage Stock MVt onb Close or valid Shipment.
valid($user, $notrigger=0)
Validate object and update stock if option enabled.
const STATUS_SIGNED
Signed status.
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.
fetch($id, $ref='', $ref_ext='', $notused='')
Get object and lines from database.
reOpen()
Classify the shipping as validated/opened.
deleteLine($user, $lineid)
Delete detail line.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
list_delivery_methods($id=0)
Fetch all deliveries method and return an array.
Class 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.
const TYPE_SERVICE
Service.
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.
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)
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.
dol_dir_list($utf8_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:63
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
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:88
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:143
publicphonebutton2 phonegreen basiclayout basiclayout TotalHT VATCode TotalVAT TotalLT1 TotalLT2 TotalTTC TotalHT clearboth nowraponall TAKEPOS_SHOW_SUBPRICE right right right takeposterminal SELECT e e e e e statut
Definition invoice.php:1991