dolibarr 23.0.3
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-2025 Frédéric France <frederic.france@free.fr>
15 * Copyright (C) 2020 Lenin Rivas <lenin@leninrivas.com>
16 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
17 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
18 * Copyright (C) 2025 Nick Fragoulis
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 3 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program. If not, see <https://www.gnu.org/licenses/>.
32 */
33
40require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
41require_once DOL_DOCUMENT_ROOT."/expedition/class/expeditionligne.class.php";
42require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
43if (isModEnabled("propal")) {
44 require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
45}
46if (isModEnabled('order')) {
47 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
48}
49require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionlinebatch.class.php';
50require_once DOL_DOCUMENT_ROOT.'/core/class/commonsignedobject.class.php';
51require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php';
52
59{
60 use CommonIncoterm;
61 use CommonSignedObject;
62 use CommonSubtotal;
63
67 public $element = "shipping";
68
72 public $fk_element = "fk_expedition";
73
77 public $table_element = "expedition";
78
82 public $table_element_line = "expeditiondet";
83
87 public $class_element_line = 'ExpeditionLigne';
88
92 public $picto = 'dolly';
93
94
98 public $fields = array();
99
104 public $user_author_id;
105
110 public $fk_user_author;
111
116 public $fk_user_valid;
117
121 public $socid;
122
128 public $ref_client;
129
133 public $ref_customer;
134
138 public $entrepot_id;
139
143 public $tracking_number;
144
148 public $tracking_url;
152 public $billed;
153
157 public $trueWeight;
161 public $weight_units;
165 public $trueWidth;
169 public $width_units;
173 public $trueHeight;
177 public $height_units;
181 public $trueDepth;
185 public $depth_units;
189 public $trueSize;
190
194 public $livraison_id;
195
199 public $multicurrency_subprice;
200
204 public $size_units;
205
209 public $sizeH;
210
214 public $sizeS;
215
219 public $sizeW;
220
224 public $weight;
225
229 public $date_delivery;
230
236 public $date;
237
243 public $date_expedition;
244
249 public $date_shipping;
250
254 public $date_valid;
255
259 public $meths;
263 public $listmeths; // List of carriers
264
268 public $commande_id;
269
273 public $commande;
274
278 public $lines = array();
279
280 // Multicurrency
284 public $fk_multicurrency;
285
289 public $multicurrency_code;
293 public $multicurrency_tx;
297 public $multicurrency_total_ht;
301 public $multicurrency_total_tva;
305 public $multicurrency_total_ttc;
306
310 const STATUS_DRAFT = 0;
311
319
325 const STATUS_CLOSED = 2;
326
330 const STATUS_CANCELED = -1;
331
340
346 public function __construct($db)
347 {
348 global $conf;
349
350 $this->db = $db;
351
352 $this->ismultientitymanaged = 1;
353 $this->isextrafieldmanaged = 1;
354
355 // List of long language codes for status
356 $this->labelStatus = array();
357 $this->labelStatus[-1] = 'StatusSendingCanceled';
358 $this->labelStatus[0] = 'StatusSendingDraft';
359 $this->labelStatus[1] = 'StatusSendingValidated';
360 $this->labelStatus[2] = 'StatusSendingProcessed';
361
362 // List of short language codes for status
363 $this->labelStatusShort = array();
364 $this->labelStatusShort[-1] = 'StatusSendingCanceledShort';
365 $this->labelStatusShort[0] = 'StatusSendingDraftShort';
366 $this->labelStatusShort[1] = 'StatusSendingValidatedShort';
367 $this->labelStatusShort[2] = 'StatusSendingProcessedShort';
368 }
369
376 public function getNextNumRef($soc)
377 {
378 global $langs, $conf;
379 $langs->load("sendings");
380
381 if (getDolGlobalString('EXPEDITION_ADDON_NUMBER')) {
382 $mybool = false;
383
384 $file = getDolGlobalString('EXPEDITION_ADDON_NUMBER') . ".php";
385 $classname = getDolGlobalString('EXPEDITION_ADDON_NUMBER');
386
387 // Include file with class
388 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
389
390 foreach ($dirmodels as $reldir) {
391 $dir = dol_buildpath($reldir."core/modules/expedition/");
392
393 // Load file with numbering class (if found)
394 $mybool = ((bool) @include_once $dir.$file) || $mybool;
395 }
396
397 if (!$mybool) {
398 dol_print_error(null, "Failed to include file ".$file);
399 return '';
400 }
401
402 $obj = new $classname();
403 '@phan-var-force ModelNumRefExpedition $obj';
405 $numref = $obj->getNextValue($soc, $this);
406
407 if ($numref != "") {
408 return $numref;
409 } else {
410 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
411 return "";
412 }
413 } else {
414 print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
415 return "";
416 }
417 }
418
426 public function create($user, $notrigger = 0)
427 {
428 $now = dol_now();
429
430 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
431 $error = 0;
432
433 // Clean parameters
434 $this->tracking_number = dol_sanitizeFileName((string) $this->tracking_number);
435 if (empty($this->fk_project)) {
436 $this->fk_project = 0;
437 }
438 if (empty($this->date_creation)) {
439 $this->date_creation = $now;
440 }
441 if (empty($this->date_shipping) && !empty($this->date_expedition)) {
442 $this->date_shipping = $this->date_expedition;
443 }
444 $this->entity = setEntity($this);
445
446 $this->user = $user;
447
448 $this->db->begin();
449
450 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
451 $sql .= "ref";
452 $sql .= ", entity";
453 $sql .= ", ref_customer";
454 $sql .= ", ref_ext";
455 $sql .= ", date_creation";
456 $sql .= ", fk_user_author";
457 $sql .= ", date_expedition";
458 $sql .= ", date_delivery";
459 $sql .= ", fk_soc";
460 $sql .= ", fk_projet";
461 $sql .= ", fk_address";
462 $sql .= ", fk_shipping_method";
463 $sql .= ", tracking_number";
464 $sql .= ", weight";
465 $sql .= ", size";
466 $sql .= ", width";
467 $sql .= ", height";
468 $sql .= ", weight_units";
469 $sql .= ", size_units";
470 $sql .= ", note_private";
471 $sql .= ", note_public";
472 $sql .= ", model_pdf";
473 $sql .= ", fk_incoterms, location_incoterms";
474 $sql .= ", signed_status";
475 $sql .= ") VALUES (";
476 $sql .= "'(PROV)'";
477 $sql .= ", ".((int) $this->entity);
478 $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
479 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
480 $sql .= ", '".$this->db->idate($this->date_creation)."'";
481 $sql .= ", ".((int) $user->id);
482 $sql .= ", ".($this->date_shipping > 0 ? "'".$this->db->idate($this->date_shipping)."'" : "null");
483 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
484 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
485 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
486 $sql .= ", ".($this->fk_delivery_address > 0 ? ((int) $this->fk_delivery_address) : "null");
487 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
488 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
489 $sql .= ", ".(is_numeric($this->weight) ? (float) $this->weight : 'NULL');
490 $sql .= ", ".(is_numeric($this->sizeS) ? (float) $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
491 $sql .= ", ".(is_numeric($this->sizeW) ? (float) $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
492 $sql .= ", ".(is_numeric($this->sizeH) ? (float) $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
493 $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
494 $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
495 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
496 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
497 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
498 $sql .= ", ".((int) $this->fk_incoterms);
499 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
500 $sql .= ", ".((int) $this->signed_status);
501 $sql .= ")";
502
503 dol_syslog(get_class($this)."::create", LOG_DEBUG);
504 $resql = $this->db->query($sql);
505 if ($resql) {
506 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
507
508 // update ref
509 $initialref = '(PROV'.$this->id.')';
510 if (!empty($this->ref)) {
511 $initialref = $this->ref;
512 }
513
514 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
515 $sql .= " SET ref = '".$this->db->escape($initialref)."'";
516 $sql .= " WHERE rowid = ".((int) $this->id);
517
518 dol_syslog(get_class($this)."::create", LOG_DEBUG);
519 if ($this->db->query($sql)) {
520 $this->ref = $initialref;
521 // Insert of lines
522 $num = count($this->lines);
523 $kits_list = array();
524 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
525 for ($i = 0; $i < $num; $i++) {
526 $objectsrc = new OrderLine($this->db);
527 $objectsrc->fetch($this->lines[$i]->origin_line_id);
528 if ($this->lines[$i]->product_type == "9" && $objectsrc->special_code == SUBTOTALS_SPECIAL_CODE) {
529 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) {
530 $error++;
531 }
532 continue;
533 }
534 if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
535 // virtual products
536 $line = $this->lines[$i];
537 if ($line->fk_product > 0) {
538 if (!isset($kits_list[$line->fk_product])) {
539 if (!is_object($line->product)) {
540 $line_product = new Product($this->db);
541 $result = $line_product->fetch($line->fk_product, '', '', '', 1, 1, 1);
542 if ($result <= 0) {
543 $error++;
544 }
545 } else {
546 $line_product = $line->product;
547 }
548
549 // get all children of virtual product
550 $line_product->get_sousproduits_arbo();
551 $prods_arbo = $line_product->get_arbo_each_prod($line->qty);
552 if (count($prods_arbo) > 0) {
553 $kits_list[$line->fk_product] = array(
554 'arbo' => $prods_arbo,
555 'total_qty' => $line->qty,
556 );
557 }
558 } else {
559 $kits_list[$line->fk_product]['total_qty'] += $line->qty;
560 }
561 }
562 }
563 }
564 }
565 $kits_id_cached = array();
566 $sub_kits_id_cached = array();
567 for ($i = 0; $i < $num; $i++) {
568 $line = $this->lines[$i];
569 if (empty($line->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
570 $line_id = 0;
571 if (!isset($kits_id_cached[$line->fk_product])) {
572 if (!isset($line->detail_batch) || (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE'))) { // no batch management or is kit
573 $qty = isset($kits_list[$line->fk_product]) ? $kits_list[$line->fk_product]['total_qty'] : $line->qty;
574 $warehouse_id = (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) ? 0 : $line->entrepot_id;
575 $line_id = $this->create_line($warehouse_id, $line->origin_line_id, $qty, $line->rang, $line->array_options, 0, $line->fk_product);
576 if ($line_id <= 0) {
577 $error++;
578 }
579 if (isset($kits_list[$line->fk_product])) {
580 $kits_id_cached[$line->fk_product] = $line_id;
581 }
582 } else { // with batch management
583 if ($this->create_line_batch($line, $line->array_options) <= 0) {
584 $error++;
585 }
586 }
587 } else {
588 $line_id = $kits_id_cached[$line->fk_product];
589 }
590
591 // virtual products
592 if (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) {
593 $prods_arbo = $kits_list[$line->fk_product]['arbo'];
594 $total_qty = $kits_list[$line->fk_product]['total_qty'];
595
596 // get all children of virtual product
597 $parent_line_id = $line_id; // parent line created
598 $level_last = 1;
599 $product_child_id = 0;
600 foreach ($prods_arbo as $index => $product_child_arr) {
601 // 'id' => Id product
602 // 'id_parent' => Id parent product
603 // 'ref' => Ref product
604 // 'nb' => Nb of units that compose parent product
605 // 'nb_total' => // Nb of units for all nb of product
606 // 'stock' => Stock
607 // 'stock_alert' => Stock alert
608 // 'label' => Label
609 // 'fullpath' => // Full path label
610 // 'type' =>
611 // 'desiredstock' => Desired stock
612 // 'level' => Level
613 // 'incdec' => Need to be incremented or decremented
614 // 'entity' => Entity
615 $product_child_level = (int) $product_child_arr['level'];
616 $product_child_incdec = !empty($product_child_arr['incdec']);
617
618 // detect new level
619 if ($product_child_level != $level_last) {
620 $parent_line_id = $line_id; // last line id
621 $parent_product_id = $product_child_id; // last line id
622 if (isset($kits_id_cached[$parent_product_id])) {
623 $parent_line_id = $kits_id_cached[$parent_product_id];
624 } else {
625 $kits_id_cached[$parent_product_id] = $parent_line_id;
626 }
627 }
628
629 // determine if it's a kit : check next level
630 $is_kit = false;
631 $next_level = $product_child_level;
632 $next_index = $index + 1;
633 if (isset($prods_arbo[$next_index])) {
634 $next_level = (int) $prods_arbo[$next_index]['level'];
635 }
636 if ($next_level > $product_child_level) {
637 $is_kit = true;
638 }
639
640 // determine quantity of sub-product
641 $product_child_id = (int) $product_child_arr['id'];
642 $product_child_qty = (float) $product_child_arr['nb_total']; // by default
643 $warehouse_id = $line->entrepot_id; // by default
644 if ($is_kit || !$product_child_incdec) {
645 if (!$product_child_incdec) {
646 $product_child_qty = 0;
647 }
648 $warehouse_id = 0; // no warehouse used for a kit or if stock is not managed (empty incdec)
649 }
650
651 // create line for a child of virtual product
652 if (!isset($sub_kits_id_cached[$product_child_id]) || $warehouse_id > 0) {
653 $line_id = $this->create_line($warehouse_id, ($parent_line_id ? 0 : $line->origin_line_id), $product_child_qty, $line->rang, $line->array_options, $parent_line_id, $product_child_id);
654 if ($line_id <= 0) {
655 $error++;
656 dol_syslog(__METHOD__ . ' : ' . $this->errorsToString(), LOG_ERR);
657 break;
658 }
659
660 // if kit or not manage stock (empty incdec)
661 if (empty($warehouse_id)) {
662 $sub_kits_id_cached[$product_child_id] = $line_id;
663 }
664 }
665
666 $level_last = $product_child_level;
667 }
668 }
669 }
670 }
671
672 if (!$error && $this->id && $this->origin_id) {
673 $ret = $this->add_object_linked();
674 if (!$ret) {
675 $error++;
676 }
677 }
678
679 if (!$error && $this->id && getDolGlobalInt('SHIPPING_USE_ITS_OWN_CONTACTS') && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin_type) && !empty($this->origin_id)) { // Get contact from origin object
680 $originforcontact = $this->origin_type;
681 $originidforcontact = $this->origin_id;
682
683 $sqlcontact = "SELECT ctc.code, ctc.source, ec.fk_socpeople FROM ".MAIN_DB_PREFIX."element_contact as ec, ".MAIN_DB_PREFIX."c_type_contact as ctc";
684 $sqlcontact .= " WHERE element_id = ".((int) $originidforcontact)." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$this->db->escape($originforcontact)."'";
685
686 $resqlcontact = $this->db->query($sqlcontact);
687 if ($resqlcontact) {
688 while ($objcontact = $this->db->fetch_object($resqlcontact)) {
689 $this->add_contact($objcontact->fk_socpeople, $objcontact->code, $objcontact->source); // May failed because of duplicate key or because code of contact type does not exists for new object
690 }
691 } else {
692 dol_print_error($this->db);
693 }
694 }
695
696 // Actions on extra fields
697 if (!$error) {
698 $result = $this->insertExtraFields();
699 if ($result < 0) {
700 $error++;
701 }
702 }
703
704 if (!$error && !$notrigger) {
705 // Call trigger
706 $result = $this->call_trigger('SHIPPING_CREATE', $user);
707 if ($result < 0) {
708 $error++;
709 }
710 // End call triggers
711
712 if (!$error) {
713 $this->db->commit();
714 return $this->id;
715 } else {
716 foreach ($this->errors as $errmsg) {
717 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
718 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
719 }
720 $this->db->rollback();
721 return -1 * $error;
722 }
723 } else {
724 $error++;
725 $this->db->rollback();
726 return -3;
727 }
728 } else {
729 $error++;
730 $this->error = $this->db->lasterror()." - sql=$sql";
731 $this->db->rollback();
732 return -2;
733 }
734 } else {
735 $error++;
736 $this->error = $this->db->error()." - sql=$sql";
737 $this->db->rollback();
738 return -1;
739 }
740 }
741
742 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
755 public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [], $parent_line_id = 0, $product_id = 0)
756 {
757 //phpcs:enable
758 global $user;
759
760 $expeditionline = new ExpeditionLigne($this->db);
761 $expeditionline->fk_expedition = $this->id;
762 $expeditionline->entrepot_id = $entrepot_id;
763 $expeditionline->fk_elementdet = $origin_line_id;
764 $expeditionline->element_type = $this->origin;
765 $expeditionline->fk_parent = $parent_line_id;
766 $expeditionline->fk_product = $product_id;
767 $expeditionline->qty = $qty;
768 $expeditionline->rang = $rang;
769 $expeditionline->array_options = $array_options;
770
771 if (!($expeditionline->fk_product > 0)) {
772 $order_line = new OrderLine($this->db);
773 $order_line->fetch($expeditionline->fk_elementdet);
774 $expeditionline->fk_product = $order_line->fk_product;
775 }
776
777 if (($lineId = $expeditionline->insert($user)) < 0) {
778 $this->errors[] = $expeditionline->error;
779 }
780 return $lineId;
781 }
782
783
784 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
792 public function create_line_batch($line_ext, $array_options = [])
793 {
794 // phpcs:enable
795 $error = 0;
796 $stockLocationQty = array(); // associated array with batch qty in stock location
797
798 $tab = $line_ext->detail_batch;
799 // create stockLocation Qty array
800 foreach ($tab as $detbatch) {
801 if (!empty($detbatch->fk_warehouse)) {
802 if (empty($stockLocationQty[$detbatch->fk_warehouse])) {
803 $stockLocationQty[$detbatch->fk_warehouse] = 0;
804 }
805 $stockLocationQty[$detbatch->fk_warehouse] += $detbatch->qty;
806 }
807 }
808 // create shipment lines
809 foreach ($stockLocationQty as $stockLocation => $qty) {
810 $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
811 if ($line_id < 0) {
812 $error++;
813 } else {
814 // create shipment batch lines for stockLocation
815 foreach ($tab as $detbatch) {
816 if ($detbatch->fk_warehouse == $stockLocation) {
817 if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch
818 $this->errors = $detbatch->errors;
819 $error++;
820 }
821 }
822 }
823 }
824 }
825
826 if (!$error) {
827 return 1;
828 } else {
829 return -1;
830 }
831 }
832
842 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
843 {
844 global $conf;
845
846 // Check parameters
847 if (empty($id) && empty($ref) && empty($ref_ext)) {
848 return -1;
849 }
850
851 $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";
852 $sql .= ", e.date_valid";
853 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
854 $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
855 $sql .= ", e.fk_shipping_method, e.tracking_number";
856 $sql .= ", e.note_private, e.note_public";
857 $sql .= ', e.fk_incoterms, e.location_incoterms';
858 $sql .= ', e.signed_status';
859 $sql .= ', i.libelle as label_incoterms';
860 $sql .= ', s.libelle as shipping_method';
861 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin_type";
862 $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
863 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
864 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
865 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
866 $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
867 if ($id) {
868 $sql .= " AND e.rowid = ".((int) $id);
869 }
870 if ($ref) {
871 $sql .= " AND e.ref='".$this->db->escape($ref)."'";
872 }
873 if ($ref_ext) {
874 $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
875 }
876
877 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
878 $result = $this->db->query($sql);
879 if ($result) {
880 if ($this->db->num_rows($result)) {
881 $obj = $this->db->fetch_object($result);
882
883 $this->id = (int) $obj->rowid;
884 $this->entity = $obj->entity;
885 $this->ref = $obj->ref;
886 $this->socid = $obj->socid;
887 $this->ref_customer = $obj->ref_customer;
888 $this->ref_ext = $obj->ref_ext;
889 $this->status = $obj->fk_statut;
890 $this->statut = $this->status; // Deprecated
891 $this->signed_status = $obj->signed_status;
892 $this->user_author_id = $obj->fk_user_author;
893 $this->fk_user_author = $obj->fk_user_author;
894 $this->user_creation_id = $obj->fk_user_author;
895 $this->date_creation = $this->db->jdate($obj->date_creation);
896 $this->date_valid = $this->db->jdate($obj->date_valid);
897 $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
898 $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
899 $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
900 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
901 $this->fk_delivery_address = $obj->fk_address;
902 $this->model_pdf = $obj->model_pdf;
903 $this->shipping_method_id = $obj->fk_shipping_method;
904 $this->shipping_method = $obj->shipping_method;
905 $this->tracking_number = $obj->tracking_number;
906 $this->origin = ($obj->origin_type ? $obj->origin_type : 'commande'); // For compatibility
907 $this->origin_type = ($obj->origin_type ? $obj->origin_type : 'commande');
908 $this->origin_id = $obj->origin_id;
909 $this->billed = $obj->billed;
910 $this->fk_project = $obj->fk_project;
911 $this->signed_status = $obj->signed_status;
912 $this->trueWeight = $obj->weight;
913 $this->weight_units = $obj->weight_units;
914
915 $this->trueWidth = $obj->width;
916 $this->width_units = $obj->size_units;
917 $this->trueHeight = $obj->height;
918 $this->height_units = $obj->size_units;
919 $this->trueDepth = $obj->size;
920 $this->depth_units = $obj->size_units;
921
922 $this->note_public = $obj->note_public;
923 $this->note_private = $obj->note_private;
924
925 // A denormalized value
926 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
927 $this->size_units = $obj->size_units;
928
929 //Incoterms
930 $this->fk_incoterms = $obj->fk_incoterms;
931 $this->location_incoterms = $obj->location_incoterms;
932 $this->label_incoterms = $obj->label_incoterms;
933
934 $this->db->free($result);
935
936 // Tracking url
937 $this->getUrlTrackingStatus($obj->tracking_number);
938
939 // Thirdparty
940 $result = $this->fetch_thirdparty(); // TODO Remove this
941
942 // Retrieve extrafields
943 $this->fetch_optionals();
944
945 // Fix Get multicurrency param for transmitted
946 if (isModEnabled('multicurrency')) {
947 if (!empty($this->multicurrency_code)) {
948 $this->multicurrency_code = $this->thirdparty->multicurrency_code;
949 }
950 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($this->thirdparty->multicurrency_tx)) {
951 $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
952 }
953 }
954
955 /*
956 * Lines
957 */
958 if (empty($obj->origin_id)) {
959 $result = $this->fetch_lines_free();
960 } else {
961 $result = $this->fetch_lines();
962 }
963 if ($result < 0) {
964 return -3;
965 }
966
967 return 1;
968 } else {
969 dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
970 $this->error = 'Shipment with id '.$id.' not found';
971 return 0;
972 }
973 } else {
974 $this->error = $this->db->error();
975 return -1;
976 }
977 }
978
986 public function valid($user, $notrigger = 0)
987 {
988 global $conf;
989
990 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
991
992 dol_syslog(get_class($this)."::valid");
993
994 // Protection
995 if ($this->status) {
996 dol_syslog(get_class($this)."::valid not in draft status", LOG_WARNING);
997 return 0;
998 }
999
1000 if (!isset($this->socid)) {
1001 dol_syslog(get_class($this)."::can't valid socid not set", LOG_WARNING);
1002 return 0;
1003 }
1004
1005 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'creer'))
1006 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'shipping_advance', 'validate')))) {
1007 $this->error = 'Permission denied';
1008 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
1009 return -1;
1010 }
1011
1012 $this->db->begin();
1013
1014 $error = 0;
1015
1016 // Define new ref
1017 $soc = new Societe($this->db);
1018 $soc->fetch($this->socid);
1019
1020 // Class of company linked to order
1021 $result = $soc->setAsCustomer();
1022
1023 // Define new ref
1024 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1025 $numref = $this->getNextNumRef($soc);
1026 } elseif (!empty($this->ref)) {
1027 $numref = (string) $this->ref;
1028 } else {
1029 $numref = "EXP".$this->id;
1030 }
1031 $this->newref = dol_sanitizeFileName($numref);
1032
1033 $now = dol_now();
1034
1035 // Validate
1036 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1037 $sql .= " ref = '".$this->db->escape($numref)."'";
1038 $sql .= ", fk_statut = 1";
1039 $sql .= ", date_valid = '".$this->db->idate($now)."'";
1040 $sql .= ", fk_user_valid = ".((int) $user->id);
1041 $sql .= " WHERE rowid = ".((int) $this->id);
1042
1043 dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
1044 $resql = $this->db->query($sql);
1045 if (!$resql) {
1046 $this->error = $this->db->lasterror();
1047 $error++;
1048 }
1049
1050 // If stock increment is done on sending (recommended choice)
1051 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
1052 $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
1053 if ($result < 0) {
1054 return -2;
1055 }
1056 }
1057
1058 // Change status of order to "shipment in process"
1059 $triggerKey = 'SHIPPING_'; // Because when the trigger is fired the object is a shipping and not the real target object, so I add a prefix like SHIPPING_ to avoid confusion
1060 if ($this->origin == 'commande') {
1061 $triggerKey .= 'ORDER_SHIPMENTONPROCESS';
1062 } else {
1063 $triggerKey .= strtoupper($this->origin).'_SHIPMENTONPROCESS';
1064 }
1065
1066 // TODO : load the origin object to trigger the right setStatus according to origin object
1067 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin, $triggerKey);
1068 if (!$ret) {
1069 $error++;
1070 }
1071
1072 if (!$error && !$notrigger) {
1073 // Call trigger
1074 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
1075 if ($result < 0) {
1076 $error++;
1077 }
1078 // End call triggers
1079 }
1080
1081 if (!$error) {
1082 $this->oldref = $this->ref;
1083
1084 // Rename directory if dir was a temporary ref
1085 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1086 // Now we rename also files into index
1087 $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)."'";
1088 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
1089 $resql = $this->db->query($sql);
1090 if (!$resql) {
1091 $error++;
1092 $this->error = $this->db->lasterror();
1093 }
1094 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
1095 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
1096 $resql = $this->db->query($sql);
1097 if (!$resql) {
1098 $error++;
1099 $this->error = $this->db->lasterror();
1100 }
1101
1102 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1103 $oldref = dol_sanitizeFileName($this->ref);
1104 $newref = dol_sanitizeFileName($numref);
1105 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
1106 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
1107 if (!$error && file_exists($dirsource)) {
1108 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
1109
1110 if (@rename($dirsource, $dirdest)) {
1111 dol_syslog("Rename ok");
1112 // Rename docs starting with $oldref with $newref
1113 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
1114 foreach ($listoffiles as $fileentry) {
1115 $dirsource = $fileentry['name'];
1116 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1117 $dirsource = $fileentry['path'].'/'.$dirsource;
1118 $dirdest = $fileentry['path'].'/'.$dirdest;
1119 @rename($dirsource, $dirdest);
1120 }
1121 }
1122 }
1123 }
1124 }
1125
1126 // Set new ref and current status
1127 if (!$error) {
1128 $this->ref = $numref;
1129 $this->statut = self::STATUS_VALIDATED;
1131 }
1132
1133 if (!$error) {
1134 $this->db->commit();
1135 return 1;
1136 } else {
1137 $this->db->rollback();
1138 return -1 * $error;
1139 }
1140 }
1141
1142
1143 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1150 public function create_delivery($user)
1151 {
1152 // phpcs:enable
1153 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
1154 if ($this->status == self::STATUS_VALIDATED || $this->status == self::STATUS_CLOSED) {
1155 // Expedition validated
1156 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
1157 $delivery = new Delivery($this->db);
1158 $result = $delivery->create_from_sending($user, $this->id);
1159 if ($result <= 0) {
1160 $this->setErrorsFromObject($delivery);
1161 }
1162 return $result;
1163 }
1164 }
1165
1166 return 0;
1167 }
1168
1183 public function addline($entrepot_id, $id, $qty, $array_options = [], $fk_product = 0, $fk_parent = 0)
1184 {
1185 global $langs;
1186
1187 $num = count($this->lines);
1188 $line = new ExpeditionLigne($this->db);
1189
1190 $line->entrepot_id = $entrepot_id;
1191 $line->origin_line_id = $id;
1192 $line->fk_elementdet = $id;
1193 $line->element_type = 'order';
1194 $line->fk_parent = $fk_parent;
1195 $line->fk_product = $fk_product;
1196 $line->qty = $qty;
1197
1198 $orderline = new OrderLine($this->db);
1199 $orderline->fetch($id);
1200
1201 // Copy the rang of the order line to the expedition line
1202 $line->rang = $orderline->rang;
1203 $line->product_type = $orderline->product_type;
1204 if (!($line->fk_product > 0)) {
1205 $line->fk_product = $orderline->fk_product;
1206 }
1207
1208 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
1209 $product = new Product($this->db);
1210 $product->fetch($orderline->fk_product);
1211
1212 if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS') && !(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') && $line->product_type == Product::TYPE_SERVICE) && $product->stockable_product == Product::ENABLED_STOCK) {
1213 $langs->load("errors");
1214 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
1215 return -1;
1216 }
1217
1218 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
1219 $productChildrenNb = 0;
1220 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
1221 $productChildrenNb = $product->hasFatherOrChild(1);
1222 }
1223 if ($productChildrenNb > 0) {
1224 $product_stock = null;
1225 $product->loadStockForVirtualProduct('warehouseopen', $line->qty);
1226 if ($entrepot_id > 0) {
1227 if (isset($product->stock_warehouse[$entrepot_id])) {
1228 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
1229 }
1230 } else {
1231 foreach ($product->stock_warehouse as $componentStockWarehouse) {
1232 if ($product_stock === null) {
1233 $product_stock = $componentStockWarehouse->real;
1234 } else {
1235 $product_stock = min($product_stock, $componentStockWarehouse->real);
1236 }
1237 }
1238 }
1239 if ($product_stock === null) {
1240 $product_stock = 0;
1241 }
1242 } else {
1243 // Check must be done for stock of product into warehouse if $entrepot_id defined
1244 if ($entrepot_id > 0) {
1245 $product->load_stock('warehouseopen');
1246 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
1247 } else {
1248 $product_stock = $product->stock_reel;
1249 }
1250 }
1251
1252 $product_type = $product->type;
1253 if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1254 $isavirtualproduct = ($productChildrenNb > 0);
1255 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
1256 if (
1257 !$isavirtualproduct
1258 || !getDolGlobalInt('PRODUIT_SOUSPRODUITS')
1259 || ($isavirtualproduct && !getDolGlobalInt('STOCK_EXCLUDE_VIRTUAL_PRODUCTS'))
1260 ) {
1261 // If STOCK_EXCLUDE_VIRTUAL_PRODUCTS is set, we do not manage stock for kits/virtual products.
1262 if ($product->stockable_product == Product::ENABLED_STOCK && $product_stock < $qty) {
1263 $langs->load("errors");
1264 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
1265 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
1266
1267 return -3;
1268 }
1269 }
1270 }
1271 }
1272 }
1273
1274 // If product need a batch number, we should not have called this function but addline_batch instead.
1275 // If this happen, we may have a bug in card.php page
1276 if (isModEnabled('productbatch') && !empty($line->fk_product) && !empty($orderline->product_tobatch)) {
1277 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$line->fk_product; //
1278 return -4;
1279 }
1280
1281 // extrafields
1282 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1283 $line->array_options = $array_options;
1284 }
1285
1286 $this->lines[$num] = $line;
1287
1288 return 1;
1289 }
1290
1304 public function addlinefree($qty, $element_type, $fk_product, $fk_unit, $rang, $description, $fk_parent, $array_options = [])
1305 {
1306 global $mysoc, $langs, $user;
1307
1308 if ($this->status == self::STATUS_DRAFT) {
1309 if (empty($rang)) {
1310 $rang = 0;
1311 }
1312
1313 $qty = (float) price2num($qty);
1314
1315 $this->db->begin();
1316
1317 // Rang to use
1318 $ranktouse = $rang;
1319 if ($ranktouse == -1) {
1320 $rangmax = $this->line_max($fk_parent);
1321 $ranktouse = $rangmax + 1;
1322 }
1323
1324 // Insert line
1325 $this->line = new ExpeditionLigne($this->db);
1326 $this->line->fk_expedition = $this->id;
1327 $this->line->element_type = $element_type;
1328 $this->line->fk_product = $fk_product;
1329 $this->line->description = $description;
1330 $this->line->desc = $description;
1331 $this->line->fk_parent = $fk_parent;
1332 $this->line->qty = (float) $qty;
1333 $this->line->fk_unit = $fk_unit;
1334 $this->line->rang = $ranktouse;
1335
1336 if (is_array($array_options) && count($array_options) > 0) {
1337 $this->line->array_options = $array_options;
1338 }
1339
1340 $result = $this->line->insert($user);
1341 if ($result > 0) {
1342 if (!isset($this->context['createfromclone'])) {
1343 if (!empty($fk_parent)) {
1344 $this->line_order(true, 'DESC');
1345 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
1346 $linecount = count($this->lines);
1347 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
1348 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
1349 }
1350 }
1351 $this->lines[] = $this->line;
1352 }
1353
1354 $this->db->commit();
1355 return $this->line->id;
1356 } else {
1357 $this->error = $this->line->error;
1358 dol_syslog(get_class($this)."::addlinefree error=".$this->error, LOG_ERR);
1359 $this->db->rollback();
1360 return -2;
1361 }
1362 } else {
1363 dol_syslog(get_class($this)."::addlinefree status of shipment must be Draft to allow use of ->addlinefree()", LOG_ERR);
1364 return -3;
1365 }
1366 }
1367
1383 public function updatelinefree($rowid, $qty, $element_type, $fk_product, $fk_unit, $rang, $description, $fk_parent, $notrigger, $array_options = array())
1384 {
1385 global $mysoc, $langs, $user;
1386
1387 if ($this->status == self::STATUS_DRAFT) {
1388 $this->db->begin();
1389
1390 if (empty($qty)) {
1391 $qty = 0;
1392 }
1393 if (empty($rang)) {
1394 $rang = 0;
1395 }
1396
1397 $qty = (float) $qty;
1398 $description = trim($description);
1399
1400 // Fetch current line from the database and then clone the object and set it in $oldline property
1401 $line = new ExpeditionLigne($this->db);
1402
1403 $line->fetch($rowid);
1404 $line->fetch_optionals();
1405
1406 if (!empty($line->fk_product)) {
1407 $product = new Product($this->db);
1408 $result = $product->fetch($line->fk_product);
1409 $product_type = $product->type;
1410 }
1411
1412 $staticline = clone $line;
1413
1414 $line->oldline = $staticline;
1415 $this->line = $line;
1416 $this->line->context = $this->context;
1417 $this->line->rang = $rang;
1418 $this->line->fk_expedition = $this->id;
1419 $this->line->element_type = $element_type;
1420 $this->line->fk_product = $fk_product;
1421 $this->line->qty = $qty;
1422 $this->line->fk_unit = $fk_unit;
1423 $this->line->fk_parent = $fk_parent;
1424 $this->line->description = $description;
1425
1426 if (is_array($array_options) && count($array_options) > 0) {
1427 // We replace values in this->line->array_options only for entries defined into $array_options
1428 foreach ($array_options as $key => $value) {
1429 $this->line->array_options[$key] = $array_options[$key];
1430 }
1431 }
1432
1433 $result = $this->line->update($user, $notrigger);
1434 if ($result > 0) {
1435 // Reorder if child line
1436 if (!empty($fk_parent)) {
1437 $this->line_order(true, 'DESC');
1438 }
1439
1440 $this->db->commit();
1441 return $result;
1442 } else {
1443 $this->error = $this->line->error;
1444
1445 $this->db->rollback();
1446 return -1;
1447 }
1448 } else {
1449 $this->error = get_class($this)."::updatelinefree Shipment status makes operation forbidden";
1450 $this->errors = array('ShipmentStatusMakeOperationForbidden');
1451 return -2;
1452 }
1453 }
1454
1455 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1464 public function addline_batch($dbatch, $array_options = [], $origin_line = null)
1465 {
1466 // phpcs:enable
1467 global $langs;
1468
1469 $num = count($this->lines);
1470 $linebatch = null;
1471 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1472 $line = new ExpeditionLigne($this->db);
1473 $tab = array();
1474 foreach ($dbatch['detail'] as $key => $value) {
1475 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1476 // $value['q']=qty to move
1477 // $value['id_batch']=id into llx_product_batch of record to move
1478 //var_dump($value);
1479
1480 $linebatch = new ExpeditionLineBatch($this->db);
1481 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1482 if ($ret < 0) {
1483 $this->setErrorsFromObject($linebatch);
1484 return -1;
1485 }
1486 $linebatch->qty = $value['q'];
1487 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1488 $linebatch->batch = null;
1489 }
1490 $tab[] = $linebatch;
1491
1492 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1493 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1494 $prod_batch = new Productbatch($this->db);
1495 $prod_batch->fetch($value['id_batch']);
1496
1497 if ($prod_batch->qty < $linebatch->qty) {
1498 $langs->load("errors");
1499 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1500 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1501 return -1;
1502 }
1503 }
1504 }
1505 }
1506 if (is_object($linebatch)) {
1507 $line->entrepot_id = $linebatch->fk_warehouse;
1508 }
1509 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1510 $line->fk_elementdet = $dbatch['ix_l'];
1511 $line->qty = $dbatch['qty'];
1512 $line->detail_batch = $tab;
1513 if (!($line->rang > 0)) {
1514 $line->rang = $origin_line->rang;
1515 }
1516 if (!($line->fk_product > 0)) {
1517 $line->fk_product = $origin_line->fk_product;
1518 }
1519
1520 // extrafields
1521 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1522 $line->array_options = $array_options;
1523 }
1524
1525 //var_dump($line);
1526 $this->lines[$num] = $line;
1527 return 1;
1528 }
1529 return 0;
1530 }
1531
1539 public function update($user = null, $notrigger = 0)
1540 {
1541 global $conf;
1542 $error = 0;
1543
1544 // Clean parameters
1545
1546 if (isset($this->ref)) {
1547 $this->ref = trim($this->ref);
1548 }
1549 if (isset($this->entity)) {
1550 $this->entity = (int) $this->entity;
1551 }
1552 if (isset($this->ref_customer)) {
1553 $this->ref_customer = trim($this->ref_customer);
1554 }
1555 if (isset($this->socid)) {
1556 $this->socid = (int) $this->socid;
1557 }
1558 if (isset($this->fk_user_author)) {
1559 $this->fk_user_author = (int) $this->fk_user_author;
1560 }
1561 if (isset($this->fk_user_valid)) {
1562 $this->fk_user_valid = (int) $this->fk_user_valid;
1563 }
1564 if (isset($this->fk_delivery_address)) {
1565 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1566 }
1567 if (isset($this->shipping_method_id)) {
1568 $this->shipping_method_id = (int) $this->shipping_method_id;
1569 }
1570 if (isset($this->tracking_number)) {
1571 $this->tracking_number = trim($this->tracking_number);
1572 }
1573 if (isset($this->statut)) {
1574 $this->statut = (int) $this->statut;
1575 }
1576 if (isset($this->status)) {
1577 $this->status = (int) $this->status;
1578 }
1579 if (isset($this->trueDepth)) {
1580 $this->trueDepth = trim($this->trueDepth);
1581 }
1582 if (isset($this->trueWidth)) {
1583 $this->trueWidth = trim($this->trueWidth);
1584 }
1585 if (isset($this->trueHeight)) {
1586 $this->trueHeight = trim($this->trueHeight);
1587 }
1588 if (isset($this->size_units)) {
1589 $this->size_units = trim($this->size_units);
1590 }
1591 if (isset($this->weight_units)) {
1592 $this->weight_units = (int) $this->weight_units;
1593 }
1594 if (isset($this->trueWeight)) {
1595 $this->weight = trim((string) $this->trueWeight);
1596 }
1597 if (isset($this->note_private)) {
1598 $this->note_private = trim($this->note_private);
1599 }
1600 if (isset($this->note_public)) {
1601 $this->note_public = trim($this->note_public);
1602 }
1603 if (isset($this->model_pdf)) {
1604 $this->model_pdf = trim($this->model_pdf);
1605 }
1606 if (!empty($this->date_expedition)) {
1607 $this->date_shipping = $this->date_expedition;
1608 }
1609
1610 // Check parameters
1611 // Put here code to add control on parameters values
1612
1613 // Update request
1614 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1615 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1616 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1617 $sql .= " ref_customer = ".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1618 $sql .= " fk_soc = ".(isset($this->socid) ? ((int) $this->socid) : "null").",";
1619 $sql .= " date_creation = ".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1620 $sql .= " fk_user_author = ".(isset($this->fk_user_author) ? ((int) $this->fk_user_author) : "null").",";
1621 $sql .= " date_valid = ".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1622 $sql .= " fk_user_valid = ".(isset($this->fk_user_valid) ? ((int) $this->fk_user_valid) : "null").",";
1623 $sql .= " date_expedition = ".(dol_strlen($this->date_shipping) != 0 ? "'".$this->db->idate($this->date_shipping)."'" : 'null').",";
1624 $sql .= " date_delivery = ".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1625 $sql .= " fk_address = ".(isset($this->fk_delivery_address) ? ((int) $this->fk_delivery_address) : "null").",";
1626 $sql .= " fk_shipping_method = ".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? ((int) $this->shipping_method_id) : "null").",";
1627 $sql .= " tracking_number = ".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1628 $sql .= " fk_statut = ".(isset($this->status) ? ((int) $this->status) : "null").",";
1629 $sql .= " fk_projet = ".(isset($this->fk_project) ? ((int) $this->fk_project) : "null").",";
1630 $sql .= " height = ".(($this->trueHeight != '') ? ((float) $this->trueHeight) : "null").",";
1631 $sql .= " width = ".(($this->trueWidth != '') ? ((float) $this->trueWidth) : "null").",";
1632 $sql .= " size_units = ".(isset($this->size_units) ? ((int) $this->size_units) : "null").",";
1633 $sql .= " size = ".(($this->trueDepth != '') ? ((float) $this->trueDepth) : "null").",";
1634 $sql .= " weight_units = ".(isset($this->weight_units) ? ((int) $this->weight_units) : "null").",";
1635 $sql .= " weight = ".(($this->trueWeight != '') ? ((float) $this->trueWeight) : "null").",";
1636 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1637 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1638 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1639 $sql .= " entity = ".((int) $conf->entity);
1640 $sql .= " WHERE rowid = ".((int) $this->id);
1641
1642 $this->db->begin();
1643
1644 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1645 $resql = $this->db->query($sql);
1646 if (!$resql) {
1647 $error++;
1648 $this->errors[] = "Error ".$this->db->lasterror();
1649 }
1650
1651 // Actions on extra fields
1652 if (!$error) {
1653 $result = $this->insertExtraFields();
1654 if ($result < 0) {
1655 $error++;
1656 }
1657 }
1658
1659 if (!$error && !$notrigger) {
1660 // Call trigger
1661 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1662 if ($result < 0) {
1663 $error++;
1664 }
1665 // End call triggers
1666 }
1667
1668 // Commit or rollback
1669 if ($error) {
1670 foreach ($this->errors as $errmsg) {
1671 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1672 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1673 }
1674 $this->db->rollback();
1675 return -1 * $error;
1676 } else {
1677 $this->db->commit();
1678 return 1;
1679 }
1680 }
1681
1682
1691 public function cancel($user, $notrigger = 0, $also_update_stock = false)
1692 {
1693 global $conf, $langs, $user;
1694
1695 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1696
1697 $error = 0;
1698 $this->error = '';
1699
1700 $this->db->begin();
1701
1702 // Add a protection to refuse deleting if shipment has at least one delivery
1703 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1704 if (isset($this->linkedObjectsIds['delivery']) && count($this->linkedObjectsIds['delivery']) > 0) {
1705 $this->error = 'ErrorThereIsSomeDeliveries';
1706 $error++;
1707 }
1708
1709 if (!$error && !$notrigger) {
1710 // Call trigger
1711 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1712 if ($result < 0) {
1713 $error++;
1714 }
1715 // End call triggers
1716 }
1717
1718 // Stock control
1719 $can_update_stock = isModEnabled('stock') &&
1720 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->status > self::STATUS_DRAFT) ||
1721 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->status == self::STATUS_CLOSED && $also_update_stock));
1722 if (!$error) {
1723 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1724
1725 $langs->load("agenda");
1726
1727 // Loop on each product line to add a stock movement (contain sub-products)
1728 $sql = "SELECT ";
1729 $sql .= " ed.fk_product";
1730 $sql .= ", ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1731 $sql .= ", SUM(".$this->db->ifsql("pa.rowid IS NOT NULL", "1", "0").") as iskit";
1732 $sql .= ", ".$this->db->ifsql("pai.incdec IS NULL", "1", "pai.incdec")." as incdec";
1733 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
1734 $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pa ON pa.fk_product_pere = ed.fk_product";
1735 $sql .= " LEFT JOIN ".$this->db->prefix()."expeditiondet as edp ON edp.rowid = ed.fk_parent";
1736 $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pai ON pai.fk_product_pere = edp.fk_product AND pai.fk_product_fils = ed.fk_product";
1737 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1738 $sql .= " GROUP BY ed.fk_product, ed.qty, ed.fk_entrepot, ed.rowid, pai.incdec";
1739 $sql .= $this->db->order("ed.rowid", "DESC");
1740
1741 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1742 $resql = $this->db->query($sql);
1743 if ($resql) {
1744 $cpt = $this->db->num_rows($resql);
1745
1746 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1747
1748 for ($i = 0; $i < $cpt; $i++) {
1749 dol_syslog(get_class($this)."::delete movement index ".$i);
1750 $obj = $this->db->fetch_object($resql);
1751 $line_id = (int) $obj->expeditiondet_id;
1752
1753 if ($can_update_stock && (empty($obj->iskit) || getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) && !empty($obj->incdec)) {
1754 $mouvS = new MouvementStock($this->db);
1755 // we do not log origin because it will be deleted
1756 $mouvS->origin = '';
1757 // get lot/serial
1758 $lotArray = null;
1759 if (isModEnabled('productbatch')) {
1760 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1761 if (!is_array($lotArray)) {
1762 $error++;
1763 $this->errors[] = "Error ".$this->db->lasterror();
1764 }
1765 }
1766
1767 if (empty($lotArray)) {
1768 // no lot/serial
1769 // We increment stock of product (and sub-products)
1770 // We use warehouse selected for each line
1771 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref), '', '', '', '', 0, '', 0, (empty($obj->iskit) ? 1 : 0)); // Price is set to 0, because we don't want to see WAP changed
1772 if ($result < 0) {
1773 $error++;
1774 $this->errors = array_merge($this->errors, $mouvS->errors);
1775 break;
1776 }
1777 } else {
1778 // We increment stock of batches
1779 // We use warehouse selected for each line
1780 foreach ($lotArray as $lot) {
1781 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref), $lot->eatby, $lot->sellby, (string) $lot->batch, '', 0, '', 0, (empty($obj->iskit) ? 1 : 0)); // Price is set to 0, because we don't want to see WAP changed
1782 if ($result < 0) {
1783 $error++;
1784 $this->errors = array_merge($this->errors, $mouvS->errors);
1785 break;
1786 }
1787 }
1788 if ($error) {
1789 break; // break for loop in case of error
1790 }
1791 }
1792 }
1793
1794 if (!$error) {
1795 // delete all children and batches of this shipment line
1796 $shipment_line = new ExpeditionLigne($this->db);
1797 $res = $shipment_line->fetch($line_id);
1798 if ($res > 0) {
1799 $result = $shipment_line->delete($user);
1800 if ($result < 0) {
1801 $error++;
1802 $this->errors[] = "Error ".$shipment_line->errorsToString();
1803 }
1804 } else {
1805 $error++;
1806 $this->errors[] = "Error ".$shipment_line->errorsToString();
1807 }
1808 }
1809
1810 if ($error) {
1811 break;
1812 }
1813 }
1814 } else {
1815 $error++;
1816 $this->errors[] = "Error ".$this->db->lasterror();
1817 }
1818 }
1819
1820 if (!$error) {
1821 // Delete linked object
1822 $res = $this->deleteObjectLinked();
1823 if ($res < 0) {
1824 $error++;
1825 }
1826
1827 // No delete expedition
1828 if (!$error) {
1829 $sql = "SELECT rowid FROM ".$this->db->prefix()."expedition";
1830 $sql .= " WHERE rowid = ".((int) $this->id);
1831
1832 if ($this->db->query($sql)) {
1833 if (!empty($this->origin) && $this->origin_id > 0) {
1834 $this->fetch_origin();
1836 '@phan-var-force Facture|Commande $origin_object';
1838 if ($origin_object->status == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1839 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1840 $origin_object->loadExpeditions();
1841 if (count($origin_object->expeditions) <= 0) {
1843 }
1844 }
1845 }
1846
1847 $this->db->commit();
1848
1849 // We delete PDFs
1850 $ref = dol_sanitizeFileName($this->ref);
1851 if (!empty($conf->expedition->dir_output)) {
1852 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1853 $file = $dir.'/'.$ref.'.pdf';
1854 if (file_exists($file)) {
1855 if (!dol_delete_file($file)) {
1856 return 0;
1857 }
1858 }
1859 if (file_exists($dir)) {
1860 if (!dol_delete_dir_recursive($dir)) {
1861 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1862 return 0;
1863 }
1864 }
1865 }
1866
1867 return 1;
1868 } else {
1869 $this->error = $this->db->lasterror()." - sql=$sql";
1870 $this->db->rollback();
1871 return -3;
1872 }
1873 } else {
1874 $this->db->rollback();
1875 return -2;
1876 }
1877 } else {
1878 $this->db->rollback();
1879 return -1;
1880 }
1881 }
1882
1892 public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1893 {
1894 global $conf, $langs;
1895
1896 if (empty($user)) {
1897 global $user;
1898 }
1899
1900 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1901
1902 $error = 0;
1903 $this->error = '';
1904
1905 $this->db->begin();
1906
1907 // Add a protection to refuse deleting if shipment has at least one delivery
1908 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1909 if (isset($this->linkedObjectsIds['delivery']) && count($this->linkedObjectsIds['delivery']) > 0) {
1910 $this->error = 'ErrorThereIsSomeDeliveries';
1911 $error++;
1912 }
1913
1914 if (!$error && !$notrigger) {
1915 // Call trigger
1916 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1917 if ($result < 0) {
1918 $error++;
1919 }
1920 // End call triggers
1921 }
1922
1923 // Stock control
1924 $can_update_stock = isModEnabled('stock') &&
1925 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->status > self::STATUS_DRAFT) ||
1926 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->status == self::STATUS_CLOSED && $also_update_stock));
1927 if (!$error) {
1928 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1929
1930 $langs->load("agenda");
1931
1932 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1933 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1934
1935 // Loop on each product line to add a stock movement (contain sub-products)
1936 $sql = "SELECT ";
1937 $sql .= " ed.fk_product";
1938 $sql .= ", ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1939 $sql .= ", SUM(".$this->db->ifsql("pa.rowid IS NOT NULL", "1", "0").") as iskit";
1940 $sql .= ", ".$this->db->ifsql("pai.incdec IS NULL", "1", "pai.incdec")." as incdec";
1941 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
1942 $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pa ON pa.fk_product_pere = ed.fk_product";
1943 $sql .= " LEFT JOIN ".$this->db->prefix()."expeditiondet as edp ON edp.rowid = ed.fk_parent";
1944 $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pai ON pai.fk_product_pere = edp.fk_product AND pai.fk_product_fils = ed.fk_product";
1945 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1946 $sql .= " GROUP BY ed.fk_product, ed.qty, ed.fk_entrepot, ed.rowid, pai.incdec";
1947 $sql .= $this->db->order("ed.rowid", "DESC");
1948
1949 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1950 $resql = $this->db->query($sql);
1951 if ($resql) {
1952 $cpt = $this->db->num_rows($resql);
1953 for ($i = 0; $i < $cpt; $i++) {
1954 dol_syslog(get_class($this)."::delete movement index ".$i);
1955 $obj = $this->db->fetch_object($resql);
1956 $line_id = (int) $obj->expeditiondet_id;
1957
1958 if ($can_update_stock && (empty($obj->iskit) || getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) && !empty($obj->incdec)) {
1959 $mouvS = new MouvementStock($this->db);
1960 // we do not log origin because it will be deleted
1961 $mouvS->origin = '';
1962 // get lot/serial
1963 $lotArray = $shipmentlinebatch->fetchAll($line_id);
1964 if (!is_array($lotArray)) {
1965 $error++;
1966 $this->errors[] = "Error ".$this->db->lasterror();
1967 }
1968 if (empty($lotArray)) {
1969 // no lot/serial
1970 // We increment stock of product (disable for sub-products : already in shipment lines)
1971 // We use warehouse selected for each line
1972 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref), '', '', '', '', 0, '', 0, (empty($obj->iskit) ? 1 : 0)); // Price is set to 0, because we don't want to see WAP changed
1973 if ($result < 0) {
1974 $error++;
1975 $this->errors = array_merge($this->errors, $mouvS->errors);
1976 break;
1977 }
1978 } else {
1979 // We increment stock of batches
1980 // We use warehouse selected for each line
1981 foreach ($lotArray as $lot) {
1982 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref), $lot->eatby, $lot->sellby, (string) $lot->batch, '', 0, '', 0, (empty($obj->iskit) ? 1 : 0)); // Price is set to 0, because we don't want to see WAP changed
1983 if ($result < 0) {
1984 $error++;
1985 $this->errors = array_merge($this->errors, $mouvS->errors);
1986 break;
1987 }
1988 }
1989 if ($error) {
1990 break; // break for loop in case of error
1991 }
1992 }
1993 }
1994
1995 if (!$error) {
1996 // delete all children and batches of this shipment line
1997 $shipment_line = new ExpeditionLigne($this->db);
1998 $res = $shipment_line->fetch($line_id);
1999 if ($res > 0) {
2000 $result = $shipment_line->delete($user);
2001 if ($result < 0) {
2002 $error++;
2003 $this->errors[] = "Error ".$shipment_line->errorsToString();
2004 }
2005 } else {
2006 $error++;
2007 $this->errors[] = "Error ".$shipment_line->errorsToString();
2008 }
2009 }
2010
2011 if ($error) {
2012 break;
2013 }
2014 }
2015 } else {
2016 $error++;
2017 $this->errors[] = "Error ".$this->db->lasterror();
2018 }
2019 }
2020
2021 if (!$error) {
2022 // Delete linked object
2023 $res = $this->deleteObjectLinked();
2024 if ($res < 0) {
2025 $error++;
2026 }
2027
2028 // delete extrafields
2029 $res = $this->deleteExtraFields();
2030 if ($res < 0) {
2031 $error++;
2032 }
2033
2034 if (!$error) {
2035 // Delete linked contacts
2036 $res = $this->delete_linked_contact();
2037 if ($res < 0) {
2038 $error++;
2039 }
2040 }
2041 if (!$error) {
2042 $sql = "DELETE FROM ".$this->db->prefix()."expedition";
2043 $sql .= " WHERE rowid = ".((int) $this->id);
2044
2045 if ($this->db->query($sql)) {
2046 if (!empty($this->origin) && $this->origin_id > 0) {
2047 $this->fetch_origin();
2049 '@phan-var-force Facture|Commande $origin_object';
2050 if ($origin_object->status == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
2051 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
2052 $origin_object->loadExpeditions();
2053 if (count($origin_object->expeditions) <= 0) {
2055 }
2056 }
2057 }
2058
2059 $this->db->commit();
2060
2061 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2062 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2063 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2064
2065 // We delete PDFs
2066 $ref = dol_sanitizeFileName($this->ref);
2067 if (!empty($conf->expedition->dir_output)) {
2068 $dir = $conf->expedition->dir_output . '/sending/' . $ref;
2069 $file = $dir . '/' . $ref . '.pdf';
2070 if (file_exists($file)) {
2071 if (!dol_delete_file($file)) {
2072 return 0;
2073 }
2074 }
2075 if (file_exists($dir)) {
2076 if (!dol_delete_dir_recursive($dir)) {
2077 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
2078 return 0;
2079 }
2080 }
2081 }
2082
2083 return 1;
2084 } else {
2085 $this->error = $this->db->lasterror()." - sql=$sql";
2086 $this->db->rollback();
2087 return -3;
2088 }
2089 } else {
2090 $this->db->rollback();
2091 return -2;
2092 }
2093 } else {
2094 $this->db->rollback();
2095 return -1;
2096 }
2097 }
2098
2099 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2105 public function fetch_lines()
2106 {
2107 // phpcs:enable
2108 global $mysoc;
2109
2110 $this->lines = array();
2111
2112 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
2113 // TODO: See if we can restore a common fetch_lines (one line = one record)
2114
2115 $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";
2116 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
2117 $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
2118 $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";
2119 $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, cd.special_code";
2120 $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot, ed.extraparams";
2121 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
2122 $sql .= ", p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units";
2123 $sql .= ", p.surface, p.surface_units, p.volume, p.volume_units, p.tosell as product_tosell, p.tobuy as product_tobuy";
2124 $sql .= ", p.tobatch as product_tobatch, p.stockable_product";
2125 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
2126 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
2127 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2128 $sql .= " AND ed.fk_elementdet = cd.rowid";
2129 $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.
2130
2131 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
2132 $resql = $this->db->query($sql);
2133 if ($resql) {
2134 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2135
2136 $num = $this->db->num_rows($resql);
2137 $i = 0;
2138 $line = new ExpeditionLigne($this->db);
2139 $lineindex = 0;
2140 $originline = 0;
2141
2142 $this->total_ht = 0;
2143 $this->total_tva = 0;
2144 $this->total_ttc = 0;
2145 $this->total_localtax1 = 0;
2146 $this->total_localtax2 = 0;
2147
2148 $this->multicurrency_total_ht = 0;
2149 $this->multicurrency_total_tva = 0;
2150 $this->multicurrency_total_ttc = 0;
2151
2152 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
2153
2154 $line = new ExpeditionLigne($this->db); // always set $line for PHAN analyser. @phan-var-force muse be used after an assignation, and there is no assignation for $line.
2155 while ($i < $num) {
2156 $obj = $this->db->fetch_object($resql);
2157
2158 if ($originline > 0 && $originline == $obj->fk_elementdet) {
2159 // '@phan-var-force ExpeditionLigne $line'; // $line from previous loop
2160 $line->entrepot_id = 0; // entrepod_id in details_entrepot
2161 $line->qty_shipped += $obj->qty_shipped;
2162 } else {
2163 $line = new ExpeditionLigne($this->db); // new group to start
2164 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
2165 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
2166 }
2167
2168 $detail_entrepot = new stdClass();
2169 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
2170 $detail_entrepot->qty_shipped = $obj->qty_shipped;
2171 $detail_entrepot->line_id = $obj->line_id;
2172 $line->details_entrepot[] = $detail_entrepot;
2173
2174 $line->line_id = $obj->line_id; // TODO deprecated
2175 $line->rowid = $obj->line_id; // TODO deprecated
2176 $line->id = $obj->line_id;
2177
2178 $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
2179
2180 $line->fk_element = $obj->fk_element;
2181 $line->origin_id = $obj->fk_element;
2182 $line->fk_elementdet = $obj->fk_elementdet;
2183 $line->origin_line_id = $obj->fk_elementdet;
2184 $line->element_type = $obj->element_type;
2185
2186 $line->fk_expedition = $this->id; // id of parent
2187
2188 $line->stockable_product = $obj->stockable_product;
2189 $line->product_type = $obj->product_type;
2190 $line->fk_product = $obj->fk_product;
2191 $line->fk_product_type = $obj->fk_product_type;
2192 $line->ref = $obj->product_ref; // TODO deprecated
2193 $line->product_ref = $obj->product_ref;
2194 $line->product_label = $obj->product_label;
2195 $line->libelle = $obj->product_label; // TODO deprecated
2196 $line->product_barcode = $obj->product_barcode; // Barcode number product
2197 $line->product_tosell = $obj->product_tosell;
2198 $line->product_tobuy = $obj->product_tobuy;
2199 $line->product_tobatch = $obj->product_tobatch;
2200 $line->fk_fournprice = $obj->fk_fournprice;
2201 $line->label = $obj->custom_label;
2202 $line->description = $obj->description;
2203 $line->qty_asked = $obj->qty_asked;
2204 $line->rang = $obj->rang;
2205 $line->weight = $obj->weight;
2206 $line->weight_units = $obj->weight_units;
2207 $line->length = $obj->length;
2208 $line->length_units = $obj->length_units;
2209 $line->width = $obj->width;
2210 $line->width_units = $obj->width_units;
2211 $line->height = $obj->height;
2212 $line->height_units = $obj->height_units;
2213 $line->surface = $obj->surface;
2214 $line->surface_units = $obj->surface_units;
2215 $line->volume = $obj->volume;
2216 $line->volume_units = $obj->volume_units;
2217 $line->stockable_product = $obj->stockable_product;
2218 $line->fk_unit = $obj->fk_unit;
2219
2220 $line->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2221
2222 $line->pa_ht = $obj->pa_ht;
2223
2224 // Local taxes
2225 $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
2226 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
2227 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
2228
2229 // For invoicing
2230 $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
2231 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
2232 $line->qty = $line->qty_shipped;
2233 $line->total_ht = (float) $tabprice[0];
2234 $line->total_localtax1 = (float) $tabprice[9];
2235 $line->total_localtax2 = (float) $tabprice[10];
2236 $line->total_ttc = (float) $tabprice[2];
2237 $line->total_tva = (float) $tabprice[1];
2238 $line->vat_src_code = $obj->vat_src_code;
2239 $line->tva_tx = $obj->tva_tx;
2240 $line->localtax1_tx = $obj->localtax1_tx;
2241 $line->localtax2_tx = $obj->localtax2_tx;
2242 $line->info_bits = $obj->info_bits;
2243 $line->price = $obj->price;
2244 $line->subprice = $obj->subprice;
2245 $line->fk_remise_except = $obj->fk_remise_except;
2246 $line->remise_percent = $obj->remise_percent;
2247
2248 $this->total_ht += $tabprice[0];
2249 $this->total_tva += $tabprice[1];
2250 $this->total_ttc += $tabprice[2];
2251 $this->total_localtax1 += $tabprice[9];
2252 $this->total_localtax2 += $tabprice[10];
2253
2254 $line->date_start = $this->db->jdate($obj->date_start);
2255 $line->date_end = $this->db->jdate($obj->date_end);
2256
2257 $line->special_code = $obj->special_code;
2258
2259 // Multicurrency
2260 $this->fk_multicurrency = $obj->fk_multicurrency;
2261 $this->multicurrency_code = $obj->multicurrency_code;
2262 $line->multicurrency_subprice = $obj->multicurrency_subprice;
2263 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
2264 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
2265 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
2266
2267 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
2268 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
2269 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
2270
2271 if ($originline != $obj->fk_elementdet) {
2272 $line->detail_batch = array();
2273 }
2274
2275 // Detail of batch
2276 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
2277 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
2278
2279 if (is_array($newdetailbatch)) {
2280 if ($originline != $obj->fk_elementdet) {
2281 $line->detail_batch = $newdetailbatch;
2282 } else {
2283 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
2284 }
2285 }
2286 }
2287
2288 // virtual product : find all children stock (group by product id and warehouse id)
2289 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
2290 $detail_children = array(); // detail by product : array of [warehouse_id => total_qty]
2291 $line_child_list = array();
2292 $res = $line->findAllChild($line->id, $line_child_list, 1);
2293 if ($res > 0) {
2294 foreach ($line_child_list as $child_line) {
2295 foreach ($child_line as $child_obj) {
2296 $child_product_id = (int) $child_obj->fk_product;
2297 $child_warehouse_id = (int) $child_obj->fk_warehouse;
2298
2299 if ($child_warehouse_id > 0) {
2300 // child quantities group by warehouses
2301 if (!isset($detail_children[$child_product_id])) {
2302 $detail_children[$child_product_id] = array();
2303 }
2304 if (!isset($detail_children[$child_product_id][$child_warehouse_id])) {
2305 $detail_children[$child_product_id][$child_warehouse_id] = 0;
2306 }
2307 $detail_children[$child_product_id][$child_warehouse_id] += $child_obj->qty;
2308 }
2309 }
2310 }
2311 }
2312 $line->detail_children = $detail_children;
2313 }
2314
2315 $line->fetch_optionals();
2316
2317 if ($originline != $obj->fk_elementdet) {
2318 $this->lines[$lineindex] = $line;
2319 $lineindex++;
2320 } else {
2321 $line->total_ht += $tabprice[0];
2322 $line->total_localtax1 += $tabprice[9];
2323 $line->total_localtax2 += $tabprice[10];
2324 $line->total_ttc += $tabprice[2];
2325 $line->total_tva += $tabprice[1];
2326 }
2327
2328 $i++;
2329 $originline = $obj->fk_elementdet;
2330 }
2331 $this->db->free($resql);
2332 return 1;
2333 } else {
2334 $this->error = $this->db->error();
2335 return -3;
2336 }
2337 }
2338
2339
2340 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2346 public function fetch_lines_free()
2347 {
2348 // phpcs:enable
2349 global $mysoc;
2350
2351 $this->lines = array();
2352
2353 $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_product, ed.fk_unit, ed.description, ed.fk_elementdet, ed.fk_element, ed.element_type, ed.qty, ed.rang';
2354 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as ed';
2355 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (p.rowid = ed.fk_product)';
2356 $sql .= ' WHERE ed.fk_expedition = '.((int) $this->id);
2357 $sql .= ' ORDER BY ed.rang, ed.rowid';
2358
2359 dol_syslog(get_class($this)."::fetch_lines_free", LOG_DEBUG);
2360 $result = $this->db->query($sql);
2361 if ($result) {
2362 $num = $this->db->num_rows($result);
2363
2364 $i = 0;
2365 while ($i < $num) {
2366 $objp = $this->db->fetch_object($result);
2367
2368 $line = new ExpeditionLigne($this->db);
2369
2370 $line->rowid = $objp->rowid;
2371 $line->id = $objp->rowid;
2372 $line->fk_expedition = $this->id;
2373 $line->description = $objp->description;
2374 $line->qty = $objp->qty;
2375 $line->fk_entrepot = $objp->fk_entrepot;
2376 $line->fk_product = $objp->fk_product;
2377 $line->rang = $objp->rang;
2378 $line->fk_element = $objp->fk_element;
2379 $line->fk_unit = $objp->fk_unit;
2380 $line->fk_elementdet = $objp->fk_elementdet;
2381 $line->fk_element_type = $objp->element_type;
2382 $line->fetch_optionals();
2383
2384 $this->lines[$i] = $line;
2385
2386 $i++;
2387 }
2388
2389 $this->db->free($result);
2390
2391 return 1;
2392 } else {
2393 $this->error = $this->db->error();
2394 return -3;
2395 }
2396 }
2397
2403 public function getLinesArray()
2404 {
2405 return $this->fetch_lines_free();
2406 }
2407
2415 public function deleteLine($user, $lineid)
2416 {
2417 global $user;
2418
2419 if ($this->status == self::STATUS_DRAFT) {
2420 $this->db->begin();
2421
2422 $line = new ExpeditionLigne($this->db);
2423
2424 // For triggers
2425 $line->fetch($lineid);
2426
2427 if ($line->delete($user) > 0) {
2428 //$this->update_price(1);
2429
2430 $this->db->commit();
2431 return 1;
2432 } else {
2433 $this->db->rollback();
2434 return -1;
2435 }
2436 } else {
2437 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
2438 return -2;
2439 }
2440 }
2441
2442
2449 public function getTooltipContentArray($params)
2450 {
2451 global $conf, $langs;
2452
2453 $langs->load('sendings');
2454
2455 $nofetch = !empty($params['nofetch']);
2456
2457 $datas = array();
2458 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
2459 if (isset($this->status)) {
2460 $datas['picto'] .= ' '.$this->getLibStatut(5);
2461 }
2462 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
2463 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
2464 if (!$nofetch) {
2465 $langs->load('companies');
2466 if (empty($this->thirdparty)) {
2467 $this->fetch_thirdparty();
2468 }
2469 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
2470 }
2471
2472 return $datas;
2473 }
2474
2486 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
2487 {
2488 global $langs, $hookmanager;
2489
2490 $result = '';
2491 $params = [
2492 'id' => $this->id,
2493 'objecttype' => $this->element,
2494 'option' => $option,
2495 'nofetch' => 1,
2496 ];
2497 $classfortooltip = 'classfortooltip';
2498 $dataparams = '';
2499 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2500 $classfortooltip = 'classforajaxtooltip';
2501 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2502 $label = '';
2503 } else {
2504 $label = implode($this->getTooltipContentArray($params));
2505 }
2506
2507 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
2508
2509 if ($short) {
2510 return $url;
2511 }
2512
2513 if ($option !== 'nolink') {
2514 // Add param to save lastsearch_values or not
2515 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2516 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2517 $add_save_lastsearch_values = 1;
2518 }
2519 if ($add_save_lastsearch_values) {
2520 $url .= '&save_lastsearch_values=1';
2521 }
2522 }
2523
2524 $linkclose = '';
2525 if (empty($notooltip)) {
2526 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2527 $label = $langs->trans("Shipment");
2528 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2529 }
2530 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2531 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2532 }
2533
2534 $linkstart = '<a href="'.$url.'"';
2535 $linkstart .= $linkclose.'>';
2536 $linkend = '</a>';
2537
2538 $result .= $linkstart;
2539 if ($withpicto) {
2540 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2541 }
2542 if ($withpicto != 2) {
2543 $result .= $this->ref;
2544 }
2545 $result .= $linkend;
2546 global $action;
2547 $hookmanager->initHooks(array($this->element . 'dao'));
2548 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2549 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2550 if ($reshook > 0) {
2551 $result = $hookmanager->resPrint;
2552 } else {
2553 $result .= $hookmanager->resPrint;
2554 }
2555 return $result;
2556 }
2557
2564 public function getLibStatut($mode = 0)
2565 {
2566 return $this->LibStatut($this->status, $mode);
2567 }
2568
2569 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2577 public function LibStatut($status, $mode)
2578 {
2579 // phpcs:enable
2580 global $langs;
2581
2582 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
2583 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
2584
2585 $statusType = 'status'.$status;
2586 if ($status == self::STATUS_VALIDATED) {
2587 $statusType = 'status4';
2588 }
2589 if ($status == self::STATUS_CLOSED) {
2590 $statusType = 'status6';
2591 }
2592 if ($status == self::STATUS_CANCELED) {
2593 $statusType = 'status9';
2594 }
2595
2596 $signed_label = ' (' . $this->getLibSignedStatus() . ')';
2597 $status_label = $this->signed_status ? $labelStatus . $signed_label : $labelStatus;
2598 $status_label_short = $this->signed_status ? $labelStatusShort . $signed_label : $labelStatusShort;
2599
2600 return dolGetStatus($status_label, $status_label_short, '', $statusType, $mode);
2601 }
2602
2610 public function getKanbanView($option = '', $arraydata = null)
2611 {
2612 global $langs, $conf;
2613
2614 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2615
2616 $return = '<div class="box-flex-item box-flex-grow-zero">';
2617 $return .= '<div class="info-box info-box-sm">';
2618 $return .= '<div class="info-box-icon bg-infobox-action">';
2619 $return .= img_picto('', 'order');
2620 $return .= '</div>';
2621 $return .= '<div class="info-box-content">';
2622 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
2623 if ($selected >= 0) {
2624 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2625 }
2626 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
2627 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, getDolCurrency()).' '.$langs->trans('HT').'</div>';
2628 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2629 $return .= '</div>';
2630 $return .= '</div>';
2631 $return .= '</div>';
2632
2633 return $return;
2634 }
2635
2643 public function initAsSpecimen()
2644 {
2645 global $langs;
2646
2647 $now = dol_now();
2648
2649 dol_syslog(get_class($this)."::initAsSpecimen");
2650
2651 $order = new Commande($this->db);
2652 $order->initAsSpecimen();
2653
2654 // Initialise parameters
2655 $this->id = 0;
2656 $this->ref = 'SPECIMEN';
2657 $this->specimen = 1;
2659 $this->livraison_id = 0;
2660 $this->date = $now;
2661 $this->date_creation = $now;
2662 $this->date_valid = $now;
2663 $this->date_delivery = $now + 24 * 3600;
2664 $this->date_expedition = $now + 24 * 3600;
2665
2666 $this->entrepot_id = 0;
2667 $this->fk_delivery_address = 0;
2668 $this->socid = 1;
2669
2670 $this->commande_id = 0;
2671 $this->commande = $order;
2672
2673 $this->origin_id = 1;
2674 $this->origin_type = 'commande';
2675
2676 $this->note_private = 'Private note';
2677 $this->note_public = 'Public note';
2678
2679 $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)
2680 $xnbp = 0;
2681 while ($xnbp < $nbp) {
2682 $line = new ExpeditionLigne($this->db);
2683 $line->product_desc = $langs->trans("Description")." ".$xnbp;
2684 $line->product_label = $langs->trans("Description")." ".$xnbp;
2685 $line->qty = 10;
2686 $line->qty_asked = 5;
2687 $line->qty_shipped = 4;
2688 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2689
2690 $line->weight = 1.123456;
2691 $line->weight_units = 0; // kg
2692
2693 $line->volume = 2.34567;
2694 $line->volume_unit = 0;
2695
2696 $this->lines[] = $line;
2697 $xnbp++;
2698 }
2699
2700 return 1;
2701 }
2702
2703 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2712 public function set_date_livraison($user, $delivery_date)
2713 {
2714 // phpcs:enable
2715 return $this->setDeliveryDate($user, $delivery_date);
2716 }
2717
2725 public function setDeliveryDate($user, $delivery_date)
2726 {
2727 if ($user->hasRight('expedition', 'creer')) {
2728 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2729 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2730 $sql .= " WHERE rowid = ".((int) $this->id);
2731
2732 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2733 $resql = $this->db->query($sql);
2734 if ($resql) {
2735 $this->date_delivery = $delivery_date;
2736 return 1;
2737 } else {
2738 $this->error = $this->db->error();
2739 return -1;
2740 }
2741 } else {
2742 return -2;
2743 }
2744 }
2745
2753 public function setShippingDate($user, $shipping_date)
2754 {
2755 if ($user->hasRight('expedition', 'creer')) {
2756 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2757 $sql .= " SET date_expedition = ".($shipping_date ? "'".$this->db->idate($shipping_date)."'" : 'null');
2758 $sql .= " WHERE rowid = ".((int) $this->id);
2759
2760 dol_syslog(get_class($this)."::setShippingDate", LOG_DEBUG);
2761 $resql = $this->db->query($sql);
2762 if ($resql) {
2763 $this->date_shipping = $shipping_date;
2764 return 1;
2765 } else {
2766 $this->error = $this->db->error();
2767 return -1;
2768 }
2769 } else {
2770 return -2;
2771 }
2772 }
2773
2774 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2780 public function fetch_delivery_methods()
2781 {
2782 // phpcs:enable
2783 global $langs;
2784 $this->meths = [];
2785
2786 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2787 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2788 $sql .= " WHERE em.active = 1";
2789 $sql .= " ORDER BY em.libelle ASC";
2790
2791 $resql = $this->db->query($sql);
2792 if ($resql) {
2793 while ($obj = $this->db->fetch_object($resql)) {
2794 $label = $langs->trans('SendingMethod'.$obj->code);
2795 $this->meths[(int) $obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2796 }
2797 }
2798 }
2799
2800 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2807 public function list_delivery_methods($id = 0)
2808 {
2809 // phpcs:enable
2810 global $langs;
2811
2812 $this->listmeths = [];
2813 $i = 0;
2814
2815 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2816 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2817 if (!empty($id)) {
2818 $sql .= " WHERE em.rowid=".((int) $id);
2819 }
2820
2821 $resql = $this->db->query($sql);
2822 if ($resql) {
2823 while ($obj = $this->db->fetch_object($resql)) {
2824 $label = $langs->trans('SendingMethod'.$obj->code);
2825 $this->listmeths[$i] = [
2826 'rowid' => (int) $obj->rowid,
2827 'code' => $obj->code,
2828 'libelle' => ($label != 'SendingMethod'.$obj->code ? $label : $obj->label),
2829 'description' => $obj->description,
2830 'tracking' => $obj->tracking,
2831 'active' => (int) $obj->active,
2832 ];
2833 $i++;
2834 }
2835 }
2836 }
2837
2844 public function getUrlTrackingStatus($value = '')
2845 {
2846 $tracking = '';
2847 if (!empty($this->shipping_method_id)) {
2848 $sql = "SELECT em.code, em.tracking";
2849 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2850 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2851
2852 $resql = $this->db->query($sql);
2853 if ($resql) {
2854 if ($obj = $this->db->fetch_object($resql)) {
2855 $tracking = (string) $obj->tracking;
2856 }
2857 }
2858 }
2859
2860 if (!empty($tracking) && !empty($value)) {
2861 $url = str_replace('{TRACKID}', $value, $tracking);
2862 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, $value);
2863 } else {
2864 $this->tracking_url = $value;
2865 }
2866 }
2867
2873 public function setClosed()
2874 {
2875 global $user;
2876
2877 $error = 0;
2878
2879 // Protection. This avoid to move stock later when we should not
2880 if ($this->status == self::STATUS_CLOSED) {
2881 return 0;
2882 }
2883
2884 $this->db->begin();
2885
2886 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED;
2887 if (empty($this->date_shipping)) { // Date of real shipment was not yet set, we force it on closing
2888 $sql .= ", date_expedition = '".$this->db->escape($this->db->idate(dol_now()))."'";
2889 }
2890 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2891
2892 $resql = $this->db->query($sql);
2893 if ($resql) {
2894 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2895 if ($this->origin_type == 'commande' && $this->origin_id > 0) {
2896 $order = new Commande($this->db);
2897 $order->fetch($this->origin_id);
2898
2899 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2900
2901 $shipments_match_order = 1;
2902 foreach ($order->lines as $line) {
2903 $lineid = $line->id;
2904 $qty = $line->qty;
2905 if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2906 $shipments_match_order = 0;
2907 $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';
2908 dol_syslog($text);
2909 break;
2910 }
2911 }
2912 if ($shipments_match_order) {
2913 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');
2914 // We close the order
2915 $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2916 }
2917 }
2918
2919 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2920 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2921
2922 // If stock increment is done on closing
2923 if (isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2924 $result = $this->manageStockMvtOnEvt($user);
2925
2926 if ($result < 0) {
2927 $error++;
2928 }
2929 }
2930
2931 // Call trigger
2932 if (!$error) {
2933 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2934 if ($result < 0) {
2935 $error++;
2936 }
2937 }
2938 } else {
2939 dol_print_error($this->db);
2940 $error++;
2941 }
2942
2943 if (!$error) {
2944 $this->db->commit();
2945 return 1;
2946 } else {
2947 $this->statut = self::STATUS_VALIDATED;
2949
2950 $this->db->rollback();
2951 return -1;
2952 }
2953 }
2954
2963 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2964 {
2965 global $langs;
2966
2967 $error = 0;
2968
2969 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2970
2971 $langs->load("agenda");
2972
2973 // Loop on each product line to add a stock movement
2974 $sql = "SELECT";
2975 $sql .= " ed.rowid as edid, ed.fk_product, ed.qty, ed.fk_entrepot";
2976 $sql .= ", cd.rowid as cdid";
2977 $sql .= ", cd.subprice";
2978 $sql .= ", edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2979 $sql .= ", e.ref";
2980 $sql .= " FROM " . $this->db->prefix() . "expeditiondet as ed";
2981 $sql .= " LEFT JOIN " . $this->db->prefix() . "commandedet as cd ON cd.rowid = ed.fk_elementdet";
2982 $sql .= " LEFT JOIN " . $this->db->prefix() . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2983 $sql .= " INNER JOIN " . $this->db->prefix() . "expedition as e ON ed.fk_expedition = e.rowid";
2984 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2985 //$sql .= " AND cd.rowid = ed.fk_elementdet";
2986
2987 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2988 $resql = $this->db->query($sql);
2989 if ($resql) {
2990 $cpt = $this->db->num_rows($resql);
2991 for ($i = 0; $i < $cpt; $i++) {
2992 $obj = $this->db->fetch_object($resql);
2993 if (empty($obj->edbrowid)) {
2994 $qty = $obj->qty;
2995 } else {
2996 $qty = $obj->edbqty;
2997 }
2998 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2999 continue;
3000 }
3001 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->edid . " edb.rowid=" . $obj->edbrowid);
3002
3003 $mouvS = new MouvementStock($this->db);
3004 $mouvS->origin = &$this;
3005 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
3006
3007 if (empty($obj->edbrowid)) {
3008 // line without batch detail
3009
3010 // 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
3011 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
3012 if ($result < 0) {
3013 $this->setErrorsFromObject($mouvS);
3014 $error++;
3015 break;
3016 }
3017 } else {
3018 // line with batch detail
3019
3020 // 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
3021 $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);
3022 if ($result < 0) {
3023 $this->setErrorsFromObject($mouvS);
3024 $error++;
3025 break;
3026 }
3027 }
3028
3029 // 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
3030 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
3031 $sqldelete = "DELETE FROM ".$this->db->prefix()."product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM ".$this->db->prefix()."product_batch as pb)";
3032 $resqldelete = $this->db->query($sqldelete);
3033 // We do not test error, it can fails if there is child in batch details
3034 }
3035 } else {
3036 $this->error = $this->db->lasterror();
3037 $this->errors[] = $this->db->lasterror();
3038 $error++;
3039 }
3040
3041 if (!$error) {
3042 return 1;
3043 } else {
3044 return -1;
3045 }
3046 }
3047
3053 public function setBilled()
3054 {
3055 global $user;
3056 $error = 0;
3057
3058 $this->db->begin();
3059
3060 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET billed = 1';
3061 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
3062
3063 $resql = $this->db->query($sql);
3064 if ($resql) {
3065 $this->billed = 1;
3066
3067 // Call trigger
3068 $result = $this->call_trigger('SHIPPING_BILLED', $user);
3069 if ($result < 0) {
3070 $this->billed = 0;
3071 $error++;
3072 }
3073 } else {
3074 $error++;
3075 $this->errors[] = $this->db->lasterror;
3076 }
3077
3078 if (empty($error)) {
3079 $this->db->commit();
3080 return 1;
3081 } else {
3082 $this->db->rollback();
3083 return -1;
3084 }
3085 }
3086
3094 public function setDraft($user, $notrigger = 0)
3095 {
3096 // Protection
3097 if ($this->status <= self::STATUS_DRAFT) {
3098 return 0;
3099 }
3100
3101 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
3102 }
3103
3109 public function reOpen()
3110 {
3111 global $langs, $user;
3112
3113 $error = 0;
3114
3115 // Protection. This avoid to move stock later when we should not
3116 if ($this->status == self::STATUS_VALIDATED) {
3117 return 0;
3118 }
3119
3120 $this->db->begin();
3121
3122 $oldbilled = $this->billed;
3123
3124 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
3125 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
3126
3127 $resql = $this->db->query($sql);
3128 if ($resql) {
3129 $this->statut = self::STATUS_VALIDATED;
3131 $this->billed = 0;
3132
3133 // If stock increment is done on closing
3134 if (isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
3135 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
3136
3137 $langs->load("agenda");
3138
3139 // Loop on each product line to add a stock movement
3140 // TODO possibilite d'expedier a partir d'une propale ou autre origine
3141 $sql = "SELECT cd.fk_product, cd.subprice,";
3142 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
3143 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
3144 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
3145 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
3146 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
3147 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
3148 $sql .= " AND cd.rowid = ed.fk_elementdet";
3149
3150 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
3151 $resql = $this->db->query($sql);
3152 if ($resql) {
3153 $cpt = $this->db->num_rows($resql);
3154 for ($i = 0; $i < $cpt; $i++) {
3155 $obj = $this->db->fetch_object($resql);
3156 if (empty($obj->edbrowid)) {
3157 $qty = $obj->qty;
3158 } else {
3159 $qty = $obj->edbqty;
3160 }
3161 if ($qty <= 0) {
3162 continue;
3163 }
3164 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
3165
3166 //var_dump($this->lines[$i]);
3167 $mouvS = new MouvementStock($this->db);
3168 $mouvS->origin = &$this;
3169 $mouvS->setOrigin($this->element, $this->id);
3170
3171 if (empty($obj->edbrowid)) {
3172 // line without batch detail
3173
3174 // 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
3175 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
3176 if ($result < 0) {
3177 $this->setErrorsFromObject($mouvS);
3178 $error++;
3179 break;
3180 }
3181 } else {
3182 // line with batch detail
3183
3184 // 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
3185 $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);
3186 if ($result < 0) {
3187 $this->setErrorsFromObject($mouvS);
3188 $error++;
3189 break;
3190 }
3191 }
3192 }
3193 } else {
3194 $this->error = $this->db->lasterror();
3195 $error++;
3196 }
3197 }
3198
3199 if (!$error) {
3200 // Call trigger
3201 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
3202 if ($result < 0) {
3203 $error++;
3204 }
3205 }
3206 } else {
3207 $error++;
3208 $this->errors[] = $this->db->lasterror();
3209 }
3210
3211 if (!$error) {
3212 $this->db->commit();
3213 return 1;
3214 } else {
3215 $this->statut = self::STATUS_CLOSED;
3216 $this->status = self::STATUS_CLOSED;
3217 $this->billed = $oldbilled;
3218 $this->db->rollback();
3219 return -1;
3220 }
3221 }
3222
3234 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3235 {
3236 $outputlangs->load("products");
3237
3238 if (!dol_strlen($modele)) {
3239 $modele = 'rouget';
3240
3241 if (!empty($this->model_pdf)) {
3242 $modele = $this->model_pdf;
3243 } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
3244 $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
3245 }
3246 }
3247
3248 $modelpath = "core/modules/expedition/doc/";
3249
3250 $this->fetch_origin();
3251
3252 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3253 }
3254
3263 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3264 {
3265 $tables = array(
3266 'expedition'
3267 );
3268
3269 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3270 }
3271}
$object ref
Definition info.php:90
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...
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteEcmFiles($mode=0)
Delete related files of object in database.
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='')
Set status of an object.
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
updateRangOfLine($rowid, $rang)
Update position of line (rang)
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).
errorsToString()
Method to output saved errors.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
fetch_origin()
Read linked origin object.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
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)
addline_batch($dbatch, $array_options=[], $origin_line=null)
Add a shipment line with batch record.
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.
create_line($entrepot_id, $origin_line_id, $qty, $rang=0, $array_options=[], $parent_line_id=0, $product_id=0)
Create a expedition line.
fetch_lines()
Load lines.
addline($entrepot_id, $id, $qty, $array_options=[], $fk_product=0, $fk_parent=0)
Add an expedition line.
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...
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
getTooltipContentArray($params)
getTooltipContentArray
addlinefree($qty, $element_type, $fk_product, $fk_unit, $rang, $description, $fk_parent, $array_options=[])
Add a simple expedition line.
setDeliveryDate($user, $delivery_date)
Set the planned delivery date.
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.
updatelinefree($rowid, $qty, $element_type, $fk_product, $fk_unit, $rang, $description, $fk_parent, $notrigger, $array_options=array())
Update a simple expedition line.
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.
fetch_lines_free()
Load lines of simple 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.
getLinesArray()
Create an array of shipment lines.
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.
global $mysoc
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:64
dol_now($mode='gmt')
Return date for now.
setEntity($currentobject)
Set entity id to use when to create an object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getDolCurrency()
Return the main currency ('EUR', 'USD', ...)
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular VAT rate, 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=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
if(getDolGlobalString( 'TAKEPOS_SHOW_CUSTOMER')) print $langs trans('Date')." left Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:464
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:129