dolibarr 24.0.0-beta
expedition.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003-2008 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
5 * Copyright (C) 2006-2012 Laurent Destailleur <eldy@users.sourceforge.net>
6 * Copyright (C) 2011-2020 Juanjo Menent <jmenent@2byte.es>
7 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
8 * Copyright (C) 2014 Cedric GROSS <c.gross@kreiz-it.fr>
9 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
10 * Copyright (C) 2014-2017 Francis Appels <francis.appels@yahoo.com>
11 * Copyright (C) 2015 Claudio Aschieri <c.aschieri@19.coop>
12 * Copyright (C) 2016-2024 Ferran Marcet <fmarcet@2byte.es>
13 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2018-2025 Frédéric France <frederic.france@free.fr>
15 * Copyright (C) 2020 Lenin Rivas <lenin@leninrivas.com>
16 * Copyright (C) 2024-2026 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 = 0;
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 $this->db = $db;
349
350 $this->ismultientitymanaged = 1;
351 $this->isextrafieldmanaged = 1;
352
353 // List of long language codes for status
354 $this->labelStatus = array();
355 $this->labelStatus[-1] = 'StatusSendingCanceled';
356 $this->labelStatus[0] = 'StatusSendingDraft';
357 $this->labelStatus[1] = 'StatusSendingValidated';
358 $this->labelStatus[2] = 'StatusSendingProcessed';
359
360 // List of short language codes for status
361 $this->labelStatusShort = array();
362 $this->labelStatusShort[-1] = 'StatusSendingCanceledShort';
363 $this->labelStatusShort[0] = 'StatusSendingDraftShort';
364 $this->labelStatusShort[1] = 'StatusSendingValidatedShort';
365 $this->labelStatusShort[2] = 'StatusSendingProcessedShort';
366 }
367
374 public function getNextNumRef($soc)
375 {
376 global $langs, $conf;
377 $langs->load("sendings");
378
379 if (getDolGlobalString('EXPEDITION_ADDON_NUMBER')) {
380 $mybool = false;
381
382 $file = getDolGlobalString('EXPEDITION_ADDON_NUMBER') . ".php";
383 $classname = getDolGlobalString('EXPEDITION_ADDON_NUMBER');
384
385 // Include file with class
386 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
387
388 foreach ($dirmodels as $reldir) {
389 $dir = dol_buildpath($reldir."core/modules/expedition/");
390
391 // Load file with numbering class (if found)
392 $mybool = ((bool) @include_once $dir.$file) || $mybool;
393 }
394
395 if (!$mybool) {
396 dol_print_error(null, "Failed to include file ".$file);
397 return '';
398 }
399
400 $obj = new $classname();
401 '@phan-var-force ModelNumRefExpedition $obj';
403 $numref = $obj->getNextValue($soc, $this);
404
405 if ($numref != "") {
406 return $numref;
407 } else {
408 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
409 return "";
410 }
411 } else {
412 print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
413 return "";
414 }
415 }
416
424 public function create($user, $notrigger = 0)
425 {
426 $now = dol_now();
427
428 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
429 $error = 0;
430
431 // Clean parameters
432 $this->tracking_number = dol_sanitizeFileName((string) $this->tracking_number);
433 $this->tracking_number = preg_replace('/\s+/', '', $this->tracking_number);
434
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 .= ", billed";
476 $sql .= ") VALUES (";
477 $sql .= "'(PROV)'";
478 $sql .= ", ".((int) $this->entity);
479 $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
480 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
481 $sql .= ", '".$this->db->idate($this->date_creation)."'";
482 $sql .= ", ".((int) $user->id);
483 $sql .= ", ".($this->date_shipping > 0 ? "'".$this->db->idate($this->date_shipping)."'" : "null");
484 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
485 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
486 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
487 $sql .= ", ".($this->fk_delivery_address > 0 ? ((int) $this->fk_delivery_address) : "null");
488 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
489 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
490 $sql .= ", ".(is_numeric($this->weight) ? (float) $this->weight : 'NULL');
491 $sql .= ", ".(is_numeric($this->sizeS) ? (float) $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
492 $sql .= ", ".(is_numeric($this->sizeW) ? (float) $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
493 $sql .= ", ".(is_numeric($this->sizeH) ? (float) $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
494 $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
495 $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
496 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
497 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
498 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
499 $sql .= ", ".((int) $this->fk_incoterms);
500 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
501 $sql .= ", ".((int) $this->signed_status);
502 $sql .= ", ".((int) $this->billed);
503 $sql .= ")";
504
505 dol_syslog(get_class($this)."::create", LOG_DEBUG);
506 $resql = $this->db->query($sql);
507 if ($resql) {
508 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
509
510 // update ref
511 $initialref = '(PROV'.$this->id.')';
512 if (!empty($this->ref)) {
513 $initialref = $this->ref;
514 }
515
516 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
517 $sql .= " SET ref = '".$this->db->escape($initialref)."'";
518 $sql .= " WHERE rowid = ".((int) $this->id);
519
520 dol_syslog(get_class($this)."::create", LOG_DEBUG);
521 if ($this->db->query($sql)) {
522 $this->ref = $initialref;
523 // Insert of lines
524 $num = count($this->lines);
525 $kits_list = array();
526 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
527 for ($i = 0; $i < $num; $i++) {
528 $objectsrc = new OrderLine($this->db);
529 $objectsrc->fetch($this->lines[$i]->origin_line_id);
530 if ($this->lines[$i]->product_type == "9" && $objectsrc->special_code == SUBTOTALS_SPECIAL_CODE) {
531 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) {
532 $error++;
533 }
534 continue;
535 }
536 if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
537 // virtual products
538 $line = $this->lines[$i];
539 if ($line->fk_product > 0) {
540 if (!isset($kits_list[$line->fk_product])) {
541 if (!is_object($line->product)) {
542 $line_product = new Product($this->db);
543 $result = $line_product->fetch($line->fk_product, '', '', '', 1, 1, 1);
544 if ($result <= 0) {
545 $error++;
546 }
547 } else {
548 $line_product = $line->product;
549 }
550
551 // get all children of virtual product
552 $line_product->get_sousproduits_arbo();
553 $prods_arbo = $line_product->get_arbo_each_prod($line->qty);
554 if (count($prods_arbo) > 0) {
555 $kits_list[$line->fk_product] = array(
556 'arbo' => $prods_arbo,
557 'total_qty' => $line->qty,
558 );
559 }
560 } else {
561 $kits_list[$line->fk_product]['total_qty'] += $line->qty;
562 }
563 }
564 }
565 }
566 }
567 $kits_id_cached = array();
568 $sub_kits_id_cached = array();
569 for ($i = 0; $i < $num; $i++) {
570 $line = $this->lines[$i];
571 if (empty($line->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
572 $line_id = 0;
573 if (!isset($kits_id_cached[$line->fk_product])) {
574 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
575 $qty = isset($kits_list[$line->fk_product]) ? $kits_list[$line->fk_product]['total_qty'] : $line->qty;
576 $warehouse_id = (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) ? 0 : $line->entrepot_id;
577 $line_id = $this->create_line($warehouse_id, $line->origin_line_id, $qty, $line->rang, $line->array_options, 0, $line->fk_product);
578 if ($line_id <= 0) {
579 $error++;
580 }
581 if (isset($kits_list[$line->fk_product])) {
582 $kits_id_cached[$line->fk_product] = $line_id;
583 }
584 } else { // with batch management
585 if ($this->create_line_batch($line, $line->array_options) <= 0) {
586 $error++;
587 }
588 }
589 } else {
590 $line_id = $kits_id_cached[$line->fk_product];
591 }
592
593 // virtual products
594 if (isset($kits_list[$line->fk_product]) && !getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) {
595 $prods_arbo = $kits_list[$line->fk_product]['arbo'];
596 $total_qty = $kits_list[$line->fk_product]['total_qty'];
597
598 // get all children of virtual product
599 $parent_line_id = $line_id; // parent line created
600 $level_last = 1;
601 $product_child_id = 0;
602 foreach ($prods_arbo as $index => $product_child_arr) {
603 // 'id' => Id product
604 // 'id_parent' => Id parent product
605 // 'ref' => Ref product
606 // 'nb' => Nb of units that compose parent product
607 // 'nb_total' => // Nb of units for all nb of product
608 // 'stock' => Stock
609 // 'stock_alert' => Stock alert
610 // 'label' => Label
611 // 'fullpath' => // Full path label
612 // 'type' =>
613 // 'desiredstock' => Desired stock
614 // 'level' => Level
615 // 'incdec' => Need to be incremented or decremented
616 // 'entity' => Entity
617 $product_child_level = (int) $product_child_arr['level'];
618 $product_child_incdec = !empty($product_child_arr['incdec']);
619
620 // detect new level
621 if ($product_child_level != $level_last) {
622 $parent_line_id = $line_id; // last line id
623 $parent_product_id = $product_child_id; // last line id
624 if (isset($kits_id_cached[$parent_product_id])) {
625 $parent_line_id = $kits_id_cached[$parent_product_id];
626 } else {
627 $kits_id_cached[$parent_product_id] = $parent_line_id;
628 }
629 }
630
631 // determine if it's a kit : check next level
632 $is_kit = false;
633 $next_level = $product_child_level;
634 $next_index = $index + 1;
635 if (isset($prods_arbo[$next_index])) {
636 $next_level = (int) $prods_arbo[$next_index]['level'];
637 }
638 if ($next_level > $product_child_level) {
639 $is_kit = true;
640 }
641
642 // determine quantity of sub-product
643 $product_child_id = (int) $product_child_arr['id'];
644 $product_child_qty = (float) $product_child_arr['nb_total']; // by default
645 $warehouse_id = $line->entrepot_id; // by default
646 if ($is_kit || !$product_child_incdec) {
647 if (!$product_child_incdec) {
648 $product_child_qty = 0;
649 }
650 $warehouse_id = 0; // no warehouse used for a kit or if stock is not managed (empty incdec)
651 }
652
653 // create line for a child of virtual product
654 if (!isset($sub_kits_id_cached[$product_child_id]) || $warehouse_id > 0) {
655 $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);
656 if ($line_id <= 0) {
657 $error++;
658 dol_syslog(__METHOD__ . ' : ' . $this->errorsToString(), LOG_ERR);
659 break;
660 }
661
662 // if kit or not manage stock (empty incdec)
663 if (empty($warehouse_id)) {
664 $sub_kits_id_cached[$product_child_id] = $line_id;
665 }
666 }
667
668 $level_last = $product_child_level;
669 }
670 }
671 }
672 }
673
674 if (!$error && $this->id && $this->origin_id) {
675 $ret = $this->add_object_linked();
676 if (!$ret) {
677 $error++;
678 }
679 }
680
681 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
682 $originforcontact = $this->origin_type;
683 $originidforcontact = $this->origin_id;
684
685 $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";
686 $sqlcontact .= " WHERE element_id = ".((int) $originidforcontact)." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$this->db->escape($originforcontact)."'";
687
688 $resqlcontact = $this->db->query($sqlcontact);
689 if ($resqlcontact) {
690 while ($objcontact = $this->db->fetch_object($resqlcontact)) {
691 $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
692 }
693 } else {
694 dol_print_error($this->db);
695 }
696 }
697
698 // Actions on extra fields
699 if (!$error) {
700 $result = $this->insertExtraFields();
701 if ($result < 0) {
702 $error++;
703 }
704 }
705
706 if (!$error && !$notrigger) {
707 // Call trigger
708 $result = $this->call_trigger('SHIPPING_CREATE', $user);
709 if ($result < 0) {
710 $error++;
711 }
712 // End call triggers
713
714 if (!$error) {
715 $this->db->commit();
716 return $this->id;
717 } else {
718 foreach ($this->errors as $errmsg) {
719 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
720 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
721 }
722 $this->db->rollback();
723 return -1 * $error;
724 }
725 } else {
726 $error++;
727 $this->db->rollback();
728 return -3;
729 }
730 } else {
731 $error++;
732 $this->error = $this->db->lasterror()." - sql=$sql";
733 $this->db->rollback();
734 return -2;
735 }
736 } else {
737 $error++;
738 $this->error = $this->db->error()." - sql=$sql";
739 $this->db->rollback();
740 return -1;
741 }
742 }
743
744 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
757 public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [], $parent_line_id = 0, $product_id = 0)
758 {
759 //phpcs:enable
760 global $user;
761
762 $expeditionline = new ExpeditionLigne($this->db);
763 $expeditionline->fk_expedition = $this->id;
764 $expeditionline->entrepot_id = $entrepot_id;
765 $expeditionline->fk_elementdet = $origin_line_id;
766 $expeditionline->element_type = $this->origin;
767 $expeditionline->fk_parent = $parent_line_id;
768 $expeditionline->fk_product = $product_id;
769 $expeditionline->qty = $qty;
770 $expeditionline->rang = $rang;
771 $expeditionline->array_options = $array_options;
772
773 if (!($expeditionline->fk_product > 0)) {
774 $order_line = new OrderLine($this->db);
775 $order_line->fetch($expeditionline->fk_elementdet);
776 $expeditionline->fk_product = $order_line->fk_product;
777 }
778
779 if (($lineId = $expeditionline->insert($user)) < 0) {
780 $this->errors[] = $expeditionline->error;
781 }
782 return $lineId;
783 }
784
785
786 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
794 public function create_line_batch($line_ext, $array_options = [])
795 {
796 // phpcs:enable
797 $error = 0;
798 $stockLocationQty = array(); // associated array with batch qty in stock location
799
800 $tab = $line_ext->detail_batch;
801 // create stockLocation Qty array
802 foreach ($tab as $detbatch) {
803 if (!empty($detbatch->fk_warehouse)) {
804 if (empty($stockLocationQty[$detbatch->fk_warehouse])) {
805 $stockLocationQty[$detbatch->fk_warehouse] = 0;
806 }
807 $stockLocationQty[$detbatch->fk_warehouse] += $detbatch->qty;
808 }
809 }
810 // create shipment lines
811 foreach ($stockLocationQty as $stockLocation => $qty) {
812 $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
813 if ($line_id < 0) {
814 $error++;
815 } else {
816 // create shipment batch lines for stockLocation
817 foreach ($tab as $detbatch) {
818 if ($detbatch->fk_warehouse == $stockLocation) {
819 if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch
820 $this->errors = $detbatch->errors;
821 $error++;
822 }
823 }
824 }
825 }
826 }
827
828 if (!$error) {
829 return 1;
830 } else {
831 return -1;
832 }
833 }
834
844 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
845 {
846 global $conf;
847
848 // Check parameters
849 if (empty($id) && empty($ref) && empty($ref_ext)) {
850 return -1;
851 }
852
853 $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";
854 $sql .= ", e.date_valid";
855 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
856 $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
857 $sql .= ", e.fk_shipping_method, e.tracking_number";
858 $sql .= ", e.note_private, e.note_public";
859 $sql .= ', e.fk_incoterms, e.location_incoterms';
860 $sql .= ', e.signed_status';
861 $sql .= ', i.libelle as label_incoterms';
862 $sql .= ', s.libelle as shipping_method';
863 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin_type";
864 $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
865 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
866 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
867 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
868 $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
869 if ($id) {
870 $sql .= " AND e.rowid = ".((int) $id);
871 }
872 if ($ref) {
873 $sql .= " AND e.ref='".$this->db->escape($ref)."'";
874 }
875 if ($ref_ext) {
876 $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
877 }
878
879 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
880 $result = $this->db->query($sql);
881 if ($result) {
882 if ($this->db->num_rows($result)) {
883 $obj = $this->db->fetch_object($result);
884
885 $this->id = (int) $obj->rowid;
886 $this->entity = $obj->entity;
887 $this->ref = $obj->ref;
888 $this->socid = $obj->socid;
889 $this->ref_customer = $obj->ref_customer;
890 $this->ref_ext = $obj->ref_ext;
891 $this->status = $obj->fk_statut;
892 $this->statut = $this->status; // Deprecated
893 $this->signed_status = $obj->signed_status;
894 $this->user_author_id = $obj->fk_user_author;
895 $this->fk_user_author = $obj->fk_user_author;
896 $this->user_creation_id = $obj->fk_user_author;
897 $this->date_creation = $this->db->jdate($obj->date_creation);
898 $this->date_valid = $this->db->jdate($obj->date_valid);
899 $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
900 $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
901 $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
902 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
903 $this->fk_delivery_address = $obj->fk_address;
904 $this->model_pdf = $obj->model_pdf;
905 $this->shipping_method_id = $obj->fk_shipping_method;
906 $this->shipping_method = $obj->shipping_method;
907 $this->tracking_number = $obj->tracking_number;
908 $this->origin = ($obj->origin_type ? $obj->origin_type : 'commande'); // For compatibility
909 $this->origin_type = ($obj->origin_type ? $obj->origin_type : 'commande');
910 $this->origin_id = $obj->origin_id;
911 $this->billed = $obj->billed;
912 $this->fk_project = $obj->fk_project;
913 $this->signed_status = $obj->signed_status;
914 $this->trueWeight = $obj->weight;
915 $this->weight_units = $obj->weight_units;
916
917 $this->trueWidth = $obj->width;
918 $this->width_units = $obj->size_units;
919 $this->trueHeight = $obj->height;
920 $this->height_units = $obj->size_units;
921 $this->trueDepth = $obj->size;
922 $this->depth_units = $obj->size_units;
923
924 $this->note_public = $obj->note_public;
925 $this->note_private = $obj->note_private;
926
927 // A denormalized value
928 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
929 $this->size_units = $obj->size_units;
930
931 //Incoterms
932 $this->fk_incoterms = $obj->fk_incoterms;
933 $this->location_incoterms = $obj->location_incoterms;
934 $this->label_incoterms = $obj->label_incoterms;
935
936 $this->db->free($result);
937
938 // Tracking url
939 $this->getUrlTrackingStatus($obj->tracking_number);
940
941 // Thirdparty
942 $result = $this->fetch_thirdparty(); // TODO Remove this
943
944 // Retrieve extrafields
945 $this->fetch_optionals();
946
947 // Fix Get multicurrency param for transmitted
948 if (isModEnabled('multicurrency')) {
949 if (!empty($this->multicurrency_code)) {
950 $this->multicurrency_code = $this->thirdparty->multicurrency_code;
951 }
952 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($this->thirdparty->multicurrency_tx)) {
953 $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
954 }
955 }
956
957 /*
958 * Lines
959 */
960 if (empty($obj->origin_id)) {
961 $result = $this->fetch_lines_free();
962 } else {
963 $result = $this->fetch_lines();
964 }
965 if ($result < 0) {
966 return -3;
967 }
968
969 return 1;
970 } else {
971 dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
972 $this->error = 'Shipment with id '.$id.' not found';
973 return 0;
974 }
975 } else {
976 $this->error = $this->db->error();
977 return -1;
978 }
979 }
980
988 public function valid($user, $notrigger = 0)
989 {
990 global $conf;
991
992 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
993
994 dol_syslog(get_class($this)."::valid");
995
996 // Protection
997 if ($this->status) {
998 dol_syslog(get_class($this)."::valid not in draft status", LOG_WARNING);
999 return 0;
1000 }
1001
1002 if (!isset($this->socid)) {
1003 dol_syslog(get_class($this)."::can't valid socid not set", LOG_WARNING);
1004 return 0;
1005 }
1006
1007 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'creer'))
1008 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'shipping_advance', 'validate')))) {
1009 $this->error = 'Permission denied';
1010 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
1011 return -1;
1012 }
1013
1014 $this->db->begin();
1015
1016 $error = 0;
1017
1018 // Define new ref
1019 $soc = new Societe($this->db);
1020 $soc->fetch($this->socid);
1021
1022 // Class of company linked to order
1023 $result = $soc->setAsCustomer();
1024
1025 // Define new ref
1026 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1027 $numref = $this->getNextNumRef($soc);
1028 } elseif (!empty($this->ref)) {
1029 $numref = (string) $this->ref;
1030 } else {
1031 $numref = "EXP".$this->id;
1032 }
1033 $this->newref = dol_sanitizeFileName($numref);
1034
1035 $now = dol_now();
1036
1037 // Validate
1038 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1039 $sql .= " ref = '".$this->db->escape($numref)."'";
1040 $sql .= ", fk_statut = 1";
1041 $sql .= ", date_valid = '".$this->db->idate($now)."'";
1042 $sql .= ", fk_user_valid = ".((int) $user->id);
1043 $sql .= " WHERE rowid = ".((int) $this->id);
1044
1045 dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
1046 $resql = $this->db->query($sql);
1047 if (!$resql) {
1048 $this->error = $this->db->lasterror();
1049 $error++;
1050 }
1051
1052 // If stock increment is done on sending (recommended choice)
1053 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
1054 $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
1055 if ($result < 0) {
1056 return -2;
1057 }
1058 }
1059
1060 // Change status of order to "shipment in process"
1061 $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
1062 if ($this->origin == 'commande') {
1063 $triggerKey .= 'ORDER_SHIPMENTONPROCESS';
1064 } else {
1065 $triggerKey .= strtoupper($this->origin).'_SHIPMENTONPROCESS';
1066 }
1067
1068 // TODO : load the origin object to trigger the right setStatus according to origin object
1069 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin, $triggerKey);
1070 if (!$ret) {
1071 $error++;
1072 }
1073
1074 if (!$error && !$notrigger) {
1075 // Call trigger
1076 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
1077 if ($result < 0) {
1078 $error++;
1079 }
1080 // End call triggers
1081 }
1082
1083 if (!$error) {
1084 $this->oldref = $this->ref;
1085
1086 // Rename directory if dir was a temporary ref
1087 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1088 // Now we rename also files into index
1089 $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)."'";
1090 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
1091 $resql = $this->db->query($sql);
1092 if (!$resql) {
1093 $error++;
1094 $this->error = $this->db->lasterror();
1095 }
1096 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
1097 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
1098 $resql = $this->db->query($sql);
1099 if (!$resql) {
1100 $error++;
1101 $this->error = $this->db->lasterror();
1102 }
1103
1104 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1105 $oldref = dol_sanitizeFileName($this->ref);
1106 $newref = dol_sanitizeFileName($numref);
1107 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
1108 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
1109 if (!$error && file_exists($dirsource)) {
1110 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
1111
1112 if (@rename($dirsource, $dirdest)) {
1113 dol_syslog("Rename ok");
1114 // Rename docs starting with $oldref with $newref
1115 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
1116 foreach ($listoffiles as $fileentry) {
1117 $dirsource = $fileentry['name'];
1118 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1119 $dirsource = $fileentry['path'].'/'.$dirsource;
1120 $dirdest = $fileentry['path'].'/'.$dirdest;
1121 @rename($dirsource, $dirdest);
1122 }
1123 }
1124 }
1125 }
1126 }
1127
1128 // Set new ref and current status
1129 if (!$error) {
1130 $this->ref = $numref;
1131 $this->statut = self::STATUS_VALIDATED;
1133 }
1134
1135 if (!$error) {
1136 $this->db->commit();
1137 return 1;
1138 } else {
1139 $this->db->rollback();
1140 return -1 * $error;
1141 }
1142 }
1143
1144 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1151 public function line_ajaxorder($rows)
1152 {
1153 if (empty($rows) || !is_array($rows)) {
1154 return;
1155 }
1156
1157 $rowToOrigin = array();
1158 $originToRows = array();
1159
1160 $sql = "SELECT rowid, fk_elementdet";
1161 $sql .= " FROM ".$this->db->prefix()."expeditiondet";
1162 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1163 $sql .= " ORDER BY rang ASC, rowid ASC";
1164
1165 $resql = $this->db->query($sql);
1166 if ($resql) {
1167 while ($obj = $this->db->fetch_object($resql)) {
1168 $rowid = (int) $obj->rowid;
1169 $originLine = (int) $obj->fk_elementdet;
1170 $rowToOrigin[$rowid] = $originLine;
1171
1172 if (!isset($originToRows[$originLine])) {
1173 $originToRows[$originLine] = array();
1174 }
1175 $originToRows[$originLine][] = $rowid;
1176 }
1177 $this->db->free($resql);
1178 } else {
1179 parent::line_ajaxorder($rows);
1180 return;
1181 }
1182
1183 $processedOrigins = array();
1184 $position = 1;
1185
1186 foreach ($rows as $rowid) {
1187 $rowid = (int) $rowid;
1188 if (empty($rowid)) {
1189 continue;
1190 }
1191
1192 $originLine = isset($rowToOrigin[$rowid]) ? $rowToOrigin[$rowid] : 0;
1193 if ($originLine > 0 && !empty($processedOrigins[$originLine])) {
1194 continue;
1195 }
1196
1197 $rowidsToUpdate = array($rowid);
1198 if ($originLine > 0 && !empty($originToRows[$originLine])) {
1199 $rowidsToUpdate = $originToRows[$originLine];
1200 $processedOrigins[$originLine] = 1;
1201 }
1202
1203 foreach ($rowidsToUpdate as $childRowId) {
1204 $this->updateRangOfLine($childRowId, $position);
1205 $position++;
1206 }
1207 }
1208 }
1209 // phpcs:enable
1210
1211 // phpcs:disable
1218 public function create_delivery($user)
1219 {
1220 // phpcs:enable
1221 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
1222 if ($this->status == self::STATUS_VALIDATED || $this->status == self::STATUS_CLOSED) {
1223 // Expedition validated
1224 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
1225 $delivery = new Delivery($this->db);
1226 $result = $delivery->create_from_sending($user, $this->id);
1227 if ($result <= 0) {
1228 $this->setErrorsFromObject($delivery);
1229 }
1230 return $result;
1231 }
1232 }
1233
1234 return 0;
1235 }
1236
1251 public function addline($entrepot_id, $id, $qty, $array_options = [], $fk_product = 0, $fk_parent = 0)
1252 {
1253 global $langs;
1254
1255 $num = count($this->lines);
1256 $line = new ExpeditionLigne($this->db);
1257
1258 $line->entrepot_id = $entrepot_id;
1259 $line->origin_line_id = $id;
1260 $line->fk_elementdet = $id;
1261 $line->element_type = 'order';
1262 $line->fk_parent = $fk_parent;
1263 $line->fk_product = $fk_product;
1264 $line->qty = $qty;
1265
1266 $orderline = new OrderLine($this->db);
1267 $orderline->fetch($id);
1268
1269 // Copy the rang of the order line to the expedition line
1270 $line->rang = $orderline->rang;
1271 $line->product_type = $orderline->product_type;
1272 if (!($line->fk_product > 0)) {
1273 $line->fk_product = $orderline->fk_product;
1274 }
1275
1276 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
1277 $product = new Product($this->db);
1278 $product->fetch($orderline->fk_product);
1279
1280 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) {
1281 $langs->load("errors");
1282 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
1283 return -1;
1284 }
1285
1286 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
1287 $productChildrenNb = 0;
1288 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
1289 $productChildrenNb = $product->hasFatherOrChild(1);
1290 }
1291 if ($productChildrenNb > 0) {
1292 $product_stock = null;
1293 $product->loadStockForVirtualProduct('warehouseopen', $line->qty);
1294 if ($entrepot_id > 0) {
1295 if (isset($product->stock_warehouse[$entrepot_id])) {
1296 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
1297 }
1298 } else {
1299 foreach ($product->stock_warehouse as $componentStockWarehouse) {
1300 if ($product_stock === null) {
1301 $product_stock = $componentStockWarehouse->real;
1302 } else {
1303 $product_stock = min($product_stock, $componentStockWarehouse->real);
1304 }
1305 }
1306 }
1307 if ($product_stock === null) {
1308 $product_stock = 0;
1309 }
1310 } else {
1311 // Check must be done for stock of product into warehouse if $entrepot_id defined
1312 if ($entrepot_id > 0) {
1313 $product->load_stock('warehouseopen');
1314 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
1315 } else {
1316 $product_stock = $product->stock_reel;
1317 }
1318 }
1319
1320 $product_type = $product->type;
1321 if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1322 $isavirtualproduct = ($productChildrenNb > 0);
1323 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
1324 if (
1325 !$isavirtualproduct
1326 || !getDolGlobalInt('PRODUIT_SOUSPRODUITS')
1327 || ($isavirtualproduct && !getDolGlobalInt('STOCK_EXCLUDE_VIRTUAL_PRODUCTS'))
1328 ) {
1329 // If STOCK_EXCLUDE_VIRTUAL_PRODUCTS is set, we do not manage stock for kits/virtual products.
1330 if ($product->stockable_product == Product::ENABLED_STOCK && $product_stock < $qty) {
1331 $langs->load("errors");
1332 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
1333 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
1334
1335 return -3;
1336 }
1337 }
1338 }
1339 }
1340 }
1341
1342 // If product need a batch number, we should not have called this function but addline_batch instead.
1343 // If this happen, we may have a bug in card.php page
1344 if (isModEnabled('productbatch') && !empty($line->fk_product) && !empty($orderline->product_tobatch)) {
1345 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$line->fk_product; //
1346 return -4;
1347 }
1348
1349 // extrafields
1350 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1351 $line->array_options = $array_options;
1352 }
1353
1354 $this->lines[$num] = $line;
1355
1356 return 1;
1357 }
1358
1372 public function addlinefree($qty, $element_type, $fk_product, $fk_unit, $rang, $description, $fk_parent, $array_options = [])
1373 {
1374 global $mysoc, $langs, $user;
1375
1376 if ($this->status == self::STATUS_DRAFT) {
1377 if (empty($rang)) {
1378 $rang = 0;
1379 }
1380
1381 $qty = (float) price2num($qty);
1382
1383 $this->db->begin();
1384
1385 // Rang to use
1386 $ranktouse = $rang;
1387 if ($ranktouse == -1) {
1388 $rangmax = $this->line_max($fk_parent);
1389 $ranktouse = $rangmax + 1;
1390 }
1391
1392 // Insert line
1393 $this->line = new ExpeditionLigne($this->db);
1394 $this->line->fk_expedition = $this->id;
1395 $this->line->element_type = $element_type;
1396 $this->line->fk_product = $fk_product;
1397 $this->line->description = $description;
1398 $this->line->desc = $description;
1399 $this->line->fk_parent = $fk_parent;
1400 $this->line->qty = (float) $qty;
1401 $this->line->fk_unit = $fk_unit;
1402 $this->line->rang = $ranktouse;
1403
1404 if (is_array($array_options) && count($array_options) > 0) {
1405 $this->line->array_options = $array_options;
1406 }
1407
1408 $result = $this->line->insert($user);
1409 if ($result > 0) {
1410 if (!isset($this->context['createfromclone'])) {
1411 if (!empty($fk_parent)) {
1412 $this->line_order(true, 'DESC');
1413 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
1414 $linecount = count($this->lines);
1415 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
1416 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
1417 }
1418 }
1419 $this->lines[] = $this->line;
1420 }
1421
1422 $this->db->commit();
1423 return $this->line->id;
1424 } else {
1425 $this->error = $this->line->error;
1426 dol_syslog(get_class($this)."::addlinefree error=".$this->error, LOG_ERR);
1427 $this->db->rollback();
1428 return -2;
1429 }
1430 } else {
1431 dol_syslog(get_class($this)."::addlinefree status of shipment must be Draft to allow use of ->addlinefree()", LOG_ERR);
1432 return -3;
1433 }
1434 }
1435
1451 public function updatelinefree($rowid, $qty, $element_type, $fk_product, $fk_unit, $rang, $description, $fk_parent, $notrigger, $array_options = array())
1452 {
1453 global $mysoc, $langs, $user;
1454
1455 if ($this->status == self::STATUS_DRAFT) {
1456 $this->db->begin();
1457
1458 if (empty($qty)) {
1459 $qty = 0;
1460 }
1461 if (empty($rang)) {
1462 $rang = 0;
1463 }
1464
1465 $qty = (float) $qty;
1466 $description = trim($description);
1467
1468 // Fetch current line from the database and then clone the object and set it in $oldline property
1469 $line = new ExpeditionLigne($this->db);
1470
1471 $line->fetch($rowid);
1472 $line->fetch_optionals();
1473
1474 if (!empty($line->fk_product)) {
1475 $product = new Product($this->db);
1476 $result = $product->fetch($line->fk_product);
1477 $product_type = $product->type;
1478 }
1479
1480 $staticline = clone $line;
1481
1482 $line->oldline = $staticline;
1483 $this->line = $line;
1484 $this->line->context = $this->context;
1485 $this->line->rang = $rang;
1486 $this->line->fk_expedition = $this->id;
1487 $this->line->element_type = $element_type;
1488 $this->line->fk_product = $fk_product;
1489 $this->line->qty = $qty;
1490 $this->line->fk_unit = $fk_unit;
1491 $this->line->fk_parent = $fk_parent;
1492 $this->line->description = $description;
1493
1494 if (is_array($array_options) && count($array_options) > 0) {
1495 // We replace values in this->line->array_options only for entries defined into $array_options
1496 foreach ($array_options as $key => $value) {
1497 $this->line->array_options[$key] = $array_options[$key];
1498 }
1499 }
1500
1501 $result = $this->line->update($user, $notrigger);
1502 if ($result > 0) {
1503 // Reorder if child line
1504 if (!empty($fk_parent)) {
1505 $this->line_order(true, 'DESC');
1506 }
1507
1508 $this->db->commit();
1509 return $result;
1510 } else {
1511 $this->error = $this->line->error;
1512
1513 $this->db->rollback();
1514 return -1;
1515 }
1516 } else {
1517 $this->error = get_class($this)."::updatelinefree Shipment status makes operation forbidden";
1518 $this->errors = array('ShipmentStatusMakeOperationForbidden');
1519 return -2;
1520 }
1521 }
1522
1523 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1532 public function addline_batch($dbatch, $array_options = [], $origin_line = null)
1533 {
1534 // phpcs:enable
1535 global $langs;
1536
1537 $num = count($this->lines);
1538 $linebatch = null;
1539 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1540 $line = new ExpeditionLigne($this->db);
1541 $tab = array();
1542 foreach ($dbatch['detail'] as $key => $value) {
1543 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1544 // $value['q']=qty to move
1545 // $value['id_batch']=id into llx_product_batch of record to move
1546 //var_dump($value);
1547
1548 $linebatch = new ExpeditionLineBatch($this->db);
1549 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1550 if ($ret < 0) {
1551 $this->setErrorsFromObject($linebatch);
1552 return -1;
1553 }
1554 $linebatch->qty = $value['q'];
1555 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1556 $linebatch->batch = null;
1557 }
1558 $tab[] = $linebatch;
1559
1560 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1561 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1562 $prod_batch = new Productbatch($this->db);
1563 $prod_batch->fetch($value['id_batch']);
1564
1565 if ($prod_batch->qty < $linebatch->qty) {
1566 $langs->load("errors");
1567 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1568 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1569 return -1;
1570 }
1571 }
1572 }
1573 }
1574 if (is_object($linebatch)) {
1575 $line->entrepot_id = $linebatch->fk_warehouse;
1576 }
1577 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1578 $line->fk_elementdet = $dbatch['ix_l'];
1579 $line->qty = $dbatch['qty'];
1580 $line->detail_batch = $tab;
1581 if (!($line->rang > 0)) {
1582 $line->rang = $origin_line->rang;
1583 }
1584 if (!($line->fk_product > 0)) {
1585 $line->fk_product = $origin_line->fk_product;
1586 }
1587
1588 // extrafields
1589 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1590 $line->array_options = $array_options;
1591 }
1592
1593 //var_dump($line);
1594 $this->lines[$num] = $line;
1595 return 1;
1596 }
1597 return 0;
1598 }
1599
1607 public function update($user = null, $notrigger = 0)
1608 {
1609 global $conf;
1610 $error = 0;
1611
1612 // Clean parameters
1613
1614 if (isset($this->ref)) {
1615 $this->ref = trim($this->ref);
1616 }
1617 if (isset($this->entity)) {
1618 $this->entity = (int) $this->entity;
1619 }
1620 if (isset($this->ref_customer)) {
1621 $this->ref_customer = trim($this->ref_customer);
1622 }
1623 if (isset($this->socid)) {
1624 $this->socid = (int) $this->socid;
1625 }
1626 if (isset($this->fk_user_author)) {
1627 $this->fk_user_author = (int) $this->fk_user_author;
1628 }
1629 if (isset($this->fk_user_valid)) {
1630 $this->fk_user_valid = (int) $this->fk_user_valid;
1631 }
1632 if (isset($this->fk_delivery_address)) {
1633 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1634 }
1635 if (isset($this->shipping_method_id)) {
1636 $this->shipping_method_id = (int) $this->shipping_method_id;
1637 }
1638 if (isset($this->tracking_number)) {
1639 $this->tracking_number = trim($this->tracking_number);
1640 }
1641 if (isset($this->statut)) {
1642 $this->statut = (int) $this->statut;
1643 }
1644 if (isset($this->status)) {
1645 $this->status = (int) $this->status;
1646 }
1647 if (isset($this->trueDepth)) {
1648 $this->trueDepth = trim($this->trueDepth);
1649 }
1650 if (isset($this->trueWidth)) {
1651 $this->trueWidth = trim($this->trueWidth);
1652 }
1653 if (isset($this->trueHeight)) {
1654 $this->trueHeight = trim($this->trueHeight);
1655 }
1656 if (isset($this->size_units)) {
1657 $this->size_units = trim($this->size_units);
1658 }
1659 if (isset($this->weight_units)) {
1660 $this->weight_units = (int) $this->weight_units;
1661 }
1662 if (isset($this->trueWeight)) {
1663 $this->weight = trim((string) $this->trueWeight);
1664 }
1665 if (isset($this->note_private)) {
1666 $this->note_private = trim($this->note_private);
1667 }
1668 if (isset($this->note_public)) {
1669 $this->note_public = trim($this->note_public);
1670 }
1671 if (isset($this->model_pdf)) {
1672 $this->model_pdf = trim($this->model_pdf);
1673 }
1674 if (!empty($this->date_expedition)) {
1675 $this->date_shipping = $this->date_expedition;
1676 }
1677
1678 // Check parameters
1679 // Put here code to add control on parameters values
1680
1681 // Update request
1682 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1683 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1684 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1685 $sql .= " ref_customer = ".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1686 $sql .= " fk_soc = ".(isset($this->socid) ? ((int) $this->socid) : "null").",";
1687 $sql .= " date_creation = ".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1688 $sql .= " fk_user_author = ".(isset($this->fk_user_author) ? ((int) $this->fk_user_author) : "null").",";
1689 $sql .= " date_valid = ".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1690 $sql .= " fk_user_valid = ".(isset($this->fk_user_valid) ? ((int) $this->fk_user_valid) : "null").",";
1691 $sql .= " date_expedition = ".(dol_strlen($this->date_shipping) != 0 ? "'".$this->db->idate($this->date_shipping)."'" : 'null').",";
1692 $sql .= " date_delivery = ".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1693 $sql .= " fk_address = ".(isset($this->fk_delivery_address) ? ((int) $this->fk_delivery_address) : "null").",";
1694 $sql .= " fk_shipping_method = ".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? ((int) $this->shipping_method_id) : "null").",";
1695 $sql .= " tracking_number = ".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1696 $sql .= " fk_statut = ".(isset($this->status) ? ((int) $this->status) : "null").",";
1697 $sql .= " fk_projet = ".(isset($this->fk_project) ? ((int) $this->fk_project) : "null").",";
1698 $sql .= " height = ".(($this->trueHeight != '') ? ((float) $this->trueHeight) : "null").",";
1699 $sql .= " width = ".(($this->trueWidth != '') ? ((float) $this->trueWidth) : "null").",";
1700 $sql .= " size_units = ".(isset($this->size_units) ? ((int) $this->size_units) : "null").",";
1701 $sql .= " size = ".(($this->trueDepth != '') ? ((float) $this->trueDepth) : "null").",";
1702 $sql .= " weight_units = ".(isset($this->weight_units) ? ((int) $this->weight_units) : "null").",";
1703 $sql .= " weight = ".(($this->trueWeight != '') ? ((float) $this->trueWeight) : "null").",";
1704 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1705 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1706 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1707 $sql .= " entity = ".((int) $conf->entity);
1708 $sql .= " WHERE rowid = ".((int) $this->id);
1709
1710 $this->db->begin();
1711
1712 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1713 $resql = $this->db->query($sql);
1714 if (!$resql) {
1715 $error++;
1716 $this->errors[] = "Error ".$this->db->lasterror();
1717 }
1718
1719 // Actions on extra fields
1720 if (!$error) {
1721 $result = $this->insertExtraFields();
1722 if ($result < 0) {
1723 $error++;
1724 }
1725 }
1726
1727 if (!$error && !$notrigger) {
1728 // Call trigger
1729 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1730 if ($result < 0) {
1731 $error++;
1732 }
1733 // End call triggers
1734 }
1735
1736 // Commit or rollback
1737 if ($error) {
1738 foreach ($this->errors as $errmsg) {
1739 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1740 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1741 }
1742 $this->db->rollback();
1743 return -1 * $error;
1744 } else {
1745 $this->db->commit();
1746 return 1;
1747 }
1748 }
1749
1750
1759 public function cancel($user, $notrigger = 0, $also_update_stock = false)
1760 {
1761 global $conf, $langs, $user;
1762
1763 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1764
1765 $error = 0;
1766 $this->error = '';
1767
1768 $this->db->begin();
1769
1770 // Add a protection to refuse deleting if shipment has at least one delivery
1771 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1772 if (isset($this->linkedObjectsIds['delivery']) && count($this->linkedObjectsIds['delivery']) > 0) {
1773 $this->error = 'ErrorThereIsSomeDeliveries';
1774 $error++;
1775 }
1776
1777 if (!$error && !$notrigger) {
1778 // Call trigger
1779 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1780 if ($result < 0) {
1781 $error++;
1782 }
1783 // End call triggers
1784 }
1785
1786 // Stock control
1787 $can_update_stock = isModEnabled('stock') &&
1788 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->status > self::STATUS_DRAFT) ||
1789 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->status == self::STATUS_CLOSED && $also_update_stock));
1790 if (!$error) {
1791 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1792
1793 $langs->load("agenda");
1794
1795 // Loop on each product line to add a stock movement (contain sub-products)
1796 $sql = "SELECT ";
1797 $sql .= " ed.fk_product";
1798 $sql .= ", ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1799 $sql .= ", SUM(".$this->db->ifsql("pa.rowid IS NOT NULL", "1", "0").") as iskit";
1800 $sql .= ", ".$this->db->ifsql("pai.incdec IS NULL", "1", "pai.incdec")." as incdec";
1801 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
1802 $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pa ON pa.fk_product_pere = ed.fk_product";
1803 $sql .= " LEFT JOIN ".$this->db->prefix()."expeditiondet as edp ON edp.rowid = ed.fk_parent";
1804 $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";
1805 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1806 $sql .= " GROUP BY ed.fk_product, ed.qty, ed.fk_entrepot, ed.rowid, pai.incdec";
1807 $sql .= $this->db->order("ed.rowid", "DESC");
1808
1809 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1810 $resql = $this->db->query($sql);
1811 if ($resql) {
1812 $cpt = $this->db->num_rows($resql);
1813
1814 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1815
1816 for ($i = 0; $i < $cpt; $i++) {
1817 dol_syslog(get_class($this)."::delete movement index ".$i);
1818 $obj = $this->db->fetch_object($resql);
1819 $line_id = (int) $obj->expeditiondet_id;
1820
1821 if ($can_update_stock && (empty($obj->iskit) || getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) && !empty($obj->incdec)) {
1822 $mouvS = new MouvementStock($this->db);
1823 // we do not log origin because it will be deleted
1824 $mouvS->origin = '';
1825 // get lot/serial
1826 $lotArray = null;
1827 if (isModEnabled('productbatch')) {
1828 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1829 if (!is_array($lotArray)) {
1830 $error++;
1831 $this->errors[] = "Error ".$this->db->lasterror();
1832 }
1833 }
1834
1835 if (empty($lotArray)) {
1836 // no lot/serial
1837 // We increment stock of product (and sub-products)
1838 // We use warehouse selected for each line
1839 $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
1840 if ($result < 0) {
1841 $error++;
1842 $this->errors = array_merge($this->errors, $mouvS->errors);
1843 break;
1844 }
1845 } else {
1846 // We increment stock of batches
1847 // We use warehouse selected for each line
1848 foreach ($lotArray as $lot) {
1849 $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
1850 if ($result < 0) {
1851 $error++;
1852 $this->errors = array_merge($this->errors, $mouvS->errors);
1853 break;
1854 }
1855 }
1856 if ($error) {
1857 break; // break for loop in case of error
1858 }
1859 }
1860 }
1861
1862 if (!$error) {
1863 // delete all children and batches of this shipment line
1864 $shipment_line = new ExpeditionLigne($this->db);
1865 $res = $shipment_line->fetch($line_id);
1866 if ($res > 0) {
1867 $result = $shipment_line->delete($user);
1868 if ($result < 0) {
1869 $error++;
1870 $this->errors[] = "Error ".$shipment_line->errorsToString();
1871 }
1872 } else {
1873 $error++;
1874 $this->errors[] = "Error ".$shipment_line->errorsToString();
1875 }
1876 }
1877
1878 if ($error) {
1879 break;
1880 }
1881 }
1882 } else {
1883 $error++;
1884 $this->errors[] = "Error ".$this->db->lasterror();
1885 }
1886 }
1887
1888 if (!$error) {
1889 // Delete linked object
1890 $res = $this->deleteObjectLinked();
1891 if ($res < 0) {
1892 $error++;
1893 }
1894
1895 // No delete expedition
1896 if (!$error) {
1897 $sql = "SELECT rowid FROM ".$this->db->prefix()."expedition";
1898 $sql .= " WHERE rowid = ".((int) $this->id);
1899
1900 if ($this->db->query($sql)) {
1901 if (!empty($this->origin) && $this->origin_id > 0) {
1902 $this->fetch_origin();
1904 '@phan-var-force Facture|Commande $origin_object';
1906 if ($origin_object->status == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1907 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1908 $origin_object->loadExpeditions();
1909 if (count($origin_object->expeditions) <= 0) {
1911 }
1912 }
1913 }
1914
1915 $this->db->commit();
1916
1917 // We delete PDFs
1918 $ref = dol_sanitizeFileName($this->ref);
1919 if (!empty($conf->expedition->dir_output)) {
1920 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1921 $file = $dir.'/'.$ref.'.pdf';
1922 if (file_exists($file)) {
1923 if (!dol_delete_file($file)) {
1924 return 0;
1925 }
1926 }
1927 if (file_exists($dir)) {
1928 if (!dol_delete_dir_recursive($dir)) {
1929 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1930 return 0;
1931 }
1932 }
1933 }
1934
1935 return 1;
1936 } else {
1937 $this->error = $this->db->lasterror()." - sql=$sql";
1938 $this->db->rollback();
1939 return -3;
1940 }
1941 } else {
1942 $this->db->rollback();
1943 return -2;
1944 }
1945 } else {
1946 $this->db->rollback();
1947 return -1;
1948 }
1949 }
1950
1960 public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1961 {
1962 global $conf, $langs;
1963
1964 if (empty($user)) {
1965 global $user;
1966 }
1967
1968 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1969
1970 $error = 0;
1971 $this->error = '';
1972
1973 $this->db->begin();
1974
1975 // Add a protection to refuse deleting if shipment has at least one delivery
1976 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1977 if (isset($this->linkedObjectsIds['delivery']) && count($this->linkedObjectsIds['delivery']) > 0) {
1978 $this->error = 'ErrorThereIsSomeDeliveries';
1979 $error++;
1980 }
1981
1982 if (!$error && !$notrigger) {
1983 // Call trigger
1984 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1985 if ($result < 0) {
1986 $error++;
1987 }
1988 // End call triggers
1989 }
1990
1991 // Stock control
1992 $can_update_stock = isModEnabled('stock') &&
1993 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->status > self::STATUS_DRAFT) ||
1994 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->status == self::STATUS_CLOSED && $also_update_stock));
1995 if (!$error) {
1996 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1997
1998 $langs->load("agenda");
1999
2000 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
2001 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
2002
2003 // Loop on each product line to add a stock movement (contain sub-products)
2004 $sql = "SELECT ";
2005 $sql .= " ed.fk_product";
2006 $sql .= ", ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
2007 $sql .= ", SUM(".$this->db->ifsql("pa.rowid IS NOT NULL", "1", "0").") as iskit";
2008 $sql .= ", ".$this->db->ifsql("pai.incdec IS NULL", "1", "pai.incdec")." as incdec";
2009 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
2010 $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pa ON pa.fk_product_pere = ed.fk_product";
2011 $sql .= " LEFT JOIN ".$this->db->prefix()."expeditiondet as edp ON edp.rowid = ed.fk_parent";
2012 $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";
2013 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2014 $sql .= " GROUP BY ed.fk_product, ed.qty, ed.fk_entrepot, ed.rowid, pai.incdec";
2015 $sql .= $this->db->order("ed.rowid", "DESC");
2016
2017 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
2018 $resql = $this->db->query($sql);
2019 if ($resql) {
2020 $cpt = $this->db->num_rows($resql);
2021 for ($i = 0; $i < $cpt; $i++) {
2022 dol_syslog(get_class($this)."::delete movement index ".$i);
2023 $obj = $this->db->fetch_object($resql);
2024 $line_id = (int) $obj->expeditiondet_id;
2025
2026 if ($can_update_stock && (empty($obj->iskit) || getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) && !empty($obj->incdec)) {
2027 $mouvS = new MouvementStock($this->db);
2028 // we do not log origin because it will be deleted
2029 $mouvS->origin = '';
2030 // get lot/serial
2031 $lotArray = $shipmentlinebatch->fetchAll($line_id);
2032 if (!is_array($lotArray)) {
2033 $error++;
2034 $this->errors[] = "Error ".$this->db->lasterror();
2035 }
2036 if (empty($lotArray)) {
2037 // no lot/serial
2038 // We increment stock of product (disable for sub-products : already in shipment lines)
2039 // We use warehouse selected for each line
2040 $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
2041 if ($result < 0) {
2042 $error++;
2043 $this->errors = array_merge($this->errors, $mouvS->errors);
2044 break;
2045 }
2046 } else {
2047 // We increment stock of batches
2048 // We use warehouse selected for each line
2049 foreach ($lotArray as $lot) {
2050 $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
2051 if ($result < 0) {
2052 $error++;
2053 $this->errors = array_merge($this->errors, $mouvS->errors);
2054 break;
2055 }
2056 }
2057 if ($error) {
2058 break; // break for loop in case of error
2059 }
2060 }
2061 }
2062
2063 if (!$error) {
2064 // delete all children and batches of this shipment line
2065 $shipment_line = new ExpeditionLigne($this->db);
2066 $res = $shipment_line->fetch($line_id);
2067 if ($res > 0) {
2068 $result = $shipment_line->delete($user);
2069 if ($result < 0) {
2070 $error++;
2071 $this->errors[] = "Error ".$shipment_line->errorsToString();
2072 }
2073 } else {
2074 $error++;
2075 $this->errors[] = "Error ".$shipment_line->errorsToString();
2076 }
2077 }
2078
2079 if ($error) {
2080 break;
2081 }
2082 }
2083 } else {
2084 $error++;
2085 $this->errors[] = "Error ".$this->db->lasterror();
2086 }
2087 }
2088
2089 if (!$error) {
2090 // Delete linked object
2091 $res = $this->deleteObjectLinked();
2092 if ($res < 0) {
2093 $error++;
2094 }
2095
2096 // delete extrafields
2097 $res = $this->deleteExtraFields();
2098 if ($res < 0) {
2099 $error++;
2100 }
2101
2102 if (!$error) {
2103 // Delete linked contacts
2104 $res = $this->delete_linked_contact();
2105 if ($res < 0) {
2106 $error++;
2107 }
2108 }
2109 if (!$error) {
2110 $sql = "DELETE FROM ".$this->db->prefix()."expedition";
2111 $sql .= " WHERE rowid = ".((int) $this->id);
2112
2113 if ($this->db->query($sql)) {
2114 if (!empty($this->origin) && $this->origin_id > 0) {
2115 $this->fetch_origin();
2117 '@phan-var-force Facture|Commande $origin_object';
2118 if ($origin_object->status == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
2119 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
2120 $origin_object->loadExpeditions();
2121 if (count($origin_object->expeditions) <= 0) {
2123 }
2124 }
2125 }
2126
2127 $this->db->commit();
2128
2129 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2130 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2131 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2132
2133 // We delete PDFs
2134 $ref = dol_sanitizeFileName($this->ref);
2135 if (!empty($conf->expedition->dir_output)) {
2136 $dir = $conf->expedition->dir_output . '/sending/' . $ref;
2137 $file = $dir . '/' . $ref . '.pdf';
2138 if (file_exists($file)) {
2139 if (!dol_delete_file($file)) {
2140 return 0;
2141 }
2142 }
2143 if (file_exists($dir)) {
2144 if (!dol_delete_dir_recursive($dir)) {
2145 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
2146 return 0;
2147 }
2148 }
2149 }
2150
2151 return 1;
2152 } else {
2153 $this->error = $this->db->lasterror()." - sql=$sql";
2154 $this->db->rollback();
2155 return -3;
2156 }
2157 } else {
2158 $this->db->rollback();
2159 return -2;
2160 }
2161 } else {
2162 $this->db->rollback();
2163 return -1;
2164 }
2165 }
2166
2167 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2173 public function fetch_lines()
2174 {
2175 // phpcs:enable
2176 global $mysoc;
2177
2178 $this->lines = array();
2179
2180 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
2181 // TODO: See if we can restore a common fetch_lines (one line = one record)
2182
2183 $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";
2184 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
2185 $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
2186 $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";
2187 $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";
2188 $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";
2189 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
2190 $sql .= ", p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units";
2191 $sql .= ", p.surface, p.surface_units, p.volume, p.volume_units, p.tosell as product_tosell, p.tobuy as product_tobuy";
2192 $sql .= ", p.tobatch as product_tobatch, p.stockable_product";
2193 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
2194 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
2195 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2196 $sql .= " AND ed.fk_elementdet = cd.rowid";
2197 $sql .= " ORDER BY CASE WHEN ed.rang IS NULL OR ed.rang = 0 THEN cd.rang ELSE ed.rang END, ed.rowid";
2198
2199 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
2200 $resql = $this->db->query($sql);
2201 if ($resql) {
2202 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2203
2204 $num = $this->db->num_rows($resql);
2205 $i = 0;
2206 $line = new ExpeditionLigne($this->db);
2207 $lineindex = 0;
2208 $originline = 0;
2209
2210 $this->total_ht = 0;
2211 $this->total_tva = 0;
2212 $this->total_ttc = 0;
2213 $this->total_localtax1 = 0;
2214 $this->total_localtax2 = 0;
2215
2216 $this->multicurrency_total_ht = 0;
2217 $this->multicurrency_total_tva = 0;
2218 $this->multicurrency_total_ttc = 0;
2219
2220 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
2221
2222 $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.
2223 while ($i < $num) {
2224 $obj = $this->db->fetch_object($resql);
2225
2226 if ($originline > 0 && $originline == $obj->fk_elementdet) {
2227 // '@phan-var-force ExpeditionLigne $line'; // $line from previous loop
2228 $line->entrepot_id = 0; // entrepod_id in details_entrepot
2229 $line->qty_shipped += $obj->qty_shipped;
2230 } else {
2231 $line = new ExpeditionLigne($this->db); // new group to start
2232 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
2233 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
2234 }
2235
2236 $detail_entrepot = new stdClass();
2237 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
2238 $detail_entrepot->qty_shipped = $obj->qty_shipped;
2239 $detail_entrepot->line_id = $obj->line_id;
2240 $line->details_entrepot[] = $detail_entrepot;
2241
2242 $line->line_id = $obj->line_id; // TODO deprecated
2243 $line->rowid = $obj->line_id; // TODO deprecated
2244 $line->id = $obj->line_id;
2245
2246 $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
2247
2248 $line->fk_element = $obj->fk_element;
2249 $line->origin_id = $obj->fk_element;
2250 $line->fk_elementdet = $obj->fk_elementdet;
2251 $line->origin_line_id = $obj->fk_elementdet;
2252 $line->element_type = $obj->element_type;
2253
2254 $line->fk_expedition = $this->id; // id of parent
2255
2256 $line->stockable_product = $obj->stockable_product;
2257 $line->product_type = $obj->product_type;
2258 $line->fk_product = $obj->fk_product;
2259 $line->fk_product_type = $obj->fk_product_type;
2260 $line->ref = $obj->product_ref; // TODO deprecated
2261 $line->product_ref = $obj->product_ref;
2262 $line->product_label = $obj->product_label;
2263 $line->libelle = $obj->product_label; // TODO deprecated
2264 $line->product_barcode = $obj->product_barcode; // Barcode number product
2265 $line->product_tosell = $obj->product_tosell;
2266 $line->product_tobuy = $obj->product_tobuy;
2267 $line->product_tobatch = $obj->product_tobatch;
2268 $line->fk_fournprice = $obj->fk_fournprice;
2269 $line->label = $obj->custom_label;
2270 $line->description = $obj->description;
2271 $line->qty_asked = $obj->qty_asked;
2272 $line->rang = $obj->rang;
2273 $line->weight = $obj->weight;
2274 $line->weight_units = $obj->weight_units;
2275 $line->length = $obj->length;
2276 $line->length_units = $obj->length_units;
2277 $line->width = $obj->width;
2278 $line->width_units = $obj->width_units;
2279 $line->height = $obj->height;
2280 $line->height_units = $obj->height_units;
2281 $line->surface = $obj->surface;
2282 $line->surface_units = $obj->surface_units;
2283 $line->volume = $obj->volume;
2284 $line->volume_units = $obj->volume_units;
2285 $line->stockable_product = $obj->stockable_product;
2286 $line->fk_unit = $obj->fk_unit;
2287
2288 $line->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2289
2290 $line->pa_ht = $obj->pa_ht;
2291
2292 // Local taxes
2293 $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
2294 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
2295 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
2296
2297 // For invoicing
2298 $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
2299 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
2300 $line->qty = $line->qty_shipped;
2301 $line->total_ht = (float) $tabprice[0];
2302 $line->total_localtax1 = (float) $tabprice[9];
2303 $line->total_localtax2 = (float) $tabprice[10];
2304 $line->total_ttc = (float) $tabprice[2];
2305 $line->total_tva = (float) $tabprice[1];
2306 $line->vat_src_code = $obj->vat_src_code;
2307 $line->tva_tx = $obj->tva_tx;
2308 $line->localtax1_tx = $obj->localtax1_tx;
2309 $line->localtax2_tx = $obj->localtax2_tx;
2310 $line->info_bits = $obj->info_bits;
2311 $line->price = $obj->price;
2312 $line->subprice = $obj->subprice;
2313 $line->fk_remise_except = $obj->fk_remise_except;
2314 $line->remise_percent = $obj->remise_percent;
2315
2316 $this->total_ht += $tabprice[0];
2317 $this->total_tva += $tabprice[1];
2318 $this->total_ttc += $tabprice[2];
2319 $this->total_localtax1 += $tabprice[9];
2320 $this->total_localtax2 += $tabprice[10];
2321
2322 $line->date_start = $this->db->jdate($obj->date_start);
2323 $line->date_end = $this->db->jdate($obj->date_end);
2324
2325 $line->special_code = $obj->special_code;
2326
2327 // Multicurrency
2328 $this->fk_multicurrency = $obj->fk_multicurrency;
2329 $this->multicurrency_code = $obj->multicurrency_code;
2330 $line->multicurrency_subprice = $obj->multicurrency_subprice;
2331 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
2332 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
2333 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
2334
2335 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
2336 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
2337 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
2338
2339 // Detail of batch
2340 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
2341 if ($originline != $obj->fk_elementdet) {
2342 $line->detail_batch = array();
2343 }
2344 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
2345
2346 if (is_array($newdetailbatch)) {
2347 if ($originline != $obj->fk_elementdet) {
2348 $line->detail_batch = $newdetailbatch;
2349 } else {
2350 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
2351 }
2352 }
2353 }
2354
2355 // virtual product : find all children stock (group by product id and warehouse id)
2356 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
2357 $detail_children = array(); // detail by product : array of [warehouse_id => total_qty]
2358 $line_child_list = array();
2359 $res = $line->findAllChild($line->id, $line_child_list, 1);
2360 if ($res > 0) {
2361 foreach ($line_child_list as $child_line) {
2362 foreach ($child_line as $child_obj) {
2363 $child_product_id = (int) $child_obj->fk_product;
2364 $child_warehouse_id = (int) $child_obj->fk_warehouse;
2365
2366 if ($child_warehouse_id > 0) {
2367 // child quantities group by warehouses
2368 if (!isset($detail_children[$child_product_id])) {
2369 $detail_children[$child_product_id] = array();
2370 }
2371 if (!isset($detail_children[$child_product_id][$child_warehouse_id])) {
2372 $detail_children[$child_product_id][$child_warehouse_id] = 0;
2373 }
2374 $detail_children[$child_product_id][$child_warehouse_id] += $child_obj->qty;
2375 }
2376 }
2377 }
2378 }
2379 $line->detail_children = $detail_children;
2380 }
2381
2382 $line->fetch_optionals();
2383
2384 if ($originline != $obj->fk_elementdet) {
2385 $this->lines[$lineindex] = $line;
2386 $lineindex++;
2387 } else {
2388 $line->total_ht += $tabprice[0];
2389 $line->total_localtax1 += $tabprice[9];
2390 $line->total_localtax2 += $tabprice[10];
2391 $line->total_ttc += $tabprice[2];
2392 $line->total_tva += $tabprice[1];
2393 }
2394
2395 $i++;
2396 $originline = $obj->fk_elementdet;
2397 }
2398 $this->db->free($resql);
2399 return 1;
2400 } else {
2401 $this->error = $this->db->error();
2402 return -3;
2403 }
2404 }
2405
2406
2407 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2413 public function fetch_lines_free()
2414 {
2415 // phpcs:enable
2416 global $mysoc;
2417
2418 $this->lines = array();
2419
2420 $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';
2421 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as ed';
2422 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (p.rowid = ed.fk_product)';
2423 $sql .= ' WHERE ed.fk_expedition = '.((int) $this->id);
2424 $sql .= ' ORDER BY ed.rang, ed.rowid';
2425
2426 dol_syslog(get_class($this)."::fetch_lines_free", LOG_DEBUG);
2427 $result = $this->db->query($sql);
2428 if ($result) {
2429 $num = $this->db->num_rows($result);
2430
2431 $i = 0;
2432 while ($i < $num) {
2433 $objp = $this->db->fetch_object($result);
2434
2435 $line = new ExpeditionLigne($this->db);
2436
2437 $line->rowid = $objp->rowid;
2438 $line->id = $objp->rowid;
2439 $line->fk_expedition = $this->id;
2440 $line->description = $objp->description;
2441 $line->qty = $objp->qty;
2442 $line->fk_entrepot = $objp->fk_entrepot;
2443 $line->fk_product = $objp->fk_product;
2444 $line->rang = $objp->rang;
2445 $line->fk_element = $objp->fk_element;
2446 $line->fk_unit = $objp->fk_unit;
2447 $line->fk_elementdet = $objp->fk_elementdet;
2448 $line->fk_element_type = $objp->element_type;
2449 $line->fetch_optionals();
2450
2451 $this->lines[$i] = $line;
2452
2453 $i++;
2454 }
2455
2456 $this->db->free($result);
2457
2458 return 1;
2459 } else {
2460 $this->error = $this->db->error();
2461 return -3;
2462 }
2463 }
2464
2470 public function getLinesArray()
2471 {
2472 return $this->fetch_lines_free();
2473 }
2474
2482 public function deleteLine($user, $lineid)
2483 {
2484 global $user;
2485
2486 if ($this->status == self::STATUS_DRAFT) {
2487 $this->db->begin();
2488
2489 $line = new ExpeditionLigne($this->db);
2490
2491 // For triggers
2492 $line->fetch($lineid);
2493
2494 if ($line->delete($user) > 0) {
2495 //$this->update_price(1);
2496
2497 $this->db->commit();
2498 return 1;
2499 } else {
2500 $this->db->rollback();
2501 return -1;
2502 }
2503 } else {
2504 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
2505 return -2;
2506 }
2507 }
2508
2509
2516 public function getTooltipContentArray($params)
2517 {
2518 global $conf, $langs;
2519
2520 $langs->load('sendings');
2521
2522 $nofetch = !empty($params['nofetch']);
2523
2524 $datas = array();
2525 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
2526 if (isset($this->status)) {
2527 $datas['picto'] .= ' '.$this->getLibStatut(5);
2528 }
2529 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
2530 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
2531 if (!$nofetch) {
2532 $langs->load('companies');
2533 if (empty($this->thirdparty)) {
2534 $this->fetch_thirdparty();
2535 }
2536 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
2537 }
2538
2539 return $datas;
2540 }
2541
2553 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
2554 {
2555 global $langs, $hookmanager;
2556
2557 $result = '';
2558 $params = [
2559 'id' => $this->id,
2560 'objecttype' => $this->element,
2561 'option' => $option,
2562 'nofetch' => 1,
2563 ];
2564 $classfortooltip = 'classfortooltip';
2565 $dataparams = '';
2566 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2567 $classfortooltip = 'classforajaxtooltip';
2568 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2569 $label = '';
2570 } else {
2571 $label = implode($this->getTooltipContentArray($params));
2572 }
2573
2574 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
2575
2576 if ($short) {
2577 return $url;
2578 }
2579
2580 if ($option !== 'nolink') {
2581 // Add param to save lastsearch_values or not
2582 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2583 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2584 $add_save_lastsearch_values = 1;
2585 }
2586 if ($add_save_lastsearch_values) {
2587 $url .= '&save_lastsearch_values=1';
2588 }
2589 }
2590
2591 $linkclose = '';
2592 if (empty($notooltip)) {
2593 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2594 $label = $langs->trans("Shipment");
2595 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2596 }
2597 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2598 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2599 }
2600
2601 $linkstart = '<a href="'.$url.'"';
2602 $linkstart .= $linkclose.'>';
2603 $linkend = '</a>';
2604
2605 $result .= $linkstart;
2606 if ($withpicto) {
2607 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2608 }
2609 if ($withpicto != 2) {
2610 $result .= $this->ref;
2611 }
2612 $result .= $linkend;
2613 global $action;
2614 $hookmanager->initHooks(array($this->element . 'dao'));
2615 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2616 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2617 if ($reshook > 0) {
2618 $result = $hookmanager->resPrint;
2619 } else {
2620 $result .= $hookmanager->resPrint;
2621 }
2622 return $result;
2623 }
2624
2631 public function getLibStatut($mode = 0)
2632 {
2633 return $this->LibStatut($this->status, $mode);
2634 }
2635
2636 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2644 public function LibStatut($status, $mode)
2645 {
2646 // phpcs:enable
2647 global $langs;
2648
2649 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
2650 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
2651
2652 $statusType = 'status'.$status;
2653 if ($status == self::STATUS_VALIDATED) {
2654 $statusType = 'status4';
2655 }
2656 if ($status == self::STATUS_CLOSED) {
2657 $statusType = 'status6';
2658 }
2659 if ($status == self::STATUS_CANCELED) {
2660 $statusType = 'status9';
2661 }
2662
2663 $signed_label = ' (' . $this->getLibSignedStatus() . ')';
2664 $status_label = $this->signed_status ? $labelStatus . $signed_label : $labelStatus;
2665 $status_label_short = $this->signed_status ? $labelStatusShort . $signed_label : $labelStatusShort;
2666
2667 return dolGetStatus($status_label, $status_label_short, '', $statusType, $mode);
2668 }
2669
2677 public function getKanbanView($option = '', $arraydata = null)
2678 {
2679 global $langs, $conf;
2680
2681 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2682
2683 $return = '<div class="box-flex-item box-flex-grow-zero">';
2684 $return .= '<div class="info-box info-box-sm">';
2685 $return .= '<div class="info-box-icon bg-infobox-action">';
2686 $return .= img_picto('', 'order');
2687 $return .= '</div>';
2688 $return .= '<div class="info-box-content">';
2689 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
2690 if ($selected >= 0) {
2691 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2692 }
2693 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
2694 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, getDolCurrency()).' '.$langs->trans('HT').'</div>';
2695 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2696 $return .= '</div>';
2697 $return .= '</div>';
2698 $return .= '</div>';
2699
2700 return $return;
2701 }
2702
2710 public function initAsSpecimen()
2711 {
2712 global $langs;
2713
2714 $now = dol_now();
2715
2716 dol_syslog(get_class($this)."::initAsSpecimen");
2717
2718 $order = new Commande($this->db);
2719 $order->initAsSpecimen();
2720
2721 // Initialise parameters
2722 $this->id = 0;
2723 $this->ref = 'SPECIMEN';
2724 $this->specimen = 1;
2726 $this->livraison_id = 0;
2727 $this->date = $now;
2728 $this->date_creation = $now;
2729 $this->date_valid = $now;
2730 $this->date_delivery = $now + 24 * 3600;
2731 $this->date_expedition = $now + 24 * 3600;
2732
2733 $this->entrepot_id = 0;
2734 $this->fk_delivery_address = 0;
2735 $this->socid = 1;
2736
2737 $this->commande_id = 0;
2738 $this->commande = $order;
2739
2740 $this->origin_id = 1;
2741 $this->origin_type = 'commande';
2742
2743 $this->note_private = 'Private note';
2744 $this->note_public = 'Public note';
2745
2746 $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)
2747 $xnbp = 0;
2748 while ($xnbp < $nbp) {
2749 $line = new ExpeditionLigne($this->db);
2750 $line->product_desc = $langs->trans("Description")." ".$xnbp;
2751 $line->product_label = $langs->trans("Description")." ".$xnbp;
2752 $line->qty = 10;
2753 $line->qty_asked = 5;
2754 $line->qty_shipped = 4;
2755 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2756
2757 $line->weight = 1.123456;
2758 $line->weight_units = 0; // kg
2759
2760 $line->volume = 2.34567;
2761 $line->volume_unit = 0;
2762
2763 $this->lines[] = $line;
2764 $xnbp++;
2765 }
2766
2767 return 1;
2768 }
2769
2770 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2779 public function set_date_livraison($user, $delivery_date)
2780 {
2781 // phpcs:enable
2782 return $this->setDeliveryDate($user, $delivery_date);
2783 }
2784
2792 public function setDeliveryDate($user, $delivery_date)
2793 {
2794 if ($user->hasRight('expedition', 'creer')) {
2795 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2796 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2797 $sql .= " WHERE rowid = ".((int) $this->id);
2798
2799 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2800 $resql = $this->db->query($sql);
2801 if ($resql) {
2802 $this->date_delivery = $delivery_date;
2803 return 1;
2804 } else {
2805 $this->error = $this->db->error();
2806 return -1;
2807 }
2808 } else {
2809 return -2;
2810 }
2811 }
2812
2820 public function setShippingDate($user, $shipping_date)
2821 {
2822 if ($user->hasRight('expedition', 'creer')) {
2823 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2824 $sql .= " SET date_expedition = ".($shipping_date ? "'".$this->db->idate($shipping_date)."'" : 'null');
2825 $sql .= " WHERE rowid = ".((int) $this->id);
2826
2827 dol_syslog(get_class($this)."::setShippingDate", LOG_DEBUG);
2828 $resql = $this->db->query($sql);
2829 if ($resql) {
2830 $this->date_shipping = $shipping_date;
2831 return 1;
2832 } else {
2833 $this->error = $this->db->error();
2834 return -1;
2835 }
2836 } else {
2837 return -2;
2838 }
2839 }
2840
2841 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2847 public function fetch_delivery_methods()
2848 {
2849 // phpcs:enable
2850 global $langs;
2851 $this->meths = [];
2852
2853 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2854 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2855 $sql .= " WHERE em.active = 1";
2856 $sql .= " ORDER BY em.libelle ASC";
2857
2858 $resql = $this->db->query($sql);
2859 if ($resql) {
2860 while ($obj = $this->db->fetch_object($resql)) {
2861 $label = $langs->trans('SendingMethod'.$obj->code);
2862 $this->meths[(int) $obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2863 }
2864 }
2865 }
2866
2867 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2874 public function list_delivery_methods($id = 0)
2875 {
2876 // phpcs:enable
2877 global $langs;
2878
2879 $this->listmeths = [];
2880 $i = 0;
2881
2882 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2883 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2884 if (!empty($id)) {
2885 $sql .= " WHERE em.rowid=".((int) $id);
2886 }
2887
2888 $resql = $this->db->query($sql);
2889 if ($resql) {
2890 while ($obj = $this->db->fetch_object($resql)) {
2891 $label = $langs->trans('SendingMethod'.$obj->code);
2892 $this->listmeths[$i] = [
2893 'rowid' => (int) $obj->rowid,
2894 'code' => $obj->code,
2895 'libelle' => ($label != 'SendingMethod'.$obj->code ? $label : $obj->label),
2896 'description' => $obj->description,
2897 'tracking' => $obj->tracking,
2898 'active' => (int) $obj->active,
2899 ];
2900 $i++;
2901 }
2902 }
2903 }
2904
2911 public function getUrlTrackingStatus($value = '')
2912 {
2913 $tracking = '';
2914 if (!empty($this->shipping_method_id)) {
2915 $sql = "SELECT em.code, em.tracking";
2916 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2917 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2918
2919 $resql = $this->db->query($sql);
2920 if ($resql) {
2921 if ($obj = $this->db->fetch_object($resql)) {
2922 $tracking = (string) $obj->tracking;
2923 }
2924 }
2925 }
2926
2927 if (!empty($tracking) && !empty($value)) {
2928 $url = str_replace('{TRACKID}', $value, $tracking);
2929 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, $value);
2930 } else {
2931 $this->tracking_url = $value;
2932 }
2933 }
2934
2940 public function setClosed()
2941 {
2942 global $user;
2943
2944 $error = 0;
2945
2946 // Protection. This avoid to move stock later when we should not
2947 if ($this->status == self::STATUS_CLOSED) {
2948 return 0;
2949 }
2950
2951 // We need to validate shipping before closing it
2952 if ($this->status == self::STATUS_DRAFT) {
2953 $this->valid($user);
2954 }
2955
2956 $this->db->begin();
2957
2958 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED;
2959 if (empty($this->date_shipping)) { // Date of real shipment was not yet set, we force it on closing
2960 $sql .= ", date_expedition = '".$this->db->escape($this->db->idate(dol_now()))."'";
2961 }
2962 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2963
2964 $resql = $this->db->query($sql);
2965 if ($resql) {
2966 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2967 if ($this->origin_type == 'commande' && $this->origin_id > 0) {
2968 $order = new Commande($this->db);
2969 $order->fetch($this->origin_id);
2970
2971 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2972
2973 $shipments_match_order = 1;
2974 foreach ($order->lines as $line) {
2975 $lineid = $line->id;
2976 $qty = $line->qty;
2977 if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2978 $shipments_match_order = 0;
2979 $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';
2980 dol_syslog($text);
2981 break;
2982 }
2983 }
2984 if ($shipments_match_order) {
2985 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');
2986 // We close the order
2987 $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2988 }
2989 }
2990
2991 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2992 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2993
2994 // If stock increment is done on closing
2995 if (isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2996 $result = $this->manageStockMvtOnEvt($user);
2997
2998 if ($result < 0) {
2999 $error++;
3000 }
3001 }
3002
3003 // Call trigger
3004 if (!$error) {
3005 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
3006 if ($result < 0) {
3007 $error++;
3008 }
3009 }
3010 } else {
3011 dol_print_error($this->db);
3012 $error++;
3013 }
3014
3015 if (!$error) {
3016 $this->db->commit();
3017 return 1;
3018 } else {
3019 $this->statut = self::STATUS_VALIDATED;
3021
3022 $this->db->rollback();
3023 return -1;
3024 }
3025 }
3026
3035 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
3036 {
3037 global $langs;
3038
3039 $error = 0;
3040
3041 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
3042
3043 $langs->load("agenda");
3044
3045 // Loop on each product line to add a stock movement
3046 $sql = "SELECT";
3047 $sql .= " ed.rowid as edid, ed.fk_product, ed.qty, ed.fk_entrepot";
3048 $sql .= ", cd.rowid as cdid";
3049 $sql .= ", cd.subprice";
3050 $sql .= ", edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
3051 $sql .= ", e.ref";
3052 $sql .= " FROM " . $this->db->prefix() . "expeditiondet as ed";
3053 $sql .= " LEFT JOIN " . $this->db->prefix() . "commandedet as cd ON cd.rowid = ed.fk_elementdet";
3054 $sql .= " LEFT JOIN " . $this->db->prefix() . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
3055 $sql .= " INNER JOIN " . $this->db->prefix() . "expedition as e ON ed.fk_expedition = e.rowid";
3056 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
3057 //$sql .= " AND cd.rowid = ed.fk_elementdet";
3058
3059 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
3060 $resql = $this->db->query($sql);
3061 if ($resql) {
3062 $cpt = $this->db->num_rows($resql);
3063 for ($i = 0; $i < $cpt; $i++) {
3064 $obj = $this->db->fetch_object($resql);
3065 if (empty($obj->edbrowid)) {
3066 $qty = $obj->qty;
3067 } else {
3068 $qty = $obj->edbqty;
3069 }
3070 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
3071 continue;
3072 }
3073 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->edid . " edb.rowid=" . $obj->edbrowid);
3074
3075 $mouvS = new MouvementStock($this->db);
3076 $mouvS->origin = &$this;
3077 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
3078
3079 if (empty($obj->edbrowid)) {
3080 // line without batch detail
3081
3082 // 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
3083 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
3084 if ($result < 0) {
3085 $this->setErrorsFromObject($mouvS);
3086 $error++;
3087 break;
3088 }
3089 } else {
3090 // line with batch detail
3091
3092 // 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
3093 $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);
3094 if ($result < 0) {
3095 $this->setErrorsFromObject($mouvS);
3096 $error++;
3097 break;
3098 }
3099 }
3100
3101 // 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
3102 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
3103 $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)";
3104 $resqldelete = $this->db->query($sqldelete);
3105 // We do not test error, it can fails if there is child in batch details
3106 }
3107 } else {
3108 $this->error = $this->db->lasterror();
3109 $this->errors[] = $this->db->lasterror();
3110 $error++;
3111 }
3112
3113 if (!$error) {
3114 return 1;
3115 } else {
3116 return -1;
3117 }
3118 }
3119
3125 public function setBilled()
3126 {
3127 global $user;
3128 $error = 0;
3129
3130 $this->db->begin();
3131
3132 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET billed = 1';
3133 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
3134
3135 $resql = $this->db->query($sql);
3136 if ($resql) {
3137 $this->billed = 1;
3138
3139 // Call trigger
3140 $result = $this->call_trigger('SHIPPING_BILLED', $user);
3141 if ($result < 0) {
3142 $this->billed = 0;
3143 $error++;
3144 }
3145 } else {
3146 $error++;
3147 $this->errors[] = $this->db->lasterror;
3148 }
3149
3150 if (empty($error)) {
3151 $this->db->commit();
3152 return 1;
3153 } else {
3154 $this->db->rollback();
3155 return -1;
3156 }
3157 }
3158
3166 public function setDraft($user, $notrigger = 0)
3167 {
3168 // Protection
3169 if ($this->status <= self::STATUS_DRAFT) {
3170 return 0;
3171 }
3172
3173 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
3174 }
3175
3181 public function reOpen()
3182 {
3183 global $langs, $user;
3184
3185 $error = 0;
3186
3187 // Protection. This avoid to move stock later when we should not
3188 if ($this->status == self::STATUS_VALIDATED) {
3189 return 0;
3190 }
3191
3192 $this->db->begin();
3193
3194 $oldbilled = $this->billed;
3195
3196 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
3197 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
3198
3199 $resql = $this->db->query($sql);
3200 if ($resql) {
3201 $this->statut = self::STATUS_VALIDATED;
3203 $this->billed = 0;
3204
3205 // If stock increment is done on closing
3206 if (isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
3207 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
3208
3209 $langs->load("agenda");
3210
3211 // Loop on each product line to add a stock movement
3212 // TODO possibilite d'expedier a partir d'une propale ou autre origine
3213 $sql = "SELECT cd.fk_product, cd.subprice,";
3214 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
3215 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
3216 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
3217 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
3218 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
3219 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
3220 $sql .= " AND cd.rowid = ed.fk_elementdet";
3221
3222 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
3223 $resql = $this->db->query($sql);
3224 if ($resql) {
3225 $cpt = $this->db->num_rows($resql);
3226 for ($i = 0; $i < $cpt; $i++) {
3227 $obj = $this->db->fetch_object($resql);
3228 if (empty($obj->edbrowid)) {
3229 $qty = $obj->qty;
3230 } else {
3231 $qty = $obj->edbqty;
3232 }
3233 if ($qty <= 0) {
3234 continue;
3235 }
3236 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
3237
3238 //var_dump($this->lines[$i]);
3239 $mouvS = new MouvementStock($this->db);
3240 $mouvS->origin = &$this;
3241 $mouvS->setOrigin($this->element, $this->id);
3242
3243 if (empty($obj->edbrowid)) {
3244 // line without batch detail
3245
3246 // 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
3247 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
3248 if ($result < 0) {
3249 $this->setErrorsFromObject($mouvS);
3250 $error++;
3251 break;
3252 }
3253 } else {
3254 // line with batch detail
3255
3256 // 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
3257 $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);
3258 if ($result < 0) {
3259 $this->setErrorsFromObject($mouvS);
3260 $error++;
3261 break;
3262 }
3263 }
3264 }
3265 } else {
3266 $this->error = $this->db->lasterror();
3267 $error++;
3268 }
3269 }
3270
3271 if (!$error) {
3272 // Call trigger
3273 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
3274 if ($result < 0) {
3275 $error++;
3276 }
3277 }
3278 } else {
3279 $error++;
3280 $this->errors[] = $this->db->lasterror();
3281 }
3282
3283 if (!$error) {
3284 $this->db->commit();
3285 return 1;
3286 } else {
3287 $this->statut = self::STATUS_CLOSED;
3288 $this->status = self::STATUS_CLOSED;
3289 $this->billed = $oldbilled;
3290 $this->db->rollback();
3291 return -1;
3292 }
3293 }
3294
3306 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3307 {
3308 $outputlangs->load("products");
3309
3310 if (!dol_strlen($modele)) {
3311 $modele = 'rouget';
3312
3313 if (!empty($this->model_pdf)) {
3314 $modele = $this->model_pdf;
3315 } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
3316 $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
3317 }
3318 }
3319
3320 $modelpath = "core/modules/expedition/doc/";
3321
3322 $this->fetch_origin();
3323
3324 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3325 }
3326
3335 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3336 {
3337 $tables = array(
3338 'expedition'
3339 );
3340
3341 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3342 }
3343}
$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.
line_ajaxorder($rows)
Override to keep every expeditiondet row that belongs to the displayed line in sync when drag & drop ...
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:168
getLibSignedStatus(int $mode=0)
Returns the label for signed status.
global $mysoc
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
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', ...)
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.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
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
print $langs trans('Date')." left Ref 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 Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:487
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:134