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