dolibarr 23.0.4
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 $error++;
1060 }
1061 }
1062
1063 // Change status of order to "shipment in process"
1064 if (!$error) {
1065 $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
1066 if ($this->origin == 'commande') {
1067 $triggerKey.= 'ORDER_SHIPMENTONPROCESS';
1068 } else {
1069 $triggerKey.= strtoupper($this->origin).'_SHIPMENTONPROCESS';
1070 }
1071
1072 // TODO : load the origin object to trigger the right setStatus according to origin object
1073 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin, $triggerKey);
1074 if (!$ret) {
1075 $error++;
1076 }
1077 }
1078
1079 if (!$error && !$notrigger) {
1080 // Call trigger
1081 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
1082 if ($result < 0) {
1083 $error++;
1084 }
1085 // End call triggers
1086 }
1087
1088 if (!$error) {
1089 $this->oldref = $this->ref;
1090
1091 // Rename directory if dir was a temporary ref
1092 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1093 // Now we rename also files into index
1094 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
1095 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
1096 $resql = $this->db->query($sql);
1097 if (!$resql) {
1098 $error++;
1099 $this->error = $this->db->lasterror();
1100 }
1101 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
1102 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
1103 $resql = $this->db->query($sql);
1104 if (!$resql) {
1105 $error++;
1106 $this->error = $this->db->lasterror();
1107 }
1108
1109 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1110 $oldref = dol_sanitizeFileName($this->ref);
1111 $newref = dol_sanitizeFileName($numref);
1112 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
1113 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
1114 if (!$error && file_exists($dirsource)) {
1115 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
1116
1117 if (@rename($dirsource, $dirdest)) {
1118 dol_syslog("Rename ok");
1119 // Rename docs starting with $oldref with $newref
1120 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
1121 foreach ($listoffiles as $fileentry) {
1122 $dirsource = $fileentry['name'];
1123 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1124 $dirsource = $fileentry['path'].'/'.$dirsource;
1125 $dirdest = $fileentry['path'].'/'.$dirdest;
1126 @rename($dirsource, $dirdest);
1127 }
1128 }
1129 }
1130 }
1131 }
1132
1133 // Set new ref and current status
1134 if (!$error) {
1135 $this->ref = $numref;
1136 $this->statut = self::STATUS_VALIDATED;
1138 }
1139
1140 if (!$error) {
1141 $this->db->commit();
1142 return 1;
1143 } else {
1144 $this->db->rollback();
1145 return -1 * $error;
1146 }
1147 }
1148
1149
1150 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1157 public function create_delivery($user)
1158 {
1159 // phpcs:enable
1160 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
1161 if ($this->status == self::STATUS_VALIDATED || $this->status == self::STATUS_CLOSED) {
1162 // Expedition validated
1163 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
1164 $delivery = new Delivery($this->db);
1165 $result = $delivery->create_from_sending($user, $this->id);
1166 if ($result <= 0) {
1167 $this->setErrorsFromObject($delivery);
1168 }
1169 return $result;
1170 }
1171 }
1172
1173 return 0;
1174 }
1175
1190 public function addline($entrepot_id, $id, $qty, $array_options = [], $fk_product = 0, $fk_parent = 0)
1191 {
1192 global $langs;
1193
1194 $num = count($this->lines);
1195 $line = new ExpeditionLigne($this->db);
1196
1197 $line->entrepot_id = $entrepot_id;
1198 $line->origin_line_id = $id;
1199 $line->fk_elementdet = $id;
1200 $line->element_type = 'order';
1201 $line->fk_parent = $fk_parent;
1202 $line->fk_product = $fk_product;
1203 $line->qty = $qty;
1204
1205 $orderline = new OrderLine($this->db);
1206 $orderline->fetch($id);
1207
1208 // Copy the rang of the order line to the expedition line
1209 $line->rang = $orderline->rang;
1210 $line->product_type = $orderline->product_type;
1211 if (!($line->fk_product > 0)) {
1212 $line->fk_product = $orderline->fk_product;
1213 }
1214
1215 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
1216 $product = new Product($this->db);
1217 $product->fetch($orderline->fk_product);
1218
1219 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)) {
1220 $langs->load("errors");
1221 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
1222 return -1;
1223 }
1224
1225 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT') && ($qty > 0 || !getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1226 $productChildrenNb = 0;
1227 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
1228 $productChildrenNb = $product->hasFatherOrChild(1);
1229 }
1230 if ($productChildrenNb > 0) {
1231 $product_stock = null;
1232 $product->loadStockForVirtualProduct('warehouseopen', $line->qty);
1233 if ($entrepot_id > 0) {
1234 if (isset($product->stock_warehouse[$entrepot_id])) {
1235 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
1236 }
1237 } else {
1238 foreach ($product->stock_warehouse as $componentStockWarehouse) {
1239 if ($product_stock === null) {
1240 $product_stock = $componentStockWarehouse->real;
1241 } else {
1242 $product_stock = min($product_stock, $componentStockWarehouse->real);
1243 }
1244 }
1245 }
1246 if ($product_stock === null) {
1247 $product_stock = 0;
1248 }
1249 } else {
1250 // Check must be done for stock of product into warehouse if $entrepot_id defined
1251 if ($entrepot_id > 0) {
1252 $product->load_stock('warehouseopen');
1253 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
1254 } else {
1255 $product_stock = $product->stock_reel;
1256 }
1257 }
1258
1259 $product_type = $product->type;
1260 if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1261 $isavirtualproduct = ($productChildrenNb > 0);
1262 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
1263 if (
1264 !$isavirtualproduct
1265 || !getDolGlobalInt('PRODUIT_SOUSPRODUITS')
1266 || ($isavirtualproduct && !getDolGlobalInt('STOCK_EXCLUDE_VIRTUAL_PRODUCTS'))
1267 ) {
1268 // If STOCK_EXCLUDE_VIRTUAL_PRODUCTS is set, we do not manage stock for kits/virtual products.
1269 if ($product->stockable_product == Product::ENABLED_STOCK && $product_stock < $qty) {
1270 $langs->load("errors");
1271 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
1272 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
1273
1274 return -3;
1275 }
1276 }
1277 }
1278 }
1279 }
1280
1281 // If product need a batch number, we should not have called this function but addline_batch instead.
1282 // If this happen, we may have a bug in card.php page
1283 if (isModEnabled('productbatch') && !empty($line->fk_product) && !empty($orderline->product_tobatch)) {
1284 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$line->fk_product; //
1285 return -4;
1286 }
1287
1288 // extrafields
1289 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1290 $line->array_options = $array_options;
1291 }
1292
1293 $this->lines[$num] = $line;
1294
1295 return 1;
1296 }
1297
1311 public function addlinefree($qty, $element_type, $fk_product, $fk_unit, $rang, $description, $fk_parent, $array_options = [])
1312 {
1313 global $mysoc, $langs, $user;
1314
1315 if ($this->status == self::STATUS_DRAFT) {
1316 if (empty($rang)) {
1317 $rang = 0;
1318 }
1319
1320 $qty = (float) price2num($qty);
1321
1322 $this->db->begin();
1323
1324 // Rang to use
1325 $ranktouse = $rang;
1326 if ($ranktouse == -1) {
1327 $rangmax = $this->line_max($fk_parent);
1328 $ranktouse = $rangmax + 1;
1329 }
1330
1331 // Insert line
1332 $this->line = new ExpeditionLigne($this->db);
1333 $this->line->fk_expedition = $this->id;
1334 $this->line->element_type = $element_type;
1335 $this->line->fk_product = $fk_product;
1336 $this->line->description = $description;
1337 $this->line->desc = $description;
1338 $this->line->fk_parent = $fk_parent;
1339 $this->line->qty = (float) $qty;
1340 $this->line->fk_unit = $fk_unit;
1341 $this->line->rang = $ranktouse;
1342
1343 if (is_array($array_options) && count($array_options) > 0) {
1344 $this->line->array_options = $array_options;
1345 }
1346
1347 $result = $this->line->insert($user);
1348 if ($result > 0) {
1349 if (!isset($this->context['createfromclone'])) {
1350 if (!empty($fk_parent)) {
1351 $this->line_order(true, 'DESC');
1352 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
1353 $linecount = count($this->lines);
1354 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
1355 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
1356 }
1357 }
1358 $this->lines[] = $this->line;
1359 }
1360
1361 $this->db->commit();
1362 return $this->line->id;
1363 } else {
1364 $this->error = $this->line->error;
1365 dol_syslog(get_class($this)."::addlinefree error=".$this->error, LOG_ERR);
1366 $this->db->rollback();
1367 return -2;
1368 }
1369 } else {
1370 dol_syslog(get_class($this)."::addlinefree status of shipment must be Draft to allow use of ->addlinefree()", LOG_ERR);
1371 return -3;
1372 }
1373 }
1374
1390 public function updatelinefree($rowid, $qty, $element_type, $fk_product, $fk_unit, $rang, $description, $fk_parent, $notrigger, $array_options = array())
1391 {
1392 global $mysoc, $langs, $user;
1393
1394 if ($this->status == self::STATUS_DRAFT) {
1395 $this->db->begin();
1396
1397 if (empty($qty)) {
1398 $qty = 0;
1399 }
1400 if (empty($rang)) {
1401 $rang = 0;
1402 }
1403
1404 $qty = (float) $qty;
1405 $description = trim($description);
1406
1407 // Fetch current line from the database and then clone the object and set it in $oldline property
1408 $line = new ExpeditionLigne($this->db);
1409
1410 $line->fetch($rowid);
1411 $line->fetch_optionals();
1412
1413 if (!empty($line->fk_product)) {
1414 $product = new Product($this->db);
1415 $result = $product->fetch($line->fk_product);
1416 $product_type = $product->type;
1417 }
1418
1419 $staticline = clone $line;
1420
1421 $line->oldline = $staticline;
1422 $this->line = $line;
1423 $this->line->context = $this->context;
1424 $this->line->rang = $rang;
1425 $this->line->fk_expedition = $this->id;
1426 $this->line->element_type = $element_type;
1427 $this->line->fk_product = $fk_product;
1428 $this->line->qty = $qty;
1429 $this->line->fk_unit = $fk_unit;
1430 $this->line->fk_parent = $fk_parent;
1431 $this->line->description = $description;
1432
1433 if (is_array($array_options) && count($array_options) > 0) {
1434 // We replace values in this->line->array_options only for entries defined into $array_options
1435 foreach ($array_options as $key => $value) {
1436 $this->line->array_options[$key] = $array_options[$key];
1437 }
1438 }
1439
1440 $result = $this->line->update($user, $notrigger);
1441 if ($result > 0) {
1442 // Reorder if child line
1443 if (!empty($fk_parent)) {
1444 $this->line_order(true, 'DESC');
1445 }
1446
1447 $this->db->commit();
1448 return $result;
1449 } else {
1450 $this->error = $this->line->error;
1451
1452 $this->db->rollback();
1453 return -1;
1454 }
1455 } else {
1456 $this->error = get_class($this)."::updatelinefree Shipment status makes operation forbidden";
1457 $this->errors = array('ShipmentStatusMakeOperationForbidden');
1458 return -2;
1459 }
1460 }
1461
1462 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1471 public function addline_batch($dbatch, $array_options = [], $origin_line = null)
1472 {
1473 // phpcs:enable
1474 global $langs;
1475
1476 $num = count($this->lines);
1477 $linebatch = null;
1478 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1479 $line = new ExpeditionLigne($this->db);
1480 $tab = array();
1481 foreach ($dbatch['detail'] as $key => $value) {
1482 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1483 // $value['q']=qty to move
1484 // $value['id_batch']=id into llx_product_batch of record to move
1485 //var_dump($value);
1486
1487 $linebatch = new ExpeditionLineBatch($this->db);
1488 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1489 if ($ret < 0) {
1490 $this->setErrorsFromObject($linebatch);
1491 return -1;
1492 }
1493 $linebatch->qty = $value['q'];
1494 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1495 $linebatch->batch = null;
1496 }
1497 $tab[] = $linebatch;
1498
1499 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0') && ($linebatch->qty > 0 || !getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1500 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1501 $prod_batch = new Productbatch($this->db);
1502 $prod_batch->fetch($value['id_batch']);
1503
1504 if ($prod_batch->qty < $linebatch->qty) {
1505 $langs->load("errors");
1506 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1507 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1508 return -1;
1509 }
1510 }
1511 }
1512 }
1513 if (is_object($linebatch)) {
1514 $line->entrepot_id = $linebatch->fk_warehouse;
1515 }
1516 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1517 $line->fk_elementdet = $dbatch['ix_l'];
1518 $line->qty = $dbatch['qty'];
1519 $line->detail_batch = $tab;
1520 if (!($line->rang > 0)) {
1521 $line->rang = $origin_line->rang;
1522 }
1523 if (!($line->fk_product > 0)) {
1524 $line->fk_product = $origin_line->fk_product;
1525 }
1526
1527 // extrafields
1528 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1529 $line->array_options = $array_options;
1530 }
1531
1532 //var_dump($line);
1533 $this->lines[$num] = $line;
1534 return 1;
1535 }
1536 return 0;
1537 }
1538
1546 public function update($user = null, $notrigger = 0)
1547 {
1548 global $conf;
1549 $error = 0;
1550
1551 // Clean parameters
1552
1553 if (isset($this->ref)) {
1554 $this->ref = trim($this->ref);
1555 }
1556 if (isset($this->entity)) {
1557 $this->entity = (int) $this->entity;
1558 }
1559 if (isset($this->ref_customer)) {
1560 $this->ref_customer = trim($this->ref_customer);
1561 }
1562 if (isset($this->socid)) {
1563 $this->socid = (int) $this->socid;
1564 }
1565 if (isset($this->fk_user_author)) {
1566 $this->fk_user_author = (int) $this->fk_user_author;
1567 }
1568 if (isset($this->fk_user_valid)) {
1569 $this->fk_user_valid = (int) $this->fk_user_valid;
1570 }
1571 if (isset($this->fk_delivery_address)) {
1572 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1573 }
1574 if (isset($this->shipping_method_id)) {
1575 $this->shipping_method_id = (int) $this->shipping_method_id;
1576 }
1577 if (isset($this->tracking_number)) {
1578 $this->tracking_number = trim($this->tracking_number);
1579 }
1580 if (isset($this->statut)) {
1581 $this->statut = (int) $this->statut;
1582 }
1583 if (isset($this->status)) {
1584 $this->status = (int) $this->status;
1585 }
1586 if (isset($this->trueDepth)) {
1587 $this->trueDepth = trim($this->trueDepth);
1588 }
1589 if (isset($this->trueWidth)) {
1590 $this->trueWidth = trim($this->trueWidth);
1591 }
1592 if (isset($this->trueHeight)) {
1593 $this->trueHeight = trim($this->trueHeight);
1594 }
1595 if (isset($this->size_units)) {
1596 $this->size_units = trim($this->size_units);
1597 }
1598 if (isset($this->weight_units)) {
1599 $this->weight_units = (int) $this->weight_units;
1600 }
1601 if (isset($this->trueWeight)) {
1602 $this->weight = trim((string) $this->trueWeight);
1603 }
1604 if (isset($this->note_private)) {
1605 $this->note_private = trim($this->note_private);
1606 }
1607 if (isset($this->note_public)) {
1608 $this->note_public = trim($this->note_public);
1609 }
1610 if (isset($this->model_pdf)) {
1611 $this->model_pdf = trim($this->model_pdf);
1612 }
1613 if (!empty($this->date_expedition)) {
1614 $this->date_shipping = $this->date_expedition;
1615 }
1616
1617 // Check parameters
1618 // Put here code to add control on parameters values
1619
1620 // Update request
1621 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1622 $sql .= " ref = ".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1623 $sql .= " ref_ext = ".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1624 $sql .= " ref_customer = ".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1625 $sql .= " fk_soc = ".(isset($this->socid) ? ((int) $this->socid) : "null").",";
1626 $sql .= " date_creation = ".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1627 $sql .= " fk_user_author = ".(isset($this->fk_user_author) ? ((int) $this->fk_user_author) : "null").",";
1628 $sql .= " date_valid = ".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1629 $sql .= " fk_user_valid = ".(isset($this->fk_user_valid) ? ((int) $this->fk_user_valid) : "null").",";
1630 $sql .= " date_expedition = ".(dol_strlen($this->date_shipping) != 0 ? "'".$this->db->idate($this->date_shipping)."'" : 'null').",";
1631 $sql .= " date_delivery = ".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1632 $sql .= " fk_address = ".(isset($this->fk_delivery_address) ? ((int) $this->fk_delivery_address) : "null").",";
1633 $sql .= " fk_shipping_method = ".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? ((int) $this->shipping_method_id) : "null").",";
1634 $sql .= " tracking_number = ".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1635 $sql .= " fk_statut = ".(isset($this->status) ? ((int) $this->status) : "null").",";
1636 $sql .= " fk_projet = ".(isset($this->fk_project) ? ((int) $this->fk_project) : "null").",";
1637 $sql .= " height = ".(($this->trueHeight != '') ? ((float) $this->trueHeight) : "null").",";
1638 $sql .= " width = ".(($this->trueWidth != '') ? ((float) $this->trueWidth) : "null").",";
1639 $sql .= " size_units = ".(isset($this->size_units) ? ((int) $this->size_units) : "null").",";
1640 $sql .= " size = ".(($this->trueDepth != '') ? ((float) $this->trueDepth) : "null").",";
1641 $sql .= " weight_units = ".(isset($this->weight_units) ? ((int) $this->weight_units) : "null").",";
1642 $sql .= " weight = ".(($this->trueWeight != '') ? ((float) $this->trueWeight) : "null").",";
1643 $sql .= " note_private = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1644 $sql .= " note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1645 $sql .= " model_pdf = ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1646 $sql .= " entity = ".((int) $conf->entity);
1647 $sql .= " WHERE rowid = ".((int) $this->id);
1648
1649 $this->db->begin();
1650
1651 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1652 $resql = $this->db->query($sql);
1653 if (!$resql) {
1654 $error++;
1655 $this->errors[] = "Error ".$this->db->lasterror();
1656 }
1657
1658 // Actions on extra fields
1659 if (!$error) {
1660 $result = $this->insertExtraFields();
1661 if ($result < 0) {
1662 $error++;
1663 }
1664 }
1665
1666 if (!$error && !$notrigger) {
1667 // Call trigger
1668 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1669 if ($result < 0) {
1670 $error++;
1671 }
1672 // End call triggers
1673 }
1674
1675 // Commit or rollback
1676 if ($error) {
1677 foreach ($this->errors as $errmsg) {
1678 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1679 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1680 }
1681 $this->db->rollback();
1682 return -1 * $error;
1683 } else {
1684 $this->db->commit();
1685 return 1;
1686 }
1687 }
1688
1689
1698 public function cancel($user, $notrigger = 0, $also_update_stock = false)
1699 {
1700 global $conf, $langs, $user;
1701
1702 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1703
1704 $error = 0;
1705 $this->error = '';
1706
1707 $this->db->begin();
1708
1709 // Add a protection to refuse deleting if shipment has at least one delivery
1710 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1711 if (isset($this->linkedObjectsIds['delivery']) && count($this->linkedObjectsIds['delivery']) > 0) {
1712 $this->error = 'ErrorThereIsSomeDeliveries';
1713 $error++;
1714 }
1715
1716 if (!$error && !$notrigger) {
1717 // Call trigger
1718 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1719 if ($result < 0) {
1720 $error++;
1721 }
1722 // End call triggers
1723 }
1724
1725 // Stock control
1726 $can_update_stock = isModEnabled('stock') &&
1727 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->status > self::STATUS_DRAFT) ||
1728 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->status == self::STATUS_CLOSED && $also_update_stock));
1729 if (!$error) {
1730 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1731
1732 $langs->load("agenda");
1733
1734 // Loop on each product line to add a stock movement (contain sub-products)
1735 $sql = "SELECT ";
1736 $sql .= " ed.fk_product";
1737 $sql .= ", ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1738 $sql .= ", SUM(".$this->db->ifsql("pa.rowid IS NOT NULL", "1", "0").") as iskit";
1739 $sql .= ", ".$this->db->ifsql("pai.incdec IS NULL", "1", "pai.incdec")." as incdec";
1740 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
1741 $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pa ON pa.fk_product_pere = ed.fk_product";
1742 $sql .= " LEFT JOIN ".$this->db->prefix()."expeditiondet as edp ON edp.rowid = ed.fk_parent";
1743 $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";
1744 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1745 $sql .= " GROUP BY ed.fk_product, ed.qty, ed.fk_entrepot, ed.rowid, pai.incdec";
1746 $sql .= $this->db->order("ed.rowid", "DESC");
1747
1748 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1749 $resql = $this->db->query($sql);
1750 if ($resql) {
1751 $cpt = $this->db->num_rows($resql);
1752
1753 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1754
1755 for ($i = 0; $i < $cpt; $i++) {
1756 dol_syslog(get_class($this)."::delete movement index ".$i);
1757 $obj = $this->db->fetch_object($resql);
1758 $line_id = (int) $obj->expeditiondet_id;
1759
1760 if ($can_update_stock && (empty($obj->iskit) || getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) && !empty($obj->incdec)) {
1761 $mouvS = new MouvementStock($this->db);
1762 // we do not log origin because it will be deleted
1763 $mouvS->origin = '';
1764 // get lot/serial
1765 $lotArray = null;
1766 if (isModEnabled('productbatch')) {
1767 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1768 if (!is_array($lotArray)) {
1769 $error++;
1770 $this->errors[] = "Error ".$this->db->lasterror();
1771 }
1772 }
1773
1774 if (empty($lotArray)) {
1775 // no lot/serial
1776 // We increment stock of product (and sub-products)
1777 // We use warehouse selected for each line
1778 $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
1779 if ($result < 0) {
1780 $error++;
1781 $this->errors = array_merge($this->errors, $mouvS->errors);
1782 break;
1783 }
1784 } else {
1785 // We increment stock of batches
1786 // We use warehouse selected for each line
1787 foreach ($lotArray as $lot) {
1788 $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
1789 if ($result < 0) {
1790 $error++;
1791 $this->errors = array_merge($this->errors, $mouvS->errors);
1792 break;
1793 }
1794 }
1795 if ($error) {
1796 break; // break for loop in case of error
1797 }
1798 }
1799 }
1800
1801 if (!$error) {
1802 // delete all children and batches of this shipment line
1803 $shipment_line = new ExpeditionLigne($this->db);
1804 $res = $shipment_line->fetch($line_id);
1805 if ($res > 0) {
1806 $result = $shipment_line->delete($user);
1807 if ($result < 0) {
1808 $error++;
1809 $this->errors[] = "Error ".$shipment_line->errorsToString();
1810 }
1811 } else {
1812 $error++;
1813 $this->errors[] = "Error ".$shipment_line->errorsToString();
1814 }
1815 }
1816
1817 if ($error) {
1818 break;
1819 }
1820 }
1821 } else {
1822 $error++;
1823 $this->errors[] = "Error ".$this->db->lasterror();
1824 }
1825 }
1826
1827 if (!$error) {
1828 // Delete linked object
1829 $res = $this->deleteObjectLinked();
1830 if ($res < 0) {
1831 $error++;
1832 }
1833
1834 // No delete expedition
1835 if (!$error) {
1836 $sql = "SELECT rowid FROM ".$this->db->prefix()."expedition";
1837 $sql .= " WHERE rowid = ".((int) $this->id);
1838
1839 if ($this->db->query($sql)) {
1840 if (!empty($this->origin) && $this->origin_id > 0) {
1841 $this->fetch_origin();
1843 '@phan-var-force Facture|Commande $origin_object';
1845 if ($origin_object->status == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1846 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1847 $origin_object->loadExpeditions();
1848 if (count($origin_object->expeditions) <= 0) {
1850 }
1851 }
1852 }
1853
1854 $this->db->commit();
1855
1856 // We delete PDFs
1857 $ref = dol_sanitizeFileName($this->ref);
1858 if (!empty($conf->expedition->dir_output)) {
1859 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1860 $file = $dir.'/'.$ref.'.pdf';
1861 if (file_exists($file)) {
1862 if (!dol_delete_file($file)) {
1863 return 0;
1864 }
1865 }
1866 if (file_exists($dir)) {
1867 if (!dol_delete_dir_recursive($dir)) {
1868 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1869 return 0;
1870 }
1871 }
1872 }
1873
1874 return 1;
1875 } else {
1876 $this->error = $this->db->lasterror()." - sql=$sql";
1877 $this->db->rollback();
1878 return -3;
1879 }
1880 } else {
1881 $this->db->rollback();
1882 return -2;
1883 }
1884 } else {
1885 $this->db->rollback();
1886 return -1;
1887 }
1888 }
1889
1899 public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1900 {
1901 global $conf, $langs;
1902
1903 if (empty($user)) {
1904 global $user;
1905 }
1906
1907 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1908
1909 $error = 0;
1910 $this->error = '';
1911
1912 $this->db->begin();
1913
1914 // Add a protection to refuse deleting if shipment has at least one delivery
1915 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1916 if (isset($this->linkedObjectsIds['delivery']) && count($this->linkedObjectsIds['delivery']) > 0) {
1917 $this->error = 'ErrorThereIsSomeDeliveries';
1918 $error++;
1919 }
1920
1921 if (!$error && !$notrigger) {
1922 // Call trigger
1923 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1924 if ($result < 0) {
1925 $error++;
1926 }
1927 // End call triggers
1928 }
1929
1930 // Stock control
1931 $can_update_stock = isModEnabled('stock') &&
1932 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->status > self::STATUS_DRAFT) ||
1933 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->status == self::STATUS_CLOSED && $also_update_stock));
1934 if (!$error) {
1935 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1936
1937 $langs->load("agenda");
1938
1939 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1940 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1941
1942 // Loop on each product line to add a stock movement (contain sub-products)
1943 $sql = "SELECT ";
1944 $sql .= " ed.fk_product";
1945 $sql .= ", ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1946 $sql .= ", SUM(".$this->db->ifsql("pa.rowid IS NOT NULL", "1", "0").") as iskit";
1947 $sql .= ", ".$this->db->ifsql("pai.incdec IS NULL", "1", "pai.incdec")." as incdec";
1948 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
1949 $sql .= " LEFT JOIN ".$this->db->prefix()."product_association as pa ON pa.fk_product_pere = ed.fk_product";
1950 $sql .= " LEFT JOIN ".$this->db->prefix()."expeditiondet as edp ON edp.rowid = ed.fk_parent";
1951 $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";
1952 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1953 $sql .= " GROUP BY ed.fk_product, ed.qty, ed.fk_entrepot, ed.rowid, pai.incdec";
1954 $sql .= $this->db->order("ed.rowid", "DESC");
1955
1956 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1957 $resql = $this->db->query($sql);
1958 if ($resql) {
1959 $cpt = $this->db->num_rows($resql);
1960 for ($i = 0; $i < $cpt; $i++) {
1961 dol_syslog(get_class($this)."::delete movement index ".$i);
1962 $obj = $this->db->fetch_object($resql);
1963 $line_id = (int) $obj->expeditiondet_id;
1964
1965 if ($can_update_stock && (empty($obj->iskit) || getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE')) && !empty($obj->incdec)) {
1966 $mouvS = new MouvementStock($this->db);
1967 // we do not log origin because it will be deleted
1968 $mouvS->origin = '';
1969 // get lot/serial
1970 $lotArray = $shipmentlinebatch->fetchAll($line_id);
1971 if (!is_array($lotArray)) {
1972 $error++;
1973 $this->errors[] = "Error ".$this->db->lasterror();
1974 }
1975 if (empty($lotArray)) {
1976 // no lot/serial
1977 // We increment stock of product (disable for sub-products : already in shipment lines)
1978 // We use warehouse selected for each line
1979 $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
1980 if ($result < 0) {
1981 $error++;
1982 $this->errors = array_merge($this->errors, $mouvS->errors);
1983 break;
1984 }
1985 } else {
1986 // We increment stock of batches
1987 // We use warehouse selected for each line
1988 foreach ($lotArray as $lot) {
1989 if (empty($lot->qty)) {
1990 continue;
1991 }
1992 $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
1993 if ($result < 0) {
1994 $error++;
1995 $this->errors = array_merge($this->errors, $mouvS->errors);
1996 break;
1997 }
1998 }
1999 if ($error) {
2000 break; // break for loop in case of error
2001 }
2002 }
2003 }
2004
2005 if (!$error) {
2006 // delete all children and batches of this shipment line
2007 $shipment_line = new ExpeditionLigne($this->db);
2008 $res = $shipment_line->fetch($line_id);
2009 if ($res > 0) {
2010 $result = $shipment_line->delete($user);
2011 if ($result < 0) {
2012 $error++;
2013 $this->errors[] = "Error ".$shipment_line->errorsToString();
2014 }
2015 } else {
2016 $error++;
2017 $this->errors[] = "Error ".$shipment_line->errorsToString();
2018 }
2019 }
2020
2021 if ($error) {
2022 break;
2023 }
2024 }
2025 } else {
2026 $error++;
2027 $this->errors[] = "Error ".$this->db->lasterror();
2028 }
2029 }
2030
2031 if (!$error) {
2032 // Delete linked object
2033 $res = $this->deleteObjectLinked();
2034 if ($res < 0) {
2035 $error++;
2036 }
2037
2038 // delete extrafields
2039 $res = $this->deleteExtraFields();
2040 if ($res < 0) {
2041 $error++;
2042 }
2043
2044 if (!$error) {
2045 // Delete linked contacts
2046 $res = $this->delete_linked_contact();
2047 if ($res < 0) {
2048 $error++;
2049 }
2050 }
2051 if (!$error) {
2052 $sql = "DELETE FROM ".$this->db->prefix()."expedition";
2053 $sql .= " WHERE rowid = ".((int) $this->id);
2054
2055 if ($this->db->query($sql)) {
2056 if (!empty($this->origin) && $this->origin_id > 0) {
2057 $this->fetch_origin();
2059 '@phan-var-force Facture|Commande $origin_object';
2060 if ($origin_object->status == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
2061 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
2062 $origin_object->loadExpeditions();
2063 if (count($origin_object->expeditions) <= 0) {
2065 }
2066 }
2067 }
2068
2069 $this->db->commit();
2070
2071 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2072 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2073 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2074
2075 // We delete PDFs
2076 $ref = dol_sanitizeFileName($this->ref);
2077 if (!empty($conf->expedition->dir_output) && !empty($ref)) {
2078 $dir = $conf->expedition->dir_output . '/sending/' . $ref;
2079 $file = $dir . '/' . $ref . '.pdf';
2080 if (file_exists($file)) {
2081 if (!dol_delete_file($file)) {
2082 return 0;
2083 }
2084 }
2085 if (file_exists($dir)) {
2086 if (!dol_delete_dir_recursive($dir)) {
2087 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
2088 return 0;
2089 }
2090 }
2091 }
2092
2093 return 1;
2094 } else {
2095 $this->error = $this->db->lasterror()." - sql=$sql";
2096 $this->db->rollback();
2097 return -3;
2098 }
2099 } else {
2100 $this->db->rollback();
2101 return -2;
2102 }
2103 } else {
2104 $this->db->rollback();
2105 return -1;
2106 }
2107 }
2108
2109 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2115 public function fetch_lines()
2116 {
2117 // phpcs:enable
2118 global $mysoc;
2119
2120 $this->lines = array();
2121
2122 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
2123 // TODO: See if we can restore a common fetch_lines (one line = one record)
2124
2125 $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";
2126 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
2127 $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
2128 $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";
2129 $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";
2130 $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";
2131 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
2132 $sql .= ", p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units";
2133 $sql .= ", p.surface, p.surface_units, p.volume, p.volume_units, p.tosell as product_tosell, p.tobuy as product_tobuy";
2134 $sql .= ", p.tobatch as product_tobatch, p.stockable_product";
2135 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
2136 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
2137 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2138 $sql .= " AND ed.fk_elementdet = cd.rowid";
2139 $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.
2140
2141 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
2142 $resql = $this->db->query($sql);
2143 if ($resql) {
2144 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2145
2146 $num = $this->db->num_rows($resql);
2147 $i = 0;
2148 $line = new ExpeditionLigne($this->db);
2149 $lineindex = 0;
2150 $originline = 0;
2151
2152 $this->total_ht = 0;
2153 $this->total_tva = 0;
2154 $this->total_ttc = 0;
2155 $this->total_localtax1 = 0;
2156 $this->total_localtax2 = 0;
2157
2158 $this->multicurrency_total_ht = 0;
2159 $this->multicurrency_total_tva = 0;
2160 $this->multicurrency_total_ttc = 0;
2161
2162 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
2163
2164 $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.
2165 while ($i < $num) {
2166 $obj = $this->db->fetch_object($resql);
2167
2168 if ($originline > 0 && $originline == $obj->fk_elementdet) {
2169 // '@phan-var-force ExpeditionLigne $line'; // $line from previous loop
2170 $line->entrepot_id = 0; // entrepod_id in details_entrepot
2171 $line->qty_shipped += $obj->qty_shipped;
2172 } else {
2173 $line = new ExpeditionLigne($this->db); // new group to start
2174 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
2175 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
2176 }
2177
2178 $detail_entrepot = new stdClass();
2179 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
2180 $detail_entrepot->qty_shipped = $obj->qty_shipped;
2181 $detail_entrepot->line_id = $obj->line_id;
2182 $line->details_entrepot[] = $detail_entrepot;
2183
2184 $line->line_id = $obj->line_id; // TODO deprecated
2185 $line->rowid = $obj->line_id; // TODO deprecated
2186 $line->id = $obj->line_id;
2187
2188 $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
2189
2190 $line->fk_element = $obj->fk_element;
2191 $line->origin_id = $obj->fk_element;
2192 $line->fk_elementdet = $obj->fk_elementdet;
2193 $line->origin_line_id = $obj->fk_elementdet;
2194 $line->element_type = $obj->element_type;
2195
2196 $line->fk_expedition = $this->id; // id of parent
2197
2198 $line->stockable_product = $obj->stockable_product;
2199 $line->product_type = $obj->product_type;
2200 $line->fk_product = $obj->fk_product;
2201 $line->fk_product_type = $obj->fk_product_type;
2202 $line->ref = $obj->product_ref; // TODO deprecated
2203 $line->product_ref = $obj->product_ref;
2204 $line->product_label = $obj->product_label;
2205 $line->libelle = $obj->product_label; // TODO deprecated
2206 $line->product_barcode = $obj->product_barcode; // Barcode number product
2207 $line->product_tosell = $obj->product_tosell;
2208 $line->product_tobuy = $obj->product_tobuy;
2209 $line->product_tobatch = $obj->product_tobatch;
2210 $line->fk_fournprice = $obj->fk_fournprice;
2211 $line->label = $obj->custom_label;
2212 $line->description = $obj->description;
2213 $line->qty_asked = $obj->qty_asked;
2214 $line->rang = $obj->rang;
2215 $line->weight = $obj->weight;
2216 $line->weight_units = $obj->weight_units;
2217 $line->length = $obj->length;
2218 $line->length_units = $obj->length_units;
2219 $line->width = $obj->width;
2220 $line->width_units = $obj->width_units;
2221 $line->height = $obj->height;
2222 $line->height_units = $obj->height_units;
2223 $line->surface = $obj->surface;
2224 $line->surface_units = $obj->surface_units;
2225 $line->volume = $obj->volume;
2226 $line->volume_units = $obj->volume_units;
2227 $line->stockable_product = $obj->stockable_product;
2228 $line->fk_unit = $obj->fk_unit;
2229
2230 $line->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2231
2232 $line->pa_ht = $obj->pa_ht;
2233
2234 // Local taxes
2235 $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
2236 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
2237 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
2238
2239 // For invoicing
2240 $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
2241 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
2242 $line->qty = $line->qty_shipped;
2243 $line->total_ht = (float) $tabprice[0];
2244 $line->total_localtax1 = (float) $tabprice[9];
2245 $line->total_localtax2 = (float) $tabprice[10];
2246 $line->total_ttc = (float) $tabprice[2];
2247 $line->total_tva = (float) $tabprice[1];
2248 $line->vat_src_code = $obj->vat_src_code;
2249 $line->tva_tx = $obj->tva_tx;
2250 $line->localtax1_tx = $obj->localtax1_tx;
2251 $line->localtax2_tx = $obj->localtax2_tx;
2252 $line->info_bits = $obj->info_bits;
2253 $line->price = $obj->price;
2254 $line->subprice = $obj->subprice;
2255 $line->fk_remise_except = $obj->fk_remise_except;
2256 $line->remise_percent = $obj->remise_percent;
2257
2258 $this->total_ht += $tabprice[0];
2259 $this->total_tva += $tabprice[1];
2260 $this->total_ttc += $tabprice[2];
2261 $this->total_localtax1 += $tabprice[9];
2262 $this->total_localtax2 += $tabprice[10];
2263
2264 $line->date_start = $this->db->jdate($obj->date_start);
2265 $line->date_end = $this->db->jdate($obj->date_end);
2266
2267 $line->special_code = $obj->special_code;
2268
2269 // Multicurrency
2270 $this->fk_multicurrency = $obj->fk_multicurrency;
2271 $this->multicurrency_code = $obj->multicurrency_code;
2272 $line->multicurrency_subprice = $obj->multicurrency_subprice;
2273 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
2274 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
2275 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
2276
2277 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
2278 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
2279 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
2280
2281 if ($originline != $obj->fk_elementdet) {
2282 $line->detail_batch = array();
2283 }
2284
2285 // Detail of batch
2286 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
2287 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
2288
2289 if (is_array($newdetailbatch)) {
2290 if ($originline != $obj->fk_elementdet) {
2291 $line->detail_batch = $newdetailbatch;
2292 } else {
2293 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
2294 }
2295 }
2296 }
2297
2298 // virtual product : find all children stock (group by product id and warehouse id)
2299 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
2300 $detail_children = array(); // detail by product : array of [warehouse_id => total_qty]
2301 $line_child_list = array();
2302 $res = $line->findAllChild($line->id, $line_child_list, 1);
2303 if ($res > 0) {
2304 foreach ($line_child_list as $child_line) {
2305 foreach ($child_line as $child_obj) {
2306 $child_product_id = (int) $child_obj->fk_product;
2307 $child_warehouse_id = (int) $child_obj->fk_warehouse;
2308
2309 if ($child_warehouse_id > 0) {
2310 // child quantities group by warehouses
2311 if (!isset($detail_children[$child_product_id])) {
2312 $detail_children[$child_product_id] = array();
2313 }
2314 if (!isset($detail_children[$child_product_id][$child_warehouse_id])) {
2315 $detail_children[$child_product_id][$child_warehouse_id] = 0;
2316 }
2317 $detail_children[$child_product_id][$child_warehouse_id] += $child_obj->qty;
2318 }
2319 }
2320 }
2321 }
2322 $line->detail_children = $detail_children;
2323 }
2324
2325 $line->fetch_optionals();
2326
2327 if ($originline != $obj->fk_elementdet) {
2328 $this->lines[$lineindex] = $line;
2329 $lineindex++;
2330 } else {
2331 $line->total_ht += $tabprice[0];
2332 $line->total_localtax1 += $tabprice[9];
2333 $line->total_localtax2 += $tabprice[10];
2334 $line->total_ttc += $tabprice[2];
2335 $line->total_tva += $tabprice[1];
2336 }
2337
2338 $i++;
2339 $originline = $obj->fk_elementdet;
2340 }
2341 $this->db->free($resql);
2342 return 1;
2343 } else {
2344 $this->error = $this->db->error();
2345 return -3;
2346 }
2347 }
2348
2349
2350 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2356 public function fetch_lines_free()
2357 {
2358 // phpcs:enable
2359 global $mysoc;
2360
2361 $this->lines = array();
2362
2363 $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';
2364 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as ed';
2365 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (p.rowid = ed.fk_product)';
2366 $sql .= ' WHERE ed.fk_expedition = '.((int) $this->id);
2367 $sql .= ' ORDER BY ed.rang, ed.rowid';
2368
2369 dol_syslog(get_class($this)."::fetch_lines_free", LOG_DEBUG);
2370 $result = $this->db->query($sql);
2371 if ($result) {
2372 $num = $this->db->num_rows($result);
2373
2374 $i = 0;
2375 while ($i < $num) {
2376 $objp = $this->db->fetch_object($result);
2377
2378 $line = new ExpeditionLigne($this->db);
2379
2380 $line->rowid = $objp->rowid;
2381 $line->id = $objp->rowid;
2382 $line->fk_expedition = $this->id;
2383 $line->description = $objp->description;
2384 $line->qty = $objp->qty;
2385 $line->fk_entrepot = $objp->fk_entrepot;
2386 $line->fk_product = $objp->fk_product;
2387 $line->rang = $objp->rang;
2388 $line->fk_element = $objp->fk_element;
2389 $line->fk_unit = $objp->fk_unit;
2390 $line->fk_elementdet = $objp->fk_elementdet;
2391 $line->fk_element_type = $objp->element_type;
2392 $line->fetch_optionals();
2393
2394 $this->lines[$i] = $line;
2395
2396 $i++;
2397 }
2398
2399 $this->db->free($result);
2400
2401 return 1;
2402 } else {
2403 $this->error = $this->db->error();
2404 return -3;
2405 }
2406 }
2407
2413 public function getLinesArray()
2414 {
2415 return $this->fetch_lines_free();
2416 }
2417
2425 public function deleteLine($user, $lineid)
2426 {
2427 global $user;
2428
2429 if ($this->status == self::STATUS_DRAFT) {
2430 $this->db->begin();
2431
2432 $line = new ExpeditionLigne($this->db);
2433
2434 // For triggers
2435 $line->fetch($lineid);
2436
2437 if ($this->id > 0 && (int) $line->fk_expedition !== (int) $this->id) {
2438 $this->db->rollback();
2439 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2440 return -1;
2441 }
2442
2443 if ($line->delete($user) > 0) {
2444 //$this->update_price(1);
2445
2446 $this->db->commit();
2447 return 1;
2448 } else {
2449 $this->db->rollback();
2450 return -1;
2451 }
2452 } else {
2453 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
2454 return -2;
2455 }
2456 }
2457
2458
2465 public function getTooltipContentArray($params)
2466 {
2467 global $conf, $langs;
2468
2469 $langs->load('sendings');
2470
2471 $nofetch = !empty($params['nofetch']);
2472
2473 $datas = array();
2474 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
2475 if (isset($this->status)) {
2476 $datas['picto'] .= ' '.$this->getLibStatut(5);
2477 }
2478 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
2479 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
2480 if (!$nofetch) {
2481 $langs->load('companies');
2482 if (empty($this->thirdparty)) {
2483 $this->fetch_thirdparty();
2484 }
2485 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
2486 }
2487
2488 return $datas;
2489 }
2490
2502 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
2503 {
2504 global $langs, $hookmanager;
2505
2506 $result = '';
2507 $params = [
2508 'id' => $this->id,
2509 'objecttype' => $this->element,
2510 'option' => $option,
2511 'nofetch' => 1,
2512 ];
2513 $classfortooltip = 'classfortooltip';
2514 $dataparams = '';
2515 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2516 $classfortooltip = 'classforajaxtooltip';
2517 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2518 $label = '';
2519 } else {
2520 $label = implode($this->getTooltipContentArray($params));
2521 }
2522
2523 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
2524
2525 if ($short) {
2526 return $url;
2527 }
2528
2529 if ($option !== 'nolink') {
2530 // Add param to save lastsearch_values or not
2531 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2532 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2533 $add_save_lastsearch_values = 1;
2534 }
2535 if ($add_save_lastsearch_values) {
2536 $url .= '&save_lastsearch_values=1';
2537 }
2538 }
2539
2540 $linkclose = '';
2541 if (empty($notooltip)) {
2542 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2543 $label = $langs->trans("Shipment");
2544 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2545 }
2546 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2547 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2548 }
2549
2550 $linkstart = '<a href="'.$url.'"';
2551 $linkstart .= $linkclose.'>';
2552 $linkend = '</a>';
2553
2554 $result .= $linkstart;
2555 if ($withpicto) {
2556 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2557 }
2558 if ($withpicto != 2) {
2559 $result .= $this->ref;
2560 }
2561 $result .= $linkend;
2562 global $action;
2563 $hookmanager->initHooks(array($this->element . 'dao'));
2564 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2565 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2566 if ($reshook > 0) {
2567 $result = $hookmanager->resPrint;
2568 } else {
2569 $result .= $hookmanager->resPrint;
2570 }
2571 return $result;
2572 }
2573
2580 public function getLibStatut($mode = 0)
2581 {
2582 return $this->LibStatut($this->status, $mode);
2583 }
2584
2585 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2593 public function LibStatut($status, $mode)
2594 {
2595 // phpcs:enable
2596 global $langs;
2597
2598 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
2599 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
2600
2601 $statusType = 'status'.$status;
2602 if ($status == self::STATUS_VALIDATED) {
2603 $statusType = 'status4';
2604 }
2605 if ($status == self::STATUS_CLOSED) {
2606 $statusType = 'status6';
2607 }
2608 if ($status == self::STATUS_CANCELED) {
2609 $statusType = 'status9';
2610 }
2611
2612 $signed_label = ' (' . $this->getLibSignedStatus() . ')';
2613 $status_label = $this->signed_status ? $labelStatus . $signed_label : $labelStatus;
2614 $status_label_short = $this->signed_status ? $labelStatusShort . $signed_label : $labelStatusShort;
2615
2616 return dolGetStatus($status_label, $status_label_short, '', $statusType, $mode);
2617 }
2618
2626 public function getKanbanView($option = '', $arraydata = null)
2627 {
2628 global $langs, $conf;
2629
2630 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2631
2632 $return = '<div class="box-flex-item box-flex-grow-zero">';
2633 $return .= '<div class="info-box info-box-sm">';
2634 $return .= '<div class="info-box-icon bg-infobox-action">';
2635 $return .= img_picto('', 'order');
2636 $return .= '</div>';
2637 $return .= '<div class="info-box-content">';
2638 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
2639 if ($selected >= 0) {
2640 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2641 }
2642 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
2643 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, getDolCurrency()).' '.$langs->trans('HT').'</div>';
2644 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2645 $return .= '</div>';
2646 $return .= '</div>';
2647 $return .= '</div>';
2648
2649 return $return;
2650 }
2651
2659 public function initAsSpecimen()
2660 {
2661 global $langs;
2662
2663 $now = dol_now();
2664
2665 dol_syslog(get_class($this)."::initAsSpecimen");
2666
2667 $order = new Commande($this->db);
2668 $order->initAsSpecimen();
2669
2670 // Initialise parameters
2671 $this->id = 0;
2672 $this->ref = 'SPECIMEN';
2673 $this->specimen = 1;
2675 $this->livraison_id = 0;
2676 $this->date = $now;
2677 $this->date_creation = $now;
2678 $this->date_valid = $now;
2679 $this->date_delivery = $now + 24 * 3600;
2680 $this->date_expedition = $now + 24 * 3600;
2681
2682 $this->entrepot_id = 0;
2683 $this->fk_delivery_address = 0;
2684 $this->socid = 1;
2685
2686 $this->commande_id = 0;
2687 $this->commande = $order;
2688
2689 $this->origin_id = 1;
2690 $this->origin_type = 'commande';
2691
2692 $this->note_private = 'Private note';
2693 $this->note_public = 'Public note';
2694
2695 $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)
2696 $xnbp = 0;
2697 while ($xnbp < $nbp) {
2698 $line = new ExpeditionLigne($this->db);
2699 $line->product_desc = $langs->trans("Description")." ".$xnbp;
2700 $line->product_label = $langs->trans("Description")." ".$xnbp;
2701 $line->qty = 10;
2702 $line->qty_asked = 5;
2703 $line->qty_shipped = 4;
2704 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2705
2706 $line->weight = 1.123456;
2707 $line->weight_units = 0; // kg
2708
2709 $line->volume = 2.34567;
2710 $line->volume_unit = 0;
2711
2712 $this->lines[] = $line;
2713 $xnbp++;
2714 }
2715
2716 return 1;
2717 }
2718
2719 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2728 public function set_date_livraison($user, $delivery_date)
2729 {
2730 // phpcs:enable
2731 return $this->setDeliveryDate($user, $delivery_date);
2732 }
2733
2741 public function setDeliveryDate($user, $delivery_date)
2742 {
2743 if ($user->hasRight('expedition', 'creer')) {
2744 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2745 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2746 $sql .= " WHERE rowid = ".((int) $this->id);
2747
2748 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2749 $resql = $this->db->query($sql);
2750 if ($resql) {
2751 $this->date_delivery = $delivery_date;
2752 return 1;
2753 } else {
2754 $this->error = $this->db->error();
2755 return -1;
2756 }
2757 } else {
2758 return -2;
2759 }
2760 }
2761
2769 public function setShippingDate($user, $shipping_date)
2770 {
2771 if ($user->hasRight('expedition', 'creer')) {
2772 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2773 $sql .= " SET date_expedition = ".($shipping_date ? "'".$this->db->idate($shipping_date)."'" : 'null');
2774 $sql .= " WHERE rowid = ".((int) $this->id);
2775
2776 dol_syslog(get_class($this)."::setShippingDate", LOG_DEBUG);
2777 $resql = $this->db->query($sql);
2778 if ($resql) {
2779 $this->date_shipping = $shipping_date;
2780 return 1;
2781 } else {
2782 $this->error = $this->db->error();
2783 return -1;
2784 }
2785 } else {
2786 return -2;
2787 }
2788 }
2789
2790 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2796 public function fetch_delivery_methods()
2797 {
2798 // phpcs:enable
2799 global $langs;
2800 $this->meths = [];
2801
2802 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2803 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2804 $sql .= " WHERE em.active = 1";
2805 $sql .= " ORDER BY em.libelle ASC";
2806
2807 $resql = $this->db->query($sql);
2808 if ($resql) {
2809 while ($obj = $this->db->fetch_object($resql)) {
2810 $label = $langs->trans('SendingMethod'.$obj->code);
2811 $this->meths[(int) $obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2812 }
2813 }
2814 }
2815
2816 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2823 public function list_delivery_methods($id = 0)
2824 {
2825 // phpcs:enable
2826 global $langs;
2827
2828 $this->listmeths = [];
2829 $i = 0;
2830
2831 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2832 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2833 if (!empty($id)) {
2834 $sql .= " WHERE em.rowid=".((int) $id);
2835 }
2836
2837 $resql = $this->db->query($sql);
2838 if ($resql) {
2839 while ($obj = $this->db->fetch_object($resql)) {
2840 $label = $langs->trans('SendingMethod'.$obj->code);
2841 $this->listmeths[$i] = [
2842 'rowid' => (int) $obj->rowid,
2843 'code' => $obj->code,
2844 'libelle' => ($label != 'SendingMethod'.$obj->code ? $label : $obj->label),
2845 'description' => $obj->description,
2846 'tracking' => $obj->tracking,
2847 'active' => (int) $obj->active,
2848 ];
2849 $i++;
2850 }
2851 }
2852 }
2853
2860 public function getUrlTrackingStatus($value = '')
2861 {
2862 $tracking = '';
2863 if (!empty($this->shipping_method_id)) {
2864 $sql = "SELECT em.code, em.tracking";
2865 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2866 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2867
2868 $resql = $this->db->query($sql);
2869 if ($resql) {
2870 if ($obj = $this->db->fetch_object($resql)) {
2871 $tracking = (string) $obj->tracking;
2872 }
2873 }
2874 }
2875
2876 if (!empty($tracking) && !empty($value)) {
2877 $url = str_replace('{TRACKID}', $value, $tracking);
2878 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, $value);
2879 } else {
2880 $this->tracking_url = $value;
2881 }
2882 }
2883
2889 public function setClosed()
2890 {
2891 global $user;
2892
2893 $error = 0;
2894
2895 // Protection. This avoid to move stock later when we should not
2896 if ($this->status == self::STATUS_CLOSED) {
2897 return 0;
2898 }
2899
2900 $this->db->begin();
2901
2902 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED;
2903 if (empty($this->date_shipping)) { // Date of real shipment was not yet set, we force it on closing
2904 $sql .= ", date_expedition = '".$this->db->escape($this->db->idate(dol_now()))."'";
2905 }
2906 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2907
2908 $resql = $this->db->query($sql);
2909 if ($resql) {
2910 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2911 if ($this->origin_type == 'commande' && $this->origin_id > 0) {
2912 $order = new Commande($this->db);
2913 $order->fetch($this->origin_id);
2914
2915 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2916
2917 $shipments_match_order = 1;
2918 foreach ($order->lines as $line) {
2919 $lineid = $line->id;
2920 $qty = $line->qty;
2921 if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2922 $shipments_match_order = 0;
2923 $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';
2924 dol_syslog($text);
2925 break;
2926 }
2927 }
2928 if ($shipments_match_order) {
2929 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');
2930 // We close the order
2931 $order->cloture($user, 0, 0); // 0 = do not check the close permission: this is an automatic action of the shipment closing. Note this may also create an invoice if module workflow ask it
2932 }
2933 }
2934
2935 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2936 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2937
2938 // If stock increment is done on closing
2939 if (isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2940 $result = $this->manageStockMvtOnEvt($user);
2941
2942 if ($result < 0) {
2943 $error++;
2944 }
2945 }
2946
2947 // Call trigger
2948 if (!$error) {
2949 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2950 if ($result < 0) {
2951 $error++;
2952 }
2953 }
2954 } else {
2955 dol_print_error($this->db);
2956 $error++;
2957 }
2958
2959 if (!$error) {
2960 $this->db->commit();
2961 return 1;
2962 } else {
2963 $this->statut = self::STATUS_VALIDATED;
2965
2966 $this->db->rollback();
2967 return -1;
2968 }
2969 }
2970
2979 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2980 {
2981 global $langs;
2982
2983 $error = 0;
2984
2985 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2986
2987 $langs->load("agenda");
2988
2989 // Loop on each product line to add a stock movement
2990 $sql = "SELECT";
2991 $sql .= " ed.rowid as edid, ed.fk_product, ed.qty, ed.fk_entrepot";
2992 $sql .= ", cd.rowid as cdid";
2993 $sql .= ", cd.subprice";
2994 $sql .= ", edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2995 $sql .= ", e.ref";
2996 $sql .= " FROM " . $this->db->prefix() . "expeditiondet as ed";
2997 $sql .= " LEFT JOIN " . $this->db->prefix() . "commandedet as cd ON cd.rowid = ed.fk_elementdet";
2998 $sql .= " LEFT JOIN " . $this->db->prefix() . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2999 $sql .= " INNER JOIN " . $this->db->prefix() . "expedition as e ON ed.fk_expedition = e.rowid";
3000 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
3001 //$sql .= " AND cd.rowid = ed.fk_elementdet";
3002
3003 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
3004 $resql = $this->db->query($sql);
3005 if ($resql) {
3006 $cpt = $this->db->num_rows($resql);
3007 for ($i = 0; $i < $cpt; $i++) {
3008 $obj = $this->db->fetch_object($resql);
3009 if (empty($obj->edbrowid)) {
3010 $qty = $obj->qty;
3011 } else {
3012 $qty = $obj->edbqty;
3013 }
3014 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
3015 continue;
3016 }
3017 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->edid . " edb.rowid=" . $obj->edbrowid);
3018
3019 $mouvS = new MouvementStock($this->db);
3020 $mouvS->origin = &$this;
3021 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
3022
3023 if (empty($obj->edbrowid)) {
3024 // line without batch detail
3025
3026 // 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
3027 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
3028 if ($result < 0) {
3029 $this->setErrorsFromObject($mouvS);
3030 $error++;
3031 break;
3032 }
3033 } else {
3034 // line with batch detail
3035
3036 // 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
3037 $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);
3038 if ($result < 0) {
3039 $this->setErrorsFromObject($mouvS);
3040 $error++;
3041 break;
3042 }
3043 }
3044
3045 // 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
3046 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
3047 $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)";
3048 $resqldelete = $this->db->query($sqldelete);
3049 // We do not test error, it can fails if there is child in batch details
3050 }
3051 } else {
3052 $this->error = $this->db->lasterror();
3053 $this->errors[] = $this->db->lasterror();
3054 $error++;
3055 }
3056
3057 if (!$error) {
3058 return 1;
3059 } else {
3060 return -1;
3061 }
3062 }
3063
3069 public function setBilled()
3070 {
3071 global $user;
3072 $error = 0;
3073
3074 $this->db->begin();
3075
3076 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET billed = 1';
3077 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
3078
3079 $resql = $this->db->query($sql);
3080 if ($resql) {
3081 $this->billed = 1;
3082
3083 // Call trigger
3084 $result = $this->call_trigger('SHIPPING_BILLED', $user);
3085 if ($result < 0) {
3086 $this->billed = 0;
3087 $error++;
3088 }
3089 } else {
3090 $error++;
3091 $this->errors[] = $this->db->lasterror;
3092 }
3093
3094 if (empty($error)) {
3095 $this->db->commit();
3096 return 1;
3097 } else {
3098 $this->db->rollback();
3099 return -1;
3100 }
3101 }
3102
3110 public function setDraft($user, $notrigger = 0)
3111 {
3112 // Protection
3113 if ($this->status <= self::STATUS_DRAFT) {
3114 return 0;
3115 }
3116
3117 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
3118 }
3119
3125 public function reOpen()
3126 {
3127 global $langs, $user;
3128
3129 $error = 0;
3130
3131 // Protection. This avoid to move stock later when we should not
3132 if ($this->status == self::STATUS_VALIDATED) {
3133 return 0;
3134 }
3135
3136 $this->db->begin();
3137
3138 $oldbilled = $this->billed;
3139
3140 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
3141 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
3142
3143 $resql = $this->db->query($sql);
3144 if ($resql) {
3145 $this->statut = self::STATUS_VALIDATED;
3147 $this->billed = 0;
3148
3149 // If stock increment is done on closing
3150 if (isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
3151 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
3152
3153 $langs->load("agenda");
3154
3155 // Loop on each product line to add a stock movement
3156 // TODO possibilite d'expedier a partir d'une propale ou autre origine
3157 $sql = "SELECT cd.fk_product, cd.subprice,";
3158 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
3159 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
3160 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
3161 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
3162 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
3163 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
3164 $sql .= " AND cd.rowid = ed.fk_elementdet";
3165
3166 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
3167 $resql = $this->db->query($sql);
3168 if ($resql) {
3169 $cpt = $this->db->num_rows($resql);
3170 for ($i = 0; $i < $cpt; $i++) {
3171 $obj = $this->db->fetch_object($resql);
3172 if (empty($obj->edbrowid)) {
3173 $qty = $obj->qty;
3174 } else {
3175 $qty = $obj->edbqty;
3176 }
3177 if ($qty <= 0) {
3178 continue;
3179 }
3180 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
3181
3182 //var_dump($this->lines[$i]);
3183 $mouvS = new MouvementStock($this->db);
3184 $mouvS->origin = &$this;
3185 $mouvS->setOrigin($this->element, $this->id);
3186
3187 if (empty($obj->edbrowid)) {
3188 // line without batch detail
3189
3190 // 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
3191 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
3192 if ($result < 0) {
3193 $this->setErrorsFromObject($mouvS);
3194 $error++;
3195 break;
3196 }
3197 } else {
3198 // line with batch detail
3199
3200 // 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
3201 $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);
3202 if ($result < 0) {
3203 $this->setErrorsFromObject($mouvS);
3204 $error++;
3205 break;
3206 }
3207 }
3208 }
3209 } else {
3210 $this->error = $this->db->lasterror();
3211 $error++;
3212 }
3213 }
3214
3215 if (!$error) {
3216 // Call trigger
3217 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
3218 if ($result < 0) {
3219 $error++;
3220 }
3221 }
3222 } else {
3223 $error++;
3224 $this->errors[] = $this->db->lasterror();
3225 }
3226
3227 if (!$error) {
3228 $this->db->commit();
3229 return 1;
3230 } else {
3231 $this->statut = self::STATUS_CLOSED;
3232 $this->status = self::STATUS_CLOSED;
3233 $this->billed = $oldbilled;
3234 $this->db->rollback();
3235 return -1;
3236 }
3237 }
3238
3250 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3251 {
3252 $outputlangs->load("products");
3253
3254 if (!dol_strlen($modele)) {
3255 $modele = 'rouget';
3256
3257 if (!empty($this->model_pdf)) {
3258 $modele = $this->model_pdf;
3259 } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
3260 $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
3261 }
3262 }
3263
3264 $modelpath = "core/modules/expedition/doc/";
3265
3266 $this->fetch_origin();
3267
3268 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3269 }
3270
3279 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3280 {
3281 $tables = array(
3282 'expedition'
3283 );
3284
3285 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3286 }
3287}
$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:469
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:136