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 return $error;
2299 }
2300
2306 public function setBilled()
2307 {
2308 global $user;
2309 $error = 0;
2310
2311 $this->db->begin();
2312
2313 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut=2, billed=1'; // TODO Update only billed
2314 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2315
2316 $resql = $this->db->query($sql);
2317 if ($resql) {
2318 $this->statut = self::STATUS_CLOSED;
2319 $this->billed = 1;
2320
2321 // Call trigger
2322 $result = $this->call_trigger('SHIPPING_BILLED', $user);
2323 if ($result < 0) {
2324 $error++;
2325 }
2326 } else {
2327 $error++;
2328 $this->errors[] = $this->db->lasterror;
2329 }
2330
2331 if (empty($error)) {
2332 $this->db->commit();
2333 return 1;
2334 } else {
2336 $this->billed = 0;
2337 $this->db->rollback();
2338 return -1;
2339 }
2340 }
2341
2349 public function setDraft($user, $notrigger = 0)
2350 {
2351 // Protection
2352 if ($this->statut <= self::STATUS_DRAFT) {
2353 return 0;
2354 }
2355
2356 return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2357 }
2358
2364 public function reOpen()
2365 {
2366 global $conf, $langs, $user;
2367
2368 $error = 0;
2369
2370 // Protection. This avoid to move stock later when we should not
2371 if ($this->statut == self::STATUS_VALIDATED) {
2372 return 0;
2373 }
2374
2375 $this->db->begin();
2376
2377 $oldbilled = $this->billed;
2378
2379 $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut = 1';
2380 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2381
2382 $resql = $this->db->query($sql);
2383 if ($resql) {
2385 $this->billed = 0;
2386
2387 // If stock increment is done on closing
2388 if (!$error && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
2389 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2390
2391 $langs->load("agenda");
2392
2393 // Loop on each product line to add a stock movement
2394 // TODO possibilite d'expedier a partir d'une propale ou autre origine
2395 $sql = "SELECT cd.fk_product, cd.subprice,";
2396 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2397 $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2398 $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
2399 $sql .= " ".MAIN_DB_PREFIX."expeditiondet as ed";
2400 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2401 $sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
2402 $sql .= " AND cd.rowid = ed.fk_origin_line";
2403
2404 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2405 $resql = $this->db->query($sql);
2406 if ($resql) {
2407 $cpt = $this->db->num_rows($resql);
2408 for ($i = 0; $i < $cpt; $i++) {
2409 $obj = $this->db->fetch_object($resql);
2410 if (empty($obj->edbrowid)) {
2411 $qty = $obj->qty;
2412 } else {
2413 $qty = $obj->edbqty;
2414 }
2415 if ($qty <= 0) {
2416 continue;
2417 }
2418 dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
2419
2420 //var_dump($this->lines[$i]);
2421 $mouvS = new MouvementStock($this->db);
2422 $mouvS->origin = &$this;
2423 $mouvS->setOrigin($this->element, $this->id);
2424
2425 if (empty($obj->edbrowid)) {
2426 // line without batch detail
2427
2428 // 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
2429 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2430 if ($result < 0) {
2431 $this->error = $mouvS->error;
2432 $this->errors = $mouvS->errors;
2433 $error++;
2434 break;
2435 }
2436 } else {
2437 // line with batch detail
2438
2439 // 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
2440 $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);
2441 if ($result < 0) {
2442 $this->error = $mouvS->error;
2443 $this->errors = $mouvS->errors;
2444 $error++;
2445 break;
2446 }
2447 }
2448 }
2449 } else {
2450 $this->error = $this->db->lasterror();
2451 $error++;
2452 }
2453 }
2454
2455 if (!$error) {
2456 // Call trigger
2457 $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2458 if ($result < 0) {
2459 $error++;
2460 }
2461 }
2462 } else {
2463 $error++;
2464 $this->errors[] = $this->db->lasterror();
2465 }
2466
2467 if (!$error) {
2468 $this->db->commit();
2469 return 1;
2470 } else {
2471 $this->statut = self::STATUS_CLOSED;
2472 $this->billed = $oldbilled;
2473 $this->db->rollback();
2474 return -1;
2475 }
2476 }
2477
2489 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2490 {
2491 global $conf;
2492
2493 $outputlangs->load("products");
2494
2495 if (!dol_strlen($modele)) {
2496 $modele = 'rouget';
2497
2498 if (!empty($this->model_pdf)) {
2499 $modele = $this->model_pdf;
2500 } elseif (!empty($this->modelpdf)) { // deprecated
2501 $modele = $this->modelpdf;
2502 } elseif (!empty($conf->global->EXPEDITION_ADDON_PDF)) {
2503 $modele = $conf->global->EXPEDITION_ADDON_PDF;
2504 }
2505 }
2506
2507 $modelpath = "core/modules/expedition/doc/";
2508
2509 $this->fetch_origin();
2510
2511 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2512 }
2513
2522 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2523 {
2524 $tables = array(
2525 'expedition'
2526 );
2527
2528 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2529 }
2530}
2531
2532
2537{
2541 public $element = 'expeditiondet';
2542
2546 public $table_element = 'expeditiondet';
2547
2548
2555 public $line_id; // deprecated
2556
2562
2568 public $fk_origin; // Example: 'orderline'
2569
2573 public $fk_origin_line;
2574
2578 public $fk_expedition;
2579
2583 public $db;
2584
2588 public $qty;
2589
2593 public $qty_shipped;
2594
2598 public $fk_product;
2599
2600 // detail of lot and qty = array(id in llx_expeditiondet_batch, fk_expeditiondet, batch, qty, fk_origin_stock)
2601 // We can use this to know warehouse planned to be used for each lot.
2602 public $detail_batch;
2603
2604 // detail of warehouses and qty
2605 // We can use this to know warehouse when there is no lot.
2606 public $details_entrepot;
2607
2608
2612 public $entrepot_id;
2613
2614
2618 public $qty_asked;
2619
2624 public $ref;
2625
2629 public $product_ref;
2630
2635 public $libelle;
2636
2640 public $product_label;
2641
2647 public $desc;
2648
2652 public $product_desc;
2653
2658 public $product_type = 0;
2659
2663 public $rang;
2664
2668 public $weight;
2669 public $weight_units;
2670
2674 public $length;
2675 public $length_units;
2676
2680 public $surface;
2681 public $surface_units;
2682
2686 public $volume;
2687 public $volume_units;
2688
2689 // Invoicing
2690 public $remise_percent;
2691 public $tva_tx;
2692
2696 public $total_ht;
2697
2701 public $total_ttc;
2702
2706 public $total_tva;
2707
2711 public $total_localtax1;
2712
2716 public $total_localtax2;
2717
2718
2724 public function __construct($db)
2725 {
2726 $this->db = $db;
2727 }
2728
2735 public function fetch($rowid)
2736 {
2737 $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_origin_line, ed.qty, ed.rang';
2738 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
2739 $sql .= ' WHERE ed.rowid = '.((int) $rowid);
2740 $result = $this->db->query($sql);
2741 if ($result) {
2742 $objp = $this->db->fetch_object($result);
2743 $this->id = $objp->rowid;
2744 $this->fk_expedition = $objp->fk_expedition;
2745 $this->entrepot_id = $objp->fk_entrepot;
2746 $this->fk_origin_line = $objp->fk_origin_line;
2747 $this->qty = $objp->qty;
2748 $this->rang = $objp->rang;
2749
2750 $this->db->free($result);
2751
2752 return 1;
2753 } else {
2754 $this->errors[] = $this->db->lasterror();
2755 $this->error = $this->db->lasterror();
2756 return -1;
2757 }
2758 }
2759
2767 public function insert($user, $notrigger = 0)
2768 {
2769 global $langs, $conf;
2770
2771 $error = 0;
2772
2773 // Check parameters
2774 if (empty($this->fk_expedition) || empty($this->fk_origin_line) || !is_numeric($this->qty)) {
2775 $this->error = 'ErrorMandatoryParametersNotProvided';
2776 return -1;
2777 }
2778
2779 $this->db->begin();
2780
2781 if (empty($this->rang)) {
2782 $this->rang = 0;
2783 }
2784
2785 // Rank to use
2786 $ranktouse = $this->rang;
2787 if ($ranktouse == -1) {
2788 $rangmax = $this->line_max($this->fk_expedition);
2789 $ranktouse = $rangmax + 1;
2790 }
2791
2792 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
2793 $sql .= "fk_expedition";
2794 $sql .= ", fk_entrepot";
2795 $sql .= ", fk_origin_line";
2796 $sql .= ", qty";
2797 $sql .= ", rang";
2798 $sql .= ") VALUES (";
2799 $sql .= $this->fk_expedition;
2800 $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
2801 $sql .= ", ".((int) $this->fk_origin_line);
2802 $sql .= ", ".price2num($this->qty, 'MS');
2803 $sql .= ", ".((int) $ranktouse);
2804 $sql .= ")";
2805
2806 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
2807 $resql = $this->db->query($sql);
2808 if ($resql) {
2809 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
2810
2811 if (!$error) {
2812 $result = $this->insertExtraFields();
2813 if ($result < 0) {
2814 $error++;
2815 }
2816 }
2817
2818 if (!$error && !$notrigger) {
2819 // Call trigger
2820 $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
2821 if ($result < 0) {
2822 $error++;
2823 }
2824 // End call triggers
2825 }
2826
2827 if ($error) {
2828 foreach ($this->errors as $errmsg) {
2829 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2830 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2831 }
2832 }
2833 } else {
2834 $error++;
2835 }
2836
2837 if ($error) {
2838 $this->db->rollback();
2839 return -1;
2840 } else {
2841 $this->db->commit();
2842 return $this->id;
2843 }
2844 }
2845
2853 public function delete($user = null, $notrigger = 0)
2854 {
2855 $error = 0;
2856
2857 $this->db->begin();
2858
2859 // delete batch expedition line
2860 if (isModEnabled('productbatch')) {
2861 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
2862 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
2863
2864 if (!$this->db->query($sql)) {
2865 $this->errors[] = $this->db->lasterror()." - sql=$sql";
2866 $error++;
2867 }
2868 }
2869
2870 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
2871 $sql .= " WHERE rowid = ".((int) $this->id);
2872
2873 if (!$error && $this->db->query($sql)) {
2874 // Remove extrafields
2875 if (!$error) {
2876 $result = $this->deleteExtraFields();
2877 if ($result < 0) {
2878 $this->errors[] = $this->error;
2879 $error++;
2880 }
2881 }
2882 if (!$error && !$notrigger) {
2883 // Call trigger
2884 $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
2885 if ($result < 0) {
2886 $this->errors[] = $this->error;
2887 $error++;
2888 }
2889 // End call triggers
2890 }
2891 } else {
2892 $this->errors[] = $this->db->lasterror()." - sql=$sql";
2893 $error++;
2894 }
2895
2896 if (!$error) {
2897 $this->db->commit();
2898 return 1;
2899 } else {
2900 foreach ($this->errors as $errmsg) {
2901 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
2902 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2903 }
2904 $this->db->rollback();
2905 return -1 * $error;
2906 }
2907 }
2908
2916 public function update($user = null, $notrigger = 0)
2917 {
2918 $error = 0;
2919
2920 dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
2921
2922 $this->db->begin();
2923
2924 // Clean parameters
2925 if (empty($this->qty)) {
2926 $this->qty = 0;
2927 }
2928 $qty = price2num($this->qty);
2929 $remainingQty = 0;
2930 $batch = null;
2931 $batch_id = null;
2932 $expedition_batch_id = null;
2933 if (is_array($this->detail_batch)) { // array of ExpeditionLineBatch
2934 if (count($this->detail_batch) > 1) {
2935 dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
2936 $this->errors[] = 'ErrorBadParameters';
2937 $error++;
2938 } else {
2939 $batch = $this->detail_batch[0]->batch;
2940 $batch_id = $this->detail_batch[0]->fk_origin_stock;
2941 $expedition_batch_id = $this->detail_batch[0]->id;
2942 if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id) {
2943 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
2944 $this->errors[] = 'ErrorBadParameters';
2945 $error++;
2946 }
2947 $qty = price2num($this->detail_batch[0]->qty);
2948 }
2949 } elseif (!empty($this->detail_batch)) {
2950 $batch = $this->detail_batch->batch;
2951 $batch_id = $this->detail_batch->fk_origin_stock;
2952 $expedition_batch_id = $this->detail_batch->id;
2953 if ($this->entrepot_id != $this->detail_batch->entrepot_id) {
2954 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
2955 $this->errors[] = 'ErrorBadParameters';
2956 $error++;
2957 }
2958 $qty = price2num($this->detail_batch->qty);
2959 }
2960
2961 // check parameters
2962 if (!isset($this->id) || !isset($this->entrepot_id)) {
2963 dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
2964 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
2965 $error++;
2966 return -1;
2967 }
2968
2969 // update lot
2970
2971 if (!empty($batch) && isModEnabled('productbatch')) {
2972 dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id, batch=$batch");
2973
2974 if (empty($batch_id) || empty($this->fk_product)) {
2975 dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
2976 $this->errors[] = 'ErrorMandatoryParametersNotProvided';
2977 $error++;
2978 }
2979
2980 // fetch remaining lot qty
2981 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
2982
2983 if (!$error && ($lotArray = $shipmentlinebatch->fetchAll($this->id)) < 0) {
2984 $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
2985 $error++;
2986 } else {
2987 // caculate new total line qty
2988 foreach ($lotArray as $lot) {
2989 if ($expedition_batch_id != $lot->id) {
2990 $remainingQty += $lot->qty;
2991 }
2992 }
2993 $qty += $remainingQty;
2994
2995 //fetch lot details
2996
2997 // fetch from product_lot
2998 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
2999 $lot = new Productlot($this->db);
3000 if ($lot->fetch(0, $this->fk_product, $batch) < 0) {
3001 $this->errors[] = $lot->errors;
3002 $error++;
3003 }
3004 if (!$error && !empty($expedition_batch_id)) {
3005 // delete lot expedition line
3006 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
3007 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
3008 $sql .= " AND rowid = ".((int) $expedition_batch_id);
3009
3010 if (!$this->db->query($sql)) {
3011 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3012 $error++;
3013 }
3014 }
3015 if (!$error && $this->detail_batch->qty > 0) {
3016 // create lot expedition line
3017 if (isset($lot->id)) {
3018 $shipmentLot = new ExpeditionLineBatch($this->db);
3019 $shipmentLot->batch = $lot->batch;
3020 $shipmentLot->eatby = $lot->eatby;
3021 $shipmentLot->sellby = $lot->sellby;
3022 $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id;
3023 $shipmentLot->qty = $this->detail_batch->qty;
3024 $shipmentLot->fk_origin_stock = $batch_id;
3025 if ($shipmentLot->create($this->id) < 0) {
3026 $this->errors = $shipmentLot->errors;
3027 $error++;
3028 }
3029 }
3030 }
3031 }
3032 }
3033 if (!$error) {
3034 // update line
3035 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3036 $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
3037 $sql .= " , qty = ".((float) price2num($qty, 'MS'));
3038 $sql .= " WHERE rowid = ".((int) $this->id);
3039
3040 if (!$this->db->query($sql)) {
3041 $this->errors[] = $this->db->lasterror()." - sql=$sql";
3042 $error++;
3043 }
3044 }
3045
3046 if (!$error) {
3047 if (!$error) {
3048 $result = $this->insertExtraFields();
3049 if ($result < 0) {
3050 $this->errors[] = $this->error;
3051 $error++;
3052 }
3053 }
3054 }
3055
3056 if (!$error && !$notrigger) {
3057 // Call trigger
3058 $result = $this->call_trigger('LINESHIPPING_MODIFY', $user);
3059 if ($result < 0) {
3060 $this->errors[] = $this->error;
3061 $error++;
3062 }
3063 // End call triggers
3064 }
3065 if (!$error) {
3066 $this->db->commit();
3067 return 1;
3068 } else {
3069 foreach ($this->errors as $errmsg) {
3070 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3071 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3072 }
3073 $this->db->rollback();
3074 return -1 * $error;
3075 }
3076 }
3077}
$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