dolibarr 22.0.5
expeditionligne.class.php
1<?php
2/* Copyright (C) 2003-2008 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
5 * Copyright (C) 2006-2012 Laurent Destailleur <eldy@users.sourceforge.net>
6 * Copyright (C) 2011-2020 Juanjo Menent <jmenent@2byte.es>
7 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
8 * Copyright (C) 2014 Cedric GROSS <c.gross@kreiz-it.fr>
9 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
10 * Copyright (C) 2014-2017 Francis Appels <francis.appels@yahoo.com>
11 * Copyright (C) 2015 Claudio Aschieri <c.aschieri@19.coop>
12 * Copyright (C) 2016-2024 Ferran Marcet <fmarcet@2byte.es>
13 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
14 * Copyright (C) 2018-2025 Frédéric France <frederic.france@free.fr>
15 * Copyright (C) 2020 Lenin Rivas <lenin@leninrivas.com>
16 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 3 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program. If not, see <https://www.gnu.org/licenses/>.
30 */
31
38require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
39require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionlinebatch.class.php';
40
45{
49 public $element = 'expeditiondet';
50
54 public $table_element = 'expeditiondet';
55
59 public $parent_element = 'expedition';
60
64 public $fk_parent_attribute = 'fk_expedition';
65
72 public $line_id; // deprecated
73
77 public $fk_element;
78
82 public $origin_id;
83
87 public $fk_elementdet;
88
92 public $origin_line_id;
93
97 public $fk_parent;
98
102 public $element_type;
103
104
111 public $fk_origin; // Example: 'orderline'
112
116 public $fk_expedition;
117
121 public $db;
122
126 public $qty;
127
131 public $qty_shipped;
132
136 public $fk_product;
137
143 public $detail_batch;
144
149 public $detail_children;
150
155 public $details_entrepot;
156
157
161 public $entrepot_id;
162
163
167 public $qty_asked;
168
174 public $ref;
175
179 public $product_ref;
180
186 public $libelle;
187
191 public $product_label;
192
198 public $desc;
199
203 public $product_desc;
204
209 public $product_type = 0;
210
214 public $rang;
215
219 public $weight;
220
224 public $weight_units;
225
229 public $length;
230
234 public $length_units;
235
239 public $width;
240
244 public $width_units;
245
249 public $height;
250
254 public $height_units;
255
259 public $surface;
260
264 public $surface_units;
265
269 public $volume;
270
274 public $volume_units;
275
281 public $stockable_product = 1;
282
286 public $remise_percent;
287
291 public $tva_tx;
292
296 public $total_ht;
297
301 public $total_ttc;
302
306 public $total_tva;
307
311 public $total_localtax1;
312
316 public $total_localtax2;
317
318
324 public function __construct($db)
325 {
326 $this->db = $db;
327 }
328
335 public function fetch($rowid)
336 {
337 $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_elementdet, ed.element_type, ed.qty, ed.rang, ed.extraparams';
338 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
339 $sql .= ' WHERE ed.rowid = '.((int) $rowid);
340 $result = $this->db->query($sql);
341 if ($result) {
342 $objp = $this->db->fetch_object($result);
343 $this->id = $objp->rowid;
344 $this->fk_expedition = $objp->fk_expedition;
345 $this->entrepot_id = $objp->fk_entrepot;
346 $this->fk_elementdet = $objp->fk_elementdet;
347 $this->element_type = $objp->element_type;
348 $this->qty = $objp->qty;
349 $this->rang = $objp->rang;
350
351 $this->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
352
353 $this->db->free($result);
354
355 return 1;
356 } else {
357 $this->errors[] = $this->db->lasterror();
358 $this->error = $this->db->lasterror();
359 return -1;
360 }
361 }
362
370 public function insert($user, $notrigger = 0)
371 {
372 global $langs;
373 $error = 0;
374
375 $skip_check_parameters = false;
376
377 // Handling parent line subtotal line
378 if (!empty($this->element_type)
379 && !empty($this->fk_elementdet)
380 && $this->element_type == 'commande') {
381 $objectsrc_line = new OrderLine($this->db);
382 $objectsrc_line->fetch($this->fk_elementdet);
383 $skip_check_parameters = $objectsrc_line->special_code == SUBTOTALS_SPECIAL_CODE;
384 }
385
386 // Check parameters
387 if ((empty($this->fk_expedition)
388 || (empty($this->fk_elementdet) && empty($this->fk_parent)) // at least origin line id of parent line id is set
389 || !is_numeric($this->qty))
390 && !$skip_check_parameters) {
391 $langs->load('errors');
392 $this->errors[] = $langs->trans('ErrorMandatoryParametersNotProvided');
393 return -1;
394 }
395
396 $this->db->begin();
397
398 if (empty($this->rang)) {
399 $this->rang = 0;
400 }
401
402 // Rank to use
403 $ranktouse = $this->rang;
404 if ($ranktouse == -1) {
405 $rangmax = $this->line_max($this->fk_expedition);
406 $ranktouse = $rangmax + 1;
407 }
408
409 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
410 $sql .= "fk_expedition";
411 $sql .= ", fk_entrepot";
412 $sql .= ", fk_elementdet";
413 $sql .= ", fk_parent";
414 $sql .= ", fk_product";
415 $sql .= ", element_type";
416 $sql .= ", qty";
417 $sql .= ", rang";
418 $sql .= ") VALUES (";
419 $sql .= $this->fk_expedition;
420 $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
421 $sql .= ", ".(empty($this->fk_elementdet) ? 'NULL' : $this->fk_elementdet);
422 $sql .= ", ".(empty($this->fk_parent) ? 'NULL' : $this->fk_parent);
423 $sql .= ", ".(empty($this->fk_product) ? 'NULL' : $this->fk_product);
424 $sql .= ", '".(empty($this->element_type) ? 'order' : $this->db->escape($this->element_type))."'";
425 $sql .= ", ".price2num($this->qty, 'MS');
426 $sql .= ", ".((int) $ranktouse);
427 $sql .= ")";
428
429 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
430 $resql = $this->db->query($sql);
431 if ($resql) {
432 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
433
434 if (!$error) {
435 $result = $this->insertExtraFields();
436 if ($result < 0) {
437 $error++;
438 }
439 }
440
441 if (!$error && !$notrigger) {
442 // Call trigger
443 $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
444 if ($result < 0) {
445 $error++;
446 }
447 // End call triggers
448 }
449
450 if ($error) {
451 foreach ($this->errors as $errmsg) {
452 dol_syslog(__METHOD__.' '.$errmsg, LOG_ERR);
453 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
454 }
455 }
456 } else {
457 $error++;
458 }
459
460 if ($error) {
461 $this->db->rollback();
462 return -1;
463 } else {
464 $this->db->commit();
465 return $this->id;
466 }
467 }
468
477 public function findAllChild($line_id, &$list = array(), $mode = 0)
478 {
479 if ($line_id > 0) {
480 // find all child
481 $sql = "SELECT ed.rowid as child_line_id";
482 if ($mode == 1) {
483 $sql .= ", ed.fk_product";
484 $sql .= ", ed.fk_parent";
485 $sql .= ", " . $this->db->ifsql('eb.rowid IS NULL', 'ed.qty', 'eb.qty') . " as qty";
486 $sql .= ", " . $this->db->ifsql('eb.rowid IS NULL', 'ed.fk_entrepot', 'eb.fk_warehouse') . " as fk_warehouse";
487 $sql .= ", eb.batch, eb.eatby, eb.sellby";
488 }
489 $sql .= " FROM " . $this->db->prefix() . $this->table_element . " as ed";
490 $sql .= " LEFT JOIN " . $this->db->prefix() . "expeditiondet_batch as eb ON eb.fk_expeditiondet = " . ((int) $line_id);
491 $sql .= " WHERE ed.fk_parent = " . ((int) $line_id);
492 $sql .= $this->db->order('ed.fk_product,ed.rowid', 'ASC,ASC');
493
494 $resql = $this->db->query($sql);
495 if ($resql) {
496 while ($obj = $this->db->fetch_object($resql)) {
497 $child_line_id = (int) $obj->child_line_id;
498 if (!isset($list[$line_id])) {
499 $list[$line_id] = array();
500 }
501
502 if ($mode == 0) {
503 $list[$line_id][] = $child_line_id;
504 } elseif ($mode == 1) {
505 $line_obj = new stdClass();
506 $line_obj->rowid = $child_line_id;
507 $line_obj->fk_product = $obj->fk_product;
508 $line_obj->fk_parent = $obj->fk_parent;
509 $line_obj->qty = $obj->qty;
510 $line_obj->fk_warehouse = $obj->fk_warehouse;
511 $line_obj->batch = $obj->batch;
512 $line_obj->eatby = $obj->eatby;
513 $line_obj->sellby = $obj->sellby;
514 $line_obj->iskit = 0;
515 $line_obj->incdec = 0;
516 $list[$line_id][] = $line_obj;
517 }
518
519 $this->findAllChild($child_line_id, $list, $mode);
520 }
521 $this->db->free($resql);
522 } else {
523 $this->error = $this->db->lasterror();
524 $this->errors[] = $this->error;
525 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
526 }
527 }
528
529 return 1;
530 }
531
539 public function delete($user = null, $notrigger = 0)
540 {
541 $error = 0;
542
543 $this->db->begin();
544
545 // virtual products : delete all children and batch
546 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS') && !($this->fk_parent > 0)) {
547 // find all children
548 $line_id_list = array();
549 $result = $this->findAllChild($this->id, $line_id_list);
550 if ($result) {
551 $child_line_id_list = array_reverse($line_id_list, true);
552 foreach ($child_line_id_list as $child_line_id_arr) {
553 foreach ($child_line_id_arr as $child_line_id) {
554 // delete batch expedition line
555 if (isModEnabled('productbatch')) {
556 $sql = "DELETE FROM " . $this->db->prefix() . "expeditiondet_batch";
557 $sql .= " WHERE fk_expeditiondet = " . ((int) $child_line_id);
558 if (!$this->db->query($sql)) {
559 $error++;
560 $this->errors[] = $this->db->lasterror() . " - sql=$sql";
561 }
562 }
563
564 $sql = "DELETE FROM " . $this->db->prefix() . "expeditiondet";
565 $sql .= " WHERE rowid = " . ((int) $child_line_id);
566 if (!$this->db->query($sql)) {
567 $error++;
568 $this->errors[] = $this->db->lasterror() . " - sql=$sql";
569 }
570
571 if ($error) {
572 break;
573 }
574 }
575 if ($error) {
576 break;
577 }
578 }
579 } else {
580 $error++;
581 }
582 }
583
584 if (!$error) {
585 // delete batch expedition line
586 if (isModEnabled('productbatch')) {
587 $sql = "DELETE FROM ".$this->db->prefix()."expeditiondet_batch";
588 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
589
590 if (!$this->db->query($sql)) {
591 $this->errors[] = $this->db->lasterror()." - sql=$sql";
592 $error++;
593 }
594 }
595
596 $sql = "DELETE FROM ".$this->db->prefix()."expeditiondet";
597 $sql .= " WHERE rowid = ".((int) $this->id);
598
599 if (!$error && $this->db->query($sql)) {
600 // Remove extrafields
601 if (!$error) {
602 $result = $this->deleteExtraFields();
603 if ($result < 0) {
604 $this->errors[] = $this->error;
605 $error++;
606 }
607 }
608 if (!$error && !$notrigger) {
609 // Call trigger
610 $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
611 if ($result < 0) {
612 $this->errors[] = $this->error;
613 $error++;
614 }
615 // End call triggers
616 }
617 } else {
618 $this->errors[] = $this->db->lasterror()." - sql=$sql";
619 $error++;
620 }
621 }
622
623 if (!$error) {
624 $this->db->commit();
625 return 1;
626 } else {
627 foreach ($this->errors as $errmsg) {
628 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
629 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
630 }
631 $this->db->rollback();
632 return -1 * $error;
633 }
634 }
635
643 public function update($user = null, $notrigger = 0)
644 {
645 global $langs;
646 $error = 0;
647
648 dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
649
650 $this->db->begin();
651
652 // Clean parameters
653 if (empty($this->qty)) {
654 $this->qty = 0;
655 }
656 $qty = price2num($this->qty);
657 $remainingQty = 0;
658 $batch = null;
659 $batch_id = 0;
660 $expedition_batch_id = 0;
661 if (is_array($this->detail_batch)) { // array of ExpeditionLineBatch
662 if (count($this->detail_batch) > 1) {
663 dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
664 $this->errors[] = 'ErrorBadParameters';
665 $error++;
666 } else {
667 $batch = $this->detail_batch[0]->batch;
668 $batch_id = $this->detail_batch[0]->fk_origin_stock;
669 $expedition_batch_id = $this->detail_batch[0]->id;
670 if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id) {
671 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
672 $this->errors[] = 'ErrorBadParameters';
673 $error++;
674 }
675 $qty = price2num($this->detail_batch[0]->qty);
676 }
677 } elseif (!empty($this->detail_batch)) {
678 $batch = $this->detail_batch->batch;
679 $batch_id = $this->detail_batch->fk_origin_stock;
680 $expedition_batch_id = $this->detail_batch->id;
681 if ($this->entrepot_id != $this->detail_batch->entrepot_id) {
682 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
683 $this->errors[] = 'ErrorBadParameters';
684 $error++;
685 }
686 $qty = price2num($this->detail_batch->qty);
687 }
688
689 // check parameters
690 if (!isset($this->id) || !isset($this->entrepot_id)) {
691 dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
692 $langs->load('errors');
693 $this->errors[] = $langs->trans('ErrorMandatoryParametersNotProvided');
694 $error++;
695 return -1;
696 }
697
698 // update lot
699
700 if (!empty($batch) && isModEnabled('productbatch')) {
701 $batch_id_str = $batch_id ?? 'null';
702 dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id_str, batch=$batch");
703
704 if (empty($batch_id) || empty($this->fk_product)) {
705 dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
706 $langs->load('errors');
707 $this->errors[] = $langs->trans('ErrorMandatoryParametersNotProvided');
708 $error++;
709 }
710
711 // fetch remaining lot qty
712 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
713 $lotArray = $shipmentlinebatch->fetchAll($this->id);
714 if (!$error && $lotArray < 0) {
715 $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
716 $error++;
717 } else {
718 // calculate new total line qty
719 foreach ($lotArray as $lot) {
720 if ($expedition_batch_id != $lot->id) {
721 $remainingQty += $lot->qty;
722 }
723 }
724 $qty += $remainingQty;
725
726 //fetch lot details
727
728 // fetch from product_lot
729 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
730 $lot = new Productlot($this->db);
731 if ($lot->fetch(0, $this->fk_product, $batch) < 0) {
732 $this->errors = array_merge($this->errors, $lot->errors);
733 $error++;
734 }
735 if (!$error && !empty($expedition_batch_id)) {
736 // delete lot expedition line
737 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
738 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
739 $sql .= " AND rowid = ".((int) $expedition_batch_id);
740
741 if (!$this->db->query($sql)) {
742 $this->errors[] = $this->db->lasterror()." - sql=$sql";
743 $error++;
744 }
745 }
746 if (!$error && $this->detail_batch->qty > 0) {
747 // create lot expedition line
748 if (isset($lot->id)) {
749 $shipmentLot = new ExpeditionLineBatch($this->db);
750 $shipmentLot->batch = $lot->batch;
751 $shipmentLot->eatby = $lot->eatby;
752 $shipmentLot->sellby = $lot->sellby;
753 $shipmentLot->fk_warehouse = $this->detail_batch->entrepot_id;
754 $shipmentLot->qty = $this->detail_batch->qty;
755 $shipmentLot->fk_origin_stock = (int) $batch_id;
756 if ($shipmentLot->create($this->id) < 0) {
757 $this->errors = $shipmentLot->errors;
758 $error++;
759 }
760 }
761 }
762 }
763 }
764 if (!$error) {
765 // update line
766 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
767 $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
768 $sql .= " , qty = ".((float) price2num($qty, 'MS'));
769 $sql .= " WHERE rowid = ".((int) $this->id);
770
771 if (!$this->db->query($sql)) {
772 $this->errors[] = $this->db->lasterror()." - sql=$sql";
773 $error++;
774 }
775 }
776
777 if (!$error) {
778 $result = $this->insertExtraFields();
779 if ($result < 0) {
780 $this->errors[] = $this->error;
781 $error++;
782 }
783 }
784
785 if (!$error && !$notrigger) {
786 // Call trigger
787 $result = $this->call_trigger('LINESHIPPING_MODIFY', $user);
788 if ($result < 0) {
789 $this->errors[] = $this->error;
790 $error++;
791 }
792 // End call triggers
793 }
794 if (!$error) {
795 $this->db->commit();
796 return 1;
797 } else {
798 foreach ($this->errors as $errmsg) {
799 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
800 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
801 }
802 $this->db->rollback();
803 return -1 * $error;
804 }
805 }
806}
deleteExtraFields()
Delete all extra fields values for the current object.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
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 lines of shipment.
fetch($rowid)
Load line expedition.
findAllChild($line_id, &$list=array(), $mode=0)
Find all children.
__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 order lines.
Class with list of lots and properties.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.