dolibarr 21.0.0-beta
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 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
865 if (!$ret) {
866 $error++;
867 }
868
869 if (!$error && !$notrigger) {
870 // Call trigger
871 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
872 if ($result < 0) {
873 $error++;
874 }
875 // End call triggers
876 }
877
878 if (!$error) {
879 $this->oldref = $this->ref;
880
881 // Rename directory if dir was a temporary ref
882 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
883 // Now we rename also files into index
884 $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)."'";
885 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
886 $resql = $this->db->query($sql);
887 if (!$resql) {
888 $error++;
889 $this->error = $this->db->lasterror();
890 }
891 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
892 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
893 $resql = $this->db->query($sql);
894 if (!$resql) {
895 $error++;
896 $this->error = $this->db->lasterror();
897 }
898
899 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
900 $oldref = dol_sanitizeFileName($this->ref);
901 $newref = dol_sanitizeFileName($numref);
902 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
903 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
904 if (!$error && file_exists($dirsource)) {
905 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
906
907 if (@rename($dirsource, $dirdest)) {
908 dol_syslog("Rename ok");
909 // Rename docs starting with $oldref with $newref
910 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
911 foreach ($listoffiles as $fileentry) {
912 $dirsource = $fileentry['name'];
913 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
914 $dirsource = $fileentry['path'].'/'.$dirsource;
915 $dirdest = $fileentry['path'].'/'.$dirdest;
916 @rename($dirsource, $dirdest);
917 }
918 }
919 }
920 }
921 }
922
923 // Set new ref and current status
924 if (!$error) {
925 $this->ref = $numref;
926 $this->statut = self::STATUS_VALIDATED;
928 }
929
930 if (!$error) {
931 $this->db->commit();
932 return 1;
933 } else {
934 $this->db->rollback();
935 return -1 * $error;
936 }
937 }
938
939
940 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
947 public function create_delivery($user)
948 {
949 // phpcs:enable
950 global $conf;
951
952 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
953 if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
954 // Expedition validee
955 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
956 $delivery = new Delivery($this->db);
957 $result = $delivery->create_from_sending($user, $this->id);
958 if ($result > 0) {
959 return $result;
960 } else {
961 $this->error = $delivery->error;
962 return $result;
963 }
964 } else {
965 return 0;
966 }
967 } else {
968 return 0;
969 }
970 }
971
984 public function addline($entrepot_id, $id, $qty, $array_options = [])
985 {
986 global $conf, $langs;
987
988 $num = count($this->lines);
989 $line = new ExpeditionLigne($this->db);
990
991 $line->entrepot_id = $entrepot_id;
992 $line->origin_line_id = $id;
993 $line->fk_elementdet = $id;
994 $line->element_type = 'order';
995 $line->qty = $qty;
996
997 $orderline = new OrderLine($this->db);
998 $orderline->fetch($id);
999
1000 // Copy the rang of the order line to the expedition line
1001 $line->rang = $orderline->rang;
1002 $line->product_type = $orderline->product_type;
1003
1004 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
1005 $fk_product = $orderline->fk_product;
1006
1007 if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS') && !(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') && $line->product_type == Product::TYPE_SERVICE)) {
1008 $langs->load("errors");
1009 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
1010 return -1;
1011 }
1012
1013 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
1014 $product = new Product($this->db);
1015 $product->fetch($fk_product);
1016
1017 // Check must be done for stock of product into warehouse if $entrepot_id defined
1018 if ($entrepot_id > 0) {
1019 $product->load_stock('warehouseopen');
1020 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
1021 } else {
1022 $product_stock = $product->stock_reel;
1023 }
1024
1025 $product_type = $product->type;
1026 if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1027 $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
1028 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
1029 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.
1030 if ($product_stock < $qty) {
1031 $langs->load("errors");
1032 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
1033 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
1034
1035 $this->db->rollback();
1036 return -3;
1037 }
1038 }
1039 }
1040 }
1041 }
1042
1043 // If product need a batch number, we should not have called this function but addline_batch instead.
1044 // If this happen, we may have a bug in card.php page
1045 if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
1046 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; //
1047 return -4;
1048 }
1049
1050 // extrafields
1051 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1052 $line->array_options = $array_options;
1053 }
1054
1055 $this->lines[$num] = $line;
1056
1057 return 1;
1058 }
1059
1060 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1068 public function addline_batch($dbatch, $array_options = [])
1069 {
1070 // phpcs:enable
1071 global $conf, $langs;
1072
1073 $num = count($this->lines);
1074 $linebatch = null;
1075 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1076 $line = new ExpeditionLigne($this->db);
1077 $tab = array();
1078 foreach ($dbatch['detail'] as $key => $value) {
1079 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1080 // $value['q']=qty to move
1081 // $value['id_batch']=id into llx_product_batch of record to move
1082 //var_dump($value);
1083
1084 $linebatch = new ExpeditionLineBatch($this->db);
1085 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1086 if ($ret < 0) {
1087 $this->setErrorsFromObject($linebatch);
1088 return -1;
1089 }
1090 $linebatch->qty = $value['q'];
1091 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1092 $linebatch->batch = null;
1093 }
1094 $tab[] = $linebatch;
1095
1096 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1097 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1098 $prod_batch = new Productbatch($this->db);
1099 $prod_batch->fetch($value['id_batch']);
1100
1101 if ($prod_batch->qty < $linebatch->qty) {
1102 $langs->load("errors");
1103 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1104 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1105 $this->db->rollback();
1106 return -1;
1107 }
1108 }
1109
1110 //var_dump($linebatch);
1111 }
1112 }
1113 if (is_object($linebatch)) {
1114 $line->entrepot_id = $linebatch->entrepot_id;
1115 }
1116 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1117 $line->fk_elementdet = $dbatch['ix_l'];
1118 $line->qty = $dbatch['qty'];
1119 $line->detail_batch = $tab;
1120
1121 // extrafields
1122 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1123 $line->array_options = $array_options;
1124 }
1125
1126 //var_dump($line);
1127 $this->lines[$num] = $line;
1128 return 1;
1129 }
1130 return 0;
1131 }
1132
1140 public function update($user = null, $notrigger = 0)
1141 {
1142 global $conf;
1143 $error = 0;
1144
1145 // Clean parameters
1146
1147 if (isset($this->ref)) {
1148 $this->ref = trim($this->ref);
1149 }
1150 if (isset($this->entity)) {
1151 $this->entity = (int) $this->entity;
1152 }
1153 if (isset($this->ref_customer)) {
1154 $this->ref_customer = trim($this->ref_customer);
1155 }
1156 if (isset($this->socid)) {
1157 $this->socid = (int) $this->socid;
1158 }
1159 if (isset($this->fk_user_author)) {
1160 $this->fk_user_author = (int) $this->fk_user_author;
1161 }
1162 if (isset($this->fk_user_valid)) { // @phan-ignore-current-line PhanUndeclaredProperty
1163 // If set, then accept @phan-ignore-next-line PhanUndeclaredProperty
1164 $this->fk_user_valid = (int) $this->fk_user_valid;
1165 }
1166 if (isset($this->fk_delivery_address)) {
1167 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1168 }
1169 if (isset($this->shipping_method_id)) {
1170 $this->shipping_method_id = (int) $this->shipping_method_id;
1171 }
1172 if (isset($this->tracking_number)) {
1173 $this->tracking_number = trim($this->tracking_number);
1174 }
1175 if (isset($this->statut)) {
1176 $this->statut = (int) $this->statut;
1177 }
1178 if (isset($this->trueDepth)) {
1179 $this->trueDepth = trim($this->trueDepth);
1180 }
1181 if (isset($this->trueWidth)) {
1182 $this->trueWidth = trim($this->trueWidth);
1183 }
1184 if (isset($this->trueHeight)) {
1185 $this->trueHeight = trim($this->trueHeight);
1186 }
1187 if (isset($this->size_units)) {
1188 $this->size_units = trim($this->size_units);
1189 }
1190 if (isset($this->weight_units)) {
1191 $this->weight_units = (int) $this->weight_units;
1192 }
1193 if (isset($this->trueWeight)) {
1194 $this->weight = trim((string) $this->trueWeight);
1195 }
1196 if (isset($this->note_private)) {
1197 $this->note_private = trim($this->note_private);
1198 }
1199 if (isset($this->note_public)) {
1200 $this->note_public = trim($this->note_public);
1201 }
1202 if (isset($this->model_pdf)) {
1203 $this->model_pdf = trim($this->model_pdf);
1204 }
1205 if (!empty($this->date_expedition)) {
1206 $this->date_shipping = $this->date_expedition;
1207 }
1208
1209 // Check parameters
1210 // Put here code to add control on parameters values
1211
1212 // Update request
1213 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1214 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1215 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1216 $sql .= " ref_customer = ".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1217 $sql .= " fk_soc = ".(isset($this->socid) ? $this->socid : "null").",";
1218 $sql .= " date_creation = ".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1219 $sql .= " fk_user_author = ".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1220 $sql .= " date_valid = ".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1221 $sql .= " fk_user_valid = ".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1222 $sql .= " date_expedition = ".(dol_strlen($this->date_shipping) != 0 ? "'".$this->db->idate($this->date_shipping)."'" : 'null').",";
1223 $sql .= " date_delivery = ".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1224 $sql .= " fk_address = ".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1225 $sql .= " fk_shipping_method = ".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1226 $sql .= " tracking_number = ".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1227 $sql .= " fk_statut = ".(isset($this->statut) ? $this->statut : "null").",";
1228 $sql .= " fk_projet = ".(isset($this->fk_project) ? $this->fk_project : "null").",";
1229 $sql .= " height = ".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1230 $sql .= " width = ".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1231 $sql .= " size_units = ".(isset($this->size_units) ? $this->size_units : "null").",";
1232 $sql .= " size = ".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1233 $sql .= " weight_units = ".(isset($this->weight_units) ? $this->weight_units : "null").",";
1234 $sql .= " weight = ".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1235 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1236 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1237 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1238 $sql .= " entity = ".((int) $conf->entity);
1239 $sql .= " WHERE rowid = ".((int) $this->id);
1240
1241 $this->db->begin();
1242
1243 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1244 $resql = $this->db->query($sql);
1245 if (!$resql) {
1246 $error++;
1247 $this->errors[] = "Error ".$this->db->lasterror();
1248 }
1249
1250 // Actions on extra fields
1251 if (!$error) {
1252 $result = $this->insertExtraFields();
1253 if ($result < 0) {
1254 $error++;
1255 }
1256 }
1257
1258 if (!$error && !$notrigger) {
1259 // Call trigger
1260 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1261 if ($result < 0) {
1262 $error++;
1263 }
1264 // End call triggers
1265 }
1266
1267 // Commit or rollback
1268 if ($error) {
1269 foreach ($this->errors as $errmsg) {
1270 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1271 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1272 }
1273 $this->db->rollback();
1274 return -1 * $error;
1275 } else {
1276 $this->db->commit();
1277 return 1;
1278 }
1279 }
1280
1281
1289 public function cancel($notrigger = 0, $also_update_stock = false)
1290 {
1291 global $conf, $langs, $user;
1292
1293 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1294
1295 $error = 0;
1296 $this->error = '';
1297
1298 $this->db->begin();
1299
1300 // Add a protection to refuse deleting if shipment has at least one delivery
1301 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1302 if (count($this->linkedObjectsIds) > 0) {
1303 $this->error = 'ErrorThereIsSomeDeliveries';
1304 $error++;
1305 }
1306
1307 if (!$error && !$notrigger) {
1308 // Call trigger
1309 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1310 if ($result < 0) {
1311 $error++;
1312 }
1313 // End call triggers
1314 }
1315
1316 // Stock control
1317 if (!$error && isModEnabled('stock') &&
1318 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1319 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1320 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1321
1322 $langs->load("agenda");
1323
1324 // Loop on each product line to add a stock movement and delete features
1325 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1326 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1327 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1328 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1329 $sql .= " AND cd.rowid = ed.fk_elementdet";
1330
1331 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1332 $resql = $this->db->query($sql);
1333 if ($resql) {
1334 $cpt = $this->db->num_rows($resql);
1335
1336 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1337
1338 for ($i = 0; $i < $cpt; $i++) {
1339 dol_syslog(get_class($this)."::delete movement index ".$i);
1340 $obj = $this->db->fetch_object($resql);
1341
1342 $mouvS = new MouvementStock($this->db);
1343 // we do not log origin because it will be deleted
1344 $mouvS->origin = '';
1345 // get lot/serial
1346 $lotArray = null;
1347 if (isModEnabled('productbatch')) {
1348 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1349 if (!is_array($lotArray)) {
1350 $error++;
1351 $this->errors[] = "Error ".$this->db->lasterror();
1352 }
1353 }
1354
1355 if (empty($lotArray)) {
1356 // no lot/serial
1357 // We increment stock of product (and sub-products)
1358 // We use warehouse selected for each line
1359 $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
1360 if ($result < 0) {
1361 $error++;
1362 $this->errors = array_merge($this->errors, $mouvS->errors);
1363 break;
1364 }
1365 } else {
1366 // We increment stock of batches
1367 // We use warehouse selected for each line
1368 foreach ($lotArray as $lot) {
1369 $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
1370 if ($result < 0) {
1371 $error++;
1372 $this->errors = array_merge($this->errors, $mouvS->errors);
1373 break;
1374 }
1375 }
1376 if ($error) {
1377 break; // break for loop in case of error
1378 }
1379 }
1380 }
1381 } else {
1382 $error++;
1383 $this->errors[] = "Error ".$this->db->lasterror();
1384 }
1385 }
1386
1387 // delete batch expedition line
1388 if (!$error && isModEnabled('productbatch')) {
1389 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1390 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1391 $error++;
1392 $this->errors[] = "Error ".$this->db->lasterror();
1393 }
1394 }
1395
1396
1397 if (!$error) {
1398 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1399 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1400
1401 if ($this->db->query($sql)) {
1402 // Delete linked object
1403 $res = $this->deleteObjectLinked();
1404 if ($res < 0) {
1405 $error++;
1406 }
1407
1408 // No delete expedition
1409 if (!$error) {
1410 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1411 $sql .= " WHERE rowid = ".((int) $this->id);
1412
1413 if ($this->db->query($sql)) {
1414 if (!empty($this->origin) && $this->origin_id > 0) {
1415 $this->fetch_origin();
1417 '@phan-var-force Facture|Commande $origin_object';
1418 if ($origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1419 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1420 $origin_object->loadExpeditions();
1421 //var_dump($this->$origin->expeditions);exit;
1422 if (count($origin_object->expeditions) <= 0) {
1424 }
1425 }
1426 }
1427
1428 if (!$error) {
1429 $this->db->commit();
1430
1431 // We delete PDFs
1432 $ref = dol_sanitizeFileName($this->ref);
1433 if (!empty($conf->expedition->dir_output)) {
1434 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1435 $file = $dir.'/'.$ref.'.pdf';
1436 if (file_exists($file)) {
1437 if (!dol_delete_file($file)) {
1438 return 0;
1439 }
1440 }
1441 if (file_exists($dir)) {
1442 if (!dol_delete_dir_recursive($dir)) {
1443 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1444 return 0;
1445 }
1446 }
1447 }
1448
1449 return 1;
1450 } else {
1451 $this->db->rollback();
1452 return -1;
1453 }
1454 } else {
1455 $this->error = $this->db->lasterror()." - sql=$sql";
1456 $this->db->rollback();
1457 return -3;
1458 }
1459 } else {
1460 $this->error = $this->db->lasterror()." - sql=$sql";
1461 $this->db->rollback();
1462 return -2;
1463 }//*/
1464 } else {
1465 $this->error = $this->db->lasterror()." - sql=$sql";
1466 $this->db->rollback();
1467 return -1;
1468 }
1469 } else {
1470 $this->db->rollback();
1471 return -1;
1472 }
1473 }
1474
1484 public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1485 {
1486 global $conf, $langs;
1487
1488 if (empty($user)) {
1489 global $user;
1490 }
1491
1492 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1493
1494 $error = 0;
1495 $this->error = '';
1496
1497 $this->db->begin();
1498
1499 // Add a protection to refuse deleting if shipment has at least one delivery
1500 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1501 if (count($this->linkedObjectsIds) > 0) {
1502 $this->error = 'ErrorThereIsSomeDeliveries';
1503 $error++;
1504 }
1505
1506 if (!$error && !$notrigger) {
1507 // Call trigger
1508 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1509 if ($result < 0) {
1510 $error++;
1511 }
1512 // End call triggers
1513 }
1514
1515 // Stock control
1516 if (!$error && isModEnabled('stock') &&
1517 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1518 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1519 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1520
1521 $langs->load("agenda");
1522
1523 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1524 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1525
1526 // Loop on each product line to add a stock movement
1527 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1528 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1529 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1530 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1531 $sql .= " AND cd.rowid = ed.fk_elementdet";
1532
1533 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1534 $resql = $this->db->query($sql);
1535 if ($resql) {
1536 $cpt = $this->db->num_rows($resql);
1537 for ($i = 0; $i < $cpt; $i++) {
1538 dol_syslog(get_class($this)."::delete movement index ".$i);
1539 $obj = $this->db->fetch_object($resql);
1540
1541 $mouvS = new MouvementStock($this->db);
1542 // we do not log origin because it will be deleted
1543 $mouvS->origin = '';
1544 // get lot/serial
1545 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1546 if (!is_array($lotArray)) {
1547 $error++;
1548 $this->errors[] = "Error ".$this->db->lasterror();
1549 }
1550 if (empty($lotArray)) {
1551 // no lot/serial
1552 // We increment stock of product (and sub-products)
1553 // We use warehouse selected for each line
1554 $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
1555 if ($result < 0) {
1556 $error++;
1557 $this->errors = array_merge($this->errors, $mouvS->errors);
1558 break;
1559 }
1560 } else {
1561 // We increment stock of batches
1562 // We use warehouse selected for each line
1563 foreach ($lotArray as $lot) {
1564 $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
1565 if ($result < 0) {
1566 $error++;
1567 $this->errors = array_merge($this->errors, $mouvS->errors);
1568 break;
1569 }
1570 }
1571 if ($error) {
1572 break; // break for loop in case of error
1573 }
1574 }
1575 }
1576 } else {
1577 $error++;
1578 $this->errors[] = "Error ".$this->db->lasterror();
1579 }
1580 }
1581
1582 // delete batch expedition line
1583 if (!$error) {
1584 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1585 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1586 $error++;
1587 $this->errors[] = "Error ".$this->db->lasterror();
1588 }
1589 }
1590
1591 if (!$error) {
1592 $main = MAIN_DB_PREFIX.'expeditiondet';
1593 $ef = $main."_extrafields";
1594 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
1595
1596 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1597 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1598
1599 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1600 // Delete linked object
1601 $res = $this->deleteObjectLinked();
1602 if ($res < 0) {
1603 $error++;
1604 }
1605
1606 // delete extrafields
1607 $res = $this->deleteExtraFields();
1608 if ($res < 0) {
1609 $error++;
1610 }
1611
1612 if (!$error) {
1613 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1614 $sql .= " WHERE rowid = ".((int) $this->id);
1615
1616 if ($this->db->query($sql)) {
1617 if (!empty($this->origin) && $this->origin_id > 0) {
1618 $this->fetch_origin();
1620 '@phan-var-force Facture|Commande $origin_object';
1621 if ($origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1622 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1623 $origin_object->loadExpeditions();
1624 //var_dump($this->$origin->expeditions);exit;
1625 if (count($origin_object->expeditions) <= 0) {
1627 }
1628 }
1629 }
1630
1631 if (!$error) {
1632 $this->db->commit();
1633
1634 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1635 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1636 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1637
1638 // We delete PDFs
1639 $ref = dol_sanitizeFileName($this->ref);
1640 if (!empty($conf->expedition->dir_output)) {
1641 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1642 $file = $dir.'/'.$ref.'.pdf';
1643 if (file_exists($file)) {
1644 if (!dol_delete_file($file)) {
1645 return 0;
1646 }
1647 }
1648 if (file_exists($dir)) {
1649 if (!dol_delete_dir_recursive($dir)) {
1650 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1651 return 0;
1652 }
1653 }
1654 }
1655
1656 return 1;
1657 } else {
1658 $this->db->rollback();
1659 return -1;
1660 }
1661 } else {
1662 $this->error = $this->db->lasterror()." - sql=$sql";
1663 $this->db->rollback();
1664 return -3;
1665 }
1666 } else {
1667 $this->error = $this->db->lasterror()." - sql=$sql";
1668 $this->db->rollback();
1669 return -2;
1670 }
1671 } else {
1672 $this->error = $this->db->lasterror()." - sql=$sql";
1673 $this->db->rollback();
1674 return -1;
1675 }
1676 } else {
1677 $this->db->rollback();
1678 return -1;
1679 }
1680 }
1681
1682 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1688 public function fetch_lines()
1689 {
1690 // phpcs:enable
1691 global $mysoc;
1692
1693 $this->lines = array();
1694
1695 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1696 // TODO: See if we can restore a common fetch_lines (one line = one record)
1697
1698 $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";
1699 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1700 $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
1701 $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";
1702 $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";
1703 $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot";
1704 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
1705 $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";
1706 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1707 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1708 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1709 $sql .= " AND ed.fk_elementdet = cd.rowid";
1710 $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.
1711
1712 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1713 $resql = $this->db->query($sql);
1714 if ($resql) {
1715 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1716
1717 $num = $this->db->num_rows($resql);
1718 $i = 0;
1719 $lineindex = 0;
1720 $originline = 0;
1721
1722 $this->total_ht = 0;
1723 $this->total_tva = 0;
1724 $this->total_ttc = 0;
1725 $this->total_localtax1 = 0;
1726 $this->total_localtax2 = 0;
1727
1728 $this->multicurrency_total_ht = 0;
1729 $this->multicurrency_total_tva = 0;
1730 $this->multicurrency_total_ttc = 0;
1731
1732 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1733
1734 while ($i < $num) {
1735 $obj = $this->db->fetch_object($resql);
1736
1737
1738 if ($originline > 0 && $originline == $obj->fk_elementdet) {
1739 '@phan-var-force ExpeditionLigne $line'; // $line from previous loop
1740 $line->entrepot_id = 0; // entrepod_id in details_entrepot
1741 $line->qty_shipped += $obj->qty_shipped;
1742 } else {
1743 $line = new ExpeditionLigne($this->db); // new group to start
1744 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
1745 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
1746 }
1747
1748 $detail_entrepot = new stdClass();
1749 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1750 $detail_entrepot->qty_shipped = $obj->qty_shipped;
1751 $detail_entrepot->line_id = $obj->line_id;
1752 $line->details_entrepot[] = $detail_entrepot;
1753
1754 $line->line_id = $obj->line_id; // TODO deprecated
1755 $line->rowid = $obj->line_id; // TODO deprecated
1756 $line->id = $obj->line_id;
1757
1758 $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
1759
1760 $line->fk_element = $obj->fk_element;
1761 $line->origin_id = $obj->fk_element;
1762 $line->fk_elementdet = $obj->fk_elementdet;
1763 $line->origin_line_id = $obj->fk_elementdet;
1764 $line->element_type = $obj->element_type;
1765
1766 $line->fk_expedition = $this->id; // id of parent
1767
1768 $line->product_type = $obj->product_type;
1769 $line->fk_product = $obj->fk_product;
1770 $line->fk_product_type = $obj->fk_product_type;
1771 $line->ref = $obj->product_ref; // TODO deprecated
1772 $line->product_ref = $obj->product_ref;
1773 $line->product_label = $obj->product_label;
1774 $line->libelle = $obj->product_label; // TODO deprecated
1775 $line->product_barcode = $obj->product_barcode; // Barcode number product
1776 $line->product_tosell = $obj->product_tosell;
1777 $line->product_tobuy = $obj->product_tobuy;
1778 $line->product_tobatch = $obj->product_tobatch;
1779 $line->fk_fournprice = $obj->fk_fournprice;
1780 $line->label = $obj->custom_label;
1781 $line->description = $obj->description;
1782 $line->qty_asked = $obj->qty_asked;
1783 $line->rang = $obj->rang;
1784 $line->weight = $obj->weight;
1785 $line->weight_units = $obj->weight_units;
1786 $line->length = $obj->length;
1787 $line->length_units = $obj->length_units;
1788 $line->width = $obj->width;
1789 $line->width_units = $obj->width_units;
1790 $line->height = $obj->height;
1791 $line->height_units = $obj->height_units;
1792 $line->surface = $obj->surface;
1793 $line->surface_units = $obj->surface_units;
1794 $line->volume = $obj->volume;
1795 $line->volume_units = $obj->volume_units;
1796 $line->fk_unit = $obj->fk_unit;
1797
1798 $line->pa_ht = $obj->pa_ht;
1799
1800 // Local taxes
1801 $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
1802 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1803 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1804
1805 // For invoicing
1806 $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
1807 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1808 $line->qty = $line->qty_shipped;
1809 $line->total_ht = (float) $tabprice[0];
1810 $line->total_localtax1 = (float) $tabprice[9];
1811 $line->total_localtax2 = (float) $tabprice[10];
1812 $line->total_ttc = (float) $tabprice[2];
1813 $line->total_tva = (float) $tabprice[1];
1814 $line->vat_src_code = $obj->vat_src_code;
1815 $line->tva_tx = $obj->tva_tx;
1816 $line->localtax1_tx = $obj->localtax1_tx;
1817 $line->localtax2_tx = $obj->localtax2_tx;
1818 $line->info_bits = $obj->info_bits;
1819 $line->price = $obj->price;
1820 $line->subprice = $obj->subprice;
1821 $line->fk_remise_except = $obj->fk_remise_except;
1822 $line->remise_percent = $obj->remise_percent;
1823
1824 $this->total_ht += $tabprice[0];
1825 $this->total_tva += $tabprice[1];
1826 $this->total_ttc += $tabprice[2];
1827 $this->total_localtax1 += $tabprice[9];
1828 $this->total_localtax2 += $tabprice[10];
1829
1830 $line->date_start = $this->db->jdate($obj->date_start);
1831 $line->date_end = $this->db->jdate($obj->date_end);
1832
1833 // Multicurrency
1834 $this->fk_multicurrency = $obj->fk_multicurrency;
1835 $this->multicurrency_code = $obj->multicurrency_code;
1836 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1837 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1838 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1839 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1840
1841 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
1842 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
1843 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
1844
1845 if ($originline != $obj->fk_elementdet) {
1846 $line->detail_batch = array();
1847 }
1848
1849 // Detail of batch
1850 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1851 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1852
1853 if (is_array($newdetailbatch)) {
1854 if ($originline != $obj->fk_elementdet) {
1855 $line->detail_batch = $newdetailbatch;
1856 } else {
1857 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1858 }
1859 }
1860 }
1861
1862 $line->fetch_optionals();
1863
1864 if ($originline != $obj->fk_elementdet) {
1865 $this->lines[$lineindex] = $line;
1866 $lineindex++;
1867 } else {
1868 $line->total_ht += $tabprice[0];
1869 $line->total_localtax1 += $tabprice[9];
1870 $line->total_localtax2 += $tabprice[10];
1871 $line->total_ttc += $tabprice[2];
1872 $line->total_tva += $tabprice[1];
1873 }
1874
1875 $i++;
1876 $originline = $obj->fk_elementdet;
1877 }
1878 $this->db->free($resql);
1879 return 1;
1880 } else {
1881 $this->error = $this->db->error();
1882 return -3;
1883 }
1884 }
1885
1893 public function deleteLine($user, $lineid)
1894 {
1895 global $user;
1896
1897 if ($this->statut == self::STATUS_DRAFT) {
1898 $this->db->begin();
1899
1900 $line = new ExpeditionLigne($this->db);
1901
1902 // For triggers
1903 $line->fetch($lineid);
1904
1905 if ($line->delete($user) > 0) {
1906 //$this->update_price(1);
1907
1908 $this->db->commit();
1909 return 1;
1910 } else {
1911 $this->db->rollback();
1912 return -1;
1913 }
1914 } else {
1915 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1916 return -2;
1917 }
1918 }
1919
1920
1927 public function getTooltipContentArray($params)
1928 {
1929 global $conf, $langs;
1930
1931 $langs->load('sendings');
1932
1933 $nofetch = !empty($params['nofetch']);
1934
1935 $datas = array();
1936 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
1937 if (isset($this->statut)) {
1938 $datas['picto'] .= ' '.$this->getLibStatut(5);
1939 }
1940 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1941 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1942 if (!$nofetch) {
1943 $langs->load('companies');
1944 if (empty($this->thirdparty)) {
1945 $this->fetch_thirdparty();
1946 }
1947 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1948 }
1949
1950 return $datas;
1951 }
1952
1964 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1965 {
1966 global $langs, $hookmanager;
1967
1968 $result = '';
1969 $params = [
1970 'id' => $this->id,
1971 'objecttype' => $this->element,
1972 'option' => $option,
1973 'nofetch' => 1,
1974 ];
1975 $classfortooltip = 'classfortooltip';
1976 $dataparams = '';
1977 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1978 $classfortooltip = 'classforajaxtooltip';
1979 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1980 $label = '';
1981 } else {
1982 $label = implode($this->getTooltipContentArray($params));
1983 }
1984
1985 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1986
1987 if ($short) {
1988 return $url;
1989 }
1990
1991 if ($option !== 'nolink') {
1992 // Add param to save lastsearch_values or not
1993 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1994 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1995 $add_save_lastsearch_values = 1;
1996 }
1997 if ($add_save_lastsearch_values) {
1998 $url .= '&save_lastsearch_values=1';
1999 }
2000 }
2001
2002 $linkclose = '';
2003 if (empty($notooltip)) {
2004 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2005 $label = $langs->trans("Shipment");
2006 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
2007 }
2008 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
2009 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2010 }
2011
2012 $linkstart = '<a href="'.$url.'"';
2013 $linkstart .= $linkclose.'>';
2014 $linkend = '</a>';
2015
2016 $result .= $linkstart;
2017 if ($withpicto) {
2018 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2019 }
2020 if ($withpicto != 2) {
2021 $result .= $this->ref;
2022 }
2023 $result .= $linkend;
2024 global $action;
2025 $hookmanager->initHooks(array($this->element . 'dao'));
2026 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2027 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2028 if ($reshook > 0) {
2029 $result = $hookmanager->resPrint;
2030 } else {
2031 $result .= $hookmanager->resPrint;
2032 }
2033 return $result;
2034 }
2035
2042 public function getLibStatut($mode = 0)
2043 {
2044 return $this->LibStatut($this->statut, $mode);
2045 }
2046
2047 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2055 public function LibStatut($status, $mode)
2056 {
2057 // phpcs:enable
2058 global $langs;
2059
2060 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
2061 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
2062
2063 $statusType = 'status'.$status;
2064 if ($status == self::STATUS_VALIDATED) {
2065 $statusType = 'status4';
2066 }
2067 if ($status == self::STATUS_CLOSED) {
2068 $statusType = 'status6';
2069 }
2070 if ($status == self::STATUS_CANCELED) {
2071 $statusType = 'status9';
2072 }
2073
2074 $signed_label = ' (' . $this->getLibSignedStatus() . ')';
2075 $status_label = $this->signed_status ? $labelStatus . $signed_label : $labelStatus;
2076 $status_label_short = $this->signed_status ? $labelStatusShort . $signed_label : $labelStatusShort;
2077
2078 return dolGetStatus($status_label, $status_label_short, '', $statusType, $mode);
2079 }
2080
2088 public function getKanbanView($option = '', $arraydata = null)
2089 {
2090 global $langs, $conf;
2091
2092 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2093
2094 $return = '<div class="box-flex-item box-flex-grow-zero">';
2095 $return .= '<div class="info-box info-box-sm">';
2096 $return .= '<div class="info-box-icon bg-infobox-action">';
2097 $return .= img_picto('', 'order');
2098 $return .= '</div>';
2099 $return .= '<div class="info-box-content">';
2100 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
2101 if ($selected >= 0) {
2102 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2103 }
2104 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
2105 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
2106 }
2107 if (property_exists($this, 'total_ht')) {
2108 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'</div>';
2109 }
2110 if (method_exists($this, 'getLibStatut')) {
2111 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2112 }
2113 $return .= '</div>';
2114 $return .= '</div>';
2115 $return .= '</div>';
2116
2117 return $return;
2118 }
2119
2127 public function initAsSpecimen()
2128 {
2129 global $langs;
2130
2131 $now = dol_now();
2132
2133 dol_syslog(get_class($this)."::initAsSpecimen");
2134
2135 $order = new Commande($this->db);
2136 $order->initAsSpecimen();
2137
2138 // Initialise parameters
2139 $this->id = 0;
2140 $this->ref = 'SPECIMEN';
2141 $this->specimen = 1;
2142 $this->statut = self::STATUS_VALIDATED;
2143 $this->livraison_id = 0;
2144 $this->date = $now;
2145 $this->date_creation = $now;
2146 $this->date_valid = $now;
2147 $this->date_delivery = $now + 24 * 3600;
2148 $this->date_expedition = $now + 24 * 3600;
2149
2150 $this->entrepot_id = 0;
2151 $this->fk_delivery_address = 0;
2152 $this->socid = 1;
2153
2154 $this->commande_id = 0;
2155 $this->commande = $order;
2156
2157 $this->origin_id = 1;
2158 $this->origin = 'commande';
2159
2160 $this->note_private = 'Private note';
2161 $this->note_public = 'Public note';
2162
2163 $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)
2164 $xnbp = 0;
2165 while ($xnbp < $nbp) {
2166 $line = new ExpeditionLigne($this->db);
2167 $line->product_desc = $langs->trans("Description")." ".$xnbp;
2168 $line->product_label = $langs->trans("Description")." ".$xnbp;
2169 $line->qty = 10;
2170 $line->qty_asked = 5;
2171 $line->qty_shipped = 4;
2172 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2173
2174 $line->weight = 1.123456;
2175 $line->weight_units = 0; // kg
2176
2177 $line->volume = 2.34567;
2178 $line->volume_unit = 0;
2179
2180 $this->lines[] = $line;
2181 $xnbp++;
2182 }
2183
2184 return 1;
2185 }
2186
2187 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2196 public function set_date_livraison($user, $delivery_date)
2197 {
2198 // phpcs:enable
2199 return $this->setDeliveryDate($user, $delivery_date);
2200 }
2201
2209 public function setDeliveryDate($user, $delivery_date)
2210 {
2211 if ($user->hasRight('expedition', 'creer')) {
2212 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2213 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2214 $sql .= " WHERE rowid = ".((int) $this->id);
2215
2216 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2217 $resql = $this->db->query($sql);
2218 if ($resql) {
2219 $this->date_delivery = $delivery_date;
2220 return 1;
2221 } else {
2222 $this->error = $this->db->error();
2223 return -1;
2224 }
2225 } else {
2226 return -2;
2227 }
2228 }
2229
2237 public function setShippingDate($user, $shipping_date)
2238 {
2239 if ($user->hasRight('expedition', 'creer')) {
2240 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2241 $sql .= " SET date_expedition = ".($shipping_date ? "'".$this->db->idate($shipping_date)."'" : 'null');
2242 $sql .= " WHERE rowid = ".((int) $this->id);
2243
2244 dol_syslog(get_class($this)."::setShippingDate", LOG_DEBUG);
2245 $resql = $this->db->query($sql);
2246 if ($resql) {
2247 $this->date_shipping = $shipping_date;
2248 return 1;
2249 } else {
2250 $this->error = $this->db->error();
2251 return -1;
2252 }
2253 } else {
2254 return -2;
2255 }
2256 }
2257
2258 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2264 public function fetch_delivery_methods()
2265 {
2266 // phpcs:enable
2267 global $langs;
2268 $this->meths = array();
2269
2270 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2271 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2272 $sql .= " WHERE em.active = 1";
2273 $sql .= " ORDER BY em.libelle ASC";
2274
2275 $resql = $this->db->query($sql);
2276 if ($resql) {
2277 while ($obj = $this->db->fetch_object($resql)) {
2278 $label = $langs->trans('SendingMethod'.$obj->code);
2279 $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2280 }
2281 }
2282 }
2283
2284 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2291 public function list_delivery_methods($id = 0)
2292 {
2293 // phpcs:enable
2294 global $langs;
2295
2296 $this->listmeths = array();
2297 $i = 0;
2298
2299 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2300 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2301 if (!empty($id)) {
2302 $sql .= " WHERE em.rowid=".((int) $id);
2303 }
2304
2305 $resql = $this->db->query($sql);
2306 if ($resql) {
2307 while ($obj = $this->db->fetch_object($resql)) {
2308 $this->listmeths[$i]['rowid'] = $obj->rowid;
2309 $this->listmeths[$i]['code'] = $obj->code;
2310 $label = $langs->trans('SendingMethod'.$obj->code);
2311 $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2312 $this->listmeths[$i]['description'] = $obj->description;
2313 $this->listmeths[$i]['tracking'] = $obj->tracking;
2314 $this->listmeths[$i]['active'] = $obj->active;
2315 $i++;
2316 }
2317 }
2318 }
2319
2326 public function getUrlTrackingStatus($value = '')
2327 {
2328 if (!empty($this->shipping_method_id)) {
2329 $sql = "SELECT em.code, em.tracking";
2330 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2331 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2332
2333 $resql = $this->db->query($sql);
2334 if ($resql) {
2335 if ($obj = $this->db->fetch_object($resql)) {
2336 $tracking = $obj->tracking;
2337 }
2338 }
2339 }
2340
2341 if (!empty($tracking) && !empty($value)) {
2342 $url = str_replace('{TRACKID}', $value, $tracking);
2343 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
2344 } else {
2345 $this->tracking_url = $value;
2346 }
2347 }
2348
2354 public function setClosed()
2355 {
2356 global $user;
2357
2358 $error = 0;
2359
2360 // Protection. This avoid to move stock later when we should not
2361 if ($this->statut == self::STATUS_CLOSED) {
2362 return 0;
2363 }
2364
2365 $this->db->begin();
2366
2367 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED.", date_expedition = '".$this->db->escape($this->db->idate(dol_now()))."'";
2368 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2369
2370 $resql = $this->db->query($sql);
2371 if ($resql) {
2372 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2373 if ($this->origin == 'commande' && $this->origin_id > 0) {
2374 $order = new Commande($this->db);
2375 $order->fetch($this->origin_id);
2376
2377 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2378
2379 $shipments_match_order = 1;
2380 foreach ($order->lines as $line) {
2381 $lineid = $line->id;
2382 $qty = $line->qty;
2383 if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2384 $shipments_match_order = 0;
2385 $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';
2386 dol_syslog($text);
2387 break;
2388 }
2389 }
2390 if ($shipments_match_order) {
2391 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');
2392 // We close the order
2393 $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2394 }
2395 }
2396
2397 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2398 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2399
2400 // If stock increment is done on closing
2401 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2402 $result = $this->manageStockMvtOnEvt($user);
2403 if ($result < 0) {
2404 $error++;
2405 }
2406 }
2407
2408 // Call trigger
2409 if (!$error) {
2410 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2411 if ($result < 0) {
2412 $error++;
2413 }
2414 }
2415 } else {
2416 dol_print_error($this->db);
2417 $error++;
2418 }
2419
2420 if (!$error) {
2421 $this->db->commit();
2422 return 1;
2423 } else {
2424 $this->statut = self::STATUS_VALIDATED;
2426
2427 $this->db->rollback();
2428 return -1;
2429 }
2430 }
2431
2441 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2442 {
2443 global $langs;
2444
2445 $error = 0;
2446
2447 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2448
2449 $langs->load("agenda");
2450
2451 // Loop on each product line to add a stock movement
2452 $sql = "SELECT cd.fk_product, cd.subprice,";
2453 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2454 $sql .= " e.ref,";
2455 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2456 $sql .= " cd.rowid as cdid, ed.rowid as edid";
2457 $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2458 $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2459 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2460 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2461 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2462 $sql .= " AND cd.rowid = ed.fk_elementdet";
2463
2464 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2465 $resql = $this->db->query($sql);
2466 if ($resql) {
2467 $cpt = $this->db->num_rows($resql);
2468 for ($i = 0; $i < $cpt; $i++) {
2469 $obj = $this->db->fetch_object($resql);
2470 if (empty($obj->edbrowid)) {
2471 $qty = $obj->qty;
2472 } else {
2473 $qty = $obj->edbqty;
2474 }
2475 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2476 continue;
2477 }
2478 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2479
2480 $mouvS = new MouvementStock($this->db);
2481 $mouvS->origin = &$this;
2482 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2483
2484 if (empty($obj->edbrowid)) {
2485 // line without batch detail
2486
2487 // 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
2488 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
2489 if ($result < 0) {
2490 $this->error = $mouvS->error;
2491 $this->errors = $mouvS->errors;
2492 $error++;
2493 break;
2494 }
2495 } else {
2496 // line with batch detail
2497
2498 // 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
2499 $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);
2500 if ($result < 0) {
2501 $this->error = $mouvS->error;
2502 $this->errors = $mouvS->errors;
2503 $error++;
2504 break;
2505 }
2506 }
2507
2508 // 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
2509 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2510 $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)";
2511 $resqldelete = $this->db->query($sqldelete);
2512 // We do not test error, it can fails if there is child in batch details
2513 }
2514 } else {
2515 $this->error = $this->db->lasterror();
2516 $this->errors[] = $this->db->lasterror();
2517 $error++;
2518 }
2519
2520 if (!$error) {
2521 return 1;
2522 } else {
2523 return -1;
2524 }
2525 }
2526
2532 public function setBilled()
2533 {
2534 global $user;
2535 $error = 0;
2536
2537 $this->db->begin();
2538
2539 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET billed = 1';
2540 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2541
2542 $resql = $this->db->query($sql);
2543 if ($resql) {
2544 $this->billed = 1;
2545
2546 // Call trigger
2547 $result = $this->call_trigger('SHIPPING_BILLED', $user);
2548 if ($result < 0) {
2549 $this->billed = 0;
2550 $error++;
2551 }
2552 } else {
2553 $error++;
2554 $this->errors[] = $this->db->lasterror;
2555 }
2556
2557 if (empty($error)) {
2558 $this->db->commit();
2559 return 1;
2560 } else {
2561 $this->db->rollback();
2562 return -1;
2563 }
2564 }
2565
2573 public function setDraft($user, $notrigger = 0)
2574 {
2575 // Protection
2576 if ($this->statut <= self::STATUS_DRAFT) {
2577 return 0;
2578 }
2579
2580 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2581 }
2582
2588 public function reOpen()
2589 {
2590 global $langs, $user;
2591
2592 $error = 0;
2593
2594 // Protection. This avoid to move stock later when we should not
2595 if ($this->statut == self::STATUS_VALIDATED) {
2596 return 0;
2597 }
2598
2599 $this->db->begin();
2600
2601 $oldbilled = $this->billed;
2602
2603 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2604 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2605
2606 $resql = $this->db->query($sql);
2607 if ($resql) {
2608 $this->statut = self::STATUS_VALIDATED;
2610 $this->billed = 0;
2611
2612 // If stock increment is done on closing
2613 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2614 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2615
2616 $langs->load("agenda");
2617
2618 // Loop on each product line to add a stock movement
2619 // TODO possibilite d'expedier a partir d'une propale ou autre origine
2620 $sql = "SELECT cd.fk_product, cd.subprice,";
2621 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2622 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2623 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2624 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2625 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2626 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2627 $sql .= " AND cd.rowid = ed.fk_elementdet";
2628
2629 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2630 $resql = $this->db->query($sql);
2631 if ($resql) {
2632 $cpt = $this->db->num_rows($resql);
2633 for ($i = 0; $i < $cpt; $i++) {
2634 $obj = $this->db->fetch_object($resql);
2635 if (empty($obj->edbrowid)) {
2636 $qty = $obj->qty;
2637 } else {
2638 $qty = $obj->edbqty;
2639 }
2640 if ($qty <= 0) {
2641 continue;
2642 }
2643 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2644
2645 //var_dump($this->lines[$i]);
2646 $mouvS = new MouvementStock($this->db);
2647 $mouvS->origin = &$this;
2648 $mouvS->setOrigin($this->element, $this->id);
2649
2650 if (empty($obj->edbrowid)) {
2651 // line without batch detail
2652
2653 // 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
2654 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2655 if ($result < 0) {
2656 $this->error = $mouvS->error;
2657 $this->errors = $mouvS->errors;
2658 $error++;
2659 break;
2660 }
2661 } else {
2662 // line with batch detail
2663
2664 // 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
2665 $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);
2666 if ($result < 0) {
2667 $this->error = $mouvS->error;
2668 $this->errors = $mouvS->errors;
2669 $error++;
2670 break;
2671 }
2672 }
2673 }
2674 } else {
2675 $this->error = $this->db->lasterror();
2676 $error++;
2677 }
2678 }
2679
2680 if (!$error) {
2681 // Call trigger
2682 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2683 if ($result < 0) {
2684 $error++;
2685 }
2686 }
2687 } else {
2688 $error++;
2689 $this->errors[] = $this->db->lasterror();
2690 }
2691
2692 if (!$error) {
2693 $this->db->commit();
2694 return 1;
2695 } else {
2696 $this->statut = self::STATUS_CLOSED;
2697 $this->status = self::STATUS_CLOSED;
2698 $this->billed = $oldbilled;
2699 $this->db->rollback();
2700 return -1;
2701 }
2702 }
2703
2715 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2716 {
2717 $outputlangs->load("products");
2718
2719 if (!dol_strlen($modele)) {
2720 $modele = 'rouget';
2721
2722 if (!empty($this->model_pdf)) {
2723 $modele = $this->model_pdf;
2724 } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
2725 $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
2726 }
2727 }
2728
2729 $modelpath = "core/modules/expedition/doc/";
2730
2731 $this->fetch_origin();
2732
2733 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2734 }
2735
2744 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2745 {
2746 $tables = array(
2747 'expedition'
2748 );
2749
2750 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2751 }
2752}
$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_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)
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)
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.
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_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 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:153