dolibarr 21.0.4
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 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 3 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31 */
32
39require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
40require_once DOL_DOCUMENT_ROOT."/expedition/class/expeditionligne.class.php";
41require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
42if (isModEnabled("propal")) {
43 require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
44}
45if (isModEnabled('order')) {
46 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
47}
48require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionlinebatch.class.php';
49require_once DOL_DOCUMENT_ROOT.'/core/class/commonsignedobject.class.php';
50
51
58{
59 use CommonIncoterm;
60 use CommonSignedObject;
61
65 public $element = "shipping";
66
70 public $fk_element = "fk_expedition";
71
75 public $table_element = "expedition";
76
80 public $table_element_line = "expeditiondet";
81
85 public $picto = 'dolly';
86
87
91 public $fields = array();
92
96 public $user_author_id;
97
101 public $fk_user_author;
102
106 public $socid;
107
113 public $ref_client;
114
118 public $ref_customer;
119
123 public $entrepot_id;
124
128 public $tracking_number;
129
133 public $tracking_url;
137 public $billed;
138
142 public $trueWeight;
146 public $weight_units;
150 public $trueWidth;
154 public $width_units;
158 public $trueHeight;
162 public $height_units;
166 public $trueDepth;
170 public $depth_units;
174 public $trueSize;
175
179 public $livraison_id;
180
184 public $multicurrency_subprice;
185
189 public $size_units;
190
194 public $sizeH;
195
199 public $sizeS;
200
204 public $sizeW;
205
209 public $weight;
210
214 public $date_delivery;
215
221 public $date;
222
228 public $date_expedition;
229
234 public $date_shipping;
235
239 public $date_valid;
240
244 public $meths;
248 public $listmeths; // List of carriers
249
253 public $commande_id;
254
258 public $commande;
259
263 public $lines = array();
264
265 // Multicurrency
269 public $fk_multicurrency;
270
274 public $multicurrency_code;
278 public $multicurrency_tx;
282 public $multicurrency_total_ht;
286 public $multicurrency_total_tva;
290 public $multicurrency_total_ttc;
291
295 const STATUS_DRAFT = 0;
296
304
311 const STATUS_CLOSED = 2;
312
316 const STATUS_CANCELED = -1;
317
326
332 public function __construct($db)
333 {
334 global $conf;
335
336 $this->db = $db;
337
338 $this->ismultientitymanaged = 1;
339 $this->isextrafieldmanaged = 1;
340
341 // List of long language codes for status
342 $this->labelStatus = array();
343 $this->labelStatus[-1] = 'StatusSendingCanceled';
344 $this->labelStatus[0] = 'StatusSendingDraft';
345 $this->labelStatus[1] = 'StatusSendingValidated';
346 $this->labelStatus[2] = 'StatusSendingProcessed';
347
348 // List of short language codes for status
349 $this->labelStatusShort = array();
350 $this->labelStatusShort[-1] = 'StatusSendingCanceledShort';
351 $this->labelStatusShort[0] = 'StatusSendingDraftShort';
352 $this->labelStatusShort[1] = 'StatusSendingValidatedShort';
353 $this->labelStatusShort[2] = 'StatusSendingProcessedShort';
354 }
355
362 public function getNextNumRef($soc)
363 {
364 global $langs, $conf;
365 $langs->load("sendings");
366
367 if (getDolGlobalString('EXPEDITION_ADDON_NUMBER')) {
368 $mybool = false;
369
370 $file = getDolGlobalString('EXPEDITION_ADDON_NUMBER') . ".php";
371 $classname = getDolGlobalString('EXPEDITION_ADDON_NUMBER');
372
373 // Include file with class
374 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
375
376 foreach ($dirmodels as $reldir) {
377 $dir = dol_buildpath($reldir."core/modules/expedition/");
378
379 // Load file with numbering class (if found)
380 $mybool = ((bool) @include_once $dir.$file) || $mybool;
381 }
382
383 if (!$mybool) {
384 dol_print_error(null, "Failed to include file ".$file);
385 return '';
386 }
387
388 $obj = new $classname();
389 '@phan-var-force ModelNumRefExpedition $obj';
390 $numref = $obj->getNextValue($soc, $this);
391
392 if ($numref != "") {
393 return $numref;
394 } else {
395 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
396 return "";
397 }
398 } else {
399 print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
400 return "";
401 }
402 }
403
411 public function create($user, $notrigger = 0)
412 {
413 global $conf, $hookmanager;
414
415 $now = dol_now();
416
417 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
418 $error = 0;
419
420 // Clean parameters
421 $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
422 if (empty($this->fk_project)) {
423 $this->fk_project = 0;
424 }
425 if (empty($this->date_shipping) && !empty($this->date_expedition)) {
426 $this->date_shipping = $this->date_expedition;
427 }
428 $this->entity = setEntity($this);
429
430 $this->user = $user;
431
432 $this->db->begin();
433
434 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
435 $sql .= "ref";
436 $sql .= ", entity";
437 $sql .= ", ref_customer";
438 $sql .= ", ref_ext";
439 $sql .= ", date_creation";
440 $sql .= ", fk_user_author";
441 $sql .= ", date_expedition";
442 $sql .= ", date_delivery";
443 $sql .= ", fk_soc";
444 $sql .= ", fk_projet";
445 $sql .= ", fk_address";
446 $sql .= ", fk_shipping_method";
447 $sql .= ", tracking_number";
448 $sql .= ", weight";
449 $sql .= ", size";
450 $sql .= ", width";
451 $sql .= ", height";
452 $sql .= ", weight_units";
453 $sql .= ", size_units";
454 $sql .= ", note_private";
455 $sql .= ", note_public";
456 $sql .= ", model_pdf";
457 $sql .= ", fk_incoterms, location_incoterms";
458 $sql .= ", signed_status";
459 $sql .= ") VALUES (";
460 $sql .= "'(PROV)'";
461 $sql .= ", ".((int) $this->entity);
462 $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
463 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
464 $sql .= ", '".$this->db->idate($now)."'";
465 $sql .= ", ".((int) $user->id);
466 $sql .= ", ".($this->date_shipping > 0 ? "'".$this->db->idate($this->date_shipping)."'" : "null");
467 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
468 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
469 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
470 $sql .= ", ".($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
471 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
472 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
473 $sql .= ", ".(is_numeric($this->weight) ? $this->weight : 'NULL');
474 $sql .= ", ".(is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
475 $sql .= ", ".(is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
476 $sql .= ", ".(is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
477 $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
478 $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
479 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
480 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
481 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
482 $sql .= ", ".(int) $this->fk_incoterms;
483 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
484 $sql .= ", ".($this->signed_status);
485 $sql .= ")";
486
487 dol_syslog(get_class($this)."::create", LOG_DEBUG);
488 $resql = $this->db->query($sql);
489 if ($resql) {
490 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
491
492 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
493 $sql .= " SET ref = '(PROV".$this->id.")'";
494 $sql .= " WHERE rowid = ".((int) $this->id);
495
496 dol_syslog(get_class($this)."::create", LOG_DEBUG);
497 if ($this->db->query($sql)) {
498 // Insert of lines
499 $num = count($this->lines);
500 for ($i = 0; $i < $num; $i++) {
501 if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
502 if (!isset($this->lines[$i]->detail_batch)) { // no batch management
503 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) {
504 $error++;
505 }
506 } else { // with batch management
507 if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
508 $error++;
509 }
510 }
511 }
512 }
513
514 if (!$error && $this->id && $this->origin_id) {
515 $ret = $this->add_object_linked();
516 if (!$ret) {
517 $error++;
518 }
519 }
520
521 // Actions on extra fields
522 if (!$error) {
523 $result = $this->insertExtraFields();
524 if ($result < 0) {
525 $error++;
526 }
527 }
528
529 if (!$error && !$notrigger) {
530 // Call trigger
531 $result = $this->call_trigger('SHIPPING_CREATE', $user);
532 if ($result < 0) {
533 $error++;
534 }
535 // End call triggers
536
537 if (!$error) {
538 $this->db->commit();
539 return $this->id;
540 } else {
541 foreach ($this->errors as $errmsg) {
542 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
543 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
544 }
545 $this->db->rollback();
546 return -1 * $error;
547 }
548 } else {
549 $error++;
550 $this->db->rollback();
551 return -3;
552 }
553 } else {
554 $error++;
555 $this->error = $this->db->lasterror()." - sql=$sql";
556 $this->db->rollback();
557 return -2;
558 }
559 } else {
560 $error++;
561 $this->error = $this->db->error()." - sql=$sql";
562 $this->db->rollback();
563 return -1;
564 }
565 }
566
567 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
578 public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [])
579 {
580 //phpcs:enable
581 global $user;
582
583 $expeditionline = new ExpeditionLigne($this->db);
584 $expeditionline->fk_expedition = $this->id;
585 $expeditionline->entrepot_id = $entrepot_id;
586 $expeditionline->fk_elementdet = $origin_line_id;
587 $expeditionline->element_type = $this->origin;
588 $expeditionline->qty = $qty;
589 $expeditionline->rang = $rang;
590 $expeditionline->array_options = $array_options;
591
592 if (($lineId = $expeditionline->insert($user)) < 0) {
593 $this->errors[] = $expeditionline->error;
594 }
595 return $lineId;
596 }
597
598
599 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
607 public function create_line_batch($line_ext, $array_options = [])
608 {
609 // phpcs:enable
610 $error = 0;
611 $stockLocationQty = array(); // associated array with batch qty in stock location
612
613 $tab = $line_ext->detail_batch;
614 // create stockLocation Qty array
615 foreach ($tab as $detbatch) {
616 if (!empty($detbatch->entrepot_id)) {
617 if (empty($stockLocationQty[$detbatch->entrepot_id])) {
618 $stockLocationQty[$detbatch->entrepot_id] = 0;
619 }
620 $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
621 }
622 }
623 // create shipment lines
624 foreach ($stockLocationQty as $stockLocation => $qty) {
625 $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
626 if ($line_id < 0) {
627 $error++;
628 } else {
629 // create shipment batch lines for stockLocation
630 foreach ($tab as $detbatch) {
631 if ($detbatch->entrepot_id == $stockLocation) {
632 if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch
633 $this->errors = $detbatch->errors;
634 $error++;
635 }
636 }
637 }
638 }
639 }
640
641 if (!$error) {
642 return 1;
643 } else {
644 return -1;
645 }
646 }
647
657 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
658 {
659 global $conf;
660
661 // Check parameters
662 if (empty($id) && empty($ref) && empty($ref_ext)) {
663 return -1;
664 }
665
666 $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.signed_status, e.fk_projet as fk_project, e.billed";
667 $sql .= ", e.date_valid";
668 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
669 $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
670 $sql .= ", e.fk_shipping_method, e.tracking_number";
671 $sql .= ", e.note_private, e.note_public";
672 $sql .= ', e.fk_incoterms, e.location_incoterms';
673 $sql .= ', e.signed_status';
674 $sql .= ', i.libelle as label_incoterms';
675 $sql .= ', s.libelle as shipping_method';
676 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin_type";
677 $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
678 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
679 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
680 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
681 $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
682 if ($id) {
683 $sql .= " AND e.rowid = ".((int) $id);
684 }
685 if ($ref) {
686 $sql .= " AND e.ref='".$this->db->escape($ref)."'";
687 }
688 if ($ref_ext) {
689 $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
690 }
691
692 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
693 $result = $this->db->query($sql);
694 if ($result) {
695 if ($this->db->num_rows($result)) {
696 $obj = $this->db->fetch_object($result);
697
698 $this->id = $obj->rowid;
699 $this->entity = $obj->entity;
700 $this->ref = $obj->ref;
701 $this->socid = $obj->socid;
702 $this->ref_customer = $obj->ref_customer;
703 $this->ref_ext = $obj->ref_ext;
704 $this->status = $obj->fk_statut;
705 $this->statut = $this->status; // Deprecated
706 $this->signed_status = $obj->signed_status;
707 $this->user_author_id = $obj->fk_user_author;
708 $this->fk_user_author = $obj->fk_user_author;
709 $this->date_creation = $this->db->jdate($obj->date_creation);
710 $this->date_valid = $this->db->jdate($obj->date_valid);
711 $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
712 $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
713 $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
714 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
715 $this->fk_delivery_address = $obj->fk_address;
716 $this->model_pdf = $obj->model_pdf;
717 $this->shipping_method_id = $obj->fk_shipping_method;
718 $this->shipping_method = $obj->shipping_method;
719 $this->tracking_number = $obj->tracking_number;
720 $this->origin = ($obj->origin_type ? $obj->origin_type : 'commande'); // For compatibility
721 $this->origin_type = ($obj->origin_type ? $obj->origin_type : 'commande');
722 $this->origin_id = $obj->origin_id;
723 $this->billed = $obj->billed;
724 $this->fk_project = $obj->fk_project;
725 $this->signed_status = $obj->signed_status;
726 $this->trueWeight = $obj->weight;
727 $this->weight_units = $obj->weight_units;
728
729 $this->trueWidth = $obj->width;
730 $this->width_units = $obj->size_units;
731 $this->trueHeight = $obj->height;
732 $this->height_units = $obj->size_units;
733 $this->trueDepth = $obj->size;
734 $this->depth_units = $obj->size_units;
735
736 $this->note_public = $obj->note_public;
737 $this->note_private = $obj->note_private;
738
739 // A denormalized value
740 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
741 $this->size_units = $obj->size_units;
742
743 //Incoterms
744 $this->fk_incoterms = $obj->fk_incoterms;
745 $this->location_incoterms = $obj->location_incoterms;
746 $this->label_incoterms = $obj->label_incoterms;
747
748 $this->db->free($result);
749
750 // Tracking url
751 $this->getUrlTrackingStatus($obj->tracking_number);
752
753 // Thirdparty
754 $result = $this->fetch_thirdparty(); // TODO Remove this
755
756 // Retrieve extrafields
757 $this->fetch_optionals();
758
759 // Fix Get multicurrency param for transmitted
760 if (isModEnabled('multicurrency')) {
761 if (!empty($this->multicurrency_code)) {
762 $this->multicurrency_code = $this->thirdparty->multicurrency_code;
763 }
764 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($this->thirdparty->multicurrency_tx)) {
765 $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
766 }
767 }
768
769 /*
770 * Lines
771 */
772 $result = $this->fetch_lines();
773 if ($result < 0) {
774 return -3;
775 }
776
777 return 1;
778 } else {
779 dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
780 $this->error = 'Shipment with id '.$id.' not found';
781 return 0;
782 }
783 } else {
784 $this->error = $this->db->error();
785 return -1;
786 }
787 }
788
796 public function valid($user, $notrigger = 0)
797 {
798 global $conf;
799
800 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
801
802 dol_syslog(get_class($this)."::valid");
803
804 // Protection
805 if ($this->status) {
806 dol_syslog(get_class($this)."::valid not in draft status", LOG_WARNING);
807 return 0;
808 }
809
810 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'creer'))
811 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'shipping_advance', 'validate')))) {
812 $this->error = 'Permission denied';
813 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
814 return -1;
815 }
816
817 $this->db->begin();
818
819 $error = 0;
820
821 // Define new ref
822 $soc = new Societe($this->db);
823 $soc->fetch($this->socid);
824
825 // Class of company linked to order
826 $result = $soc->setAsCustomer();
827
828 // Define new ref
829 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
830 $numref = $this->getNextNumRef($soc);
831 } elseif (!empty($this->ref)) {
832 $numref = $this->ref;
833 } else {
834 $numref = "EXP".$this->id;
835 }
836 $this->newref = dol_sanitizeFileName($numref);
837
838 $now = dol_now();
839
840 // Validate
841 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
842 $sql .= " ref='".$this->db->escape($numref)."'";
843 $sql .= ", fk_statut = 1";
844 $sql .= ", date_valid = '".$this->db->idate($now)."'";
845 $sql .= ", fk_user_valid = ".((int) $user->id);
846 $sql .= " WHERE rowid = ".((int) $this->id);
847
848 dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
849 $resql = $this->db->query($sql);
850 if (!$resql) {
851 $this->error = $this->db->lasterror();
852 $error++;
853 }
854
855 // If stock increment is done on sending (recommended choice)
856 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
857 $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
858 if ($result < 0) {
859 return -2;
860 }
861 }
862
863 // Change status of order to "shipment in process"
864 $triggerKey = 'SHIPPING_'; // Because when the trigger is fired the object is a shipping and not the real target object, so I add a prefix like SHIPPING_ to avoid confusion
865 if ($this->origin == 'commande') {
866 $triggerKey.= 'ORDER_SHIPMENTONPROCESS';
867 } else {
868 $triggerKey.= strtoupper($this->origin).'_SHIPMENTONPROCESS';
869 }
870
871 // TODO : load the origin object to trigger the right setStatus according to origin object
872 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin, $triggerKey);
873 if (!$ret) {
874 $error++;
875 }
876
877 if (!$error && !$notrigger) {
878 // Call trigger
879 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
880 if ($result < 0) {
881 $error++;
882 }
883 // End call triggers
884 }
885
886 if (!$error) {
887 $this->oldref = $this->ref;
888
889 // Rename directory if dir was a temporary ref
890 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
891 // Now we rename also files into index
892 $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)."'";
893 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
894 $resql = $this->db->query($sql);
895 if (!$resql) {
896 $error++;
897 $this->error = $this->db->lasterror();
898 }
899 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
900 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
901 $resql = $this->db->query($sql);
902 if (!$resql) {
903 $error++;
904 $this->error = $this->db->lasterror();
905 }
906
907 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
908 $oldref = dol_sanitizeFileName($this->ref);
909 $newref = dol_sanitizeFileName($numref);
910 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
911 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
912 if (!$error && file_exists($dirsource)) {
913 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
914
915 if (@rename($dirsource, $dirdest)) {
916 dol_syslog("Rename ok");
917 // Rename docs starting with $oldref with $newref
918 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
919 foreach ($listoffiles as $fileentry) {
920 $dirsource = $fileentry['name'];
921 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
922 $dirsource = $fileentry['path'].'/'.$dirsource;
923 $dirdest = $fileentry['path'].'/'.$dirdest;
924 @rename($dirsource, $dirdest);
925 }
926 }
927 }
928 }
929 }
930
931 // Set new ref and current status
932 if (!$error) {
933 $this->ref = $numref;
934 $this->statut = self::STATUS_VALIDATED;
936 }
937
938 if (!$error) {
939 $this->db->commit();
940 return 1;
941 } else {
942 $this->db->rollback();
943 return -1 * $error;
944 }
945 }
946
947
948 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
955 public function create_delivery($user)
956 {
957 // phpcs:enable
958 global $conf;
959
960 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
961 if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
962 // Expedition validee
963 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
964 $delivery = new Delivery($this->db);
965 $result = $delivery->create_from_sending($user, $this->id);
966 if ($result > 0) {
967 return $result;
968 } else {
969 $this->error = $delivery->error;
970 return $result;
971 }
972 } else {
973 return 0;
974 }
975 } else {
976 return 0;
977 }
978 }
979
992 public function addline($entrepot_id, $id, $qty, $array_options = [])
993 {
994 global $conf, $langs;
995
996 $num = count($this->lines);
997 $line = new ExpeditionLigne($this->db);
998
999 $line->entrepot_id = $entrepot_id;
1000 $line->origin_line_id = $id;
1001 $line->fk_elementdet = $id;
1002 $line->element_type = 'order';
1003 $line->qty = $qty;
1004
1005 $orderline = new OrderLine($this->db);
1006 $orderline->fetch($id);
1007
1008 // Copy the rang of the order line to the expedition line
1009 $line->rang = $orderline->rang;
1010 $line->product_type = $orderline->product_type;
1011
1012 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
1013 $fk_product = $orderline->fk_product;
1014
1015 if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS') && !(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') && $line->product_type == Product::TYPE_SERVICE)) {
1016 $langs->load("errors");
1017 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
1018 return -1;
1019 }
1020
1021 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
1022 $product = new Product($this->db);
1023 $product->fetch($fk_product);
1024
1025 // Check must be done for stock of product into warehouse if $entrepot_id defined
1026 if ($entrepot_id > 0) {
1027 $product->load_stock('warehouseopen');
1028 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
1029 } else {
1030 $product_stock = $product->stock_reel;
1031 }
1032
1033 $product_type = $product->type;
1034 if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1035 $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
1036 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
1037 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.
1038 if ($product_stock < $qty) {
1039 $langs->load("errors");
1040 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
1041 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
1042
1043 $this->db->rollback();
1044 return -3;
1045 }
1046 }
1047 }
1048 }
1049 }
1050
1051 // If product need a batch number, we should not have called this function but addline_batch instead.
1052 // If this happen, we may have a bug in card.php page
1053 if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
1054 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; //
1055 return -4;
1056 }
1057
1058 // extrafields
1059 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1060 $line->array_options = $array_options;
1061 }
1062
1063 $this->lines[$num] = $line;
1064
1065 return 1;
1066 }
1067
1068 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1076 public function addline_batch($dbatch, $array_options = [])
1077 {
1078 // phpcs:enable
1079 global $conf, $langs;
1080
1081 $num = count($this->lines);
1082 $linebatch = null;
1083 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1084 $line = new ExpeditionLigne($this->db);
1085 $tab = array();
1086 foreach ($dbatch['detail'] as $key => $value) {
1087 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1088 // $value['q']=qty to move
1089 // $value['id_batch']=id into llx_product_batch of record to move
1090 //var_dump($value);
1091
1092 $linebatch = new ExpeditionLineBatch($this->db);
1093 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1094 if ($ret < 0) {
1095 $this->setErrorsFromObject($linebatch);
1096 return -1;
1097 }
1098 $linebatch->qty = $value['q'];
1099 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1100 $linebatch->batch = null;
1101 }
1102 $tab[] = $linebatch;
1103
1104 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1105 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1106 $prod_batch = new Productbatch($this->db);
1107 $prod_batch->fetch($value['id_batch']);
1108
1109 if ($prod_batch->qty < $linebatch->qty) {
1110 $langs->load("errors");
1111 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1112 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1113 $this->db->rollback();
1114 return -1;
1115 }
1116 }
1117
1118 //var_dump($linebatch);
1119 }
1120 }
1121 if (is_object($linebatch)) {
1122 $line->entrepot_id = $linebatch->entrepot_id;
1123 }
1124 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1125 $line->fk_elementdet = $dbatch['ix_l'];
1126 $line->qty = $dbatch['qty'];
1127 $line->detail_batch = $tab;
1128
1129 // extrafields
1130 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1131 $line->array_options = $array_options;
1132 }
1133
1134 //var_dump($line);
1135 $this->lines[$num] = $line;
1136 return 1;
1137 }
1138 return 0;
1139 }
1140
1148 public function update($user = null, $notrigger = 0)
1149 {
1150 global $conf;
1151 $error = 0;
1152
1153 // Clean parameters
1154
1155 if (isset($this->ref)) {
1156 $this->ref = trim($this->ref);
1157 }
1158 if (isset($this->entity)) {
1159 $this->entity = (int) $this->entity;
1160 }
1161 if (isset($this->ref_customer)) {
1162 $this->ref_customer = trim($this->ref_customer);
1163 }
1164 if (isset($this->socid)) {
1165 $this->socid = (int) $this->socid;
1166 }
1167 if (isset($this->fk_user_author)) {
1168 $this->fk_user_author = (int) $this->fk_user_author;
1169 }
1170 if (isset($this->fk_user_valid)) { // @phan-ignore-current-line PhanUndeclaredProperty
1171 // If set, then accept @phan-ignore-next-line PhanUndeclaredProperty
1172 $this->fk_user_valid = (int) $this->fk_user_valid;
1173 }
1174 if (isset($this->fk_delivery_address)) {
1175 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1176 }
1177 if (isset($this->shipping_method_id)) {
1178 $this->shipping_method_id = (int) $this->shipping_method_id;
1179 }
1180 if (isset($this->tracking_number)) {
1181 $this->tracking_number = trim($this->tracking_number);
1182 }
1183 if (isset($this->statut)) {
1184 $this->statut = (int) $this->statut;
1185 }
1186 if (isset($this->trueDepth)) {
1187 $this->trueDepth = trim($this->trueDepth);
1188 }
1189 if (isset($this->trueWidth)) {
1190 $this->trueWidth = trim($this->trueWidth);
1191 }
1192 if (isset($this->trueHeight)) {
1193 $this->trueHeight = trim($this->trueHeight);
1194 }
1195 if (isset($this->size_units)) {
1196 $this->size_units = trim($this->size_units);
1197 }
1198 if (isset($this->weight_units)) {
1199 $this->weight_units = (int) $this->weight_units;
1200 }
1201 if (isset($this->trueWeight)) {
1202 $this->weight = trim((string) $this->trueWeight);
1203 }
1204 if (isset($this->note_private)) {
1205 $this->note_private = trim($this->note_private);
1206 }
1207 if (isset($this->note_public)) {
1208 $this->note_public = trim($this->note_public);
1209 }
1210 if (isset($this->model_pdf)) {
1211 $this->model_pdf = trim($this->model_pdf);
1212 }
1213 if (!empty($this->date_expedition)) {
1214 $this->date_shipping = $this->date_expedition;
1215 }
1216
1217 // Check parameters
1218 // Put here code to add control on parameters values
1219
1220 // Update request
1221 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1222 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1223 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1224 $sql .= " ref_customer = ".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1225 $sql .= " fk_soc = ".(isset($this->socid) ? $this->socid : "null").",";
1226 $sql .= " date_creation = ".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1227 $sql .= " fk_user_author = ".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1228 $sql .= " date_valid = ".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1229 $sql .= " fk_user_valid = ".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1230 $sql .= " date_expedition = ".(dol_strlen($this->date_shipping) != 0 ? "'".$this->db->idate($this->date_shipping)."'" : 'null').",";
1231 $sql .= " date_delivery = ".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1232 $sql .= " fk_address = ".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1233 $sql .= " fk_shipping_method = ".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1234 $sql .= " tracking_number = ".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1235 $sql .= " fk_statut = ".(isset($this->statut) ? $this->statut : "null").",";
1236 $sql .= " fk_projet = ".(isset($this->fk_project) ? $this->fk_project : "null").",";
1237 $sql .= " height = ".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1238 $sql .= " width = ".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1239 $sql .= " size_units = ".(isset($this->size_units) ? $this->size_units : "null").",";
1240 $sql .= " size = ".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1241 $sql .= " weight_units = ".(isset($this->weight_units) ? $this->weight_units : "null").",";
1242 $sql .= " weight = ".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1243 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1244 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1245 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1246 $sql .= " entity = ".((int) $conf->entity);
1247 $sql .= " WHERE rowid = ".((int) $this->id);
1248
1249 $this->db->begin();
1250
1251 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1252 $resql = $this->db->query($sql);
1253 if (!$resql) {
1254 $error++;
1255 $this->errors[] = "Error ".$this->db->lasterror();
1256 }
1257
1258 // Actions on extra fields
1259 if (!$error) {
1260 $result = $this->insertExtraFields();
1261 if ($result < 0) {
1262 $error++;
1263 }
1264 }
1265
1266 if (!$error && !$notrigger) {
1267 // Call trigger
1268 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1269 if ($result < 0) {
1270 $error++;
1271 }
1272 // End call triggers
1273 }
1274
1275 // Commit or rollback
1276 if ($error) {
1277 foreach ($this->errors as $errmsg) {
1278 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1279 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1280 }
1281 $this->db->rollback();
1282 return -1 * $error;
1283 } else {
1284 $this->db->commit();
1285 return 1;
1286 }
1287 }
1288
1289
1297 public function cancel($notrigger = 0, $also_update_stock = false)
1298 {
1299 global $conf, $langs, $user;
1300
1301 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1302
1303 $error = 0;
1304 $this->error = '';
1305
1306 $this->db->begin();
1307
1308 // Add a protection to refuse deleting if shipment has at least one delivery
1309 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1310 if (count($this->linkedObjectsIds) > 0) {
1311 $this->error = 'ErrorThereIsSomeDeliveries';
1312 $error++;
1313 }
1314
1315 if (!$error && !$notrigger) {
1316 // Call trigger
1317 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1318 if ($result < 0) {
1319 $error++;
1320 }
1321 // End call triggers
1322 }
1323
1324 // Stock control
1325 if (!$error && isModEnabled('stock') &&
1326 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1327 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1328 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1329
1330 $langs->load("agenda");
1331
1332 // Loop on each product line to add a stock movement and delete features
1333 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1334 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1335 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1336 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1337 $sql .= " AND cd.rowid = ed.fk_elementdet";
1338
1339 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1340 $resql = $this->db->query($sql);
1341 if ($resql) {
1342 $cpt = $this->db->num_rows($resql);
1343
1344 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1345
1346 for ($i = 0; $i < $cpt; $i++) {
1347 dol_syslog(get_class($this)."::delete movement index ".$i);
1348 $obj = $this->db->fetch_object($resql);
1349
1350 $mouvS = new MouvementStock($this->db);
1351 // we do not log origin because it will be deleted
1352 $mouvS->origin = '';
1353 // get lot/serial
1354 $lotArray = null;
1355 if (isModEnabled('productbatch')) {
1356 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1357 if (!is_array($lotArray)) {
1358 $error++;
1359 $this->errors[] = "Error ".$this->db->lasterror();
1360 }
1361 }
1362
1363 if (empty($lotArray)) {
1364 // no lot/serial
1365 // We increment stock of product (and sub-products)
1366 // We use warehouse selected for each line
1367 $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
1368 if ($result < 0) {
1369 $error++;
1370 $this->errors = array_merge($this->errors, $mouvS->errors);
1371 break;
1372 }
1373 } else {
1374 // We increment stock of batches
1375 // We use warehouse selected for each line
1376 foreach ($lotArray as $lot) {
1377 $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
1378 if ($result < 0) {
1379 $error++;
1380 $this->errors = array_merge($this->errors, $mouvS->errors);
1381 break;
1382 }
1383 }
1384 if ($error) {
1385 break; // break for loop in case of error
1386 }
1387 }
1388 }
1389 } else {
1390 $error++;
1391 $this->errors[] = "Error ".$this->db->lasterror();
1392 }
1393 }
1394
1395 // delete batch expedition line
1396 if (!$error && isModEnabled('productbatch')) {
1397 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1398 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1399 $error++;
1400 $this->errors[] = "Error ".$this->db->lasterror();
1401 }
1402 }
1403
1404
1405 if (!$error) {
1406 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1407 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1408
1409 if ($this->db->query($sql)) {
1410 // Delete linked object
1411 $res = $this->deleteObjectLinked();
1412 if ($res < 0) {
1413 $error++;
1414 }
1415
1416 // No delete expedition
1417 if (!$error) {
1418 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1419 $sql .= " WHERE rowid = ".((int) $this->id);
1420
1421 if ($this->db->query($sql)) {
1422 if (!empty($this->origin) && $this->origin_id > 0) {
1423 $this->fetch_origin();
1425 '@phan-var-force Facture|Commande $origin_object';
1426 if ($origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1427 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1428 $origin_object->loadExpeditions();
1429 //var_dump($this->$origin->expeditions);exit;
1430 if (count($origin_object->expeditions) <= 0) {
1432 }
1433 }
1434 }
1435
1436 if (!$error) {
1437 $this->db->commit();
1438
1439 // We delete PDFs
1440 $ref = dol_sanitizeFileName($this->ref);
1441 if (!empty($conf->expedition->dir_output)) {
1442 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1443 $file = $dir.'/'.$ref.'.pdf';
1444 if (file_exists($file)) {
1445 if (!dol_delete_file($file)) {
1446 return 0;
1447 }
1448 }
1449 if (file_exists($dir)) {
1450 if (!dol_delete_dir_recursive($dir)) {
1451 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1452 return 0;
1453 }
1454 }
1455 }
1456
1457 return 1;
1458 } else {
1459 $this->db->rollback();
1460 return -1;
1461 }
1462 } else {
1463 $this->error = $this->db->lasterror()." - sql=$sql";
1464 $this->db->rollback();
1465 return -3;
1466 }
1467 } else {
1468 $this->error = $this->db->lasterror()." - sql=$sql";
1469 $this->db->rollback();
1470 return -2;
1471 }//*/
1472 } else {
1473 $this->error = $this->db->lasterror()." - sql=$sql";
1474 $this->db->rollback();
1475 return -1;
1476 }
1477 } else {
1478 $this->db->rollback();
1479 return -1;
1480 }
1481 }
1482
1492 public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1493 {
1494 global $conf, $langs;
1495
1496 if (empty($user)) {
1497 global $user;
1498 }
1499
1500 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1501
1502 $error = 0;
1503 $this->error = '';
1504
1505 $this->db->begin();
1506
1507 // Add a protection to refuse deleting if shipment has at least one delivery
1508 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1509 if (count($this->linkedObjectsIds) > 0) {
1510 $this->error = 'ErrorThereIsSomeDeliveries';
1511 $error++;
1512 }
1513
1514 if (!$error && !$notrigger) {
1515 // Call trigger
1516 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1517 if ($result < 0) {
1518 $error++;
1519 }
1520 // End call triggers
1521 }
1522
1523 // Stock control
1524 if (!$error && isModEnabled('stock') &&
1525 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1526 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1527 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1528
1529 $langs->load("agenda");
1530
1531 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1532 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1533
1534 // Loop on each product line to add a stock movement
1535 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1536 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1537 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1538 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1539 $sql .= " AND cd.rowid = ed.fk_elementdet";
1540
1541 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1542 $resql = $this->db->query($sql);
1543 if ($resql) {
1544 $cpt = $this->db->num_rows($resql);
1545 for ($i = 0; $i < $cpt; $i++) {
1546 dol_syslog(get_class($this)."::delete movement index ".$i);
1547 $obj = $this->db->fetch_object($resql);
1548
1549 $mouvS = new MouvementStock($this->db);
1550 // we do not log origin because it will be deleted
1551 $mouvS->origin = '';
1552 // get lot/serial
1553 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1554 if (!is_array($lotArray)) {
1555 $error++;
1556 $this->errors[] = "Error ".$this->db->lasterror();
1557 }
1558 if (empty($lotArray)) {
1559 // no lot/serial
1560 // We increment stock of product (and sub-products)
1561 // We use warehouse selected for each line
1562 $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
1563 if ($result < 0) {
1564 $error++;
1565 $this->errors = array_merge($this->errors, $mouvS->errors);
1566 break;
1567 }
1568 } else {
1569 // We increment stock of batches
1570 // We use warehouse selected for each line
1571 foreach ($lotArray as $lot) {
1572 $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
1573 if ($result < 0) {
1574 $error++;
1575 $this->errors = array_merge($this->errors, $mouvS->errors);
1576 break;
1577 }
1578 }
1579 if ($error) {
1580 break; // break for loop in case of error
1581 }
1582 }
1583 }
1584 } else {
1585 $error++;
1586 $this->errors[] = "Error ".$this->db->lasterror();
1587 }
1588 }
1589
1590 // delete batch expedition line
1591 if (!$error) {
1592 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1593 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1594 $error++;
1595 $this->errors[] = "Error ".$this->db->lasterror();
1596 }
1597 }
1598
1599 if (!$error) {
1600 $main = MAIN_DB_PREFIX.'expeditiondet';
1601 $ef = $main."_extrafields";
1602 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
1603
1604 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1605 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1606
1607 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1608 // Delete linked object
1609 $res = $this->deleteObjectLinked();
1610 if ($res < 0) {
1611 $error++;
1612 }
1613
1614 // delete extrafields
1615 $res = $this->deleteExtraFields();
1616 if ($res < 0) {
1617 $error++;
1618 }
1619
1620 if (!$error) {
1621 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1622 $sql .= " WHERE rowid = ".((int) $this->id);
1623
1624 if ($this->db->query($sql)) {
1625 if (!empty($this->origin) && $this->origin_id > 0) {
1626 $this->fetch_origin();
1628 '@phan-var-force Facture|Commande $origin_object';
1629 if ($origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1630 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1631 $origin_object->loadExpeditions();
1632 //var_dump($this->$origin->expeditions);exit;
1633 if (count($origin_object->expeditions) <= 0) {
1635 }
1636 }
1637 }
1638
1639 if (!$error) {
1640 $this->db->commit();
1641
1642 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1643 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1644 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1645
1646 // We delete PDFs
1647 $ref = dol_sanitizeFileName($this->ref);
1648 if (!empty($conf->expedition->dir_output)) {
1649 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1650 $file = $dir.'/'.$ref.'.pdf';
1651 if (file_exists($file)) {
1652 if (!dol_delete_file($file)) {
1653 return 0;
1654 }
1655 }
1656 if (file_exists($dir)) {
1657 if (!dol_delete_dir_recursive($dir)) {
1658 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1659 return 0;
1660 }
1661 }
1662 }
1663
1664 return 1;
1665 } else {
1666 $this->db->rollback();
1667 return -1;
1668 }
1669 } else {
1670 $this->error = $this->db->lasterror()." - sql=$sql";
1671 $this->db->rollback();
1672 return -3;
1673 }
1674 } else {
1675 $this->error = $this->db->lasterror()." - sql=$sql";
1676 $this->db->rollback();
1677 return -2;
1678 }
1679 } else {
1680 $this->error = $this->db->lasterror()." - sql=$sql";
1681 $this->db->rollback();
1682 return -1;
1683 }
1684 } else {
1685 $this->db->rollback();
1686 return -1;
1687 }
1688 }
1689
1690 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1696 public function fetch_lines()
1697 {
1698 // phpcs:enable
1699 global $mysoc;
1700
1701 $this->lines = array();
1702
1703 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1704 // TODO: See if we can restore a common fetch_lines (one line = one record)
1705
1706 $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";
1707 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1708 $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
1709 $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";
1710 $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";
1711 $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot";
1712 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
1713 $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";
1714 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1715 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1716 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1717 $sql .= " AND ed.fk_elementdet = cd.rowid";
1718 $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.
1719
1720 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1721 $resql = $this->db->query($sql);
1722 if ($resql) {
1723 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1724
1725 $num = $this->db->num_rows($resql);
1726 $i = 0;
1727 $lineindex = 0;
1728 $originline = 0;
1729
1730 $this->total_ht = 0;
1731 $this->total_tva = 0;
1732 $this->total_ttc = 0;
1733 $this->total_localtax1 = 0;
1734 $this->total_localtax2 = 0;
1735
1736 $this->multicurrency_total_ht = 0;
1737 $this->multicurrency_total_tva = 0;
1738 $this->multicurrency_total_ttc = 0;
1739
1740 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1741
1742 while ($i < $num) {
1743 $obj = $this->db->fetch_object($resql);
1744
1745
1746 if ($originline > 0 && $originline == $obj->fk_elementdet) {
1747 '@phan-var-force ExpeditionLigne $line'; // $line from previous loop
1748 $line->entrepot_id = 0; // entrepod_id in details_entrepot
1749 $line->qty_shipped += $obj->qty_shipped;
1750 } else {
1751 $line = new ExpeditionLigne($this->db); // new group to start
1752 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
1753 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
1754 }
1755
1756 $detail_entrepot = new stdClass();
1757 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1758 $detail_entrepot->qty_shipped = $obj->qty_shipped;
1759 $detail_entrepot->line_id = $obj->line_id;
1760 $line->details_entrepot[] = $detail_entrepot;
1761
1762 $line->line_id = $obj->line_id; // TODO deprecated
1763 $line->rowid = $obj->line_id; // TODO deprecated
1764 $line->id = $obj->line_id;
1765
1766 $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
1767
1768 $line->fk_element = $obj->fk_element;
1769 $line->origin_id = $obj->fk_element;
1770 $line->fk_elementdet = $obj->fk_elementdet;
1771 $line->origin_line_id = $obj->fk_elementdet;
1772 $line->element_type = $obj->element_type;
1773
1774 $line->fk_expedition = $this->id; // id of parent
1775
1776 $line->product_type = $obj->product_type;
1777 $line->fk_product = $obj->fk_product;
1778 $line->fk_product_type = $obj->fk_product_type;
1779 $line->ref = $obj->product_ref; // TODO deprecated
1780 $line->product_ref = $obj->product_ref;
1781 $line->product_label = $obj->product_label;
1782 $line->libelle = $obj->product_label; // TODO deprecated
1783 $line->product_barcode = $obj->product_barcode; // Barcode number product
1784 $line->product_tosell = $obj->product_tosell;
1785 $line->product_tobuy = $obj->product_tobuy;
1786 $line->product_tobatch = $obj->product_tobatch;
1787 $line->fk_fournprice = $obj->fk_fournprice;
1788 $line->label = $obj->custom_label;
1789 $line->description = $obj->description;
1790 $line->qty_asked = $obj->qty_asked;
1791 $line->rang = $obj->rang;
1792 $line->weight = $obj->weight;
1793 $line->weight_units = $obj->weight_units;
1794 $line->length = $obj->length;
1795 $line->length_units = $obj->length_units;
1796 $line->width = $obj->width;
1797 $line->width_units = $obj->width_units;
1798 $line->height = $obj->height;
1799 $line->height_units = $obj->height_units;
1800 $line->surface = $obj->surface;
1801 $line->surface_units = $obj->surface_units;
1802 $line->volume = $obj->volume;
1803 $line->volume_units = $obj->volume_units;
1804 $line->fk_unit = $obj->fk_unit;
1805
1806 $line->pa_ht = $obj->pa_ht;
1807
1808 // Local taxes
1809 $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
1810 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1811 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1812
1813 // For invoicing
1814 $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
1815 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1816 $line->qty = $line->qty_shipped;
1817 $line->total_ht = (float) $tabprice[0];
1818 $line->total_localtax1 = (float) $tabprice[9];
1819 $line->total_localtax2 = (float) $tabprice[10];
1820 $line->total_ttc = (float) $tabprice[2];
1821 $line->total_tva = (float) $tabprice[1];
1822 $line->vat_src_code = $obj->vat_src_code;
1823 $line->tva_tx = $obj->tva_tx;
1824 $line->localtax1_tx = $obj->localtax1_tx;
1825 $line->localtax2_tx = $obj->localtax2_tx;
1826 $line->info_bits = $obj->info_bits;
1827 $line->price = $obj->price;
1828 $line->subprice = $obj->subprice;
1829 $line->fk_remise_except = $obj->fk_remise_except;
1830 $line->remise_percent = $obj->remise_percent;
1831
1832 $this->total_ht += $tabprice[0];
1833 $this->total_tva += $tabprice[1];
1834 $this->total_ttc += $tabprice[2];
1835 $this->total_localtax1 += $tabprice[9];
1836 $this->total_localtax2 += $tabprice[10];
1837
1838 $line->date_start = $this->db->jdate($obj->date_start);
1839 $line->date_end = $this->db->jdate($obj->date_end);
1840
1841 // Multicurrency
1842 $this->fk_multicurrency = $obj->fk_multicurrency;
1843 $this->multicurrency_code = $obj->multicurrency_code;
1844 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1845 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1846 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1847 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1848
1849 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
1850 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
1851 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
1852
1853 if ($originline != $obj->fk_elementdet) {
1854 $line->detail_batch = array();
1855 }
1856
1857 // Detail of batch
1858 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1859 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1860
1861 if (is_array($newdetailbatch)) {
1862 if ($originline != $obj->fk_elementdet) {
1863 $line->detail_batch = $newdetailbatch;
1864 } else {
1865 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1866 }
1867 }
1868 }
1869
1870 $line->fetch_optionals();
1871
1872 if ($originline != $obj->fk_elementdet) {
1873 $this->lines[$lineindex] = $line;
1874 $lineindex++;
1875 } else {
1876 $line->total_ht += $tabprice[0];
1877 $line->total_localtax1 += $tabprice[9];
1878 $line->total_localtax2 += $tabprice[10];
1879 $line->total_ttc += $tabprice[2];
1880 $line->total_tva += $tabprice[1];
1881 }
1882
1883 $i++;
1884 $originline = $obj->fk_elementdet;
1885 }
1886 $this->db->free($resql);
1887 return 1;
1888 } else {
1889 $this->error = $this->db->error();
1890 return -3;
1891 }
1892 }
1893
1901 public function deleteLine($user, $lineid)
1902 {
1903 global $user;
1904
1905 if ($this->statut == self::STATUS_DRAFT) {
1906 $this->db->begin();
1907
1908 $line = new ExpeditionLigne($this->db);
1909
1910 // For triggers
1911 $line->fetch($lineid);
1912
1913 if ($line->delete($user) > 0) {
1914 //$this->update_price(1);
1915
1916 $this->db->commit();
1917 return 1;
1918 } else {
1919 $this->db->rollback();
1920 return -1;
1921 }
1922 } else {
1923 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1924 return -2;
1925 }
1926 }
1927
1928
1935 public function getTooltipContentArray($params)
1936 {
1937 global $conf, $langs;
1938
1939 $langs->load('sendings');
1940
1941 $nofetch = !empty($params['nofetch']);
1942
1943 $datas = array();
1944 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
1945 if (isset($this->statut)) {
1946 $datas['picto'] .= ' '.$this->getLibStatut(5);
1947 }
1948 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1949 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1950 if (!$nofetch) {
1951 $langs->load('companies');
1952 if (empty($this->thirdparty)) {
1953 $this->fetch_thirdparty();
1954 }
1955 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1956 }
1957
1958 return $datas;
1959 }
1960
1972 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1973 {
1974 global $langs, $hookmanager;
1975
1976 $result = '';
1977 $params = [
1978 'id' => $this->id,
1979 'objecttype' => $this->element,
1980 'option' => $option,
1981 'nofetch' => 1,
1982 ];
1983 $classfortooltip = 'classfortooltip';
1984 $dataparams = '';
1985 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1986 $classfortooltip = 'classforajaxtooltip';
1987 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1988 $label = '';
1989 } else {
1990 $label = implode($this->getTooltipContentArray($params));
1991 }
1992
1993 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1994
1995 if ($short) {
1996 return $url;
1997 }
1998
1999 if ($option !== 'nolink') {
2000 // Add param to save lastsearch_values or not
2001 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2002 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2003 $add_save_lastsearch_values = 1;
2004 }
2005 if ($add_save_lastsearch_values) {
2006 $url .= '&save_lastsearch_values=1';
2007 }
2008 }
2009
2010 $linkclose = '';
2011 if (empty($notooltip)) {
2012 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2013 $label = $langs->trans("Shipment");
2014 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2015 }
2016 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2017 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2018 }
2019
2020 $linkstart = '<a href="'.$url.'"';
2021 $linkstart .= $linkclose.'>';
2022 $linkend = '</a>';
2023
2024 $result .= $linkstart;
2025 if ($withpicto) {
2026 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2027 }
2028 if ($withpicto != 2) {
2029 $result .= $this->ref;
2030 }
2031 $result .= $linkend;
2032 global $action;
2033 $hookmanager->initHooks(array($this->element . 'dao'));
2034 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2035 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2036 if ($reshook > 0) {
2037 $result = $hookmanager->resPrint;
2038 } else {
2039 $result .= $hookmanager->resPrint;
2040 }
2041 return $result;
2042 }
2043
2050 public function getLibStatut($mode = 0)
2051 {
2052 return $this->LibStatut($this->statut, $mode);
2053 }
2054
2055 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2063 public function LibStatut($status, $mode)
2064 {
2065 // phpcs:enable
2066 global $langs;
2067
2068 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
2069 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
2070
2071 $statusType = 'status'.$status;
2072 if ($status == self::STATUS_VALIDATED) {
2073 $statusType = 'status4';
2074 }
2075 if ($status == self::STATUS_CLOSED) {
2076 $statusType = 'status6';
2077 }
2078 if ($status == self::STATUS_CANCELED) {
2079 $statusType = 'status9';
2080 }
2081
2082 $signed_label = ' (' . $this->getLibSignedStatus() . ')';
2083 $status_label = $this->signed_status ? $labelStatus . $signed_label : $labelStatus;
2084 $status_label_short = $this->signed_status ? $labelStatusShort . $signed_label : $labelStatusShort;
2085
2086 return dolGetStatus($status_label, $status_label_short, '', $statusType, $mode);
2087 }
2088
2096 public function getKanbanView($option = '', $arraydata = null)
2097 {
2098 global $langs, $conf;
2099
2100 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2101
2102 $return = '<div class="box-flex-item box-flex-grow-zero">';
2103 $return .= '<div class="info-box info-box-sm">';
2104 $return .= '<div class="info-box-icon bg-infobox-action">';
2105 $return .= img_picto('', 'order');
2106 $return .= '</div>';
2107 $return .= '<div class="info-box-content">';
2108 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
2109 if ($selected >= 0) {
2110 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2111 }
2112 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
2113 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
2114 }
2115 if (property_exists($this, 'total_ht')) {
2116 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'</div>';
2117 }
2118 if (method_exists($this, 'getLibStatut')) {
2119 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2120 }
2121 $return .= '</div>';
2122 $return .= '</div>';
2123 $return .= '</div>';
2124
2125 return $return;
2126 }
2127
2135 public function initAsSpecimen()
2136 {
2137 global $langs;
2138
2139 $now = dol_now();
2140
2141 dol_syslog(get_class($this)."::initAsSpecimen");
2142
2143 $order = new Commande($this->db);
2144 $order->initAsSpecimen();
2145
2146 // Initialise parameters
2147 $this->id = 0;
2148 $this->ref = 'SPECIMEN';
2149 $this->specimen = 1;
2150 $this->statut = self::STATUS_VALIDATED;
2151 $this->livraison_id = 0;
2152 $this->date = $now;
2153 $this->date_creation = $now;
2154 $this->date_valid = $now;
2155 $this->date_delivery = $now + 24 * 3600;
2156 $this->date_expedition = $now + 24 * 3600;
2157
2158 $this->entrepot_id = 0;
2159 $this->fk_delivery_address = 0;
2160 $this->socid = 1;
2161
2162 $this->commande_id = 0;
2163 $this->commande = $order;
2164
2165 $this->origin_id = 1;
2166 $this->origin = 'commande';
2167
2168 $this->note_private = 'Private note';
2169 $this->note_public = 'Public note';
2170
2171 $nbp = min(1000, GETPOSTINT('nblines') ? GETPOSTINT('nblines') : 5); // We can force the nb of lines to test from command line (but not more than 1000)
2172 $xnbp = 0;
2173 while ($xnbp < $nbp) {
2174 $line = new ExpeditionLigne($this->db);
2175 $line->product_desc = $langs->trans("Description")." ".$xnbp;
2176 $line->product_label = $langs->trans("Description")." ".$xnbp;
2177 $line->qty = 10;
2178 $line->qty_asked = 5;
2179 $line->qty_shipped = 4;
2180 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2181
2182 $line->weight = 1.123456;
2183 $line->weight_units = 0; // kg
2184
2185 $line->volume = 2.34567;
2186 $line->volume_unit = 0;
2187
2188 $this->lines[] = $line;
2189 $xnbp++;
2190 }
2191
2192 return 1;
2193 }
2194
2195 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2204 public function set_date_livraison($user, $delivery_date)
2205 {
2206 // phpcs:enable
2207 return $this->setDeliveryDate($user, $delivery_date);
2208 }
2209
2217 public function setDeliveryDate($user, $delivery_date)
2218 {
2219 if ($user->hasRight('expedition', 'creer')) {
2220 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2221 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2222 $sql .= " WHERE rowid = ".((int) $this->id);
2223
2224 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2225 $resql = $this->db->query($sql);
2226 if ($resql) {
2227 $this->date_delivery = $delivery_date;
2228 return 1;
2229 } else {
2230 $this->error = $this->db->error();
2231 return -1;
2232 }
2233 } else {
2234 return -2;
2235 }
2236 }
2237
2245 public function setShippingDate($user, $shipping_date)
2246 {
2247 if ($user->hasRight('expedition', 'creer')) {
2248 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2249 $sql .= " SET date_expedition = ".($shipping_date ? "'".$this->db->idate($shipping_date)."'" : 'null');
2250 $sql .= " WHERE rowid = ".((int) $this->id);
2251
2252 dol_syslog(get_class($this)."::setShippingDate", LOG_DEBUG);
2253 $resql = $this->db->query($sql);
2254 if ($resql) {
2255 $this->date_shipping = $shipping_date;
2256 return 1;
2257 } else {
2258 $this->error = $this->db->error();
2259 return -1;
2260 }
2261 } else {
2262 return -2;
2263 }
2264 }
2265
2266 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2272 public function fetch_delivery_methods()
2273 {
2274 // phpcs:enable
2275 global $langs;
2276 $this->meths = array();
2277
2278 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2279 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2280 $sql .= " WHERE em.active = 1";
2281 $sql .= " ORDER BY em.libelle ASC";
2282
2283 $resql = $this->db->query($sql);
2284 if ($resql) {
2285 while ($obj = $this->db->fetch_object($resql)) {
2286 $label = $langs->trans('SendingMethod'.$obj->code);
2287 $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2288 }
2289 }
2290 }
2291
2292 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2299 public function list_delivery_methods($id = 0)
2300 {
2301 // phpcs:enable
2302 global $langs;
2303
2304 $this->listmeths = array();
2305 $i = 0;
2306
2307 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2308 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2309 if (!empty($id)) {
2310 $sql .= " WHERE em.rowid=".((int) $id);
2311 }
2312
2313 $resql = $this->db->query($sql);
2314 if ($resql) {
2315 while ($obj = $this->db->fetch_object($resql)) {
2316 $this->listmeths[$i]['rowid'] = $obj->rowid;
2317 $this->listmeths[$i]['code'] = $obj->code;
2318 $label = $langs->trans('SendingMethod'.$obj->code);
2319 $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2320 $this->listmeths[$i]['description'] = $obj->description;
2321 $this->listmeths[$i]['tracking'] = $obj->tracking;
2322 $this->listmeths[$i]['active'] = $obj->active;
2323 $i++;
2324 }
2325 }
2326 }
2327
2334 public function getUrlTrackingStatus($value = '')
2335 {
2336 if (!empty($this->shipping_method_id)) {
2337 $sql = "SELECT em.code, em.tracking";
2338 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2339 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2340
2341 $resql = $this->db->query($sql);
2342 if ($resql) {
2343 if ($obj = $this->db->fetch_object($resql)) {
2344 $tracking = $obj->tracking;
2345 }
2346 }
2347 }
2348
2349 if (!empty($tracking) && !empty($value)) {
2350 $url = str_replace('{TRACKID}', $value, $tracking);
2351 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
2352 } else {
2353 $this->tracking_url = $value;
2354 }
2355 }
2356
2362 public function setClosed()
2363 {
2364 global $user;
2365
2366 $error = 0;
2367
2368 // Protection. This avoid to move stock later when we should not
2369 if ($this->statut == self::STATUS_CLOSED) {
2370 return 0;
2371 }
2372
2373 $this->db->begin();
2374
2375 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED.", date_expedition = '".$this->db->escape($this->db->idate(dol_now()))."'";
2376 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2377
2378 $resql = $this->db->query($sql);
2379 if ($resql) {
2380 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2381 if ($this->origin == 'commande' && $this->origin_id > 0) {
2382 $order = new Commande($this->db);
2383 $order->fetch($this->origin_id);
2384
2385 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2386
2387 $shipments_match_order = 1;
2388 foreach ($order->lines as $line) {
2389 $lineid = $line->id;
2390 $qty = $line->qty;
2391 if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2392 $shipments_match_order = 0;
2393 $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';
2394 dol_syslog($text);
2395 break;
2396 }
2397 }
2398 if ($shipments_match_order) {
2399 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');
2400 // We close the order
2401 $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2402 }
2403 }
2404
2405 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2406 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2407
2408 // If stock increment is done on closing
2409 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2410 $result = $this->manageStockMvtOnEvt($user);
2411 if ($result < 0) {
2412 $error++;
2413 }
2414 }
2415
2416 // Call trigger
2417 if (!$error) {
2418 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2419 if ($result < 0) {
2420 $error++;
2421 }
2422 }
2423 } else {
2424 dol_print_error($this->db);
2425 $error++;
2426 }
2427
2428 if (!$error) {
2429 $this->db->commit();
2430 return 1;
2431 } else {
2432 $this->statut = self::STATUS_VALIDATED;
2434
2435 $this->db->rollback();
2436 return -1;
2437 }
2438 }
2439
2449 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2450 {
2451 global $langs;
2452
2453 $error = 0;
2454
2455 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2456
2457 $langs->load("agenda");
2458
2459 // Loop on each product line to add a stock movement
2460 $sql = "SELECT cd.fk_product, cd.subprice,";
2461 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2462 $sql .= " e.ref,";
2463 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2464 $sql .= " cd.rowid as cdid, ed.rowid as edid";
2465 $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2466 $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2467 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2468 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2469 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2470 $sql .= " AND cd.rowid = ed.fk_elementdet";
2471
2472 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2473 $resql = $this->db->query($sql);
2474 if ($resql) {
2475 $cpt = $this->db->num_rows($resql);
2476 for ($i = 0; $i < $cpt; $i++) {
2477 $obj = $this->db->fetch_object($resql);
2478 if (empty($obj->edbrowid)) {
2479 $qty = $obj->qty;
2480 } else {
2481 $qty = $obj->edbqty;
2482 }
2483 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2484 continue;
2485 }
2486 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2487
2488 $mouvS = new MouvementStock($this->db);
2489 $mouvS->origin = &$this;
2490 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2491
2492 if (empty($obj->edbrowid)) {
2493 // line without batch detail
2494
2495 // 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
2496 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
2497 if ($result < 0) {
2498 $this->error = $mouvS->error;
2499 $this->errors = $mouvS->errors;
2500 $error++;
2501 break;
2502 }
2503 } else {
2504 // line with batch detail
2505
2506 // 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
2507 $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);
2508 if ($result < 0) {
2509 $this->error = $mouvS->error;
2510 $this->errors = $mouvS->errors;
2511 $error++;
2512 break;
2513 }
2514 }
2515
2516 // 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
2517 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2518 $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)";
2519 $resqldelete = $this->db->query($sqldelete);
2520 // We do not test error, it can fails if there is child in batch details
2521 }
2522 } else {
2523 $this->error = $this->db->lasterror();
2524 $this->errors[] = $this->db->lasterror();
2525 $error++;
2526 }
2527
2528 if (!$error) {
2529 return 1;
2530 } else {
2531 return -1;
2532 }
2533 }
2534
2540 public function setBilled()
2541 {
2542 global $user;
2543 $error = 0;
2544
2545 $this->db->begin();
2546
2547 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET billed = 1';
2548 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2549
2550 $resql = $this->db->query($sql);
2551 if ($resql) {
2552 $this->billed = 1;
2553
2554 // Call trigger
2555 $result = $this->call_trigger('SHIPPING_BILLED', $user);
2556 if ($result < 0) {
2557 $this->billed = 0;
2558 $error++;
2559 }
2560 } else {
2561 $error++;
2562 $this->errors[] = $this->db->lasterror;
2563 }
2564
2565 if (empty($error)) {
2566 $this->db->commit();
2567 return 1;
2568 } else {
2569 $this->db->rollback();
2570 return -1;
2571 }
2572 }
2573
2581 public function setDraft($user, $notrigger = 0)
2582 {
2583 // Protection
2584 if ($this->statut <= self::STATUS_DRAFT) {
2585 return 0;
2586 }
2587
2588 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2589 }
2590
2596 public function reOpen()
2597 {
2598 global $langs, $user;
2599
2600 $error = 0;
2601
2602 // Protection. This avoid to move stock later when we should not
2603 if ($this->statut == self::STATUS_VALIDATED) {
2604 return 0;
2605 }
2606
2607 $this->db->begin();
2608
2609 $oldbilled = $this->billed;
2610
2611 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2612 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2613
2614 $resql = $this->db->query($sql);
2615 if ($resql) {
2616 $this->statut = self::STATUS_VALIDATED;
2618 $this->billed = 0;
2619
2620 // If stock increment is done on closing
2621 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2622 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2623
2624 $langs->load("agenda");
2625
2626 // Loop on each product line to add a stock movement
2627 // TODO possibilite d'expedier a partir d'une propale ou autre origine
2628 $sql = "SELECT cd.fk_product, cd.subprice,";
2629 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2630 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2631 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2632 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2633 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2634 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2635 $sql .= " AND cd.rowid = ed.fk_elementdet";
2636
2637 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2638 $resql = $this->db->query($sql);
2639 if ($resql) {
2640 $cpt = $this->db->num_rows($resql);
2641 for ($i = 0; $i < $cpt; $i++) {
2642 $obj = $this->db->fetch_object($resql);
2643 if (empty($obj->edbrowid)) {
2644 $qty = $obj->qty;
2645 } else {
2646 $qty = $obj->edbqty;
2647 }
2648 if ($qty <= 0) {
2649 continue;
2650 }
2651 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2652
2653 //var_dump($this->lines[$i]);
2654 $mouvS = new MouvementStock($this->db);
2655 $mouvS->origin = &$this;
2656 $mouvS->setOrigin($this->element, $this->id);
2657
2658 if (empty($obj->edbrowid)) {
2659 // line without batch detail
2660
2661 // 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
2662 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2663 if ($result < 0) {
2664 $this->error = $mouvS->error;
2665 $this->errors = $mouvS->errors;
2666 $error++;
2667 break;
2668 }
2669 } else {
2670 // line with batch detail
2671
2672 // 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
2673 $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);
2674 if ($result < 0) {
2675 $this->error = $mouvS->error;
2676 $this->errors = $mouvS->errors;
2677 $error++;
2678 break;
2679 }
2680 }
2681 }
2682 } else {
2683 $this->error = $this->db->lasterror();
2684 $error++;
2685 }
2686 }
2687
2688 if (!$error) {
2689 // Call trigger
2690 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2691 if ($result < 0) {
2692 $error++;
2693 }
2694 }
2695 } else {
2696 $error++;
2697 $this->errors[] = $this->db->lasterror();
2698 }
2699
2700 if (!$error) {
2701 $this->db->commit();
2702 return 1;
2703 } else {
2704 $this->statut = self::STATUS_CLOSED;
2705 $this->status = self::STATUS_CLOSED;
2706 $this->billed = $oldbilled;
2707 $this->db->rollback();
2708 return -1;
2709 }
2710 }
2711
2723 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2724 {
2725 $outputlangs->load("products");
2726
2727 if (!dol_strlen($modele)) {
2728 $modele = 'rouget';
2729
2730 if (!empty($this->model_pdf)) {
2731 $modele = $this->model_pdf;
2732 } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
2733 $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
2734 }
2735 }
2736
2737 $modelpath = "core/modules/expedition/doc/";
2738
2739 $this->fetch_origin();
2740
2741 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2742 }
2743
2752 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2753 {
2754 $tables = array(
2755 'expedition'
2756 );
2757
2758 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2759 }
2760}
$object ref
Definition info.php:89
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.
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.
Class to manage receptions.
Class to manage Dolibarr database access.
getNomUrl($withpicto=0, $option='', $max=0, $short=0, $notooltip=0, $save_lastsearch_value=-1)
Return clickable link of object (with eventually picto)
create_delivery($user)
Create a delivery receipt from a shipment.
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 clickable link of object (with eventually picto)
setShippingDate($user, $shipping_date)
Set the shipping date.
const STATUS_DRAFT
Draft status.
const STATUS_CANCELED
Canceled status.
getLibStatut($mode=0)
Return status label.
set_date_livraison($user, $delivery_date)
Set delivery date.
initAsSpecimen()
Initialise an instance with random values.
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.
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.
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 to manage third parties objects (customers, suppliers, prospects...)
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
getLibSignedStatus(int $mode=0)
Returns the label for signed status.
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_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
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
setEntity($currentobject)
Set entity id to use when to create an object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return a 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.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
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:90
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:154