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