dolibarr 22.0.5
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-2024 Frédéric France <frederic.france@free.fr>
15 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
16 * Copyright (C) 2026 Mathieu Moulin <mathieu@iprospective.fr>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 3 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program. If not, see <https://www.gnu.org/licenses/>.
30 */
31
38require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
39require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
40require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
41if (isModEnabled("propal")) {
42 require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
43}
44if (isModEnabled('order')) {
45 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
46}
47
48
53{
54 use CommonIncoterm;
55
59 public $code = "";
60
64 public $element = "reception";
65
69 public $fk_element = "fk_reception";
70 public $table_element = "reception";
71 public $table_element_line = "receptiondet_batch";
72
76 public $picto = 'dollyrevert';
77
81 public $socid;
85 public $ref_supplier;
86
90 public $entrepot_id;
94 public $tracking_number;
98 public $tracking_url;
102 public $billed;
103
107 public $weight;
111 public $trueWeight;
115 public $weight_units;
119 public $trueWidth;
123 public $width_units;
127 public $trueHeight;
131 public $height_units;
135 public $trueDepth;
139 public $depth_units;
143 public $trueSize;
147 public $size_units;
151 public $user_author_id;
152
156 public $date_delivery;
157
163 public $date;
164
168 public $date_reception;
169
173 public $date_valid;
174
178 public $meths;
182 public $listmeths; // List of carriers
183
187 public $lines = array();
188
189
194 public $detail_batch;
195
196 const STATUS_DRAFT = 0;
197 const STATUS_VALIDATED = 1;
198 const STATUS_CLOSED = 2;
199
200
206 public function __construct($db)
207 {
208 $this->db = $db;
209
210 $this->ismultientitymanaged = 1; // 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
211 }
212
219 public function getNextNumRef($soc)
220 {
221 global $langs, $conf;
222 $langs->load("receptions");
223
224 if (getDolGlobalString('RECEPTION_ADDON_NUMBER')) {
225 $mybool = false;
226
227 $file = getDolGlobalString('RECEPTION_ADDON_NUMBER') . ".php";
228 $classname = getDolGlobalString('RECEPTION_ADDON_NUMBER');
229
230 // Include file with class
231 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
232
233 foreach ($dirmodels as $reldir) {
234 $dir = dol_buildpath($reldir."core/modules/reception/");
235
236 // Load file with numbering class (if found)
237 $mybool = ((bool) @include_once $dir.$file) || $mybool;
238 }
239
240 if (!$mybool) {
241 dol_print_error(null, "Failed to include file ".$file);
242 return '';
243 }
244
245 $obj = new $classname();
246 '@phan-var-force ModelNumRefReception $obj';
247
248 $numref = $obj->getNextValue($soc, $this);
249
250 if ($numref != "") {
251 return $numref;
252 } else {
253 dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
254 return "";
255 }
256 } else {
257 print $langs->trans("Error")." ".$langs->trans("Error_RECEPTION_ADDON_NUMBER_NotDefined");
258 return "";
259 }
260 }
261
269 public function create($user, $notrigger = 0)
270 {
271 global $conf;
272
273 $now = dol_now();
274
275 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
276 $error = 0;
277
278 // Clean parameters
279 $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
280 if (empty($this->fk_project)) {
281 $this->fk_project = 0;
282 }
283 if (empty($this->weight_units)) {
284 $this->weight_units = 0;
285 }
286 if (empty($this->size_units)) {
287 $this->size_units = 0;
288 }
289
290 $this->user = $user;
291
292 $this->db->begin();
293
294 $sql = "INSERT INTO ".MAIN_DB_PREFIX."reception (";
295 $sql .= "ref";
296 $sql .= ", entity";
297 $sql .= ", ref_supplier";
298 $sql .= ", date_creation";
299 $sql .= ", fk_user_author";
300 $sql .= ", date_reception";
301 $sql .= ", date_delivery";
302 $sql .= ", fk_soc";
303 $sql .= ", fk_projet";
304 $sql .= ", fk_shipping_method";
305 $sql .= ", tracking_number";
306 $sql .= ", weight";
307 $sql .= ", size";
308 $sql .= ", width";
309 $sql .= ", height";
310 $sql .= ", weight_units";
311 $sql .= ", size_units";
312 $sql .= ", note_private";
313 $sql .= ", note_public";
314 $sql .= ", model_pdf";
315 $sql .= ", fk_incoterms, location_incoterms";
316 $sql .= ") VALUES (";
317 $sql .= "'(PROV)'";
318 $sql .= ", ".((int) $conf->entity);
319 $sql .= ", ".($this->ref_supplier ? "'".$this->db->escape($this->ref_supplier)."'" : "null");
320 $sql .= ", '".$this->db->idate($now)."'";
321 $sql .= ", ".((int) $user->id);
322 $sql .= ", ".($this->date_reception > 0 ? "'".$this->db->idate($this->date_reception)."'" : "null");
323 $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
324 $sql .= ", ".((int) $this->socid);
325 $sql .= ", ".((int) $this->fk_project);
326 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
327 $sql .= ", '".$this->db->escape($this->tracking_number)."'";
328 $sql .= ", ".(is_null($this->weight) ? "NULL" : ((float) $this->weight));
329 $sql .= ", ".(is_null($this->trueDepth) ? "NULL" : ((float) $this->trueDepth));
330 $sql .= ", ".(is_null($this->trueWidth) ? "NULL" : ((float) $this->trueWidth));
331 $sql .= ", ".(is_null($this->trueHeight) ? "NULL" : ((float) $this->trueHeight));
332 $sql .= ", ".((int) $this->weight_units);
333 $sql .= ", ".((int) $this->size_units);
334 $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
335 $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
336 $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
337 $sql .= ", ".(int) $this->fk_incoterms;
338 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
339 $sql .= ")";
340
341 dol_syslog(get_class($this)."::create", LOG_DEBUG);
342
343 $resql = $this->db->query($sql);
344
345 if ($resql) {
346 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."reception");
347
348 $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
349 $sql .= " SET ref = '(PROV".((int) $this->id).")'";
350 $sql .= " WHERE rowid = ".((int) $this->id);
351
352 dol_syslog(get_class($this)."::create", LOG_DEBUG);
353 if ($this->db->query($sql)) {
354 // Insert of lines
355 $num = count($this->lines);
356 for ($i = 0; $i < $num; $i++) {
357 $this->lines[$i]->fk_reception = $this->id;
358
359 if (!$this->lines[$i]->create($user) > 0) {
360 $error++;
361 }
362 }
363
364 if (!$error && $this->id && $this->origin_id) {
365 $ret = $this->add_object_linked();
366 if (!$ret) {
367 $error++;
368 }
369 }
370
371 // Create extrafields
372 if (!$error) {
373 $result = $this->insertExtraFields();
374 if ($result < 0) {
375 $error++;
376 }
377 }
378
379 if (!$error && !$notrigger) {
380 // Call trigger
381 $result = $this->call_trigger('RECEPTION_CREATE', $user);
382 if ($result < 0) {
383 $error++;
384 }
385 // End call triggers
386 }
387
388 if (!$error) {
389 $this->db->commit();
390 return $this->id;
391 } else {
392 foreach ($this->errors as $errmsg) {
393 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
394 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
395 }
396 $this->db->rollback();
397 return -1 * $error;
398 }
399 } else {
400 $error++;
401 $this->error = $this->db->lasterror()." - sql=$sql";
402 $this->db->rollback();
403 return -2;
404 }
405 } else {
406 $error++;
407 $this->error = $this->db->error()." - sql=$sql";
408 $this->db->rollback();
409 return -1;
410 }
411 }
412
413
414
423 public function fetch($id, $ref = '', $ref_ext = '')
424 {
425 // Check parameters
426 if (empty($id) && empty($ref) && empty($ref_ext)) {
427 return -1;
428 }
429
430 $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.billed";
431 $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
432 $sql .= ", e.date_reception as date_reception, e.model_pdf, e.date_delivery, e.date_valid";
433 $sql .= ", e.fk_shipping_method, e.tracking_number";
434 $sql .= ", el.fk_source as origin_id, el.sourcetype as origin";
435 $sql .= ", e.note_private, e.note_public";
436 $sql .= ', e.fk_incoterms, e.location_incoterms';
437 $sql .= ', i.libelle as label_incoterms';
438 $sql .= " FROM ".MAIN_DB_PREFIX."reception as e";
439 $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'";
440 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
441
442 if ($id) {
443 $sql .= " WHERE e.rowid = ".((int) $id);
444 } else {
445 $sql .= " WHERE e.entity IN (".getEntity('reception').")";
446 if ($ref) {
447 $sql .= " AND e.ref = '".$this->db->escape($ref)."'";
448 } elseif ($ref_ext) {
449 $sql .= " AND e.ref_ext = '".$this->db->escape($ref_ext)."'";
450 }
451 }
452
453 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
454 $result = $this->db->query($sql);
455 if ($result) {
456 if ($this->db->num_rows($result)) {
457 $obj = $this->db->fetch_object($result);
458
459 $this->id = $obj->rowid;
460 $this->entity = $obj->entity;
461 $this->ref = $obj->ref;
462 $this->socid = $obj->socid;
463 $this->ref_supplier = $obj->ref_supplier;
464 $this->ref_ext = $obj->ref_ext;
465 $this->statut = $obj->status;
466 $this->status = $obj->status;
467 $this->billed = $obj->billed;
468
469 $this->user_author_id = $obj->fk_user_author;
470 $this->date_creation = $this->db->jdate($obj->date_creation);
471 $this->date = $this->db->jdate($obj->date_reception); // TODO deprecated
472 $this->date_reception = $this->db->jdate($obj->date_reception); // Date real
473 $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
474 $this->date_valid = $this->db->jdate($obj->date_valid); // Date validation
475 $this->model_pdf = $obj->model_pdf;
476 $this->shipping_method_id = $obj->fk_shipping_method;
477 $this->tracking_number = $obj->tracking_number;
478 $this->origin = ($obj->origin ? $obj->origin : 'commande'); // For compatibility
479 $this->origin_type = ($obj->origin ? $obj->origin : 'commande'); // For compatibility
480 $this->origin_id = $obj->origin_id;
481
482 $this->trueWeight = $obj->weight;
483 $this->weight_units = $obj->weight_units;
484
485 $this->trueWidth = $obj->width;
486 $this->width_units = $obj->size_units;
487 $this->trueHeight = $obj->height;
488 $this->height_units = $obj->size_units;
489 $this->trueDepth = $obj->size;
490 $this->depth_units = $obj->size_units;
491
492 $this->note_public = $obj->note_public;
493 $this->note_private = $obj->note_private;
494
495 // A denormalized value
496 $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
497 $this->size_units = $obj->size_units;
498
499 //Incoterms
500 $this->fk_incoterms = $obj->fk_incoterms;
501 $this->location_incoterms = $obj->location_incoterms;
502 $this->label_incoterms = $obj->label_incoterms;
503
504 $this->db->free($result);
505
506 //$file = $conf->reception->dir_output."/".get_exdir(0, 0, 0, 1, $this, 'reception')."/".$this->id.".pdf";
507 //$this->pdf_filename = $file;
508
509 // Tracking url
510 $this->getUrlTrackingStatus($obj->tracking_number);
511
512 /*
513 * Thirdparty
514 */
515 $result = $this->fetch_thirdparty();
516
517
518 // Retrieve all extrafields for reception
519 // fetch optionals attributes and labels
520 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
521 $extrafields = new ExtraFields($this->db);
522 $extrafields->fetch_name_optionals_label($this->table_element, true);
523 $this->fetch_optionals();
524
525 /*
526 * Lines
527 */
528 $result = $this->fetch_lines();
529 if ($result < 0) {
530 return -3;
531 }
532
533 return 1;
534 } else {
535 dol_syslog(get_class($this).'::Fetch no reception found', LOG_ERR);
536 $this->error = 'Reception with id '.$id.' not found';
537 return 0;
538 }
539 } else {
540 $this->error = $this->db->error();
541 return -1;
542 }
543 }
544
552 public function valid($user, $notrigger = 0)
553 {
554 global $conf, $langs;
555
556 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
557
558 dol_syslog(get_class($this)."::valid");
559
560 // Protection
561 if ($this->statut) {
562 dol_syslog(get_class($this)."::valid no draft status", LOG_WARNING);
563 return 0;
564 }
565
566 if (!((!getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'creer'))
567 || (getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'reception_advance', 'validate')))) {
568 $this->error = 'Permission denied';
569 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
570 return -1;
571 }
572
573 $this->db->begin();
574
575 $error = 0;
576
577 // Define new ref
578 $soc = new Societe($this->db);
579 $soc->fetch($this->socid);
580
581
582 // Define new ref
583 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
584 $numref = $this->getNextNumRef($soc);
585 } else {
586 $numref = $this->ref;
587 }
588
589 $this->newref = dol_sanitizeFileName($numref);
590
591 $now = dol_now();
592
593 // Validate
594 $sql = "UPDATE ".MAIN_DB_PREFIX."reception SET";
595 $sql .= " ref='".$this->db->escape($numref)."'";
596 $sql .= ", fk_statut = 1";
597 $sql .= ", date_valid = '".$this->db->idate($now)."'";
598 $sql .= ", fk_user_valid = ".((int) $user->id);
599 $sql .= " WHERE rowid = ".((int) $this->id);
600 dol_syslog(get_class($this)."::valid update reception", LOG_DEBUG);
601 $resql = $this->db->query($sql);
602 if (!$resql) {
603 $this->error = $this->db->lasterror();
604 $error++;
605 }
606
607 // If stock increment is done on reception (recommended choice)
608 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION')) {
609 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
610
611 $langs->load("agenda");
612
613 // Loop on each product line to add a stock movement
614 // TODO in future, reception lines may not be linked to order line
615 $sql = "SELECT cd.fk_product, cd.subprice, cd.remise_percent,";
616 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
617 $sql .= " ed.eatby, ed.sellby, ed.batch,";
618 $sql .= " ed.cost_price";
619 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
620 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
621 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
622 $sql .= " AND cd.rowid = ed.fk_elementdet";
623
624 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
625 $resql = $this->db->query($sql);
626 if ($resql) {
627 $cpt = $this->db->num_rows($resql);
628 for ($i = 0; $i < $cpt; $i++) {
629 $obj = $this->db->fetch_object($resql);
630
631 $qty = $obj->qty;
632
633 if ($qty == 0 || ($qty < 0 && !getDolGlobalInt('RECEPTION_ALLOW_NEGATIVE_QTY'))) {
634 continue;
635 }
636
637 dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid);
638
639 //var_dump($this->lines[$i]);
640 $mouvS = new MouvementStock($this->db);
641 $mouvS->origin = &$this;
642 $mouvS->setOrigin($this->element, $this->id);
643
644 if (empty($obj->batch)) {
645 // line without batch detail
646
647 // 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.
648 $inventorycode = '';
649 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionValidatedInDolibarr", $numref), '', '', '', '', 0, $inventorycode);
650
651 if (intval($result) < 0) {
652 $error++;
653 $this->setErrorsFromObject($mouvS);
654 break;
655 }
656 } else {
657 // line with batch detail
658
659 // 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.
660 // Note: ->fk_origin_stock = id into table llx_product_batch (may be rename into llx_product_stock_batch in another version)
661 $inventorycode = '';
662 $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);
663
664 if (intval($result) < 0) {
665 $error++;
666 $this->setErrorsFromObject($mouvS);
667 break;
668 }
669 }
670 }
671 } else {
672 $this->db->rollback();
673 $this->error = $this->db->error();
674 return -2;
675 }
676 }
677
678 if (!$error) {
679 // Change status of purchase order to "reception in process" or "totally received"
680 $status = $this->getStatusDispatch();
681 if ($status < 0) {
682 $error++;
683 } else {
684 $trigger_key = '';
685 if ($this->origin_object instanceof CommandeFournisseur && $status == CommandeFournisseur::STATUS_RECEIVED_COMPLETELY) {
686 $ret = $this->origin_object->Livraison($user, dol_now(), 'tot', '');
687 if ($ret < 0) {
688 $error++;
689 $this->errors = array_merge($this->errors, $this->origin_object->errors);
690 }
691 } else {
692 $ret = $this->setStatut($status, $this->origin_id, 'commande_fournisseur', $trigger_key);
693 if ($ret < 0) {
694 $error++;
695 }
696 }
697 }
698 }
699
700 if (!$error && !$notrigger) {
701 // Call trigger
702 $result = $this->call_trigger('RECEPTION_VALIDATE', $user);
703 if ($result < 0) {
704 $error++;
705 }
706 // End call triggers
707 }
708
709 if (!$error) {
710 $this->oldref = $this->ref;
711
712 // Rename directory if dir was a temporary ref
713 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
714 // Now we rename also files into index
715 $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)."'";
716 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'reception/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity);
717 $resql = $this->db->query($sql);
718 if (!$resql) {
719 $error++;
720 $this->error = $this->db->lasterror();
721 }
722 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'reception/".$this->db->escape($this->newref)."'";
723 $sql .= " WHERE filepath = 'reception/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
724 $resql = $this->db->query($sql);
725 if (!$resql) {
726 $error++;
727 $this->error = $this->db->lasterror();
728 }
729
730 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
731 $oldref = dol_sanitizeFileName($this->ref);
732 $newref = dol_sanitizeFileName($numref);
733 $dirsource = $conf->reception->dir_output.'/'.$oldref;
734 $dirdest = $conf->reception->dir_output.'/'.$newref;
735 if (!$error && file_exists($dirsource)) {
736 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
737
738 if (@rename($dirsource, $dirdest)) {
739 dol_syslog("Rename ok");
740 // Rename docs starting with $oldref with $newref
741 $listoffiles = dol_dir_list($conf->reception->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
742 foreach ($listoffiles as $fileentry) {
743 $dirsource = $fileentry['name'];
744 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
745 $dirsource = $fileentry['path'].'/'.$dirsource;
746 $dirdest = $fileentry['path'].'/'.$dirdest;
747 @rename($dirsource, $dirdest);
748 }
749 }
750 }
751 }
752 }
753
754 // Set new ref and current status
755 if (!$error) {
756 $this->ref = $numref;
757 $this->statut = self::STATUS_VALIDATED;
758 $this->status = self::STATUS_VALIDATED;
759 }
760
761 if (!$error) {
762 $this->db->commit();
763 return 1;
764 } else {
765 foreach ($this->errors as $errmsg) {
766 dol_syslog(get_class($this)."::valid ".$errmsg, LOG_ERR);
767 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
768 }
769 $this->db->rollback();
770 return -1 * $error;
771 }
772 }
773
779 public function getStatusDispatch()
780 {
781 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
782 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
783
785
786 if (!empty($this->origin) && $this->origin_id > 0 && ($this->origin == 'order_supplier' || $this->origin == 'commandeFournisseur')) {
787 if (empty($this->origin_object)) {
788 $this->fetch_origin();
789 if ($this->origin_object instanceof CommonObject && empty($this->origin_object->lines)) {
790 $res = $this->origin_object->fetch_lines();
791 $this->commandeFournisseur = null; // deprecated
792 if ($res < 0) {
793 return $res;
794 }
795 } elseif ($this->origin_object instanceof CommandeFournisseur && empty($this->origin_object->lines)) {
796 $res = $this->origin_object->fetch_lines();
797 $this->commandeFournisseur = $this->origin_object; // deprecated
798 if ($res < 0) {
799 return $res;
800 }
801 }
802 }
803
804 $qty_received = array();
805 $qty_wished = array();
806
807 $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
808 $filter = array('t.fk_element' => $this->origin_id);
809 if (getDolGlobalInt('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
810 $filter['t.status'] = 1; // Restrict to lines with status validated
811 }
812
813 $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
814 if ($ret < 0) {
815 $this->setErrorsFromObject($supplierorderdispatch);
816 return $ret;
817 } else {
818 // build array with quantity received by product in all supplier orders (origin)
819 foreach ($supplierorderdispatch->lines as $dispatch_line) {
820 if (array_key_exists($dispatch_line->fk_product, $qty_received)) {
821 $qty_received[$dispatch_line->fk_product] += $dispatch_line->qty;
822 } else {
823 $qty_received[$dispatch_line->fk_product] = $dispatch_line->qty;
824 }
825 }
826
827 // qty wished in origin (purchase order, ...)
828 foreach ($this->origin_object->lines as $origin_line) {
829 // exclude lines not qualified for reception
830 if ((!getDolGlobalInt('STOCK_SUPPORTS_SERVICES') && $origin_line->product_type > 0) || $origin_line->product_type > 1) {
831 continue;
832 }
833 if (array_key_exists($origin_line->fk_product, $qty_wished)) {
834 $qty_wished[$origin_line->fk_product] += $origin_line->qty;
835 } else {
836 $qty_wished[$origin_line->fk_product] = $origin_line->qty;
837 }
838 }
839
840 // compare array
841 $diff_array = array_diff_assoc($qty_received, $qty_wished); // Warning: $diff_array is done only on common keys.
842 $keys_in_wished_not_in_received = array_diff(array_keys($qty_wished), array_keys($qty_received));
843 $keys_in_received_not_in_wished = array_diff(array_keys($qty_received), array_keys($qty_wished));
844
845 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
847 } elseif (getDolGlobalInt('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
848 // set totally received if more products received than ordered
849 $close = 0;
850
851 if (count($diff_array) > 0) {
852 // there are some difference between the two arrays
853 // scan the array of results
854 foreach ($diff_array as $key => $value) {
855 // if the quantity delivered is greater or equal to ordered quantity @phan-suppress-next-line PhanTypeInvalidDimOffset
856 if ($qty_received[$key] >= $qty_wished[$key]) {
857 $close++;
858 }
859 }
860 }
861
862 if ($close == count($diff_array)) {
863 // all the products are received equal or more than the ordered quantity
865 }
866 }
867 }
868 }
869
870 return $status;
871 }
872
889 public function addline($entrepot_id, $id, $qty, $array_options = [], $comment = '', $eatby = null, $sellby = null, $batch = '', $cost_price = 0)
890 {
891 global $conf, $langs, $user;
892
893 $num = count($this->lines);
894 $line = new CommandeFournisseurDispatch($this->db);
895
896 $line->fk_entrepot = $entrepot_id;
897 $line->fk_commandefourndet = $id;
898 $line->qty = $qty;
899
900 $supplierorderline = new CommandeFournisseurLigne($this->db);
901 $result = $supplierorderline->fetch($id);
902 if ($result <= 0) {
903 $this->setErrorsFromObject($supplierorderline);
904 return -1;
905 }
906
907 $fk_product = 0;
908 if (isModEnabled('stock') && !empty($supplierorderline->fk_product)) {
909 $fk_product = $supplierorderline->fk_product;
910
911 if (!($entrepot_id > 0) && !getDolGlobalInt('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_RECEPTIONS')) {
912 $langs->load("errors");
913 $this->error = $langs->trans("ErrorWarehouseRequiredIntoReceptionLine");
914 return -1;
915 }
916 }
917
918 // Check batch is set
919 $product = new Product($this->db);
920 $product->fetch($fk_product);
921 if (isModEnabled('productbatch')) {
922 $langs->load("errors");
923 if (!empty($product->status_batch) && empty($batch)) {
924 $this->error = $langs->trans('ErrorProductNeedBatchNumber', $product->ref);
925 return -1;
926 } elseif (empty($product->status_batch) && !empty($batch)) {
927 $this->error = $langs->trans('ErrorProductDoesNotNeedBatchNumber', $product->ref);
928 return -1;
929 }
930
931 // check sell-by / eat-by date is mandatory
932 $errorMsgArr = Productlot::checkSellOrEatByMandatoryFromProductAndDates($product, $sellby, $eatby);
933 if (!empty($errorMsgArr)) {
934 $errorMessage = '<b>' . $product->ref . '</b> : ';
935 $errorMessage .= '<ul>';
936 foreach ($errorMsgArr as $errorMsg) {
937 $errorMessage .= '<li>' . $errorMsg . '</li>';
938 }
939 $errorMessage .= '</ul>';
940 $this->error = $errorMessage;
941 return -1;
942 }
943 }
944 unset($product);
945
946 // extrafields
947 $line->array_options = $supplierorderline->array_options;
948 if (!getDolGlobalInt('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) {
949 foreach ($array_options as $key => $value) {
950 $line->array_options[$key] = $value;
951 }
952 }
953
954 $line->fk_product = $fk_product;
955 $line->fk_commande = $supplierorderline->fk_commande;
956 $line->fk_user = $user->id;
957 $line->comment = $comment;
958 $line->batch = $batch;
959 $line->eatby = $eatby;
960 $line->sellby = $sellby;
961 $line->status = 1;
962 $line->cost_price = $cost_price;
963 $line->fk_reception = $this->id;
964
965 $this->lines[$num] = $line;
966
967 return $num;
968 }
969
970
978 public function update($user = null, $notrigger = 0)
979 {
980 global $conf;
981 $error = 0;
982
983 // Clean parameters
984
985 if (isset($this->ref)) {
986 $this->ref = trim($this->ref);
987 }
988 if (isset($this->entity)) {
989 $this->entity = (int) $this->entity;
990 }
991 if (isset($this->ref_supplier)) {
992 $this->ref_supplier = trim($this->ref_supplier);
993 }
994 if (isset($this->socid)) {
995 $this->socid = (int) trim((string) $this->socid);
996 }
997 if (isset($this->fk_user_author)) {
998 $this->fk_user_author = (int) $this->fk_user_author;
999 }
1000 if (isset($this->fk_user_valid)) {
1001 $this->fk_user_valid = (int) $this->fk_user_valid;
1002 }
1003 if (isset($this->shipping_method_id)) {
1004 $this->shipping_method_id = (int) $this->shipping_method_id;
1005 }
1006 if (isset($this->tracking_number)) {
1007 $this->tracking_number = trim($this->tracking_number);
1008 }
1009 if (isset($this->statut)) {
1010 $this->statut = (int) $this->statut;
1011 }
1012 if (isset($this->trueDepth)) {
1013 $this->trueDepth = price2num($this->trueDepth);
1014 }
1015 if (isset($this->trueWidth)) {
1016 $this->trueWidth = price2num($this->trueWidth);
1017 }
1018 if (isset($this->trueHeight)) {
1019 $this->trueHeight = price2num($this->trueHeight);
1020 }
1021 $this->size_units = (int) $this->size_units;
1022
1023 if (isset($this->trueWeight)) {
1024 $this->weight = price2num($this->trueWeight);
1025 }
1026 $this->weight_units = (int) $this->weight_units;
1027
1028 if (isset($this->note_private)) {
1029 $this->note_private = trim($this->note_private);
1030 }
1031 if (isset($this->note_public)) {
1032 $this->note_public = trim($this->note_public);
1033 }
1034 if (isset($this->model_pdf)) {
1035 $this->model_pdf = trim($this->model_pdf);
1036 }
1037
1038
1039 // Check parameters
1040 // Put here code to add control on parameters values
1041
1042 // Update request
1043 $sql = "UPDATE ".MAIN_DB_PREFIX."reception SET";
1044
1045 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1046 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1047 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1048 $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1049 $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1050 $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1051 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1052 $sql .= " date_reception=".(dol_strlen($this->date_reception) != 0 ? "'".$this->db->idate($this->date_reception)."'" : 'null').",";
1053 $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1054 $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1055 $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1056 $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1057 $sql .= " height=".(($this->trueHeight != '') ? (float) $this->trueHeight : "null").",";
1058 $sql .= " width=".(($this->trueWidth != '') ? (float) $this->trueWidth : "null").",";
1059 $sql .= " size_units=".((int) $this->size_units).",";
1060 $sql .= " size=".(($this->trueDepth != '') ? (float) $this->trueDepth : "null").",";
1061 $sql .= " weight_units=".((int) $this->weight_units).",";
1062 $sql .= " weight=".(($this->trueWeight != '') ? (float) $this->trueWeight : "null").",";
1063 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1064 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1065 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1066 $sql .= " entity = ".((int) $conf->entity);
1067 $sql .= " WHERE rowid=".((int) $this->id);
1068
1069 $this->db->begin();
1070
1071 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1072 $resql = $this->db->query($sql);
1073 if (!$resql) {
1074 $error++;
1075 $this->errors[] = "Error ".$this->db->lasterror();
1076 }
1077
1078 if (!$error) {
1079 if (!$notrigger) {
1080 // Call trigger
1081 $result = $this->call_trigger('RECEPTION_MODIFY', $user);
1082 if ($result < 0) {
1083 $error++;
1084 }
1085 // End call triggers
1086 }
1087 }
1088
1089 // Commit or rollback
1090 if ($error) {
1091 foreach ($this->errors as $errmsg) {
1092 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1093 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1094 }
1095 $this->db->rollback();
1096 return -1 * $error;
1097 } else {
1098 $this->db->commit();
1099 return 1;
1100 }
1101 }
1102
1109 public function delete(User $user)
1110 {
1111 global $conf, $langs, $user;
1112 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1113
1114 $error = 0;
1115 $this->error = '';
1116
1117
1118 $this->db->begin();
1119
1120 // Stock control
1121 if (isModEnabled('stock') && ((getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION') && $this->status > Reception::STATUS_DRAFT)
1122 || (getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE') && $this->status == Reception::STATUS_CLOSED))
1123 ) {
1124 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1125
1126 $langs->load("agenda");
1127
1128 // Loop on each product line to add a stock movement
1129 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.eatby, ed.sellby, ed.batch, ed.rowid as receptiondet_batch_id";
1130 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1131 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1132 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1133 $sql .= " AND cd.rowid = ed.fk_elementdet";
1134
1135 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1136 $resql = $this->db->query($sql);
1137 if ($resql) {
1138 $cpt = $this->db->num_rows($resql);
1139 for ($i = 0; $i < $cpt; $i++) {
1140 dol_syslog(get_class($this)."::delete movement index ".$i);
1141 $obj = $this->db->fetch_object($resql);
1142
1143 $mouvS = new MouvementStock($this->db);
1144 // we do not log origin because it will be deleted
1145 $mouvS->origin = null;
1146
1147 $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
1148 if ($result < 0) {
1149 $error++;
1150 $this->error = $mouvS->error;
1151 $this->errors = $mouvS->errors;
1152 }
1153 }
1154 } else {
1155 $error++;
1156 $this->errors[] = "Error ".$this->db->lasterror();
1157 }
1158 }
1159
1160 if (!$error) {
1161 $main = MAIN_DB_PREFIX.'receptiondet_batch';
1162 $ef = $main."_extrafields";
1163
1164 $sqlef = "DELETE FROM ".$ef." WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_reception = ".((int) $this->id).")";
1165
1166 $sql = "DELETE FROM ".MAIN_DB_PREFIX."receptiondet_batch";
1167 $sql .= " WHERE fk_reception = ".((int) $this->id);
1168
1169 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1170 // Delete linked object
1171 $res = $this->deleteObjectLinked();
1172 if ($res < 0) {
1173 $error++;
1174 }
1175
1176 if (!$error) {
1177 $sql = "DELETE FROM ".MAIN_DB_PREFIX."reception";
1178 $sql .= " WHERE rowid = ".((int) $this->id);
1179
1180 if ($this->db->query($sql)) {
1181 // Call trigger
1182 $result = $this->call_trigger('RECEPTION_DELETE', $user);
1183 if ($result < 0) {
1184 $error++;
1185 }
1186 // End call triggers
1187
1188 if (!empty($this->origin) && $this->origin_id > 0) {
1189 $this->fetch_origin();
1191 '@phan-var-force CommandeFournisseur $origin_object';
1192 if ($origin_object->statut == 4) { // If order source of reception is "partially received"
1193 // Check if there is no more reception. If not, we can move back status of order to "validated" instead of "reception in progress"
1194 $origin_object->loadReceptions();
1195 //var_dump($this->$origin->receptions);exit;
1196 if (count($origin_object->receptions) <= 0) {
1197 $origin_object->setStatut(3); // ordered
1198 }
1199 }
1200 }
1201
1202 if (!$error) {
1203 $this->db->commit();
1204
1205 // We delete PDFs
1206 $ref = dol_sanitizeFileName($this->ref);
1207 if (!empty($conf->reception->dir_output)) {
1208 $dir = $conf->reception->dir_output.'/'.$ref;
1209 $file = $dir.'/'.$ref.'.pdf';
1210 if (file_exists($file)) {
1211 if (!dol_delete_file($file)) {
1212 return 0;
1213 }
1214 }
1215 if (file_exists($dir)) {
1216 if (!dol_delete_dir_recursive($dir)) {
1217 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1218 return 0;
1219 }
1220 }
1221 }
1222
1223 return 1;
1224 } else {
1225 $this->db->rollback();
1226 return -1;
1227 }
1228 } else {
1229 $this->error = $this->db->lasterror()." - sql=$sql";
1230 $this->db->rollback();
1231 return -3;
1232 }
1233 } else {
1234 $this->error = $this->db->lasterror()." - sql=$sql";
1235 $this->db->rollback();
1236 return -2;
1237 }
1238 } else {
1239 $this->error = $this->db->lasterror()." - sql=$sql";
1240 $this->db->rollback();
1241 return -1;
1242 }
1243 } else {
1244 $this->db->rollback();
1245 return -1;
1246 }
1247 }
1248
1249 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1255 public function fetch_lines()
1256 {
1257 // phpcs:enable
1258 $this->lines = array();
1259
1260 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
1261
1262 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."receptiondet_batch";
1263 $sql .= " WHERE fk_reception = ".((int) $this->id);
1264
1265 $resql = $this->db->query($sql);
1266
1267 if (!empty($resql)) {
1268 while ($obj = $this->db->fetch_object($resql)) {
1269 $line = new CommandeFournisseurDispatch($this->db);
1270
1271 $line->fetch($obj->rowid);
1272
1273 // TODO Remove or keep this ?
1274 $line->fetch_product();
1275
1276 $sql_commfourndet = 'SELECT qty, ref, label, description, tva_tx, vat_src_code, subprice, multicurrency_subprice, remise_percent, total_ht, total_ttc, total_tva';
1277 $sql_commfourndet .= ' FROM '.MAIN_DB_PREFIX.'commande_fournisseurdet';
1278 $sql_commfourndet .= ' WHERE rowid = '.((int) $line->fk_commandefourndet);
1279 $sql_commfourndet .= ' ORDER BY rang';
1280
1281 $resql_commfourndet = $this->db->query($sql_commfourndet);
1282 if (!empty($resql_commfourndet)) {
1283 $obj = $this->db->fetch_object($resql_commfourndet);
1284 $line->qty_asked = $obj->qty;
1285 $line->description = $obj->description;
1286 $line->desc = $obj->description;
1287 $line->tva_tx = $obj->tva_tx;
1288 $line->vat_src_code = $obj->vat_src_code;
1289 $line->subprice = $obj->subprice;
1290 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1291 $line->remise_percent = $obj->remise_percent;
1292 $line->label = !empty($obj->label) ? $obj->label : (is_object($line->product) ? $line->product->label : '');
1293 $line->ref_supplier = $obj->ref;
1294 $line->total_ht = $obj->total_ht;
1295 $line->total_ttc = $obj->total_ttc;
1296 $line->total_tva = $obj->total_tva;
1297 } else {
1298 $line->qty_asked = 0;
1299 $line->description = '';
1300 $line->desc = '';
1301 $line->label = $obj->label;
1302 }
1303
1304 $pu_ht = ($line->subprice * $line->qty) * (100 - $line->remise_percent) / 100;
1305 $tva = $pu_ht * $line->tva_tx / 100;
1306 $this->total_ht += $pu_ht;
1307 $this->total_tva += $pu_ht * $line->tva_tx / 100;
1308
1309 $this->total_ttc += $pu_ht + $tva;
1310
1311 if (isModEnabled('productbatch') && !empty($line->batch)) {
1312 $detail_batch = new stdClass();
1313 $detail_batch->eatby = $line->eatby;
1314 $detail_batch->sellby = $line->sellby;
1315 $detail_batch->batch = $line->batch;
1316 $detail_batch->qty = $line->qty;
1317
1318 $line->detail_batch[] = $detail_batch;
1319 }
1320
1321 $this->lines[] = $line;
1322 }
1323
1324 return 1;
1325 } else {
1326 return -1;
1327 }
1328 }
1329
1340 public function getNomUrl($withpicto = 0, $option = 0, $max = 0, $short = 0, $notooltip = 0)
1341 {
1342 global $langs, $hookmanager;
1343
1344 $result = '';
1345 $label = img_picto('', $this->picto).' <u>'.$langs->trans("Reception").'</u>';
1346 $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1347 $label .= '<br><b>'.$langs->trans('RefSupplier').':</b> '.($this->ref_supplier ? $this->ref_supplier : '');
1348
1349 $url = DOL_URL_ROOT.'/reception/card.php?id='.$this->id;
1350
1351 if ($short) {
1352 return $url;
1353 }
1354
1355 $linkclose = '';
1356 if (empty($notooltip)) {
1357 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1358 $label = $langs->trans("Reception");
1359 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1360 }
1361 $linkclose .= ' title="'.dolPrintHTMLForAttribute($label).'"';
1362 $linkclose .= ' class="classfortooltip"';
1363 }
1364
1365 $linkstart = '<a href="'.$url.'"';
1366 $linkstart .= $linkclose.'>';
1367 $linkend = '</a>';
1368
1369 $result .= $linkstart;
1370 if ($withpicto) {
1371 $result .= img_object(($notooltip ? '' : $label), $this->picto, '', 0, 0, $notooltip ? 0 : 1);
1372 }
1373 if ($withpicto != 2) {
1374 $result .= $this->ref;
1375 }
1376
1377 $result .= $linkend;
1378
1379 global $action;
1380 $hookmanager->initHooks(array($this->element . 'dao'));
1381 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1382 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1383 if ($reshook > 0) {
1384 $result = $hookmanager->resPrint;
1385 } else {
1386 $result .= $hookmanager->resPrint;
1387 }
1388 return $result;
1389 }
1390
1397 public function getLibStatut($mode = 0)
1398 {
1399 return $this->LibStatut($this->statut, $mode);
1400 }
1401
1402 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1410 public function LibStatut($status, $mode)
1411 {
1412 // phpcs:enable
1413 global $langs;
1414
1415 // List of long language codes for status
1416 $this->labelStatus[-1] = 'StatusReceptionCanceled';
1417 $this->labelStatus[0] = 'StatusReceptionDraft';
1418 // product to receive if stock increase is on close or already received if stock increase is on validation
1419 $this->labelStatus[1] = 'StatusReceptionValidated';
1420 if (getDolGlobalInt("STOCK_CALCULATE_ON_RECEPTION")) {
1421 $this->labelStatus[1] = 'StatusReceptionValidatedReceived';
1422 }
1423 if (getDolGlobalInt("STOCK_CALCULATE_ON_RECEPTION_CLOSE")) {
1424 $this->labelStatus[1] = 'StatusReceptionValidatedToReceive';
1425 }
1426 $this->labelStatus[2] = 'StatusReceptionProcessed';
1427
1428 // List of short language codes for status
1429 $this->labelStatusShort[-1] = 'StatusReceptionCanceledShort';
1430 $this->labelStatusShort[0] = 'StatusReceptionDraftShort';
1431 $this->labelStatusShort[1] = 'StatusReceptionValidatedShort';
1432 $this->labelStatusShort[2] = 'StatusReceptionProcessedShort';
1433
1434 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
1435 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
1436
1437 $statusType = 'status'.$status;
1438 if ($status == self::STATUS_VALIDATED) {
1439 $statusType = 'status4';
1440 }
1441 if ($status == self::STATUS_CLOSED) {
1442 $statusType = 'status6';
1443 }
1444
1445 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
1446 }
1447
1455 public function getKanbanView($option = '', $arraydata = null)
1456 {
1457 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1458
1459 $return = '<div class="box-flex-item box-flex-grow-zero">';
1460 $return .= '<div class="info-box info-box-sm">';
1461 $return .= '<div class="info-box-icon bg-infobox-action">';
1462 $return .= img_picto('', 'order');
1463 $return .= '</div>';
1464 $return .= '<div class="info-box-content">';
1465 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
1466 if ($selected >= 0) {
1467 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1468 }
1469 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
1470 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
1471 }
1472 /*if (property_exists($this, 'total_ht')) {
1473 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'</div>';
1474 }*/
1475 if (method_exists($this, 'getLibStatut')) {
1476 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1477 }
1478 $return .= '</div>';
1479 $return .= '</div>';
1480 $return .= '</div>';
1481
1482 return $return;
1483 }
1484
1492 public function initAsSpecimen()
1493 {
1494 global $langs;
1495
1496 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
1497 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
1498 $now = dol_now();
1499
1500 dol_syslog(get_class($this)."::initAsSpecimen");
1501
1502 $order = new CommandeFournisseur($this->db);
1503 $order->initAsSpecimen();
1504
1505 // Initialise parameters
1506 $this->id = 0;
1507 $this->ref = 'SPECIMEN';
1508 $this->specimen = 1;
1509 $this->statut = 1;
1510 $this->status = 1;
1511 $this->date = $now;
1512 $this->date_creation = $now;
1513 $this->date_valid = $now;
1514 $this->date_delivery = $now;
1515 $this->date_reception = $now + 24 * 3600;
1516
1517 $this->entrepot_id = 0;
1518 $this->socid = 1;
1519
1520 $this->origin_id = 1;
1521 $this->origin_type = 'supplier_order';
1522 $this->origin_object = $order;
1523
1524 $this->note_private = 'Private note';
1525 $this->note_public = 'Public note';
1526
1527 $this->tracking_number = 'TRACKID-ABC123';
1528
1529 $this->fk_incoterms = 1;
1530
1531 $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)
1532 $xnbp = 0;
1533 while ($xnbp < $nbp) {
1534 $line = new CommandeFournisseurDispatch($this->db);
1535 $line->desc = $langs->trans("Description")." ".$xnbp;
1536 $line->libelle = $langs->trans("Description")." ".$xnbp; // deprecated
1537 $line->label = $langs->trans("Description")." ".$xnbp;
1538 $line->qty = 10;
1539
1540 $line->fk_product = $this->origin_object->lines[$xnbp]->fk_product;
1541
1542 $this->lines[] = $line;
1543 $xnbp++;
1544 }
1545
1546 return 1;
1547 }
1548
1556 public function setDeliveryDate($user, $delivery_date)
1557 {
1558 // phpcs:enable
1559 if ($user->hasRight('reception', 'creer')) {
1560 $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
1561 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
1562 $sql .= " WHERE rowid = ".((int) $this->id);
1563
1564 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
1565 $resql = $this->db->query($sql);
1566 if ($resql) {
1567 $this->date_delivery = $delivery_date;
1568 return 1;
1569 } else {
1570 $this->error = $this->db->error();
1571 return -1;
1572 }
1573 } else {
1574 return -2;
1575 }
1576 }
1577
1578 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1584 public function fetch_delivery_methods()
1585 {
1586 // phpcs:enable
1587 global $langs;
1588 $this->meths = array();
1589
1590 $sql = "SELECT em.rowid, em.code, em.libelle";
1591 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1592 $sql .= " WHERE em.active = 1";
1593 $sql .= " ORDER BY em.libelle ASC";
1594
1595 $resql = $this->db->query($sql);
1596 if ($resql) {
1597 while ($obj = $this->db->fetch_object($resql)) {
1598 $label = $langs->trans('ReceptionMethod'.$obj->code);
1599 $this->meths[$obj->rowid] = ($label != 'ReceptionMethod'.$obj->code ? $label : $obj->libelle);
1600 }
1601 }
1602 }
1603
1604 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1611 public function list_delivery_methods($id = 0)
1612 {
1613 // phpcs:enable
1614 global $langs;
1615
1616 $this->listmeths = array();
1617 $i = 0;
1618
1619 $sql = "SELECT em.rowid, em.code, em.libelle, em.description, em.tracking, em.active";
1620 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1621 if (!empty($id)) {
1622 $sql .= " WHERE em.rowid = ".((int) $id);
1623 }
1624
1625 $resql = $this->db->query($sql);
1626 if ($resql) {
1627 while ($obj = $this->db->fetch_object($resql)) {
1628 $this->listmeths[$i]['rowid'] = $obj->rowid;
1629 $this->listmeths[$i]['code'] = $obj->code;
1630 $label = $langs->trans('ReceptionMethod'.$obj->code);
1631 $this->listmeths[$i]['libelle'] = ($label != 'ReceptionMethod'.$obj->code ? $label : $obj->libelle);
1632 $this->listmeths[$i]['description'] = $obj->description;
1633 $this->listmeths[$i]['tracking'] = $obj->tracking;
1634 $this->listmeths[$i]['active'] = $obj->active;
1635 $i++;
1636 }
1637 }
1638 }
1639
1646 public function getUrlTrackingStatus($value = '')
1647 {
1648 if (!empty($this->shipping_method_id)) {
1649 $sql = "SELECT em.code, em.tracking";
1650 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1651 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
1652
1653 $resql = $this->db->query($sql);
1654 if ($resql) {
1655 if ($obj = $this->db->fetch_object($resql)) {
1656 $tracking = $obj->tracking;
1657 }
1658 }
1659 }
1660
1661 if (!empty($tracking) && !empty($value)) {
1662 $url = str_replace('{TRACKID}', $value, $tracking);
1663 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
1664 } else {
1665 $this->tracking_url = $value;
1666 }
1667 }
1668
1674 public function setClosed()
1675 {
1676 global $conf, $langs, $user;
1677
1678 $error = 0;
1679
1680 // Protection. This avoid to move stock later when we should not
1681 if ($this->statut == Reception::STATUS_CLOSED) {
1682 dol_syslog(get_class($this)."::setClosed already in closed status", LOG_WARNING);
1683 return 0;
1684 }
1685
1686 $this->db->begin();
1687
1688 $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET fk_statut = '.self::STATUS_CLOSED;
1689 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
1690
1691 $resql = $this->db->query($sql);
1692 if ($resql) {
1693 // Set order billed if 100% of order is received (qty in reception lines match qty in order lines)
1694 if ($this->origin == 'order_supplier' && $this->origin_id > 0) {
1695 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
1696
1697 $order = new CommandeFournisseur($this->db);
1698 $order->fetch($this->origin_id);
1699
1700 $order->loadReceptions(self::STATUS_CLOSED); // Fill $order->receptions = array(orderlineid => qty)
1701
1702 $receptions_match_order = 1;
1703 foreach ($order->lines as $line) {
1704 $lineid = $line->id;
1705 $qty = $line->qty;
1706 if (($line->product_type == 0 || getDolGlobalInt('STOCK_SUPPORTS_SERVICES')) && $order->receptions[$lineid] < $qty) {
1707 $receptions_match_order = 0;
1708 $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';
1709 dol_syslog($text);
1710 break;
1711 }
1712 }
1713 if ($receptions_match_order) {
1714 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');
1715 $order->Livraison($user, dol_now(), 'tot', 'Reception '.$this->ref);
1716 }
1717 }
1718
1719 $this->statut = self::STATUS_CLOSED;
1720 $this->status = self::STATUS_CLOSED;
1721
1722 // If stock increment is done on closing
1723 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
1724 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1725
1726 $langs->load("agenda");
1727
1728 // Loop on each product line to add a stock movement
1729 // TODO possibilite de receptionner a partir d'une propale ou autre origine ?
1730 $sql = "SELECT cd.fk_product, cd.subprice,";
1731 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
1732 $sql .= " ed.eatby, ed.sellby, ed.batch,";
1733 $sql .= " ed.cost_price";
1734 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1735 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1736 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1737 $sql .= " AND cd.rowid = ed.fk_elementdet";
1738
1739 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
1740 $resql = $this->db->query($sql);
1741
1742 if ($resql) {
1743 $cpt = $this->db->num_rows($resql);
1744 for ($i = 0; $i < $cpt; $i++) {
1745 $obj = $this->db->fetch_object($resql);
1746
1747 $qty = $obj->qty;
1748
1749 if ($qty <= 0) {
1750 continue;
1751 }
1752
1753 dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid);
1754
1755 $mouvS = new MouvementStock($this->db);
1756 $mouvS->origin = &$this;
1757 $mouvS->setOrigin($this->element, $this->id);
1758
1759 if (empty($obj->batch)) {
1760 // line without batch detail
1761
1762 // 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
1763 $inventorycode = '';
1764 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionClassifyClosedInDolibarr", $this->ref), '', '', '', '', 0, $inventorycode);
1765 if ($result < 0) {
1766 $this->error = $mouvS->error;
1767 $this->errors = $mouvS->errors;
1768 $error++;
1769 break;
1770 }
1771 } else {
1772 // line with batch detail
1773
1774 // 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
1775 $inventorycode = '';
1776 $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);
1777
1778 if ($result < 0) {
1779 $this->error = $mouvS->error;
1780 $this->errors = $mouvS->errors;
1781 $error++;
1782 break;
1783 }
1784 }
1785 }
1786 } else {
1787 $this->error = $this->db->lasterror();
1788 $error++;
1789 }
1790 }
1791
1792 // Call trigger
1793 if (!$error) {
1794 $result = $this->call_trigger('RECEPTION_CLOSED', $user);
1795 if ($result < 0) {
1796 $error++;
1797 }
1798 }
1799 } else {
1800 dol_print_error($this->db);
1801 $error++;
1802 }
1803
1804 if (!$error) {
1805 $this->db->commit();
1806 return 1;
1807 } else {
1808 $this->statut = self::STATUS_VALIDATED;
1809 $this->status = self::STATUS_VALIDATED;
1810 $this->db->rollback();
1811 return -1;
1812 }
1813 }
1814
1820 public function setBilled()
1821 {
1822 global $user;
1823 $error = 0;
1824
1825 $this->db->begin();
1826
1827 if ($this->statut == Reception::STATUS_VALIDATED) {
1828 // do not close if already closed
1829 $this->setClosed();
1830 }
1831
1832 $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET billed=1';
1833 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
1834
1835 $resql = $this->db->query($sql);
1836 if ($resql) {
1837 $this->billed = 1;
1838
1839 // Call trigger
1840 $result = $this->call_trigger('RECEPTION_BILLED', $user);
1841 if ($result < 0) {
1842 $this->billed = 0;
1843 $error++;
1844 }
1845 } else {
1846 $error++;
1847 $this->errors[] = $this->db->lasterror;
1848 }
1849
1850 if (empty($error)) {
1851 $this->db->commit();
1852 return 1;
1853 } else {
1854 $this->db->rollback();
1855 return -1;
1856 }
1857 }
1858
1864 public function reOpen()
1865 {
1866 global $conf, $langs, $user;
1867
1868 $error = 0;
1869
1870 $this->db->begin();
1871
1872 $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET fk_statut=1, billed=0';
1873 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
1874
1875 $resql = $this->db->query($sql);
1876 if ($resql) {
1877 $this->statut = self::STATUS_VALIDATED;
1878 $this->status = self::STATUS_VALIDATED;
1879 $this->billed = 0;
1880
1881 // If stock increment is done on closing
1882 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
1883 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1884 $numref = $this->ref;
1885 $langs->load("agenda");
1886
1887 // Loop on each product line to add a stock movement
1888 // TODO possibilite de receptionner a partir d'une propale ou autre origine
1889 $sql = "SELECT ed.fk_product, cd.subprice,";
1890 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
1891 $sql .= " ed.eatby, ed.sellby, ed.batch,";
1892 $sql .= " ed.cost_price";
1893 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1894 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1895 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1896 $sql .= " AND cd.rowid = ed.fk_elementdet";
1897
1898 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
1899 $resql = $this->db->query($sql);
1900 if ($resql) {
1901 $cpt = $this->db->num_rows($resql);
1902 for ($i = 0; $i < $cpt; $i++) {
1903 $obj = $this->db->fetch_object($resql);
1904
1905 $qty = $obj->qty;
1906
1907 if ($qty <= 0) {
1908 continue;
1909 }
1910 dol_syslog(get_class($this)."::reopen reception movement index ".$i." ed.rowid=".$obj->rowid);
1911
1912 //var_dump($this->lines[$i]);
1913 $mouvS = new MouvementStock($this->db);
1914 $mouvS->origin = &$this;
1915 $mouvS->setOrigin($this->element, $this->id);
1916
1917 if (empty($obj->batch)) {
1918 // line without batch detail
1919
1920 // 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
1921 $inventorycode = '';
1922 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionUnClassifyCloseddInDolibarr", $numref), '', '', '', '', 0, $inventorycode);
1923
1924 if ($result < 0) {
1925 $this->error = $mouvS->error;
1926 $this->errors = $mouvS->errors;
1927 $error++;
1928 break;
1929 }
1930 } else {
1931 // line with batch detail
1932
1933 // 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
1934 $inventorycode = '';
1935 $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);
1936 if ($result < 0) {
1937 $this->error = $mouvS->error;
1938 $this->errors = $mouvS->errors;
1939 $error++;
1940 break;
1941 }
1942 }
1943 }
1944 } else {
1945 $this->error = $this->db->lasterror();
1946 $error++;
1947 }
1948 }
1949
1950 if (!$error) {
1951 // Call trigger
1952 $result = $this->call_trigger('RECEPTION_REOPEN', $user);
1953 if ($result < 0) {
1954 $error++;
1955 }
1956 }
1957
1958 if (!$error && $this->origin == 'order_supplier') {
1959 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
1960
1961 $commande = new CommandeFournisseur($this->db);
1962 $commande->fetch($this->origin_id);
1963 $result = $commande->setStatus($user, 4);
1964 if ($result < 0) {
1965 $error++;
1966 $this->error = $commande->error;
1967 $this->errors = $commande->errors;
1968 }
1969 }
1970 } else {
1971 $error++;
1972 $this->errors[] = $this->db->lasterror();
1973 }
1974
1975 if (!$error) {
1976 $this->db->commit();
1977 return 1;
1978 } else {
1979 $this->db->rollback();
1980 return -1;
1981 }
1982 }
1983
1990 public function setDraft($user)
1991 {
1992 // phpcs:enable
1993 global $conf, $langs;
1994
1995 $error = 0;
1996
1997 // Protection
1998 if ($this->statut <= self::STATUS_DRAFT) {
1999 return 0;
2000 }
2001
2002 if (!((!getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'creer'))
2003 || (getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'reception_advance', 'validate')))) {
2004 $this->error = 'Permission denied';
2005 return -1;
2006 }
2007
2008 $this->db->begin();
2009
2010 $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
2011 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2012 $sql .= " WHERE rowid = ".((int) $this->id);
2013
2014 dol_syslog(__METHOD__, LOG_DEBUG);
2015 if ($this->db->query($sql)) {
2016 // If stock increment is done on closing
2017 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION')) {
2018 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2019
2020 $langs->load("agenda");
2021
2022 // Loop on each product line to add a stock movement
2023 // TODO possibilite de receptionner a partir d'une propale ou autre origine
2024 $sql = "SELECT cd.fk_product, cd.subprice,";
2025 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2026 $sql .= " ed.eatby, ed.sellby, ed.batch,";
2027 $sql .= " ed.cost_price";
2028 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
2029 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
2030 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
2031 $sql .= " AND cd.rowid = ed.fk_elementdet";
2032
2033 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2034 $resql = $this->db->query($sql);
2035 if ($resql) {
2036 $cpt = $this->db->num_rows($resql);
2037 for ($i = 0; $i < $cpt; $i++) {
2038 $obj = $this->db->fetch_object($resql);
2039
2040 $qty = $obj->qty;
2041
2042 if ($qty <= 0) {
2043 continue;
2044 }
2045
2046 dol_syslog(get_class($this)."::reopen reception movement index ".$i." ed.rowid=".$obj->rowid);
2047
2048 //var_dump($this->lines[$i]);
2049 $mouvS = new MouvementStock($this->db);
2050 $mouvS->origin = &$this;
2051 $mouvS->setOrigin($this->element, $this->id);
2052
2053 if (empty($obj->batch)) {
2054 // line without batch detail
2055
2056 // 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
2057 $inventorycode = '';
2058 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionBackToDraftInDolibarr", $this->ref), '', '', '', '', 0, $inventorycode);
2059 if ($result < 0) {
2060 $this->error = $mouvS->error;
2061 $this->errors = $mouvS->errors;
2062 $error++;
2063 break;
2064 }
2065 } else {
2066 // line with batch detail
2067
2068 // 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
2069 $inventorycode = '';
2070 $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);
2071 if ($result < 0) {
2072 $this->error = $mouvS->error;
2073 $this->errors = $mouvS->errors;
2074 $error++;
2075 break;
2076 }
2077 }
2078 }
2079 } else {
2080 $this->error = $this->db->lasterror();
2081 $error++;
2082 }
2083 }
2084
2085 if (!$error) {
2086 // Call trigger
2087 $result = $this->call_trigger('RECEPTION_UNVALIDATE', $user);
2088 if ($result < 0) {
2089 $error++;
2090 }
2091 }
2092 if ($this->origin == 'order_supplier') {
2093 if (!empty($this->origin) && $this->origin_id > 0) {
2094 $this->fetch_origin();
2095 if ($this->origin_object->statut == 4) { // If order source of reception is "partially received"
2096 // Check if there is no more reception validated.
2097 $this->origin_object->fetchObjectLinked();
2098 $setStatut = 1;
2099 if (!empty($this->origin_object->linkedObjects['reception'])) {
2100 foreach ($this->origin_object->linkedObjects['reception'] as $rcption) {
2101 if ($rcption->statut > 0) {
2102 $setStatut = 0;
2103 break;
2104 }
2105 }
2106 //var_dump($this->$origin->receptions);exit;
2107 if ($setStatut) {
2108 $this->origin_object->setStatut(3); // ordered
2109 }
2110 }
2111 }
2112 }
2113 }
2114
2115 if (!$error) {
2116 $this->statut = self::STATUS_DRAFT;
2117 $this->status = self::STATUS_DRAFT;
2118 $this->db->commit();
2119 return 1;
2120 } else {
2121 $this->db->rollback();
2122 return -1;
2123 }
2124 } else {
2125 $this->error = $this->db->error();
2126 $this->db->rollback();
2127 return -1;
2128 }
2129 }
2130
2141 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
2142 {
2143 global $conf, $langs;
2144
2145 $langs->load("receptions");
2146
2147 if (!dol_strlen($modele)) {
2148 $modele = 'squille';
2149
2150 if ($this->model_pdf) {
2151 $modele = $this->model_pdf;
2152 } elseif (getDolGlobalString('RECEPTION_ADDON_PDF')) {
2153 $modele = getDolGlobalString('RECEPTION_ADDON_PDF');
2154 }
2155 }
2156
2157 $modelpath = "core/modules/reception/doc/";
2158
2159 $this->fetch_origin();
2160
2161 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
2162 }
2163
2172 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2173 {
2174 $tables = array('reception');
2175
2176 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2177 }
2178
2187 public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
2188 {
2189 $tables = array(
2190 'receptiondet_batch'
2191 );
2192
2193 return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
2194 }
2195}
$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...
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
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='fk_statut')
Set status of an object.
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.
fetch_origin()
Read linked origin object.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
call_trigger($triggerName, $user)
Call trigger based on this instance.
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.
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.
getNomUrl($withpicto=0, $option=0, $max=0, $short=0, $notooltip=0)
Return clickable link of object (with eventually picto)
update($user=null, $notrigger=0)
Update database.
setClosed()
Classify the reception as closed (this records also the stock movement)
getNextNumRef($soc)
Return next contract ref.
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.
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.
initAsSpecimen()
Initialise an instance with random values.
fetch_lines()
Load lines.
create($user, $notrigger=0)
Create reception en base.
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 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
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:63
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_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_now($mode='auto')
Return date for now.
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...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
$conf db user
Active Directory does not allow anonymous connections.
Definition repair.php:162