dolibarr 21.0.0-alpha
expedition.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003-2008 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
5 * Copyright (C) 2006-2012 Laurent Destailleur <eldy@users.sourceforge.net>
6 * Copyright (C) 2011-2020 Juanjo Menent <jmenent@2byte.es>
7 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
8 * Copyright (C) 2014 Cedric GROSS <c.gross@kreiz-it.fr>
9 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
10 * Copyright (C) 2014-2017 Francis Appels <francis.appels@yahoo.com>
11 * Copyright (C) 2015 Claudio Aschieri <c.aschieri@19.coop>
12 * Copyright (C) 2016-2024 Ferran Marcet <fmarcet@2byte.es>
13 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2018-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_valid;
188
189 public $meths;
190 public $listmeths; // List of carriers
191
195 public $commande_id;
196
200 public $commande;
201
205 public $lines = array();
206
207 // Multicurrency
211 public $fk_multicurrency;
212
216 public $multicurrency_code;
217 public $multicurrency_tx;
218 public $multicurrency_total_ht;
219 public $multicurrency_total_tva;
220 public $multicurrency_total_ttc;
221
225 public $signed_status = 0;
226
230 const STATUS_DRAFT = 0;
231
239
246 const STATUS_CLOSED = 2;
247
251 const STATUS_CANCELED = -1;
252
261
262
267
271 const STATUS_SIGNED = 1;
272
273
274
280 public function __construct($db)
281 {
282 global $conf;
283
284 $this->db = $db;
285
286 $this->ismultientitymanaged = 1;
287 $this->isextrafieldmanaged = 1;
288
289 // List of long language codes for status
290 $this->labelStatus = array();
291 $this->labelStatus[-1] = 'StatusSendingCanceled';
292 $this->labelStatus[0] = 'StatusSendingDraft';
293 $this->labelStatus[1] = 'StatusSendingValidated';
294 $this->labelStatus[2] = 'StatusSendingProcessed';
295
296 // List of short language codes for status
297 $this->labelStatusShort = array();
298 $this->labelStatusShort[-1] = 'StatusSendingCanceledShort';
299 $this->labelStatusShort[0] = 'StatusSendingDraftShort';
300 $this->labelStatusShort[1] = 'StatusSendingValidatedShort';
301 $this->labelStatusShort[2] = 'StatusSendingProcessedShort';
302 }
303
310 public function getNextNumRef($soc)
311 {
312 global $langs, $conf;
313 $langs->load("sendings");
314
315 if (getDolGlobalString('EXPEDITION_ADDON_NUMBER')) {
316 $mybool = false;
317
318 $file = getDolGlobalString('EXPEDITION_ADDON_NUMBER') . ".php";
319 $classname = getDolGlobalString('EXPEDITION_ADDON_NUMBER');
320
321 // Include file with class
322 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
323
324 foreach ($dirmodels as $reldir) {
325 $dir = dol_buildpath($reldir."core/modules/expedition/");
326
327 // Load file with numbering class (if found)
328 $mybool = ((bool) @include_once $dir.$file) || $mybool;
329 }
330
331 if (!$mybool) {
332 dol_print_error(null, "Failed to include file ".$file);
333 return '';
334 }
335
336 $obj = new $classname();
337 $numref = "";
338 $numref = $obj->getNextValue($soc, $this);
339
340 if ($numref != "") {
341 return $numref;
342 } else {
343 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
344 return "";
345 }
346 } else {
347 print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
348 return "";
349 }
350 }
351
359 public function create($user, $notrigger = 0)
360 {
361 global $conf, $hookmanager;
362
363 $now = dol_now();
364
365 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
366 $error = 0;
367
368 // Clean parameters
369 $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
370 if (empty($this->fk_project)) {
371 $this->fk_project = 0;
372 }
373 if (empty($this->date_shipping) && !empty($this->date_expedition)) {
374 $this->date_shipping = $this->date_expedition;
375 }
376
377 $this->user = $user;
378
379 $this->db->begin();
380
381 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
382 $sql .= "ref";
383 $sql .= ", entity";
384 $sql .= ", ref_customer";
385 $sql .= ", ref_ext";
386 $sql .= ", date_creation";
387 $sql .= ", fk_user_author";
388 $sql .= ", date_expedition";
389 $sql .= ", date_delivery";
390 $sql .= ", fk_soc";
391 $sql .= ", fk_projet";
392 $sql .= ", fk_address";
393 $sql .= ", fk_shipping_method";
394 $sql .= ", tracking_number";
395 $sql .= ", weight";
396 $sql .= ", size";
397 $sql .= ", width";
398 $sql .= ", height";
399 $sql .= ", weight_units";
400 $sql .= ", size_units";
401 $sql .= ", note_private";
402 $sql .= ", note_public";
403 $sql .= ", model_pdf";
404 $sql .= ", fk_incoterms, location_incoterms";
405 $sql .= ") VALUES (";
406 $sql .= "'(PROV)'";
407 $sql .= ", ".((int) $conf->entity);
408 $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
409 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
410 $sql .= ", '".$this->db->idate($now)."'";
411 $sql .= ", ".((int) $user->id);
412 $sql .= ", ".($this->date_shipping > 0 ? "'".$this->db->idate($this->date_shipping)."'" : "null");
413 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
414 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
415 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
416 $sql .= ", ".($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
417 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
418 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
419 $sql .= ", ".(is_numeric($this->weight) ? $this->weight : 'NULL');
420 $sql .= ", ".(is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
421 $sql .= ", ".(is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
422 $sql .= ", ".(is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
423 $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
424 $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
425 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
426 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
427 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
428 $sql .= ", ".(int) $this->fk_incoterms;
429 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
430 $sql .= ")";
431
432 dol_syslog(get_class($this)."::create", LOG_DEBUG);
433 $resql = $this->db->query($sql);
434 if ($resql) {
435 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
436
437 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
438 $sql .= " SET ref = '(PROV".$this->id.")'";
439 $sql .= " WHERE rowid = ".((int) $this->id);
440
441 dol_syslog(get_class($this)."::create", LOG_DEBUG);
442 if ($this->db->query($sql)) {
443 // Insert of lines
444 $num = count($this->lines);
445 for ($i = 0; $i < $num; $i++) {
446 if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
447 if (!isset($this->lines[$i]->detail_batch)) { // no batch management
448 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) {
449 $error++;
450 }
451 } else { // with batch management
452 if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
453 $error++;
454 }
455 }
456 }
457 }
458
459 if (!$error && $this->id && $this->origin_id) {
460 $ret = $this->add_object_linked();
461 if (!$ret) {
462 $error++;
463 }
464 }
465
466 // Actions on extra fields
467 if (!$error) {
468 $result = $this->insertExtraFields();
469 if ($result < 0) {
470 $error++;
471 }
472 }
473
474 if (!$error && !$notrigger) {
475 // Call trigger
476 $result = $this->call_trigger('SHIPPING_CREATE', $user);
477 if ($result < 0) {
478 $error++;
479 }
480 // End call triggers
481
482 if (!$error) {
483 $this->db->commit();
484 return $this->id;
485 } else {
486 foreach ($this->errors as $errmsg) {
487 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
488 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
489 }
490 $this->db->rollback();
491 return -1 * $error;
492 }
493 } else {
494 $error++;
495 $this->db->rollback();
496 return -3;
497 }
498 } else {
499 $error++;
500 $this->error = $this->db->lasterror()." - sql=$sql";
501 $this->db->rollback();
502 return -2;
503 }
504 } else {
505 $error++;
506 $this->error = $this->db->error()." - sql=$sql";
507 $this->db->rollback();
508 return -1;
509 }
510 }
511
512 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
523 public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [])
524 {
525 //phpcs:enable
526 global $user;
527
528 $expeditionline = new ExpeditionLigne($this->db);
529 $expeditionline->fk_expedition = $this->id;
530 $expeditionline->entrepot_id = $entrepot_id;
531 $expeditionline->fk_elementdet = $origin_line_id;
532 $expeditionline->element_type = $this->origin;
533 $expeditionline->qty = $qty;
534 $expeditionline->rang = $rang;
535 $expeditionline->array_options = $array_options;
536
537 if (($lineId = $expeditionline->insert($user)) < 0) {
538 $this->errors[] = $expeditionline->error;
539 }
540 return $lineId;
541 }
542
543
544 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
552 public function create_line_batch($line_ext, $array_options = [])
553 {
554 // phpcs:enable
555 $error = 0;
556 $stockLocationQty = array(); // associated array with batch qty in stock location
557
558 $tab = $line_ext->detail_batch;
559 // create stockLocation Qty array
560 foreach ($tab as $detbatch) {
561 if (!empty($detbatch->entrepot_id)) {
562 if (empty($stockLocationQty[$detbatch->entrepot_id])) {
563 $stockLocationQty[$detbatch->entrepot_id] = 0;
564 }
565 $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
566 }
567 }
568 // create shipment lines
569 foreach ($stockLocationQty as $stockLocation => $qty) {
570 $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
571 if ($line_id < 0) {
572 $error++;
573 } else {
574 // create shipment batch lines for stockLocation
575 foreach ($tab as $detbatch) {
576 if ($detbatch->entrepot_id == $stockLocation) {
577 if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch
578 $this->errors = $detbatch->errors;
579 $error++;
580 }
581 }
582 }
583 }
584 }
585
586 if (!$error) {
587 return 1;
588 } else {
589 return -1;
590 }
591 }
592
602 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
603 {
604 global $conf;
605
606 // Check parameters
607 if (empty($id) && empty($ref) && empty($ref_ext)) {
608 return -1;
609 }
610
611 $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";
612 $sql .= ", e.date_valid";
613 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
614 $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
615 $sql .= ", e.fk_shipping_method, e.tracking_number";
616 $sql .= ", e.note_private, e.note_public";
617 $sql .= ', e.fk_incoterms, e.location_incoterms';
618 $sql .= ', e.signed_status';
619 $sql .= ', i.libelle as label_incoterms';
620 $sql .= ', s.libelle as shipping_method';
621 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin_type";
622 $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
623 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
624 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
625 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
626 $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
627 if ($id) {
628 $sql .= " AND e.rowid = ".((int) $id);
629 }
630 if ($ref) {
631 $sql .= " AND e.ref='".$this->db->escape($ref)."'";
632 }
633 if ($ref_ext) {
634 $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
635 }
636
637 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
638 $result = $this->db->query($sql);
639 if ($result) {
640 if ($this->db->num_rows($result)) {
641 $obj = $this->db->fetch_object($result);
642
643 $this->id = $obj->rowid;
644 $this->entity = $obj->entity;
645 $this->ref = $obj->ref;
646 $this->socid = $obj->socid;
647 $this->ref_customer = $obj->ref_customer;
648 $this->ref_ext = $obj->ref_ext;
649 $this->status = $obj->fk_statut;
650 $this->statut = $this->status; // Deprecated
651 $this->user_author_id = $obj->fk_user_author;
652 $this->fk_user_author = $obj->fk_user_author;
653 $this->date_creation = $this->db->jdate($obj->date_creation);
654 $this->date_valid = $this->db->jdate($obj->date_valid);
655 $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
656 $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
657 $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
658 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
659 $this->fk_delivery_address = $obj->fk_address;
660 $this->model_pdf = $obj->model_pdf;
661 $this->shipping_method_id = $obj->fk_shipping_method;
662 $this->shipping_method = $obj->shipping_method;
663 $this->tracking_number = $obj->tracking_number;
664 $this->origin = ($obj->origin_type ? $obj->origin_type : 'commande'); // For compatibility
665 $this->origin_type = ($obj->origin_type ? $obj->origin_type : 'commande');
666 $this->origin_id = $obj->origin_id;
667 $this->billed = $obj->billed;
668 $this->fk_project = $obj->fk_project;
669 $this->signed_status = $obj->signed_status;
670 $this->trueWeight = $obj->weight;
671 $this->weight_units = $obj->weight_units;
672
673 $this->trueWidth = $obj->width;
674 $this->width_units = $obj->size_units;
675 $this->trueHeight = $obj->height;
676 $this->height_units = $obj->size_units;
677 $this->trueDepth = $obj->size;
678 $this->depth_units = $obj->size_units;
679
680 $this->note_public = $obj->note_public;
681 $this->note_private = $obj->note_private;
682
683 // A denormalized value
684 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
685 $this->size_units = $obj->size_units;
686
687 //Incoterms
688 $this->fk_incoterms = $obj->fk_incoterms;
689 $this->location_incoterms = $obj->location_incoterms;
690 $this->label_incoterms = $obj->label_incoterms;
691
692 $this->db->free($result);
693
694 // Tracking url
695 $this->getUrlTrackingStatus($obj->tracking_number);
696
697 // Thirdparty
698 $result = $this->fetch_thirdparty(); // TODO Remove this
699
700 // Retrieve extrafields
701 $this->fetch_optionals();
702
703 // Fix Get multicurrency param for transmitted
704 if (isModEnabled('multicurrency')) {
705 if (!empty($this->multicurrency_code)) {
706 $this->multicurrency_code = $this->thirdparty->multicurrency_code;
707 }
708 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($this->thirdparty->multicurrency_tx)) {
709 $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
710 }
711 }
712
713 /*
714 * Lines
715 */
716 $result = $this->fetch_lines();
717 if ($result < 0) {
718 return -3;
719 }
720
721 return 1;
722 } else {
723 dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
724 $this->error = 'Shipment with id '.$id.' not found';
725 return 0;
726 }
727 } else {
728 $this->error = $this->db->error();
729 return -1;
730 }
731 }
732
740 public function valid($user, $notrigger = 0)
741 {
742 global $conf;
743
744 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
745
746 dol_syslog(get_class($this)."::valid");
747
748 // Protection
749 if ($this->status) {
750 dol_syslog(get_class($this)."::valid not in draft status", LOG_WARNING);
751 return 0;
752 }
753
754 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'creer'))
755 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'shipping_advance', 'validate')))) {
756 $this->error = 'Permission denied';
757 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
758 return -1;
759 }
760
761 $this->db->begin();
762
763 $error = 0;
764
765 // Define new ref
766 $soc = new Societe($this->db);
767 $soc->fetch($this->socid);
768
769 // Class of company linked to order
770 $result = $soc->setAsCustomer();
771
772 // Define new ref
773 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
774 $numref = $this->getNextNumRef($soc);
775 } elseif (!empty($this->ref)) {
776 $numref = $this->ref;
777 } else {
778 $numref = "EXP".$this->id;
779 }
780 $this->newref = dol_sanitizeFileName($numref);
781
782 $now = dol_now();
783
784 // Validate
785 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
786 $sql .= " ref='".$this->db->escape($numref)."'";
787 $sql .= ", fk_statut = 1";
788 $sql .= ", date_valid = '".$this->db->idate($now)."'";
789 $sql .= ", fk_user_valid = ".$user->id;
790 $sql .= " WHERE rowid = ".((int) $this->id);
791
792 dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
793 $resql = $this->db->query($sql);
794 if (!$resql) {
795 $this->error = $this->db->lasterror();
796 $error++;
797 }
798
799 // If stock increment is done on sending (recommended choice)
800 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
801 $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
802 if ($result < 0) {
803 return -2;
804 }
805 }
806
807 // Change status of order to "shipment in process"
808 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
809 if (!$ret) {
810 $error++;
811 }
812
813 if (!$error && !$notrigger) {
814 // Call trigger
815 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
816 if ($result < 0) {
817 $error++;
818 }
819 // End call triggers
820 }
821
822 if (!$error) {
823 $this->oldref = $this->ref;
824
825 // Rename directory if dir was a temporary ref
826 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
827 // Now we rename also files into index
828 $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)."'";
829 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
830 $resql = $this->db->query($sql);
831 if (!$resql) {
832 $error++;
833 $this->error = $this->db->lasterror();
834 }
835 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
836 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
837 $resql = $this->db->query($sql);
838 if (!$resql) {
839 $error++;
840 $this->error = $this->db->lasterror();
841 }
842
843 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
844 $oldref = dol_sanitizeFileName($this->ref);
845 $newref = dol_sanitizeFileName($numref);
846 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
847 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
848 if (!$error && file_exists($dirsource)) {
849 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
850
851 if (@rename($dirsource, $dirdest)) {
852 dol_syslog("Rename ok");
853 // Rename docs starting with $oldref with $newref
854 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
855 foreach ($listoffiles as $fileentry) {
856 $dirsource = $fileentry['name'];
857 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
858 $dirsource = $fileentry['path'].'/'.$dirsource;
859 $dirdest = $fileentry['path'].'/'.$dirdest;
860 @rename($dirsource, $dirdest);
861 }
862 }
863 }
864 }
865 }
866
867 // Set new ref and current status
868 if (!$error) {
869 $this->ref = $numref;
872 }
873
874 if (!$error) {
875 $this->db->commit();
876 return 1;
877 } else {
878 $this->db->rollback();
879 return -1 * $error;
880 }
881 }
882
883
884 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
891 public function create_delivery($user)
892 {
893 // phpcs:enable
894 global $conf;
895
896 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
897 if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
898 // Expedition validee
899 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
900 $delivery = new Delivery($this->db);
901 $result = $delivery->create_from_sending($user, $this->id);
902 if ($result > 0) {
903 return $result;
904 } else {
905 $this->error = $delivery->error;
906 return $result;
907 }
908 } else {
909 return 0;
910 }
911 } else {
912 return 0;
913 }
914 }
915
928 public function addline($entrepot_id, $id, $qty, $array_options = [])
929 {
930 global $conf, $langs;
931
932 $num = count($this->lines);
933 $line = new ExpeditionLigne($this->db);
934
935 $line->entrepot_id = $entrepot_id;
936 $line->origin_line_id = $id;
937 $line->fk_elementdet = $id;
938 $line->element_type = 'order';
939 $line->qty = $qty;
940
941 $orderline = new OrderLine($this->db);
942 $orderline->fetch($id);
943
944 // Copy the rang of the order line to the expedition line
945 $line->rang = $orderline->rang;
946 $line->product_type = $orderline->product_type;
947
948 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
949 $fk_product = $orderline->fk_product;
950
951 if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS') && !(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') && $line->product_type == Product::TYPE_SERVICE)) {
952 $langs->load("errors");
953 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
954 return -1;
955 }
956
957 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
958 $product = new Product($this->db);
959 $product->fetch($fk_product);
960
961 // Check must be done for stock of product into warehouse if $entrepot_id defined
962 if ($entrepot_id > 0) {
963 $product->load_stock('warehouseopen');
964 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
965 } else {
966 $product_stock = $product->stock_reel;
967 }
968
969 $product_type = $product->type;
970 if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
971 $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
972 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
973 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.
974 if ($product_stock < $qty) {
975 $langs->load("errors");
976 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
977 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
978
979 $this->db->rollback();
980 return -3;
981 }
982 }
983 }
984 }
985 }
986
987 // If product need a batch number, we should not have called this function but addline_batch instead.
988 // If this happen, we may have a bug in card.php page
989 if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
990 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; //
991 return -4;
992 }
993
994 // extrafields
995 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
996 $line->array_options = $array_options;
997 }
998
999 $this->lines[$num] = $line;
1000
1001 return 1;
1002 }
1003
1004 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1012 public function addline_batch($dbatch, $array_options = [])
1013 {
1014 // phpcs:enable
1015 global $conf, $langs;
1016
1017 $num = count($this->lines);
1018 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1019 $line = new ExpeditionLigne($this->db);
1020 $tab = array();
1021 foreach ($dbatch['detail'] as $key => $value) {
1022 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1023 // $value['q']=qty to move
1024 // $value['id_batch']=id into llx_product_batch of record to move
1025 //var_dump($value);
1026
1027 $linebatch = new ExpeditionLineBatch($this->db);
1028 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1029 if ($ret < 0) {
1030 $this->setErrorsFromObject($linebatch);
1031 return -1;
1032 }
1033 $linebatch->qty = $value['q'];
1034 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1035 $linebatch->batch = null;
1036 }
1037 $tab[] = $linebatch;
1038
1039 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1040 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1041 $prod_batch = new Productbatch($this->db);
1042 $prod_batch->fetch($value['id_batch']);
1043
1044 if ($prod_batch->qty < $linebatch->qty) {
1045 $langs->load("errors");
1046 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1047 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1048 $this->db->rollback();
1049 return -1;
1050 }
1051 }
1052
1053 //var_dump($linebatch);
1054 }
1055 }
1056 $line->entrepot_id = $linebatch->entrepot_id;
1057 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1058 $line->fk_elementdet = $dbatch['ix_l'];
1059 $line->qty = $dbatch['qty'];
1060 $line->detail_batch = $tab;
1061
1062 // extrafields
1063 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1064 $line->array_options = $array_options;
1065 }
1066
1067 //var_dump($line);
1068 $this->lines[$num] = $line;
1069 return 1;
1070 }
1071 return 0;
1072 }
1073
1081 public function update($user = null, $notrigger = 0)
1082 {
1083 global $conf;
1084 $error = 0;
1085
1086 // Clean parameters
1087
1088 if (isset($this->ref)) {
1089 $this->ref = trim($this->ref);
1090 }
1091 if (isset($this->entity)) {
1092 $this->entity = (int) $this->entity;
1093 }
1094 if (isset($this->ref_customer)) {
1095 $this->ref_customer = trim($this->ref_customer);
1096 }
1097 if (isset($this->socid)) {
1098 $this->socid = (int) $this->socid;
1099 }
1100 if (isset($this->fk_user_author)) {
1101 $this->fk_user_author = (int) $this->fk_user_author;
1102 }
1103 if (isset($this->fk_user_valid)) {
1104 $this->fk_user_valid = (int) $this->fk_user_valid;
1105 }
1106 if (isset($this->fk_delivery_address)) {
1107 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1108 }
1109 if (isset($this->shipping_method_id)) {
1110 $this->shipping_method_id = (int) $this->shipping_method_id;
1111 }
1112 if (isset($this->tracking_number)) {
1113 $this->tracking_number = trim($this->tracking_number);
1114 }
1115 if (isset($this->statut)) {
1116 $this->statut = (int) $this->statut;
1117 }
1118 if (isset($this->trueDepth)) {
1119 $this->trueDepth = trim($this->trueDepth);
1120 }
1121 if (isset($this->trueWidth)) {
1122 $this->trueWidth = trim($this->trueWidth);
1123 }
1124 if (isset($this->trueHeight)) {
1125 $this->trueHeight = trim($this->trueHeight);
1126 }
1127 if (isset($this->size_units)) {
1128 $this->size_units = trim($this->size_units);
1129 }
1130 if (isset($this->weight_units)) {
1131 $this->weight_units = trim($this->weight_units);
1132 }
1133 if (isset($this->trueWeight)) {
1134 $this->weight = trim((string) $this->trueWeight);
1135 }
1136 if (isset($this->note_private)) {
1137 $this->note_private = trim($this->note_private);
1138 }
1139 if (isset($this->note_public)) {
1140 $this->note_public = trim($this->note_public);
1141 }
1142 if (isset($this->model_pdf)) {
1143 $this->model_pdf = trim($this->model_pdf);
1144 }
1145
1146 // Check parameters
1147 // Put here code to add control on parameters values
1148
1149 // Update request
1150 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1151 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1152 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1153 $sql .= " ref_customer=".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1154 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1155 $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1156 $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1157 $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1158 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1159 $sql .= " date_expedition=".(dol_strlen($this->date_expedition) != 0 ? "'".$this->db->idate($this->date_expedition)."'" : 'null').",";
1160 $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1161 $sql .= " fk_address=".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1162 $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1163 $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1164 $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1165 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1166 $sql .= " height=".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1167 $sql .= " width=".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1168 $sql .= " size_units=".(isset($this->size_units) ? $this->size_units : "null").",";
1169 $sql .= " size=".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1170 $sql .= " weight_units=".(isset($this->weight_units) ? $this->weight_units : "null").",";
1171 $sql .= " weight=".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1172 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1173 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1174 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1175 $sql .= " entity=".$conf->entity;
1176 $sql .= " WHERE rowid=".((int) $this->id);
1177
1178 $this->db->begin();
1179
1180 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1181 $resql = $this->db->query($sql);
1182 if (!$resql) {
1183 $error++;
1184 $this->errors[] = "Error ".$this->db->lasterror();
1185 }
1186
1187 if (!$error && !$notrigger) {
1188 // Call trigger
1189 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1190 if ($result < 0) {
1191 $error++;
1192 }
1193 // End call triggers
1194 }
1195
1196 // Commit or rollback
1197 if ($error) {
1198 foreach ($this->errors as $errmsg) {
1199 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1200 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1201 }
1202 $this->db->rollback();
1203 return -1 * $error;
1204 } else {
1205 $this->db->commit();
1206 return 1;
1207 }
1208 }
1209
1210
1218 public function cancel($notrigger = 0, $also_update_stock = false)
1219 {
1220 global $conf, $langs, $user;
1221
1222 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1223
1224 $error = 0;
1225 $this->error = '';
1226
1227 $this->db->begin();
1228
1229 // Add a protection to refuse deleting if shipment has at least one delivery
1230 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1231 if (count($this->linkedObjectsIds) > 0) {
1232 $this->error = 'ErrorThereIsSomeDeliveries';
1233 $error++;
1234 }
1235
1236 if (!$error && !$notrigger) {
1237 // Call trigger
1238 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1239 if ($result < 0) {
1240 $error++;
1241 }
1242 // End call triggers
1243 }
1244
1245 // Stock control
1246 if (!$error && isModEnabled('stock') &&
1247 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1248 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1249 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1250
1251 $langs->load("agenda");
1252
1253 // Loop on each product line to add a stock movement and delete features
1254 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1255 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1256 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1257 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1258 $sql .= " AND cd.rowid = ed.fk_elementdet";
1259
1260 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1261 $resql = $this->db->query($sql);
1262 if ($resql) {
1263 $cpt = $this->db->num_rows($resql);
1264
1265 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1266
1267 for ($i = 0; $i < $cpt; $i++) {
1268 dol_syslog(get_class($this)."::delete movement index ".$i);
1269 $obj = $this->db->fetch_object($resql);
1270
1271 $mouvS = new MouvementStock($this->db);
1272 // we do not log origin because it will be deleted
1273 $mouvS->origin = '';
1274 // get lot/serial
1275 $lotArray = null;
1276 if (isModEnabled('productbatch')) {
1277 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1278 if (!is_array($lotArray)) {
1279 $error++;
1280 $this->errors[] = "Error ".$this->db->lasterror();
1281 }
1282 }
1283
1284 if (empty($lotArray)) {
1285 // no lot/serial
1286 // We increment stock of product (and sub-products)
1287 // We use warehouse selected for each line
1288 $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
1289 if ($result < 0) {
1290 $error++;
1291 $this->errors = array_merge($this->errors, $mouvS->errors);
1292 break;
1293 }
1294 } else {
1295 // We increment stock of batches
1296 // We use warehouse selected for each line
1297 foreach ($lotArray as $lot) {
1298 $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
1299 if ($result < 0) {
1300 $error++;
1301 $this->errors = array_merge($this->errors, $mouvS->errors);
1302 break;
1303 }
1304 }
1305 if ($error) {
1306 break; // break for loop in case of error
1307 }
1308 }
1309 }
1310 } else {
1311 $error++;
1312 $this->errors[] = "Error ".$this->db->lasterror();
1313 }
1314 }
1315
1316 // delete batch expedition line
1317 if (!$error && isModEnabled('productbatch')) {
1318 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1319 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1320 $error++;
1321 $this->errors[] = "Error ".$this->db->lasterror();
1322 }
1323 }
1324
1325
1326 if (!$error) {
1327 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1328 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1329
1330 if ($this->db->query($sql)) {
1331 // Delete linked object
1332 $res = $this->deleteObjectLinked();
1333 if ($res < 0) {
1334 $error++;
1335 }
1336
1337 // No delete expedition
1338 if (!$error) {
1339 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1340 $sql .= " WHERE rowid = ".((int) $this->id);
1341
1342 if ($this->db->query($sql)) {
1343 if (!empty($this->origin) && $this->origin_id > 0) {
1344 $this->fetch_origin();
1345 if ($this->origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1346 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1347 $this->origin_object->loadExpeditions();
1348 //var_dump($this->$origin->expeditions);exit;
1349 if (count($this->origin_object->expeditions) <= 0) {
1350 $this->origin_object->setStatut(Commande::STATUS_VALIDATED);
1351 }
1352 }
1353 }
1354
1355 if (!$error) {
1356 $this->db->commit();
1357
1358 // We delete PDFs
1359 $ref = dol_sanitizeFileName($this->ref);
1360 if (!empty($conf->expedition->dir_output)) {
1361 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1362 $file = $dir.'/'.$ref.'.pdf';
1363 if (file_exists($file)) {
1364 if (!dol_delete_file($file)) {
1365 return 0;
1366 }
1367 }
1368 if (file_exists($dir)) {
1369 if (!dol_delete_dir_recursive($dir)) {
1370 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1371 return 0;
1372 }
1373 }
1374 }
1375
1376 return 1;
1377 } else {
1378 $this->db->rollback();
1379 return -1;
1380 }
1381 } else {
1382 $this->error = $this->db->lasterror()." - sql=$sql";
1383 $this->db->rollback();
1384 return -3;
1385 }
1386 } else {
1387 $this->error = $this->db->lasterror()." - sql=$sql";
1388 $this->db->rollback();
1389 return -2;
1390 }//*/
1391 } else {
1392 $this->error = $this->db->lasterror()." - sql=$sql";
1393 $this->db->rollback();
1394 return -1;
1395 }
1396 } else {
1397 $this->db->rollback();
1398 return -1;
1399 }
1400 }
1401
1411 public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1412 {
1413 global $conf, $langs;
1414
1415 if (empty($user)) {
1416 global $user;
1417 }
1418
1419 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1420
1421 $error = 0;
1422 $this->error = '';
1423
1424 $this->db->begin();
1425
1426 // Add a protection to refuse deleting if shipment has at least one delivery
1427 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1428 if (count($this->linkedObjectsIds) > 0) {
1429 $this->error = 'ErrorThereIsSomeDeliveries';
1430 $error++;
1431 }
1432
1433 if (!$error && !$notrigger) {
1434 // Call trigger
1435 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1436 if ($result < 0) {
1437 $error++;
1438 }
1439 // End call triggers
1440 }
1441
1442 // Stock control
1443 if (!$error && isModEnabled('stock') &&
1444 ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
1445 (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1446 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1447
1448 $langs->load("agenda");
1449
1450 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1451 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1452
1453 // Loop on each product line to add a stock movement
1454 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1455 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1456 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1457 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1458 $sql .= " AND cd.rowid = ed.fk_elementdet";
1459
1460 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1461 $resql = $this->db->query($sql);
1462 if ($resql) {
1463 $cpt = $this->db->num_rows($resql);
1464 for ($i = 0; $i < $cpt; $i++) {
1465 dol_syslog(get_class($this)."::delete movement index ".$i);
1466 $obj = $this->db->fetch_object($resql);
1467
1468 $mouvS = new MouvementStock($this->db);
1469 // we do not log origin because it will be deleted
1470 $mouvS->origin = '';
1471 // get lot/serial
1472 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1473 if (!is_array($lotArray)) {
1474 $error++;
1475 $this->errors[] = "Error ".$this->db->lasterror();
1476 }
1477 if (empty($lotArray)) {
1478 // no lot/serial
1479 // We increment stock of product (and sub-products)
1480 // We use warehouse selected for each line
1481 $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
1482 if ($result < 0) {
1483 $error++;
1484 $this->errors = array_merge($this->errors, $mouvS->errors);
1485 break;
1486 }
1487 } else {
1488 // We increment stock of batches
1489 // We use warehouse selected for each line
1490 foreach ($lotArray as $lot) {
1491 $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
1492 if ($result < 0) {
1493 $error++;
1494 $this->errors = array_merge($this->errors, $mouvS->errors);
1495 break;
1496 }
1497 }
1498 if ($error) {
1499 break; // break for loop in case of error
1500 }
1501 }
1502 }
1503 } else {
1504 $error++;
1505 $this->errors[] = "Error ".$this->db->lasterror();
1506 }
1507 }
1508
1509 // delete batch expedition line
1510 if (!$error) {
1511 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1512 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1513 $error++;
1514 $this->errors[] = "Error ".$this->db->lasterror();
1515 }
1516 }
1517
1518 if (!$error) {
1519 $main = MAIN_DB_PREFIX.'expeditiondet';
1520 $ef = $main."_extrafields";
1521 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
1522
1523 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1524 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1525
1526 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1527 // Delete linked object
1528 $res = $this->deleteObjectLinked();
1529 if ($res < 0) {
1530 $error++;
1531 }
1532
1533 // delete extrafields
1534 $res = $this->deleteExtraFields();
1535 if ($res < 0) {
1536 $error++;
1537 }
1538
1539 if (!$error) {
1540 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1541 $sql .= " WHERE rowid = ".((int) $this->id);
1542
1543 if ($this->db->query($sql)) {
1544 if (!empty($this->origin) && $this->origin_id > 0) {
1545 $this->fetch_origin();
1546 if ($this->origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1547 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1548 $this->origin_object->loadExpeditions();
1549 //var_dump($this->$origin->expeditions);exit;
1550 if (count($this->origin_object->expeditions) <= 0) {
1551 $this->origin_object->setStatut(Commande::STATUS_VALIDATED);
1552 }
1553 }
1554 }
1555
1556 if (!$error) {
1557 $this->db->commit();
1558
1559 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1560 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1561 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1562
1563 // We delete PDFs
1564 $ref = dol_sanitizeFileName($this->ref);
1565 if (!empty($conf->expedition->dir_output)) {
1566 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1567 $file = $dir.'/'.$ref.'.pdf';
1568 if (file_exists($file)) {
1569 if (!dol_delete_file($file)) {
1570 return 0;
1571 }
1572 }
1573 if (file_exists($dir)) {
1574 if (!dol_delete_dir_recursive($dir)) {
1575 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1576 return 0;
1577 }
1578 }
1579 }
1580
1581 return 1;
1582 } else {
1583 $this->db->rollback();
1584 return -1;
1585 }
1586 } else {
1587 $this->error = $this->db->lasterror()." - sql=$sql";
1588 $this->db->rollback();
1589 return -3;
1590 }
1591 } else {
1592 $this->error = $this->db->lasterror()." - sql=$sql";
1593 $this->db->rollback();
1594 return -2;
1595 }
1596 } else {
1597 $this->error = $this->db->lasterror()." - sql=$sql";
1598 $this->db->rollback();
1599 return -1;
1600 }
1601 } else {
1602 $this->db->rollback();
1603 return -1;
1604 }
1605 }
1606
1607 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1613 public function fetch_lines()
1614 {
1615 // phpcs:enable
1616 global $mysoc;
1617
1618 $this->lines = array();
1619
1620 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1621 // TODO: See if we can restore a common fetch_lines (one line = one record)
1622
1623 $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";
1624 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1625 $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
1626 $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";
1627 $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";
1628 $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot";
1629 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
1630 $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";
1631 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1632 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1633 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1634 $sql .= " AND ed.fk_elementdet = cd.rowid";
1635 $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.
1636
1637 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1638 $resql = $this->db->query($sql);
1639 if ($resql) {
1640 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1641
1642 $num = $this->db->num_rows($resql);
1643 $i = 0;
1644 $lineindex = 0;
1645 $originline = 0;
1646
1647 $this->total_ht = 0;
1648 $this->total_tva = 0;
1649 $this->total_ttc = 0;
1650 $this->total_localtax1 = 0;
1651 $this->total_localtax2 = 0;
1652
1653 $this->multicurrency_total_ht = 0;
1654 $this->multicurrency_total_tva = 0;
1655 $this->multicurrency_total_ttc = 0;
1656
1657 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1658
1659 while ($i < $num) {
1660 $obj = $this->db->fetch_object($resql);
1661
1662
1663 if ($originline > 0 && $originline == $obj->fk_elementdet) {
1664 '@phan-var-force ExpeditionLigne $line'; // $line from previous loop
1665 $line->entrepot_id = 0; // entrepod_id in details_entrepot
1666 $line->qty_shipped += $obj->qty_shipped;
1667 } else {
1668 $line = new ExpeditionLigne($this->db); // new group to start
1669 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
1670 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
1671 }
1672
1673 $detail_entrepot = new stdClass();
1674 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1675 $detail_entrepot->qty_shipped = $obj->qty_shipped;
1676 $detail_entrepot->line_id = $obj->line_id;
1677 $line->details_entrepot[] = $detail_entrepot;
1678
1679 $line->line_id = $obj->line_id; // TODO deprecated
1680 $line->rowid = $obj->line_id; // TODO deprecated
1681 $line->id = $obj->line_id;
1682
1683 $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
1684
1685 $line->fk_element = $obj->fk_element;
1686 $line->origin_id = $obj->fk_element;
1687 $line->fk_elementdet = $obj->fk_elementdet;
1688 $line->origin_line_id = $obj->fk_elementdet;
1689 $line->element_type = $obj->element_type;
1690
1691 $line->fk_expedition = $this->id; // id of parent
1692
1693 $line->product_type = $obj->product_type;
1694 $line->fk_product = $obj->fk_product;
1695 $line->fk_product_type = $obj->fk_product_type;
1696 $line->ref = $obj->product_ref; // TODO deprecated
1697 $line->product_ref = $obj->product_ref;
1698 $line->product_label = $obj->product_label;
1699 $line->libelle = $obj->product_label; // TODO deprecated
1700 $line->product_barcode = $obj->product_barcode; // Barcode number product
1701 $line->product_tosell = $obj->product_tosell;
1702 $line->product_tobuy = $obj->product_tobuy;
1703 $line->product_tobatch = $obj->product_tobatch;
1704 $line->fk_fournprice = $obj->fk_fournprice;
1705 $line->label = $obj->custom_label;
1706 $line->description = $obj->description;
1707 $line->qty_asked = $obj->qty_asked;
1708 $line->rang = $obj->rang;
1709 $line->weight = $obj->weight;
1710 $line->weight_units = $obj->weight_units;
1711 $line->length = $obj->length;
1712 $line->length_units = $obj->length_units;
1713 $line->width = $obj->width;
1714 $line->width_units = $obj->width_units;
1715 $line->height = $obj->height;
1716 $line->height_units = $obj->height_units;
1717 $line->surface = $obj->surface;
1718 $line->surface_units = $obj->surface_units;
1719 $line->volume = $obj->volume;
1720 $line->volume_units = $obj->volume_units;
1721 $line->fk_unit = $obj->fk_unit;
1722
1723 $line->pa_ht = $obj->pa_ht;
1724
1725 // Local taxes
1726 $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
1727 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1728 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1729
1730 // For invoicing
1731 $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
1732 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1733 $line->qty = $line->qty_shipped;
1734 $line->total_ht = $tabprice[0];
1735 $line->total_localtax1 = $tabprice[9];
1736 $line->total_localtax2 = $tabprice[10];
1737 $line->total_ttc = $tabprice[2];
1738 $line->total_tva = $tabprice[1];
1739 $line->vat_src_code = $obj->vat_src_code;
1740 $line->tva_tx = $obj->tva_tx;
1741 $line->localtax1_tx = $obj->localtax1_tx;
1742 $line->localtax2_tx = $obj->localtax2_tx;
1743 $line->info_bits = $obj->info_bits;
1744 $line->price = $obj->price;
1745 $line->subprice = $obj->subprice;
1746 $line->fk_remise_except = $obj->fk_remise_except;
1747 $line->remise_percent = $obj->remise_percent;
1748
1749 $this->total_ht += $tabprice[0];
1750 $this->total_tva += $tabprice[1];
1751 $this->total_ttc += $tabprice[2];
1752 $this->total_localtax1 += $tabprice[9];
1753 $this->total_localtax2 += $tabprice[10];
1754
1755 $line->date_start = $this->db->jdate($obj->date_start);
1756 $line->date_end = $this->db->jdate($obj->date_end);
1757
1758 // Multicurrency
1759 $this->fk_multicurrency = $obj->fk_multicurrency;
1760 $this->multicurrency_code = $obj->multicurrency_code;
1761 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1762 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1763 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1764 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1765
1766 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
1767 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
1768 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
1769
1770 if ($originline != $obj->fk_elementdet) {
1771 $line->detail_batch = array();
1772 }
1773
1774 // Detail of batch
1775 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1776 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1777
1778 if (is_array($newdetailbatch)) {
1779 if ($originline != $obj->fk_elementdet) {
1780 $line->detail_batch = $newdetailbatch;
1781 } else {
1782 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1783 }
1784 }
1785 }
1786
1787 $line->fetch_optionals();
1788
1789 if ($originline != $obj->fk_elementdet) {
1790 $this->lines[$lineindex] = $line;
1791 $lineindex++;
1792 } else {
1793 $line->total_ht += $tabprice[0];
1794 $line->total_localtax1 += $tabprice[9];
1795 $line->total_localtax2 += $tabprice[10];
1796 $line->total_ttc += $tabprice[2];
1797 $line->total_tva += $tabprice[1];
1798 }
1799
1800 $i++;
1801 $originline = $obj->fk_elementdet;
1802 }
1803 $this->db->free($resql);
1804 return 1;
1805 } else {
1806 $this->error = $this->db->error();
1807 return -3;
1808 }
1809 }
1810
1818 public function deleteLine($user, $lineid)
1819 {
1820 global $user;
1821
1822 if ($this->statut == self::STATUS_DRAFT) {
1823 $this->db->begin();
1824
1825 $line = new ExpeditionLigne($this->db);
1826
1827 // For triggers
1828 $line->fetch($lineid);
1829
1830 if ($line->delete($user) > 0) {
1831 //$this->update_price(1);
1832
1833 $this->db->commit();
1834 return 1;
1835 } else {
1836 $this->db->rollback();
1837 return -1;
1838 }
1839 } else {
1840 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1841 return -2;
1842 }
1843 }
1844
1845
1853 public function getTooltipContentArray($params)
1854 {
1855 global $conf, $langs;
1856
1857 $langs->load('sendings');
1858
1859 $nofetch = !empty($params['nofetch']);
1860
1861 $datas = array();
1862 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
1863 if (isset($this->statut)) {
1864 $datas['picto'] .= ' '.$this->getLibStatut(5);
1865 }
1866 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1867 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1868 if (!$nofetch) {
1869 $langs->load('companies');
1870 if (empty($this->thirdparty)) {
1871 $this->fetch_thirdparty();
1872 }
1873 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1874 }
1875
1876 return $datas;
1877 }
1878
1890 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1891 {
1892 global $langs, $hookmanager;
1893
1894 $result = '';
1895 $params = [
1896 'id' => $this->id,
1897 'objecttype' => $this->element,
1898 'option' => $option,
1899 'nofetch' => 1,
1900 ];
1901 $classfortooltip = 'classfortooltip';
1902 $dataparams = '';
1903 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1904 $classfortooltip = 'classforajaxtooltip';
1905 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1906 $label = '';
1907 } else {
1908 $label = implode($this->getTooltipContentArray($params));
1909 }
1910
1911 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1912
1913 if ($short) {
1914 return $url;
1915 }
1916
1917 if ($option !== 'nolink') {
1918 // Add param to save lastsearch_values or not
1919 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1920 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1921 $add_save_lastsearch_values = 1;
1922 }
1923 if ($add_save_lastsearch_values) {
1924 $url .= '&save_lastsearch_values=1';
1925 }
1926 }
1927
1928 $linkclose = '';
1929 if (empty($notooltip)) {
1930 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1931 $label = $langs->trans("Shipment");
1932 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1933 }
1934 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1935 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1936 }
1937
1938 $linkstart = '<a href="'.$url.'"';
1939 $linkstart .= $linkclose.'>';
1940 $linkend = '</a>';
1941
1942 $result .= $linkstart;
1943 if ($withpicto) {
1944 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1945 }
1946 if ($withpicto != 2) {
1947 $result .= $this->ref;
1948 }
1949 $result .= $linkend;
1950 global $action;
1951 $hookmanager->initHooks(array($this->element . 'dao'));
1952 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1953 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1954 if ($reshook > 0) {
1955 $result = $hookmanager->resPrint;
1956 } else {
1957 $result .= $hookmanager->resPrint;
1958 }
1959 return $result;
1960 }
1961
1968 public function getLibStatut($mode = 0)
1969 {
1970 return $this->LibStatut($this->statut, $mode);
1971 }
1972
1973 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1981 public function LibStatut($status, $mode)
1982 {
1983 // phpcs:enable
1984 global $langs;
1985
1986 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
1987 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
1988
1989 $statusType = 'status'.$status;
1990 if ($status == self::STATUS_VALIDATED) {
1991 $statusType = 'status4';
1992 }
1993 if ($status == self::STATUS_CLOSED) {
1994 $statusType = 'status6';
1995 }
1996 if ($status == self::STATUS_CANCELED) {
1997 $statusType = 'status9';
1998 }
1999
2000 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
2001 }
2002
2010 public function getKanbanView($option = '', $arraydata = null)
2011 {
2012 global $langs, $conf;
2013
2014 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2015
2016 $return = '<div class="box-flex-item box-flex-grow-zero">';
2017 $return .= '<div class="info-box info-box-sm">';
2018 $return .= '<div class="info-box-icon bg-infobox-action">';
2019 $return .= img_picto('', 'order');
2020 $return .= '</div>';
2021 $return .= '<div class="info-box-content">';
2022 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
2023 if ($selected >= 0) {
2024 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2025 }
2026 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
2027 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
2028 }
2029 if (property_exists($this, 'total_ht')) {
2030 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'</div>';
2031 }
2032 if (method_exists($this, 'getLibStatut')) {
2033 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2034 }
2035 $return .= '</div>';
2036 $return .= '</div>';
2037 $return .= '</div>';
2038
2039 return $return;
2040 }
2041
2049 public function initAsSpecimen()
2050 {
2051 global $langs;
2052
2053 $now = dol_now();
2054
2055 dol_syslog(get_class($this)."::initAsSpecimen");
2056
2057 $order = new Commande($this->db);
2058 $order->initAsSpecimen();
2059
2060 // Initialise parameters
2061 $this->id = 0;
2062 $this->ref = 'SPECIMEN';
2063 $this->specimen = 1;
2065 $this->livraison_id = 0;
2066 $this->date = $now;
2067 $this->date_creation = $now;
2068 $this->date_valid = $now;
2069 $this->date_delivery = $now + 24 * 3600;
2070 $this->date_expedition = $now + 24 * 3600;
2071
2072 $this->entrepot_id = 0;
2073 $this->fk_delivery_address = 0;
2074 $this->socid = 1;
2075
2076 $this->commande_id = 0;
2077 $this->commande = $order;
2078
2079 $this->origin_id = 1;
2080 $this->origin = 'commande';
2081
2082 $this->note_private = 'Private note';
2083 $this->note_public = 'Public note';
2084
2085 $nbp = 5;
2086 $xnbp = 0;
2087 while ($xnbp < $nbp) {
2088 $line = new ExpeditionLigne($this->db);
2089 $line->product_desc = $langs->trans("Description")." ".$xnbp;
2090 $line->product_label = $langs->trans("Description")." ".$xnbp;
2091 $line->qty = 10;
2092 $line->qty_asked = 5;
2093 $line->qty_shipped = 4;
2094 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2095
2096 $this->lines[] = $line;
2097 $xnbp++;
2098 }
2099
2100 return 1;
2101 }
2102
2103 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2112 public function set_date_livraison($user, $delivery_date)
2113 {
2114 // phpcs:enable
2115 return $this->setDeliveryDate($user, $delivery_date);
2116 }
2117
2125 public function setDeliveryDate($user, $delivery_date)
2126 {
2127 if ($user->hasRight('expedition', 'creer')) {
2128 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2129 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2130 $sql .= " WHERE rowid = ".((int) $this->id);
2131
2132 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2133 $resql = $this->db->query($sql);
2134 if ($resql) {
2135 $this->date_delivery = $delivery_date;
2136 return 1;
2137 } else {
2138 $this->error = $this->db->error();
2139 return -1;
2140 }
2141 } else {
2142 return -2;
2143 }
2144 }
2145
2146 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2152 public function fetch_delivery_methods()
2153 {
2154 // phpcs:enable
2155 global $langs;
2156 $this->meths = array();
2157
2158 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2159 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2160 $sql .= " WHERE em.active = 1";
2161 $sql .= " ORDER BY em.libelle ASC";
2162
2163 $resql = $this->db->query($sql);
2164 if ($resql) {
2165 while ($obj = $this->db->fetch_object($resql)) {
2166 $label = $langs->trans('SendingMethod'.$obj->code);
2167 $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2168 }
2169 }
2170 }
2171
2172 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2179 public function list_delivery_methods($id = 0)
2180 {
2181 // phpcs:enable
2182 global $langs;
2183
2184 $this->listmeths = array();
2185 $i = 0;
2186
2187 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2188 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2189 if (!empty($id)) {
2190 $sql .= " WHERE em.rowid=".((int) $id);
2191 }
2192
2193 $resql = $this->db->query($sql);
2194 if ($resql) {
2195 while ($obj = $this->db->fetch_object($resql)) {
2196 $this->listmeths[$i]['rowid'] = $obj->rowid;
2197 $this->listmeths[$i]['code'] = $obj->code;
2198 $label = $langs->trans('SendingMethod'.$obj->code);
2199 $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2200 $this->listmeths[$i]['description'] = $obj->description;
2201 $this->listmeths[$i]['tracking'] = $obj->tracking;
2202 $this->listmeths[$i]['active'] = $obj->active;
2203 $i++;
2204 }
2205 }
2206 }
2207
2214 public function getUrlTrackingStatus($value = '')
2215 {
2216 if (!empty($this->shipping_method_id)) {
2217 $sql = "SELECT em.code, em.tracking";
2218 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2219 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2220
2221 $resql = $this->db->query($sql);
2222 if ($resql) {
2223 if ($obj = $this->db->fetch_object($resql)) {
2224 $tracking = $obj->tracking;
2225 }
2226 }
2227 }
2228
2229 if (!empty($tracking) && !empty($value)) {
2230 $url = str_replace('{TRACKID}', $value, $tracking);
2231 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
2232 } else {
2233 $this->tracking_url = $value;
2234 }
2235 }
2236
2242 public function setClosed()
2243 {
2244 global $user;
2245
2246 $error = 0;
2247
2248 // Protection. This avoid to move stock later when we should not
2249 if ($this->statut == self::STATUS_CLOSED) {
2250 return 0;
2251 }
2252
2253 $this->db->begin();
2254
2255 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED;
2256 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2257
2258 $resql = $this->db->query($sql);
2259 if ($resql) {
2260 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2261 if ($this->origin == 'commande' && $this->origin_id > 0) {
2262 $order = new Commande($this->db);
2263 $order->fetch($this->origin_id);
2264
2265 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2266
2267 $shipments_match_order = 1;
2268 foreach ($order->lines as $line) {
2269 $lineid = $line->id;
2270 $qty = $line->qty;
2271 if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2272 $shipments_match_order = 0;
2273 $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';
2274 dol_syslog($text);
2275 break;
2276 }
2277 }
2278 if ($shipments_match_order) {
2279 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');
2280 // We close the order
2281 $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2282 }
2283 }
2284
2285 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2286 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2287
2288 // If stock increment is done on closing
2289 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2290 $result = $this->manageStockMvtOnEvt($user);
2291 if ($result < 0) {
2292 $error++;
2293 }
2294 }
2295
2296 // Call trigger
2297 if (!$error) {
2298 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2299 if ($result < 0) {
2300 $error++;
2301 }
2302 }
2303 } else {
2304 dol_print_error($this->db);
2305 $error++;
2306 }
2307
2308 if (!$error) {
2309 $this->db->commit();
2310 return 1;
2311 } else {
2314
2315 $this->db->rollback();
2316 return -1;
2317 }
2318 }
2319
2329 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2330 {
2331 global $langs;
2332
2333 $error = 0;
2334
2335 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2336
2337 $langs->load("agenda");
2338
2339 // Loop on each product line to add a stock movement
2340 $sql = "SELECT cd.fk_product, cd.subprice,";
2341 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2342 $sql .= " e.ref,";
2343 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2344 $sql .= " cd.rowid as cdid, ed.rowid as edid";
2345 $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2346 $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2347 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2348 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2349 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2350 $sql .= " AND cd.rowid = ed.fk_elementdet";
2351
2352 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2353 $resql = $this->db->query($sql);
2354 if ($resql) {
2355 $cpt = $this->db->num_rows($resql);
2356 for ($i = 0; $i < $cpt; $i++) {
2357 $obj = $this->db->fetch_object($resql);
2358 if (empty($obj->edbrowid)) {
2359 $qty = $obj->qty;
2360 } else {
2361 $qty = $obj->edbqty;
2362 }
2363 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2364 continue;
2365 }
2366 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2367
2368 $mouvS = new MouvementStock($this->db);
2369 $mouvS->origin = &$this;
2370 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2371
2372 if (empty($obj->edbrowid)) {
2373 // line without batch detail
2374
2375 // 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
2376 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
2377 if ($result < 0) {
2378 $this->error = $mouvS->error;
2379 $this->errors = $mouvS->errors;
2380 $error++;
2381 break;
2382 }
2383 } else {
2384 // line with batch detail
2385
2386 // 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
2387 $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);
2388 if ($result < 0) {
2389 $this->error = $mouvS->error;
2390 $this->errors = $mouvS->errors;
2391 $error++;
2392 break;
2393 }
2394 }
2395
2396 // 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
2397 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2398 $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)";
2399 $resqldelete = $this->db->query($sqldelete);
2400 // We do not test error, it can fails if there is child in batch details
2401 }
2402 } else {
2403 $this->error = $this->db->lasterror();
2404 $this->errors[] = $this->db->lasterror();
2405 $error++;
2406 }
2407
2408 return $error;
2409 }
2410
2416 public function setBilled()
2417 {
2418 global $user;
2419 $error = 0;
2420
2421 $this->db->begin();
2422
2423 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET billed = 1';
2424 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2425
2426 $resql = $this->db->query($sql);
2427 if ($resql) {
2428 $this->billed = 1;
2429
2430 // Call trigger
2431 $result = $this->call_trigger('SHIPPING_BILLED', $user);
2432 if ($result < 0) {
2433 $this->billed = 0;
2434 $error++;
2435 }
2436 } else {
2437 $error++;
2438 $this->errors[] = $this->db->lasterror;
2439 }
2440
2441 if (empty($error)) {
2442 $this->db->commit();
2443 return 1;
2444 } else {
2445 $this->db->rollback();
2446 return -1;
2447 }
2448 }
2449
2457 public function setDraft($user, $notrigger = 0)
2458 {
2459 // Protection
2460 if ($this->statut <= self::STATUS_DRAFT) {
2461 return 0;
2462 }
2463
2464 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2465 }
2466
2472 public function reOpen()
2473 {
2474 global $langs, $user;
2475
2476 $error = 0;
2477
2478 // Protection. This avoid to move stock later when we should not
2479 if ($this->statut == self::STATUS_VALIDATED) {
2480 return 0;
2481 }
2482
2483 $this->db->begin();
2484
2485 $oldbilled = $this->billed;
2486
2487 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2488 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2489
2490 $resql = $this->db->query($sql);
2491 if ($resql) {
2494 $this->billed = 0;
2495
2496 // If stock increment is done on closing
2497 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2498 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2499
2500 $langs->load("agenda");
2501
2502 // Loop on each product line to add a stock movement
2503 // TODO possibilite d'expedier a partir d'une propale ou autre origine
2504 $sql = "SELECT cd.fk_product, cd.subprice,";
2505 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2506 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2507 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2508 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2509 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2510 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2511 $sql .= " AND cd.rowid = ed.fk_elementdet";
2512
2513 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2514 $resql = $this->db->query($sql);
2515 if ($resql) {
2516 $cpt = $this->db->num_rows($resql);
2517 for ($i = 0; $i < $cpt; $i++) {
2518 $obj = $this->db->fetch_object($resql);
2519 if (empty($obj->edbrowid)) {
2520 $qty = $obj->qty;
2521 } else {
2522 $qty = $obj->edbqty;
2523 }
2524 if ($qty <= 0) {
2525 continue;
2526 }
2527 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2528
2529 //var_dump($this->lines[$i]);
2530 $mouvS = new MouvementStock($this->db);
2531 $mouvS->origin = &$this;
2532 $mouvS->setOrigin($this->element, $this->id);
2533
2534 if (empty($obj->edbrowid)) {
2535 // line without batch detail
2536
2537 // 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
2538 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2539 if ($result < 0) {
2540 $this->error = $mouvS->error;
2541 $this->errors = $mouvS->errors;
2542 $error++;
2543 break;
2544 }
2545 } else {
2546 // line with batch detail
2547
2548 // 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
2549 $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);
2550 if ($result < 0) {
2551 $this->error = $mouvS->error;
2552 $this->errors = $mouvS->errors;
2553 $error++;
2554 break;
2555 }
2556 }
2557 }
2558 } else {
2559 $this->error = $this->db->lasterror();
2560 $error++;
2561 }
2562 }
2563
2564 if (!$error) {
2565 // Call trigger
2566 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2567 if ($result < 0) {
2568 $error++;
2569 }
2570 }
2571 } else {
2572 $error++;
2573 $this->errors[] = $this->db->lasterror();
2574 }
2575
2576 if (!$error) {
2577 $this->db->commit();
2578 return 1;
2579 } else {
2580 $this->statut = self::STATUS_CLOSED;
2581 $this->status = self::STATUS_CLOSED;
2582 $this->billed = $oldbilled;
2583 $this->db->rollback();
2584 return -1;
2585 }
2586 }
2587
2599 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2600 {
2601 $outputlangs->load("products");
2602
2603 if (!dol_strlen($modele)) {
2604 $modele = 'rouget';
2605
2606 if (!empty($this->model_pdf)) {
2607 $modele = $this->model_pdf;
2608 } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
2609 $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
2610 }
2611 }
2612
2613 $modelpath = "core/modules/expedition/doc/";
2614
2615 $this->fetch_origin();
2616
2617 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2618 }
2619
2628 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2629 {
2630 $tables = array(
2631 'expedition'
2632 );
2633
2634 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2635 }
2636}
2637
2638
2643{
2647 public $element = 'expeditiondet';
2648
2652 public $table_element = 'expeditiondet';
2653
2657 public $parent_element = 'expedition';
2658
2662 public $fk_parent_attribute = 'fk_expedition';
2663
2670 public $line_id; // deprecated
2671
2675 public $fk_element;
2676
2680 public $origin_id;
2681
2685 public $fk_elementdet;
2686
2690 public $origin_line_id;
2691
2695 public $element_type;
2696
2697
2704 public $fk_origin; // Example: 'orderline'
2705
2709 public $fk_expedition;
2710
2714 public $db;
2715
2719 public $qty;
2720
2724 public $qty_shipped;
2725
2729 public $fk_product;
2730
2731 // detail of lot and qty = array(id in llx_expeditiondet_batch, fk_expeditiondet, batch, qty, fk_origin_stock)
2732 // We can use this to know warehouse planned to be used for each lot.
2733 public $detail_batch;
2734
2735 // detail of warehouses and qty
2736 // We can use this to know warehouse when there is no lot.
2737 public $details_entrepot;
2738
2739
2743 public $entrepot_id;
2744
2745
2749 public $qty_asked;
2750
2755 public $ref;
2756
2760 public $product_ref;
2761
2766 public $libelle;
2767
2771 public $product_label;
2772
2778 public $desc;
2779
2783 public $product_desc;
2784
2789 public $product_type = 0;
2790
2794 public $rang;
2795
2799 public $weight;
2800 public $weight_units;
2801
2805 public $length;
2806 public $length_units;
2807
2811 public $width;
2812 public $width_units;
2813
2817 public $height;
2818 public $height_units;
2819
2823 public $surface;
2824 public $surface_units;
2825
2829 public $volume;
2830 public $volume_units;
2831
2832 // Invoicing
2833 public $remise_percent;
2834 public $tva_tx;
2835
2839 public $total_ht;
2840
2844 public $total_ttc;
2845
2849 public $total_tva;
2850
2854 public $total_localtax1;
2855
2859 public $total_localtax2;
2860
2861
2867 public function __construct($db)
2868 {
2869 $this->db = $db;
2870 }
2871
2878 public function fetch($rowid)
2879 {
2880 $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_elementdet, ed.element_type, ed.qty, ed.rang';
2881 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
2882 $sql .= ' WHERE ed.rowid = '.((int) $rowid);
2883 $result = $this->db->query($sql);
2884 if ($result) {
2885 $objp = $this->db->fetch_object($result);
2886 $this->id = $objp->rowid;
2887 $this->fk_expedition = $objp->fk_expedition;
2888 $this->entrepot_id = $objp->fk_entrepot;
2889 $this->fk_elementdet = $objp->fk_elementdet;
2890 $this->element_type = $objp->element_type;
2891 $this->qty = $objp->qty;
2892 $this->rang = $objp->rang;
2893
2894 $this->db->free($result);
2895
2896 return 1;
2897 } else {
2898 $this->errors[] = $this->db->lasterror();
2899 $this->error = $this->db->lasterror();
2900 return -1;
2901 }
2902 }
2903
2911 public function insert($user, $notrigger = 0)
2912 {
2913 $error = 0;
2914
2915 // Check parameters
2916 if (empty($this->fk_expedition) || empty($this->fk_elementdet) || !is_numeric($this->qty)) {
2917 $this->error = 'ErrorMandatoryParametersNotProvided';
2918 return -1;
2919 }
2920
2921 $this->db->begin();
2922
2923 if (empty($this->rang)) {
2924 $this->rang = 0;
2925 }
2926
2927 // Rank to use
2928 $ranktouse = $this->rang;
2929 if ($ranktouse == -1) {
2930 $rangmax = $this->line_max($this->fk_expedition);
2931 $ranktouse = $rangmax + 1;
2932 }
2933
2934 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
2935 $sql .= "fk_expedition";
2936 $sql .= ", fk_entrepot";
2937 $sql .= ", fk_elementdet";
2938 $sql .= ", element_type";
2939 $sql .= ", qty";
2940 $sql .= ", rang";
2941 $sql .= ") VALUES (";
2942 $sql .= $this->fk_expedition;
2943 $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
2944 $sql .= ", ".((int) $this->fk_elementdet);
2945 $sql .= ", '".(empty($this->element_type) ? 'order' : $this->db->escape($this->element_type))."'";
2946 $sql .= ", ".price2num($this->qty, 'MS');
2947 $sql .= ", ".((int) $ranktouse);
2948 $sql .= ")";
2949
2950 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
2951 $resql = $this->db->query($sql);
2952 if ($resql) {
2953 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
2954
2955 if (!$error) {
2956 $result = $this->insertExtraFields();
2957 if ($result < 0) {
2958 $error++;
2959 }
2960 }
2961
2962 if (!$error && !$notrigger) {
2963 // Call trigger
2964 $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
2965 if ($result < 0) {
2966 $error++;
2967 }
2968 // End call triggers
2969 }
2970
2971 if ($error) {
2972 foreach ($this->errors as $errmsg) {
2973 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2974 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2975 }
2976 }
2977 } else {
2978 $error++;
2979 }
2980
2981 if ($error) {
2982 $this->db->rollback();
2983 return -1;
2984 } else {
2985 $this->db->commit();
2986 return $this->id;
2987 }
2988 }
2989
2997 public function delete($user = null, $notrigger = 0)
2998 {
2999 $error = 0;
3000
3001 $this->db->begin();
3002
3003 // delete batch expedition line
3004 if (isModEnabled('productbatch')) {
3005 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
3006 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
3007
3008 if (!$this->db->query($sql)) {
3009 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3010 $error++;
3011 }
3012 }
3013
3014 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
3015 $sql .= " WHERE rowid = ".((int) $this->id);
3016
3017 if (!$error && $this->db->query($sql)) {
3018 // Remove extrafields
3019 if (!$error) {
3020 $result = $this->deleteExtraFields();
3021 if ($result < 0) {
3022 $this->errors[] = $this->error;
3023 $error++;
3024 }
3025 }
3026 if (!$error && !$notrigger) {
3027 // Call trigger
3028 $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
3029 if ($result < 0) {
3030 $this->errors[] = $this->error;
3031 $error++;
3032 }
3033 // End call triggers
3034 }
3035 } else {
3036 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3037 $error++;
3038 }
3039
3040 if (!$error) {
3041 $this->db->commit();
3042 return 1;
3043 } else {
3044 foreach ($this->errors as $errmsg) {
3045 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
3046 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3047 }
3048 $this->db->rollback();
3049 return -1 * $error;
3050 }
3051 }
3052
3060 public function update($user = null, $notrigger = 0)
3061 {
3062 $error = 0;
3063
3064 dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
3065
3066 $this->db->begin();
3067
3068 // Clean parameters
3069 if (empty($this->qty)) {
3070 $this->qty = 0;
3071 }
3072 $qty = price2num($this->qty);
3073 $remainingQty = 0;
3074 $batch = null;
3075 $batch_id = null;
3076 $expedition_batch_id = null;
3077 if (is_array($this->detail_batch)) { // array of ExpeditionLineBatch
3078 if (count($this->detail_batch) > 1) {
3079 dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
3080 $this->errors[] = 'ErrorBadParameters';
3081 $error++;
3082 } else {
3083 $batch = $this->detail_batch[0]->batch;
3084 $batch_id = $this->detail_batch[0]->fk_origin_stock;
3085 $expedition_batch_id = $this->detail_batch[0]->id;
3086 if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id) {
3087 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
3088 $this->errors[] = 'ErrorBadParameters';
3089 $error++;
3090 }
3091 $qty = price2num($this->detail_batch[0]->qty);
3092 }
3093 } elseif (!empty($this->detail_batch)) {
3094 $batch = $this->detail_batch->batch;
3095 $batch_id = $this->detail_batch->fk_origin_stock;
3096 $expedition_batch_id = $this->detail_batch->id;
3097 if ($this->entrepot_id != $this->detail_batch->entrepot_id) {
3098 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
3099 $this->errors[] = 'ErrorBadParameters';
3100 $error++;
3101 }
3102 $qty = price2num($this->detail_batch->qty);
3103 }
3104
3105 // check parameters
3106 if (!isset($this->id) || !isset($this->entrepot_id)) {
3107 dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
3108 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
3109 $error++;
3110 return -1;
3111 }
3112
3113 // update lot
3114
3115 if (!empty($batch) && isModEnabled('productbatch')) {
3116 $batch_id_str = $batch_id ?? 'null';
3117 dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id_str, batch=$batch");
3118
3119 if (empty($batch_id) || empty($this->fk_product)) {
3120 dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
3121 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
3122 $error++;
3123 }
3124
3125 // fetch remaining lot qty
3126 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
3127
3128 if (!$error && ($lotArray = $shipmentlinebatch->fetchAll($this->id)) < 0) {
3129 $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
3130 $error++;
3131 } else {
3132 // calculate new total line qty
3133 foreach ($lotArray as $lot) {
3134 if ($expedition_batch_id != $lot->id) {
3135 $remainingQty += $lot->qty;
3136 }
3137 }
3138 $qty += $remainingQty;
3139
3140 //fetch lot details
3141
3142 // fetch from product_lot
3143 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
3144 $lot = new Productlot($this->db);
3145 if ($lot->fetch(0, $this->fk_product, $batch) < 0) {
3146 $this->errors[] = $lot->errors;
3147 $error++;
3148 }
3149 if (!$error && !empty($expedition_batch_id)) {
3150 // delete lot expedition line
3151 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
3152 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
3153 $sql .= " AND rowid = ".((int) $expedition_batch_id);
3154
3155 if (!$this->db->query($sql)) {
3156 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3157 $error++;
3158 }
3159 }
3160 if (!$error && $this->detail_batch->qty > 0) {
3161 // create lot expedition line
3162 if (isset($lot->id)) {
3163 $shipmentLot = new ExpeditionLineBatch($this->db);
3164 $shipmentLot->batch = $lot->batch;
3165 $shipmentLot->eatby = $lot->eatby;
3166 $shipmentLot->sellby = $lot->sellby;
3167 $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id;
3168 $shipmentLot->qty = $this->detail_batch->qty;
3169 $shipmentLot->fk_origin_stock = $batch_id;
3170 if ($shipmentLot->create($this->id) < 0) {
3171 $this->errors = $shipmentLot->errors;
3172 $error++;
3173 }
3174 }
3175 }
3176 }
3177 }
3178 if (!$error) {
3179 // update line
3180 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3181 $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
3182 $sql .= " , qty = ".((float) price2num($qty, 'MS'));
3183 $sql .= " WHERE rowid = ".((int) $this->id);
3184
3185 if (!$this->db->query($sql)) {
3186 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3187 $error++;
3188 }
3189 }
3190
3191 if (!$error) {
3192 $result = $this->insertExtraFields();
3193 if ($result < 0) {
3194 $this->errors[] = $this->error;
3195 $error++;
3196 }
3197 }
3198
3199 if (!$error && !$notrigger) {
3200 // Call trigger
3201 $result = $this->call_trigger('LINESHIPPING_MODIFY', $user);
3202 if ($result < 0) {
3203 $this->errors[] = $this->error;
3204 $error++;
3205 }
3206 // End call triggers
3207 }
3208 if (!$error) {
3209 $this->db->commit();
3210 return 1;
3211 } else {
3212 foreach ($this->errors as $errmsg) {
3213 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3214 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3215 }
3216 $this->db->rollback();
3217 return -1 * $error;
3218 }
3219 }
3220}
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:626
$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:143
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:1929