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