dolibarr 23.0.3
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 * Copyright (C) 2025 Nick Fragoulis
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 3 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31 */
32
39require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
40require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionlinebatch.class.php';
41
46{
50 public $element = 'expeditiondet';
51
55 public $table_element = 'expeditiondet';
56
60 public $parent_element = 'expedition';
61
65 public $fk_parent_attribute = 'fk_expedition';
66
73 public $line_id; // deprecated
74
78 public $fk_element;
79
83 public $origin_id;
84
88 public $fk_elementdet;
89
93 public $origin_line_id;
94
98 public $fk_parent;
99
103 public $element_type;
104
105
112 public $fk_origin; // Example: 'orderline'
113
117 public $fk_expedition;
118
122 public $db;
123
127 public $qty;
128
132 public $qty_shipped;
133
137 public $fk_product;
138
144 public $detail_batch;
145
150 public $detail_children;
151
156 public $details_entrepot;
157
158
162 public $entrepot_id;
163
164
168 public $qty_asked;
169
175 public $ref;
176
180 public $product_ref;
181
187 public $libelle;
188
192 public $product_label;
193
199 public $desc;
200
204 public $product_desc;
205
210 public $product_type = 0;
211
215 public $rang;
216
220 public $weight;
221
225 public $weight_units;
226
230 public $length;
231
235 public $length_units;
236
240 public $width;
241
245 public $width_units;
246
250 public $height;
251
255 public $height_units;
256
260 public $surface;
261
265 public $surface_units;
266
270 public $volume;
271
275 public $volume_units;
276
282 public $stockable_product = 1;
283
287 public $remise_percent;
288
292 public $tva_tx;
293
297 public $total_ht;
298
302 public $total_ttc;
303
307 public $total_tva;
308
312 public $total_localtax1;
313
317 public $total_localtax2;
318
319
325 public function __construct($db)
326 {
327 $this->db = $db;
328 }
329
336 public function fetch($rowid)
337 {
338 $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.description, ed.fk_unit, ed.fk_elementdet, ed.element_type, ed.qty, ed.rang, ed.extraparams';
339 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
340 $sql .= ' WHERE ed.rowid = '.((int) $rowid);
341 $result = $this->db->query($sql);
342 if ($result) {
343 $objp = $this->db->fetch_object($result);
344 $this->id = $objp->rowid;
345 $this->fk_expedition = $objp->fk_expedition;
346 $this->entrepot_id = $objp->fk_entrepot;
347 $this->description = $objp->description;
348 $this->fk_unit = $objp->fk_unit;
349 $this->fk_elementdet = $objp->fk_elementdet;
350 $this->element_type = $objp->element_type;
351 $this->qty = $objp->qty;
352 $this->rang = $objp->rang;
353
354 $this->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
355
356 $this->db->free($result);
357
358 return 1;
359 } else {
360 $this->errors[] = $this->db->lasterror();
361 $this->error = $this->db->lasterror();
362 return -1;
363 }
364 }
365
373 public function insert($user = null, $notrigger = 0)
374 {
375 global $langs;
376 $error = 0;
377
378 $skip_check_parameters = false;
379
380 // Handling parent line subtotal line
381 if (!empty($this->element_type)
382 && !empty($this->fk_elementdet)
383 && $this->element_type == 'commande') {
384 $objectsrc_line = new OrderLine($this->db);
385 $objectsrc_line->fetch($this->fk_elementdet);
386 $skip_check_parameters = $objectsrc_line->special_code == SUBTOTALS_SPECIAL_CODE;
387 }
388
389 // Check parameters
390 $origin_id = $this->origin_id;
391 if ($origin_id > 0) {
392 if ((empty($this->fk_expedition)
393 || (empty($this->fk_elementdet) && empty($this->fk_parent)) // at least origin line id of parent line id is set
394 || !is_numeric($this->qty))
395 && !$skip_check_parameters) {
396 $langs->load('errors');
397 $this->errors[] = $langs->trans('ErrorMandatoryParametersNotProvided');
398 return -1;
399 }
400 } else {
401 if (empty($this->fk_expedition) || !is_numeric($this->qty)) {
402 $langs->load('errors');
403 $this->errors[] = $langs->trans('ErrorMandatoryParametersNotProvided');
404 return -1;
405 }
406 }
407 $this->db->begin();
408
409 if (empty($this->rang)) {
410 $this->rang = 0;
411 }
412
413 // Rank to use
414 $ranktouse = $this->rang;
415 if ($ranktouse == -1) {
416 $rangmax = $this->line_max($this->fk_expedition);
417 $ranktouse = $rangmax + 1;
418 }
419
420 $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
421 $sql .= "fk_expedition";
422 $sql .= ", fk_entrepot";
423 $sql .= ", fk_elementdet";
424 $sql .= ", fk_parent";
425 $sql .= ", fk_product";
426 $sql .= ", element_type";
427 $sql .= ", qty";
428 $sql .= ", fk_unit";
429 $sql .= ", description";
430 $sql .= ", rang";
431 $sql .= ") VALUES (";
432 $sql .= $this->fk_expedition;
433 $sql .= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
434 $sql .= ", ".(empty($this->fk_elementdet) ? 'NULL' : $this->fk_elementdet);
435 $sql .= ", ".(empty($this->fk_parent) ? 'NULL' : $this->fk_parent);
436 $sql .= ", ".(empty($this->fk_product) ? 'NULL' : $this->fk_product);
437 $sql .= ", '".(empty($this->element_type) ? 'order' : $this->db->escape($this->element_type))."'";
438 $sql .= ", ".price2num($this->qty, 'MS');
439 $sql .= ", ".((int) $this->fk_unit);
440 $sql .= ", '".(empty($this->description) ? '' : $this->db->escape($this->description))."'";
441 $sql .= ", ".((int) $ranktouse);
442 $sql .= ")";
443
444 dol_syslog(get_class($this)."::insert", LOG_DEBUG);
445 $resql = $this->db->query($sql);
446 if ($resql) {
447 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
448
449 if (!$error) {
450 $result = $this->insertExtraFields();
451 if ($result < 0) {
452 $error++;
453 }
454 }
455
456 if (!$error && !$notrigger) {
457 // Call trigger
458 $result = $this->call_trigger('LINESHIPPING_INSERT', $user);
459 if ($result < 0) {
460 $error++;
461 }
462 // End call triggers
463 }
464
465 if ($error) {
466 foreach ($this->errors as $errmsg) {
467 dol_syslog(__METHOD__.' '.$errmsg, LOG_ERR);
468 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
469 }
470 }
471 } else {
472 $error++;
473 }
474
475 if ($error) {
476 $this->db->rollback();
477 return -1;
478 } else {
479 $this->db->commit();
480 return $this->id;
481 }
482 }
483
492 public function findAllChild($line_id, &$list = array(), $mode = 0)
493 {
494 if ($line_id > 0) {
495 // find all child
496 $sql = "SELECT ed.rowid as child_line_id";
497 if ($mode == 1) {
498 $sql .= ", ed.fk_product";
499 $sql .= ", ed.fk_parent";
500 $sql .= ", " . $this->db->ifsql('eb.rowid IS NULL', 'ed.qty', 'eb.qty') . " as qty";
501 $sql .= ", " . $this->db->ifsql('eb.rowid IS NULL', 'ed.fk_entrepot', 'eb.fk_warehouse') . " as fk_warehouse";
502 $sql .= ", eb.batch, eb.eatby, eb.sellby";
503 }
504 $sql .= " FROM " . $this->db->prefix() . $this->table_element . " as ed";
505 $sql .= " LEFT JOIN " . $this->db->prefix() . "expeditiondet_batch as eb ON eb.fk_expeditiondet = " . ((int) $line_id);
506 $sql .= " WHERE ed.fk_parent = " . ((int) $line_id);
507 $sql .= $this->db->order('ed.fk_product,ed.rowid', 'ASC,ASC');
508
509 $resql = $this->db->query($sql);
510 if ($resql) {
511 while ($obj = $this->db->fetch_object($resql)) {
512 $child_line_id = (int) $obj->child_line_id;
513 if (!isset($list[$line_id])) {
514 $list[$line_id] = array();
515 }
516
517 if ($mode == 0) {
518 $list[$line_id][] = $child_line_id;
519 } elseif ($mode == 1) {
520 $line_obj = new stdClass();
521 $line_obj->rowid = $child_line_id;
522 $line_obj->fk_product = $obj->fk_product;
523 $line_obj->fk_parent = $obj->fk_parent;
524 $line_obj->qty = $obj->qty;
525 $line_obj->fk_warehouse = $obj->fk_warehouse;
526 $line_obj->batch = $obj->batch;
527 $line_obj->eatby = $obj->eatby;
528 $line_obj->sellby = $obj->sellby;
529 $line_obj->iskit = 0;
530 $line_obj->incdec = 0;
531 $list[$line_id][] = $line_obj;
532 }
533
534 $this->findAllChild($child_line_id, $list, $mode);
535 }
536 $this->db->free($resql);
537 } else {
538 $this->error = $this->db->lasterror();
539 $this->errors[] = $this->error;
540 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
541 }
542 }
543
544 return 1;
545 }
546
554 public function delete($user = null, $notrigger = 0)
555 {
556 $error = 0;
557
558 $this->db->begin();
559
560 // virtual products : delete all children and batch
561 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS') && !($this->fk_parent > 0)) {
562 // find all children
563 $line_id_list = array();
564 $result = $this->findAllChild($this->id, $line_id_list);
565 if ($result) {
566 $child_line_id_list = array_reverse($line_id_list, true);
567 foreach ($child_line_id_list as $child_line_id_arr) {
568 foreach ($child_line_id_arr as $child_line_id) {
569 // delete batch expedition line
570 if (isModEnabled('productbatch')) {
571 $sql = "DELETE FROM " . $this->db->prefix() . "expeditiondet_batch";
572 $sql .= " WHERE fk_expeditiondet = " . ((int) $child_line_id);
573 if (!$this->db->query($sql)) {
574 $error++;
575 $this->errors[] = $this->db->lasterror() . " - sql=$sql";
576 }
577 }
578
579 $sql = "DELETE FROM " . $this->db->prefix() . "expeditiondet";
580 $sql .= " WHERE rowid = " . ((int) $child_line_id);
581 if (!$this->db->query($sql)) {
582 $error++;
583 $this->errors[] = $this->db->lasterror() . " - sql=$sql";
584 }
585
586 if ($error) {
587 break;
588 }
589 }
590 if ($error) {
591 break;
592 }
593 }
594 } else {
595 $error++;
596 }
597 }
598
599 if (!$error) {
600 // delete batch expedition line
601 if (isModEnabled('productbatch')) {
602 $sql = "DELETE FROM ".$this->db->prefix()."expeditiondet_batch";
603 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
604
605 if (!$this->db->query($sql)) {
606 $this->errors[] = $this->db->lasterror()." - sql=$sql";
607 $error++;
608 }
609 }
610
611 $sql = "DELETE FROM ".$this->db->prefix()."expeditiondet";
612 $sql .= " WHERE rowid = ".((int) $this->id);
613
614 if (!$error && $this->db->query($sql)) {
615 // Remove extrafields
616 if (!$error) {
617 $result = $this->deleteExtraFields();
618 if ($result < 0) {
619 $this->errors[] = $this->error;
620 $error++;
621 }
622 }
623 if (!$error && !$notrigger) {
624 // Call trigger
625 $result = $this->call_trigger('LINESHIPPING_DELETE', $user);
626 if ($result < 0) {
627 $this->errors[] = $this->error;
628 $error++;
629 }
630 // End call triggers
631 }
632 } else {
633 $this->errors[] = $this->db->lasterror()." - sql=$sql";
634 $error++;
635 }
636 }
637
638 if (!$error) {
639 $this->db->commit();
640 return 1;
641 } else {
642 foreach ($this->errors as $errmsg) {
643 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
644 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
645 }
646 $this->db->rollback();
647 return -1 * $error;
648 }
649 }
650
658 public function update($user = null, $notrigger = 0)
659 {
660 global $langs;
661 $error = 0;
662
663 dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
664
665 $this->db->begin();
666
667 // Clean parameters
668 if (empty($this->qty)) {
669 $this->qty = 0;
670 }
671 $qty = price2num($this->qty);
672 $fk_unit = $this->fk_unit;
673 $remainingQty = 0;
674 $batch = null;
675 $batch_id = 0;
676 $expedition_batch_id = 0;
677 $origin_id = $this->origin_id;
678 if ($origin_id > 0) {
679 if (is_array($this->detail_batch)) { // array of ExpeditionLineBatch
680 if (count($this->detail_batch) > 1) {
681 dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
682 $this->errors[] = 'ErrorBadParameters';
683 $error++;
684 } else {
685 $batch = $this->detail_batch[0]->batch;
686 $batch_id = $this->detail_batch[0]->fk_origin_stock;
687 $expedition_batch_id = $this->detail_batch[0]->id;
688 if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id) {
689 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
690 $this->errors[] = 'ErrorBadParameters';
691 $error++;
692 }
693 $qty = price2num($this->detail_batch[0]->qty);
694 }
695 } elseif (!empty($this->detail_batch)) {
696 $batch = $this->detail_batch->batch;
697 $batch_id = $this->detail_batch->fk_origin_stock;
698 $expedition_batch_id = $this->detail_batch->id;
699 if ($this->entrepot_id != $this->detail_batch->entrepot_id) {
700 dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
701 $this->errors[] = 'ErrorBadParameters';
702 $error++;
703 }
704 $qty = price2num($this->detail_batch->qty);
705 }
706
707 // check parameters
708 if (!isset($this->id) || !isset($this->entrepot_id)) {
709 dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
710 $langs->load('errors');
711 $this->errors[] = $langs->trans('ErrorMandatoryParametersNotProvided');
712 $error++;
713 return -1;
714 }
715
716 // update lot
717 if (!empty($batch) && isModEnabled('productbatch')) {
718 $batch_id_str = $batch_id ?? 'null';
719 dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id_str, batch=$batch");
720
721 if (empty($batch_id) || empty($this->fk_product)) {
722 dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
723 $langs->load('errors');
724 $this->errors[] = $langs->trans('ErrorMandatoryParametersNotProvided');
725 $error++;
726 }
727
728 // fetch remaining lot qty
729 $shipmentlinebatch = new ExpeditionLineBatch($this->db);
730 $lotArray = $shipmentlinebatch->fetchAll($this->id);
731 if (!$error && $lotArray < 0) {
732 $this->errors[] = $this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
733 $error++;
734 } else {
735 // calculate new total line qty
736 foreach ($lotArray as $lot) {
737 if ($expedition_batch_id != $lot->id) {
738 $remainingQty += $lot->qty;
739 }
740 }
741 $qty += $remainingQty;
742
743 //fetch lot details
744 // fetch from product_lot
745 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
746 $lot = new Productlot($this->db);
747 if ($lot->fetch(0, $this->fk_product, $batch) < 0) {
748 $this->errors = array_merge($this->errors, $lot->errors);
749 $error++;
750 }
751 if (!$error && !empty($expedition_batch_id)) {
752 // delete lot expedition line
753 $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
754 $sql .= " WHERE fk_expeditiondet = ".((int) $this->id);
755 $sql .= " AND rowid = ".((int) $expedition_batch_id);
756
757 if (!$this->db->query($sql)) {
758 $this->errors[] = $this->db->lasterror()." - sql=$sql";
759 $error++;
760 }
761 }
762 if (!$error && $this->detail_batch->qty > 0) {
763 // create lot expedition line
764 if (isset($lot->id)) {
765 $shipmentLot = new ExpeditionLineBatch($this->db);
766 $shipmentLot->batch = $lot->batch;
767 $shipmentLot->eatby = $lot->eatby;
768 $shipmentLot->sellby = $lot->sellby;
769 $shipmentLot->fk_warehouse = $this->detail_batch->entrepot_id;
770 $shipmentLot->qty = $this->detail_batch->qty;
771 $shipmentLot->fk_origin_stock = (int) $batch_id;
772 if ($shipmentLot->create($this->id) < 0) {
773 $this->errors = $shipmentLot->errors;
774 $error++;
775 }
776 }
777 }
778 }
779 }
780 }
781 if (!$error) {
782 // update line
783 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
784 $sql .= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
785 $sql .= " , qty = ".((float) price2num($qty, 'MS'));
786 $sql .= " , fk_unit = ".((int) $this->fk_unit);
787 $sql .= " WHERE rowid = ".((int) $this->id);
788
789 if (!$this->db->query($sql)) {
790 $this->errors[] = $this->db->lasterror()." - sql=$sql";
791 $error++;
792 }
793 }
794
795 if (!$error) {
796 $result = $this->insertExtraFields();
797 if ($result < 0) {
798 $this->errors[] = $this->error;
799 $error++;
800 }
801 }
802
803 if (!$error && !$notrigger) {
804 // Call trigger
805 $result = $this->call_trigger('LINESHIPPING_MODIFY', $user);
806 if ($result < 0) {
807 $this->errors[] = $this->error;
808 $error++;
809 }
810 // End call triggers
811 }
812 if (!$error) {
813 $this->db->commit();
814 return 1;
815 } else {
816 foreach ($this->errors as $errmsg) {
817 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
818 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
819 }
820 $this->db->rollback();
821 return -1 * $error;
822 }
823 }
824}
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.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage lines of shipment.
insert($user=null, $notrigger=0)
Insert line into database.
fetch($rowid)
Load line expedition.
findAllChild($line_id, &$list=array(), $mode=0)
Find all children.
__construct($db)
Constructor.
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.
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
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.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.