dolibarr 19.0.3
expedition.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003-2008 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
5 * Copyright (C) 2006-2012 Laurent Destailleur <eldy@users.sourceforge.net>
6 * Copyright (C) 2011-2020 Juanjo Menent <jmenent@2byte.es>
7 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
8 * Copyright (C) 2014 Cedric GROSS <c.gross@kreiz-it.fr>
9 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
10 * Copyright (C) 2014-2017 Francis Appels <francis.appels@yahoo.com>
11 * Copyright (C) 2015 Claudio Aschieri <c.aschieri@19.coop>
12 * Copyright (C) 2016-2024 Ferran Marcet <fmarcet@2byte.es>
13 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2018-2022 Frédéric France <frederic.france@netlogic.fr>
15 * Copyright (C) 2020 Lenin Rivas <lenin@leninrivas.com>
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <https://www.gnu.org/licenses/>.
29 */
30
37require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
38require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
39require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
40if (isModEnabled("propal")) {
41 require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
42}
43if (isModEnabled('commande')) {
44 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
45}
46require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionlinebatch.class.php';
47
48
53{
55
59 public $element = "shipping";
60
64 public $fk_element = "fk_expedition";
65
69 public $table_element = "expedition";
70
74 public $table_element_line = "expeditiondet";
75
80 public $ismultientitymanaged = 1;
81
85 public $isextrafieldmanaged = 1;
86
90 public $picto = 'dolly';
91
92
96 public $fields = array();
97
101 public $user_author_id;
102
106 public $fk_user_author;
107
108 public $socid;
109
115 public $ref_client;
116
120 public $ref_customer;
121
125 public $entrepot_id;
126
130 public $tracking_number;
131
135 public $tracking_url;
136 public $billed;
137
141 public $model_pdf;
142
143 public $trueWeight;
144 public $weight_units;
145 public $trueWidth;
146 public $width_units;
147 public $trueHeight;
148 public $height_units;
149 public $trueDepth;
150 public $depth_units;
151 // A denormalized value
152 public $trueSize;
153
154 public $livraison_id;
155
159 public $multicurrency_subprice;
160
161 public $size_units;
162
163 public $sizeH;
164
165 public $sizeS;
166
167 public $sizeW;
168
169 public $weight;
170
174 public $date_delivery;
175
180 public $date;
181
187
192 public $date_shipping;
193
197 public $date_creation;
198
202 public $date_valid;
203
204 public $meths;
205 public $listmeths; // List of carriers
206
210 public $commande_id;
211
215 public $commande;
216
220 public $lines = array();
221
222 // Multicurrency
226 public $fk_multicurrency;
227
231 public $multicurrency_code;
232 public $multicurrency_tx;
233 public $multicurrency_total_ht;
234 public $multicurrency_total_tva;
235 public $multicurrency_total_ttc;
236
240 const STATUS_DRAFT = 0;
241
246
250 const STATUS_CLOSED = 2;
251
255 const STATUS_CANCELED = -1;
256
257
263 public function __construct($db)
264 {
265 global $conf;
266
267 $this->db = $db;
268
269 // List of long language codes for status
270 $this->labelStatus = array();
271 $this->labelStatus[-1] = 'StatusSendingCanceled';
272 $this->labelStatus[0] = 'StatusSendingDraft';
273 $this->labelStatus[1] = 'StatusSendingValidated';
274 $this->labelStatus[2] = 'StatusSendingProcessed';
275
276 // List of short language codes for status
277 $this->labelStatusShort = array();
278 $this->labelStatusShort[-1] = 'StatusSendingCanceledShort';
279 $this->labelStatusShort[0] = 'StatusSendingDraftShort';
280 $this->labelStatusShort[1] = 'StatusSendingValidatedShort';
281 $this->labelStatusShort[2] = 'StatusSendingProcessedShort';
282 }
283
290 public function getNextNumRef($soc)
291 {
292 global $langs, $conf;
293 $langs->load("sendings");
294
295 if (getDolGlobalString('EXPEDITION_ADDON_NUMBER')) {
296 $mybool = false;
297
298 $file = getDolGlobalString('EXPEDITION_ADDON_NUMBER') . ".php";
299 $classname = $conf->global->EXPEDITION_ADDON_NUMBER;
300
301 // Include file with class
302 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
303
304 foreach ($dirmodels as $reldir) {
305 $dir = dol_buildpath($reldir."core/modules/expedition/");
306
307 // Load file with numbering class (if found)
308 $mybool |= @include_once $dir.$file;
309 }
310
311 if (!$mybool) {
312 dol_print_error('', "Failed to include file ".$file);
313 return '';
314 }
315
316 $obj = new $classname();
317 $numref = "";
318 $numref = $obj->getNextValue($soc, $this);
319
320 if ($numref != "") {
321 return $numref;
322 } else {
323 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
324 return "";
325 }
326 } else {
327 print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
328 return "";
329 }
330 }
331
339 public function create($user, $notrigger = 0)
340 {
341 global $conf, $hookmanager;
342
343 $now = dol_now();
344
345 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
346 $error = 0;
347
348 // Clean parameters
349 $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
350 if (empty($this->fk_project)) {
351 $this->fk_project = 0;
352 }
353
354 $this->user = $user;
355
356 $this->db->begin();
357
358 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
359 $sql .= "ref";
360 $sql .= ", entity";
361 $sql .= ", ref_customer";
362 $sql .= ", ref_ext";
363 $sql .= ", date_creation";
364 $sql .= ", fk_user_author";
365 $sql .= ", date_expedition";
366 $sql .= ", date_delivery";
367 $sql .= ", fk_soc";
368 $sql .= ", fk_projet";
369 $sql .= ", fk_address";
370 $sql .= ", fk_shipping_method";
371 $sql .= ", tracking_number";
372 $sql .= ", weight";
373 $sql .= ", size";
374 $sql .= ", width";
375 $sql .= ", height";
376 $sql .= ", weight_units";
377 $sql .= ", size_units";
378 $sql .= ", note_private";
379 $sql .= ", note_public";
380 $sql .= ", model_pdf";
381 $sql .= ", fk_incoterms, location_incoterms";
382 $sql .= ") VALUES (";
383 $sql .= "'(PROV)'";
384 $sql .= ", ".((int) $conf->entity);
385 $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
386 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
387 $sql .= ", '".$this->db->idate($now)."'";
388 $sql .= ", ".((int) $user->id);
389 $sql .= ", ".($this->date_expedition > 0 ? "'".$this->db->idate($this->date_expedition)."'" : "null");
390 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
391 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
392 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
393 $sql .= ", ".($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
394 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
395 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
396 $sql .= ", ".(is_numeric($this->weight) ? $this->weight : 'NULL');
397 $sql .= ", ".(is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
398 $sql .= ", ".(is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
399 $sql .= ", ".(is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
400 $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
401 $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
402 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
403 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
404 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
405 $sql .= ", ".(int) $this->fk_incoterms;
406 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
407 $sql .= ")";
408
409 dol_syslog(get_class($this)."::create", LOG_DEBUG);
410 $resql = $this->db->query($sql);
411 if ($resql) {
412 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
413
414 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
415 $sql .= " SET ref = '(PROV".$this->id.")'";
416 $sql .= " WHERE rowid = ".((int) $this->id);
417
418 dol_syslog(get_class($this)."::create", LOG_DEBUG);
419 if ($this->db->query($sql)) {
420 // Insert of lines
421 $num = count($this->lines);
422 for ($i = 0; $i < $num; $i++) {
423 if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
424 if (!isset($this->lines[$i]->detail_batch)) { // no batch management
425 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) {
426 $error++;
427 }
428 } else { // with batch management
429 if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
430 $error++;
431 }
432 }
433 }
434 }
435
436 if (!$error && $this->id && $this->origin_id) {
437 $ret = $this->add_object_linked();
438 if (!$ret) {
439 $error++;
440 }
441 }
442
443 // Actions on extra fields
444 if (!$error) {
445 $result = $this->insertExtraFields();
446 if ($result < 0) {
447 $error++;
448 }
449 }
450
451 if (!$error && !$notrigger) {
452 // Call trigger
453 $result = $this->call_trigger('SHIPPING_CREATE', $user);
454 if ($result < 0) {
455 $error++;
456 }
457 // End call triggers
458
459 if (!$error) {
460 $this->db->commit();
461 return $this->id;
462 } else {
463 foreach ($this->errors as $errmsg) {
464 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
465 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
466 }
467 $this->db->rollback();
468 return -1 * $error;
469 }
470 } else {
471 $error++;
472 $this->db->rollback();
473 return -3;
474 }
475 } else {
476 $error++;
477 $this->error = $this->db->lasterror()." - sql=$sql";
478 $this->db->rollback();
479 return -2;
480 }
481 } else {
482 $error++;
483 $this->error = $this->db->error()." - sql=$sql";
484 $this->db->rollback();
485 return -1;
486 }
487 }
488
489 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
500 public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = null)
501 {
502 //phpcs:enable
503 global $user;
504
505 $expeditionline = new ExpeditionLigne($this->db);
506 $expeditionline->fk_expedition = $this->id;
507 $expeditionline->entrepot_id = $entrepot_id;
508 $expeditionline->fk_origin_line = $origin_line_id;
509 $expeditionline->qty = $qty;
510 $expeditionline->rang = $rang;
511 $expeditionline->array_options = $array_options;
512
513 if (($lineId = $expeditionline->insert($user)) < 0) {
514 $this->errors[] = $expeditionline->error;
515 }
516 return $lineId;
517 }
518
519
520 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
528 public function create_line_batch($line_ext, $array_options = 0)
529 {
530 // phpcs:enable
531 $error = 0;
532 $stockLocationQty = array(); // associated array with batch qty in stock location
533
534 $tab = $line_ext->detail_batch;
535 // create stockLocation Qty array
536 foreach ($tab as $detbatch) {
537 if (!empty($detbatch->entrepot_id)) {
538 if (empty($stockLocationQty[$detbatch->entrepot_id])) {
539 $stockLocationQty[$detbatch->entrepot_id] = 0;
540 }
541 $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
542 }
543 }
544 // create shipment lines
545 foreach ($stockLocationQty as $stockLocation => $qty) {
546 $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
547 if ($line_id < 0) {
548 $error++;
549 } else {
550 // create shipment batch lines for stockLocation
551 foreach ($tab as $detbatch) {
552 if ($detbatch->entrepot_id == $stockLocation) {
553 if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch
554 $this->errors = $detbatch->errors;
555 $error++;
556 }
557 }
558 }
559 }
560 }
561
562 if (!$error) {
563 return 1;
564 } else {
565 return -1;
566 }
567 }
568
578 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
579 {
580 global $conf;
581
582 // Check parameters
583 if (empty($id) && empty($ref) && empty($ref_ext)) {
584 return -1;
585 }
586
587 $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";
588 $sql .= ", e.date_valid";
589 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
590 $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
591 $sql .= ", e.fk_shipping_method, e.tracking_number";
592 $sql .= ", e.note_private, e.note_public";
593 $sql .= ', e.fk_incoterms, e.location_incoterms';
594 $sql .= ', i.libelle as label_incoterms';
595 $sql .= ', s.libelle as shipping_method';
596 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin";
597 $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
598 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
599 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
600 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
601 $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
602 if ($id) {
603 $sql .= " AND e.rowid = ".((int) $id);
604 }
605 if ($ref) {
606 $sql .= " AND e.ref='".$this->db->escape($ref)."'";
607 }
608 if ($ref_ext) {
609 $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
610 }
611
612 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
613 $result = $this->db->query($sql);
614 if ($result) {
615 if ($this->db->num_rows($result)) {
616 $obj = $this->db->fetch_object($result);
617
618 $this->id = $obj->rowid;
619 $this->entity = $obj->entity;
620 $this->ref = $obj->ref;
621 $this->socid = $obj->socid;
622 $this->ref_customer = $obj->ref_customer;
623 $this->ref_ext = $obj->ref_ext;
624 $this->status = $obj->fk_statut;
625 $this->statut = $this->status; // Deprecated
626 $this->user_author_id = $obj->fk_user_author;
627 $this->fk_user_author = $obj->fk_user_author;
628 $this->date_creation = $this->db->jdate($obj->date_creation);
629 $this->date_valid = $this->db->jdate($obj->date_valid);
630 $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
631 $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
632 $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
633 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planed
634 $this->fk_delivery_address = $obj->fk_address;
635 $this->model_pdf = $obj->model_pdf;
636 $this->shipping_method_id = $obj->fk_shipping_method;
637 $this->shipping_method = $obj->shipping_method;
638 $this->tracking_number = $obj->tracking_number;
639 $this->origin = ($obj->origin ? $obj->origin : 'commande'); // For compatibility
640 $this->origin_id = $obj->origin_id;
641 $this->billed = $obj->billed;
642 $this->fk_project = $obj->fk_project;
643
644 $this->trueWeight = $obj->weight;
645 $this->weight_units = $obj->weight_units;
646
647 $this->trueWidth = $obj->width;
648 $this->width_units = $obj->size_units;
649 $this->trueHeight = $obj->height;
650 $this->height_units = $obj->size_units;
651 $this->trueDepth = $obj->size;
652 $this->depth_units = $obj->size_units;
653
654 $this->note_public = $obj->note_public;
655 $this->note_private = $obj->note_private;
656
657 // A denormalized value
658 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
659 $this->size_units = $obj->size_units;
660
661 //Incoterms
662 $this->fk_incoterms = $obj->fk_incoterms;
663 $this->location_incoterms = $obj->location_incoterms;
664 $this->label_incoterms = $obj->label_incoterms;
665
666 $this->db->free($result);
667
668 // Tracking url
669 $this->getUrlTrackingStatus($obj->tracking_number);
670
671 // Thirdparty
672 $result = $this->fetch_thirdparty(); // TODO Remove this
673
674 // Retrieve extrafields
675 $this->fetch_optionals();
676
677 // Fix Get multicurrency param for transmited
678 if (isModEnabled('multicurrency')) {
679 if (!empty($this->multicurrency_code)) {
680 $this->multicurrency_code = $this->thirdparty->multicurrency_code;
681 }
682 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($this->thirdparty->multicurrency_tx)) {
683 $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
684 }
685 }
686
687 /*
688 * Lines
689 */
690 $result = $this->fetch_lines();
691 if ($result < 0) {
692 return -3;
693 }
694
695 return 1;
696 } else {
697 dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
698 $this->error = 'Shipment with id '.$id.' not found';
699 return 0;
700 }
701 } else {
702 $this->error = $this->db->error();
703 return -1;
704 }
705 }
706
714 public function valid($user, $notrigger = 0)
715 {
716 global $conf;
717
718 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
719
720 dol_syslog(get_class($this)."::valid");
721
722 // Protection
723 if ($this->status) {
724 dol_syslog(get_class($this)."::valid not in draft status", LOG_WARNING);
725 return 0;
726 }
727
728 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'creer'))
729 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'shipping_advance', 'validate')))) {
730 $this->error = 'Permission denied';
731 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
732 return -1;
733 }
734
735 $this->db->begin();
736
737 $error = 0;
738
739 // Define new ref
740 $soc = new Societe($this->db);
741 $soc->fetch($this->socid);
742
743 // Class of company linked to order
744 $result = $soc->setAsCustomer();
745
746 // Define new ref
747 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
748 $numref = $this->getNextNumRef($soc);
749 } elseif (!empty($this->ref)) {
750 $numref = $this->ref;
751 } else {
752 $numref = "EXP".$this->id;
753 }
754 $this->newref = dol_sanitizeFileName($numref);
755
756 $now = dol_now();
757
758 // Validate
759 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
760 $sql .= " ref='".$this->db->escape($numref)."'";
761 $sql .= ", fk_statut = 1";
762 $sql .= ", date_valid = '".$this->db->idate($now)."'";
763 $sql .= ", fk_user_valid = ".$user->id;
764 $sql .= " WHERE rowid = ".((int) $this->id);
765
766 dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
767 $resql = $this->db->query($sql);
768 if (!$resql) {
769 $this->error = $this->db->lasterror();
770 $error++;
771 }
772
773 // If stock increment is done on sending (recommanded choice)
774 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
775 $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
776 if ($result < 0) {
777 return -2;
778 }
779 }
780
781 // Change status of order to "shipment in process"
782 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
783 if (!$ret) {
784 $error++;
785 }
786
787 if (!$error && !$notrigger) {
788 // Call trigger
789 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
790 if ($result < 0) {
791 $error++;
792 }
793 // End call triggers
794 }
795
796 if (!$error) {
797 $this->oldref = $this->ref;
798
799 // Rename directory if dir was a temporary ref
800 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
801 // Now we rename also files into index
802 $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)."'";
803 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
804 $resql = $this->db->query($sql);
805 if (!$resql) {
806 $error++;
807 $this->error = $this->db->lasterror();
808 }
809 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
810 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
811 $resql = $this->db->query($sql);
812 if (!$resql) {
813 $error++;
814 $this->error = $this->db->lasterror();
815 }
816
817 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
818 $oldref = dol_sanitizeFileName($this->ref);
819 $newref = dol_sanitizeFileName($numref);
820 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
821 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
822 if (!$error && file_exists($dirsource)) {
823 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
824
825 if (@rename($dirsource, $dirdest)) {
826 dol_syslog("Rename ok");
827 // Rename docs starting with $oldref with $newref
828 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
829 foreach ($listoffiles as $fileentry) {
830 $dirsource = $fileentry['name'];
831 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
832 $dirsource = $fileentry['path'].'/'.$dirsource;
833 $dirdest = $fileentry['path'].'/'.$dirdest;
834 @rename($dirsource, $dirdest);
835 }
836 }
837 }
838 }
839 }
840
841 // Set new ref and current status
842 if (!$error) {
843 $this->ref = $numref;
845 }
846
847 if (!$error) {
848 $this->db->commit();
849 return 1;
850 } else {
851 $this->db->rollback();
852 return -1 * $error;
853 }
854 }
855
856
857 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
864 public function create_delivery($user)
865 {
866 // phpcs:enable
867 global $conf;
868
869 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
870 if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
871 // Expedition validee
872 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
873 $delivery = new Delivery($this->db);
874 $result = $delivery->create_from_sending($user, $this->id);
875 if ($result > 0) {
876 return $result;
877 } else {
878 $this->error = $delivery->error;
879 return $result;
880 }
881 } else {
882 return 0;
883 }
884 } else {
885 return 0;
886 }
887 }
888
901 public function addline($entrepot_id, $id, $qty, $array_options = 0)
902 {
903 global $conf, $langs;
904
905 $num = count($this->lines);
906 $line = new ExpeditionLigne($this->db);
907
908 $line->entrepot_id = $entrepot_id;
909 $line->origin_line_id = $id;
910 $line->fk_origin_line = $id;
911 $line->qty = $qty;
912
913 $orderline = new OrderLine($this->db);
914 $orderline->fetch($id);
915
916 // Copy the rang of the order line to the expedition line
917 $line->rang = $orderline->rang;
918 $line->product_type = $orderline->product_type;
919
920 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
921 $fk_product = $orderline->fk_product;
922
923 if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS') && !(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') && $line->product_type == Product::TYPE_SERVICE)) {
924 $langs->load("errors");
925 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
926 return -1;
927 }
928
929 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
930 $product = new Product($this->db);
931 $product->fetch($fk_product);
932
933 // Check must be done for stock of product into warehouse if $entrepot_id defined
934 if ($entrepot_id > 0) {
935 $product->load_stock('warehouseopen');
936 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
937 } else {
938 $product_stock = $product->stock_reel;
939 }
940
941 $product_type = $product->type;
942 if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
943 $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
944 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
945 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.
946 if ($product_stock < $qty) {
947 $langs->load("errors");
948 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
949 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
950
951 $this->db->rollback();
952 return -3;
953 }
954 }
955 }
956 }
957 }
958
959 // If product need a batch number, we should not have called this function but addline_batch instead.
960 // If this happen, we may have a bug in card.php page
961 if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
962 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; //
963 return -4;
964 }
965
966 // extrafields
967 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
968 $line->array_options = $array_options;
969 }
970
971 $this->lines[$num] = $line;
972
973 return 1;
974 }
975
976 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
984 public function addline_batch($dbatch, $array_options = 0)
985 {
986 // phpcs:enable
987 global $conf, $langs;
988
989 $num = count($this->lines);
990 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
991 $line = new ExpeditionLigne($this->db);
992 $tab = array();
993 foreach ($dbatch['detail'] as $key => $value) {
994 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
995 // $value['q']=qty to move
996 // $value['id_batch']=id into llx_product_batch of record to move
997 //var_dump($value);
998
999 $linebatch = new ExpeditionLineBatch($this->db);
1000 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1001 if ($ret < 0) {
1002 $this->setErrorsFromObject($linebatch);
1003 return -1;
1004 }
1005 $linebatch->qty = $value['q'];
1006 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1007 $linebatch->batch = null;
1008 }
1009 $tab[] = $linebatch;
1010
1011 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1012 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
1013 $prod_batch = new Productbatch($this->db);
1014 $prod_batch->fetch($value['id_batch']);
1015
1016 if ($prod_batch->qty < $linebatch->qty) {
1017 $langs->load("errors");
1018 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1019 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1020 $this->db->rollback();
1021 return -1;
1022 }
1023 }
1024
1025 //var_dump($linebatch);
1026 }
1027 }
1028 $line->entrepot_id = $linebatch->entrepot_id;
1029 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1030 $line->fk_origin_line = $dbatch['ix_l'];
1031 $line->qty = $dbatch['qty'];
1032 $line->detail_batch = $tab;
1033
1034 // extrafields
1035 if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1036 $line->array_options = $array_options;
1037 }
1038
1039 //var_dump($line);
1040 $this->lines[$num] = $line;
1041 return 1;
1042 }
1043 return 0;
1044 }
1045
1053 public function update($user = null, $notrigger = 0)
1054 {
1055 global $conf;
1056 $error = 0;
1057
1058 // Clean parameters
1059
1060 if (isset($this->ref)) {
1061 $this->ref = trim($this->ref);
1062 }
1063 if (isset($this->entity)) {
1064 $this->entity = (int) $this->entity;
1065 }
1066 if (isset($this->ref_customer)) {
1067 $this->ref_customer = trim($this->ref_customer);
1068 }
1069 if (isset($this->socid)) {
1070 $this->socid = (int) $this->socid;
1071 }
1072 if (isset($this->fk_user_author)) {
1073 $this->fk_user_author = (int) $this->fk_user_author;
1074 }
1075 if (isset($this->fk_user_valid)) {
1076 $this->fk_user_valid = (int) $this->fk_user_valid;
1077 }
1078 if (isset($this->fk_delivery_address)) {
1079 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1080 }
1081 if (isset($this->shipping_method_id)) {
1082 $this->shipping_method_id = (int) $this->shipping_method_id;
1083 }
1084 if (isset($this->tracking_number)) {
1085 $this->tracking_number = trim($this->tracking_number);
1086 }
1087 if (isset($this->statut)) {
1088 $this->statut = (int) $this->statut;
1089 }
1090 if (isset($this->trueDepth)) {
1091 $this->trueDepth = trim($this->trueDepth);
1092 }
1093 if (isset($this->trueWidth)) {
1094 $this->trueWidth = trim($this->trueWidth);
1095 }
1096 if (isset($this->trueHeight)) {
1097 $this->trueHeight = trim($this->trueHeight);
1098 }
1099 if (isset($this->size_units)) {
1100 $this->size_units = trim($this->size_units);
1101 }
1102 if (isset($this->weight_units)) {
1103 $this->weight_units = trim($this->weight_units);
1104 }
1105 if (isset($this->trueWeight)) {
1106 $this->weight = trim($this->trueWeight);
1107 }
1108 if (isset($this->note_private)) {
1109 $this->note_private = trim($this->note_private);
1110 }
1111 if (isset($this->note_public)) {
1112 $this->note_public = trim($this->note_public);
1113 }
1114 if (isset($this->model_pdf)) {
1115 $this->model_pdf = trim($this->model_pdf);
1116 }
1117
1118 // Check parameters
1119 // Put here code to add control on parameters values
1120
1121 // Update request
1122 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1123 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1124 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1125 $sql .= " ref_customer=".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1126 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1127 $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1128 $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1129 $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1130 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1131 $sql .= " date_expedition=".(dol_strlen($this->date_expedition) != 0 ? "'".$this->db->idate($this->date_expedition)."'" : 'null').",";
1132 $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1133 $sql .= " fk_address=".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1134 $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1135 $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1136 $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1137 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1138 $sql .= " height=".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1139 $sql .= " width=".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1140 $sql .= " size_units=".(isset($this->size_units) ? $this->size_units : "null").",";
1141 $sql .= " size=".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1142 $sql .= " weight_units=".(isset($this->weight_units) ? $this->weight_units : "null").",";
1143 $sql .= " weight=".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1144 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1145 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1146 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1147 $sql .= " entity=".$conf->entity;
1148 $sql .= " WHERE rowid=".((int) $this->id);
1149
1150 $this->db->begin();
1151
1152 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1153 $resql = $this->db->query($sql);
1154 if (!$resql) {
1155 $error++;
1156 $this->errors[] = "Error ".$this->db->lasterror();
1157 }
1158
1159 if (!$error && !$notrigger) {
1160 // Call trigger
1161 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1162 if ($result < 0) {
1163 $error++;
1164 }
1165 // End call triggers
1166 }
1167
1168 // Commit or rollback
1169 if ($error) {
1170 foreach ($this->errors as $errmsg) {
1171 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1172 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1173 }
1174 $this->db->rollback();
1175 return -1 * $error;
1176 } else {
1177 $this->db->commit();
1178 return 1;
1179 }
1180 }
1181
1182
1190 public function cancel($notrigger = 0, $also_update_stock = false)
1191 {
1192 global $conf, $langs, $user;
1193
1194 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1195
1196 $error = 0;
1197 $this->error = '';
1198
1199 $this->db->begin();
1200
1201 // Add a protection to refuse deleting if shipment has at least one delivery
1202 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1203 if (count($this->linkedObjectsIds) > 0) {
1204 $this->error = 'ErrorThereIsSomeDeliveries';
1205 $error++;
1206 }
1207
1208 if (!$error && !$notrigger) {
1209 // Call trigger
1210 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1211 if ($result < 0) {
1212 $error++;
1213 }
1214 // End call triggers
1215 }
1216
1217 // Stock control
1218 if (!$error && isModEnabled('stock') &&
1219 (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
1220 ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1221 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1222
1223 $langs->load("agenda");
1224
1225 // Loop on each product line to add a stock movement and delete features
1226 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1227 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1228 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1229 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1230 $sql .= " AND cd.rowid = ed.fk_origin_line";
1231
1232 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1233 $resql = $this->db->query($sql);
1234 if ($resql) {
1235 $cpt = $this->db->num_rows($resql);
1236
1237 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1238
1239 for ($i = 0; $i < $cpt; $i++) {
1240 dol_syslog(get_class($this)."::delete movement index ".$i);
1241 $obj = $this->db->fetch_object($resql);
1242
1243 $mouvS = new MouvementStock($this->db);
1244 // we do not log origin because it will be deleted
1245 $mouvS->origin = null;
1246 // get lot/serial
1247 $lotArray = null;
1248 if (isModEnabled('productbatch')) {
1249 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1250 if (!is_array($lotArray)) {
1251 $error++;
1252 $this->errors[] = "Error ".$this->db->lasterror();
1253 }
1254 }
1255
1256 if (empty($lotArray)) {
1257 // no lot/serial
1258 // We increment stock of product (and sub-products)
1259 // We use warehouse selected for each line
1260 $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
1261 if ($result < 0) {
1262 $error++;
1263 $this->errors = array_merge($this->errors, $mouvS->errors);
1264 break;
1265 }
1266 } else {
1267 // We increment stock of batches
1268 // We use warehouse selected for each line
1269 foreach ($lotArray as $lot) {
1270 $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
1271 if ($result < 0) {
1272 $error++;
1273 $this->errors = array_merge($this->errors, $mouvS->errors);
1274 break;
1275 }
1276 }
1277 if ($error) {
1278 break; // break for loop incase of error
1279 }
1280 }
1281 }
1282 } else {
1283 $error++;
1284 $this->errors[] = "Error ".$this->db->lasterror();
1285 }
1286 }
1287
1288 // delete batch expedition line
1289 if (!$error && isModEnabled('productbatch')) {
1290 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1291 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1292 $error++;
1293 $this->errors[] = "Error ".$this->db->lasterror();
1294 }
1295 }
1296
1297
1298 if (!$error) {
1299 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1300 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1301
1302 if ($this->db->query($sql)) {
1303 // Delete linked object
1304 $res = $this->deleteObjectLinked();
1305 if ($res < 0) {
1306 $error++;
1307 }
1308
1309 // No delete expedition
1310 if (!$error) {
1311 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1312 $sql .= " WHERE rowid = ".((int) $this->id);
1313
1314 if ($this->db->query($sql)) {
1315 if (!empty($this->origin) && $this->origin_id > 0) {
1316 $this->fetch_origin();
1317 $origin = $this->origin;
1318 if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1319 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1320 $this->$origin->loadExpeditions();
1321 //var_dump($this->$origin->expeditions);exit;
1322 if (count($this->$origin->expeditions) <= 0) {
1323 $this->$origin->setStatut(Commande::STATUS_VALIDATED);
1324 }
1325 }
1326 }
1327
1328 if (!$error) {
1329 $this->db->commit();
1330
1331 // We delete PDFs
1332 $ref = dol_sanitizeFileName($this->ref);
1333 if (!empty($conf->expedition->dir_output)) {
1334 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1335 $file = $dir.'/'.$ref.'.pdf';
1336 if (file_exists($file)) {
1337 if (!dol_delete_file($file)) {
1338 return 0;
1339 }
1340 }
1341 if (file_exists($dir)) {
1342 if (!dol_delete_dir_recursive($dir)) {
1343 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1344 return 0;
1345 }
1346 }
1347 }
1348
1349 return 1;
1350 } else {
1351 $this->db->rollback();
1352 return -1;
1353 }
1354 } else {
1355 $this->error = $this->db->lasterror()." - sql=$sql";
1356 $this->db->rollback();
1357 return -3;
1358 }
1359 } else {
1360 $this->error = $this->db->lasterror()." - sql=$sql";
1361 $this->db->rollback();
1362 return -2;
1363 }//*/
1364 } else {
1365 $this->error = $this->db->lasterror()." - sql=$sql";
1366 $this->db->rollback();
1367 return -1;
1368 }
1369 } else {
1370 $this->db->rollback();
1371 return -1;
1372 }
1373 }
1374
1383 public function delete($notrigger = 0, $also_update_stock = false)
1384 {
1385 global $conf, $langs, $user;
1386
1387 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1388
1389 $error = 0;
1390 $this->error = '';
1391
1392 $this->db->begin();
1393
1394 // Add a protection to refuse deleting if shipment has at least one delivery
1395 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1396 if (count($this->linkedObjectsIds) > 0) {
1397 $this->error = 'ErrorThereIsSomeDeliveries';
1398 $error++;
1399 }
1400
1401 if (!$error && !$notrigger) {
1402 // Call trigger
1403 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1404 if ($result < 0) {
1405 $error++;
1406 }
1407 // End call triggers
1408 }
1409
1410 // Stock control
1411 if (!$error && isModEnabled('stock') &&
1412 (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
1413 ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1414 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1415
1416 $langs->load("agenda");
1417
1418 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1419 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1420
1421 // Loop on each product line to add a stock movement
1422 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1423 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1424 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1425 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1426 $sql .= " AND cd.rowid = ed.fk_origin_line";
1427
1428 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1429 $resql = $this->db->query($sql);
1430 if ($resql) {
1431 $cpt = $this->db->num_rows($resql);
1432 for ($i = 0; $i < $cpt; $i++) {
1433 dol_syslog(get_class($this)."::delete movement index ".$i);
1434 $obj = $this->db->fetch_object($resql);
1435
1436 $mouvS = new MouvementStock($this->db);
1437 // we do not log origin because it will be deleted
1438 $mouvS->origin = null;
1439 // get lot/serial
1440 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1441 if (!is_array($lotArray)) {
1442 $error++;
1443 $this->errors[] = "Error ".$this->db->lasterror();
1444 }
1445 if (empty($lotArray)) {
1446 // no lot/serial
1447 // We increment stock of product (and sub-products)
1448 // We use warehouse selected for each line
1449 $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
1450 if ($result < 0) {
1451 $error++;
1452 $this->errors = array_merge($this->errors, $mouvS->errors);
1453 break;
1454 }
1455 } else {
1456 // We increment stock of batches
1457 // We use warehouse selected for each line
1458 foreach ($lotArray as $lot) {
1459 $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
1460 if ($result < 0) {
1461 $error++;
1462 $this->errors = array_merge($this->errors, $mouvS->errors);
1463 break;
1464 }
1465 }
1466 if ($error) {
1467 break; // break for loop incase of error
1468 }
1469 }
1470 }
1471 } else {
1472 $error++;
1473 $this->errors[] = "Error ".$this->db->lasterror();
1474 }
1475 }
1476
1477 // delete batch expedition line
1478 if (!$error) {
1479 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1480 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1481 $error++;
1482 $this->errors[] = "Error ".$this->db->lasterror();
1483 }
1484 }
1485
1486 if (!$error) {
1487 $main = MAIN_DB_PREFIX.'expeditiondet';
1488 $ef = $main."_extrafields";
1489 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
1490
1491 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1492 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1493
1494 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1495 // Delete linked object
1496 $res = $this->deleteObjectLinked();
1497 if ($res < 0) {
1498 $error++;
1499 }
1500
1501 // delete extrafields
1502 $res = $this->deleteExtraFields();
1503 if ($res < 0) {
1504 $error++;
1505 }
1506
1507 if (!$error) {
1508 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1509 $sql .= " WHERE rowid = ".((int) $this->id);
1510
1511 if ($this->db->query($sql)) {
1512 if (!empty($this->origin) && $this->origin_id > 0) {
1513 $this->fetch_origin();
1514 $origin = $this->origin;
1515 if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1516 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1517 $this->$origin->loadExpeditions();
1518 //var_dump($this->$origin->expeditions);exit;
1519 if (count($this->$origin->expeditions) <= 0) {
1520 $this->$origin->setStatut(Commande::STATUS_VALIDATED);
1521 }
1522 }
1523 }
1524
1525 if (!$error) {
1526 $this->db->commit();
1527
1528 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1529 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1530 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1531
1532 // We delete PDFs
1533 $ref = dol_sanitizeFileName($this->ref);
1534 if (!empty($conf->expedition->dir_output)) {
1535 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1536 $file = $dir.'/'.$ref.'.pdf';
1537 if (file_exists($file)) {
1538 if (!dol_delete_file($file)) {
1539 return 0;
1540 }
1541 }
1542 if (file_exists($dir)) {
1543 if (!dol_delete_dir_recursive($dir)) {
1544 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1545 return 0;
1546 }
1547 }
1548 }
1549
1550 return 1;
1551 } else {
1552 $this->db->rollback();
1553 return -1;
1554 }
1555 } else {
1556 $this->error = $this->db->lasterror()." - sql=$sql";
1557 $this->db->rollback();
1558 return -3;
1559 }
1560 } else {
1561 $this->error = $this->db->lasterror()." - sql=$sql";
1562 $this->db->rollback();
1563 return -2;
1564 }
1565 } else {
1566 $this->error = $this->db->lasterror()." - sql=$sql";
1567 $this->db->rollback();
1568 return -1;
1569 }
1570 } else {
1571 $this->db->rollback();
1572 return -1;
1573 }
1574 }
1575
1576 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1582 public function fetch_lines()
1583 {
1584 // phpcs:enable
1585 global $conf, $mysoc;
1586
1587 $this->lines = array();
1588
1589 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1590 // TODO: See if we can restore a common fetch_lines (one line = one record)
1591
1592 $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";
1593 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1594 $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";
1595 $sql .= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc, cd.rang";
1596 $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_origin_line, ed.fk_entrepot";
1597 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
1598 $sql .= ", p.weight, p.weight_units, p.length, p.length_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";
1599 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1600 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1601 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1602 $sql .= " AND ed.fk_origin_line = cd.rowid";
1603 $sql .= " ORDER BY cd.rang, ed.fk_origin_line"; // We need after a break on fk_origin_line but when there is no break on fk_origin_line, cd.rang is same so we can add it as first order criteria.
1604
1605 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1606 $resql = $this->db->query($sql);
1607 if ($resql) {
1608 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1609
1610 $num = $this->db->num_rows($resql);
1611 $i = 0;
1612 $lineindex = 0;
1613 $originline = 0;
1614
1615 $this->total_ht = 0;
1616 $this->total_tva = 0;
1617 $this->total_ttc = 0;
1618 $this->total_localtax1 = 0;
1619 $this->total_localtax2 = 0;
1620
1621 $this->multicurrency_total_ht = 0;
1622 $this->multicurrency_total_tva = 0;
1623 $this->multicurrency_total_ttc = 0;
1624
1625 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1626
1627 while ($i < $num) {
1628 $obj = $this->db->fetch_object($resql);
1629
1630
1631 if ($originline > 0 && $originline == $obj->fk_origin_line) {
1632 $line->entrepot_id = 0; // entrepod_id in details_entrepot
1633 $line->qty_shipped += $obj->qty_shipped;
1634 } else {
1635 $line = new ExpeditionLigne($this->db); // new group to start
1636 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
1637 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
1638 }
1639
1640 $detail_entrepot = new stdClass();
1641 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1642 $detail_entrepot->qty_shipped = $obj->qty_shipped;
1643 $detail_entrepot->line_id = $obj->line_id;
1644 $line->details_entrepot[] = $detail_entrepot;
1645
1646 $line->line_id = $obj->line_id;
1647 $line->rowid = $obj->line_id; // TODO deprecated
1648 $line->id = $obj->line_id;
1649
1650 $line->fk_origin = 'orderline';
1651 $line->fk_origin_line = $obj->fk_origin_line;
1652 $line->origin_line_id = $obj->fk_origin_line; // TODO deprecated
1653
1654 $line->fk_expedition = $this->id; // id of parent
1655
1656 $line->product_type = $obj->product_type;
1657 $line->fk_product = $obj->fk_product;
1658 $line->fk_product_type = $obj->fk_product_type;
1659 $line->ref = $obj->product_ref; // TODO deprecated
1660 $line->product_ref = $obj->product_ref;
1661 $line->product_label = $obj->product_label;
1662 $line->libelle = $obj->product_label; // TODO deprecated
1663 $line->product_barcode = $obj->product_barcode; // Barcode number product
1664 $line->product_tosell = $obj->product_tosell;
1665 $line->product_tobuy = $obj->product_tobuy;
1666 $line->product_tobatch = $obj->product_tobatch;
1667 $line->label = $obj->custom_label;
1668 $line->description = $obj->description;
1669 $line->qty_asked = $obj->qty_asked;
1670 $line->rang = $obj->rang;
1671 $line->weight = $obj->weight;
1672 $line->weight_units = $obj->weight_units;
1673 $line->length = $obj->length;
1674 $line->length_units = $obj->length_units;
1675 $line->surface = $obj->surface;
1676 $line->surface_units = $obj->surface_units;
1677 $line->volume = $obj->volume;
1678 $line->volume_units = $obj->volume_units;
1679 $line->fk_unit = $obj->fk_unit;
1680
1681 $line->pa_ht = $obj->pa_ht;
1682
1683 // Local taxes
1684 $localtax_array = array(0=>$obj->localtax1_type, 1=>$obj->localtax1_tx, 2=>$obj->localtax2_type, 3=>$obj->localtax2_tx);
1685 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1686 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1687
1688 // For invoicing
1689 $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
1690 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1691 $line->qty = $line->qty_shipped;
1692 $line->total_ht = $tabprice[0];
1693 $line->total_localtax1 = $tabprice[9];
1694 $line->total_localtax2 = $tabprice[10];
1695 $line->total_ttc = $tabprice[2];
1696 $line->total_tva = $tabprice[1];
1697 $line->vat_src_code = $obj->vat_src_code;
1698 $line->tva_tx = $obj->tva_tx;
1699 $line->localtax1_tx = $obj->localtax1_tx;
1700 $line->localtax2_tx = $obj->localtax2_tx;
1701 $line->info_bits = $obj->info_bits;
1702 $line->price = $obj->price;
1703 $line->subprice = $obj->subprice;
1704 $line->remise_percent = $obj->remise_percent;
1705
1706 $this->total_ht += $tabprice[0];
1707 $this->total_tva += $tabprice[1];
1708 $this->total_ttc += $tabprice[2];
1709 $this->total_localtax1 += $tabprice[9];
1710 $this->total_localtax2 += $tabprice[10];
1711
1712 // Multicurrency
1713 $this->fk_multicurrency = $obj->fk_multicurrency;
1714 $this->multicurrency_code = $obj->multicurrency_code;
1715 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1716 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1717 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1718 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1719
1720 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
1721 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
1722 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
1723
1724 if ($originline != $obj->fk_origin_line) {
1725 $line->detail_batch = array();
1726 }
1727
1728 // Detail of batch
1729 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1730 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1731
1732 if (is_array($newdetailbatch)) {
1733 if ($originline != $obj->fk_origin_line) {
1734 $line->detail_batch = $newdetailbatch;
1735 } else {
1736 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1737 }
1738 }
1739 }
1740
1741 $line->fetch_optionals();
1742
1743 if ($originline != $obj->fk_origin_line) {
1744 $this->lines[$lineindex] = $line;
1745 $lineindex++;
1746 } else {
1747 $line->total_ht += $tabprice[0];
1748 $line->total_localtax1 += $tabprice[9];
1749 $line->total_localtax2 += $tabprice[10];
1750 $line->total_ttc += $tabprice[2];
1751 $line->total_tva += $tabprice[1];
1752 }
1753
1754 $i++;
1755 $originline = $obj->fk_origin_line;
1756 }
1757 $this->db->free($resql);
1758 return 1;
1759 } else {
1760 $this->error = $this->db->error();
1761 return -3;
1762 }
1763 }
1764
1772 public function deleteline($user, $lineid)
1773 {
1774 global $user;
1775
1776 if ($this->statut == self::STATUS_DRAFT) {
1777 $this->db->begin();
1778
1779 $line = new ExpeditionLigne($this->db);
1780
1781 // For triggers
1782 $line->fetch($lineid);
1783
1784 if ($line->delete($user) > 0) {
1785 //$this->update_price(1);
1786
1787 $this->db->commit();
1788 return 1;
1789 } else {
1790 $this->db->rollback();
1791 return -1;
1792 }
1793 } else {
1794 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1795 return -2;
1796 }
1797 }
1798
1799
1807 public function getTooltipContentArray($params)
1808 {
1809 global $conf, $langs;
1810
1811 $langs->load('sendings');
1812
1813 $nofetch = !empty($params['nofetch']);
1814
1815 $datas = array();
1816 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
1817 if (isset($this->statut)) {
1818 $datas['picto'] .= ' '.$this->getLibStatut(5);
1819 }
1820 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1821 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1822 if (!$nofetch) {
1823 $langs->load('companies');
1824 if (empty($this->thirdparty)) {
1825 $this->fetch_thirdparty();
1826 }
1827 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1828 }
1829
1830 return $datas;
1831 }
1832
1844 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1845 {
1846 global $langs, $hookmanager;
1847
1848 $result = '';
1849 $params = [
1850 'id' => $this->id,
1851 'objecttype' => $this->element,
1852 'option' => $option,
1853 'nofetch' => 1,
1854 ];
1855 $classfortooltip = 'classfortooltip';
1856 $dataparams = '';
1857 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1858 $classfortooltip = 'classforajaxtooltip';
1859 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1860 $label = '';
1861 } else {
1862 $label = implode($this->getTooltipContentArray($params));
1863 }
1864
1865 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1866
1867 if ($short) {
1868 return $url;
1869 }
1870
1871 if ($option !== 'nolink') {
1872 // Add param to save lastsearch_values or not
1873 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1874 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1875 $add_save_lastsearch_values = 1;
1876 }
1877 if ($add_save_lastsearch_values) {
1878 $url .= '&save_lastsearch_values=1';
1879 }
1880 }
1881
1882 $linkclose = '';
1883 if (empty($notooltip)) {
1884 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1885 $label = $langs->trans("Shipment");
1886 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1887 }
1888 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1889 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1890 }
1891
1892 $linkstart = '<a href="'.$url.'"';
1893 $linkstart .= $linkclose.'>';
1894 $linkend = '</a>';
1895
1896 $result .= $linkstart;
1897 if ($withpicto) {
1898 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1899 }
1900 if ($withpicto != 2) {
1901 $result .= $this->ref;
1902 }
1903 $result .= $linkend;
1904 global $action;
1905 $hookmanager->initHooks(array($this->element . 'dao'));
1906 $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1907 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1908 if ($reshook > 0) {
1909 $result = $hookmanager->resPrint;
1910 } else {
1911 $result .= $hookmanager->resPrint;
1912 }
1913 return $result;
1914 }
1915
1922 public function getLibStatut($mode = 0)
1923 {
1924 return $this->LibStatut($this->statut, $mode);
1925 }
1926
1927 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1935 public function LibStatut($status, $mode)
1936 {
1937 // phpcs:enable
1938 global $langs;
1939
1940 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
1941 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
1942
1943 $statusType = 'status'.$status;
1944 if ($status == self::STATUS_VALIDATED) {
1945 $statusType = 'status4';
1946 }
1947 if ($status == self::STATUS_CLOSED) {
1948 $statusType = 'status6';
1949 }
1950 if ($status == self::STATUS_CANCELED) {
1951 $statusType = 'status9';
1952 }
1953
1954 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
1955 }
1956
1964 public function getKanbanView($option = '', $arraydata = null)
1965 {
1966 global $langs, $conf;
1967
1968 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1969
1970 $return = '<div class="box-flex-item box-flex-grow-zero">';
1971 $return .= '<div class="info-box info-box-sm">';
1972 $return .= '<div class="info-box-icon bg-infobox-action">';
1973 $return .= img_picto('', 'order');
1974 $return .= '</div>';
1975 $return .= '<div class="info-box-content">';
1976 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
1977 if ($selected >= 0) {
1978 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1979 }
1980 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
1981 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
1982 }
1983 if (property_exists($this, 'total_ht')) {
1984 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'</div>';
1985 }
1986 if (method_exists($this, 'getLibStatut')) {
1987 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1988 }
1989 $return .= '</div>';
1990 $return .= '</div>';
1991 $return .= '</div>';
1992
1993 return $return;
1994 }
1995
2003 public function initAsSpecimen()
2004 {
2005 global $langs;
2006
2007 $now = dol_now();
2008
2009 dol_syslog(get_class($this)."::initAsSpecimen");
2010
2011 $order = new Commande($this->db);
2012 $order->initAsSpecimen();
2013
2014 // Initialise parametres
2015 $this->id = 0;
2016 $this->ref = 'SPECIMEN';
2017 $this->specimen = 1;
2019 $this->livraison_id = 0;
2020 $this->date = $now;
2021 $this->date_creation = $now;
2022 $this->date_valid = $now;
2023 $this->date_delivery = $now + 24 * 3600;
2024 $this->date_expedition = $now + 24 * 3600;
2025
2026 $this->entrepot_id = 0;
2027 $this->fk_delivery_address = 0;
2028 $this->socid = 1;
2029
2030 $this->commande_id = 0;
2031 $this->commande = $order;
2032
2033 $this->origin_id = 1;
2034 $this->origin = 'commande';
2035
2036 $this->note_private = 'Private note';
2037 $this->note_public = 'Public note';
2038
2039 $nbp = 5;
2040 $xnbp = 0;
2041 while ($xnbp < $nbp) {
2042 $line = new ExpeditionLigne($this->db);
2043 $line->product_desc = $langs->trans("Description")." ".$xnbp;
2044 $line->product_label = $langs->trans("Description")." ".$xnbp;
2045 $line->qty = 10;
2046 $line->qty_asked = 5;
2047 $line->qty_shipped = 4;
2048 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2049
2050 $this->lines[] = $line;
2051 $xnbp++;
2052 }
2053 }
2054
2055 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2064 public function set_date_livraison($user, $delivery_date)
2065 {
2066 // phpcs:enable
2067 return $this->setDeliveryDate($user, $delivery_date);
2068 }
2069
2077 public function setDeliveryDate($user, $delivery_date)
2078 {
2079 if ($user->hasRight('expedition', 'creer')) {
2080 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2081 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2082 $sql .= " WHERE rowid = ".((int) $this->id);
2083
2084 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2085 $resql = $this->db->query($sql);
2086 if ($resql) {
2087 $this->date_delivery = $delivery_date;
2088 return 1;
2089 } else {
2090 $this->error = $this->db->error();
2091 return -1;
2092 }
2093 } else {
2094 return -2;
2095 }
2096 }
2097
2098 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2104 public function fetch_delivery_methods()
2105 {
2106 // phpcs:enable
2107 global $langs;
2108 $this->meths = array();
2109
2110 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2111 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2112 $sql .= " WHERE em.active = 1";
2113 $sql .= " ORDER BY em.libelle ASC";
2114
2115 $resql = $this->db->query($sql);
2116 if ($resql) {
2117 while ($obj = $this->db->fetch_object($resql)) {
2118 $label = $langs->trans('SendingMethod'.$obj->code);
2119 $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2120 }
2121 }
2122 }
2123
2124 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2131 public function list_delivery_methods($id = '')
2132 {
2133 // phpcs:enable
2134 global $langs;
2135
2136 $this->listmeths = array();
2137 $i = 0;
2138
2139 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2140 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2141 if ($id != '') {
2142 $sql .= " WHERE em.rowid=".((int) $id);
2143 }
2144
2145 $resql = $this->db->query($sql);
2146 if ($resql) {
2147 while ($obj = $this->db->fetch_object($resql)) {
2148 $this->listmeths[$i]['rowid'] = $obj->rowid;
2149 $this->listmeths[$i]['code'] = $obj->code;
2150 $label = $langs->trans('SendingMethod'.$obj->code);
2151 $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2152 $this->listmeths[$i]['description'] = $obj->description;
2153 $this->listmeths[$i]['tracking'] = $obj->tracking;
2154 $this->listmeths[$i]['active'] = $obj->active;
2155 $i++;
2156 }
2157 }
2158 }
2159
2166 public function getUrlTrackingStatus($value = '')
2167 {
2168 if (!empty($this->shipping_method_id)) {
2169 $sql = "SELECT em.code, em.tracking";
2170 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2171 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2172
2173 $resql = $this->db->query($sql);
2174 if ($resql) {
2175 if ($obj = $this->db->fetch_object($resql)) {
2176 $tracking = $obj->tracking;
2177 }
2178 }
2179 }
2180
2181 if (!empty($tracking) && !empty($value)) {
2182 $url = str_replace('{TRACKID}', $value, $tracking);
2183 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">'.($value ? $value : 'url').'</a>', $url, $url);
2184 } else {
2185 $this->tracking_url = $value;
2186 }
2187 }
2188
2194 public function setClosed()
2195 {
2196 global $conf, $user;
2197
2198 $error = 0;
2199
2200 // Protection. This avoid to move stock later when we should not
2201 if ($this->statut == self::STATUS_CLOSED) {
2202 return 0;
2203 }
2204
2205 $this->db->begin();
2206
2207 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED;
2208 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2209
2210 $resql = $this->db->query($sql);
2211 if ($resql) {
2212 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2213 if ($this->origin == 'commande' && $this->origin_id > 0) {
2214 $order = new Commande($this->db);
2215 $order->fetch($this->origin_id);
2216
2217 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2218
2219 $shipments_match_order = 1;
2220 foreach ($order->lines as $line) {
2221 $lineid = $line->id;
2222 $qty = $line->qty;
2223 if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2224 $shipments_match_order = 0;
2225 $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';
2226 dol_syslog($text);
2227 break;
2228 }
2229 }
2230 if ($shipments_match_order) {
2231 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');
2232 // We close the order
2233 $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2234 }
2235 }
2236
2237 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2238 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2239
2240 // If stock increment is done on closing
2241 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2242 $result = $this->manageStockMvtOnEvt($user);
2243 if ($result<0) {
2244 $error++;
2245 }
2246 }
2247
2248 // Call trigger
2249 if (!$error) {
2250 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2251 if ($result < 0) {
2252 $error++;
2253 }
2254 }
2255 } else {
2256 dol_print_error($this->db);
2257 $error++;
2258 }
2259
2260 if (!$error) {
2261 $this->db->commit();
2262 return 1;
2263 } else {
2266
2267 $this->db->rollback();
2268 return -1;
2269 }
2270 }
2271
2281 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2282 {
2283 global $langs;
2284
2285 $error=0;
2286
2287 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2288
2289 $langs->load("agenda");
2290
2291 // Loop on each product line to add a stock movement
2292 $sql = "SELECT cd.fk_product, cd.subprice,";
2293 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2294 $sql .= " e.ref,";
2295 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2296 $sql .= " cd.rowid as cdid, ed.rowid as edid";
2297 $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2298 $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2299 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2300 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2301 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2302 $sql .= " AND cd.rowid = ed.fk_origin_line";
2303
2304 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2305 $resql = $this->db->query($sql);
2306 if ($resql) {
2307 $cpt = $this->db->num_rows($resql);
2308 for ($i = 0; $i < $cpt; $i++) {
2309 $obj = $this->db->fetch_object($resql);
2310 if (empty($obj->edbrowid)) {
2311 $qty = $obj->qty;
2312 } else {
2313 $qty = $obj->edbqty;
2314 }
2315 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2316 continue;
2317 }
2318 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2319
2320 $mouvS = new MouvementStock($this->db);
2321 $mouvS->origin = &$this;
2322 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2323
2324 if (empty($obj->edbrowid)) {
2325 // line without batch detail
2326
2327 // 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
2328 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
2329 if ($result < 0) {
2330 $this->error = $mouvS->error;
2331 $this->errors = $mouvS->errors;
2332 $error++;
2333 break;
2334 }
2335 } else {
2336 // line with batch detail
2337
2338 // 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
2339 $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);
2340 if ($result < 0) {
2341 $this->error = $mouvS->error;
2342 $this->errors = $mouvS->errors;
2343 $error++;
2344 break;
2345 }
2346 }
2347
2348 // 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
2349 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2350 $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)";
2351 $resqldelete = $this->db->query($sqldelete);
2352 // We do not test error, it can fails if there is child in batch details
2353 }
2354 } else {
2355 $this->error = $this->db->lasterror();
2356 $this->errors[] = $this->db->lasterror();
2357 $error ++;
2358 }
2359
2360 return $error;
2361 }
2362
2368 public function setBilled()
2369 {
2370 global $user;
2371 $error = 0;
2372
2373 $this->db->begin();
2374
2375 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET billed = 1';
2376 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2377
2378 $resql = $this->db->query($sql);
2379 if ($resql) {
2380 $this->billed = 1;
2381
2382 // Call trigger
2383 $result = $this->call_trigger('SHIPPING_BILLED', $user);
2384 if ($result < 0) {
2385 $this->billed = 0;
2386 $error++;
2387 }
2388 } else {
2389 $error++;
2390 $this->errors[] = $this->db->lasterror;
2391 }
2392
2393 if (empty($error)) {
2394 $this->db->commit();
2395 return 1;
2396 } else {
2397 $this->db->rollback();
2398 return -1;
2399 }
2400 }
2401
2409 public function setDraft($user, $notrigger = 0)
2410 {
2411 // Protection
2412 if ($this->statut <= self::STATUS_DRAFT) {
2413 return 0;
2414 }
2415
2416 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2417 }
2418
2424 public function reOpen()
2425 {
2426 global $conf, $langs, $user;
2427
2428 $error = 0;
2429
2430 // Protection. This avoid to move stock later when we should not
2431 if ($this->statut == self::STATUS_VALIDATED) {
2432 return 0;
2433 }
2434
2435 $this->db->begin();
2436
2437 $oldbilled = $this->billed;
2438
2439 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2440 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2441
2442 $resql = $this->db->query($sql);
2443 if ($resql) {
2445 $this->billed = 0;
2446
2447 // If stock increment is done on closing
2448 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2449 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2450
2451 $langs->load("agenda");
2452
2453 // Loop on each product line to add a stock movement
2454 // TODO possibilite d'expedier a partir d'une propale ou autre origine
2455 $sql = "SELECT cd.fk_product, cd.subprice,";
2456 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2457 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2458 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2459 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2460 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2461 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2462 $sql .= " AND cd.rowid = ed.fk_origin_line";
2463
2464 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2465 $resql = $this->db->query($sql);
2466 if ($resql) {
2467 $cpt = $this->db->num_rows($resql);
2468 for ($i = 0; $i < $cpt; $i++) {
2469 $obj = $this->db->fetch_object($resql);
2470 if (empty($obj->edbrowid)) {
2471 $qty = $obj->qty;
2472 } else {
2473 $qty = $obj->edbqty;
2474 }
2475 if ($qty <= 0) {
2476 continue;
2477 }
2478 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2479
2480 //var_dump($this->lines[$i]);
2481 $mouvS = new MouvementStock($this->db);
2482 $mouvS->origin = &$this;
2483 $mouvS->setOrigin($this->element, $this->id);
2484
2485 if (empty($obj->edbrowid)) {
2486 // line without batch detail
2487
2488 // 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
2489 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2490 if ($result < 0) {
2491 $this->error = $mouvS->error;
2492 $this->errors = $mouvS->errors;
2493 $error++;
2494 break;
2495 }
2496 } else {
2497 // line with batch detail
2498
2499 // 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
2500 $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);
2501 if ($result < 0) {
2502 $this->error = $mouvS->error;
2503 $this->errors = $mouvS->errors;
2504 $error++;
2505 break;
2506 }
2507 }
2508 }
2509 } else {
2510 $this->error = $this->db->lasterror();
2511 $error++;
2512 }
2513 }
2514
2515 if (!$error) {
2516 // Call trigger
2517 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2518 if ($result < 0) {
2519 $error++;
2520 }
2521 }
2522 } else {
2523 $error++;
2524 $this->errors[] = $this->db->lasterror();
2525 }
2526
2527 if (!$error) {
2528 $this->db->commit();
2529 return 1;
2530 } else {
2531 $this->statut = self::STATUS_CLOSED;
2532 $this->billed = $oldbilled;
2533 $this->db->rollback();
2534 return -1;
2535 }
2536 }
2537
2549 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2550 {
2551 global $conf;
2552
2553 $outputlangs->load("products");
2554
2555 if (!dol_strlen($modele)) {
2556 $modele = 'rouget';
2557
2558 if (!empty($this->model_pdf)) {
2559 $modele = $this->model_pdf;
2560 } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
2561 $modele = $conf->global->EXPEDITION_ADDON_PDF;
2562 }
2563 }
2564
2565 $modelpath = "core/modules/expedition/doc/";
2566
2567 $this->fetch_origin();
2568
2569 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2570 }
2571
2580 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2581 {
2582 $tables = array(
2583 'expedition'
2584 );
2585
2586 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2587 }
2588}
2589
2590
2595{
2599 public $element = 'expeditiondet';
2600
2604 public $table_element = 'expeditiondet';
2605
2606
2613 public $line_id; // deprecated
2614
2620
2626 public $fk_origin; // Example: 'orderline'
2627
2631 public $fk_origin_line;
2632
2636 public $fk_expedition;
2637
2641 public $db;
2642
2646 public $qty;
2647
2651 public $qty_shipped;
2652
2656 public $fk_product;
2657
2658 // detail of lot and qty = array(id in llx_expeditiondet_batch, fk_expeditiondet, batch, qty, fk_origin_stock)
2659 // We can use this to know warehouse planned to be used for each lot.
2660 public $detail_batch;
2661
2662 // detail of warehouses and qty
2663 // We can use this to know warehouse when there is no lot.
2664 public $details_entrepot;
2665
2666
2670 public $entrepot_id;
2671
2672
2676 public $qty_asked;
2677
2682 public $ref;
2683
2687 public $product_ref;
2688
2693 public $libelle;
2694
2698 public $product_label;
2699
2705 public $desc;
2706
2710 public $product_desc;
2711
2716 public $product_type = 0;
2717
2721 public $rang;
2722
2726 public $weight;
2727 public $weight_units;
2728
2732 public $length;
2733 public $length_units;
2734
2738 public $surface;
2739 public $surface_units;
2740
2744 public $volume;
2745 public $volume_units;
2746
2747 // Invoicing
2748 public $remise_percent;
2749 public $tva_tx;
2750
2754 public $total_ht;
2755
2759 public $total_ttc;
2760
2764 public $total_tva;
2765
2769 public $total_localtax1;
2770
2774 public $total_localtax2;
2775
2776
2782 public function __construct($db)
2783 {
2784 $this->db = $db;
2785 }
2786
2793 public function fetch($rowid)
2794 {
2795 $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_origin_line, ed.qty, ed.rang';
2796 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
2797 $sql .= ' WHERE ed.rowid = '.((int) $rowid);
2798 $result = $this->db->query($sql);
2799 if ($result) {
2800 $objp = $this->db->fetch_object($result);
2801 $this->id = $objp->rowid;
2802 $this->fk_expedition = $objp->fk_expedition;
2803 $this->entrepot_id = $objp->fk_entrepot;
2804 $this->fk_origin_line = $objp->fk_origin_line;
2805 $this->qty = $objp->qty;
2806 $this->rang = $objp->rang;
2807
2808 $this->db->free($result);
2809
2810 return 1;
2811 } else {
2812 $this->errors[] = $this->db->lasterror();
2813 $this->error = $this->db->lasterror();
2814 return -1;
2815 }
2816 }
2817
2825 public function insert($user, $notrigger = 0)
2826 {
2827 $error = 0;
2828
2829 // Check parameters
2830 if (empty($this->fk_expedition) || empty($this->fk_origin_line) || !is_numeric($this->qty)) {
2831 $this->error = 'ErrorMandatoryParametersNotProvided';
2832 return -1;
2833 }
2834
2835 $this->db->begin();
2836
2837 if (empty($this->rang)) {
2838 $this->rang = 0;
2839 }
2840
2841 // Rank to use
2842 $ranktouse = $this->rang;
2843 if ($ranktouse == -1) {
2844 $rangmax = $this->line_max($this->fk_expedition);
2845 $ranktouse = $rangmax + 1;
2846 }
2847
2848 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
2849 $sql .= "fk_expedition";
2850 $sql .= ", fk_entrepot";
2851 $sql .= ", fk_origin_line";
2852 $sql .= ", qty";
2853 $sql .= ", rang";
2854 $sql .= ") VALUES (";
2855 $sql .= $this->fk_expedition;
2856 $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
2857 $sql .= ", ".((int) $this->fk_origin_line);
2858 $sql .= ", ".price2num($this->qty, 'MS');
2859 $sql .= ", ".((int) $ranktouse);
2860 $sql .= ")";
2861
2862 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
2863 $resql = $this->db->query($sql);
2864 if ($resql) {
2865 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
2866
2867 if (!$error) {
2868 $result = $this->insertExtraFields();
2869 if ($result < 0) {
2870 $error++;
2871 }
2872 }
2873
2874 if (!$error && !$notrigger) {
2875 // Call trigger
2876 $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
2877 if ($result < 0) {
2878 $error++;
2879 }
2880 // End call triggers
2881 }
2882
2883 if ($error) {
2884 foreach ($this->errors as $errmsg) {
2885 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2886 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2887 }
2888 }
2889 } else {
2890 $error++;
2891 }
2892
2893 if ($error) {
2894 $this->db->rollback();
2895 return -1;
2896 } else {
2897 $this->db->commit();
2898 return $this->id;
2899 }
2900 }
2901
2909 public function delete($user = null, $notrigger = 0)
2910 {
2911 $error = 0;
2912
2913 $this->db->begin();
2914
2915 // delete batch expedition line
2916 if (isModEnabled('productbatch')) {
2917 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
2918 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
2919
2920 if (!$this->db->query($sql)) {
2921 $this->errors[] = $this->db->lasterror()." - sql=$sql";
2922 $error++;
2923 }
2924 }
2925
2926 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
2927 $sql .= " WHERE rowid = ".((int) $this->id);
2928
2929 if (!$error && $this->db->query($sql)) {
2930 // Remove extrafields
2931 if (!$error) {
2932 $result = $this->deleteExtraFields();
2933 if ($result < 0) {
2934 $this->errors[] = $this->error;
2935 $error++;
2936 }
2937 }
2938 if (!$error && !$notrigger) {
2939 // Call trigger
2940 $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
2941 if ($result < 0) {
2942 $this->errors[] = $this->error;
2943 $error++;
2944 }
2945 // End call triggers
2946 }
2947 } else {
2948 $this->errors[] = $this->db->lasterror()." - sql=$sql";
2949 $error++;
2950 }
2951
2952 if (!$error) {
2953 $this->db->commit();
2954 return 1;
2955 } else {
2956 foreach ($this->errors as $errmsg) {
2957 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2958 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2959 }
2960 $this->db->rollback();
2961 return -1 * $error;
2962 }
2963 }
2964
2972 public function update($user = null, $notrigger = 0)
2973 {
2974 $error = 0;
2975
2976 dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
2977
2978 $this->db->begin();
2979
2980 // Clean parameters
2981 if (empty($this->qty)) {
2982 $this->qty = 0;
2983 }
2984 $qty = price2num($this->qty);
2985 $remainingQty = 0;
2986 $batch = null;
2987 $batch_id = null;
2988 $expedition_batch_id = null;
2989 if (is_array($this->detail_batch)) { // array of ExpeditionLineBatch
2990 if (count($this->detail_batch) > 1) {
2991 dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
2992 $this->errors[] = 'ErrorBadParameters';
2993 $error++;
2994 } else {
2995 $batch = $this->detail_batch[0]->batch;
2996 $batch_id = $this->detail_batch[0]->fk_origin_stock;
2997 $expedition_batch_id = $this->detail_batch[0]->id;
2998 if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id) {
2999 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
3000 $this->errors[] = 'ErrorBadParameters';
3001 $error++;
3002 }
3003 $qty = price2num($this->detail_batch[0]->qty);
3004 }
3005 } elseif (!empty($this->detail_batch)) {
3006 $batch = $this->detail_batch->batch;
3007 $batch_id = $this->detail_batch->fk_origin_stock;
3008 $expedition_batch_id = $this->detail_batch->id;
3009 if ($this->entrepot_id != $this->detail_batch->entrepot_id) {
3010 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
3011 $this->errors[] = 'ErrorBadParameters';
3012 $error++;
3013 }
3014 $qty = price2num($this->detail_batch->qty);
3015 }
3016
3017 // check parameters
3018 if (!isset($this->id) || !isset($this->entrepot_id)) {
3019 dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
3020 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
3021 $error++;
3022 return -1;
3023 }
3024
3025 // update lot
3026
3027 if (!empty($batch) && isModEnabled('productbatch')) {
3028 dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id, batch=$batch");
3029
3030 if (empty($batch_id) || empty($this->fk_product)) {
3031 dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
3032 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
3033 $error++;
3034 }
3035
3036 // fetch remaining lot qty
3037 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
3038
3039 if (!$error && ($lotArray = $shipmentlinebatch->fetchAll($this->id)) < 0) {
3040 $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
3041 $error++;
3042 } else {
3043 // caculate new total line qty
3044 foreach ($lotArray as $lot) {
3045 if ($expedition_batch_id != $lot->id) {
3046 $remainingQty += $lot->qty;
3047 }
3048 }
3049 $qty += $remainingQty;
3050
3051 //fetch lot details
3052
3053 // fetch from product_lot
3054 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
3055 $lot = new Productlot($this->db);
3056 if ($lot->fetch(0, $this->fk_product, $batch) < 0) {
3057 $this->errors[] = $lot->errors;
3058 $error++;
3059 }
3060 if (!$error && !empty($expedition_batch_id)) {
3061 // delete lot expedition line
3062 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
3063 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
3064 $sql .= " AND rowid = ".((int) $expedition_batch_id);
3065
3066 if (!$this->db->query($sql)) {
3067 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3068 $error++;
3069 }
3070 }
3071 if (!$error && $this->detail_batch->qty > 0) {
3072 // create lot expedition line
3073 if (isset($lot->id)) {
3074 $shipmentLot = new ExpeditionLineBatch($this->db);
3075 $shipmentLot->batch = $lot->batch;
3076 $shipmentLot->eatby = $lot->eatby;
3077 $shipmentLot->sellby = $lot->sellby;
3078 $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id;
3079 $shipmentLot->qty = $this->detail_batch->qty;
3080 $shipmentLot->fk_origin_stock = $batch_id;
3081 if ($shipmentLot->create($this->id) < 0) {
3082 $this->errors = $shipmentLot->errors;
3083 $error++;
3084 }
3085 }
3086 }
3087 }
3088 }
3089 if (!$error) {
3090 // update line
3091 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3092 $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
3093 $sql .= " , qty = ".((float) price2num($qty, 'MS'));
3094 $sql .= " WHERE rowid = ".((int) $this->id);
3095
3096 if (!$this->db->query($sql)) {
3097 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3098 $error++;
3099 }
3100 }
3101
3102 if (!$error) {
3103 if (!$error) {
3104 $result = $this->insertExtraFields();
3105 if ($result < 0) {
3106 $this->errors[] = $this->error;
3107 $error++;
3108 }
3109 }
3110 }
3111
3112 if (!$error && !$notrigger) {
3113 // Call trigger
3114 $result = $this->call_trigger('LINESHIPPING_MODIFY', $user);
3115 if ($result < 0) {
3116 $this->errors[] = $this->error;
3117 $error++;
3118 }
3119 // End call triggers
3120 }
3121 if (!$error) {
3122 $this->db->commit();
3123 return 1;
3124 } else {
3125 foreach ($this->errors as $errmsg) {
3126 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3127 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3128 }
3129 $this->db->rollback();
3130 return -1 * $error;
3131 }
3132 }
3133}
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Definition security.php:604
$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.
deleteline($user, $lineid)
Delete detail line.
setDraft($user, $notrigger=0)
Set draft status.
getUrlTrackingStatus($value='')
Forge an set tracking url.
setClosed()
Classify the shipping as closed (this record also the stock movement)
__construct($db)
Constructor.
fetch_lines()
Load lines.
list_delivery_methods($id='')
Fetch all deliveries method and return an array.
create($user, $notrigger=0)
Create expedition en base.
LibStatut($status, $mode)
Return label of a status.
setBilled()
Classify the shipping as invoiced (used for exemple by trigger when WORKFLOW_SHIPPING_CLASSIFY_BILLED...
addline_batch($dbatch, $array_options=0)
Add a shipment line with batch record.
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.
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.
getNextNumRef($soc)
Return next expedition ref.
const STATUS_CLOSED
Closed status.
const STATUS_VALIDATED
Validated status.
manageStockMvtOnEvt($user, $labelmovement='ShipmentClassifyClosedInDolibarr')
Manage Stock MVt onb Close or valid Shipment.
create_line_batch($line_ext, $array_options=0)
Create the detail of the expedition line.
valid($user, $notrigger=0)
Validate object and update stock if option enabled.
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.
addline($entrepot_id, $id, $qty, $array_options=0)
Add an expedition line.
fetch($id, $ref='', $ref_ext='', $notused='')
Get object and lines from database.
reOpen()
Classify the shipping as validated/opened.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
create_line($entrepot_id, $origin_line_id, $qty, $rang=0, $array_options=null)
Create a expedition line.
Classe 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($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:62
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
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.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
publicphonebutton2 phonegreen basiclayout basiclayout TotalHT VATCode TotalVAT TotalLT1 TotalLT2 TotalTTC TotalHT clearboth nowraponall right right takeposterminal SELECT e e e e e statut
Definition invoice.php:1926
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:86
$conf db user
Definition repair.php:125