dolibarr 21.0.0-alpha
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 $model_pdf;
143
147 public $trueWeight;
151 public $weight_units;
155 public $trueWidth;
159 public $width_units;
163 public $trueHeight;
167 public $height_units;
171 public $trueDepth;
175 public $depth_units;
179 public $trueSize;
180
184 public $livraison_id;
185
189 public $multicurrency_subprice;
190
194 public $size_units;
195
199 public $sizeH;
200
204 public $sizeS;
205
209 public $sizeW;
210
214 public $weight;
215
219 public $date_delivery;
220
226 public $date;
227
233 public $date_expedition;
234
239 public $date_shipping;
240
244 public $date_valid;
245
249 public $meths;
253 public $listmeths; // List of carriers
254
258 public $commande_id;
259
263 public $commande;
264
268 public $lines = array();
269
270 // Multicurrency
274 public $fk_multicurrency;
275
279 public $multicurrency_code;
283 public $multicurrency_tx;
287 public $multicurrency_total_ht;
291 public $multicurrency_total_tva;
295 public $multicurrency_total_ttc;
296
300 const STATUS_DRAFT = 0;
301
309
316 const STATUS_CLOSED = 2;
317
321 const STATUS_CANCELED = -1;
322
331
337 public function __construct($db)
338 {
339 global $conf;
340
341 $this->db = $db;
342
343 $this->ismultientitymanaged = 1;
344 $this->isextrafieldmanaged = 1;
345
346 // List of long language codes for status
347 $this->labelStatus = array();
348 $this->labelStatus[-1] = 'StatusSendingCanceled';
349 $this->labelStatus[0] = 'StatusSendingDraft';
350 $this->labelStatus[1] = 'StatusSendingValidated';
351 $this->labelStatus[2] = 'StatusSendingProcessed';
352
353 // List of short language codes for status
354 $this->labelStatusShort = array();
355 $this->labelStatusShort[-1] = 'StatusSendingCanceledShort';
356 $this->labelStatusShort[0] = 'StatusSendingDraftShort';
357 $this->labelStatusShort[1] = 'StatusSendingValidatedShort';
358 $this->labelStatusShort[2] = 'StatusSendingProcessedShort';
359 }
360
367 public function getNextNumRef($soc)
368 {
369 global $langs, $conf;
370 $langs->load("sendings");
371
372 if (getDolGlobalString('EXPEDITION_ADDON_NUMBER')) {
373 $mybool = false;
374
375 $file = getDolGlobalString('EXPEDITION_ADDON_NUMBER') . ".php";
376 $classname = getDolGlobalString('EXPEDITION_ADDON_NUMBER');
377
378 // Include file with class
379 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
380
381 foreach ($dirmodels as $reldir) {
382 $dir = dol_buildpath($reldir."core/modules/expedition/");
383
384 // Load file with numbering class (if found)
385 $mybool = ((bool) @include_once $dir.$file) || $mybool;
386 }
387
388 if (!$mybool) {
389 dol_print_error(null, "Failed to include file ".$file);
390 return '';
391 }
392
393 $obj = new $classname();
394 '@phan-var-force ModelNumRefExpedition $obj';
395 $numref = $obj->getNextValue($soc, $this);
396
397 if ($numref != "") {
398 return $numref;
399 } else {
400 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
401 return "";
402 }
403 } else {
404 print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
405 return "";
406 }
407 }
408
416 public function create($user, $notrigger = 0)
417 {
418 global $conf, $hookmanager;
419
420 $now = dol_now();
421
422 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
423 $error = 0;
424
425 // Clean parameters
426 $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
427 if (empty($this->fk_project)) {
428 $this->fk_project = 0;
429 }
430 if (empty($this->date_shipping) && !empty($this->date_expedition)) {
431 $this->date_shipping = $this->date_expedition;
432 }
433
434 $this->user = $user;
435
436 $this->db->begin();
437
438 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
439 $sql .= "ref";
440 $sql .= ", entity";
441 $sql .= ", ref_customer";
442 $sql .= ", ref_ext";
443 $sql .= ", date_creation";
444 $sql .= ", fk_user_author";
445 $sql .= ", date_expedition";
446 $sql .= ", date_delivery";
447 $sql .= ", fk_soc";
448 $sql .= ", fk_projet";
449 $sql .= ", fk_address";
450 $sql .= ", fk_shipping_method";
451 $sql .= ", tracking_number";
452 $sql .= ", weight";
453 $sql .= ", size";
454 $sql .= ", width";
455 $sql .= ", height";
456 $sql .= ", weight_units";
457 $sql .= ", size_units";
458 $sql .= ", note_private";
459 $sql .= ", note_public";
460 $sql .= ", model_pdf";
461 $sql .= ", fk_incoterms, location_incoterms";
462 $sql .= ", signed_status";
463 $sql .= ") VALUES (";
464 $sql .= "'(PROV)'";
465 $sql .= ", ".((int) $conf->entity);
466 $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
467 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
468 $sql .= ", '".$this->db->idate($now)."'";
469 $sql .= ", ".((int) $user->id);
470 $sql .= ", ".($this->date_shipping > 0 ? "'".$this->db->idate($this->date_shipping)."'" : "null");
471 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
472 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
473 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
474 $sql .= ", ".($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
475 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
476 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
477 $sql .= ", ".(is_numeric($this->weight) ? $this->weight : 'NULL');
478 $sql .= ", ".(is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
479 $sql .= ", ".(is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
480 $sql .= ", ".(is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
481 $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
482 $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
483 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
484 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
485 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
486 $sql .= ", ".(int) $this->fk_incoterms;
487 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
488 $sql .= ", ".($this->signed_status);
489 $sql .= ")";
490
491 dol_syslog(get_class($this)."::create", LOG_DEBUG);
492 $resql = $this->db->query($sql);
493 if ($resql) {
494 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
495
496 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
497 $sql .= " SET ref = '(PROV".$this->id.")'";
498 $sql .= " WHERE rowid = ".((int) $this->id);
499
500 dol_syslog(get_class($this)."::create", LOG_DEBUG);
501 if ($this->db->query($sql)) {
502 // Insert of lines
503 $num = count($this->lines);
504 for ($i = 0; $i < $num; $i++) {
505 if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
506 if (!isset($this->lines[$i]->detail_batch)) { // no batch management
507 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) {
508 $error++;
509 }
510 } else { // with batch management
511 if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
512 $error++;
513 }
514 }
515 }
516 }
517
518 if (!$error && $this->id && $this->origin_id) {
519 $ret = $this->add_object_linked();
520 if (!$ret) {
521 $error++;
522 }
523 }
524
525 // Actions on extra fields
526 if (!$error) {
527 $result = $this->insertExtraFields();
528 if ($result < 0) {
529 $error++;
530 }
531 }
532
533 if (!$error && !$notrigger) {
534 // Call trigger
535 $result = $this->call_trigger('SHIPPING_CREATE', $user);
536 if ($result < 0) {
537 $error++;
538 }
539 // End call triggers
540
541 if (!$error) {
542 $this->db->commit();
543 return $this->id;
544 } else {
545 foreach ($this->errors as $errmsg) {
546 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
547 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
548 }
549 $this->db->rollback();
550 return -1 * $error;
551 }
552 } else {
553 $error++;
554 $this->db->rollback();
555 return -3;
556 }
557 } else {
558 $error++;
559 $this->error = $this->db->lasterror()." - sql=$sql";
560 $this->db->rollback();
561 return -2;
562 }
563 } else {
564 $error++;
565 $this->error = $this->db->error()." - sql=$sql";
566 $this->db->rollback();
567 return -1;
568 }
569 }
570
571 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
582 public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [])
583 {
584 //phpcs:enable
585 global $user;
586
587 $expeditionline = new ExpeditionLigne($this->db);
588 $expeditionline->fk_expedition = $this->id;
589 $expeditionline->entrepot_id = $entrepot_id;
590 $expeditionline->fk_elementdet = $origin_line_id;
591 $expeditionline->element_type = $this->origin;
592 $expeditionline->qty = $qty;
593 $expeditionline->rang = $rang;
594 $expeditionline->array_options = $array_options;
595
596 if (($lineId = $expeditionline->insert($user)) < 0) {
597 $this->errors[] = $expeditionline->error;
598 }
599 return $lineId;
600 }
601
602
603 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
611 public function create_line_batch($line_ext, $array_options = [])
612 {
613 // phpcs:enable
614 $error = 0;
615 $stockLocationQty = array(); // associated array with batch qty in stock location
616
617 $tab = $line_ext->detail_batch;
618 // create stockLocation Qty array
619 foreach ($tab as $detbatch) {
620 if (!empty($detbatch->entrepot_id)) {
621 if (empty($stockLocationQty[$detbatch->entrepot_id])) {
622 $stockLocationQty[$detbatch->entrepot_id] = 0;
623 }
624 $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
625 }
626 }
627 // create shipment lines
628 foreach ($stockLocationQty as $stockLocation => $qty) {
629 $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
630 if ($line_id < 0) {
631 $error++;
632 } else {
633 // create shipment batch lines for stockLocation
634 foreach ($tab as $detbatch) {
635 if ($detbatch->entrepot_id == $stockLocation) {
636 if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch
637 $this->errors = $detbatch->errors;
638 $error++;
639 }
640 }
641 }
642 }
643 }
644
645 if (!$error) {
646 return 1;
647 } else {
648 return -1;
649 }
650 }
651
661 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
662 {
663 global $conf;
664
665 // Check parameters
666 if (empty($id) && empty($ref) && empty($ref_ext)) {
667 return -1;
668 }
669
670 $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";
671 $sql .= ", e.date_valid";
672 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
673 $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
674 $sql .= ", e.fk_shipping_method, e.tracking_number";
675 $sql .= ", e.note_private, e.note_public";
676 $sql .= ', e.fk_incoterms, e.location_incoterms';
677 $sql .= ', e.signed_status';
678 $sql .= ', i.libelle as label_incoterms';
679 $sql .= ', s.libelle as shipping_method';
680 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin_type";
681 $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
682 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
683 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
684 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
685 $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
686 if ($id) {
687 $sql .= " AND e.rowid = ".((int) $id);
688 }
689 if ($ref) {
690 $sql .= " AND e.ref='".$this->db->escape($ref)."'";
691 }
692 if ($ref_ext) {
693 $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
694 }
695
696 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
697 $result = $this->db->query($sql);
698 if ($result) {
699 if ($this->db->num_rows($result)) {
700 $obj = $this->db->fetch_object($result);
701
702 $this->id = $obj->rowid;
703 $this->entity = $obj->entity;
704 $this->ref = $obj->ref;
705 $this->socid = $obj->socid;
706 $this->ref_customer = $obj->ref_customer;
707 $this->ref_ext = $obj->ref_ext;
708 $this->status = $obj->fk_statut;
709 $this->statut = $this->status; // Deprecated
710 $this->signed_status = $obj->signed_status;
711 $this->user_author_id = $obj->fk_user_author;
712 $this->fk_user_author = $obj->fk_user_author;
713 $this->date_creation = $this->db->jdate($obj->date_creation);
714 $this->date_valid = $this->db->jdate($obj->date_valid);
715 $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
716 $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
717 $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
718 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
719 $this->fk_delivery_address = $obj->fk_address;
720 $this->model_pdf = $obj->model_pdf;
721 $this->shipping_method_id = $obj->fk_shipping_method;
722 $this->shipping_method = $obj->shipping_method;
723 $this->tracking_number = $obj->tracking_number;
724 $this->origin = ($obj->origin_type ? $obj->origin_type : 'commande'); // For compatibility
725 $this->origin_type = ($obj->origin_type ? $obj->origin_type : 'commande');
726 $this->origin_id = $obj->origin_id;
727 $this->billed = $obj->billed;
728 $this->fk_project = $obj->fk_project;
729 $this->signed_status = $obj->signed_status;
730 $this->trueWeight = $obj->weight;
731 $this->weight_units = $obj->weight_units;
732
733 $this->trueWidth = $obj->width;
734 $this->width_units = $obj->size_units;
735 $this->trueHeight = $obj->height;
736 $this->height_units = $obj->size_units;
737 $this->trueDepth = $obj->size;
738 $this->depth_units = $obj->size_units;
739
740 $this->note_public = $obj->note_public;
741 $this->note_private = $obj->note_private;
742
743 // A denormalized value
744 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
745 $this->size_units = $obj->size_units;
746
747 //Incoterms
748 $this->fk_incoterms = $obj->fk_incoterms;
749 $this->location_incoterms = $obj->location_incoterms;
750 $this->label_incoterms = $obj->label_incoterms;
751
752 $this->db->free($result);
753
754 // Tracking url
755 $this->getUrlTrackingStatus($obj->tracking_number);
756
757 // Thirdparty
758 $result = $this->fetch_thirdparty(); // TODO Remove this
759
760 // Retrieve extrafields
761 $this->fetch_optionals();
762
763 // Fix Get multicurrency param for transmitted
764 if (isModEnabled('multicurrency')) {
765 if (!empty($this->multicurrency_code)) {
766 $this->multicurrency_code = $this->thirdparty->multicurrency_code;
767 }
768 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($this->thirdparty->multicurrency_tx)) {
769 $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
770 }
771 }
772
773 /*
774 * Lines
775 */
776 $result = $this->fetch_lines();
777 if ($result < 0) {
778 return -3;
779 }
780
781 return 1;
782 } else {
783 dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
784 $this->error = 'Shipment with id '.$id.' not found';
785 return 0;
786 }
787 } else {
788 $this->error = $this->db->error();
789 return -1;
790 }
791 }
792
800 public function valid($user, $notrigger = 0)
801 {
802 global $conf;
803
804 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
805
806 dol_syslog(get_class($this)."::valid");
807
808 // Protection
809 if ($this->status) {
810 dol_syslog(get_class($this)."::valid not in draft status", LOG_WARNING);
811 return 0;
812 }
813
814 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'creer'))
815 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'shipping_advance', 'validate')))) {
816 $this->error = 'Permission denied';
817 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
818 return -1;
819 }
820
821 $this->db->begin();
822
823 $error = 0;
824
825 // Define new ref
826 $soc = new Societe($this->db);
827 $soc->fetch($this->socid);
828
829 // Class of company linked to order
830 $result = $soc->setAsCustomer();
831
832 // Define new ref
833 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
834 $numref = $this->getNextNumRef($soc);
835 } elseif (!empty($this->ref)) {
836 $numref = $this->ref;
837 } else {
838 $numref = "EXP".$this->id;
839 }
840 $this->newref = dol_sanitizeFileName($numref);
841
842 $now = dol_now();
843
844 // Validate
845 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
846 $sql .= " ref='".$this->db->escape($numref)."'";
847 $sql .= ", fk_statut = 1";
848 $sql .= ", date_valid = '".$this->db->idate($now)."'";
849 $sql .= ", fk_user_valid = ".((int) $user->id);
850 $sql .= " WHERE rowid = ".((int) $this->id);
851
852 dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
853 $resql = $this->db->query($sql);
854 if (!$resql) {
855 $this->error = $this->db->lasterror();
856 $error++;
857 }
858
859 // If stock increment is done on sending (recommended choice)
860 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
861 $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
862 if ($result < 0) {
863 return -2;
864 }
865 }
866
867 // Change status of order to "shipment in process"
868 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
869 if (!$ret) {
870 $error++;
871 }
872
873 if (!$error && !$notrigger) {
874 // Call trigger
875 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
876 if ($result < 0) {
877 $error++;
878 }
879 // End call triggers
880 }
881
882 if (!$error) {
883 $this->oldref = $this->ref;
884
885 // Rename directory if dir was a temporary ref
886 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
887 // Now we rename also files into index
888 $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)."'";
889 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
890 $resql = $this->db->query($sql);
891 if (!$resql) {
892 $error++;
893 $this->error = $this->db->lasterror();
894 }
895 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
896 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
897 $resql = $this->db->query($sql);
898 if (!$resql) {
899 $error++;
900 $this->error = $this->db->lasterror();
901 }
902
903 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
904 $oldref = dol_sanitizeFileName($this->ref);
905 $newref = dol_sanitizeFileName($numref);
906 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
907 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
908 if (!$error && file_exists($dirsource)) {
909 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
910
911 if (@rename($dirsource, $dirdest)) {
912 dol_syslog("Rename ok");
913 // Rename docs starting with $oldref with $newref
914 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
915 foreach ($listoffiles as $fileentry) {
916 $dirsource = $fileentry['name'];
917 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
918 $dirsource = $fileentry['path'].'/'.$dirsource;
919 $dirdest = $fileentry['path'].'/'.$dirdest;
920 @rename($dirsource, $dirdest);
921 }
922 }
923 }
924 }
925 }
926
927 // Set new ref and current status
928 if (!$error) {
929 $this->ref = $numref;
930 $this->statut = self::STATUS_VALIDATED;
932 }
933
934 if (!$error) {
935 $this->db->commit();
936 return 1;
937 } else {
938 $this->db->rollback();
939 return -1 * $error;
940 }
941 }
942
943
944 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
951 public function create_delivery($user)
952 {
953 // phpcs:enable
954 global $conf;
955
956 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
957 if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
958 // Expedition validee
959 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
960 $delivery = new Delivery($this->db);
961 $result = $delivery->create_from_sending($user, $this->id);
962 if ($result > 0) {
963 return $result;
964 } else {
965 $this->error = $delivery->error;
966 return $result;
967 }
968 } else {
969 return 0;
970 }
971 } else {
972 return 0;
973 }
974 }
975
988 public function addline($entrepot_id, $id, $qty, $array_options = [])
989 {
990 global $conf, $langs;
991
992 $num = count($this->lines);
993 $line = new ExpeditionLigne($this->db);
994
995 $line->entrepot_id = $entrepot_id;
996 $line->origin_line_id = $id;
997 $line->fk_elementdet = $id;
998 $line->element_type = 'order';
999 $line->qty = $qty;
1000
1001 $orderline = new OrderLine($this->db);
1002 $orderline->fetch($id);
1003
1004 // Copy the rang of the order line to the expedition line
1005 $line->rang = $orderline->rang;
1006 $line->product_type = $orderline->product_type;
1007
1008 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
1009 $fk_product = $orderline->fk_product;
1010
1011 if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS') && !(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') && $line->product_type == Product::TYPE_SERVICE)) {
1012 $langs->load("errors");
1013 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
1014 return -1;
1015 }
1016
1017 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
1018 $product = new Product($this->db);
1019 $product->fetch($fk_product);
1020
1021 // Check must be done for stock of product into warehouse if $entrepot_id defined
1022 if ($entrepot_id > 0) {
1023 $product->load_stock('warehouseopen');
1024 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
1025 } else {
1026 $product_stock = $product->stock_reel;
1027 }
1028
1029 $product_type = $product->type;
1030 if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1031 $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
1032 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
1033 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.
1034 if ($product_stock < $qty) {
1035 $langs->load("errors");
1036 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
1037 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
1038
1039 $this->db->rollback();
1040 return -3;
1041 }
1042 }
1043 }
1044 }
1045 }
1046
1047 // If product need a batch number, we should not have called this function but addline_batch instead.
1048 // If this happen, we may have a bug in card.php page
1049 if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
1050 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; //
1051 return -4;
1052 }
1053
1054 // extrafields
1055 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1056 $line->array_options = $array_options;
1057 }
1058
1059 $this->lines[$num] = $line;
1060
1061 return 1;
1062 }
1063
1064 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1072 public function addline_batch($dbatch, $array_options = [])
1073 {
1074 // phpcs:enable
1075 global $conf, $langs;
1076
1077 $num = count($this->lines);
1078 $linebatch = null;
1079 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1080 $line = new ExpeditionLigne($this->db);
1081 $tab = array();
1082 foreach ($dbatch['detail'] as $key => $value) {
1083 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1084 // $value['q']=qty to move
1085 // $value['id_batch']=id into llx_product_batch of record to move
1086 //var_dump($value);
1087
1088 $linebatch = new ExpeditionLineBatch($this->db);
1089 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1090 if ($ret < 0) {
1091 $this->setErrorsFromObject($linebatch);
1092 return -1;
1093 }
1094 $linebatch->qty = $value['q'];
1095 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1096 $linebatch->batch = null;
1097 }
1098 $tab[] = $linebatch;
1099
1100 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1101 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1102 $prod_batch = new Productbatch($this->db);
1103 $prod_batch->fetch($value['id_batch']);
1104
1105 if ($prod_batch->qty < $linebatch->qty) {
1106 $langs->load("errors");
1107 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1108 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1109 $this->db->rollback();
1110 return -1;
1111 }
1112 }
1113
1114 //var_dump($linebatch);
1115 }
1116 }
1117 if (is_object($linebatch)) {
1118 $line->entrepot_id = $linebatch->entrepot_id;
1119 }
1120 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1121 $line->fk_elementdet = $dbatch['ix_l'];
1122 $line->qty = $dbatch['qty'];
1123 $line->detail_batch = $tab;
1124
1125 // extrafields
1126 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1127 $line->array_options = $array_options;
1128 }
1129
1130 //var_dump($line);
1131 $this->lines[$num] = $line;
1132 return 1;
1133 }
1134 return 0;
1135 }
1136
1144 public function update($user = null, $notrigger = 0)
1145 {
1146 global $conf;
1147 $error = 0;
1148
1149 // Clean parameters
1150
1151 if (isset($this->ref)) {
1152 $this->ref = trim($this->ref);
1153 }
1154 if (isset($this->entity)) {
1155 $this->entity = (int) $this->entity;
1156 }
1157 if (isset($this->ref_customer)) {
1158 $this->ref_customer = trim($this->ref_customer);
1159 }
1160 if (isset($this->socid)) {
1161 $this->socid = (int) $this->socid;
1162 }
1163 if (isset($this->fk_user_author)) {
1164 $this->fk_user_author = (int) $this->fk_user_author;
1165 }
1166 if (isset($this->fk_user_valid)) { // @phan-ignore-current-line PhanUndeclaredProperty
1167 // If set, then accept @phan-ignore-next-line PhanUndeclaredProperty
1168 $this->fk_user_valid = (int) $this->fk_user_valid;
1169 }
1170 if (isset($this->fk_delivery_address)) {
1171 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1172 }
1173 if (isset($this->shipping_method_id)) {
1174 $this->shipping_method_id = (int) $this->shipping_method_id;
1175 }
1176 if (isset($this->tracking_number)) {
1177 $this->tracking_number = trim($this->tracking_number);
1178 }
1179 if (isset($this->statut)) {
1180 $this->statut = (int) $this->statut;
1181 }
1182 if (isset($this->trueDepth)) {
1183 $this->trueDepth = trim($this->trueDepth);
1184 }
1185 if (isset($this->trueWidth)) {
1186 $this->trueWidth = trim($this->trueWidth);
1187 }
1188 if (isset($this->trueHeight)) {
1189 $this->trueHeight = trim($this->trueHeight);
1190 }
1191 if (isset($this->size_units)) {
1192 $this->size_units = trim($this->size_units);
1193 }
1194 if (isset($this->weight_units)) {
1195 $this->weight_units = (int) $this->weight_units;
1196 }
1197 if (isset($this->trueWeight)) {
1198 $this->weight = trim((string) $this->trueWeight);
1199 }
1200 if (isset($this->note_private)) {
1201 $this->note_private = trim($this->note_private);
1202 }
1203 if (isset($this->note_public)) {
1204 $this->note_public = trim($this->note_public);
1205 }
1206 if (isset($this->model_pdf)) {
1207 $this->model_pdf = trim($this->model_pdf);
1208 }
1209
1210 // Check parameters
1211 // Put here code to add control on parameters values
1212
1213 // Update request
1214 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1215 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1216 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1217 $sql .= " ref_customer = ".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1218 $sql .= " fk_soc = ".(isset($this->socid) ? $this->socid : "null").",";
1219 $sql .= " date_creation = ".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1220 $sql .= " fk_user_author = ".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1221 $sql .= " date_valid = ".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1222 $sql .= " fk_user_valid = ".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1223 $sql .= " date_expedition = ".(dol_strlen($this->date_expedition) != 0 ? "'".$this->db->idate($this->date_expedition)."'" : 'null').",";
1224 $sql .= " date_delivery = ".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1225 $sql .= " fk_address = ".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1226 $sql .= " fk_shipping_method = ".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1227 $sql .= " tracking_number = ".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1228 $sql .= " fk_statut = ".(isset($this->statut) ? $this->statut : "null").",";
1229 $sql .= " fk_projet = ".(isset($this->fk_project) ? $this->fk_project : "null").",";
1230 $sql .= " height = ".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1231 $sql .= " width = ".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1232 $sql .= " size_units = ".(isset($this->size_units) ? $this->size_units : "null").",";
1233 $sql .= " size = ".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1234 $sql .= " weight_units = ".(isset($this->weight_units) ? $this->weight_units : "null").",";
1235 $sql .= " weight = ".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1236 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1237 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1238 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1239 $sql .= " entity = ".((int) $conf->entity);
1240 $sql .= " WHERE rowid = ".((int) $this->id);
1241
1242 $this->db->begin();
1243
1244 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1245 $resql = $this->db->query($sql);
1246 if (!$resql) {
1247 $error++;
1248 $this->errors[] = "Error ".$this->db->lasterror();
1249 }
1250
1251 if (!$error && !$notrigger) {
1252 // Call trigger
1253 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1254 if ($result < 0) {
1255 $error++;
1256 }
1257 // End call triggers
1258 }
1259
1260 // Commit or rollback
1261 if ($error) {
1262 foreach ($this->errors as $errmsg) {
1263 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1264 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1265 }
1266 $this->db->rollback();
1267 return -1 * $error;
1268 } else {
1269 $this->db->commit();
1270 return 1;
1271 }
1272 }
1273
1274
1282 public function cancel($notrigger = 0, $also_update_stock = false)
1283 {
1284 global $conf, $langs, $user;
1285
1286 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1287
1288 $error = 0;
1289 $this->error = '';
1290
1291 $this->db->begin();
1292
1293 // Add a protection to refuse deleting if shipment has at least one delivery
1294 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1295 if (count($this->linkedObjectsIds) > 0) {
1296 $this->error = 'ErrorThereIsSomeDeliveries';
1297 $error++;
1298 }
1299
1300 if (!$error && !$notrigger) {
1301 // Call trigger
1302 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1303 if ($result < 0) {
1304 $error++;
1305 }
1306 // End call triggers
1307 }
1308
1309 // Stock control
1310 if (!$error && isModEnabled('stock') &&
1311 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1312 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1313 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1314
1315 $langs->load("agenda");
1316
1317 // Loop on each product line to add a stock movement and delete features
1318 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1319 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1320 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1321 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1322 $sql .= " AND cd.rowid = ed.fk_elementdet";
1323
1324 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1325 $resql = $this->db->query($sql);
1326 if ($resql) {
1327 $cpt = $this->db->num_rows($resql);
1328
1329 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1330
1331 for ($i = 0; $i < $cpt; $i++) {
1332 dol_syslog(get_class($this)."::delete movement index ".$i);
1333 $obj = $this->db->fetch_object($resql);
1334
1335 $mouvS = new MouvementStock($this->db);
1336 // we do not log origin because it will be deleted
1337 $mouvS->origin = '';
1338 // get lot/serial
1339 $lotArray = null;
1340 if (isModEnabled('productbatch')) {
1341 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1342 if (!is_array($lotArray)) {
1343 $error++;
1344 $this->errors[] = "Error ".$this->db->lasterror();
1345 }
1346 }
1347
1348 if (empty($lotArray)) {
1349 // no lot/serial
1350 // We increment stock of product (and sub-products)
1351 // We use warehouse selected for each line
1352 $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
1353 if ($result < 0) {
1354 $error++;
1355 $this->errors = array_merge($this->errors, $mouvS->errors);
1356 break;
1357 }
1358 } else {
1359 // We increment stock of batches
1360 // We use warehouse selected for each line
1361 foreach ($lotArray as $lot) {
1362 $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
1363 if ($result < 0) {
1364 $error++;
1365 $this->errors = array_merge($this->errors, $mouvS->errors);
1366 break;
1367 }
1368 }
1369 if ($error) {
1370 break; // break for loop in case of error
1371 }
1372 }
1373 }
1374 } else {
1375 $error++;
1376 $this->errors[] = "Error ".$this->db->lasterror();
1377 }
1378 }
1379
1380 // delete batch expedition line
1381 if (!$error && isModEnabled('productbatch')) {
1382 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1383 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1384 $error++;
1385 $this->errors[] = "Error ".$this->db->lasterror();
1386 }
1387 }
1388
1389
1390 if (!$error) {
1391 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1392 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1393
1394 if ($this->db->query($sql)) {
1395 // Delete linked object
1396 $res = $this->deleteObjectLinked();
1397 if ($res < 0) {
1398 $error++;
1399 }
1400
1401 // No delete expedition
1402 if (!$error) {
1403 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1404 $sql .= " WHERE rowid = ".((int) $this->id);
1405
1406 if ($this->db->query($sql)) {
1407 if (!empty($this->origin) && $this->origin_id > 0) {
1408 $this->fetch_origin();
1410 '@phan-var-force Facture|Commande $origin_object';
1411 if ($origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1412 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1413 $origin_object->loadExpeditions();
1414 //var_dump($this->$origin->expeditions);exit;
1415 if (count($origin_object->expeditions) <= 0) {
1417 }
1418 }
1419 }
1420
1421 if (!$error) {
1422 $this->db->commit();
1423
1424 // We delete PDFs
1425 $ref = dol_sanitizeFileName($this->ref);
1426 if (!empty($conf->expedition->dir_output)) {
1427 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1428 $file = $dir.'/'.$ref.'.pdf';
1429 if (file_exists($file)) {
1430 if (!dol_delete_file($file)) {
1431 return 0;
1432 }
1433 }
1434 if (file_exists($dir)) {
1435 if (!dol_delete_dir_recursive($dir)) {
1436 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1437 return 0;
1438 }
1439 }
1440 }
1441
1442 return 1;
1443 } else {
1444 $this->db->rollback();
1445 return -1;
1446 }
1447 } else {
1448 $this->error = $this->db->lasterror()." - sql=$sql";
1449 $this->db->rollback();
1450 return -3;
1451 }
1452 } else {
1453 $this->error = $this->db->lasterror()." - sql=$sql";
1454 $this->db->rollback();
1455 return -2;
1456 }//*/
1457 } else {
1458 $this->error = $this->db->lasterror()." - sql=$sql";
1459 $this->db->rollback();
1460 return -1;
1461 }
1462 } else {
1463 $this->db->rollback();
1464 return -1;
1465 }
1466 }
1467
1477 public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1478 {
1479 global $conf, $langs;
1480
1481 if (empty($user)) {
1482 global $user;
1483 }
1484
1485 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1486
1487 $error = 0;
1488 $this->error = '';
1489
1490 $this->db->begin();
1491
1492 // Add a protection to refuse deleting if shipment has at least one delivery
1493 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1494 if (count($this->linkedObjectsIds) > 0) {
1495 $this->error = 'ErrorThereIsSomeDeliveries';
1496 $error++;
1497 }
1498
1499 if (!$error && !$notrigger) {
1500 // Call trigger
1501 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1502 if ($result < 0) {
1503 $error++;
1504 }
1505 // End call triggers
1506 }
1507
1508 // Stock control
1509 if (!$error && isModEnabled('stock') &&
1510 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1511 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1512 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1513
1514 $langs->load("agenda");
1515
1516 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1517 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1518
1519 // Loop on each product line to add a stock movement
1520 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1521 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1522 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1523 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1524 $sql .= " AND cd.rowid = ed.fk_elementdet";
1525
1526 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1527 $resql = $this->db->query($sql);
1528 if ($resql) {
1529 $cpt = $this->db->num_rows($resql);
1530 for ($i = 0; $i < $cpt; $i++) {
1531 dol_syslog(get_class($this)."::delete movement index ".$i);
1532 $obj = $this->db->fetch_object($resql);
1533
1534 $mouvS = new MouvementStock($this->db);
1535 // we do not log origin because it will be deleted
1536 $mouvS->origin = '';
1537 // get lot/serial
1538 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1539 if (!is_array($lotArray)) {
1540 $error++;
1541 $this->errors[] = "Error ".$this->db->lasterror();
1542 }
1543 if (empty($lotArray)) {
1544 // no lot/serial
1545 // We increment stock of product (and sub-products)
1546 // We use warehouse selected for each line
1547 $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
1548 if ($result < 0) {
1549 $error++;
1550 $this->errors = array_merge($this->errors, $mouvS->errors);
1551 break;
1552 }
1553 } else {
1554 // We increment stock of batches
1555 // We use warehouse selected for each line
1556 foreach ($lotArray as $lot) {
1557 $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
1558 if ($result < 0) {
1559 $error++;
1560 $this->errors = array_merge($this->errors, $mouvS->errors);
1561 break;
1562 }
1563 }
1564 if ($error) {
1565 break; // break for loop in case of error
1566 }
1567 }
1568 }
1569 } else {
1570 $error++;
1571 $this->errors[] = "Error ".$this->db->lasterror();
1572 }
1573 }
1574
1575 // delete batch expedition line
1576 if (!$error) {
1577 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1578 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1579 $error++;
1580 $this->errors[] = "Error ".$this->db->lasterror();
1581 }
1582 }
1583
1584 if (!$error) {
1585 $main = MAIN_DB_PREFIX.'expeditiondet';
1586 $ef = $main."_extrafields";
1587 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
1588
1589 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1590 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1591
1592 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1593 // Delete linked object
1594 $res = $this->deleteObjectLinked();
1595 if ($res < 0) {
1596 $error++;
1597 }
1598
1599 // delete extrafields
1600 $res = $this->deleteExtraFields();
1601 if ($res < 0) {
1602 $error++;
1603 }
1604
1605 if (!$error) {
1606 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1607 $sql .= " WHERE rowid = ".((int) $this->id);
1608
1609 if ($this->db->query($sql)) {
1610 if (!empty($this->origin) && $this->origin_id > 0) {
1611 $this->fetch_origin();
1613 '@phan-var-force Facture|Commande $origin_object';
1614 if ($origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1615 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1616 $origin_object->loadExpeditions();
1617 //var_dump($this->$origin->expeditions);exit;
1618 if (count($origin_object->expeditions) <= 0) {
1620 }
1621 }
1622 }
1623
1624 if (!$error) {
1625 $this->db->commit();
1626
1627 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1628 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1629 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1630
1631 // We delete PDFs
1632 $ref = dol_sanitizeFileName($this->ref);
1633 if (!empty($conf->expedition->dir_output)) {
1634 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1635 $file = $dir.'/'.$ref.'.pdf';
1636 if (file_exists($file)) {
1637 if (!dol_delete_file($file)) {
1638 return 0;
1639 }
1640 }
1641 if (file_exists($dir)) {
1642 if (!dol_delete_dir_recursive($dir)) {
1643 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1644 return 0;
1645 }
1646 }
1647 }
1648
1649 return 1;
1650 } else {
1651 $this->db->rollback();
1652 return -1;
1653 }
1654 } else {
1655 $this->error = $this->db->lasterror()." - sql=$sql";
1656 $this->db->rollback();
1657 return -3;
1658 }
1659 } else {
1660 $this->error = $this->db->lasterror()." - sql=$sql";
1661 $this->db->rollback();
1662 return -2;
1663 }
1664 } else {
1665 $this->error = $this->db->lasterror()." - sql=$sql";
1666 $this->db->rollback();
1667 return -1;
1668 }
1669 } else {
1670 $this->db->rollback();
1671 return -1;
1672 }
1673 }
1674
1675 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1681 public function fetch_lines()
1682 {
1683 // phpcs:enable
1684 global $mysoc;
1685
1686 $this->lines = array();
1687
1688 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1689 // TODO: See if we can restore a common fetch_lines (one line = one record)
1690
1691 $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";
1692 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1693 $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
1694 $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";
1695 $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";
1696 $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot";
1697 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
1698 $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";
1699 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1700 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1701 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1702 $sql .= " AND ed.fk_elementdet = cd.rowid";
1703 $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.
1704
1705 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1706 $resql = $this->db->query($sql);
1707 if ($resql) {
1708 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1709
1710 $num = $this->db->num_rows($resql);
1711 $i = 0;
1712 $lineindex = 0;
1713 $originline = 0;
1714
1715 $this->total_ht = 0;
1716 $this->total_tva = 0;
1717 $this->total_ttc = 0;
1718 $this->total_localtax1 = 0;
1719 $this->total_localtax2 = 0;
1720
1721 $this->multicurrency_total_ht = 0;
1722 $this->multicurrency_total_tva = 0;
1723 $this->multicurrency_total_ttc = 0;
1724
1725 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1726
1727 while ($i < $num) {
1728 $obj = $this->db->fetch_object($resql);
1729
1730
1731 if ($originline > 0 && $originline == $obj->fk_elementdet) {
1732 '@phan-var-force ExpeditionLigne $line'; // $line from previous loop
1733 $line->entrepot_id = 0; // entrepod_id in details_entrepot
1734 $line->qty_shipped += $obj->qty_shipped;
1735 } else {
1736 $line = new ExpeditionLigne($this->db); // new group to start
1737 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
1738 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
1739 }
1740
1741 $detail_entrepot = new stdClass();
1742 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1743 $detail_entrepot->qty_shipped = $obj->qty_shipped;
1744 $detail_entrepot->line_id = $obj->line_id;
1745 $line->details_entrepot[] = $detail_entrepot;
1746
1747 $line->line_id = $obj->line_id; // TODO deprecated
1748 $line->rowid = $obj->line_id; // TODO deprecated
1749 $line->id = $obj->line_id;
1750
1751 $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
1752
1753 $line->fk_element = $obj->fk_element;
1754 $line->origin_id = $obj->fk_element;
1755 $line->fk_elementdet = $obj->fk_elementdet;
1756 $line->origin_line_id = $obj->fk_elementdet;
1757 $line->element_type = $obj->element_type;
1758
1759 $line->fk_expedition = $this->id; // id of parent
1760
1761 $line->product_type = $obj->product_type;
1762 $line->fk_product = $obj->fk_product;
1763 $line->fk_product_type = $obj->fk_product_type;
1764 $line->ref = $obj->product_ref; // TODO deprecated
1765 $line->product_ref = $obj->product_ref;
1766 $line->product_label = $obj->product_label;
1767 $line->libelle = $obj->product_label; // TODO deprecated
1768 $line->product_barcode = $obj->product_barcode; // Barcode number product
1769 $line->product_tosell = $obj->product_tosell;
1770 $line->product_tobuy = $obj->product_tobuy;
1771 $line->product_tobatch = $obj->product_tobatch;
1772 $line->fk_fournprice = $obj->fk_fournprice;
1773 $line->label = $obj->custom_label;
1774 $line->description = $obj->description;
1775 $line->qty_asked = $obj->qty_asked;
1776 $line->rang = $obj->rang;
1777 $line->weight = $obj->weight;
1778 $line->weight_units = $obj->weight_units;
1779 $line->length = $obj->length;
1780 $line->length_units = $obj->length_units;
1781 $line->width = $obj->width;
1782 $line->width_units = $obj->width_units;
1783 $line->height = $obj->height;
1784 $line->height_units = $obj->height_units;
1785 $line->surface = $obj->surface;
1786 $line->surface_units = $obj->surface_units;
1787 $line->volume = $obj->volume;
1788 $line->volume_units = $obj->volume_units;
1789 $line->fk_unit = $obj->fk_unit;
1790
1791 $line->pa_ht = $obj->pa_ht;
1792
1793 // Local taxes
1794 $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
1795 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1796 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1797
1798 // For invoicing
1799 $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
1800 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1801 $line->qty = $line->qty_shipped;
1802 $line->total_ht = $tabprice[0];
1803 $line->total_localtax1 = $tabprice[9];
1804 $line->total_localtax2 = $tabprice[10];
1805 $line->total_ttc = $tabprice[2];
1806 $line->total_tva = $tabprice[1];
1807 $line->vat_src_code = $obj->vat_src_code;
1808 $line->tva_tx = $obj->tva_tx;
1809 $line->localtax1_tx = $obj->localtax1_tx;
1810 $line->localtax2_tx = $obj->localtax2_tx;
1811 $line->info_bits = $obj->info_bits;
1812 $line->price = $obj->price;
1813 $line->subprice = $obj->subprice;
1814 $line->fk_remise_except = $obj->fk_remise_except;
1815 $line->remise_percent = $obj->remise_percent;
1816
1817 $this->total_ht += $tabprice[0];
1818 $this->total_tva += $tabprice[1];
1819 $this->total_ttc += $tabprice[2];
1820 $this->total_localtax1 += $tabprice[9];
1821 $this->total_localtax2 += $tabprice[10];
1822
1823 $line->date_start = $this->db->jdate($obj->date_start);
1824 $line->date_end = $this->db->jdate($obj->date_end);
1825
1826 // Multicurrency
1827 $this->fk_multicurrency = $obj->fk_multicurrency;
1828 $this->multicurrency_code = $obj->multicurrency_code;
1829 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1830 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1831 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1832 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1833
1834 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
1835 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
1836 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
1837
1838 if ($originline != $obj->fk_elementdet) {
1839 $line->detail_batch = array();
1840 }
1841
1842 // Detail of batch
1843 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1844 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1845
1846 if (is_array($newdetailbatch)) {
1847 if ($originline != $obj->fk_elementdet) {
1848 $line->detail_batch = $newdetailbatch;
1849 } else {
1850 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1851 }
1852 }
1853 }
1854
1855 $line->fetch_optionals();
1856
1857 if ($originline != $obj->fk_elementdet) {
1858 $this->lines[$lineindex] = $line;
1859 $lineindex++;
1860 } else {
1861 $line->total_ht += $tabprice[0];
1862 $line->total_localtax1 += $tabprice[9];
1863 $line->total_localtax2 += $tabprice[10];
1864 $line->total_ttc += $tabprice[2];
1865 $line->total_tva += $tabprice[1];
1866 }
1867
1868 $i++;
1869 $originline = $obj->fk_elementdet;
1870 }
1871 $this->db->free($resql);
1872 return 1;
1873 } else {
1874 $this->error = $this->db->error();
1875 return -3;
1876 }
1877 }
1878
1886 public function deleteLine($user, $lineid)
1887 {
1888 global $user;
1889
1890 if ($this->statut == self::STATUS_DRAFT) {
1891 $this->db->begin();
1892
1893 $line = new ExpeditionLigne($this->db);
1894
1895 // For triggers
1896 $line->fetch($lineid);
1897
1898 if ($line->delete($user) > 0) {
1899 //$this->update_price(1);
1900
1901 $this->db->commit();
1902 return 1;
1903 } else {
1904 $this->db->rollback();
1905 return -1;
1906 }
1907 } else {
1908 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1909 return -2;
1910 }
1911 }
1912
1913
1920 public function getTooltipContentArray($params)
1921 {
1922 global $conf, $langs;
1923
1924 $langs->load('sendings');
1925
1926 $nofetch = !empty($params['nofetch']);
1927
1928 $datas = array();
1929 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
1930 if (isset($this->statut)) {
1931 $datas['picto'] .= ' '.$this->getLibStatut(5);
1932 }
1933 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1934 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1935 if (!$nofetch) {
1936 $langs->load('companies');
1937 if (empty($this->thirdparty)) {
1938 $this->fetch_thirdparty();
1939 }
1940 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1941 }
1942
1943 return $datas;
1944 }
1945
1957 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1958 {
1959 global $langs, $hookmanager;
1960
1961 $result = '';
1962 $params = [
1963 'id' => $this->id,
1964 'objecttype' => $this->element,
1965 'option' => $option,
1966 'nofetch' => 1,
1967 ];
1968 $classfortooltip = 'classfortooltip';
1969 $dataparams = '';
1970 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1971 $classfortooltip = 'classforajaxtooltip';
1972 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1973 $label = '';
1974 } else {
1975 $label = implode($this->getTooltipContentArray($params));
1976 }
1977
1978 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1979
1980 if ($short) {
1981 return $url;
1982 }
1983
1984 if ($option !== 'nolink') {
1985 // Add param to save lastsearch_values or not
1986 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1987 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1988 $add_save_lastsearch_values = 1;
1989 }
1990 if ($add_save_lastsearch_values) {
1991 $url .= '&save_lastsearch_values=1';
1992 }
1993 }
1994
1995 $linkclose = '';
1996 if (empty($notooltip)) {
1997 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1998 $label = $langs->trans("Shipment");
1999 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
2000 }
2001 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
2002 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2003 }
2004
2005 $linkstart = '<a href="'.$url.'"';
2006 $linkstart .= $linkclose.'>';
2007 $linkend = '</a>';
2008
2009 $result .= $linkstart;
2010 if ($withpicto) {
2011 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2012 }
2013 if ($withpicto != 2) {
2014 $result .= $this->ref;
2015 }
2016 $result .= $linkend;
2017 global $action;
2018 $hookmanager->initHooks(array($this->element . 'dao'));
2019 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2020 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2021 if ($reshook > 0) {
2022 $result = $hookmanager->resPrint;
2023 } else {
2024 $result .= $hookmanager->resPrint;
2025 }
2026 return $result;
2027 }
2028
2035 public function getLibStatut($mode = 0)
2036 {
2037 return $this->LibStatut($this->statut, $mode);
2038 }
2039
2040 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2048 public function LibStatut($status, $mode)
2049 {
2050 // phpcs:enable
2051 global $langs;
2052
2053 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
2054 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
2055
2056 $statusType = 'status'.$status;
2057 if ($status == self::STATUS_VALIDATED) {
2058 $statusType = 'status4';
2059 }
2060 if ($status == self::STATUS_CLOSED) {
2061 $statusType = 'status6';
2062 }
2063 if ($status == self::STATUS_CANCELED) {
2064 $statusType = 'status9';
2065 }
2066
2067 $signed_label = ' (' . $this->getLibSignedStatus() . ')';
2068 $status_label = $this->signed_status ? $labelStatus . $signed_label : $labelStatus;
2069 $status_label_short = $this->signed_status ? $labelStatusShort . $signed_label : $labelStatusShort;
2070
2071 return dolGetStatus($status_label, $status_label_short, '', $statusType, $mode);
2072 }
2073
2081 public function getKanbanView($option = '', $arraydata = null)
2082 {
2083 global $langs, $conf;
2084
2085 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2086
2087 $return = '<div class="box-flex-item box-flex-grow-zero">';
2088 $return .= '<div class="info-box info-box-sm">';
2089 $return .= '<div class="info-box-icon bg-infobox-action">';
2090 $return .= img_picto('', 'order');
2091 $return .= '</div>';
2092 $return .= '<div class="info-box-content">';
2093 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
2094 if ($selected >= 0) {
2095 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2096 }
2097 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
2098 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
2099 }
2100 if (property_exists($this, 'total_ht')) {
2101 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'</div>';
2102 }
2103 if (method_exists($this, 'getLibStatut')) {
2104 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2105 }
2106 $return .= '</div>';
2107 $return .= '</div>';
2108 $return .= '</div>';
2109
2110 return $return;
2111 }
2112
2120 public function initAsSpecimen()
2121 {
2122 global $langs;
2123
2124 $now = dol_now();
2125
2126 dol_syslog(get_class($this)."::initAsSpecimen");
2127
2128 $order = new Commande($this->db);
2129 $order->initAsSpecimen();
2130
2131 // Initialise parameters
2132 $this->id = 0;
2133 $this->ref = 'SPECIMEN';
2134 $this->specimen = 1;
2135 $this->statut = self::STATUS_VALIDATED;
2136 $this->livraison_id = 0;
2137 $this->date = $now;
2138 $this->date_creation = $now;
2139 $this->date_valid = $now;
2140 $this->date_delivery = $now + 24 * 3600;
2141 $this->date_expedition = $now + 24 * 3600;
2142
2143 $this->entrepot_id = 0;
2144 $this->fk_delivery_address = 0;
2145 $this->socid = 1;
2146
2147 $this->commande_id = 0;
2148 $this->commande = $order;
2149
2150 $this->origin_id = 1;
2151 $this->origin = 'commande';
2152
2153 $this->note_private = 'Private note';
2154 $this->note_public = 'Public note';
2155
2156 $nbp = 5;
2157 $xnbp = 0;
2158 while ($xnbp < $nbp) {
2159 $line = new ExpeditionLigne($this->db);
2160 $line->product_desc = $langs->trans("Description")." ".$xnbp;
2161 $line->product_label = $langs->trans("Description")." ".$xnbp;
2162 $line->qty = 10;
2163 $line->qty_asked = 5;
2164 $line->qty_shipped = 4;
2165 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2166
2167 $this->lines[] = $line;
2168 $xnbp++;
2169 }
2170
2171 return 1;
2172 }
2173
2174 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2183 public function set_date_livraison($user, $delivery_date)
2184 {
2185 // phpcs:enable
2186 return $this->setDeliveryDate($user, $delivery_date);
2187 }
2188
2196 public function setDeliveryDate($user, $delivery_date)
2197 {
2198 if ($user->hasRight('expedition', 'creer')) {
2199 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2200 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2201 $sql .= " WHERE rowid = ".((int) $this->id);
2202
2203 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2204 $resql = $this->db->query($sql);
2205 if ($resql) {
2206 $this->date_delivery = $delivery_date;
2207 return 1;
2208 } else {
2209 $this->error = $this->db->error();
2210 return -1;
2211 }
2212 } else {
2213 return -2;
2214 }
2215 }
2216
2217 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2223 public function fetch_delivery_methods()
2224 {
2225 // phpcs:enable
2226 global $langs;
2227 $this->meths = array();
2228
2229 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2230 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2231 $sql .= " WHERE em.active = 1";
2232 $sql .= " ORDER BY em.libelle ASC";
2233
2234 $resql = $this->db->query($sql);
2235 if ($resql) {
2236 while ($obj = $this->db->fetch_object($resql)) {
2237 $label = $langs->trans('SendingMethod'.$obj->code);
2238 $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2239 }
2240 }
2241 }
2242
2243 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2250 public function list_delivery_methods($id = 0)
2251 {
2252 // phpcs:enable
2253 global $langs;
2254
2255 $this->listmeths = array();
2256 $i = 0;
2257
2258 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2259 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2260 if (!empty($id)) {
2261 $sql .= " WHERE em.rowid=".((int) $id);
2262 }
2263
2264 $resql = $this->db->query($sql);
2265 if ($resql) {
2266 while ($obj = $this->db->fetch_object($resql)) {
2267 $this->listmeths[$i]['rowid'] = $obj->rowid;
2268 $this->listmeths[$i]['code'] = $obj->code;
2269 $label = $langs->trans('SendingMethod'.$obj->code);
2270 $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2271 $this->listmeths[$i]['description'] = $obj->description;
2272 $this->listmeths[$i]['tracking'] = $obj->tracking;
2273 $this->listmeths[$i]['active'] = $obj->active;
2274 $i++;
2275 }
2276 }
2277 }
2278
2285 public function getUrlTrackingStatus($value = '')
2286 {
2287 if (!empty($this->shipping_method_id)) {
2288 $sql = "SELECT em.code, em.tracking";
2289 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2290 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2291
2292 $resql = $this->db->query($sql);
2293 if ($resql) {
2294 if ($obj = $this->db->fetch_object($resql)) {
2295 $tracking = $obj->tracking;
2296 }
2297 }
2298 }
2299
2300 if (!empty($tracking) && !empty($value)) {
2301 $url = str_replace('{TRACKID}', $value, $tracking);
2302 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
2303 } else {
2304 $this->tracking_url = $value;
2305 }
2306 }
2307
2313 public function setClosed()
2314 {
2315 global $user;
2316
2317 $error = 0;
2318
2319 // Protection. This avoid to move stock later when we should not
2320 if ($this->statut == self::STATUS_CLOSED) {
2321 return 0;
2322 }
2323
2324 $this->db->begin();
2325
2326 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED.", date_expedition = '".$this->db->escape($this->db->idate(dol_now()))."'";
2327 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2328
2329 $resql = $this->db->query($sql);
2330 if ($resql) {
2331 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2332 if ($this->origin == 'commande' && $this->origin_id > 0) {
2333 $order = new Commande($this->db);
2334 $order->fetch($this->origin_id);
2335
2336 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2337
2338 $shipments_match_order = 1;
2339 foreach ($order->lines as $line) {
2340 $lineid = $line->id;
2341 $qty = $line->qty;
2342 if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2343 $shipments_match_order = 0;
2344 $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';
2345 dol_syslog($text);
2346 break;
2347 }
2348 }
2349 if ($shipments_match_order) {
2350 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');
2351 // We close the order
2352 $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2353 }
2354 }
2355
2356 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2357 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2358
2359 // If stock increment is done on closing
2360 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2361 $result = $this->manageStockMvtOnEvt($user);
2362 if ($result < 0) {
2363 $error++;
2364 }
2365 }
2366
2367 // Call trigger
2368 if (!$error) {
2369 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2370 if ($result < 0) {
2371 $error++;
2372 }
2373 }
2374 } else {
2375 dol_print_error($this->db);
2376 $error++;
2377 }
2378
2379 if (!$error) {
2380 $this->db->commit();
2381 return 1;
2382 } else {
2383 $this->statut = self::STATUS_VALIDATED;
2385
2386 $this->db->rollback();
2387 return -1;
2388 }
2389 }
2390
2400 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2401 {
2402 global $langs;
2403
2404 $error = 0;
2405
2406 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2407
2408 $langs->load("agenda");
2409
2410 // Loop on each product line to add a stock movement
2411 $sql = "SELECT cd.fk_product, cd.subprice,";
2412 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2413 $sql .= " e.ref,";
2414 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2415 $sql .= " cd.rowid as cdid, ed.rowid as edid";
2416 $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2417 $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2418 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2419 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2420 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2421 $sql .= " AND cd.rowid = ed.fk_elementdet";
2422
2423 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2424 $resql = $this->db->query($sql);
2425 if ($resql) {
2426 $cpt = $this->db->num_rows($resql);
2427 for ($i = 0; $i < $cpt; $i++) {
2428 $obj = $this->db->fetch_object($resql);
2429 if (empty($obj->edbrowid)) {
2430 $qty = $obj->qty;
2431 } else {
2432 $qty = $obj->edbqty;
2433 }
2434 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2435 continue;
2436 }
2437 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2438
2439 $mouvS = new MouvementStock($this->db);
2440 $mouvS->origin = &$this;
2441 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2442
2443 if (empty($obj->edbrowid)) {
2444 // line without batch detail
2445
2446 // 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
2447 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
2448 if ($result < 0) {
2449 $this->error = $mouvS->error;
2450 $this->errors = $mouvS->errors;
2451 $error++;
2452 break;
2453 }
2454 } else {
2455 // line with batch detail
2456
2457 // 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
2458 $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);
2459 if ($result < 0) {
2460 $this->error = $mouvS->error;
2461 $this->errors = $mouvS->errors;
2462 $error++;
2463 break;
2464 }
2465 }
2466
2467 // 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
2468 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2469 $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)";
2470 $resqldelete = $this->db->query($sqldelete);
2471 // We do not test error, it can fails if there is child in batch details
2472 }
2473 } else {
2474 $this->error = $this->db->lasterror();
2475 $this->errors[] = $this->db->lasterror();
2476 $error++;
2477 }
2478
2479 return $error;
2480 }
2481
2487 public function setBilled()
2488 {
2489 global $user;
2490 $error = 0;
2491
2492 $this->db->begin();
2493
2494 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET billed = 1';
2495 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2496
2497 $resql = $this->db->query($sql);
2498 if ($resql) {
2499 $this->billed = 1;
2500
2501 // Call trigger
2502 $result = $this->call_trigger('SHIPPING_BILLED', $user);
2503 if ($result < 0) {
2504 $this->billed = 0;
2505 $error++;
2506 }
2507 } else {
2508 $error++;
2509 $this->errors[] = $this->db->lasterror;
2510 }
2511
2512 if (empty($error)) {
2513 $this->db->commit();
2514 return 1;
2515 } else {
2516 $this->db->rollback();
2517 return -1;
2518 }
2519 }
2520
2528 public function setDraft($user, $notrigger = 0)
2529 {
2530 // Protection
2531 if ($this->statut <= self::STATUS_DRAFT) {
2532 return 0;
2533 }
2534
2535 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2536 }
2537
2543 public function reOpen()
2544 {
2545 global $langs, $user;
2546
2547 $error = 0;
2548
2549 // Protection. This avoid to move stock later when we should not
2550 if ($this->statut == self::STATUS_VALIDATED) {
2551 return 0;
2552 }
2553
2554 $this->db->begin();
2555
2556 $oldbilled = $this->billed;
2557
2558 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2559 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2560
2561 $resql = $this->db->query($sql);
2562 if ($resql) {
2563 $this->statut = self::STATUS_VALIDATED;
2565 $this->billed = 0;
2566
2567 // If stock increment is done on closing
2568 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2569 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2570
2571 $langs->load("agenda");
2572
2573 // Loop on each product line to add a stock movement
2574 // TODO possibilite d'expedier a partir d'une propale ou autre origine
2575 $sql = "SELECT cd.fk_product, cd.subprice,";
2576 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2577 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2578 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2579 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2580 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2581 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2582 $sql .= " AND cd.rowid = ed.fk_elementdet";
2583
2584 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2585 $resql = $this->db->query($sql);
2586 if ($resql) {
2587 $cpt = $this->db->num_rows($resql);
2588 for ($i = 0; $i < $cpt; $i++) {
2589 $obj = $this->db->fetch_object($resql);
2590 if (empty($obj->edbrowid)) {
2591 $qty = $obj->qty;
2592 } else {
2593 $qty = $obj->edbqty;
2594 }
2595 if ($qty <= 0) {
2596 continue;
2597 }
2598 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2599
2600 //var_dump($this->lines[$i]);
2601 $mouvS = new MouvementStock($this->db);
2602 $mouvS->origin = &$this;
2603 $mouvS->setOrigin($this->element, $this->id);
2604
2605 if (empty($obj->edbrowid)) {
2606 // line without batch detail
2607
2608 // 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
2609 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2610 if ($result < 0) {
2611 $this->error = $mouvS->error;
2612 $this->errors = $mouvS->errors;
2613 $error++;
2614 break;
2615 }
2616 } else {
2617 // line with batch detail
2618
2619 // 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
2620 $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);
2621 if ($result < 0) {
2622 $this->error = $mouvS->error;
2623 $this->errors = $mouvS->errors;
2624 $error++;
2625 break;
2626 }
2627 }
2628 }
2629 } else {
2630 $this->error = $this->db->lasterror();
2631 $error++;
2632 }
2633 }
2634
2635 if (!$error) {
2636 // Call trigger
2637 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2638 if ($result < 0) {
2639 $error++;
2640 }
2641 }
2642 } else {
2643 $error++;
2644 $this->errors[] = $this->db->lasterror();
2645 }
2646
2647 if (!$error) {
2648 $this->db->commit();
2649 return 1;
2650 } else {
2651 $this->statut = self::STATUS_CLOSED;
2652 $this->status = self::STATUS_CLOSED;
2653 $this->billed = $oldbilled;
2654 $this->db->rollback();
2655 return -1;
2656 }
2657 }
2658
2670 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2671 {
2672 $outputlangs->load("products");
2673
2674 if (!dol_strlen($modele)) {
2675 $modele = 'rouget';
2676
2677 if (!empty($this->model_pdf)) {
2678 $modele = $this->model_pdf;
2679 } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
2680 $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
2681 }
2682 }
2683
2684 $modelpath = "core/modules/expedition/doc/";
2685
2686 $this->fetch_origin();
2687
2688 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2689 }
2690
2699 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2700 {
2701 $tables = array(
2702 'expedition'
2703 );
2704
2705 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2706 }
2707}
$object ref
Definition info.php:79
Class to manage customers orders.
const STATUS_SHIPMENTONPROCESS
Shipment on process.
const STATUS_VALIDATED
Validated status.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
deleteEcmFiles($mode=0)
Delete related files of object in database.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='fk_statut')
Set status of an object.
deleteExtraFields()
Delete all extra fields values for the current object.
setStatusCommon($user, $status, $notrigger=0, $triggercode='')
Set to a status.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
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)
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:162
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)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (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.
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.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller='', $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:88
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:141