dolibarr 23.0.3
reception.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@capnetworks.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-2017 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-2020 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 Quentin Vial-Gouteyron <quentin.vial-gouteyron@atm-consulting.fr>
14 * Copyright (C) 2022-2025 Frédéric France <frederic.france@free.fr>
15 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
16 * Copyright (C) 2025 Nick Fragoulis
17 * Copyright (C) 2026 Mathieu Moulin <mathieu@iprospective.fr>
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/commonobject.class.php';
40require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
41require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
42require_once DOL_DOCUMENT_ROOT.'/reception/class/receptionlinebatch.class.php';
43if (isModEnabled("propal")) {
44 require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
45}
46if (isModEnabled('order')) {
47 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
48}
49
50
55{
56 use CommonIncoterm;
57
62 public $TRIGGER_PREFIX = 'RECEPTION'; // to be overridden in child class implementations, i.e. 'BILL', 'TASK', 'PROPAL', etc.
63
67 public $code = "";
68
72 public $element = "reception";
73
77 public $fk_element = "fk_reception";
78
82 public $table_element = "reception";
83
87 public $table_element_line = "receptiondet_batch";
88
92 public $picto = 'dollyrevert';
93
97 public $socid;
101 public $ref_supplier;
102
106 public $entrepot_id;
110 public $tracking_number;
114 public $tracking_url;
118 public $billed;
119
123 public $weight;
127 public $trueWeight;
131 public $weight_units;
135 public $trueWidth;
139 public $width_units;
143 public $trueHeight;
147 public $height_units;
151 public $trueDepth;
155 public $depth_units;
159 public $trueSize;
163 public $size_units;
167 public $user_author_id;
168
172 public $date_delivery;
173
179 public $date;
180
184 public $date_reception;
185
189 public $date_valid;
190
194 public $meths;
198 public $listmeths; // List of carriers
199
203 public $lines = array();
204
205
210 public $detail_batch;
211
212 const STATUS_DRAFT = 0;
213 const STATUS_VALIDATED = 1;
214 const STATUS_CLOSED = 2;
215
216
222 public function __construct($db)
223 {
224 $this->db = $db;
225
226 $this->ismultientitymanaged = 1; // 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
227 $this->isextrafieldmanaged = 1;
228 }
229
236 public function getNextNumRef($soc)
237 {
238 global $langs, $conf;
239 $langs->load("receptions");
240
241 if (getDolGlobalString('RECEPTION_ADDON_NUMBER')) {
242 $mybool = false;
243
244 $file = getDolGlobalString('RECEPTION_ADDON_NUMBER') . ".php";
245 $classname = getDolGlobalString('RECEPTION_ADDON_NUMBER');
246
247 // Include file with class
248 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
249
250 foreach ($dirmodels as $reldir) {
251 $dir = dol_buildpath($reldir."core/modules/reception/");
252
253 // Load file with numbering class (if found)
254 $mybool = ((bool) @include_once $dir.$file) || $mybool;
255 }
256
257 if (!$mybool) {
258 dol_print_error(null, "Failed to include file ".$file);
259 return '';
260 }
261
262 $obj = new $classname();
263 '@phan-var-force ModelNumRefReception $obj';
266 $numref = $obj->getNextValue($soc, $this);
267
268 if ($numref != "") {
269 return $numref;
270 } else {
271 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
272 return "";
273 }
274 } else {
275 print $langs->trans("Error")." ".$langs->trans("Error_RECEPTION_ADDON_NUMBER_NotDefined");
276 return "";
277 }
278 }
279
287 public function create($user, $notrigger = 0)
288 {
289 global $conf;
290
291 $now = dol_now();
292
293 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
294 $error = 0;
295
296 // Clean parameters
297 $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
298 if (empty($this->fk_project)) {
299 $this->fk_project = 0;
300 }
301 if (empty($this->weight_units)) {
302 $this->weight_units = 0;
303 }
304 if (empty($this->size_units)) {
305 $this->size_units = 0;
306 }
307 if (empty($this->date_creation)) {
308 $this->date_creation = $now;
309 }
310
311 $this->entity = setEntity($this);
312
313 $this->user = $user;
314
315 $this->db->begin();
316
317 $sql = "INSERT INTO ".MAIN_DB_PREFIX."reception (";
318 $sql .= "ref";
319 $sql .= ", entity";
320 $sql .= ", ref_supplier";
321 $sql .= ", date_creation";
322 $sql .= ", fk_user_author";
323 $sql .= ", date_reception";
324 $sql .= ", date_delivery";
325 $sql .= ", fk_soc";
326 $sql .= ", fk_projet";
327 $sql .= ", fk_shipping_method";
328 $sql .= ", tracking_number";
329 $sql .= ", weight";
330 $sql .= ", size";
331 $sql .= ", width";
332 $sql .= ", height";
333 $sql .= ", weight_units";
334 $sql .= ", size_units";
335 $sql .= ", note_private";
336 $sql .= ", note_public";
337 $sql .= ", model_pdf";
338 $sql .= ", fk_incoterms, location_incoterms";
339 $sql .= ") VALUES (";
340 $sql .= "'(PROV)'";
341 $sql .= ", ".((int) $this->entity);
342 $sql .= ", ".($this->ref_supplier ? "'".$this->db->escape($this->ref_supplier)."'" : "null");
343 $sql .= ", '".$this->db->idate($this->date_creation)."'";
344 $sql .= ", ".((int) $user->id);
345 $sql .= ", ".($this->date_reception > 0 ? "'".$this->db->idate($this->date_reception)."'" : "null");
346 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
347 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
348 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
349 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
350 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
351 $sql .= ", ".(is_null($this->weight) ? "NULL" : ((float) $this->weight));
352 $sql .= ", ".(is_null($this->trueDepth) ? "NULL" : ((float) $this->trueDepth));
353 $sql .= ", ".(is_null($this->trueWidth) ? "NULL" : ((float) $this->trueWidth));
354 $sql .= ", ".(is_null($this->trueHeight) ? "NULL" : ((float) $this->trueHeight));
355 $sql .= ", ".((int) $this->weight_units);
356 $sql .= ", ".((int) $this->size_units);
357 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
358 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
359 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
360 $sql .= ", ".(int) $this->fk_incoterms;
361 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
362 $sql .= ")";
363
364 dol_syslog(get_class($this)."::create", LOG_DEBUG);
365
366 $resql = $this->db->query($sql);
367
368 if ($resql) {
369 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."reception");
370
371 // update ref
372 $initialref = '(PROV'.$this->id.')';
373 if (!empty($this->ref)) {
374 $initialref = $this->ref;
375 }
376
377 $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
378 $sql .= " SET ref = '".$this->db->escape($initialref)."'";
379 $sql .= " WHERE rowid = ".((int) $this->id);
380
381 dol_syslog(get_class($this)."::create", LOG_DEBUG);
382 if ($this->db->query($sql)) {
383 // Insert of lines
384 $num = count($this->lines);
385 for ($i = 0; $i < $num; $i++) {
386 $this->lines[$i]->fk_reception = $this->id;
387
388 if ($this->lines[$i]->create($user) <= 0) {
389 $this->setErrorsFromObject($this->lines[$i]);
390 $error++;
391 }
392 }
393
394 if (!$error && $this->id && $this->origin_id) {
395 $ret = $this->add_object_linked();
396 if (!$ret) {
397 $error++;
398 }
399 }
400
401 // Create extrafields
402 if (!$error) {
403 $result = $this->insertExtraFields();
404 if ($result < 0) {
405 $error++;
406 }
407 }
408
409 if (!$error && !$notrigger) {
410 // Call trigger
411 $result = $this->call_trigger('RECEPTION_CREATE', $user);
412 if ($result < 0) {
413 $error++;
414 }
415 // End call triggers
416 }
417
418 if (!$error) {
419 $this->db->commit();
420 return $this->id;
421 } else {
422 foreach ($this->errors as $errmsg) {
423 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
424 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
425 }
426 $this->db->rollback();
427 return -1 * $error;
428 }
429 } else {
430 $error++;
431 $this->error = $this->db->lasterror()." - sql=$sql";
432 $this->db->rollback();
433 return -2;
434 }
435 } else {
436 $error++;
437 $this->error = $this->db->error()." - sql=$sql";
438 $this->db->rollback();
439 return -1;
440 }
441 }
442
443 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
456 public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [], $parent_line_id = 0, $product_id = 0)
457 {
458 //phpcs:enable
459 global $user;
460
461 $receptionline = new ReceptionLineBatch($this->db);
462 $receptionline->fk_reception = $this->id;
463 $receptionline->entrepot_id = $entrepot_id;
464 $receptionline->fk_elementdet = $origin_line_id;
465 $receptionline->element_type = $this->origin;
466 $receptionline->fk_parent = $parent_line_id;
467 $receptionline->fk_product = $product_id;
468 $receptionline->qty = $qty;
469 $receptionline->rang = $rang;
470 $receptionline->array_options = $array_options;
471
472 if (!($receptionline->fk_product > 0)) {
473 $order_line = new CommandeFournisseurLigne($this->db);
474 $order_line->fetch($receptionline->fk_elementdet);
475 $receptionline->fk_product = $order_line->fk_product;
476 }
477
478 if (($lineId = $receptionline->insert($user)) < 0) {
479 $this->errors[] = $receptionline->error;
480 }
481 return $lineId;
482 }
483
492 public function fetch($id, $ref = '', $ref_ext = '')
493 {
494 // Check parameters
495 if (empty($id) && empty($ref) && empty($ref_ext)) {
496 return -1;
497 }
498
499 $sql = "SELECT e.rowid, e.entity, e.ref, e.fk_soc as socid, e.date_creation, e.ref_supplier, e.ref_ext, e.fk_user_author, e.fk_statut as status, e.fk_projet as fk_project, e.billed";
500 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
501 $sql .= ", e.date_reception as date_reception, e.model_pdf, e.date_delivery, e.date_valid";
502 $sql .= ", e.fk_shipping_method, e.tracking_number";
503 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin";
504 $sql .= ", e.note_private, e.note_public";
505 $sql .= ', e.fk_incoterms, e.location_incoterms';
506 $sql .= ', i.libelle as label_incoterms';
507 $sql .= " FROM ".MAIN_DB_PREFIX."reception as e";
508 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."' AND el.sourcetype = 'order_supplier'";
509 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
510
511 if ($id) {
512 $sql .= " WHERE e.rowid = ".((int) $id);
513 } else {
514 $sql .= " WHERE e.entity IN (".getEntity('reception').")";
515 if ($ref) {
516 $sql .= " AND e.ref = '".$this->db->escape($ref)."'";
517 } elseif ($ref_ext) {
518 $sql .= " AND e.ref_ext = '".$this->db->escape($ref_ext)."'";
519 }
520 }
521
522 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
523 $result = $this->db->query($sql);
524 if ($result) {
525 if ($this->db->num_rows($result)) {
526 $obj = $this->db->fetch_object($result);
527
528 $this->id = $obj->rowid;
529 $this->entity = $obj->entity;
530 $this->ref = $obj->ref;
531 $this->socid = $obj->socid;
532 $this->ref_supplier = $obj->ref_supplier;
533 $this->ref_ext = $obj->ref_ext;
534 $this->statut = $obj->status;
535 $this->status = $obj->status;
536 $this->billed = $obj->billed;
537 $this->fk_project = $obj->fk_project;
538 $this->user_author_id = $obj->fk_user_author;
539 $this->date_creation = $this->db->jdate($obj->date_creation);
540 $this->date = $this->db->jdate($obj->date_reception); // TODO deprecated
541 $this->date_reception = $this->db->jdate($obj->date_reception); // Date real
542 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
543 $this->date_valid = $this->db->jdate($obj->date_valid); // Date validation
544 $this->model_pdf = $obj->model_pdf;
545 $this->shipping_method_id = $obj->fk_shipping_method;
546 $this->tracking_number = $obj->tracking_number;
547 $this->origin = ($obj->origin ? $obj->origin : 'commande'); // For compatibility
548 $this->origin_type = ($obj->origin ? $obj->origin : 'commande'); // For compatibility
549 $this->origin_id = $obj->origin_id;
550
551 $this->trueWeight = $obj->weight;
552 $this->weight_units = $obj->weight_units;
553
554 $this->trueWidth = $obj->width;
555 $this->width_units = $obj->size_units;
556 $this->trueHeight = $obj->height;
557 $this->height_units = $obj->size_units;
558 $this->trueDepth = $obj->size;
559 $this->depth_units = $obj->size_units;
560
561 $this->note_public = $obj->note_public;
562 $this->note_private = $obj->note_private;
563
564 // A denormalized value
565 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
566 $this->size_units = $obj->size_units;
567
568 //Incoterms
569 $this->fk_incoterms = $obj->fk_incoterms;
570 $this->location_incoterms = $obj->location_incoterms;
571 $this->label_incoterms = $obj->label_incoterms;
572
573 $this->db->free($result);
574
575 //$file = $conf->reception->dir_output."/".get_exdir(0, 0, 0, 1, $this, 'reception')."/".$this->id.".pdf";
576 //$this->pdf_filename = $file;
577
578 // Tracking url
579 $this->getUrlTrackingStatus($obj->tracking_number);
580
581 /*
582 * Thirdparty
583 */
584 $result = $this->fetch_thirdparty();
585
586
587 // Retrieve all extrafields for reception
588 // fetch optionals attributes and labels
589 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
590 $extrafields = new ExtraFields($this->db);
591 $extrafields->fetch_name_optionals_label($this->table_element, true);
592 $this->fetch_optionals();
593
594 /*
595 * Lines
596 */
597 if (empty($obj->origin_id)) {
598 $result = $this->fetch_lines_free();
599 } else {
600 $result = $this->fetch_lines();
601 }
602 if ($result < 0) {
603 return -3;
604 }
605
606 return 1;
607 } else {
608 dol_syslog(get_class($this).'::Fetch no reception found', LOG_ERR);
609 $this->error = 'Reception with id '.$id.' not found';
610 return 0;
611 }
612 } else {
613 $this->error = $this->db->error();
614 return -1;
615 }
616 }
617
625 public function valid($user, $notrigger = 0)
626 {
627 global $conf, $langs;
628
629 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
630
631 dol_syslog(get_class($this)."::valid");
632
633 // Protection
634 if ($this->statut) {
635 dol_syslog(get_class($this)."::valid no draft status", LOG_WARNING);
636 return 0;
637 }
638
639 if (!((!getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'creer'))
640 || (getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'reception_advance', 'validate')))) {
641 $this->error = 'Permission denied';
642 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
643 return -1;
644 }
645
646 $this->db->begin();
647
648 $error = 0;
649
650 // Define new ref
651 $soc = new Societe($this->db);
652 $soc->fetch($this->socid);
653
654
655 // Define new ref
656 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
657 $numref = $this->getNextNumRef($soc);
658 } else {
659 $numref = (string) $this->ref;
660 }
661
662 $this->newref = dol_sanitizeFileName($numref);
663
664 $now = dol_now();
665
666 // Validate
667 $sql = "UPDATE ".MAIN_DB_PREFIX."reception SET";
668 $sql .= " ref='".$this->db->escape($numref)."'";
669 $sql .= ", fk_statut = 1";
670 $sql .= ", date_valid = '".$this->db->idate($now)."'";
671 $sql .= ", fk_user_valid = ".((int) $user->id);
672 $sql .= " WHERE rowid = ".((int) $this->id);
673 dol_syslog(get_class($this)."::valid update reception", LOG_DEBUG);
674 $resql = $this->db->query($sql);
675 if (!$resql) {
676 $this->error = $this->db->lasterror();
677 $error++;
678 }
679
680 // If stock increment is done on reception (recommended choice)
681 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION')) {
682 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
683
684 $langs->load("agenda");
685
686 // Loop on each product line to add a stock movement
687 // TODO in future, reception lines may not be linked to order line
688 $sql = "SELECT cd.fk_product, cd.subprice, cd.remise_percent,";
689 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
690 $sql .= " ed.eatby, ed.sellby, ed.batch,";
691 $sql .= " ed.fk_elementdet, ed.cost_price";
692 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
693 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
694 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
695 $sql .= " AND cd.rowid = ed.fk_elementdet";
696
697 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
698 $resql = $this->db->query($sql);
699 if ($resql) {
700 $cpt = $this->db->num_rows($resql);
701 for ($i = 0; $i < $cpt; $i++) {
702 $obj = $this->db->fetch_object($resql);
703
704 $qty = $obj->qty;
705
706 if ($qty == 0 || ($qty < 0 && !getDolGlobalInt('RECEPTION_ALLOW_NEGATIVE_QTY'))) {
707 continue;
708 }
709
710 dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid);
711
712 //var_dump($this->lines[$i]);
713 $mouvS = new MouvementStock($this->db);
714 $mouvS->origin = &$this;
715 $mouvS->setOrigin($this->element, $this->id, $obj->fk_elementdet, $obj->rowid);
716
717 if (empty($obj->batch)) {
718 // line without batch detail
719
720 // 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.
721 $inventorycode = '';
722 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionValidatedInDolibarr", $numref), '', '', '', '', 0, $inventorycode);
723
724 if (intval($result) < 0) {
725 $error++;
726 $this->setErrorsFromObject($mouvS);
727 break;
728 }
729 } else {
730 // line with batch detail
731
732 // 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.
733 // Note: ->fk_origin_stock = id into table llx_product_batch (may be rename into llx_product_stock_batch in another version)
734 $inventorycode = '';
735 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionValidatedInDolibarr", $numref), $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, '', 0, $inventorycode);
736
737 if (intval($result) < 0) {
738 $error++;
739 $this->setErrorsFromObject($mouvS);
740 break;
741 }
742 }
743 }
744 } else {
745 $this->db->rollback();
746 $this->error = $this->db->error();
747 return -2;
748 }
749 }
750
751 if (!$error && $this->origin_id > 0) {
752 // Change status of purchase order to "reception in process" or "totally received"
753 $status = $this->getStatusDispatch();
754 if ($status < 0) {
755 $error++;
756 } else {
757 $trigger_key = '';
758 if ($this->origin_object instanceof CommandeFournisseur && $status == CommandeFournisseur::STATUS_RECEIVED_COMPLETELY) {
759 $ret = $this->origin_object->Livraison($user, dol_now(), 'tot', '');
760 if ($ret < 0) {
761 $error++;
762 $this->errors = array_merge($this->errors, $this->origin_object->errors);
763 }
764 } else {
765 $ret = $this->setStatut($status, $this->origin_id, 'commande_fournisseur', $trigger_key);
766 if ($ret < 0) {
767 $error++;
768 }
769 }
770 }
771 }
772
773 if (!$error && !$notrigger) {
774 // Call trigger
775 $result = $this->call_trigger('RECEPTION_VALIDATE', $user);
776 if ($result < 0) {
777 $error++;
778 }
779 // End call triggers
780 }
781
782 if (!$error) {
783 $this->oldref = $this->ref;
784
785 // Rename directory if dir was a temporary ref
786 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
787 // Now we rename also files into index
788 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'reception/".$this->db->escape($this->newref)."'";
789 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'reception/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity);
790 $resql = $this->db->query($sql);
791 if (!$resql) {
792 $error++;
793 $this->error = $this->db->lasterror();
794 }
795 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'reception/".$this->db->escape($this->newref)."'";
796 $sql .= " WHERE filepath = 'reception/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
797 $resql = $this->db->query($sql);
798 if (!$resql) {
799 $error++;
800 $this->error = $this->db->lasterror();
801 }
802
803 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
804 $oldref = dol_sanitizeFileName($this->ref);
805 $newref = dol_sanitizeFileName($numref);
806 $dirsource = $conf->reception->dir_output.'/'.$oldref;
807 $dirdest = $conf->reception->dir_output.'/'.$newref;
808 if (!$error && file_exists($dirsource)) {
809 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
810
811 if (@rename($dirsource, $dirdest)) {
812 dol_syslog("Rename ok");
813 // Rename docs starting with $oldref with $newref
814 $listoffiles = dol_dir_list($conf->reception->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
815 foreach ($listoffiles as $fileentry) {
816 $dirsource = $fileentry['name'];
817 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
818 $dirsource = $fileentry['path'].'/'.$dirsource;
819 $dirdest = $fileentry['path'].'/'.$dirdest;
820 @rename($dirsource, $dirdest);
821 }
822 }
823 }
824 }
825 }
826
827 // Set new ref and current status
828 if (!$error) {
829 $this->ref = $numref;
830 $this->statut = self::STATUS_VALIDATED;
831 $this->status = self::STATUS_VALIDATED;
832 }
833
834 if (!$error) {
835 $this->db->commit();
836 return 1;
837 } else {
838 foreach ($this->errors as $errmsg) {
839 dol_syslog(get_class($this)."::valid ".$errmsg, LOG_ERR);
840 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
841 }
842 $this->db->rollback();
843 return -1 * $error;
844 }
845 }
846
852 public function getStatusDispatch()
853 {
854 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
855 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
856
858
859 if (!empty($this->origin) && $this->origin_id > 0 && ($this->origin == 'order_supplier' || $this->origin == 'commandeFournisseur')) {
860 if (empty($this->origin_object)) {
861 $this->fetch_origin();
862 if ($this->origin_object instanceof CommonObject && empty($this->origin_object->lines)) {
863 $res = $this->origin_object->fetch_lines();
864 $this->commandeFournisseur = null; // deprecated
865 if ($res < 0) {
866 return $res;
867 }
868 } elseif ($this->origin_object instanceof CommandeFournisseur && empty($this->origin_object->lines)) {
869 $res = $this->origin_object->fetch_lines();
870 $this->commandeFournisseur = $this->origin_object; // deprecated
871 if ($res < 0) {
872 return $res;
873 }
874 }
875 }
876
877 $qty_received = array();
878 $qty_wished = array();
879
880 $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
881 $filter = array('t.fk_element' => $this->origin_id);
882 if (getDolGlobalInt('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
883 $filter['t.status'] = 1; // Restrict to lines with status validated
884 }
885
886 $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
887 if ($ret < 0) {
888 $this->setErrorsFromObject($supplierorderdispatch);
889 return $ret;
890 } else {
891 // build array with quantity received by product in all supplier orders (origin)
892 foreach ($supplierorderdispatch->lines as $dispatch_line) {
893 if (array_key_exists($dispatch_line->fk_product, $qty_received)) {
894 $qty_received[$dispatch_line->fk_product] += $dispatch_line->qty;
895 } else {
896 $qty_received[$dispatch_line->fk_product] = $dispatch_line->qty;
897 }
898 }
899
900 // qty wished in origin (purchase order, ...)
901 foreach ($this->origin_object->lines as $origin_line) {
902 // exclude lines not qualified for reception
903 if ((!getDolGlobalInt('STOCK_SUPPORTS_SERVICES') && $origin_line->product_type > 0) || $origin_line->product_type > 1) {
904 continue;
905 }
906 if (array_key_exists($origin_line->fk_product, $qty_wished)) {
907 $qty_wished[$origin_line->fk_product] += $origin_line->qty;
908 } else {
909 $qty_wished[$origin_line->fk_product] = $origin_line->qty;
910 }
911 }
912
913 // compare array
914 $diff_array = array_diff_assoc($qty_received, $qty_wished); // Warning: $diff_array is done only on common keys.
915 $keys_in_wished_not_in_received = array_diff(array_keys($qty_wished), array_keys($qty_received));
916 $keys_in_received_not_in_wished = array_diff(array_keys($qty_received), array_keys($qty_wished));
917
918 if (count($diff_array) == 0 && count($keys_in_wished_not_in_received) == 0 && count($keys_in_received_not_in_wished) == 0) { // no diff => mean everything is received
920 } elseif (getDolGlobalInt('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
921 // set totally received if more products received than ordered
922 $close = 0;
923
924 if (count($diff_array) > 0) {
925 // there are some difference between the two arrays
926 // scan the array of results
927 foreach ($diff_array as $key => $value) {
928 // if the quantity delivered is greater or equal to ordered quantity @phan-suppress-next-line PhanTypeInvalidDimOffset
929 if ($qty_received[$key] >= $qty_wished[$key]) {
930 $close++;
931 }
932 }
933 }
934
935 if ($close == count($diff_array)) {
936 // all the products are received equal or more than the ordered quantity
938 }
939 }
940 }
941 }
942
943 return $status;
944 }
945
962 public function addline($entrepot_id, $id, $qty, $array_options = [], $comment = '', $eatby = null, $sellby = null, $batch = '', $cost_price = 0)
963 {
964 global $conf, $langs, $user;
965
966 $num = count($this->lines);
967 $line = new CommandeFournisseurDispatch($this->db);
968
969 $line->fk_entrepot = $entrepot_id;
970 $line->fk_commandefourndet = $id;
971 $line->qty = $qty;
972
973 $supplierorderline = new CommandeFournisseurLigne($this->db);
974 $result = $supplierorderline->fetch($id);
975 if ($result <= 0) {
976 $this->setErrorsFromObject($supplierorderline);
977 return -1;
978 }
979
980 $fk_product = 0;
981 if (isModEnabled('stock') && !empty($supplierorderline->fk_product)) {
982 $fk_product = $supplierorderline->fk_product;
983
984 if (!($entrepot_id > 0) && !getDolGlobalInt('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_RECEPTIONS')) {
985 $langs->load("errors");
986 $this->error = $langs->trans("ErrorWarehouseRequiredIntoReceptionLine");
987 return -1;
988 }
989 }
990
991 // Check batch is set
992 $product = new Product($this->db);
993 $product->fetch($fk_product);
994 if (isModEnabled('productbatch')) {
995 $langs->load("errors");
996 if (!empty($product->status_batch) && empty($batch)) {
997 $this->error = $langs->trans('ErrorProductNeedBatchNumber', $product->ref);
998 return -1;
999 } elseif (empty($product->status_batch) && !empty($batch)) {
1000 $this->error = $langs->trans('ErrorProductDoesNotNeedBatchNumber', $product->ref);
1001 return -1;
1002 }
1003
1004 // check sell-by / eat-by date is mandatory
1005 $errorMsgArr = Productlot::checkSellOrEatByMandatoryFromProductAndDates($product, $sellby, $eatby);
1006 if (!empty($errorMsgArr)) {
1007 $errorMessage = '<b>' . $product->ref . '</b> : ';
1008 $errorMessage .= '<ul>';
1009 foreach ($errorMsgArr as $errorMsg) {
1010 $errorMessage .= '<li>' . $errorMsg . '</li>';
1011 }
1012 $errorMessage .= '</ul>';
1013 $this->error = $errorMessage;
1014 return -1;
1015 }
1016 }
1017 unset($product);
1018
1019 // extrafields
1020 $line->array_options = $supplierorderline->array_options;
1021 if (!getDolGlobalInt('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) {
1022 foreach ($array_options as $key => $value) {
1023 $line->array_options[$key] = $value;
1024 }
1025 }
1026
1027 $line->fk_product = $fk_product;
1028 $line->fk_commande = $supplierorderline->fk_commande;
1029 $line->fk_user = $user->id;
1030 $line->comment = $comment;
1031 $line->batch = $batch;
1032 $line->eatby = $eatby;
1033 $line->sellby = $sellby;
1034 $line->status = 1;
1035 $line->cost_price = $cost_price;
1036 $line->fk_reception = $this->id;
1037
1038 $this->lines[$num] = $line;
1039
1040 return $num;
1041 }
1042
1055 public function addlinefree($qty, $element_type, $fk_product, $fk_unit, $rang, $description, $array_options = [])
1056 {
1057 global $mysoc, $langs, $user;
1058
1059 if ($this->status == self::STATUS_DRAFT) {
1060 if (empty($rang)) {
1061 $rang = 0;
1062 }
1063
1064 $qty = (float) price2num($qty);
1065
1066 $this->db->begin();
1067
1068 // Rank to use
1069 $ranktouse = $this->rang;
1070 if ($ranktouse == -1) {
1071 $rangmax = $this->line_max($this->fk_reception);
1072 $ranktouse = $rangmax + 1;
1073 }
1074
1075 // Insert line
1076 $this->line = new ReceptionLineBatch($this->db);
1077 $this->line->fk_reception = $this->id;
1078 $this->line->element_type = $element_type;
1079 $this->line->fk_product = $fk_product;
1080 $this->line->description = $description;
1081
1082 $this->line->qty = (float) $qty;
1083 $this->line->fk_unit = $fk_unit;
1084 $this->line->rang = $ranktouse;
1085
1086 if (is_array($array_options) && count($array_options) > 0) {
1087 $this->line->array_options = $array_options;
1088 }
1089
1090 $result = $this->line->insert($user);
1091 if ($result > 0) {
1092 if (!isset($this->context['createfromclone'])) {
1093 if ($this->fk_reception) {
1094 $this->line_order(true, 'DESC');
1095 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
1096 $linecount = count($this->lines);
1097 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
1098 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
1099 }
1100 }
1101 $this->lines[] = $this->line;
1102 }
1103
1104 $this->db->commit();
1105 return $this->line->id;
1106 } else {
1107 $this->error = $this->line->error;
1108 dol_syslog(get_class($this)."::addlinefree error=".$this->error, LOG_ERR);
1109 $this->db->rollback();
1110 return -2;
1111 }
1112 } else {
1113 dol_syslog(get_class($this)."::addlinefree status of reception must be Draft to allow use of ->addlinefree()", LOG_ERR);
1114 return -3;
1115 }
1116 }
1117
1132 public function updatelinefree($rowid, $qty, $element_type, $fk_product, $fk_unit, $rang, $description, $notrigger, $array_options = array())
1133 {
1134 global $mysoc, $langs, $user;
1135
1136 if ($this->statut == self::STATUS_DRAFT) {
1137 $this->db->begin();
1138
1139 if (empty($qty)) {
1140 $qty = 0;
1141 }
1142 if (empty($rang)) {
1143 $rang = 0;
1144 }
1145
1146 $qty = (float) $qty;
1147 $description = trim($description);
1148
1149 // Fetch current line from the database and then clone the object and set it in $oldline property
1150 $line = new ReceptionLineBatch($this->db);
1151
1152 $line->fetch($rowid);
1153 $line->fetch_optionals();
1154
1155 if (!empty($line->fk_product)) {
1156 $product = new Product($this->db);
1157 $result = $product->fetch($line->fk_product);
1158 $product_type = $product->type;
1159 }
1160
1161 $staticline = clone $line;
1162
1163 $line->oldline = $staticline;
1164 $this->line = $line;
1165 $this->line->context = $this->context;
1166 $this->line->rang = $rang;
1167 $this->line->fk_reception = $this->id;
1168 $this->line->element_type = $element_type;
1169 $this->line->fk_product = $line->fk_product;
1170 $this->line->qty = $qty;
1171 $this->line->fk_unit = $fk_unit;
1172 $this->line->description = $description;
1173
1174 if (is_array($array_options) && count($array_options) > 0) {
1175 // We replace values in this->line->array_options only for entries defined into $array_options
1176 foreach ($array_options as $key => $value) {
1177 $this->line->array_options[$key] = $array_options[$key];
1178 }
1179 }
1180
1181
1182 $result = $this->line->update($user, $notrigger);
1183 if ($result > 0) {
1184 // Reorder if child line
1185
1186
1187 $this->db->commit();
1188 return $result;
1189 } else {
1190 $this->error = $this->line->error;
1191
1192 $this->db->rollback();
1193 return -1;
1194 }
1195 } else {
1196 $this->error = get_class($this)."::updatelinefree reception status makes operation forbidden";
1197 $this->errors = array('ReceptionStatusMakeOperationForbidden');
1198 return -2;
1199 }
1200 }
1201
1202 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1208 public function fetch_lines_free()
1209 {
1210 // phpcs:enable
1211 global $mysoc;
1212
1213 $this->lines = array();
1214
1215 $sql = 'SELECT rc.rowid, rc.fk_reception, rc.fk_entrepot, rc.fk_product, rc.fk_unit, rc.description, rc.fk_elementdet, rc.fk_element, rc.element_type, rc.qty, rc.rang';
1216 $sql .= ' FROM '.MAIN_DB_PREFIX.'receptiondet_batch as rc';
1217 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (p.rowid = rc.fk_product)';
1218 $sql .= ' WHERE rc.fk_reception = '.((int) $this->id);
1219
1220 $sql .= ' ORDER BY rc.rang, rc.rowid';
1221
1222 dol_syslog(get_class($this)."::fetch_lines_free", LOG_DEBUG);
1223 $result = $this->db->query($sql);
1224 if ($result) {
1225 $num = $this->db->num_rows($result);
1226
1227 $i = 0;
1228 while ($i < $num) {
1229 $objp = $this->db->fetch_object($result);
1230
1231 $line = new ReceptionLineBatch($this->db);
1232
1233 $line->rowid = $objp->rowid;
1234 $line->id = $objp->rowid;
1235 $line->fk_reception = $this->id;
1236
1237 $line->description = $objp->description;
1238 $line->qty = $objp->qty;
1239 $line->fk_entrepot = $objp->fk_entrepot;
1240 $line->fk_product = $objp->fk_product;
1241
1242 $line->rang = $objp->rang;
1243
1244
1245 $line->fk_element = $objp->fk_element;
1246 $line->fk_unit = $objp->fk_unit;
1247 $line->fk_elementdet = $objp->fk_elementdet;
1248 $line->fk_element_type = $objp->element_type;
1249 $line->fetch_optionals();
1250
1251
1252
1253 $this->lines[$i] = $line;
1254
1255 $i++;
1256 }
1257
1258 $this->db->free($result);
1259
1260 return 1;
1261 } else {
1262 $this->error = $this->db->error();
1263 return -3;
1264 }
1265 }
1266
1272 public function getLinesArray()
1273 {
1274 return $this->fetch_lines_free();
1275 }
1276
1284 public function update($user = null, $notrigger = 0)
1285 {
1286 global $conf;
1287 $error = 0;
1288
1289 // Clean parameters
1290
1291 if (isset($this->ref)) {
1292 $this->ref = trim($this->ref);
1293 }
1294 if (isset($this->entity)) {
1295 $this->entity = (int) $this->entity;
1296 }
1297 if (isset($this->ref_supplier)) {
1298 $this->ref_supplier = trim($this->ref_supplier);
1299 }
1300 if (isset($this->socid)) {
1301 $this->socid = (int) trim((string) $this->socid);
1302 }
1303 if (isset($this->fk_user_author)) {
1304 $this->fk_user_author = (int) $this->fk_user_author;
1305 }
1306 if (isset($this->fk_user_valid)) {
1307 $this->fk_user_valid = (int) $this->fk_user_valid;
1308 }
1309 if (isset($this->shipping_method_id)) {
1310 $this->shipping_method_id = (int) $this->shipping_method_id;
1311 }
1312 if (isset($this->tracking_number)) {
1313 $this->tracking_number = trim($this->tracking_number);
1314 }
1315 if (isset($this->statut)) {
1316 $this->statut = (int) $this->statut;
1317 }
1318 if (isset($this->trueDepth)) {
1319 $this->trueDepth = price2num($this->trueDepth);
1320 }
1321 if (isset($this->trueWidth)) {
1322 $this->trueWidth = price2num($this->trueWidth);
1323 }
1324 if (isset($this->trueHeight)) {
1325 $this->trueHeight = price2num($this->trueHeight);
1326 }
1327 $this->size_units = (int) $this->size_units;
1328
1329 if (isset($this->trueWeight)) {
1330 $this->weight = price2num($this->trueWeight);
1331 }
1332 $this->weight_units = (int) $this->weight_units;
1333
1334 if (isset($this->note_private)) {
1335 $this->note_private = trim($this->note_private);
1336 }
1337 if (isset($this->note_public)) {
1338 $this->note_public = trim($this->note_public);
1339 }
1340 if (isset($this->model_pdf)) {
1341 $this->model_pdf = trim($this->model_pdf);
1342 }
1343
1344
1345 // Check parameters
1346 // Put here code to add control on parameters values
1347
1348 // Update request
1349 $sql = "UPDATE ".MAIN_DB_PREFIX."reception SET";
1350
1351 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1352 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1353 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1354 $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1355 $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1356 $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1357 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1358 $sql .= " date_reception=".(dol_strlen($this->date_reception) != 0 ? "'".$this->db->idate($this->date_reception)."'" : 'null').",";
1359 $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1360 $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1361 $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1362 $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1363 $sql .= " height=".(($this->trueHeight != '') ? (float) $this->trueHeight : "null").",";
1364 $sql .= " width=".(($this->trueWidth != '') ? (float) $this->trueWidth : "null").",";
1365 $sql .= " size_units=".((int) $this->size_units).",";
1366 $sql .= " size=".(($this->trueDepth != '') ? (float) $this->trueDepth : "null").",";
1367 $sql .= " weight_units=".((int) $this->weight_units).",";
1368 $sql .= " weight=".(($this->trueWeight != '') ? (float) $this->trueWeight : "null").",";
1369 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1370 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1371 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1372 $sql .= " fk_projet=".((isset($this->fk_project) && $this->fk_project > 0) ? ((int) $this->fk_project) : "null").",";
1373 $sql .= " entity = ".((int) $conf->entity);
1374 $sql .= " WHERE rowid=".((int) $this->id);
1375
1376 $this->db->begin();
1377
1378 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1379 $resql = $this->db->query($sql);
1380 if (!$resql) {
1381 $error++;
1382 $this->errors[] = "Error ".$this->db->lasterror();
1383 }
1384
1385 if (!$error) {
1386 if (!$notrigger) {
1387 // Call trigger
1388 $result = $this->call_trigger('RECEPTION_MODIFY', $user);
1389 if ($result < 0) {
1390 $error++;
1391 }
1392 // End call triggers
1393 }
1394 }
1395
1396 // Commit or rollback
1397 if ($error) {
1398 foreach ($this->errors as $errmsg) {
1399 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1400 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1401 }
1402 $this->db->rollback();
1403 return -1 * $error;
1404 } else {
1405 $this->db->commit();
1406 return 1;
1407 }
1408 }
1409
1416 public function delete(User $user)
1417 {
1418 global $conf, $langs, $user;
1419 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1420
1421 $error = 0;
1422 $this->error = '';
1423
1424
1425 $this->db->begin();
1426
1427 // Stock control
1428 if (isModEnabled('stock') && ((getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION') && $this->status > Reception::STATUS_DRAFT)
1429 || (getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE') && $this->status == Reception::STATUS_CLOSED))
1430 ) {
1431 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1432
1433 $langs->load("agenda");
1434
1435 // Loop on each product line to add a stock movement
1436 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.eatby, ed.sellby, ed.batch, ed.rowid as receptiondet_batch_id";
1437 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1438 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1439 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1440 $sql .= " AND cd.rowid = ed.fk_elementdet";
1441
1442 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1443 $resql = $this->db->query($sql);
1444 if ($resql) {
1445 $cpt = $this->db->num_rows($resql);
1446 for ($i = 0; $i < $cpt; $i++) {
1447 dol_syslog(get_class($this)."::delete movement index ".$i);
1448 $obj = $this->db->fetch_object($resql);
1449
1450 $mouvS = new MouvementStock($this->db);
1451 // we do not log origin because it will be deleted
1452 $mouvS->origin = null;
1453
1454 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ReceptionDeletedInDolibarr", $this->ref), '', $obj->eatby ? $this->db->jdate($obj->eatby) : null, $obj->sellby ? $this->db->jdate($obj->sellby) : null, $obj->batch); // Price is set to 0, because we don't want to see WAP changed
1455 if ($result < 0) {
1456 $error++;
1457 $this->error = $mouvS->error;
1458 $this->errors = $mouvS->errors;
1459 }
1460 }
1461 } else {
1462 $error++;
1463 $this->errors[] = "Error ".$this->db->lasterror();
1464 }
1465 }
1466
1467 if (!$error) {
1468 $main = MAIN_DB_PREFIX.'receptiondet_batch';
1469 $ef = $main."_extrafields";
1470
1471 $sqlef = "DELETE FROM ".$ef." WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_reception = ".((int) $this->id).")";
1472
1473 $sql = "DELETE FROM ".MAIN_DB_PREFIX."receptiondet_batch";
1474 $sql .= " WHERE fk_reception = ".((int) $this->id);
1475
1476 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1477 // Delete linked object
1478 $res = $this->deleteObjectLinked();
1479 if ($res < 0) {
1480 $error++;
1481 }
1482
1483 if (!$error) {
1484 $sql = "DELETE FROM ".MAIN_DB_PREFIX."reception";
1485 $sql .= " WHERE rowid = ".((int) $this->id);
1486
1487 if ($this->db->query($sql)) {
1488 // Call trigger
1489 $result = $this->call_trigger('RECEPTION_DELETE', $user);
1490 if ($result < 0) {
1491 $error++;
1492 }
1493 // End call triggers
1494
1495 if (!empty($this->origin) && $this->origin_id > 0) {
1496 $this->fetch_origin();
1498 '@phan-var-force CommandeFournisseur $origin_object';
1500 if ($origin_object->status == 4) { // If order source of reception is "partially received"
1501 // Check if there is no more reception. If not, we can move back status of order to "validated" instead of "reception in progress"
1502 $origin_object->loadReceptions();
1503 //var_dump($this->$origin->receptions);exit;
1504 if (count($origin_object->receptions) <= 0) {
1505 $origin_object->setStatut(3); // ordered
1506 }
1507 }
1508 }
1509
1510 if (!$error) {
1511 $this->db->commit();
1512
1513 // We delete PDFs
1514 $ref = dol_sanitizeFileName($this->ref);
1515 if (!empty($conf->reception->dir_output)) {
1516 $dir = $conf->reception->dir_output.'/'.$ref;
1517 $file = $dir.'/'.$ref.'.pdf';
1518 if (file_exists($file)) {
1519 if (!dol_delete_file($file)) {
1520 return 0;
1521 }
1522 }
1523 if (file_exists($dir)) {
1524 if (!dol_delete_dir_recursive($dir)) {
1525 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1526 return 0;
1527 }
1528 }
1529 }
1530
1531 return 1;
1532 } else {
1533 $this->db->rollback();
1534 return -1;
1535 }
1536 } else {
1537 $this->error = $this->db->lasterror()." - sql=$sql";
1538 $this->db->rollback();
1539 return -3;
1540 }
1541 } else {
1542 $this->error = $this->db->lasterror()." - sql=$sql";
1543 $this->db->rollback();
1544 return -2;
1545 }
1546 } else {
1547 $this->error = $this->db->lasterror()." - sql=$sql";
1548 $this->db->rollback();
1549 return -1;
1550 }
1551 } else {
1552 $this->db->rollback();
1553 return -1;
1554 }
1555 }
1556
1557 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1563 public function fetch_lines()
1564 {
1565 // phpcs:enable
1566 $this->lines = array();
1567
1568 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
1569
1570 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."receptiondet_batch";
1571 $sql .= " WHERE fk_reception = ".((int) $this->id);
1572
1573 $resql = $this->db->query($sql);
1574
1575 if (!empty($resql)) {
1576 while ($obj = $this->db->fetch_object($resql)) {
1577 $line = new CommandeFournisseurDispatch($this->db);
1578
1579 $line->fetch($obj->rowid);
1580
1581 // TODO Remove or keep this ?
1582 $line->fetch_product();
1583
1584 $sql_commfourndet = 'SELECT qty, ref, label, description, tva_tx, vat_src_code, subprice, multicurrency_subprice, remise_percent, total_ht, total_ttc, total_tva';
1585 $sql_commfourndet .= ' FROM '.MAIN_DB_PREFIX.'commande_fournisseurdet';
1586 $sql_commfourndet .= ' WHERE rowid = '.((int) $line->fk_commandefourndet);
1587 $sql_commfourndet .= ' ORDER BY rang';
1588
1589 $resql_commfourndet = $this->db->query($sql_commfourndet);
1590 if (!empty($resql_commfourndet)) {
1591 $obj = $this->db->fetch_object($resql_commfourndet);
1592 $line->qty_asked = $obj->qty;
1593 $line->description = $obj->description;
1594 $line->desc = $obj->description;
1595 $line->tva_tx = $obj->tva_tx;
1596 $line->vat_src_code = $obj->vat_src_code;
1597 $line->subprice = $obj->subprice;
1598 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1599 $line->remise_percent = $obj->remise_percent;
1600 $line->label = !empty($obj->label) ? $obj->label : (is_object($line->product) ? $line->product->label : '');
1601 $line->ref_supplier = $obj->ref;
1602 $line->total_ht = $obj->total_ht;
1603 $line->total_ttc = $obj->total_ttc;
1604 $line->total_tva = $obj->total_tva;
1605 } else {
1606 $line->qty_asked = 0;
1607 $line->description = '';
1608 $line->desc = '';
1609 $line->label = $obj->label;
1610 }
1611
1612 $pu_ht = ($line->subprice * $line->qty) * (100 - $line->remise_percent) / 100;
1613 $tva = $pu_ht * $line->tva_tx / 100;
1614 $this->total_ht += $pu_ht;
1615 $this->total_tva += $pu_ht * $line->tva_tx / 100;
1616
1617 $this->total_ttc += $pu_ht + $tva;
1618
1619 if (isModEnabled('productbatch') && !empty($line->batch)) {
1620 $detail_batch = new stdClass();
1621 $detail_batch->eatby = $line->eatby;
1622 $detail_batch->sellby = $line->sellby;
1623 $detail_batch->batch = $line->batch;
1624 $detail_batch->qty = $line->qty;
1625
1626 $line->detail_batch[] = $detail_batch;
1627 }
1628
1629 $this->lines[] = $line;
1630 }
1631
1632 return 1;
1633 } else {
1634 return -1;
1635 }
1636 }
1637
1648 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0)
1649 {
1650 global $langs, $hookmanager;
1651
1652 $result = '';
1653 $label = img_picto('', $this->picto).' <u>'.$langs->trans("Reception").'</u>';
1654 $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1655 $label .= '<br><b>'.$langs->trans('RefSupplier').':</b> '.($this->ref_supplier ? $this->ref_supplier : '');
1656
1657 $url = DOL_URL_ROOT.'/reception/card.php?id='.$this->id;
1658
1659 if ($short) {
1660 return $url;
1661 }
1662
1663 $linkclose = '';
1664 if (empty($notooltip)) {
1665 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1666 $label = $langs->trans("Reception");
1667 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1668 }
1669 $linkclose .= ' title="'.dolPrintHTMLForAttribute($label).'"';
1670 $linkclose .= ' class="classfortooltip"';
1671 }
1672
1673 $linkstart = '<a href="'.$url.'"';
1674 $linkstart .= $linkclose.'>';
1675 $linkend = '</a>';
1676
1677 $result .= $linkstart;
1678 if ($withpicto) {
1679 $result .= img_object(($notooltip ? '' : $label), $this->picto, '', 0, 0, $notooltip ? 0 : 1);
1680 }
1681 if ($withpicto != 2) {
1682 $result .= $this->ref;
1683 }
1684
1685 $result .= $linkend;
1686
1687 global $action;
1688 $hookmanager->initHooks(array($this->element . 'dao'));
1689 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1690 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1691 if ($reshook > 0) {
1692 $result = $hookmanager->resPrint;
1693 } else {
1694 $result .= $hookmanager->resPrint;
1695 }
1696 return $result;
1697 }
1698
1705 public function getLibStatut($mode = 0)
1706 {
1707 return $this->LibStatut($this->statut, $mode);
1708 }
1709
1710 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1718 public function LibStatut($status, $mode)
1719 {
1720 // phpcs:enable
1721 global $langs;
1722
1723 // List of long language codes for status
1724 $this->labelStatus[-1] = 'StatusReceptionCanceled';
1725 $this->labelStatus[0] = 'StatusReceptionDraft';
1726 // product to receive if stock increase is on close or already received if stock increase is on validation
1727 $this->labelStatus[1] = 'StatusReceptionValidated';
1728 if (getDolGlobalInt("STOCK_CALCULATE_ON_RECEPTION")) {
1729 $this->labelStatus[1] = 'StatusReceptionValidatedReceived';
1730 }
1731 if (getDolGlobalInt("STOCK_CALCULATE_ON_RECEPTION_CLOSE")) {
1732 $this->labelStatus[1] = 'StatusReceptionValidatedToReceive';
1733 }
1734 $this->labelStatus[2] = 'StatusReceptionProcessed';
1735
1736 // List of short language codes for status
1737 $this->labelStatusShort[-1] = 'StatusReceptionCanceledShort';
1738 $this->labelStatusShort[0] = 'StatusReceptionDraftShort';
1739 $this->labelStatusShort[1] = 'StatusReceptionValidatedShort';
1740 $this->labelStatusShort[2] = 'StatusReceptionProcessedShort';
1741
1742 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
1743 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
1744
1745 $statusType = 'status'.$status;
1746 if ($status == self::STATUS_VALIDATED) {
1747 $statusType = 'status4';
1748 }
1749 if ($status == self::STATUS_CLOSED) {
1750 $statusType = 'status6';
1751 }
1752
1753 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
1754 }
1755
1763 public function getKanbanView($option = '', $arraydata = null)
1764 {
1765 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1766
1767 $return = '<div class="box-flex-item box-flex-grow-zero">';
1768 $return .= '<div class="info-box info-box-sm">';
1769 $return .= '<div class="info-box-icon bg-infobox-action">';
1770 $return .= img_picto('', 'order');
1771 $return .= '</div>';
1772 $return .= '<div class="info-box-content">';
1773 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
1774 if ($selected >= 0) {
1775 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1776 }
1777 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
1778 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
1779 }
1780 /*if (property_exists($this, 'total_ht')) {
1781 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, getDolCurrency()).' '.$langs->trans('HT').'</div>';
1782 }*/
1783 if (method_exists($this, 'getLibStatut')) {
1784 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1785 }
1786 $return .= '</div>';
1787 $return .= '</div>';
1788 $return .= '</div>';
1789
1790 return $return;
1791 }
1792
1800 public function initAsSpecimen()
1801 {
1802 global $langs;
1803
1804 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
1805 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
1806 $now = dol_now();
1807
1808 dol_syslog(get_class($this)."::initAsSpecimen");
1809
1810 $order = new CommandeFournisseur($this->db);
1811 $order->initAsSpecimen();
1812
1813 // Initialise parameters
1814 $this->id = 0;
1815 $this->ref = 'SPECIMEN';
1816 $this->specimen = 1;
1817 $this->statut = 1;
1818 $this->status = 1;
1819 $this->date = $now;
1820 $this->date_creation = $now;
1821 $this->date_valid = $now;
1822 $this->date_delivery = $now;
1823 $this->date_reception = $now + 24 * 3600;
1824 $this->entrepot_id = 0;
1825 $this->socid = 1;
1826 $this->origin_id = 1;
1827 $this->origin_type = 'supplier_order';
1828 $this->origin_object = $order;
1829 $this->note_private = 'Private note';
1830 $this->note_public = 'Public note';
1831 $this->tracking_number = 'TRACKID-ABC123';
1832 $this->fk_incoterms = 1;
1833 $nbp = min(1000, GETPOSTINT('nblines') ? GETPOSTINT('nblines') : 5); // We can force the nb of lines to test from command line (but not more than 1000)
1834 $xnbp = 0;
1835 while ($xnbp < $nbp) {
1836 $line = new CommandeFournisseurDispatch($this->db);
1837 $line->desc = $langs->trans("Description")." ".$xnbp;
1838 $line->libelle = $langs->trans("Description")." ".$xnbp; // deprecated
1839 $line->label = $langs->trans("Description")." ".$xnbp;
1840 $line->qty = 10;
1841
1842 $line->fk_product = $this->origin_object->lines[$xnbp]->fk_product;
1843
1844 $this->lines[] = $line;
1845 $xnbp++;
1846 }
1847
1848 return 1;
1849 }
1850
1858 public function setDeliveryDate($user, $delivery_date)
1859 {
1860 // phpcs:enable
1861 if ($user->hasRight('reception', 'creer')) {
1862 $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
1863 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
1864 $sql .= " WHERE rowid = ".((int) $this->id);
1865
1866 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
1867 $resql = $this->db->query($sql);
1868 if ($resql) {
1869 $this->date_delivery = $delivery_date;
1870 return 1;
1871 } else {
1872 $this->error = $this->db->error();
1873 return -1;
1874 }
1875 } else {
1876 return -2;
1877 }
1878 }
1879
1887 public function setReceptionDate($user, $reception_date)
1888 {
1889 if ($user->hasRight('reception', 'creer')) {
1890 $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
1891 $sql .= " SET date_reception = ".($reception_date ? "'".$this->db->idate($reception_date)."'" : 'null');
1892 $sql .= " WHERE rowid = ".((int) $this->id);
1893
1894 dol_syslog(get_class($this)."::setReceptionDate", LOG_DEBUG);
1895 $resql = $this->db->query($sql);
1896 if ($resql) {
1897 $this->date_reception = $reception_date;
1898 return 1;
1899 } else {
1900 $this->error = $this->db->error();
1901 return -1;
1902 }
1903 } else {
1904 return -2;
1905 }
1906 }
1907
1908 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1914 public function fetch_delivery_methods()
1915 {
1916 // phpcs:enable
1917 global $langs;
1918 $this->meths = array();
1919
1920 $sql = "SELECT em.rowid, em.code, em.libelle";
1921 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1922 $sql .= " WHERE em.active = 1";
1923 $sql .= " ORDER BY em.libelle ASC";
1924
1925 $resql = $this->db->query($sql);
1926 if ($resql) {
1927 while ($obj = $this->db->fetch_object($resql)) {
1928 $label = $langs->trans('ReceptionMethod'.$obj->code);
1929 $this->meths[$obj->rowid] = ($label != 'ReceptionMethod'.$obj->code ? $label : $obj->libelle);
1930 }
1931 }
1932 }
1933
1934 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1941 public function list_delivery_methods($id = 0)
1942 {
1943 // phpcs:enable
1944 global $langs;
1945
1946 $this->listmeths = array();
1947 $i = 0;
1948
1949 $sql = "SELECT em.rowid, em.code, em.libelle, em.description, em.tracking, em.active";
1950 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1951 if (!empty($id)) {
1952 $sql .= " WHERE em.rowid = ".((int) $id);
1953 }
1954
1955 $resql = $this->db->query($sql);
1956 if ($resql) {
1957 while ($obj = $this->db->fetch_object($resql)) {
1958 $this->listmeths[$i]['rowid'] = $obj->rowid;
1959 $this->listmeths[$i]['code'] = $obj->code;
1960 $label = $langs->trans('ReceptionMethod'.$obj->code);
1961 $this->listmeths[$i]['libelle'] = ($label != 'ReceptionMethod'.$obj->code ? $label : $obj->libelle);
1962 $this->listmeths[$i]['description'] = $obj->description;
1963 $this->listmeths[$i]['tracking'] = $obj->tracking;
1964 $this->listmeths[$i]['active'] = $obj->active;
1965 $i++;
1966 }
1967 }
1968 }
1969
1976 public function getUrlTrackingStatus($value = '')
1977 {
1978 if (!empty($this->shipping_method_id)) {
1979 $sql = "SELECT em.code, em.tracking";
1980 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1981 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
1982
1983 $resql = $this->db->query($sql);
1984 if ($resql) {
1985 if ($obj = $this->db->fetch_object($resql)) {
1986 $tracking = $obj->tracking;
1987 }
1988 }
1989 }
1990
1991 if (!empty($tracking) && !empty($value)) {
1992 $url = str_replace('{TRACKID}', $value, $tracking);
1993 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
1994 } else {
1995 $this->tracking_url = $value;
1996 }
1997 }
1998
2004 public function setClosed()
2005 {
2006 global $conf, $langs, $user;
2007
2008 $error = 0;
2009
2010 // Protection. This avoid to move stock later when we should not
2011 if ($this->statut == Reception::STATUS_CLOSED) {
2012 dol_syslog(get_class($this)."::setClosed already in closed status", LOG_WARNING);
2013 return 0;
2014 }
2015
2016 $this->db->begin();
2017
2018 $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET fk_statut = '.self::STATUS_CLOSED;
2019 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2020
2021 $resql = $this->db->query($sql);
2022 if ($resql) {
2023 // Set order billed if 100% of order is received (qty in reception lines match qty in order lines)
2024 if ($this->origin == 'order_supplier' && $this->origin_id > 0) {
2025 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
2026
2027 $order = new CommandeFournisseur($this->db);
2028 $order->fetch($this->origin_id);
2029
2030 $order->loadReceptions(self::STATUS_CLOSED); // Fill $order->receptions = array(orderlineid => qty)
2031
2032 $receptions_match_order = 1;
2033 foreach ($order->lines as $line) {
2034 $lineid = $line->id;
2035 $qty = $line->qty;
2036 if (($line->product_type == 0 || getDolGlobalInt('STOCK_SUPPORTS_SERVICES')) && $order->receptions[$lineid] < $qty) {
2037 $receptions_match_order = 0;
2038 $text = 'Qty for order line id '.$lineid.' is '.$qty.'. However in the receptions with status Reception::STATUS_CLOSED='.self::STATUS_CLOSED.' we have qty = '.$order->receptions[$lineid].', so we can t close order';
2039 dol_syslog($text);
2040 break;
2041 }
2042 }
2043 if ($receptions_match_order) {
2044 dol_syslog("Qty for the ".count($order->lines)." lines of order have same value for receptions with status Reception::STATUS_CLOSED=".self::STATUS_CLOSED.', so we close order');
2045 $order->Livraison($user, dol_now(), 'tot', 'Reception '.$this->ref);
2046 }
2047 }
2048
2049 $this->statut = self::STATUS_CLOSED;
2050 $this->status = self::STATUS_CLOSED;
2051
2052 // If stock increment is done on closing
2053 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
2054 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2055
2056 $langs->load("agenda");
2057
2058 // Loop on each product line to add a stock movement
2059 // TODO possibilite de receptionner a partir d'une propale ou autre origine ?
2060 $sql = "SELECT cd.fk_product, cd.subprice,";
2061 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2062 $sql .= " ed.eatby, ed.sellby, ed.batch,";
2063 $sql .= " ed.fk_elementdet, ed.cost_price";
2064 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
2065 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
2066 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
2067 $sql .= " AND cd.rowid = ed.fk_elementdet";
2068
2069 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2070 $resql = $this->db->query($sql);
2071
2072 if ($resql) {
2073 $cpt = $this->db->num_rows($resql);
2074 for ($i = 0; $i < $cpt; $i++) {
2075 $obj = $this->db->fetch_object($resql);
2076
2077 $qty = $obj->qty;
2078
2079 if ($qty <= 0) {
2080 continue;
2081 }
2082
2083 dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid);
2084
2085 $mouvS = new MouvementStock($this->db);
2086 $mouvS->origin = &$this;
2087 $mouvS->setOrigin($this->element, $this->id, $obj->fk_elementdet, $obj->rowid);
2088
2089 if (empty($obj->batch)) {
2090 // line without batch detail
2091
2092 // 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
2093 $inventorycode = '';
2094 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionClassifyClosedInDolibarr", $this->ref), '', '', '', '', 0, $inventorycode);
2095 if ($result < 0) {
2096 $this->error = $mouvS->error;
2097 $this->errors = $mouvS->errors;
2098 $error++;
2099 break;
2100 }
2101 } else {
2102 // line with batch detail
2103
2104 // 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
2105 $inventorycode = '';
2106 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionClassifyClosedInDolibarr", $this->ref), $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, '', 0, $inventorycode);
2107
2108 if ($result < 0) {
2109 $this->error = $mouvS->error;
2110 $this->errors = $mouvS->errors;
2111 $error++;
2112 break;
2113 }
2114 }
2115 }
2116 } else {
2117 $this->error = $this->db->lasterror();
2118 $error++;
2119 }
2120 }
2121
2122 // Call trigger
2123 if (!$error) {
2124 $result = $this->call_trigger('RECEPTION_CLOSED', $user);
2125 if ($result < 0) {
2126 $error++;
2127 }
2128 }
2129 } else {
2130 dol_print_error($this->db);
2131 $error++;
2132 }
2133
2134 if (!$error) {
2135 $this->db->commit();
2136 return 1;
2137 } else {
2138 $this->statut = self::STATUS_VALIDATED;
2139 $this->status = self::STATUS_VALIDATED;
2140 $this->db->rollback();
2141 return -1;
2142 }
2143 }
2144
2150 public function setBilled()
2151 {
2152 global $user;
2153 $error = 0;
2154
2155 $this->db->begin();
2156
2157 if ($this->statut == Reception::STATUS_VALIDATED) {
2158 // do not close if already closed
2159 $this->setClosed();
2160 }
2161
2162 $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET billed=1';
2163 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2164
2165 $resql = $this->db->query($sql);
2166 if ($resql) {
2167 $this->billed = 1;
2168
2169 // Call trigger
2170 $result = $this->call_trigger('RECEPTION_BILLED', $user);
2171 if ($result < 0) {
2172 $this->billed = 0;
2173 $error++;
2174 }
2175 } else {
2176 $error++;
2177 $this->errors[] = $this->db->lasterror;
2178 }
2179
2180 if (empty($error)) {
2181 $this->db->commit();
2182 return 1;
2183 } else {
2184 $this->db->rollback();
2185 return -1;
2186 }
2187 }
2188
2194 public function reOpen()
2195 {
2196 global $langs, $user;
2197
2198 $error = 0;
2199
2200 $this->db->begin();
2201
2202 $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET fk_statut=1, billed=0';
2203 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
2204
2205 $resql = $this->db->query($sql);
2206 $rollbackStatus = $this->status;
2207 $rollbackBilled = $this->billed;
2208 if ($resql) {
2209 $this->statut = self::STATUS_VALIDATED;
2210 $this->status = self::STATUS_VALIDATED;
2211 $this->billed = 0;
2212
2213 // If stock increment is done on closing
2214 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
2215 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2216 $numref = (string) $this->ref;
2217 $langs->load("agenda");
2218
2219 // Loop on each product line to add a stock movement
2220 // TODO possibilite de receptionner a partir d'une propale ou autre origine
2221 $sql = "SELECT ed.fk_product, cd.subprice,";
2222 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2223 $sql .= " ed.eatby, ed.sellby, ed.batch,";
2224 $sql .= " ed.fk_elementdet, ed.cost_price";
2225 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
2226 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
2227 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
2228 $sql .= " AND cd.rowid = ed.fk_elementdet";
2229
2230 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2231 $resql = $this->db->query($sql);
2232 if ($resql) {
2233 $cpt = $this->db->num_rows($resql);
2234 for ($i = 0; $i < $cpt; $i++) {
2235 $obj = $this->db->fetch_object($resql);
2236
2237 $qty = $obj->qty;
2238
2239 if ($qty <= 0) {
2240 continue;
2241 }
2242 dol_syslog(get_class($this)."::reopen reception movement index ".$i." ed.rowid=".$obj->rowid);
2243
2244 //var_dump($this->lines[$i]);
2245 $mouvS = new MouvementStock($this->db);
2246 $mouvS->origin = &$this;
2247 $mouvS->setOrigin($this->element, $this->id, $obj->fk_elementdet, $obj->rowid);
2248
2249 if (empty($obj->batch)) {
2250 // line without batch detail
2251
2252 // 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
2253 $inventorycode = '';
2254 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionUnClassifyCloseddInDolibarr", $numref), '', '', '', '', 0, $inventorycode);
2255
2256 if ($result < 0) {
2257 $this->error = $mouvS->error;
2258 $this->errors = $mouvS->errors;
2259 $error++;
2260 break;
2261 }
2262 } else {
2263 // line with 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 $inventorycode = '';
2267 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionUnClassifyCloseddInDolibarr", $numref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock, $inventorycode);
2268 if ($result < 0) {
2269 $this->error = $mouvS->error;
2270 $this->errors = $mouvS->errors;
2271 $error++;
2272 break;
2273 }
2274 }
2275 }
2276 } else {
2277 $this->error = $this->db->lasterror();
2278 $error++;
2279 }
2280 }
2281
2282 if (!$error) {
2283 // Call trigger
2284 $result = $this->call_trigger('RECEPTION_REOPEN', $user);
2285 if ($result < 0) {
2286 $error++;
2287 }
2288 }
2289
2290 if (!$error && $this->origin == 'order_supplier') {
2291 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
2292
2293 $commande = new CommandeFournisseur($this->db);
2294 $commande->fetch($this->origin_id);
2295 $result = $commande->setStatus($user, 4);
2296 if ($result < 0) {
2297 $error++;
2298 $this->error = $commande->error;
2299 $this->errors = $commande->errors;
2300 }
2301 }
2302 } else {
2303 $error++;
2304 $this->errors[] = $this->db->lasterror();
2305 }
2306
2307 if (!$error) {
2308 $this->db->commit();
2309 return 1;
2310 } else {
2311 $this->statut = $this->status = $rollbackStatus;
2312 $this->billed = $rollbackBilled;
2313 $this->db->rollback();
2314 return -1;
2315 }
2316 }
2317
2324 public function setDraft($user)
2325 {
2326 // phpcs:enable
2327 global $conf, $langs;
2328
2329 $error = 0;
2330
2331 // Protection
2332 if ($this->statut <= self::STATUS_DRAFT) {
2333 return 0;
2334 }
2335
2336 if (!((!getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'creer'))
2337 || (getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'reception_advance', 'validate')))) {
2338 $this->error = 'Permission denied';
2339 return -1;
2340 }
2341
2342 $this->db->begin();
2343
2344 $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
2345 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2346 $sql .= " WHERE rowid = ".((int) $this->id);
2347
2348 dol_syslog(__METHOD__, LOG_DEBUG);
2349 if ($this->db->query($sql)) {
2350 // If stock increment is done on closing
2351 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION')) {
2352 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2353
2354 $langs->load("agenda");
2355
2356 // Loop on each product line to add a stock movement
2357 // TODO possibilite de receptionner a partir d'une propale ou autre origine
2358 $sql = "SELECT cd.fk_product, cd.subprice,";
2359 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2360 $sql .= " ed.eatby, ed.sellby, ed.batch,";
2361 $sql .= " ed.fk_elementdet, ed.cost_price";
2362 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
2363 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
2364 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
2365 $sql .= " AND cd.rowid = ed.fk_elementdet";
2366
2367 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2368 $resql = $this->db->query($sql);
2369 if ($resql) {
2370 $cpt = $this->db->num_rows($resql);
2371 for ($i = 0; $i < $cpt; $i++) {
2372 $obj = $this->db->fetch_object($resql);
2373
2374 $qty = $obj->qty;
2375
2376 if ($qty <= 0) {
2377 continue;
2378 }
2379
2380 dol_syslog(get_class($this)."::reopen reception movement index ".$i." ed.rowid=".$obj->rowid);
2381
2382 //var_dump($this->lines[$i]);
2383 $mouvS = new MouvementStock($this->db);
2384 $mouvS->origin = &$this;
2385 $mouvS->setOrigin($this->element, $this->id, $obj->fk_elementdet, $obj->rowid);
2386
2387 if (empty($obj->batch)) {
2388 // line without batch detail
2389
2390 // 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
2391 $inventorycode = '';
2392 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionBackToDraftInDolibarr", $this->ref), '', '', '', '', 0, $inventorycode);
2393 if ($result < 0) {
2394 $this->error = $mouvS->error;
2395 $this->errors = $mouvS->errors;
2396 $error++;
2397 break;
2398 }
2399 } else {
2400 // line with batch detail
2401
2402 // 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
2403 $inventorycode = '';
2404 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionBackToDraftInDolibarr", $this->ref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, 0, $inventorycode);
2405 if ($result < 0) {
2406 $this->error = $mouvS->error;
2407 $this->errors = $mouvS->errors;
2408 $error++;
2409 break;
2410 }
2411 }
2412 }
2413 } else {
2414 $this->error = $this->db->lasterror();
2415 $error++;
2416 }
2417 }
2418
2419 if (!$error) {
2420 // Call trigger
2421 $result = $this->call_trigger('RECEPTION_UNVALIDATE', $user);
2422 if ($result < 0) {
2423 $error++;
2424 }
2425 }
2426 if ($this->origin == 'order_supplier') {
2427 if (!empty($this->origin) && $this->origin_id > 0) {
2428 $this->fetch_origin();
2429 if ($this->origin_object->statut == 4) { // If order source of reception is "partially received"
2430 // Check if there is no more reception validated.
2431 $this->origin_object->fetchObjectLinked();
2432 $setStatut = 1;
2433 if (!empty($this->origin_object->linkedObjects['reception'])) {
2434 foreach ($this->origin_object->linkedObjects['reception'] as $rcption) {
2435 if ($rcption->statut > 0) {
2436 $setStatut = 0;
2437 break;
2438 }
2439 }
2440 //var_dump($this->$origin->receptions);exit;
2441 if ($setStatut) {
2442 $this->origin_object->setStatut(3); // ordered
2443 }
2444 }
2445 }
2446 }
2447 }
2448
2449 if (!$error) {
2450 $this->statut = self::STATUS_DRAFT;
2451 $this->status = self::STATUS_DRAFT;
2452 $this->db->commit();
2453 return 1;
2454 } else {
2455 $this->db->rollback();
2456 return -1;
2457 }
2458 } else {
2459 $this->error = $this->db->error();
2460 $this->db->rollback();
2461 return -1;
2462 }
2463 }
2464
2475 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
2476 {
2477 global $conf, $langs;
2478
2479 $langs->load("receptions");
2480
2481 if (!dol_strlen($modele)) {
2482 $modele = 'squille';
2483
2484 if ($this->model_pdf) {
2485 $modele = $this->model_pdf;
2486 } elseif (getDolGlobalString('RECEPTION_ADDON_PDF')) {
2487 $modele = getDolGlobalString('RECEPTION_ADDON_PDF');
2488 }
2489 }
2490
2491 $modelpath = "core/modules/reception/doc/";
2492
2493 $this->fetch_origin();
2494
2495 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
2496 }
2497
2506 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2507 {
2508 $tables = array('reception');
2509
2510 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2511 }
2512
2521 public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
2522 {
2523 $tables = array(
2524 'receptiondet_batch'
2525 );
2526
2527 return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
2528 }
2529}
$object ref
Definition info.php:90
Class to manage table ReceptionLineBatch.
Class to manage predefined suppliers products.
const STATUS_RECEIVED_PARTIALLY
Received partially.
const STATUS_RECEIVED_COMPLETELY
Received completely.
Class to manage line orders.
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...
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='')
Set status of an object.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
updateRangOfLine($rowid, $rang)
Update position of line (rang)
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product 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.
Class to manage Dolibarr database access.
Class to manage standard extra fields.
Class to manage stock movements.
Class to manage products or services.
static checkSellOrEatByMandatoryFromProductAndDates($product, $sellBy, $eatBy, $onlyFieldName='', $alreadyCheckConf=false)
Check sell or eat by date is mandatory from product and sell-by and eat-by dates.
Class to manage receptions.
setBilled()
Classify the reception as invoiced (used for example by trigger when WORKFLOW_RECEPTION_CLASSIFY_BILL...
fetch_delivery_methods()
Fetch deliveries method and return an array.
getLibStatut($mode=0)
Return status label.
valid($user, $notrigger=0)
Validate object and update stock if option enabled.
updatelinefree($rowid, $qty, $element_type, $fk_product, $fk_unit, $rang, $description, $notrigger, $array_options=array())
Update a simple reception line.
getUrlTrackingStatus($value='')
Forge an set tracking url.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
getLinesArray()
Create an array of reception lines.
fetch_lines_free()
Load lines of simple reception.
update($user=null, $notrigger=0)
Update database.
setClosed()
Classify the reception as closed (this records also the stock movement)
static replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a product id with another one.
LibStatut($status, $mode)
Return label of a status.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
Create a document onto disk according to template module.
getNomUrl($withpicto=0, $option='', $max=0, $short=0, $notooltip=0)
Return clickable link of object (with eventually picto)
create_line($entrepot_id, $origin_line_id, $qty, $rang=0, $array_options=[], $parent_line_id=0, $product_id=0)
Create a reception line.
setDeliveryDate($user, $delivery_date)
Set the planned delivery date.
list_delivery_methods($id=0)
Fetch all deliveries method and return an array.
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
__construct($db)
Constructor.
addlinefree($qty, $element_type, $fk_product, $fk_unit, $rang, $description, $array_options=[])
Add a simple reception line.
initAsSpecimen()
Initialise an instance with random values.
setReceptionDate($user, $reception_date)
Set the reception date.
fetch_lines()
Load lines.
create($user, $notrigger=0)
Create reception.
fetch($id, $ref='', $ref_ext='')
Get object and lines from database.
reOpen()
Classify the reception as validated/opened.
getStatusDispatch()
Get status from all dispatched lines.
addline($entrepot_id, $id, $qty, $array_options=[], $comment='', $eatby=null, $sellby=null, $batch='', $cost_price=0)
Add an reception line.
setDraft($user)
Set draft status.
Class to manage table commandefournisseurdispatch.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
global $mysoc
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_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_dir_list($utf8_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:64
dol_now($mode='gmt')
Return date for now.
setEntity($currentobject)
Set entity id to use when to create an object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
if(getDolGlobalString( 'TAKEPOS_SHOW_CUSTOMER')) print $langs trans('Date')." left Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:464
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:129