dolibarr 18.0.6
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-2022 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 $picto = 'dolly';
86
87
91 public $fields = array();
92
96 public $user_author_id;
97
101 public $fk_user_author;
102
103 public $socid;
104
110 public $ref_client;
111
115 public $ref_customer;
116
117 public $brouillon;
118
122 public $entrepot_id;
123
127 public $tracking_number;
128
132 public $tracking_url;
133 public $billed;
134
138 public $model_pdf;
139
140 public $trueWeight;
141 public $weight_units;
142 public $trueWidth;
143 public $width_units;
144 public $trueHeight;
145 public $height_units;
146 public $trueDepth;
147 public $depth_units;
148 // A denormalized value
149 public $trueSize;
150
154 public $date_delivery;
155
160 public $date;
161
167
172 public $date_shipping;
173
177 public $date_creation;
178
182 public $date_valid;
183
184 public $meths;
185 public $listmeths; // List of carriers
186
190 public $commande_id;
191
195 public $commande;
196
200 public $lines = array();
201
202 // Multicurrency
206 public $fk_multicurrency;
207
211 public $multicurrency_code;
212 public $multicurrency_tx;
213 public $multicurrency_total_ht;
214 public $multicurrency_total_tva;
215 public $multicurrency_total_ttc;
216
220 const STATUS_DRAFT = 0;
221
226
230 const STATUS_CLOSED = 2;
231
235 const STATUS_CANCELED = -1;
236
237
243 public function __construct($db)
244 {
245 global $conf;
246
247 $this->db = $db;
248
249 // List of long language codes for status
250 $this->statuts = array();
251 $this->statuts[-1] = 'StatusSendingCanceled';
252 $this->statuts[0] = 'StatusSendingDraft';
253 $this->statuts[1] = 'StatusSendingValidated';
254 $this->statuts[2] = 'StatusSendingProcessed';
255
256 // List of short language codes for status
257 $this->statuts_short = array();
258 $this->statuts_short[-1] = 'StatusSendingCanceledShort';
259 $this->statuts_short[0] = 'StatusSendingDraftShort';
260 $this->statuts_short[1] = 'StatusSendingValidatedShort';
261 $this->statuts_short[2] = 'StatusSendingProcessedShort';
262 }
263
270 public function getNextNumRef($soc)
271 {
272 global $langs, $conf;
273 $langs->load("sendings");
274
275 if (!empty($conf->global->EXPEDITION_ADDON_NUMBER)) {
276 $mybool = false;
277
278 $file = $conf->global->EXPEDITION_ADDON_NUMBER.".php";
279 $classname = $conf->global->EXPEDITION_ADDON_NUMBER;
280
281 // Include file with class
282 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
283
284 foreach ($dirmodels as $reldir) {
285 $dir = dol_buildpath($reldir."core/modules/expedition/");
286
287 // Load file with numbering class (if found)
288 $mybool |= @include_once $dir.$file;
289 }
290
291 if (!$mybool) {
292 dol_print_error('', "Failed to include file ".$file);
293 return '';
294 }
295
296 $obj = new $classname();
297 $numref = "";
298 $numref = $obj->getNextValue($soc, $this);
299
300 if ($numref != "") {
301 return $numref;
302 } else {
303 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
304 return "";
305 }
306 } else {
307 print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
308 return "";
309 }
310 }
311
319 public function create($user, $notrigger = 0)
320 {
321 global $conf, $hookmanager;
322
323 $now = dol_now();
324
325 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
326 $error = 0;
327
328 // Clean parameters
329 $this->brouillon = 1;
330 $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
331 if (empty($this->fk_project)) {
332 $this->fk_project = 0;
333 }
334
335 $this->user = $user;
336
337
338 $this->db->begin();
339
340 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
341 $sql .= "ref";
342 $sql .= ", entity";
343 $sql .= ", ref_customer";
344 $sql .= ", ref_ext";
345 $sql .= ", date_creation";
346 $sql .= ", fk_user_author";
347 $sql .= ", date_expedition";
348 $sql .= ", date_delivery";
349 $sql .= ", fk_soc";
350 $sql .= ", fk_projet";
351 $sql .= ", fk_address";
352 $sql .= ", fk_shipping_method";
353 $sql .= ", tracking_number";
354 $sql .= ", weight";
355 $sql .= ", size";
356 $sql .= ", width";
357 $sql .= ", height";
358 $sql .= ", weight_units";
359 $sql .= ", size_units";
360 $sql .= ", note_private";
361 $sql .= ", note_public";
362 $sql .= ", model_pdf";
363 $sql .= ", fk_incoterms, location_incoterms";
364 $sql .= ") VALUES (";
365 $sql .= "'(PROV)'";
366 $sql .= ", ".((int) $conf->entity);
367 $sql .= ", ".($this->ref_customer ? "'".$this->db->escape($this->ref_customer)."'" : "null");
368 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
369 $sql .= ", '".$this->db->idate($now)."'";
370 $sql .= ", ".((int) $user->id);
371 $sql .= ", ".($this->date_expedition > 0 ? "'".$this->db->idate($this->date_expedition)."'" : "null");
372 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
373 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
374 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
375 $sql .= ", ".($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
376 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
377 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
378 $sql .= ", ".(is_numeric($this->weight) ? $this->weight : 'NULL');
379 $sql .= ", ".(is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
380 $sql .= ", ".(is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
381 $sql .= ", ".(is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
382 $sql .= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
383 $sql .= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
384 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
385 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
386 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
387 $sql .= ", ".(int) $this->fk_incoterms;
388 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
389 $sql .= ")";
390
391 dol_syslog(get_class($this)."::create", LOG_DEBUG);
392 $resql = $this->db->query($sql);
393 if ($resql) {
394 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
395
396 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
397 $sql .= " SET ref = '(PROV".$this->id.")'";
398 $sql .= " WHERE rowid = ".((int) $this->id);
399
400 dol_syslog(get_class($this)."::create", LOG_DEBUG);
401 if ($this->db->query($sql)) {
402 // Insert of lines
403 $num = count($this->lines);
404 for ($i = 0; $i < $num; $i++) {
405 if (empty($this->lines[$i]->product_type) || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
406 if (!isset($this->lines[$i]->detail_batch)) { // no batch management
407 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) {
408 $error++;
409 }
410 } else { // with batch management
411 if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
412 $error++;
413 }
414 }
415 }
416 }
417
418 if (!$error && $this->id && $this->origin_id) {
419 $ret = $this->add_object_linked();
420 if (!$ret) {
421 $error++;
422 }
423 }
424
425 // Actions on extra fields
426 if (!$error) {
427 $result = $this->insertExtraFields();
428 if ($result < 0) {
429 $error++;
430 }
431 }
432
433 if (!$error && !$notrigger) {
434 // Call trigger
435 $result = $this->call_trigger('SHIPPING_CREATE', $user);
436 if ($result < 0) {
437 $error++;
438 }
439 // End call triggers
440
441 if (!$error) {
442 $this->db->commit();
443 return $this->id;
444 } else {
445 foreach ($this->errors as $errmsg) {
446 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
447 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
448 }
449 $this->db->rollback();
450 return -1 * $error;
451 }
452 } else {
453 $error++;
454 $this->db->rollback();
455 return -3;
456 }
457 } else {
458 $error++;
459 $this->error = $this->db->lasterror()." - sql=$sql";
460 $this->db->rollback();
461 return -2;
462 }
463 } else {
464 $error++;
465 $this->error = $this->db->error()." - sql=$sql";
466 $this->db->rollback();
467 return -1;
468 }
469 }
470
471 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
482 public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = null)
483 {
484 //phpcs:enable
485 global $user;
486
487 $expeditionline = new ExpeditionLigne($this->db);
488 $expeditionline->fk_expedition = $this->id;
489 $expeditionline->entrepot_id = $entrepot_id;
490 $expeditionline->fk_origin_line = $origin_line_id;
491 $expeditionline->qty = $qty;
492 $expeditionline->rang = $rang;
493 $expeditionline->array_options = $array_options;
494
495 if (($lineId = $expeditionline->insert($user)) < 0) {
496 $this->errors[] = $expeditionline->error;
497 }
498 return $lineId;
499 }
500
501
502 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
510 public function create_line_batch($line_ext, $array_options = 0)
511 {
512 // phpcs:enable
513 $error = 0;
514 $stockLocationQty = array(); // associated array with batch qty in stock location
515
516 $tab = $line_ext->detail_batch;
517 // create stockLocation Qty array
518 foreach ($tab as $detbatch) {
519 if (!empty($detbatch->entrepot_id)) {
520 if (empty($stockLocationQty[$detbatch->entrepot_id])) {
521 $stockLocationQty[$detbatch->entrepot_id] = 0;
522 }
523 $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
524 }
525 }
526 // create shipment lines
527 foreach ($stockLocationQty as $stockLocation => $qty) {
528 $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
529 if ($line_id < 0) {
530 $error++;
531 } else {
532 // create shipment batch lines for stockLocation
533 foreach ($tab as $detbatch) {
534 if ($detbatch->entrepot_id == $stockLocation) {
535 if (!($detbatch->create($line_id) > 0)) { // Create an ExpeditionLineBatch
536 $this->errors = $detbatch->errors;
537 $error++;
538 }
539 }
540 }
541 }
542 }
543
544 if (!$error) {
545 return 1;
546 } else {
547 return -1;
548 }
549 }
550
560 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
561 {
562 global $conf;
563
564 // Check parameters
565 if (empty($id) && empty($ref) && empty($ref_ext)) {
566 return -1;
567 }
568
569 $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";
570 $sql .= ", e.date_valid";
571 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
572 $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
573 $sql .= ", e.fk_shipping_method, e.tracking_number";
574 $sql .= ", e.note_private, e.note_public";
575 $sql .= ', e.fk_incoterms, e.location_incoterms';
576 $sql .= ', i.libelle as label_incoterms';
577 $sql .= ', s.libelle as shipping_method';
578 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin";
579 $sql .= " FROM ".MAIN_DB_PREFIX."expedition as e";
580 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
581 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
582 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
583 $sql .= " WHERE e.entity IN (".getEntity('expedition').")";
584 if ($id) {
585 $sql .= " AND e.rowid = ".((int) $id);
586 }
587 if ($ref) {
588 $sql .= " AND e.ref='".$this->db->escape($ref)."'";
589 }
590 if ($ref_ext) {
591 $sql .= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
592 }
593
594 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
595 $result = $this->db->query($sql);
596 if ($result) {
597 if ($this->db->num_rows($result)) {
598 $obj = $this->db->fetch_object($result);
599
600 $this->id = $obj->rowid;
601 $this->entity = $obj->entity;
602 $this->ref = $obj->ref;
603 $this->socid = $obj->socid;
604 $this->ref_customer = $obj->ref_customer;
605 $this->ref_ext = $obj->ref_ext;
606 $this->status = $obj->fk_statut;
607 $this->statut = $this->status; // Deprecated
608 $this->user_author_id = $obj->fk_user_author;
609 $this->fk_user_author = $obj->fk_user_author;
610 $this->date_creation = $this->db->jdate($obj->date_creation);
611 $this->date_valid = $this->db->jdate($obj->date_valid);
612 $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
613 $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
614 $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
615 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planed
616 $this->fk_delivery_address = $obj->fk_address;
617 $this->model_pdf = $obj->model_pdf;
618 $this->modelpdf = $obj->model_pdf; // deprecated
619 $this->shipping_method_id = $obj->fk_shipping_method;
620 $this->shipping_method = $obj->shipping_method;
621 $this->tracking_number = $obj->tracking_number;
622 $this->origin = ($obj->origin ? $obj->origin : 'commande'); // For compatibility
623 $this->origin_id = $obj->origin_id;
624 $this->billed = $obj->billed;
625 $this->fk_project = $obj->fk_project;
626
627 $this->trueWeight = $obj->weight;
628 $this->weight_units = $obj->weight_units;
629
630 $this->trueWidth = $obj->width;
631 $this->width_units = $obj->size_units;
632 $this->trueHeight = $obj->height;
633 $this->height_units = $obj->size_units;
634 $this->trueDepth = $obj->size;
635 $this->depth_units = $obj->size_units;
636
637 $this->note_public = $obj->note_public;
638 $this->note_private = $obj->note_private;
639
640 // A denormalized value
641 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
642 $this->size_units = $obj->size_units;
643
644 //Incoterms
645 $this->fk_incoterms = $obj->fk_incoterms;
646 $this->location_incoterms = $obj->location_incoterms;
647 $this->label_incoterms = $obj->label_incoterms;
648
649 $this->db->free($result);
650
651 if ($this->statut == self::STATUS_DRAFT) {
652 $this->brouillon = 1;
653 }
654
655 // Tracking url
656 $this->getUrlTrackingStatus($obj->tracking_number);
657
658 // Thirdparty
659 $result = $this->fetch_thirdparty(); // TODO Remove this
660
661 // Retrieve extrafields
662 $this->fetch_optionals();
663
664 // Fix Get multicurrency param for transmited
665 if (isModEnabled('multicurrency')) {
666 if (!empty($this->multicurrency_code)) {
667 $this->multicurrency_code = $this->thirdparty->multicurrency_code;
668 }
669 if (!empty($conf->global->MULTICURRENCY_USE_ORIGIN_TX) && !empty($this->thirdparty->multicurrency_tx)) {
670 $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
671 }
672 }
673
674 /*
675 * Lines
676 */
677 $result = $this->fetch_lines();
678 if ($result < 0) {
679 return -3;
680 }
681
682 return 1;
683 } else {
684 dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
685 $this->error = 'Shipment with id '.$id.' not found';
686 return 0;
687 }
688 } else {
689 $this->error = $this->db->error();
690 return -1;
691 }
692 }
693
701 public function valid($user, $notrigger = 0)
702 {
703 global $conf, $langs;
704
705 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
706
707 dol_syslog(get_class($this)."::valid");
708
709 // Protection
710 if ($this->statut) {
711 dol_syslog(get_class($this)."::valid not in draft status", LOG_WARNING);
712 return 0;
713 }
714
715 if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->expedition->creer))
716 || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->expedition->shipping_advance->validate)))) {
717 $this->error = 'Permission denied';
718 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
719 return -1;
720 }
721
722 $this->db->begin();
723
724 $error = 0;
725
726 // Define new ref
727 $soc = new Societe($this->db);
728 $soc->fetch($this->socid);
729
730 // Class of company linked to order
731 $result = $soc->set_as_client();
732
733 // Define new ref
734 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
735 $numref = $this->getNextNumRef($soc);
736 } elseif (!empty($this->ref)) {
737 $numref = $this->ref;
738 } else {
739 $numref = "EXP".$this->id;
740 }
741 $this->newref = dol_sanitizeFileName($numref);
742
743 $now = dol_now();
744
745 // Validate
746 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
747 $sql .= " ref='".$this->db->escape($numref)."'";
748 $sql .= ", fk_statut = 1";
749 $sql .= ", date_valid = '".$this->db->idate($now)."'";
750 $sql .= ", fk_user_valid = ".$user->id;
751 $sql .= " WHERE rowid = ".((int) $this->id);
752
753 dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
754 $resql = $this->db->query($sql);
755 if (!$resql) {
756 $this->error = $this->db->lasterror();
757 $error++;
758 }
759
760 // If stock increment is done on sending (recommanded choice)
761 if (!$error && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)) {
762 $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
763 if ($result < 0) {
764 return -2;
765 }
766 }
767
768 // Change status of order to "shipment in process"
769 $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
770 if (!$ret) {
771 $error++;
772 }
773
774 if (!$error && !$notrigger) {
775 // Call trigger
776 $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
777 if ($result < 0) {
778 $error++;
779 }
780 // End call triggers
781 }
782
783 if (!$error) {
784 $this->oldref = $this->ref;
785
786 // Rename directory if dir was a temporary ref
787 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
788 // Now we rename also files into index
789 $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)."'";
790 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
791 $resql = $this->db->query($sql);
792 if (!$resql) {
793 $error++; $this->error = $this->db->lasterror();
794 }
795 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
796 $sql .= " WHERE filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
797 $resql = $this->db->query($sql);
798 if (!$resql) {
799 $error++; $this->error = $this->db->lasterror();
800 }
801
802 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
803 $oldref = dol_sanitizeFileName($this->ref);
804 $newref = dol_sanitizeFileName($numref);
805 $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
806 $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
807 if (!$error && file_exists($dirsource)) {
808 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
809
810 if (@rename($dirsource, $dirdest)) {
811 dol_syslog("Rename ok");
812 // Rename docs starting with $oldref with $newref
813 $listoffiles = dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
814 foreach ($listoffiles as $fileentry) {
815 $dirsource = $fileentry['name'];
816 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
817 $dirsource = $fileentry['path'].'/'.$dirsource;
818 $dirdest = $fileentry['path'].'/'.$dirdest;
819 @rename($dirsource, $dirdest);
820 }
821 }
822 }
823 }
824 }
825
826 // Set new ref and current status
827 if (!$error) {
828 $this->ref = $numref;
830 }
831
832 if (!$error) {
833 $this->db->commit();
834 return 1;
835 } else {
836 $this->db->rollback();
837 return -1 * $error;
838 }
839 }
840
841
842 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
849 public function create_delivery($user)
850 {
851 // phpcs:enable
852 global $conf;
853
854 if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
855 if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
856 // Expedition validee
857 include_once DOL_DOCUMENT_ROOT.'/delivery/class/delivery.class.php';
858 $delivery = new Delivery($this->db);
859 $result = $delivery->create_from_sending($user, $this->id);
860 if ($result > 0) {
861 return $result;
862 } else {
863 $this->error = $delivery->error;
864 return $result;
865 }
866 } else {
867 return 0;
868 }
869 } else {
870 return 0;
871 }
872 }
873
886 public function addline($entrepot_id, $id, $qty, $array_options = 0)
887 {
888 global $conf, $langs;
889
890 $num = count($this->lines);
891 $line = new ExpeditionLigne($this->db);
892
893 $line->entrepot_id = $entrepot_id;
894 $line->origin_line_id = $id;
895 $line->fk_origin_line = $id;
896 $line->qty = $qty;
897
898 $orderline = new OrderLine($this->db);
899 $orderline->fetch($id);
900
901 // Copy the rang of the order line to the expedition line
902 $line->rang = $orderline->rang;
903 $line->product_type = $orderline->product_type;
904
905 if (isModEnabled('stock') && !empty($orderline->fk_product)) {
906 $fk_product = $orderline->fk_product;
907
908 if (!($entrepot_id > 0) && empty($conf->global->STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS)) {
909 $langs->load("errors");
910 $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
911 return -1;
912 }
913
914 if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT)) {
915 $product = new Product($this->db);
916 $product->fetch($fk_product);
917
918 // Check must be done for stock of product into warehouse if $entrepot_id defined
919 if ($entrepot_id > 0) {
920 $product->load_stock('warehouseopen');
921 $product_stock = $product->stock_warehouse[$entrepot_id]->real;
922 } else {
923 $product_stock = $product->stock_reel;
924 }
925
926 $product_type = $product->type;
927 if ($product_type == 0 || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
928 $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
929 // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
930 if (!$isavirtualproduct || empty($conf->global->PRODUIT_SOUSPRODUITS) || ($isavirtualproduct && empty($conf->global->STOCK_EXCLUDE_VIRTUAL_PRODUCTS))) { // If STOCK_EXCLUDE_VIRTUAL_PRODUCTS is set, we do not manage stock for kits/virtual products.
931 if ($product_stock < $qty) {
932 $langs->load("errors");
933 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
934 $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
935
936 $this->db->rollback();
937 return -3;
938 }
939 }
940 }
941 }
942 }
943
944 // If product need a batch number, we should not have called this function but addline_batch instead.
945 // If this happen, we may have a bug in card.php page
946 if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
947 $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH '.$orderline->id.' '.$orderline->fk_product; //
948 return -4;
949 }
950
951 // extrafields
952 if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
953 $line->array_options = $array_options;
954 }
955
956 $this->lines[$num] = $line;
957
958 return 1;
959 }
960
961 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
969 public function addline_batch($dbatch, $array_options = 0)
970 {
971 // phpcs:enable
972 global $conf, $langs;
973
974 $num = count($this->lines);
975 if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
976 $line = new ExpeditionLigne($this->db);
977 $tab = array();
978 foreach ($dbatch['detail'] as $key => $value) {
979 if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
980 // $value['q']=qty to move
981 // $value['id_batch']=id into llx_product_batch of record to move
982 //var_dump($value);
983
984 $linebatch = new ExpeditionLineBatch($this->db);
985 $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
986 if ($ret < 0) {
987 $this->error = $linebatch->error;
988 return -1;
989 }
990 $linebatch->qty = $value['q'];
991 if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
992 $linebatch->batch = null;
993 }
994 $tab[] = $linebatch;
995
996 if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
997 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
998 $prod_batch = new Productbatch($this->db);
999 $prod_batch->fetch($value['id_batch']);
1000
1001 if ($prod_batch->qty < $linebatch->qty) {
1002 $langs->load("errors");
1003 $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1004 dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
1005 $this->db->rollback();
1006 return -1;
1007 }
1008 }
1009
1010 //var_dump($linebatch);
1011 }
1012 }
1013 $line->entrepot_id = $linebatch->entrepot_id;
1014 $line->origin_line_id = $dbatch['ix_l']; // deprecated
1015 $line->fk_origin_line = $dbatch['ix_l'];
1016 $line->qty = $dbatch['qty'];
1017 $line->detail_batch = $tab;
1018
1019 // extrafields
1020 if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1021 $line->array_options = $array_options;
1022 }
1023
1024 //var_dump($line);
1025 $this->lines[$num] = $line;
1026 return 1;
1027 }
1028 }
1029
1037 public function update($user = null, $notrigger = 0)
1038 {
1039 global $conf;
1040 $error = 0;
1041
1042 // Clean parameters
1043
1044 if (isset($this->ref)) {
1045 $this->ref = trim($this->ref);
1046 }
1047 if (isset($this->entity)) {
1048 $this->entity = (int) $this->entity;
1049 }
1050 if (isset($this->ref_customer)) {
1051 $this->ref_customer = trim($this->ref_customer);
1052 }
1053 if (isset($this->socid)) {
1054 $this->socid = (int) $this->socid;
1055 }
1056 if (isset($this->fk_user_author)) {
1057 $this->fk_user_author = (int) $this->fk_user_author;
1058 }
1059 if (isset($this->fk_user_valid)) {
1060 $this->fk_user_valid = (int) $this->fk_user_valid;
1061 }
1062 if (isset($this->fk_delivery_address)) {
1063 $this->fk_delivery_address = (int) $this->fk_delivery_address;
1064 }
1065 if (isset($this->shipping_method_id)) {
1066 $this->shipping_method_id = (int) $this->shipping_method_id;
1067 }
1068 if (isset($this->tracking_number)) {
1069 $this->tracking_number = trim($this->tracking_number);
1070 }
1071 if (isset($this->statut)) {
1072 $this->statut = (int) $this->statut;
1073 }
1074 if (isset($this->trueDepth)) {
1075 $this->trueDepth = trim($this->trueDepth);
1076 }
1077 if (isset($this->trueWidth)) {
1078 $this->trueWidth = trim($this->trueWidth);
1079 }
1080 if (isset($this->trueHeight)) {
1081 $this->trueHeight = trim($this->trueHeight);
1082 }
1083 if (isset($this->size_units)) {
1084 $this->size_units = trim($this->size_units);
1085 }
1086 if (isset($this->weight_units)) {
1087 $this->weight_units = trim($this->weight_units);
1088 }
1089 if (isset($this->trueWeight)) {
1090 $this->weight = trim($this->trueWeight);
1091 }
1092 if (isset($this->note_private)) {
1093 $this->note_private = trim($this->note_private);
1094 }
1095 if (isset($this->note_public)) {
1096 $this->note_public = trim($this->note_public);
1097 }
1098 if (isset($this->model_pdf)) {
1099 $this->model_pdf = trim($this->model_pdf);
1100 }
1101
1102 // Check parameters
1103 // Put here code to add control on parameters values
1104
1105 // Update request
1106 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
1107 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1108 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1109 $sql .= " ref_customer=".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1110 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1111 $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1112 $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1113 $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1114 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1115 $sql .= " date_expedition=".(dol_strlen($this->date_expedition) != 0 ? "'".$this->db->idate($this->date_expedition)."'" : 'null').",";
1116 $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1117 $sql .= " fk_address=".(isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null").",";
1118 $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1119 $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1120 $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1121 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1122 $sql .= " height=".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
1123 $sql .= " width=".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
1124 $sql .= " size_units=".(isset($this->size_units) ? $this->size_units : "null").",";
1125 $sql .= " size=".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1126 $sql .= " weight_units=".(isset($this->weight_units) ? $this->weight_units : "null").",";
1127 $sql .= " weight=".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1128 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1129 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1130 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1131 $sql .= " entity=".$conf->entity;
1132 $sql .= " WHERE rowid=".((int) $this->id);
1133
1134 $this->db->begin();
1135
1136 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1137 $resql = $this->db->query($sql);
1138 if (!$resql) {
1139 $error++; $this->errors[] = "Error ".$this->db->lasterror();
1140 }
1141
1142 if (!$error && !$notrigger) {
1143 // Call trigger
1144 $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1145 if ($result < 0) {
1146 $error++;
1147 }
1148 // End call triggers
1149 }
1150
1151 // Commit or rollback
1152 if ($error) {
1153 foreach ($this->errors as $errmsg) {
1154 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1155 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1156 }
1157 $this->db->rollback();
1158 return -1 * $error;
1159 } else {
1160 $this->db->commit();
1161 return 1;
1162 }
1163 }
1164
1165
1173 public function cancel($notrigger = 0, $also_update_stock = false)
1174 {
1175 global $conf, $langs, $user;
1176
1177 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1178
1179 $error = 0;
1180 $this->error = '';
1181
1182 $this->db->begin();
1183
1184 // Add a protection to refuse deleting if shipment has at least one delivery
1185 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1186 if (count($this->linkedObjectsIds) > 0) {
1187 $this->error = 'ErrorThereIsSomeDeliveries';
1188 $error++;
1189 }
1190
1191 if (!$error && !$notrigger) {
1192 // Call trigger
1193 $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1194 if ($result < 0) {
1195 $error++;
1196 }
1197 // End call triggers
1198 }
1199
1200 // Stock control
1201 if (!$error && isModEnabled('stock') &&
1202 (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
1203 ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1204 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1205
1206 $langs->load("agenda");
1207
1208 // Loop on each product line to add a stock movement and delete features
1209 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1210 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1211 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1212 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1213 $sql .= " AND cd.rowid = ed.fk_origin_line";
1214
1215 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1216 $resql = $this->db->query($sql);
1217 if ($resql) {
1218 $cpt = $this->db->num_rows($resql);
1219
1220 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1221
1222 for ($i = 0; $i < $cpt; $i++) {
1223 dol_syslog(get_class($this)."::delete movement index ".$i);
1224 $obj = $this->db->fetch_object($resql);
1225
1226 $mouvS = new MouvementStock($this->db);
1227 // we do not log origin because it will be deleted
1228 $mouvS->origin = null;
1229 // get lot/serial
1230 $lotArray = null;
1231 if (isModEnabled('productbatch')) {
1232 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1233 if (!is_array($lotArray)) {
1234 $error++;
1235 $this->errors[] = "Error ".$this->db->lasterror();
1236 }
1237 }
1238
1239 if (empty($lotArray)) {
1240 // no lot/serial
1241 // We increment stock of product (and sub-products)
1242 // We use warehouse selected for each line
1243 $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
1244 if ($result < 0) {
1245 $error++;
1246 $this->errors = array_merge($this->errors, $mouvS->errors);
1247 break;
1248 }
1249 } else {
1250 // We increment stock of batches
1251 // We use warehouse selected for each line
1252 foreach ($lotArray as $lot) {
1253 $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
1254 if ($result < 0) {
1255 $error++;
1256 $this->errors = array_merge($this->errors, $mouvS->errors);
1257 break;
1258 }
1259 }
1260 if ($error) {
1261 break; // break for loop incase of error
1262 }
1263 }
1264 }
1265 } else {
1266 $error++; $this->errors[] = "Error ".$this->db->lasterror();
1267 }
1268 }
1269
1270 // delete batch expedition line
1271 if (!$error && isModEnabled('productbatch')) {
1272 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1273 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1274 $error++; $this->errors[] = "Error ".$this->db->lasterror();
1275 }
1276 }
1277
1278
1279 if (!$error) {
1280 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1281 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1282
1283 if ($this->db->query($sql)) {
1284 // Delete linked object
1285 $res = $this->deleteObjectLinked();
1286 if ($res < 0) {
1287 $error++;
1288 }
1289
1290 // No delete expedition
1291 if (!$error) {
1292 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."expedition";
1293 $sql .= " WHERE rowid = ".((int) $this->id);
1294
1295 if ($this->db->query($sql)) {
1296 if (!empty($this->origin) && $this->origin_id > 0) {
1297 $this->fetch_origin();
1298 $origin = $this->origin;
1299 if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1300 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1301 $this->$origin->loadExpeditions();
1302 //var_dump($this->$origin->expeditions);exit;
1303 if (count($this->$origin->expeditions) <= 0) {
1304 $this->$origin->setStatut(Commande::STATUS_VALIDATED);
1305 }
1306 }
1307 }
1308
1309 if (!$error) {
1310 $this->db->commit();
1311
1312 // We delete PDFs
1313 $ref = dol_sanitizeFileName($this->ref);
1314 if (!empty($conf->expedition->dir_output)) {
1315 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1316 $file = $dir.'/'.$ref.'.pdf';
1317 if (file_exists($file)) {
1318 if (!dol_delete_file($file)) {
1319 return 0;
1320 }
1321 }
1322 if (file_exists($dir)) {
1323 if (!dol_delete_dir_recursive($dir)) {
1324 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1325 return 0;
1326 }
1327 }
1328 }
1329
1330 return 1;
1331 } else {
1332 $this->db->rollback();
1333 return -1;
1334 }
1335 } else {
1336 $this->error = $this->db->lasterror()." - sql=$sql";
1337 $this->db->rollback();
1338 return -3;
1339 }
1340 } else {
1341 $this->error = $this->db->lasterror()." - sql=$sql";
1342 $this->db->rollback();
1343 return -2;
1344 }//*/
1345 } else {
1346 $this->error = $this->db->lasterror()." - sql=$sql";
1347 $this->db->rollback();
1348 return -1;
1349 }
1350 } else {
1351 $this->db->rollback();
1352 return -1;
1353 }
1354 }
1355
1364 public function delete($notrigger = 0, $also_update_stock = false)
1365 {
1366 global $conf, $langs, $user;
1367
1368 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1369
1370 $error = 0;
1371 $this->error = '';
1372
1373 $this->db->begin();
1374
1375 // Add a protection to refuse deleting if shipment has at least one delivery
1376 $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1377 if (count($this->linkedObjectsIds) > 0) {
1378 $this->error = 'ErrorThereIsSomeDeliveries';
1379 $error++;
1380 }
1381
1382 if (!$error && !$notrigger) {
1383 // Call trigger
1384 $result = $this->call_trigger('SHIPPING_DELETE', $user);
1385 if ($result < 0) {
1386 $error++;
1387 }
1388 // End call triggers
1389 }
1390
1391 // Stock control
1392 if (!$error && isModEnabled('stock') &&
1393 (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
1394 ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock))) {
1395 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1396
1397 $langs->load("agenda");
1398
1399 // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1400 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1401
1402 // Loop on each product line to add a stock movement
1403 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1404 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
1405 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
1406 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1407 $sql .= " AND cd.rowid = ed.fk_origin_line";
1408
1409 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1410 $resql = $this->db->query($sql);
1411 if ($resql) {
1412 $cpt = $this->db->num_rows($resql);
1413 for ($i = 0; $i < $cpt; $i++) {
1414 dol_syslog(get_class($this)."::delete movement index ".$i);
1415 $obj = $this->db->fetch_object($resql);
1416
1417 $mouvS = new MouvementStock($this->db);
1418 // we do not log origin because it will be deleted
1419 $mouvS->origin = null;
1420 // get lot/serial
1421 $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1422 if (!is_array($lotArray)) {
1423 $error++; $this->errors[] = "Error ".$this->db->lasterror();
1424 }
1425 if (empty($lotArray)) {
1426 // no lot/serial
1427 // We increment stock of product (and sub-products)
1428 // We use warehouse selected for each line
1429 $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
1430 if ($result < 0) {
1431 $error++;
1432 $this->errors = array_merge($this->errors, $mouvS->errors);
1433 break;
1434 }
1435 } else {
1436 // We increment stock of batches
1437 // We use warehouse selected for each line
1438 foreach ($lotArray as $lot) {
1439 $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
1440 if ($result < 0) {
1441 $error++;
1442 $this->errors = array_merge($this->errors, $mouvS->errors);
1443 break;
1444 }
1445 }
1446 if ($error) {
1447 break; // break for loop incase of error
1448 }
1449 }
1450 }
1451 } else {
1452 $error++; $this->errors[] = "Error ".$this->db->lasterror();
1453 }
1454 }
1455
1456 // delete batch expedition line
1457 if (!$error) {
1458 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1459 if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1460 $error++; $this->errors[] = "Error ".$this->db->lasterror();
1461 }
1462 }
1463
1464 if (!$error) {
1465 $main = MAIN_DB_PREFIX.'expeditiondet';
1466 $ef = $main."_extrafields";
1467 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = ".((int) $this->id).")";
1468
1469 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
1470 $sql .= " WHERE fk_expedition = ".((int) $this->id);
1471
1472 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1473 // Delete linked object
1474 $res = $this->deleteObjectLinked();
1475 if ($res < 0) {
1476 $error++;
1477 }
1478
1479 // delete extrafields
1480 $res = $this->deleteExtraFields();
1481 if ($res < 0) {
1482 $error++;
1483 }
1484
1485 if (!$error) {
1486 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
1487 $sql .= " WHERE rowid = ".((int) $this->id);
1488
1489 if ($this->db->query($sql)) {
1490 if (!empty($this->origin) && $this->origin_id > 0) {
1491 $this->fetch_origin();
1492 $origin = $this->origin;
1493 if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) { // If order source of shipment is "shipment in progress"
1494 // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1495 $this->$origin->loadExpeditions();
1496 //var_dump($this->$origin->expeditions);exit;
1497 if (count($this->$origin->expeditions) <= 0) {
1498 $this->$origin->setStatut(Commande::STATUS_VALIDATED);
1499 }
1500 }
1501 }
1502
1503 if (!$error) {
1504 $this->db->commit();
1505
1506 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1507 $this->deleteEcmFiles();
1508
1509 // We delete PDFs
1510 $ref = dol_sanitizeFileName($this->ref);
1511 if (!empty($conf->expedition->dir_output)) {
1512 $dir = $conf->expedition->dir_output.'/sending/'.$ref;
1513 $file = $dir.'/'.$ref.'.pdf';
1514 if (file_exists($file)) {
1515 if (!dol_delete_file($file)) {
1516 return 0;
1517 }
1518 }
1519 if (file_exists($dir)) {
1520 if (!dol_delete_dir_recursive($dir)) {
1521 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1522 return 0;
1523 }
1524 }
1525 }
1526
1527 return 1;
1528 } else {
1529 $this->db->rollback();
1530 return -1;
1531 }
1532 } else {
1533 $this->error = $this->db->lasterror()." - sql=$sql";
1534 $this->db->rollback();
1535 return -3;
1536 }
1537 } else {
1538 $this->error = $this->db->lasterror()." - sql=$sql";
1539 $this->db->rollback();
1540 return -2;
1541 }
1542 } else {
1543 $this->error = $this->db->lasterror()." - sql=$sql";
1544 $this->db->rollback();
1545 return -1;
1546 }
1547 } else {
1548 $this->db->rollback();
1549 return -1;
1550 }
1551 }
1552
1553 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1559 public function fetch_lines()
1560 {
1561 // phpcs:enable
1562 global $conf, $mysoc;
1563
1564 $this->lines = array();
1565
1566 // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1567 // TODO: See if we can restore a common fetch_lines (one line = one record)
1568
1569 $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";
1570 $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1571 $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";
1572 $sql .= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc, cd.rang";
1573 $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_origin_line, ed.fk_entrepot";
1574 $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type";
1575 $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";
1576 $sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
1577 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
1578 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
1579 $sql .= " AND ed.fk_origin_line = cd.rowid";
1580 $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.
1581
1582 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1583 $resql = $this->db->query($sql);
1584 if ($resql) {
1585 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1586
1587 $num = $this->db->num_rows($resql);
1588 $i = 0;
1589 $lineindex = 0;
1590 $originline = 0;
1591
1592 $this->total_ht = 0;
1593 $this->total_tva = 0;
1594 $this->total_ttc = 0;
1595 $this->total_localtax1 = 0;
1596 $this->total_localtax2 = 0;
1597
1598 $this->multicurrency_total_ht = 0;
1599 $this->multicurrency_total_tva = 0;
1600 $this->multicurrency_total_ttc = 0;
1601
1602 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1603
1604 while ($i < $num) {
1605 $obj = $this->db->fetch_object($resql);
1606
1607
1608 if ($originline > 0 && $originline == $obj->fk_origin_line) {
1609 $line->entrepot_id = 0; // entrepod_id in details_entrepot
1610 $line->qty_shipped += $obj->qty_shipped;
1611 } else {
1612 $line = new ExpeditionLigne($this->db); // new group to start
1613 $line->entrepot_id = $obj->fk_entrepot; // this is a property of a shipment line
1614 $line->qty_shipped = $obj->qty_shipped; // this is a property of a shipment line
1615 }
1616
1617 $detail_entrepot = new stdClass();
1618 $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1619 $detail_entrepot->qty_shipped = $obj->qty_shipped;
1620 $detail_entrepot->line_id = $obj->line_id;
1621 $line->details_entrepot[] = $detail_entrepot;
1622
1623 $line->line_id = $obj->line_id;
1624 $line->rowid = $obj->line_id; // TODO deprecated
1625 $line->id = $obj->line_id;
1626
1627 $line->fk_origin = 'orderline';
1628 $line->fk_origin_line = $obj->fk_origin_line;
1629 $line->origin_line_id = $obj->fk_origin_line; // TODO deprecated
1630
1631 $line->fk_expedition = $this->id; // id of parent
1632
1633 $line->product_type = $obj->product_type;
1634 $line->fk_product = $obj->fk_product;
1635 $line->fk_product_type = $obj->fk_product_type;
1636 $line->ref = $obj->product_ref; // TODO deprecated
1637 $line->product_ref = $obj->product_ref;
1638 $line->product_label = $obj->product_label;
1639 $line->libelle = $obj->product_label; // TODO deprecated
1640 $line->product_tosell = $obj->product_tosell;
1641 $line->product_tobuy = $obj->product_tobuy;
1642 $line->product_tobatch = $obj->product_tobatch;
1643 $line->label = $obj->custom_label;
1644 $line->description = $obj->description;
1645 $line->qty_asked = $obj->qty_asked;
1646 $line->rang = $obj->rang;
1647 $line->weight = $obj->weight;
1648 $line->weight_units = $obj->weight_units;
1649 $line->length = $obj->length;
1650 $line->length_units = $obj->length_units;
1651 $line->surface = $obj->surface;
1652 $line->surface_units = $obj->surface_units;
1653 $line->volume = $obj->volume;
1654 $line->volume_units = $obj->volume_units;
1655 $line->fk_unit = $obj->fk_unit;
1656
1657 $line->pa_ht = $obj->pa_ht;
1658
1659 // Local taxes
1660 $localtax_array = array(0=>$obj->localtax1_type, 1=>$obj->localtax1_tx, 2=>$obj->localtax2_type, 3=>$obj->localtax2_tx);
1661 $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1662 $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1663
1664 // For invoicing
1665 $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
1666 $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1667 $line->qty = $line->qty_shipped;
1668 $line->total_ht = $tabprice[0];
1669 $line->total_localtax1 = $tabprice[9];
1670 $line->total_localtax2 = $tabprice[10];
1671 $line->total_ttc = $tabprice[2];
1672 $line->total_tva = $tabprice[1];
1673 $line->vat_src_code = $obj->vat_src_code;
1674 $line->tva_tx = $obj->tva_tx;
1675 $line->localtax1_tx = $obj->localtax1_tx;
1676 $line->localtax2_tx = $obj->localtax2_tx;
1677 $line->info_bits = $obj->info_bits;
1678 $line->price = $obj->price;
1679 $line->subprice = $obj->subprice;
1680 $line->remise_percent = $obj->remise_percent;
1681
1682 $this->total_ht += $tabprice[0];
1683 $this->total_tva += $tabprice[1];
1684 $this->total_ttc += $tabprice[2];
1685 $this->total_localtax1 += $tabprice[9];
1686 $this->total_localtax2 += $tabprice[10];
1687
1688 // Multicurrency
1689 $this->fk_multicurrency = $obj->fk_multicurrency;
1690 $this->multicurrency_code = $obj->multicurrency_code;
1691 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1692 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1693 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1694 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1695
1696 $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
1697 $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
1698 $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
1699
1700 if ($originline != $obj->fk_origin_line) {
1701 $line->detail_batch = array();
1702 }
1703
1704 // Detail of batch
1705 if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1706 $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1707
1708 if (is_array($newdetailbatch)) {
1709 if ($originline != $obj->fk_origin_line) {
1710 $line->detail_batch = $newdetailbatch;
1711 } else {
1712 $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1713 }
1714 }
1715 }
1716
1717 $line->fetch_optionals();
1718
1719 if ($originline != $obj->fk_origin_line) {
1720 $this->lines[$lineindex] = $line;
1721 $lineindex++;
1722 } else {
1723 $line->total_ht += $tabprice[0];
1724 $line->total_localtax1 += $tabprice[9];
1725 $line->total_localtax2 += $tabprice[10];
1726 $line->total_ttc += $tabprice[2];
1727 $line->total_tva += $tabprice[1];
1728 }
1729
1730 $i++;
1731 $originline = $obj->fk_origin_line;
1732 }
1733 $this->db->free($resql);
1734 return 1;
1735 } else {
1736 $this->error = $this->db->error();
1737 return -3;
1738 }
1739 }
1740
1748 public function deleteline($user, $lineid)
1749 {
1750 global $user;
1751
1752 if ($this->statut == self::STATUS_DRAFT) {
1753 $this->db->begin();
1754
1755 $line = new ExpeditionLigne($this->db);
1756
1757 // For triggers
1758 $line->fetch($lineid);
1759
1760 if ($line->delete($user) > 0) {
1761 //$this->update_price(1);
1762
1763 $this->db->commit();
1764 return 1;
1765 } else {
1766 $this->db->rollback();
1767 return -1;
1768 }
1769 } else {
1770 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1771 return -2;
1772 }
1773 }
1774
1775
1783 public function getTooltipContentArray($params)
1784 {
1785 global $conf, $langs;
1786
1787 $langs->load('sendings');
1788
1789 $nofetch = !empty($params['nofetch']);
1790
1791 $datas = array();
1792 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Shipment").'</u>';
1793 if (isset($this->statut)) {
1794 $datas['picto'] .= ' '.$this->getLibStatut(5);
1795 }
1796 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1797 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
1798 if (!$nofetch) {
1799 $langs->load('companies');
1800 if (empty($this->thirdparty)) {
1801 $this->fetch_thirdparty();
1802 }
1803 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1804 }
1805
1806 return $datas;
1807 }
1808
1820 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1821 {
1822 global $langs, $conf, $hookmanager;
1823
1824 $result = '';
1825 $params = [
1826 'id' => $this->id,
1827 'objecttype' => $this->element,
1828 'option' => $option,
1829 'nofetch' => 1,
1830 ];
1831 $classfortooltip = 'classfortooltip';
1832 $dataparams = '';
1833 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1834 $classfortooltip = 'classforajaxtooltip';
1835 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1836 $label = '';
1837 } else {
1838 $label = implode($this->getTooltipContentArray($params));
1839 }
1840
1841 $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
1842
1843 if ($short) {
1844 return $url;
1845 }
1846
1847 if ($option !== 'nolink') {
1848 // Add param to save lastsearch_values or not
1849 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1850 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1851 $add_save_lastsearch_values = 1;
1852 }
1853 if ($add_save_lastsearch_values) {
1854 $url .= '&save_lastsearch_values=1';
1855 }
1856 }
1857
1858 $linkclose = '';
1859 if (empty($notooltip)) {
1860 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1861 $label = $langs->trans("Shipment");
1862 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1863 }
1864 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1865 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1866 }
1867
1868 $linkstart = '<a href="'.$url.'"';
1869 $linkstart .= $linkclose.'>';
1870 $linkend = '</a>';
1871
1872 $result .= $linkstart;
1873 if ($withpicto) {
1874 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
1875 }
1876 if ($withpicto != 2) {
1877 $result .= $this->ref;
1878 }
1879 $result .= $linkend;
1880 global $action;
1881 $hookmanager->initHooks(array($this->element . 'dao'));
1882 $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1883 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1884 if ($reshook > 0) {
1885 $result = $hookmanager->resPrint;
1886 } else {
1887 $result .= $hookmanager->resPrint;
1888 }
1889 return $result;
1890 }
1891
1898 public function getLibStatut($mode = 0)
1899 {
1900 return $this->LibStatut($this->statut, $mode);
1901 }
1902
1903 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1911 public function LibStatut($status, $mode)
1912 {
1913 // phpcs:enable
1914 global $langs;
1915
1916 $labelStatus = $langs->transnoentitiesnoconv($this->statuts[$status]);
1917 $labelStatusShort = $langs->transnoentitiesnoconv($this->statuts_short[$status]);
1918
1919 $statusType = 'status'.$status;
1920 if ($status == self::STATUS_VALIDATED) {
1921 $statusType = 'status4';
1922 }
1923 if ($status == self::STATUS_CLOSED) {
1924 $statusType = 'status6';
1925 }
1926 if ($status == self::STATUS_CANCELED) {
1927 $statusType = 'status9';
1928 }
1929
1930 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
1931 }
1932
1940 public function initAsSpecimen()
1941 {
1942 global $langs;
1943
1944 $now = dol_now();
1945
1946 dol_syslog(get_class($this)."::initAsSpecimen");
1947
1948 $order = new Commande($this->db);
1949 $order->initAsSpecimen();
1950
1951 // Initialise parametres
1952 $this->id = 0;
1953 $this->ref = 'SPECIMEN';
1954 $this->specimen = 1;
1956 $this->livraison_id = 0;
1957 $this->date = $now;
1958 $this->date_creation = $now;
1959 $this->date_valid = $now;
1960 $this->date_delivery = $now + 24 * 3600;
1961 $this->date_expedition = $now + 24 * 3600;
1962
1963 $this->entrepot_id = 0;
1964 $this->fk_delivery_address = 0;
1965 $this->socid = 1;
1966
1967 $this->commande_id = 0;
1968 $this->commande = $order;
1969
1970 $this->origin_id = 1;
1971 $this->origin = 'commande';
1972
1973 $this->note_private = 'Private note';
1974 $this->note_public = 'Public note';
1975
1976 $nbp = 5;
1977 $xnbp = 0;
1978 while ($xnbp < $nbp) {
1979 $line = new ExpeditionLigne($this->db);
1980 $line->desc = $langs->trans("Description")." ".$xnbp;
1981 $line->libelle = $langs->trans("Description")." ".$xnbp; // deprecated
1982 $line->label = $langs->trans("Description")." ".$xnbp;
1983 $line->qty = 10;
1984 $line->qty_asked = 5;
1985 $line->qty_shipped = 4;
1986 $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
1987
1988 $this->lines[] = $line;
1989 $xnbp++;
1990 }
1991 }
1992
1993 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2002 public function set_date_livraison($user, $delivery_date)
2003 {
2004 // phpcs:enable
2005 return $this->setDeliveryDate($user, $delivery_date);
2006 }
2007
2015 public function setDeliveryDate($user, $delivery_date)
2016 {
2017 if ($user->rights->expedition->creer) {
2018 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
2019 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2020 $sql .= " WHERE rowid = ".((int) $this->id);
2021
2022 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
2023 $resql = $this->db->query($sql);
2024 if ($resql) {
2025 $this->date_delivery = $delivery_date;
2026 return 1;
2027 } else {
2028 $this->error = $this->db->error();
2029 return -1;
2030 }
2031 } else {
2032 return -2;
2033 }
2034 }
2035
2036 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2042 public function fetch_delivery_methods()
2043 {
2044 // phpcs:enable
2045 global $langs;
2046 $this->meths = array();
2047
2048 $sql = "SELECT em.rowid, em.code, em.libelle as label";
2049 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2050 $sql .= " WHERE em.active = 1";
2051 $sql .= " ORDER BY em.libelle ASC";
2052
2053 $resql = $this->db->query($sql);
2054 if ($resql) {
2055 while ($obj = $this->db->fetch_object($resql)) {
2056 $label = $langs->trans('SendingMethod'.$obj->code);
2057 $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2058 }
2059 }
2060 }
2061
2062 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2069 public function list_delivery_methods($id = '')
2070 {
2071 // phpcs:enable
2072 global $langs;
2073
2074 $this->listmeths = array();
2075 $i = 0;
2076
2077 $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2078 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2079 if ($id != '') {
2080 $sql .= " WHERE em.rowid=".((int) $id);
2081 }
2082
2083 $resql = $this->db->query($sql);
2084 if ($resql) {
2085 while ($obj = $this->db->fetch_object($resql)) {
2086 $this->listmeths[$i]['rowid'] = $obj->rowid;
2087 $this->listmeths[$i]['code'] = $obj->code;
2088 $label = $langs->trans('SendingMethod'.$obj->code);
2089 $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code ? $label : $obj->label);
2090 $this->listmeths[$i]['description'] = $obj->description;
2091 $this->listmeths[$i]['tracking'] = $obj->tracking;
2092 $this->listmeths[$i]['active'] = $obj->active;
2093 $i++;
2094 }
2095 }
2096 }
2097
2104 public function getUrlTrackingStatus($value = '')
2105 {
2106 if (!empty($this->shipping_method_id)) {
2107 $sql = "SELECT em.code, em.tracking";
2108 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
2109 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
2110
2111 $resql = $this->db->query($sql);
2112 if ($resql) {
2113 if ($obj = $this->db->fetch_object($resql)) {
2114 $tracking = $obj->tracking;
2115 }
2116 }
2117 }
2118
2119 if (!empty($tracking) && !empty($value)) {
2120 $url = str_replace('{TRACKID}', $value, $tracking);
2121 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">'.($value ? $value : 'url').'</a>', $url, $url);
2122 } else {
2123 $this->tracking_url = $value;
2124 }
2125 }
2126
2132 public function setClosed()
2133 {
2134 global $conf, $langs, $user;
2135
2136 $error = 0;
2137
2138 // Protection. This avoid to move stock later when we should not
2139 if ($this->statut == self::STATUS_CLOSED) {
2140 return 0;
2141 }
2142
2143 $this->db->begin();
2144
2145 $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET fk_statut = ".self::STATUS_CLOSED;
2146 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut > 0";
2147
2148 $resql = $this->db->query($sql);
2149 if ($resql) {
2150 // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2151 if ($this->origin == 'commande' && $this->origin_id > 0) {
2152 $order = new Commande($this->db);
2153 $order->fetch($this->origin_id);
2154
2155 $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2156
2157 $shipments_match_order = 1;
2158 foreach ($order->lines as $line) {
2159 $lineid = $line->id;
2160 $qty = $line->qty;
2161 if (($line->product_type == 0 || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) && $order->expeditions[$lineid] != $qty) {
2162 $shipments_match_order = 0;
2163 $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';
2164 dol_syslog($text);
2165 break;
2166 }
2167 }
2168 if ($shipments_match_order) {
2169 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');
2170 // We close the order
2171 $order->cloture($user); // Note this may also create an invoice if module workflow ask it
2172 }
2173 }
2174
2175 $this->statut = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2176 $this->status = self::STATUS_CLOSED; // Will be revert to STATUS_VALIDATED at end if there is a rollback
2177
2178 // If stock increment is done on closing
2179 if (!$error && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
2180 $result = $this->manageStockMvtOnEvt($user);
2181 if ($result<0) {
2182 $error++;
2183 }
2184 }
2185
2186 // Call trigger
2187 if (!$error) {
2188 $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2189 if ($result < 0) {
2190 $error++;
2191 }
2192 }
2193 } else {
2194 dol_print_error($this->db);
2195 $error++;
2196 }
2197
2198 if (!$error) {
2199 $this->db->commit();
2200 return 1;
2201 } else {
2203 $this->status = self::STATUS_VALIDATED;
2204
2205 $this->db->rollback();
2206 return -1;
2207 }
2208 }
2209
2219 private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2220 {
2221 global $langs;
2222
2223 $error=0;
2224
2225 require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
2226
2227 $langs->load("agenda");
2228
2229 // Loop on each product line to add a stock movement
2230 $sql = "SELECT cd.fk_product, cd.subprice,";
2231 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2232 $sql .= " e.ref,";
2233 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2234 $sql .= " cd.rowid as cdid, ed.rowid as edid";
2235 $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2236 $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2237 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2238 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2239 $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2240 $sql .= " AND cd.rowid = ed.fk_origin_line";
2241
2242 dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2243 $resql = $this->db->query($sql);
2244 if ($resql) {
2245 $cpt = $this->db->num_rows($resql);
2246 for ($i = 0; $i < $cpt; $i++) {
2247 $obj = $this->db->fetch_object($resql);
2248 if (empty($obj->edbrowid)) {
2249 $qty = $obj->qty;
2250 } else {
2251 $qty = $obj->edbqty;
2252 }
2253 if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2254 continue;
2255 }
2256 dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2257
2258 $mouvS = new MouvementStock($this->db);
2259 $mouvS->origin = &$this;
2260 $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2261
2262 if (empty($obj->edbrowid)) {
2263 // line without batch detail
2264
2265 // 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
2266 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
2267 if ($result < 0) {
2268 $this->error = $mouvS->error;
2269 $this->errors = $mouvS->errors;
2270 $error++;
2271 break;
2272 }
2273 } else {
2274 // line with batch detail
2275
2276 // 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
2277 $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);
2278 if ($result < 0) {
2279 $this->error = $mouvS->error;
2280 $this->errors = $mouvS->errors;
2281 $error++;
2282 break;
2283 }
2284 }
2285
2286 // 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
2287 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2288 $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)";
2289 $resqldelete = $this->db->query($sqldelete);
2290 // We do not test error, it can fails if there is child in batch details
2291 }
2292 } else {
2293 $this->error = $this->db->lasterror();
2294 $this->errors[] = $this->db->lasterror();
2295 $error ++;
2296 }
2297
2298 if (!$error) {
2299 return 1;
2300 } else {
2301 return -1;
2302 }
2303 }
2304
2310 public function setBilled()
2311 {
2312 global $user;
2313 $error = 0;
2314
2315 $this->db->begin();
2316
2317 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut=2, billed=1'; // TODO Update only billed
2318 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2319
2320 $resql = $this->db->query($sql);
2321 if ($resql) {
2322 $this->statut = self::STATUS_CLOSED;
2323 $this->billed = 1;
2324
2325 // Call trigger
2326 $result = $this->call_trigger('SHIPPING_BILLED', $user);
2327 if ($result < 0) {
2328 $error++;
2329 }
2330 } else {
2331 $error++;
2332 $this->errors[] = $this->db->lasterror;
2333 }
2334
2335 if (empty($error)) {
2336 $this->db->commit();
2337 return 1;
2338 } else {
2340 $this->billed = 0;
2341 $this->db->rollback();
2342 return -1;
2343 }
2344 }
2345
2353 public function setDraft($user, $notrigger = 0)
2354 {
2355 // Protection
2356 if ($this->statut <= self::STATUS_DRAFT) {
2357 return 0;
2358 }
2359
2360 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2361 }
2362
2368 public function reOpen()
2369 {
2370 global $conf, $langs, $user;
2371
2372 $error = 0;
2373
2374 // Protection. This avoid to move stock later when we should not
2375 if ($this->statut == self::STATUS_VALIDATED) {
2376 return 0;
2377 }
2378
2379 $this->db->begin();
2380
2381 $oldbilled = $this->billed;
2382
2383 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2384 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2385
2386 $resql = $this->db->query($sql);
2387 if ($resql) {
2389 $this->billed = 0;
2390
2391 // If stock increment is done on closing
2392 if (!$error && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
2393 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2394
2395 $langs->load("agenda");
2396
2397 // Loop on each product line to add a stock movement
2398 // TODO possibilite d'expedier a partir d'une propale ou autre origine
2399 $sql = "SELECT cd.fk_product, cd.subprice,";
2400 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2401 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2402 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2403 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2404 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2405 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2406 $sql .= " AND cd.rowid = ed.fk_origin_line";
2407
2408 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2409 $resql = $this->db->query($sql);
2410 if ($resql) {
2411 $cpt = $this->db->num_rows($resql);
2412 for ($i = 0; $i < $cpt; $i++) {
2413 $obj = $this->db->fetch_object($resql);
2414 if (empty($obj->edbrowid)) {
2415 $qty = $obj->qty;
2416 } else {
2417 $qty = $obj->edbqty;
2418 }
2419 if ($qty <= 0) {
2420 continue;
2421 }
2422 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2423
2424 //var_dump($this->lines[$i]);
2425 $mouvS = new MouvementStock($this->db);
2426 $mouvS->origin = &$this;
2427 $mouvS->setOrigin($this->element, $this->id);
2428
2429 if (empty($obj->edbrowid)) {
2430 // line without batch detail
2431
2432 // 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
2433 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2434 if ($result < 0) {
2435 $this->error = $mouvS->error;
2436 $this->errors = $mouvS->errors;
2437 $error++;
2438 break;
2439 }
2440 } else {
2441 // line with batch detail
2442
2443 // 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
2444 $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);
2445 if ($result < 0) {
2446 $this->error = $mouvS->error;
2447 $this->errors = $mouvS->errors;
2448 $error++;
2449 break;
2450 }
2451 }
2452 }
2453 } else {
2454 $this->error = $this->db->lasterror();
2455 $error++;
2456 }
2457 }
2458
2459 if (!$error) {
2460 // Call trigger
2461 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2462 if ($result < 0) {
2463 $error++;
2464 }
2465 }
2466 } else {
2467 $error++;
2468 $this->errors[] = $this->db->lasterror();
2469 }
2470
2471 if (!$error) {
2472 $this->db->commit();
2473 return 1;
2474 } else {
2475 $this->statut = self::STATUS_CLOSED;
2476 $this->billed = $oldbilled;
2477 $this->db->rollback();
2478 return -1;
2479 }
2480 }
2481
2493 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2494 {
2495 global $conf;
2496
2497 $outputlangs->load("products");
2498
2499 if (!dol_strlen($modele)) {
2500 $modele = 'rouget';
2501
2502 if (!empty($this->model_pdf)) {
2503 $modele = $this->model_pdf;
2504 } elseif (!empty($this->modelpdf)) { // deprecated
2505 $modele = $this->modelpdf;
2506 } elseif (!empty($conf->global->EXPEDITION_ADDON_PDF)) {
2507 $modele = $conf->global->EXPEDITION_ADDON_PDF;
2508 }
2509 }
2510
2511 $modelpath = "core/modules/expedition/doc/";
2512
2513 $this->fetch_origin();
2514
2515 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2516 }
2517
2526 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2527 {
2528 $tables = array(
2529 'expedition'
2530 );
2531
2532 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2533 }
2534}
2535
2536
2541{
2545 public $element = 'expeditiondet';
2546
2550 public $table_element = 'expeditiondet';
2551
2552
2559 public $line_id; // deprecated
2560
2566
2572 public $fk_origin; // Example: 'orderline'
2573
2577 public $fk_origin_line;
2578
2582 public $fk_expedition;
2583
2587 public $db;
2588
2592 public $qty;
2593
2597 public $qty_shipped;
2598
2602 public $fk_product;
2603
2604 // detail of lot and qty = array(id in llx_expeditiondet_batch, fk_expeditiondet, batch, qty, fk_origin_stock)
2605 // We can use this to know warehouse planned to be used for each lot.
2606 public $detail_batch;
2607
2608 // detail of warehouses and qty
2609 // We can use this to know warehouse when there is no lot.
2610 public $details_entrepot;
2611
2612
2616 public $entrepot_id;
2617
2618
2622 public $qty_asked;
2623
2628 public $ref;
2629
2633 public $product_ref;
2634
2639 public $libelle;
2640
2644 public $product_label;
2645
2651 public $desc;
2652
2656 public $product_desc;
2657
2662 public $product_type = 0;
2663
2667 public $rang;
2668
2672 public $weight;
2673 public $weight_units;
2674
2678 public $length;
2679 public $length_units;
2680
2684 public $surface;
2685 public $surface_units;
2686
2690 public $volume;
2691 public $volume_units;
2692
2693 // Invoicing
2694 public $remise_percent;
2695 public $tva_tx;
2696
2700 public $total_ht;
2701
2705 public $total_ttc;
2706
2710 public $total_tva;
2711
2715 public $total_localtax1;
2716
2720 public $total_localtax2;
2721
2722
2728 public function __construct($db)
2729 {
2730 $this->db = $db;
2731 }
2732
2739 public function fetch($rowid)
2740 {
2741 $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_origin_line, ed.qty, ed.rang';
2742 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
2743 $sql .= ' WHERE ed.rowid = '.((int) $rowid);
2744 $result = $this->db->query($sql);
2745 if ($result) {
2746 $objp = $this->db->fetch_object($result);
2747 $this->id = $objp->rowid;
2748 $this->fk_expedition = $objp->fk_expedition;
2749 $this->entrepot_id = $objp->fk_entrepot;
2750 $this->fk_origin_line = $objp->fk_origin_line;
2751 $this->qty = $objp->qty;
2752 $this->rang = $objp->rang;
2753
2754 $this->db->free($result);
2755
2756 return 1;
2757 } else {
2758 $this->errors[] = $this->db->lasterror();
2759 $this->error = $this->db->lasterror();
2760 return -1;
2761 }
2762 }
2763
2771 public function insert($user, $notrigger = 0)
2772 {
2773 global $langs, $conf;
2774
2775 $error = 0;
2776
2777 // Check parameters
2778 if (empty($this->fk_expedition) || empty($this->fk_origin_line) || !is_numeric($this->qty)) {
2779 $this->error = 'ErrorMandatoryParametersNotProvided';
2780 return -1;
2781 }
2782
2783 $this->db->begin();
2784
2785 if (empty($this->rang)) {
2786 $this->rang = 0;
2787 }
2788
2789 // Rank to use
2790 $ranktouse = $this->rang;
2791 if ($ranktouse == -1) {
2792 $rangmax = $this->line_max($this->fk_expedition);
2793 $ranktouse = $rangmax + 1;
2794 }
2795
2796 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
2797 $sql .= "fk_expedition";
2798 $sql .= ", fk_entrepot";
2799 $sql .= ", fk_origin_line";
2800 $sql .= ", qty";
2801 $sql .= ", rang";
2802 $sql .= ") VALUES (";
2803 $sql .= $this->fk_expedition;
2804 $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
2805 $sql .= ", ".((int) $this->fk_origin_line);
2806 $sql .= ", ".price2num($this->qty, 'MS');
2807 $sql .= ", ".((int) $ranktouse);
2808 $sql .= ")";
2809
2810 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
2811 $resql = $this->db->query($sql);
2812 if ($resql) {
2813 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
2814
2815 if (!$error) {
2816 $result = $this->insertExtraFields();
2817 if ($result < 0) {
2818 $error++;
2819 }
2820 }
2821
2822 if (!$error && !$notrigger) {
2823 // Call trigger
2824 $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
2825 if ($result < 0) {
2826 $error++;
2827 }
2828 // End call triggers
2829 }
2830
2831 if ($error) {
2832 foreach ($this->errors as $errmsg) {
2833 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2834 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2835 }
2836 }
2837 } else {
2838 $error++;
2839 }
2840
2841 if ($error) {
2842 $this->db->rollback();
2843 return -1;
2844 } else {
2845 $this->db->commit();
2846 return $this->id;
2847 }
2848 }
2849
2857 public function delete($user = null, $notrigger = 0)
2858 {
2859 $error = 0;
2860
2861 $this->db->begin();
2862
2863 // delete batch expedition line
2864 if (isModEnabled('productbatch')) {
2865 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
2866 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
2867
2868 if (!$this->db->query($sql)) {
2869 $this->errors[] = $this->db->lasterror()." - sql=$sql";
2870 $error++;
2871 }
2872 }
2873
2874 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
2875 $sql .= " WHERE rowid = ".((int) $this->id);
2876
2877 if (!$error && $this->db->query($sql)) {
2878 // Remove extrafields
2879 if (!$error) {
2880 $result = $this->deleteExtraFields();
2881 if ($result < 0) {
2882 $this->errors[] = $this->error;
2883 $error++;
2884 }
2885 }
2886 if (!$error && !$notrigger) {
2887 // Call trigger
2888 $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
2889 if ($result < 0) {
2890 $this->errors[] = $this->error;
2891 $error++;
2892 }
2893 // End call triggers
2894 }
2895 } else {
2896 $this->errors[] = $this->db->lasterror()." - sql=$sql";
2897 $error++;
2898 }
2899
2900 if (!$error) {
2901 $this->db->commit();
2902 return 1;
2903 } else {
2904 foreach ($this->errors as $errmsg) {
2905 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2906 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2907 }
2908 $this->db->rollback();
2909 return -1 * $error;
2910 }
2911 }
2912
2920 public function update($user = null, $notrigger = 0)
2921 {
2922 $error = 0;
2923
2924 dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
2925
2926 $this->db->begin();
2927
2928 // Clean parameters
2929 if (empty($this->qty)) {
2930 $this->qty = 0;
2931 }
2932 $qty = price2num($this->qty);
2933 $remainingQty = 0;
2934 $batch = null;
2935 $batch_id = null;
2936 $expedition_batch_id = null;
2937 if (is_array($this->detail_batch)) { // array of ExpeditionLineBatch
2938 if (count($this->detail_batch) > 1) {
2939 dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
2940 $this->errors[] = 'ErrorBadParameters';
2941 $error++;
2942 } else {
2943 $batch = $this->detail_batch[0]->batch;
2944 $batch_id = $this->detail_batch[0]->fk_origin_stock;
2945 $expedition_batch_id = $this->detail_batch[0]->id;
2946 if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id) {
2947 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
2948 $this->errors[] = 'ErrorBadParameters';
2949 $error++;
2950 }
2951 $qty = price2num($this->detail_batch[0]->qty);
2952 }
2953 } elseif (!empty($this->detail_batch)) {
2954 $batch = $this->detail_batch->batch;
2955 $batch_id = $this->detail_batch->fk_origin_stock;
2956 $expedition_batch_id = $this->detail_batch->id;
2957 if ($this->entrepot_id != $this->detail_batch->entrepot_id) {
2958 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
2959 $this->errors[] = 'ErrorBadParameters';
2960 $error++;
2961 }
2962 $qty = price2num($this->detail_batch->qty);
2963 }
2964
2965 // check parameters
2966 if (!isset($this->id) || !isset($this->entrepot_id)) {
2967 dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
2968 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
2969 $error++;
2970 return -1;
2971 }
2972
2973 // update lot
2974
2975 if (!empty($batch) && isModEnabled('productbatch')) {
2976 dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id, batch=$batch");
2977
2978 if (empty($batch_id) || empty($this->fk_product)) {
2979 dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
2980 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
2981 $error++;
2982 }
2983
2984 // fetch remaining lot qty
2985 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
2986
2987 if (!$error && ($lotArray = $shipmentlinebatch->fetchAll($this->id)) < 0) {
2988 $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
2989 $error++;
2990 } else {
2991 // caculate new total line qty
2992 foreach ($lotArray as $lot) {
2993 if ($expedition_batch_id != $lot->id) {
2994 $remainingQty += $lot->qty;
2995 }
2996 }
2997 $qty += $remainingQty;
2998
2999 //fetch lot details
3000
3001 // fetch from product_lot
3002 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
3003 $lot = new Productlot($this->db);
3004 if ($lot->fetch(0, $this->fk_product, $batch) < 0) {
3005 $this->errors[] = $lot->errors;
3006 $error++;
3007 }
3008 if (!$error && !empty($expedition_batch_id)) {
3009 // delete lot expedition line
3010 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
3011 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
3012 $sql .= " AND rowid = ".((int) $expedition_batch_id);
3013
3014 if (!$this->db->query($sql)) {
3015 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3016 $error++;
3017 }
3018 }
3019 if (!$error && $this->detail_batch->qty > 0) {
3020 // create lot expedition line
3021 if (isset($lot->id)) {
3022 $shipmentLot = new ExpeditionLineBatch($this->db);
3023 $shipmentLot->batch = $lot->batch;
3024 $shipmentLot->eatby = $lot->eatby;
3025 $shipmentLot->sellby = $lot->sellby;
3026 $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id;
3027 $shipmentLot->qty = $this->detail_batch->qty;
3028 $shipmentLot->fk_origin_stock = $batch_id;
3029 if ($shipmentLot->create($this->id) < 0) {
3030 $this->errors = $shipmentLot->errors;
3031 $error++;
3032 }
3033 }
3034 }
3035 }
3036 }
3037 if (!$error) {
3038 // update line
3039 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3040 $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
3041 $sql .= " , qty = ".((float) price2num($qty, 'MS'));
3042 $sql .= " WHERE rowid = ".((int) $this->id);
3043
3044 if (!$this->db->query($sql)) {
3045 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3046 $error++;
3047 }
3048 }
3049
3050 if (!$error) {
3051 if (!$error) {
3052 $result = $this->insertExtraFields();
3053 if ($result < 0) {
3054 $this->errors[] = $this->error;
3055 $error++;
3056 }
3057 }
3058 }
3059
3060 if (!$error && !$notrigger) {
3061 // Call trigger
3062 $result = $this->call_trigger('LINESHIPPING_MODIFY', $user);
3063 if ($result < 0) {
3064 $this->errors[] = $this->error;
3065 $error++;
3066 }
3067 // End call triggers
3068 }
3069 if (!$error) {
3070 $this->db->commit();
3071 return 1;
3072 } else {
3073 foreach ($this->errors as $errmsg) {
3074 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3075 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3076 }
3077 $this->db->rollback();
3078 return -1 * $error;
3079 }
3080 }
3081}
$object ref
Definition info.php:78
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='', $f_user=null, $notrigger=0)
Delete all links between an object $this.
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.
__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 when WORKFLOW_BILL_ON_SHIPMENT is on)
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.
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.
Manage record for batch number management.
Class with list of lots and properties.
Class to manage third parties objects (customers, suppliers, prospects...)
print $langs trans("Ref").' m m m statut
Definition index.php:152
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 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.
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:124