dolibarr 20.0.5
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-2024 Frédéric France <frederic.france@free.fr>
15 * Copyright (C) 2020 Lenin Rivas <lenin@leninrivas.com>
16 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 3 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program. If not, see <https://www.gnu.org/licenses/>.
30 */
31
38require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
39require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
40require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
41if (isModEnabled("propal")) {
42 require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
43}
44if (isModEnabled('order')) {
45 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
46}
47require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionlinebatch.class.php';
48
49
54{
56
60 public $element = "shipping";
61
65 public $fk_element = "fk_expedition";
66
70 public $table_element = "expedition";
71
75 public $table_element_line = "expeditiondet";
76
80 public $picto = 'dolly';
81
82
86 public $fields = array();
87
91 public $user_author_id;
92
96 public $fk_user_author;
97
98 public $socid;
99
105 public $ref_client;
106
110 public $ref_customer;
111
115 public $entrepot_id;
116
120 public $tracking_number;
121
125 public $tracking_url;
126 public $billed;
127
131 public $model_pdf;
132
133 public $trueWeight;
134 public $weight_units;
135 public $trueWidth;
136 public $width_units;
137 public $trueHeight;
138 public $height_units;
139 public $trueDepth;
140 public $depth_units;
141 // A denormalized value
142 public $trueSize;
143
144 public $livraison_id;
145
149 public $multicurrency_subprice;
150
151 public $size_units;
152
153 public $sizeH;
154
155 public $sizeS;
156
157 public $sizeW;
158
159 public $weight;
160
164 public $date_delivery;
165
170 public $date;
171
177
182 public $date_shipping;
183
187 public $date_creation;
188
192 public $date_valid;
193
194 public $meths;
195 public $listmeths; // List of carriers
196
200 public $commande_id;
201
205 public $commande;
206
210 public $lines = array();
211
212 // Multicurrency
216 public $fk_multicurrency;
217
221 public $multicurrency_code;
222 public $multicurrency_tx;
223 public $multicurrency_total_ht;
224 public $multicurrency_total_tva;
225 public $multicurrency_total_ttc;
226
230 public $signed_status = 0;
231
235 const STATUS_DRAFT = 0;
236
244
251 const STATUS_CLOSED = 2;
252
256 const STATUS_CANCELED = -1;
257
266
267
272
276 const STATUS_SIGNED = 1;
277
278
279
285 public function __construct($db)
286 {
287 global $conf;
288
289 $this->db = $db;
290
291 $this->ismultientitymanaged = 1;
292 $this->isextrafieldmanaged = 1;
293
294 // List of long language codes for status
295 $this->labelStatus = array();
296 $this->labelStatus[-1] = 'StatusSendingCanceled';
297 $this->labelStatus[0] = 'StatusSendingDraft';
298 $this->labelStatus[1] = 'StatusSendingValidated';
299 $this->labelStatus[2] = 'StatusSendingProcessed';
300
301 // List of short language codes for status
302 $this->labelStatusShort = array();
303 $this->labelStatusShort[-1] = 'StatusSendingCanceledShort';
304 $this->labelStatusShort[0] = 'StatusSendingDraftShort';
305 $this->labelStatusShort[1] = 'StatusSendingValidatedShort';
306 $this->labelStatusShort[2] = 'StatusSendingProcessedShort';
307 }
308
315 public function getNextNumRef($soc)
316 {
317 global $langs, $conf;
318 $langs->load("sendings");
319
320 if (getDolGlobalString('EXPEDITION_ADDON_NUMBER')) {
321 $mybool = false;
322
323 $file = getDolGlobalString('EXPEDITION_ADDON_NUMBER') . ".php";
324 $classname = getDolGlobalString('EXPEDITION_ADDON_NUMBER');
325
326 // Include file with class
327 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
328
329 foreach ($dirmodels as $reldir) {
330 $dir = dol_buildpath($reldir."core/modules/expedition/");
331
332 // Load file with numbering class (if found)
333 $mybool = ((bool) @include_once $dir.$file) || $mybool;
334 }
335
336 if (!$mybool) {
337 dol_print_error(null, "Failed to include file ".$file);
338 return '';
339 }
340
341 $obj = new $classname();
342 $numref = "";
343 $numref = $obj->getNextValue($soc, $this);
344
345 if ($numref != "") {
346 return $numref;
347 } else {
348 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
349 return "";
350 }
351 } else {
352 print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
353 return "";
354 }
355 }
356
364 public function create($user, $notrigger = 0)
365 {
366 global $conf, $hookmanager;
367
368 $now = dol_now();
369
370 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
371 $error = 0;
372
373 // Clean parameters
374 $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
375 if (empty($this->fk_project)) {
376 $this->fk_project = 0;
377 }
378 if (empty($this->date_shipping) && !empty($this->date_expedition)) {
379 $this->date_shipping = $this->date_expedition;
380 }
381
382 $this->user = $user;
383
384 $this->db->begin();
385
386 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
387 $sql .= "ref";
388 $sql .= ", entity";
389 $sql .= ", ref_customer";
390 $sql .= ", ref_ext";
391 $sql .= ", date_creation";
392 $sql .= ", fk_user_author";
393 $sql .= ", date_expedition";
394 $sql .= ", date_delivery";
395 $sql .= ", fk_soc";
396 $sql .= ", fk_projet";
397 $sql .= ", fk_address";
398 $sql .= ", fk_shipping_method";
399 $sql .= ", tracking_number";
400 $sql .= ", weight";
401 $sql .= ", size";
402 $sql .= ", width";
403 $sql .= ", height";
404 $sql .= ", weight_units";
405 $sql .= ", size_units";
406 $sql .= ", note_private";
407 $sql .= ", note_public";
408 $sql .= ", model_pdf";
409 $sql .= ", fk_incoterms, location_incoterms";
410 $sql .= ") VALUES (";
411 $sql .= "'(PROV)'";
412 $sql .= ", ".((int) $conf->entity);
413 $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
414 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
415 $sql .= ", '".$this->db->idate($now)."'";
416 $sql .= ", ".((int) $user->id);
417 $sql .= ", ".($this->date_shipping > 0 ? "'".$this->db->idate($this->date_shipping)."'" : "null");
418 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
419 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
420 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
421 $sql .= ", ".($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
422 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
423 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
424 $sql .= ", ".(is_numeric($this->weight) ? $this->weight : 'NULL');
425 $sql .= ", ".(is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
426 $sql .= ", ".(is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
427 $sql .= ", ".(is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
428 $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
429 $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
430 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
431 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
432 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
433 $sql .= ", ".(int) $this->fk_incoterms;
434 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
435 $sql .= ")";
436
437 dol_syslog(get_class($this)."::create", LOG_DEBUG);
438 $resql = $this->db->query($sql);
439 if ($resql) {
440 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
441
442 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
443 $sql .= " SET ref = '(PROV".$this->id.")'";
444 $sql .= " WHERE rowid = ".((int) $this->id);
445
446 dol_syslog(get_class($this)."::create", LOG_DEBUG);
447 if ($this->db->query($sql)) {
448 // Insert of lines
449 $num = count($this->lines);
450 for ($i = 0; $i < $num; $i++) {
451 if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
452 if (!isset($this->lines[$i]->detail_batch)) { // no batch management
453 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) {
454 $error++;
455 }
456 } else { // with batch management
457 if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
458 $error++;
459 }
460 }
461 }
462 }
463
464 if (!$error && $this->id && $this->origin_id) {
465 $ret = $this->add_object_linked();
466 if (!$ret) {
467 $error++;
468 }
469 }
470
471 // Actions on extra fields
472 if (!$error) {
473 $result = $this->insertExtraFields();
474 if ($result < 0) {
475 $error++;
476 }
477 }
478
479 if (!$error && !$notrigger) {
480 // Call trigger
481 $result = $this->call_trigger('SHIPPING_CREATE', $user);
482 if ($result < 0) {
483 $error++;
484 }
485 // End call triggers
486
487 if (!$error) {
488 $this->db->commit();
489 return $this->id;
490 } else {
491 foreach ($this->errors as $errmsg) {
492 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
493 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
494 }
495 $this->db->rollback();
496 return -1 * $error;
497 }
498 } else {
499 $error++;
500 $this->db->rollback();
501 return -3;
502 }
503 } else {
504 $error++;
505 $this->error = $this->db->lasterror()." - sql=$sql";
506 $this->db->rollback();
507 return -2;
508 }
509 } else {
510 $error++;
511 $this->error = $this->db->error()." - sql=$sql";
512 $this->db->rollback();
513 return -1;
514 }
515 }
516
517 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
528 public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [])
529 {
530 //phpcs:enable
531 global $user;
532
533 $expeditionline = new ExpeditionLigne($this->db);
534 $expeditionline->fk_expedition = $this->id;
535 $expeditionline->entrepot_id = $entrepot_id;
536 $expeditionline->fk_elementdet = $origin_line_id;
537 $expeditionline->element_type = $this->origin;
538 $expeditionline->qty = $qty;
539 $expeditionline->rang = $rang;
540 $expeditionline->array_options = $array_options;
541
542 if (($lineId = $expeditionline->insert($user)) < 0) {
543 $this->errors[] = $expeditionline->error;
544 }
545 return $lineId;
546 }
547
548
549 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
557 public function create_line_batch($line_ext, $array_options = [])
558 {
559 // phpcs:enable
560 $error = 0;
561 $stockLocationQty = array(); // associated array with batch qty in stock location
562
563 $tab = $line_ext->detail_batch;
564 // create stockLocation Qty array
565 foreach ($tab as $detbatch) {
566 if (!empty($detbatch->entrepot_id)) {
567 if (empty($stockLocationQty[$detbatch->entrepot_id])) {
568 $stockLocationQty[$detbatch->entrepot_id] = 0;
569 }
570 $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
571 }
572 }
573 // create shipment lines
574 foreach ($stockLocationQty as $stockLocation => $qty) {
575 $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
576 if ($line_id < 0) {
577 $error++;
578 } else {
579 // create shipment batch lines for stockLocation
580 foreach ($tab as $detbatch) {
581 if ($detbatch->entrepot_id == $stockLocation) {
582 if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch
583 $this->errors = $detbatch->errors;
584 $error++;
585 }
586 }
587 }
588 }
589 }
590
591 if (!$error) {
592 return 1;
593 } else {
594 return -1;
595 }
596 }
597
607 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
608 {
609 global $conf;
610
611 // Check parameters
612 if (empty($id) && empty($ref) && empty($ref_ext)) {
613 return -1;
614 }
615
616 $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.fk_projet as fk_project, e.billed";
617 $sql .= ", e.date_valid";
618 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
619 $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
620 $sql .= ", e.fk_shipping_method, e.tracking_number";
621 $sql .= ", e.note_private, e.note_public";
622 $sql .= ', e.fk_incoterms, e.location_incoterms';
623 $sql .= ', e.signed_status';
624 $sql .= ', i.libelle as label_incoterms';
625 $sql .= ', s.libelle as shipping_method';
626 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin_type";
627 $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
628 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
629 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
630 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
631 $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
632 if ($id) {
633 $sql .= " AND e.rowid = ".((int) $id);
634 }
635 if ($ref) {
636 $sql .= " AND e.ref='".$this->db->escape($ref)."'";
637 }
638 if ($ref_ext) {
639 $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
640 }
641
642 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
643 $result = $this->db->query($sql);
644 if ($result) {
645 if ($this->db->num_rows($result)) {
646 $obj = $this->db->fetch_object($result);
647
648 $this->id = $obj->rowid;
649 $this->entity = $obj->entity;
650 $this->ref = $obj->ref;
651 $this->socid = $obj->socid;
652 $this->ref_customer = $obj->ref_customer;
653 $this->ref_ext = $obj->ref_ext;
654 $this->status = $obj->fk_statut;
655 $this->statut = $this->status; // Deprecated
656 $this->user_author_id = $obj->fk_user_author;
657 $this->fk_user_author = $obj->fk_user_author;
658 $this->date_creation = $this->db->jdate($obj->date_creation);
659 $this->date_valid = $this->db->jdate($obj->date_valid);
660 $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
661 $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
662 $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
663 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
664 $this->fk_delivery_address = $obj->fk_address;
665 $this->model_pdf = $obj->model_pdf;
666 $this->shipping_method_id = $obj->fk_shipping_method;
667 $this->shipping_method = $obj->shipping_method;
668 $this->tracking_number = $obj->tracking_number;
669 $this->origin = ($obj->origin_type ? $obj->origin_type : 'commande'); // For compatibility
670 $this->origin_type = ($obj->origin_type ? $obj->origin_type : 'commande');
671 $this->origin_id = $obj->origin_id;
672 $this->billed = $obj->billed;
673 $this->fk_project = $obj->fk_project;
674 $this->signed_status = $obj->signed_status;
675 $this->trueWeight = $obj->weight;
676 $this->weight_units = $obj->weight_units;
677
678 $this->trueWidth = $obj->width;
679 $this->width_units = $obj->size_units;
680 $this->trueHeight = $obj->height;
681 $this->height_units = $obj->size_units;
682 $this->trueDepth = $obj->size;
683 $this->depth_units = $obj->size_units;
684
685 $this->note_public = $obj->note_public;
686 $this->note_private = $obj->note_private;
687
688 // A denormalized value
689 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
690 $this->size_units = $obj->size_units;
691
692 //Incoterms
693 $this->fk_incoterms = $obj->fk_incoterms;
694 $this->location_incoterms = $obj->location_incoterms;
695 $this->label_incoterms = $obj->label_incoterms;
696
697 $this->db->free($result);
698
699 // Tracking url
700 $this->getUrlTrackingStatus($obj->tracking_number);
701
702 // Thirdparty
703 $result = $this->fetch_thirdparty(); // TODO Remove this
704
705 // Retrieve extrafields
706 $this->fetch_optionals();
707
708 // Fix Get multicurrency param for transmitted
709 if (isModEnabled('multicurrency')) {
710 if (!empty($this->multicurrency_code)) {
711 $this->multicurrency_code = $this->thirdparty->multicurrency_code;
712 }
713 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($this->thirdparty->multicurrency_tx)) {
714 $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
715 }
716 }
717
718 /*
719 * Lines
720 */
721 $result = $this->fetch_lines();
722 if ($result < 0) {
723 return -3;
724 }
725
726 return 1;
727 } else {
728 dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
729 $this->error = 'Shipment with id '.$id.' not found';
730 return 0;
731 }
732 } else {
733 $this->error = $this->db->error();
734 return -1;
735 }
736 }
737
745 public function valid($user, $notrigger = 0)
746 {
747 global $conf;
748
749 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
750
751 dol_syslog(get_class($this)."::valid");
752
753 // Protection
754 if ($this->status) {
755 dol_syslog(get_class($this)."::valid not in draft status", LOG_WARNING);
756 return 0;
757 }
758
759 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'creer'))
760 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'shipping_advance', 'validate')))) {
761 $this->error = 'Permission denied';
762 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
763 return -1;
764 }
765
766 $this->db->begin();
767
768 $error = 0;
769
770 // Define new ref
771 $soc = new Societe($this->db);
772 $soc->fetch($this->socid);
773
774 // Class of company linked to order
775 $result = $soc->setAsCustomer();
776
777 // Define new ref
778 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
779 $numref = $this->getNextNumRef($soc);
780 } elseif (!empty($this->ref)) {
781 $numref = $this->ref;
782 } else {
783 $numref = "EXP".$this->id;
784 }
785 $this->newref = dol_sanitizeFileName($numref);
786
787 $now = dol_now();
788
789 // Validate
790 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
791 $sql .= " ref='".$this->db->escape($numref)."'";
792 $sql .= ", fk_statut = 1";
793 $sql .= ", date_valid = '".$this->db->idate($now)."'";
794 $sql .= ", fk_user_valid = ".$user->id;
795 $sql .= " WHERE rowid = ".((int) $this->id);
796
797 dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
798 $resql = $this->db->query($sql);
799 if (!$resql) {
800 $this->error = $this->db->lasterror();
801 $error++;
802 }
803
804 // If stock increment is done on sending (recommended choice)
805 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
806 $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
807 if ($result < 0) {
808 return -2;
809 }
810 }
811
812 // Change status of order to "shipment in process"
813 $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
814 if ($this->origin == 'commande') {
815 $triggerKey.= 'ORDER_SHIPMENTONPROCESS';
816 } else {
817 $triggerKey.= strtoupper($this->origin).'_SHIPMENTONPROCESS';
818 }
819
820 // TODO : load the origin object to trigger the right setStatus according to origin object
821 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin, $triggerKey);
822 if (!$ret) {
823 $error++;
824 }
825
826 if (!$error && !$notrigger) {
827 // Call trigger
828 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
829 if ($result < 0) {
830 $error++;
831 }
832 // End call triggers
833 }
834
835 if (!$error) {
836 $this->oldref = $this->ref;
837
838 // Rename directory if dir was a temporary ref
839 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
840 // Now we rename also files into index
841 $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)."'";
842 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
843 $resql = $this->db->query($sql);
844 if (!$resql) {
845 $error++;
846 $this->error = $this->db->lasterror();
847 }
848 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
849 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
850 $resql = $this->db->query($sql);
851 if (!$resql) {
852 $error++;
853 $this->error = $this->db->lasterror();
854 }
855
856 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
857 $oldref = dol_sanitizeFileName($this->ref);
858 $newref = dol_sanitizeFileName($numref);
859 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
860 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
861 if (!$error && file_exists($dirsource)) {
862 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
863
864 if (@rename($dirsource, $dirdest)) {
865 dol_syslog("Rename ok");
866 // Rename docs starting with $oldref with $newref
867 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
868 foreach ($listoffiles as $fileentry) {
869 $dirsource = $fileentry['name'];
870 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
871 $dirsource = $fileentry['path'].'/'.$dirsource;
872 $dirdest = $fileentry['path'].'/'.$dirdest;
873 @rename($dirsource, $dirdest);
874 }
875 }
876 }
877 }
878 }
879
880 // Set new ref and current status
881 if (!$error) {
882 $this->ref = $numref;
885 }
886
887 if (!$error) {
888 $this->db->commit();
889 return 1;
890 } else {
891 $this->db->rollback();
892 return -1 * $error;
893 }
894 }
895
896
897 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
904 public function create_delivery($user)
905 {
906 // phpcs:enable
907 global $conf;
908
909 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
910 if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
911 // Expedition validee
912 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
913 $delivery = new Delivery($this->db);
914 $result = $delivery->create_from_sending($user, $this->id);
915 if ($result > 0) {
916 return $result;
917 } else {
918 $this->error = $delivery->error;
919 return $result;
920 }
921 } else {
922 return 0;
923 }
924 } else {
925 return 0;
926 }
927 }
928
941 public function addline($entrepot_id, $id, $qty, $array_options = [])
942 {
943 global $conf, $langs;
944
945 $num = count($this->lines);
946 $line = new ExpeditionLigne($this->db);
947
948 $line->entrepot_id = $entrepot_id;
949 $line->origin_line_id = $id;
950 $line->fk_elementdet = $id;
951 $line->element_type = 'order';
952 $line->qty = $qty;
953
954 $orderline = new OrderLine($this->db);
955 $orderline->fetch($id);
956
957 // Copy the rang of the order line to the expedition line
958 $line->rang = $orderline->rang;
959 $line->product_type = $orderline->product_type;
960
961 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
962 $fk_product = $orderline->fk_product;
963
964 if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS') && !(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') && $line->product_type == Product::TYPE_SERVICE)) {
965 $langs->load("errors");
966 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
967 return -1;
968 }
969
970 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
971 $product = new Product($this->db);
972 $product->fetch($fk_product);
973
974 // Check must be done for stock of product into warehouse if $entrepot_id defined
975 if ($entrepot_id > 0) {
976 $product->load_stock('warehouseopen');
977 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
978 } else {
979 $product_stock = $product->stock_reel;
980 }
981
982 $product_type = $product->type;
983 if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
984 $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
985 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
986 if (!$isavirtualproduct || !getDolGlobalString('PRODUIT_SOUSPRODUITS') || ($isavirtualproduct && !getDolGlobalString('STOCK_EXCLUDE_VIRTUAL_PRODUCTS'))) { // If STOCK_EXCLUDE_VIRTUAL_PRODUCTS is set, we do not manage stock for kits/virtual products.
987 if ($product_stock < $qty) {
988 $langs->load("errors");
989 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
990 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
991
992 $this->db->rollback();
993 return -3;
994 }
995 }
996 }
997 }
998 }
999
1000 // If product need a batch number, we should not have called this function but addline_batch instead.
1001 // If this happen, we may have a bug in card.php page
1002 if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
1003 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; //
1004 return -4;
1005 }
1006
1007 // extrafields
1008 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1009 $line->array_options = $array_options;
1010 }
1011
1012 $this->lines[$num] = $line;
1013
1014 return 1;
1015 }
1016
1017 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1025 public function addline_batch($dbatch, $array_options = [])
1026 {
1027 // phpcs:enable
1028 global $conf, $langs;
1029
1030 $num = count($this->lines);
1031 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1032 $line = new ExpeditionLigne($this->db);
1033 $tab = array();
1034 foreach ($dbatch['detail'] as $key => $value) {
1035 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1036 // $value['q']=qty to move
1037 // $value['id_batch']=id into llx_product_batch of record to move
1038 //var_dump($value);
1039
1040 $linebatch = new ExpeditionLineBatch($this->db);
1041 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1042 if ($ret < 0) {
1043 $this->setErrorsFromObject($linebatch);
1044 return -1;
1045 }
1046 $linebatch->qty = $value['q'];
1047 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1048 $linebatch->batch = null;
1049 }
1050 $tab[] = $linebatch;
1051
1052 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1053 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1054 $prod_batch = new Productbatch($this->db);
1055 $prod_batch->fetch($value['id_batch']);
1056
1057 if ($prod_batch->qty < $linebatch->qty) {
1058 $langs->load("errors");
1059 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1060 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1061 $this->db->rollback();
1062 return -1;
1063 }
1064 }
1065
1066 //var_dump($linebatch);
1067 }
1068 }
1069 $line->entrepot_id = $linebatch->entrepot_id;
1070 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1071 $line->fk_elementdet = $dbatch['ix_l'];
1072 $line->qty = $dbatch['qty'];
1073 $line->detail_batch = $tab;
1074
1075 // extrafields
1076 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1077 $line->array_options = $array_options;
1078 }
1079
1080 //var_dump($line);
1081 $this->lines[$num] = $line;
1082 return 1;
1083 }
1084 return 0;
1085 }
1086
1094 public function update($user = null, $notrigger = 0)
1095 {
1096 global $conf;
1097 $error = 0;
1098
1099 // Clean parameters
1100
1101 if (isset($this->ref)) {
1102 $this->ref = trim($this->ref);
1103 }
1104 if (isset($this->entity)) {
1105 $this->entity = (int) $this->entity;
1106 }
1107 if (isset($this->ref_customer)) {
1108 $this->ref_customer = trim($this->ref_customer);
1109 }
1110 if (isset($this->socid)) {
1111 $this->socid = (int) $this->socid;
1112 }
1113 if (isset($this->fk_user_author)) {
1114 $this->fk_user_author = (int) $this->fk_user_author;
1115 }
1116 if (isset($this->fk_user_valid)) {
1117 $this->fk_user_valid = (int) $this->fk_user_valid;
1118 }
1119 if (isset($this->fk_delivery_address)) {
1120 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1121 }
1122 if (isset($this->shipping_method_id)) {
1123 $this->shipping_method_id = (int) $this->shipping_method_id;
1124 }
1125 if (isset($this->tracking_number)) {
1126 $this->tracking_number = trim($this->tracking_number);
1127 }
1128 if (isset($this->statut)) {
1129 $this->statut = (int) $this->statut;
1130 }
1131 if (isset($this->trueDepth)) {
1132 $this->trueDepth = trim($this->trueDepth);
1133 }
1134 if (isset($this->trueWidth)) {
1135 $this->trueWidth = trim($this->trueWidth);
1136 }
1137 if (isset($this->trueHeight)) {
1138 $this->trueHeight = trim($this->trueHeight);
1139 }
1140 if (isset($this->size_units)) {
1141 $this->size_units = trim($this->size_units);
1142 }
1143 if (isset($this->weight_units)) {
1144 $this->weight_units = trim($this->weight_units);
1145 }
1146 if (isset($this->trueWeight)) {
1147 $this->weight = trim((string) $this->trueWeight);
1148 }
1149 if (isset($this->note_private)) {
1150 $this->note_private = trim($this->note_private);
1151 }
1152 if (isset($this->note_public)) {
1153 $this->note_public = trim($this->note_public);
1154 }
1155 if (isset($this->model_pdf)) {
1156 $this->model_pdf = trim($this->model_pdf);
1157 }
1158
1159 // Check parameters
1160 // Put here code to add control on parameters values
1161
1162 // Update request
1163 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1164 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1165 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1166 $sql .= " ref_customer=".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1167 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1168 $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1169 $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1170 $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1171 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1172 $sql .= " date_expedition=".(dol_strlen($this->date_expedition) != 0 ? "'".$this->db->idate($this->date_expedition)."'" : 'null').",";
1173 $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1174 $sql .= " fk_address=".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1175 $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1176 $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1177 $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1178 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1179 $sql .= " height=".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1180 $sql .= " width=".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1181 $sql .= " size_units=".(isset($this->size_units) ? $this->size_units : "null").",";
1182 $sql .= " size=".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1183 $sql .= " weight_units=".(isset($this->weight_units) ? $this->weight_units : "null").",";
1184 $sql .= " weight=".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1185 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1186 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1187 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1188 $sql .= " entity=".$conf->entity;
1189 $sql .= " WHERE rowid=".((int) $this->id);
1190
1191 $this->db->begin();
1192
1193 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1194 $resql = $this->db->query($sql);
1195 if (!$resql) {
1196 $error++;
1197 $this->errors[] = "Error ".$this->db->lasterror();
1198 }
1199
1200 if (!$error && !$notrigger) {
1201 // Call trigger
1202 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1203 if ($result < 0) {
1204 $error++;
1205 }
1206 // End call triggers
1207 }
1208
1209 // Commit or rollback
1210 if ($error) {
1211 foreach ($this->errors as $errmsg) {
1212 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1213 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1214 }
1215 $this->db->rollback();
1216 return -1 * $error;
1217 } else {
1218 $this->db->commit();
1219 return 1;
1220 }
1221 }
1222
1223
1231 public function cancel($notrigger = 0, $also_update_stock = false)
1232 {
1233 global $conf, $langs, $user;
1234
1235 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1236
1237 $error = 0;
1238 $this->error = '';
1239
1240 $this->db->begin();
1241
1242 // Add a protection to refuse deleting if shipment has at least one delivery
1243 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1244 if (count($this->linkedObjectsIds) > 0) {
1245 $this->error = 'ErrorThereIsSomeDeliveries';
1246 $error++;
1247 }
1248
1249 if (!$error && !$notrigger) {
1250 // Call trigger
1251 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1252 if ($result < 0) {
1253 $error++;
1254 }
1255 // End call triggers
1256 }
1257
1258 // Stock control
1259 if (!$error && isModEnabled('stock') &&
1260 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1261 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1262 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1263
1264 $langs->load("agenda");
1265
1266 // Loop on each product line to add a stock movement and delete features
1267 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1268 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1269 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1270 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1271 $sql .= " AND cd.rowid = ed.fk_elementdet";
1272
1273 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1274 $resql = $this->db->query($sql);
1275 if ($resql) {
1276 $cpt = $this->db->num_rows($resql);
1277
1278 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1279
1280 for ($i = 0; $i < $cpt; $i++) {
1281 dol_syslog(get_class($this)."::delete movement index ".$i);
1282 $obj = $this->db->fetch_object($resql);
1283
1284 $mouvS = new MouvementStock($this->db);
1285 // we do not log origin because it will be deleted
1286 $mouvS->origin = '';
1287 // get lot/serial
1288 $lotArray = null;
1289 if (isModEnabled('productbatch')) {
1290 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1291 if (!is_array($lotArray)) {
1292 $error++;
1293 $this->errors[] = "Error ".$this->db->lasterror();
1294 }
1295 }
1296
1297 if (empty($lotArray)) {
1298 // no lot/serial
1299 // We increment stock of product (and sub-products)
1300 // We use warehouse selected for each line
1301 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref)); // Price is set to 0, because we don't want to see WAP changed
1302 if ($result < 0) {
1303 $error++;
1304 $this->errors = array_merge($this->errors, $mouvS->errors);
1305 break;
1306 }
1307 } else {
1308 // We increment stock of batches
1309 // We use warehouse selected for each line
1310 foreach ($lotArray as $lot) {
1311 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch); // Price is set to 0, because we don't want to see WAP changed
1312 if ($result < 0) {
1313 $error++;
1314 $this->errors = array_merge($this->errors, $mouvS->errors);
1315 break;
1316 }
1317 }
1318 if ($error) {
1319 break; // break for loop in case of error
1320 }
1321 }
1322 }
1323 } else {
1324 $error++;
1325 $this->errors[] = "Error ".$this->db->lasterror();
1326 }
1327 }
1328
1329 // delete batch expedition line
1330 if (!$error && isModEnabled('productbatch')) {
1331 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1332 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1333 $error++;
1334 $this->errors[] = "Error ".$this->db->lasterror();
1335 }
1336 }
1337
1338
1339 if (!$error) {
1340 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1341 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1342
1343 if ($this->db->query($sql)) {
1344 // Delete linked object
1345 $res = $this->deleteObjectLinked();
1346 if ($res < 0) {
1347 $error++;
1348 }
1349
1350 // No delete expedition
1351 if (!$error) {
1352 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1353 $sql .= " WHERE rowid = ".((int) $this->id);
1354
1355 if ($this->db->query($sql)) {
1356 if (!empty($this->origin) && $this->origin_id > 0) {
1357 $this->fetch_origin();
1358 if ($this->origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1359 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1360 $this->origin_object->loadExpeditions();
1361 //var_dump($this->$origin->expeditions);exit;
1362 if (count($this->origin_object->expeditions) <= 0) {
1363 $this->origin_object->setStatut(Commande::STATUS_VALIDATED);
1364 }
1365 }
1366 }
1367
1368 if (!$error) {
1369 $this->db->commit();
1370
1371 // We delete PDFs
1372 $ref = dol_sanitizeFileName($this->ref);
1373 if (!empty($conf->expedition->dir_output)) {
1374 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1375 $file = $dir.'/'.$ref.'.pdf';
1376 if (file_exists($file)) {
1377 if (!dol_delete_file($file)) {
1378 return 0;
1379 }
1380 }
1381 if (file_exists($dir)) {
1382 if (!dol_delete_dir_recursive($dir)) {
1383 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1384 return 0;
1385 }
1386 }
1387 }
1388
1389 return 1;
1390 } else {
1391 $this->db->rollback();
1392 return -1;
1393 }
1394 } else {
1395 $this->error = $this->db->lasterror()." - sql=$sql";
1396 $this->db->rollback();
1397 return -3;
1398 }
1399 } else {
1400 $this->error = $this->db->lasterror()." - sql=$sql";
1401 $this->db->rollback();
1402 return -2;
1403 }//*/
1404 } else {
1405 $this->error = $this->db->lasterror()." - sql=$sql";
1406 $this->db->rollback();
1407 return -1;
1408 }
1409 } else {
1410 $this->db->rollback();
1411 return -1;
1412 }
1413 }
1414
1424 public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1425 {
1426 global $conf, $langs;
1427
1428 if (empty($user)) {
1429 global $user;
1430 }
1431
1432 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1433
1434 $error = 0;
1435 $this->error = '';
1436
1437 $this->db->begin();
1438
1439 // Add a protection to refuse deleting if shipment has at least one delivery
1440 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1441 if (count($this->linkedObjectsIds) > 0) {
1442 $this->error = 'ErrorThereIsSomeDeliveries';
1443 $error++;
1444 }
1445
1446 if (!$error && !$notrigger) {
1447 // Call trigger
1448 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1449 if ($result < 0) {
1450 $error++;
1451 }
1452 // End call triggers
1453 }
1454
1455 // Stock control
1456 if (!$error && isModEnabled('stock') &&
1457 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1458 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1459 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1460
1461 $langs->load("agenda");
1462
1463 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1464 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1465
1466 // Loop on each product line to add a stock movement
1467 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1468 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1469 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1470 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1471 $sql .= " AND cd.rowid = ed.fk_elementdet";
1472
1473 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1474 $resql = $this->db->query($sql);
1475 if ($resql) {
1476 $cpt = $this->db->num_rows($resql);
1477 for ($i = 0; $i < $cpt; $i++) {
1478 dol_syslog(get_class($this)."::delete movement index ".$i);
1479 $obj = $this->db->fetch_object($resql);
1480
1481 $mouvS = new MouvementStock($this->db);
1482 // we do not log origin because it will be deleted
1483 $mouvS->origin = '';
1484 // get lot/serial
1485 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1486 if (!is_array($lotArray)) {
1487 $error++;
1488 $this->errors[] = "Error ".$this->db->lasterror();
1489 }
1490 if (empty($lotArray)) {
1491 // no lot/serial
1492 // We increment stock of product (and sub-products)
1493 // We use warehouse selected for each line
1494 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref)); // Price is set to 0, because we don't want to see WAP changed
1495 if ($result < 0) {
1496 $error++;
1497 $this->errors = array_merge($this->errors, $mouvS->errors);
1498 break;
1499 }
1500 } else {
1501 // We increment stock of batches
1502 // We use warehouse selected for each line
1503 foreach ($lotArray as $lot) {
1504 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch); // Price is set to 0, because we don't want to see WAP changed
1505 if ($result < 0) {
1506 $error++;
1507 $this->errors = array_merge($this->errors, $mouvS->errors);
1508 break;
1509 }
1510 }
1511 if ($error) {
1512 break; // break for loop in case of error
1513 }
1514 }
1515 }
1516 } else {
1517 $error++;
1518 $this->errors[] = "Error ".$this->db->lasterror();
1519 }
1520 }
1521
1522 // delete batch expedition line
1523 if (!$error) {
1524 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1525 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1526 $error++;
1527 $this->errors[] = "Error ".$this->db->lasterror();
1528 }
1529 }
1530
1531 if (!$error) {
1532 $main = MAIN_DB_PREFIX.'expeditiondet';
1533 $ef = $main."_extrafields";
1534 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
1535
1536 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1537 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1538
1539 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1540 // Delete linked object
1541 $res = $this->deleteObjectLinked();
1542 if ($res < 0) {
1543 $error++;
1544 }
1545
1546 // delete extrafields
1547 $res = $this->deleteExtraFields();
1548 if ($res < 0) {
1549 $error++;
1550 }
1551
1552 if (!$error) {
1553 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1554 $sql .= " WHERE rowid = ".((int) $this->id);
1555
1556 if ($this->db->query($sql)) {
1557 if (!empty($this->origin) && $this->origin_id > 0) {
1558 $this->fetch_origin();
1559 if ($this->origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1560 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1561 $this->origin_object->loadExpeditions();
1562 //var_dump($this->$origin->expeditions);exit;
1563 if (count($this->origin_object->expeditions) <= 0) {
1564 $this->origin_object->setStatut(Commande::STATUS_VALIDATED);
1565 }
1566 }
1567 }
1568
1569 if (!$error) {
1570 $this->db->commit();
1571
1572 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1573 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1574 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1575
1576 // We delete PDFs
1577 $ref = dol_sanitizeFileName($this->ref);
1578 if (!empty($conf->expedition->dir_output)) {
1579 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1580 $file = $dir.'/'.$ref.'.pdf';
1581 if (file_exists($file)) {
1582 if (!dol_delete_file($file)) {
1583 return 0;
1584 }
1585 }
1586 if (file_exists($dir)) {
1587 if (!dol_delete_dir_recursive($dir)) {
1588 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1589 return 0;
1590 }
1591 }
1592 }
1593
1594 return 1;
1595 } else {
1596 $this->db->rollback();
1597 return -1;
1598 }
1599 } else {
1600 $this->error = $this->db->lasterror()." - sql=$sql";
1601 $this->db->rollback();
1602 return -3;
1603 }
1604 } else {
1605 $this->error = $this->db->lasterror()." - sql=$sql";
1606 $this->db->rollback();
1607 return -2;
1608 }
1609 } else {
1610 $this->error = $this->db->lasterror()." - sql=$sql";
1611 $this->db->rollback();
1612 return -1;
1613 }
1614 } else {
1615 $this->db->rollback();
1616 return -1;
1617 }
1618 }
1619
1620 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1626 public function fetch_lines()
1627 {
1628 // phpcs:enable
1629 global $mysoc;
1630
1631 $this->lines = array();
1632
1633 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1634 // TODO: See if we can restore a common fetch_lines (one line = one record)
1635
1636 $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";
1637 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1638 $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
1639 $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";
1640 $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";
1641 $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot";
1642 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
1643 $sql .= ", p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units, p.tosell as product_tosell, p.tobuy as product_tobuy, p.tobatch as product_tobatch";
1644 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1645 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1646 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1647 $sql .= " AND ed.fk_elementdet = cd.rowid";
1648 $sql .= " ORDER BY cd.rang, ed.fk_elementdet"; // We need after a break on fk_elementdet but when there is no break on fk_elementdet, cd.rang is same so we can add it as first order criteria.
1649
1650 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1651 $resql = $this->db->query($sql);
1652 if ($resql) {
1653 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1654
1655 $num = $this->db->num_rows($resql);
1656 $i = 0;
1657 $lineindex = 0;
1658 $originline = 0;
1659
1660 $this->total_ht = 0;
1661 $this->total_tva = 0;
1662 $this->total_ttc = 0;
1663 $this->total_localtax1 = 0;
1664 $this->total_localtax2 = 0;
1665
1666 $this->multicurrency_total_ht = 0;
1667 $this->multicurrency_total_tva = 0;
1668 $this->multicurrency_total_ttc = 0;
1669
1670 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1671
1672 while ($i < $num) {
1673 $obj = $this->db->fetch_object($resql);
1674
1675
1676 if ($originline > 0 && $originline == $obj->fk_elementdet) {
1677 '@phan-var-force ExpeditionLigne $line'; // $line from previous loop
1678 $line->entrepot_id = 0; // entrepod_id in details_entrepot
1679 $line->qty_shipped += $obj->qty_shipped;
1680 } else {
1681 $line = new ExpeditionLigne($this->db); // new group to start
1682 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
1683 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
1684 }
1685
1686 $detail_entrepot = new stdClass();
1687 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1688 $detail_entrepot->qty_shipped = $obj->qty_shipped;
1689 $detail_entrepot->line_id = $obj->line_id;
1690 $line->details_entrepot[] = $detail_entrepot;
1691
1692 $line->line_id = $obj->line_id; // TODO deprecated
1693 $line->rowid = $obj->line_id; // TODO deprecated
1694 $line->id = $obj->line_id;
1695
1696 $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
1697
1698 $line->fk_element = $obj->fk_element;
1699 $line->origin_id = $obj->fk_element;
1700 $line->fk_elementdet = $obj->fk_elementdet;
1701 $line->origin_line_id = $obj->fk_elementdet;
1702 $line->element_type = $obj->element_type;
1703
1704 $line->fk_expedition = $this->id; // id of parent
1705
1706 $line->product_type = $obj->product_type;
1707 $line->fk_product = $obj->fk_product;
1708 $line->fk_product_type = $obj->fk_product_type;
1709 $line->ref = $obj->product_ref; // TODO deprecated
1710 $line->product_ref = $obj->product_ref;
1711 $line->product_label = $obj->product_label;
1712 $line->libelle = $obj->product_label; // TODO deprecated
1713 $line->product_barcode = $obj->product_barcode; // Barcode number product
1714 $line->product_tosell = $obj->product_tosell;
1715 $line->product_tobuy = $obj->product_tobuy;
1716 $line->product_tobatch = $obj->product_tobatch;
1717 $line->fk_fournprice = $obj->fk_fournprice;
1718 $line->label = $obj->custom_label;
1719 $line->description = $obj->description;
1720 $line->qty_asked = $obj->qty_asked;
1721 $line->rang = $obj->rang;
1722 $line->weight = $obj->weight;
1723 $line->weight_units = $obj->weight_units;
1724 $line->length = $obj->length;
1725 $line->length_units = $obj->length_units;
1726 $line->width = $obj->width;
1727 $line->width_units = $obj->width_units;
1728 $line->height = $obj->height;
1729 $line->height_units = $obj->height_units;
1730 $line->surface = $obj->surface;
1731 $line->surface_units = $obj->surface_units;
1732 $line->volume = $obj->volume;
1733 $line->volume_units = $obj->volume_units;
1734 $line->fk_unit = $obj->fk_unit;
1735
1736 $line->pa_ht = $obj->pa_ht;
1737
1738 // Local taxes
1739 $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
1740 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1741 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1742
1743 // For invoicing
1744 $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
1745 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1746 $line->qty = $line->qty_shipped;
1747 $line->total_ht = $tabprice[0];
1748 $line->total_localtax1 = $tabprice[9];
1749 $line->total_localtax2 = $tabprice[10];
1750 $line->total_ttc = $tabprice[2];
1751 $line->total_tva = $tabprice[1];
1752 $line->vat_src_code = $obj->vat_src_code;
1753 $line->tva_tx = $obj->tva_tx;
1754 $line->localtax1_tx = $obj->localtax1_tx;
1755 $line->localtax2_tx = $obj->localtax2_tx;
1756 $line->info_bits = $obj->info_bits;
1757 $line->price = $obj->price;
1758 $line->subprice = $obj->subprice;
1759 $line->fk_remise_except = $obj->fk_remise_except;
1760 $line->remise_percent = $obj->remise_percent;
1761
1762 $this->total_ht += $tabprice[0];
1763 $this->total_tva += $tabprice[1];
1764 $this->total_ttc += $tabprice[2];
1765 $this->total_localtax1 += $tabprice[9];
1766 $this->total_localtax2 += $tabprice[10];
1767
1768 $line->date_start = $this->db->jdate($obj->date_start);
1769 $line->date_end = $this->db->jdate($obj->date_end);
1770
1771 // Multicurrency
1772 $this->fk_multicurrency = $obj->fk_multicurrency;
1773 $this->multicurrency_code = $obj->multicurrency_code;
1774 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1775 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1776 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1777 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1778
1779 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
1780 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
1781 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
1782
1783 if ($originline != $obj->fk_elementdet) {
1784 $line->detail_batch = array();
1785 }
1786
1787 // Detail of batch
1788 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1789 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1790
1791 if (is_array($newdetailbatch)) {
1792 if ($originline != $obj->fk_elementdet) {
1793 $line->detail_batch = $newdetailbatch;
1794 } else {
1795 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1796 }
1797 }
1798 }
1799
1800 $line->fetch_optionals();
1801
1802 if ($originline != $obj->fk_elementdet) {
1803 $this->lines[$lineindex] = $line;
1804 $lineindex++;
1805 } else {
1806 $line->total_ht += $tabprice[0];
1807 $line->total_localtax1 += $tabprice[9];
1808 $line->total_localtax2 += $tabprice[10];
1809 $line->total_ttc += $tabprice[2];
1810 $line->total_tva += $tabprice[1];
1811 }
1812
1813 $i++;
1814 $originline = $obj->fk_elementdet;
1815 }
1816 $this->db->free($resql);
1817 return 1;
1818 } else {
1819 $this->error = $this->db->error();
1820 return -3;
1821 }
1822 }
1823
1831 public function deleteLine($user, $lineid)
1832 {
1833 global $user;
1834
1835 if ($this->statut == self::STATUS_DRAFT) {
1836 $this->db->begin();
1837
1838 $line = new ExpeditionLigne($this->db);
1839
1840 // For triggers
1841 $line->fetch($lineid);
1842
1843 if ($line->delete($user) > 0) {
1844 //$this->update_price(1);
1845
1846 $this->db->commit();
1847 return 1;
1848 } else {
1849 $this->db->rollback();
1850 return -1;
1851 }
1852 } else {
1853 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1854 return -2;
1855 }
1856 }
1857
1858
1866 public function getTooltipContentArray($params)
1867 {
1868 global $conf, $langs;
1869
1870 $langs->load('sendings');
1871
1872 $nofetch = !empty($params['nofetch']);
1873
1874 $datas = array();
1875 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
1876 if (isset($this->statut)) {
1877 $datas['picto'] .= ' '.$this->getLibStatut(5);
1878 }
1879 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1880 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1881 if (!$nofetch) {
1882 $langs->load('companies');
1883 if (empty($this->thirdparty)) {
1884 $this->fetch_thirdparty();
1885 }
1886 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1887 }
1888
1889 return $datas;
1890 }
1891
1903 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1904 {
1905 global $langs, $hookmanager;
1906
1907 $result = '';
1908 $params = [
1909 'id' => $this->id,
1910 'objecttype' => $this->element,
1911 'option' => $option,
1912 'nofetch' => 1,
1913 ];
1914 $classfortooltip = 'classfortooltip';
1915 $dataparams = '';
1916 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1917 $classfortooltip = 'classforajaxtooltip';
1918 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1919 $label = '';
1920 } else {
1921 $label = implode($this->getTooltipContentArray($params));
1922 }
1923
1924 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1925
1926 if ($short) {
1927 return $url;
1928 }
1929
1930 if ($option !== 'nolink') {
1931 // Add param to save lastsearch_values or not
1932 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1933 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1934 $add_save_lastsearch_values = 1;
1935 }
1936 if ($add_save_lastsearch_values) {
1937 $url .= '&save_lastsearch_values=1';
1938 }
1939 }
1940
1941 $linkclose = '';
1942 if (empty($notooltip)) {
1943 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1944 $label = $langs->trans("Shipment");
1945 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1946 }
1947 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1948 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1949 }
1950
1951 $linkstart = '<a href="'.$url.'"';
1952 $linkstart .= $linkclose.'>';
1953 $linkend = '</a>';
1954
1955 $result .= $linkstart;
1956 if ($withpicto) {
1957 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1958 }
1959 if ($withpicto != 2) {
1960 $result .= $this->ref;
1961 }
1962 $result .= $linkend;
1963 global $action;
1964 $hookmanager->initHooks(array($this->element . 'dao'));
1965 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1966 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1967 if ($reshook > 0) {
1968 $result = $hookmanager->resPrint;
1969 } else {
1970 $result .= $hookmanager->resPrint;
1971 }
1972 return $result;
1973 }
1974
1981 public function getLibStatut($mode = 0)
1982 {
1983 return $this->LibStatut($this->statut, $mode);
1984 }
1985
1986 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1994 public function LibStatut($status, $mode)
1995 {
1996 // phpcs:enable
1997 global $langs;
1998
1999 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
2000 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
2001
2002 $statusType = 'status'.$status;
2003 if ($status == self::STATUS_VALIDATED) {
2004 $statusType = 'status4';
2005 }
2006 if ($status == self::STATUS_CLOSED) {
2007 $statusType = 'status6';
2008 }
2009 if ($status == self::STATUS_CANCELED) {
2010 $statusType = 'status9';
2011 }
2012
2013 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
2014 }
2015
2023 public function getKanbanView($option = '', $arraydata = null)
2024 {
2025 global $langs, $conf;
2026
2027 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2028
2029 $return = '<div class="box-flex-item box-flex-grow-zero">';
2030 $return .= '<div class="info-box info-box-sm">';
2031 $return .= '<div class="info-box-icon bg-infobox-action">';
2032 $return .= img_picto('', 'order');
2033 $return .= '</div>';
2034 $return .= '<div class="info-box-content">';
2035 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
2036 if ($selected >= 0) {
2037 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2038 }
2039 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
2040 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
2041 }
2042 if (property_exists($this, 'total_ht')) {
2043 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'</div>';
2044 }
2045 if (method_exists($this, 'getLibStatut')) {
2046 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2047 }
2048 $return .= '</div>';
2049 $return .= '</div>';
2050 $return .= '</div>';
2051
2052 return $return;
2053 }
2054
2062 public function initAsSpecimen()
2063 {
2064 global $langs;
2065
2066 $now = dol_now();
2067
2068 dol_syslog(get_class($this)."::initAsSpecimen");
2069
2070 $order = new Commande($this->db);
2071 $order->initAsSpecimen();
2072
2073 // Initialise parameters
2074 $this->id = 0;
2075 $this->ref = 'SPECIMEN';
2076 $this->specimen = 1;
2078 $this->livraison_id = 0;
2079 $this->date = $now;
2080 $this->date_creation = $now;
2081 $this->date_valid = $now;
2082 $this->date_delivery = $now + 24 * 3600;
2083 $this->date_expedition = $now + 24 * 3600;
2084
2085 $this->entrepot_id = 0;
2086 $this->fk_delivery_address = 0;
2087 $this->socid = 1;
2088
2089 $this->commande_id = 0;
2090 $this->commande = $order;
2091
2092 $this->origin_id = 1;
2093 $this->origin = 'commande';
2094
2095 $this->note_private = 'Private note';
2096 $this->note_public = 'Public note';
2097
2098 $nbp = 5;
2099 $xnbp = 0;
2100 while ($xnbp < $nbp) {
2101 $line = new ExpeditionLigne($this->db);
2102 $line->product_desc = $langs->trans("Description")." ".$xnbp;
2103 $line->product_label = $langs->trans("Description")." ".$xnbp;
2104 $line->qty = 10;
2105 $line->qty_asked = 5;
2106 $line->qty_shipped = 4;
2107 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2108
2109 $this->lines[] = $line;
2110 $xnbp++;
2111 }
2112
2113 return 1;
2114 }
2115
2116 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2125 public function set_date_livraison($user, $delivery_date)
2126 {
2127 // phpcs:enable
2128 return $this->setDeliveryDate($user, $delivery_date);
2129 }
2130
2138 public function setDeliveryDate($user, $delivery_date)
2139 {
2140 if ($user->hasRight('expedition', 'creer')) {
2141 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2142 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2143 $sql .= " WHERE rowid = ".((int) $this->id);
2144
2145 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2146 $resql = $this->db->query($sql);
2147 if ($resql) {
2148 $this->date_delivery = $delivery_date;
2149 return 1;
2150 } else {
2151 $this->error = $this->db->error();
2152 return -1;
2153 }
2154 } else {
2155 return -2;
2156 }
2157 }
2158
2159 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2165 public function fetch_delivery_methods()
2166 {
2167 // phpcs:enable
2168 global $langs;
2169 $this->meths = array();
2170
2171 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2172 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2173 $sql .= " WHERE em.active = 1";
2174 $sql .= " ORDER BY em.libelle ASC";
2175
2176 $resql = $this->db->query($sql);
2177 if ($resql) {
2178 while ($obj = $this->db->fetch_object($resql)) {
2179 $label = $langs->trans('SendingMethod'.$obj->code);
2180 $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2181 }
2182 }
2183 }
2184
2185 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2192 public function list_delivery_methods($id = 0)
2193 {
2194 // phpcs:enable
2195 global $langs;
2196
2197 $this->listmeths = array();
2198 $i = 0;
2199
2200 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2201 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2202 if (!empty($id)) {
2203 $sql .= " WHERE em.rowid=".((int) $id);
2204 }
2205
2206 $resql = $this->db->query($sql);
2207 if ($resql) {
2208 while ($obj = $this->db->fetch_object($resql)) {
2209 $this->listmeths[$i]['rowid'] = $obj->rowid;
2210 $this->listmeths[$i]['code'] = $obj->code;
2211 $label = $langs->trans('SendingMethod'.$obj->code);
2212 $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2213 $this->listmeths[$i]['description'] = $obj->description;
2214 $this->listmeths[$i]['tracking'] = $obj->tracking;
2215 $this->listmeths[$i]['active'] = $obj->active;
2216 $i++;
2217 }
2218 }
2219 }
2220
2227 public function getUrlTrackingStatus($value = '')
2228 {
2229 if (!empty($this->shipping_method_id)) {
2230 $sql = "SELECT em.code, em.tracking";
2231 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2232 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2233
2234 $resql = $this->db->query($sql);
2235 if ($resql) {
2236 if ($obj = $this->db->fetch_object($resql)) {
2237 $tracking = $obj->tracking;
2238 }
2239 }
2240 }
2241
2242 if (!empty($tracking) && !empty($value)) {
2243 $url = str_replace('{TRACKID}', $value, $tracking);
2244 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
2245 } else {
2246 $this->tracking_url = $value;
2247 }
2248 }
2249
2255 public function setClosed()
2256 {
2257 global $user;
2258
2259 $error = 0;
2260
2261 // Protection. This avoid to move stock later when we should not
2262 if ($this->statut == self::STATUS_CLOSED) {
2263 return 0;
2264 }
2265
2266 $this->db->begin();
2267
2268 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED;
2269 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2270
2271 $resql = $this->db->query($sql);
2272 if ($resql) {
2273 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2274 if ($this->origin == 'commande' && $this->origin_id > 0) {
2275 $order = new Commande($this->db);
2276 $order->fetch($this->origin_id);
2277
2278 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2279
2280 $shipments_match_order = 1;
2281 foreach ($order->lines as $line) {
2282 $lineid = $line->id;
2283 $qty = $line->qty;
2284 if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2285 $shipments_match_order = 0;
2286 $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';
2287 dol_syslog($text);
2288 break;
2289 }
2290 }
2291 if ($shipments_match_order) {
2292 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');
2293 // We close the order
2294 $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2295 }
2296 }
2297
2298 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2299 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2300
2301 // If stock increment is done on closing
2302 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2303 $result = $this->manageStockMvtOnEvt($user);
2304 if ($result < 0) {
2305 $error++;
2306 }
2307 }
2308
2309 // Call trigger
2310 if (!$error) {
2311 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2312 if ($result < 0) {
2313 $error++;
2314 }
2315 }
2316 } else {
2317 dol_print_error($this->db);
2318 $error++;
2319 }
2320
2321 if (!$error) {
2322 $this->db->commit();
2323 return 1;
2324 } else {
2327
2328 $this->db->rollback();
2329 return -1;
2330 }
2331 }
2332
2342 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2343 {
2344 global $langs;
2345
2346 $error = 0;
2347
2348 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2349
2350 $langs->load("agenda");
2351
2352 // Loop on each product line to add a stock movement
2353 $sql = "SELECT cd.fk_product, cd.subprice,";
2354 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2355 $sql .= " e.ref,";
2356 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2357 $sql .= " cd.rowid as cdid, ed.rowid as edid";
2358 $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2359 $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2360 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2361 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2362 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2363 $sql .= " AND cd.rowid = ed.fk_elementdet";
2364
2365 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2366 $resql = $this->db->query($sql);
2367 if ($resql) {
2368 $cpt = $this->db->num_rows($resql);
2369 for ($i = 0; $i < $cpt; $i++) {
2370 $obj = $this->db->fetch_object($resql);
2371 if (empty($obj->edbrowid)) {
2372 $qty = $obj->qty;
2373 } else {
2374 $qty = $obj->edbqty;
2375 }
2376 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2377 continue;
2378 }
2379 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2380
2381 $mouvS = new MouvementStock($this->db);
2382 $mouvS->origin = &$this;
2383 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2384
2385 if (empty($obj->edbrowid)) {
2386 // line without batch detail
2387
2388 // 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
2389 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
2390 if ($result < 0) {
2391 $this->error = $mouvS->error;
2392 $this->errors = $mouvS->errors;
2393 $error++;
2394 break;
2395 }
2396 } else {
2397 // line with batch detail
2398
2399 // 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
2400 $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);
2401 if ($result < 0) {
2402 $this->error = $mouvS->error;
2403 $this->errors = $mouvS->errors;
2404 $error++;
2405 break;
2406 }
2407 }
2408
2409 // 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
2410 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2411 $sqldelete = "DELETE FROM ".MAIN_DB_PREFIX."product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM ".MAIN_DB_PREFIX."product_batch as pb)";
2412 $resqldelete = $this->db->query($sqldelete);
2413 // We do not test error, it can fails if there is child in batch details
2414 }
2415 } else {
2416 $this->error = $this->db->lasterror();
2417 $this->errors[] = $this->db->lasterror();
2418 $error++;
2419 }
2420
2421 if (!$error) {
2422 return 1;
2423 } else {
2424 return -1;
2425 }
2426 }
2427
2433 public function setBilled()
2434 {
2435 global $user;
2436 $error = 0;
2437
2438 $this->db->begin();
2439
2440 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET billed = 1';
2441 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2442
2443 $resql = $this->db->query($sql);
2444 if ($resql) {
2445 $this->billed = 1;
2446
2447 // Call trigger
2448 $result = $this->call_trigger('SHIPPING_BILLED', $user);
2449 if ($result < 0) {
2450 $this->billed = 0;
2451 $error++;
2452 }
2453 } else {
2454 $error++;
2455 $this->errors[] = $this->db->lasterror;
2456 }
2457
2458 if (empty($error)) {
2459 $this->db->commit();
2460 return 1;
2461 } else {
2462 $this->db->rollback();
2463 return -1;
2464 }
2465 }
2466
2474 public function setDraft($user, $notrigger = 0)
2475 {
2476 // Protection
2477 if ($this->statut <= self::STATUS_DRAFT) {
2478 return 0;
2479 }
2480
2481 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2482 }
2483
2489 public function reOpen()
2490 {
2491 global $langs, $user;
2492
2493 $error = 0;
2494
2495 // Protection. This avoid to move stock later when we should not
2496 if ($this->statut == self::STATUS_VALIDATED) {
2497 return 0;
2498 }
2499
2500 $this->db->begin();
2501
2502 $oldbilled = $this->billed;
2503
2504 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2505 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2506
2507 $resql = $this->db->query($sql);
2508 if ($resql) {
2511 $this->billed = 0;
2512
2513 // If stock increment is done on closing
2514 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2515 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2516
2517 $langs->load("agenda");
2518
2519 // Loop on each product line to add a stock movement
2520 // TODO possibilite d'expedier a partir d'une propale ou autre origine
2521 $sql = "SELECT cd.fk_product, cd.subprice,";
2522 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2523 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2524 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2525 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2526 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2527 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2528 $sql .= " AND cd.rowid = ed.fk_elementdet";
2529
2530 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2531 $resql = $this->db->query($sql);
2532 if ($resql) {
2533 $cpt = $this->db->num_rows($resql);
2534 for ($i = 0; $i < $cpt; $i++) {
2535 $obj = $this->db->fetch_object($resql);
2536 if (empty($obj->edbrowid)) {
2537 $qty = $obj->qty;
2538 } else {
2539 $qty = $obj->edbqty;
2540 }
2541 if ($qty <= 0) {
2542 continue;
2543 }
2544 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2545
2546 //var_dump($this->lines[$i]);
2547 $mouvS = new MouvementStock($this->db);
2548 $mouvS->origin = &$this;
2549 $mouvS->setOrigin($this->element, $this->id);
2550
2551 if (empty($obj->edbrowid)) {
2552 // line without batch detail
2553
2554 // 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
2555 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2556 if ($result < 0) {
2557 $this->error = $mouvS->error;
2558 $this->errors = $mouvS->errors;
2559 $error++;
2560 break;
2561 }
2562 } else {
2563 // line with batch detail
2564
2565 // 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
2566 $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);
2567 if ($result < 0) {
2568 $this->error = $mouvS->error;
2569 $this->errors = $mouvS->errors;
2570 $error++;
2571 break;
2572 }
2573 }
2574 }
2575 } else {
2576 $this->error = $this->db->lasterror();
2577 $error++;
2578 }
2579 }
2580
2581 if (!$error) {
2582 // Call trigger
2583 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2584 if ($result < 0) {
2585 $error++;
2586 }
2587 }
2588 } else {
2589 $error++;
2590 $this->errors[] = $this->db->lasterror();
2591 }
2592
2593 if (!$error) {
2594 $this->db->commit();
2595 return 1;
2596 } else {
2597 $this->statut = self::STATUS_CLOSED;
2598 $this->status = self::STATUS_CLOSED;
2599 $this->billed = $oldbilled;
2600 $this->db->rollback();
2601 return -1;
2602 }
2603 }
2604
2616 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2617 {
2618 $outputlangs->load("products");
2619
2620 if (!dol_strlen($modele)) {
2621 $modele = 'rouget';
2622
2623 if (!empty($this->model_pdf)) {
2624 $modele = $this->model_pdf;
2625 } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
2626 $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
2627 }
2628 }
2629
2630 $modelpath = "core/modules/expedition/doc/";
2631
2632 $this->fetch_origin();
2633
2634 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2635 }
2636
2645 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2646 {
2647 $tables = array(
2648 'expedition'
2649 );
2650
2651 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2652 }
2653}
2654
2655
2660{
2664 public $element = 'expeditiondet';
2665
2669 public $table_element = 'expeditiondet';
2670
2674 public $parent_element = 'expedition';
2675
2679 public $fk_parent_attribute = 'fk_expedition';
2680
2687 public $line_id; // deprecated
2688
2692 public $fk_element;
2693
2697 public $origin_id;
2698
2702 public $fk_elementdet;
2703
2707 public $origin_line_id;
2708
2712 public $element_type;
2713
2714
2721 public $fk_origin; // Example: 'orderline'
2722
2726 public $fk_expedition;
2727
2731 public $db;
2732
2736 public $qty;
2737
2741 public $qty_shipped;
2742
2746 public $fk_product;
2747
2748 // detail of lot and qty = array(id in llx_expeditiondet_batch, fk_expeditiondet, batch, qty, fk_origin_stock)
2749 // We can use this to know warehouse planned to be used for each lot.
2750 public $detail_batch;
2751
2752 // detail of warehouses and qty
2753 // We can use this to know warehouse when there is no lot.
2754 public $details_entrepot;
2755
2756
2760 public $entrepot_id;
2761
2762
2766 public $qty_asked;
2767
2772 public $ref;
2773
2777 public $product_ref;
2778
2783 public $libelle;
2784
2788 public $product_label;
2789
2795 public $desc;
2796
2800 public $product_desc;
2801
2806 public $product_type = 0;
2807
2811 public $rang;
2812
2816 public $weight;
2817 public $weight_units;
2818
2822 public $length;
2823 public $length_units;
2824
2828 public $width;
2829 public $width_units;
2830
2834 public $height;
2835 public $height_units;
2836
2840 public $surface;
2841 public $surface_units;
2842
2846 public $volume;
2847 public $volume_units;
2848
2849 // Invoicing
2850 public $remise_percent;
2851 public $tva_tx;
2852
2856 public $total_ht;
2857
2861 public $total_ttc;
2862
2866 public $total_tva;
2867
2871 public $total_localtax1;
2872
2876 public $total_localtax2;
2877
2878
2884 public function __construct($db)
2885 {
2886 $this->db = $db;
2887 }
2888
2895 public function fetch($rowid)
2896 {
2897 $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_elementdet, ed.element_type, ed.qty, ed.rang';
2898 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
2899 $sql .= ' WHERE ed.rowid = '.((int) $rowid);
2900 $result = $this->db->query($sql);
2901 if ($result) {
2902 $objp = $this->db->fetch_object($result);
2903 $this->id = $objp->rowid;
2904 $this->fk_expedition = $objp->fk_expedition;
2905 $this->entrepot_id = $objp->fk_entrepot;
2906 $this->fk_elementdet = $objp->fk_elementdet;
2907 $this->element_type = $objp->element_type;
2908 $this->qty = $objp->qty;
2909 $this->rang = $objp->rang;
2910
2911 $this->db->free($result);
2912
2913 return 1;
2914 } else {
2915 $this->errors[] = $this->db->lasterror();
2916 $this->error = $this->db->lasterror();
2917 return -1;
2918 }
2919 }
2920
2928 public function insert($user, $notrigger = 0)
2929 {
2930 $error = 0;
2931
2932 // Check parameters
2933 if (empty($this->fk_expedition) || empty($this->fk_elementdet) || !is_numeric($this->qty)) {
2934 $this->error = 'ErrorMandatoryParametersNotProvided';
2935 return -1;
2936 }
2937
2938 $this->db->begin();
2939
2940 if (empty($this->rang)) {
2941 $this->rang = 0;
2942 }
2943
2944 // Rank to use
2945 $ranktouse = $this->rang;
2946 if ($ranktouse == -1) {
2947 $rangmax = $this->line_max($this->fk_expedition);
2948 $ranktouse = $rangmax + 1;
2949 }
2950
2951 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
2952 $sql .= "fk_expedition";
2953 $sql .= ", fk_entrepot";
2954 $sql .= ", fk_elementdet";
2955 $sql .= ", element_type";
2956 $sql .= ", qty";
2957 $sql .= ", rang";
2958 $sql .= ") VALUES (";
2959 $sql .= $this->fk_expedition;
2960 $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
2961 $sql .= ", ".((int) $this->fk_elementdet);
2962 $sql .= ", '".(empty($this->element_type) ? 'order' : $this->db->escape($this->element_type))."'";
2963 $sql .= ", ".price2num($this->qty, 'MS');
2964 $sql .= ", ".((int) $ranktouse);
2965 $sql .= ")";
2966
2967 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
2968 $resql = $this->db->query($sql);
2969 if ($resql) {
2970 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
2971
2972 if (!$error) {
2973 $result = $this->insertExtraFields();
2974 if ($result < 0) {
2975 $error++;
2976 }
2977 }
2978
2979 if (!$error && !$notrigger) {
2980 // Call trigger
2981 $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
2982 if ($result < 0) {
2983 $error++;
2984 }
2985 // End call triggers
2986 }
2987
2988 if ($error) {
2989 foreach ($this->errors as $errmsg) {
2990 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2991 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2992 }
2993 }
2994 } else {
2995 $error++;
2996 }
2997
2998 if ($error) {
2999 $this->db->rollback();
3000 return -1;
3001 } else {
3002 $this->db->commit();
3003 return $this->id;
3004 }
3005 }
3006
3014 public function delete($user = null, $notrigger = 0)
3015 {
3016 $error = 0;
3017
3018 $this->db->begin();
3019
3020 // delete batch expedition line
3021 if (isModEnabled('productbatch')) {
3022 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
3023 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
3024
3025 if (!$this->db->query($sql)) {
3026 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3027 $error++;
3028 }
3029 }
3030
3031 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
3032 $sql .= " WHERE rowid = ".((int) $this->id);
3033
3034 if (!$error && $this->db->query($sql)) {
3035 // Remove extrafields
3036 if (!$error) {
3037 $result = $this->deleteExtraFields();
3038 if ($result < 0) {
3039 $this->errors[] = $this->error;
3040 $error++;
3041 }
3042 }
3043 if (!$error && !$notrigger) {
3044 // Call trigger
3045 $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
3046 if ($result < 0) {
3047 $this->errors[] = $this->error;
3048 $error++;
3049 }
3050 // End call triggers
3051 }
3052 } else {
3053 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3054 $error++;
3055 }
3056
3057 if (!$error) {
3058 $this->db->commit();
3059 return 1;
3060 } else {
3061 foreach ($this->errors as $errmsg) {
3062 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
3063 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3064 }
3065 $this->db->rollback();
3066 return -1 * $error;
3067 }
3068 }
3069
3077 public function update($user = null, $notrigger = 0)
3078 {
3079 $error = 0;
3080
3081 dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
3082
3083 $this->db->begin();
3084
3085 // Clean parameters
3086 if (empty($this->qty)) {
3087 $this->qty = 0;
3088 }
3089 $qty = price2num($this->qty);
3090 $remainingQty = 0;
3091 $batch = null;
3092 $batch_id = null;
3093 $expedition_batch_id = null;
3094 if (is_array($this->detail_batch)) { // array of ExpeditionLineBatch
3095 if (count($this->detail_batch) > 1) {
3096 dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
3097 $this->errors[] = 'ErrorBadParameters';
3098 $error++;
3099 } else {
3100 $batch = $this->detail_batch[0]->batch;
3101 $batch_id = $this->detail_batch[0]->fk_origin_stock;
3102 $expedition_batch_id = $this->detail_batch[0]->id;
3103 if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id) {
3104 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
3105 $this->errors[] = 'ErrorBadParameters';
3106 $error++;
3107 }
3108 $qty = price2num($this->detail_batch[0]->qty);
3109 }
3110 } elseif (!empty($this->detail_batch)) {
3111 $batch = $this->detail_batch->batch;
3112 $batch_id = $this->detail_batch->fk_origin_stock;
3113 $expedition_batch_id = $this->detail_batch->id;
3114 if ($this->entrepot_id != $this->detail_batch->entrepot_id) {
3115 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
3116 $this->errors[] = 'ErrorBadParameters';
3117 $error++;
3118 }
3119 $qty = price2num($this->detail_batch->qty);
3120 }
3121
3122 // check parameters
3123 if (!isset($this->id) || !isset($this->entrepot_id)) {
3124 dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
3125 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
3126 $error++;
3127 return -1;
3128 }
3129
3130 // update lot
3131
3132 if (!empty($batch) && isModEnabled('productbatch')) {
3133 $batch_id_str = $batch_id ?? 'null';
3134 dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id_str, batch=$batch");
3135
3136 if (empty($batch_id) || empty($this->fk_product)) {
3137 dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
3138 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
3139 $error++;
3140 }
3141
3142 // fetch remaining lot qty
3143 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
3144
3145 if (!$error && ($lotArray = $shipmentlinebatch->fetchAll($this->id)) < 0) {
3146 $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
3147 $error++;
3148 } else {
3149 // calculate new total line qty
3150 foreach ($lotArray as $lot) {
3151 if ($expedition_batch_id != $lot->id) {
3152 $remainingQty += $lot->qty;
3153 }
3154 }
3155 $qty += $remainingQty;
3156
3157 //fetch lot details
3158
3159 // fetch from product_lot
3160 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
3161 $lot = new Productlot($this->db);
3162 if ($lot->fetch(0, $this->fk_product, $batch) < 0) {
3163 $this->errors[] = $lot->errors;
3164 $error++;
3165 }
3166 if (!$error && !empty($expedition_batch_id)) {
3167 // delete lot expedition line
3168 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
3169 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
3170 $sql .= " AND rowid = ".((int) $expedition_batch_id);
3171
3172 if (!$this->db->query($sql)) {
3173 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3174 $error++;
3175 }
3176 }
3177 if (!$error && $this->detail_batch->qty > 0) {
3178 // create lot expedition line
3179 if (isset($lot->id)) {
3180 $shipmentLot = new ExpeditionLineBatch($this->db);
3181 $shipmentLot->batch = $lot->batch;
3182 $shipmentLot->eatby = $lot->eatby;
3183 $shipmentLot->sellby = $lot->sellby;
3184 $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id;
3185 $shipmentLot->qty = $this->detail_batch->qty;
3186 $shipmentLot->fk_origin_stock = $batch_id;
3187 if ($shipmentLot->create($this->id) < 0) {
3188 $this->errors = $shipmentLot->errors;
3189 $error++;
3190 }
3191 }
3192 }
3193 }
3194 }
3195 if (!$error) {
3196 // update line
3197 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3198 $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
3199 $sql .= " , qty = ".((float) price2num($qty, 'MS'));
3200 $sql .= " WHERE rowid = ".((int) $this->id);
3201
3202 if (!$this->db->query($sql)) {
3203 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3204 $error++;
3205 }
3206 }
3207
3208 if (!$error) {
3209 $result = $this->insertExtraFields();
3210 if ($result < 0) {
3211 $this->errors[] = $this->error;
3212 $error++;
3213 }
3214 }
3215
3216 if (!$error && !$notrigger) {
3217 // Call trigger
3218 $result = $this->call_trigger('LINESHIPPING_MODIFY', $user);
3219 if ($result < 0) {
3220 $this->errors[] = $this->error;
3221 $error++;
3222 }
3223 // End call triggers
3224 }
3225 if (!$error) {
3226 $this->db->commit();
3227 return 1;
3228 } else {
3229 foreach ($this->errors as $errmsg) {
3230 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3231 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3232 }
3233 $this->db->rollback();
3234 return -1 * $error;
3235 }
3236 }
3237}
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition security.php:637
$object ref
Definition info.php:79
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...
deleteEcmFiles($mode=0)
Delete related files of object in database.
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
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='fk_statut')
Set status of an object.
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).
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.
call_trigger($triggerName, $user)
Call trigger based on this instance.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage receptions.
Class to manage Dolibarr database access.
Class to manage shipments.
getNomUrl($withpicto=0, $option='', $max=0, $short=0, $notooltip=0, $save_lastsearch_value=-1)
Return clicable link of object (with eventually picto)
create_delivery($user)
Create a delivery receipt from a shipment.
const STATUS_NO_SIGNATURE
No signature.
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.
fetch_lines()
Load lines.
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...
addline($entrepot_id, $id, $qty, $array_options=[])
Add an expedition line.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
getTooltipContentArray($params)
getTooltipContentArray
setDeliveryDate($user, $delivery_date)
Set the planned delivery date.
create_line($entrepot_id, $origin_line_id, $qty, $rang=0, $array_options=[])
Create a expedition line.
getKanbanView($option='', $arraydata=null)
Return clicable link of object (with eventually picto)
const STATUS_DRAFT
Draft status.
const STATUS_CANCELED
Canceled status.
getLibStatut($mode=0)
Return status label.
set_date_livraison($user, $delivery_date)
Set delivery date.
initAsSpecimen()
Initialise an instance with random values.
addline_batch($dbatch, $array_options=[])
Add a shipment line with batch record.
getNextNumRef($soc)
Return next expedition ref.
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.
const STATUS_SIGNED
Signed status.
update($user=null, $notrigger=0)
Update database.
cancel($notrigger=0, $also_update_stock=false)
Cancel 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.
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.
fetch($rowid)
Load line expedition.
__construct($db)
Constructor.
insert($user, $notrigger=0)
Insert line into database.
update($user=null, $notrigger=0)
Update a line in database.
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 with list of lots and properties.
Class to manage third parties objects (customers, suppliers, prospects...)
trait CommonIncoterm
Superclass for incoterm classes.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
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_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:63
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
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 '.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
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 dolibarr global constant string value.
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller='', $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:88
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:141
publicphonebutton2 phonegreen basiclayout basiclayout TotalHT VATCode TotalVAT TotalLT1 TotalLT2 TotalTTC TotalHT clearboth nowraponall TAKEPOS_SHOW_SUBPRICE right right right takeposterminal SELECT e e e e e statut
Definition invoice.php:2037