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 // Lines of draft receptions are not received yet
819 $draft_lines = array();
820 $sql = "SELECT rb.rowid FROM ".MAIN_DB_PREFIX."receptiondet_batch as rb";
821 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."reception as r ON r.rowid = rb.fk_reception";
822 $sql .= " WHERE rb.fk_element = ".((int) $this->origin_id);
823 $sql .= " AND r.fk_statut = ".self::STATUS_DRAFT;
824 $resql = $this->db->query($sql);
825 if (!$resql) {
826 $this->error = $this->db->lasterror();
827 return -1;
828 }
829 while ($obj = $this->db->fetch_object($resql)) {
830 $draft_lines[(int) $obj->rowid] = (int) $obj->rowid;
831 }
832 $this->db->free($resql);
833
834 // build array with quantity received by product in all supplier orders (origin)
835 foreach ($supplierorderdispatch->lines as $dispatch_line) {
836 if (isset($draft_lines[(int) $dispatch_line->id])) {
837 continue;
838 }
839 if (array_key_exists($dispatch_line->fk_product, $qty_received)) {
840 $qty_received[$dispatch_line->fk_product] += $dispatch_line->qty;
841 } else {
842 $qty_received[$dispatch_line->fk_product] = $dispatch_line->qty;
843 }
844 }
845
846 // qty wished in origin (purchase order, ...)
847 foreach ($this->origin_object->lines as $origin_line) {
848 // exclude lines not qualified for reception
849 if ((!getDolGlobalInt('STOCK_SUPPORTS_SERVICES') && $origin_line->product_type > 0) || $origin_line->product_type > 1) {
850 continue;
851 }
852 if (array_key_exists($origin_line->fk_product, $qty_wished)) {
853 $qty_wished[$origin_line->fk_product] += $origin_line->qty;
854 } else {
855 $qty_wished[$origin_line->fk_product] = $origin_line->qty;
856 }
857 }
858
859 // compare array
860 $diff_array = array_diff_assoc($qty_received, $qty_wished); // Warning: $diff_array is done only on common keys.
861 $keys_in_wished_not_in_received = array_diff(array_keys($qty_wished), array_keys($qty_received));
862 $keys_in_received_not_in_wished = array_diff(array_keys($qty_received), array_keys($qty_wished));
863
864 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
866 } elseif (getDolGlobalInt('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
867 // set totally received if more products received than ordered
868 $close = 0;
869
870 if (count($diff_array) > 0) {
871 // there are some difference between the two arrays
872 // scan the array of results
873 foreach ($diff_array as $key => $value) {
874 // if the quantity delivered is greater or equal to ordered quantity @phan-suppress-next-line PhanTypeInvalidDimOffset
875 if ($qty_received[$key] >= $qty_wished[$key]) {
876 $close++;
877 }
878 }
879 }
880
881 if ($close == count($diff_array)) {
882 // all the products are received equal or more than the ordered quantity
884 }
885 }
886 }
887 }
888
889 return $status;
890 }
891
908 public function addline($entrepot_id, $id, $qty, $array_options = [], $comment = '', $eatby = null, $sellby = null, $batch = '', $cost_price = 0)
909 {
910 global $conf, $langs, $user;
911
912 $num = count($this->lines);
913 $line = new CommandeFournisseurDispatch($this->db);
914
915 $line->fk_entrepot = $entrepot_id;
916 $line->fk_commandefourndet = $id;
917 $line->qty = $qty;
918
919 $supplierorderline = new CommandeFournisseurLigne($this->db);
920 $result = $supplierorderline->fetch($id);
921 if ($result <= 0) {
922 $this->setErrorsFromObject($supplierorderline);
923 return -1;
924 }
925
926 $fk_product = 0;
927 if (isModEnabled('stock') && !empty($supplierorderline->fk_product)) {
928 $fk_product = $supplierorderline->fk_product;
929
930 if (!($entrepot_id > 0) && !getDolGlobalInt('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_RECEPTIONS')) {
931 $langs->load("errors");
932 $this->error = $langs->trans("ErrorWarehouseRequiredIntoReceptionLine");
933 return -1;
934 }
935 }
936
937 // Check batch is set
938 $product = new Product($this->db);
939 $product->fetch($fk_product);
940 if (isModEnabled('productbatch')) {
941 $langs->load("errors");
942 if (!empty($product->status_batch) && empty($batch)) {
943 $this->error = $langs->trans('ErrorProductNeedBatchNumber', $product->ref);
944 return -1;
945 } elseif (empty($product->status_batch) && !empty($batch)) {
946 $this->error = $langs->trans('ErrorProductDoesNotNeedBatchNumber', $product->ref);
947 return -1;
948 }
949
950 // check sell-by / eat-by date is mandatory
951 $errorMsgArr = Productlot::checkSellOrEatByMandatoryFromProductAndDates($product, $sellby, $eatby);
952 if (!empty($errorMsgArr)) {
953 $errorMessage = '<b>' . $product->ref . '</b> : ';
954 $errorMessage .= '<ul>';
955 foreach ($errorMsgArr as $errorMsg) {
956 $errorMessage .= '<li>' . $errorMsg . '</li>';
957 }
958 $errorMessage .= '</ul>';
959 $this->error = $errorMessage;
960 return -1;
961 }
962 }
963 unset($product);
964
965 // extrafields
966 $line->array_options = $supplierorderline->array_options;
967 if (!getDolGlobalInt('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) {
968 foreach ($array_options as $key => $value) {
969 $line->array_options[$key] = $value;
970 }
971 }
972
973 $line->fk_product = $fk_product;
974 $line->fk_commande = $supplierorderline->fk_commande;
975 $line->fk_user = $user->id;
976 $line->comment = $comment;
977 $line->batch = $batch;
978 $line->eatby = $eatby;
979 $line->sellby = $sellby;
980 $line->status = 1;
981 $line->cost_price = $cost_price;
982 $line->fk_reception = $this->id;
983
984 $this->lines[$num] = $line;
985
986 return $num;
987 }
988
989
997 public function update($user = null, $notrigger = 0)
998 {
999 global $conf;
1000 $error = 0;
1001
1002 // Clean parameters
1003
1004 if (isset($this->ref)) {
1005 $this->ref = trim($this->ref);
1006 }
1007 if (isset($this->entity)) {
1008 $this->entity = (int) $this->entity;
1009 }
1010 if (isset($this->ref_supplier)) {
1011 $this->ref_supplier = trim($this->ref_supplier);
1012 }
1013 if (isset($this->socid)) {
1014 $this->socid = (int) trim((string) $this->socid);
1015 }
1016 if (isset($this->fk_user_author)) {
1017 $this->fk_user_author = (int) $this->fk_user_author;
1018 }
1019 if (isset($this->fk_user_valid)) {
1020 $this->fk_user_valid = (int) $this->fk_user_valid;
1021 }
1022 if (isset($this->shipping_method_id)) {
1023 $this->shipping_method_id = (int) $this->shipping_method_id;
1024 }
1025 if (isset($this->tracking_number)) {
1026 $this->tracking_number = trim($this->tracking_number);
1027 }
1028 if (isset($this->statut)) {
1029 $this->statut = (int) $this->statut;
1030 }
1031 if (isset($this->trueDepth)) {
1032 $this->trueDepth = price2num($this->trueDepth);
1033 }
1034 if (isset($this->trueWidth)) {
1035 $this->trueWidth = price2num($this->trueWidth);
1036 }
1037 if (isset($this->trueHeight)) {
1038 $this->trueHeight = price2num($this->trueHeight);
1039 }
1040 $this->size_units = (int) $this->size_units;
1041
1042 if (isset($this->trueWeight)) {
1043 $this->weight = price2num($this->trueWeight);
1044 }
1045 $this->weight_units = (int) $this->weight_units;
1046
1047 if (isset($this->note_private)) {
1048 $this->note_private = trim($this->note_private);
1049 }
1050 if (isset($this->note_public)) {
1051 $this->note_public = trim($this->note_public);
1052 }
1053 if (isset($this->model_pdf)) {
1054 $this->model_pdf = trim($this->model_pdf);
1055 }
1056
1057
1058 // Check parameters
1059 // Put here code to add control on parameters values
1060
1061 // Update request
1062 $sql = "UPDATE ".MAIN_DB_PREFIX."reception SET";
1063
1064 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1065 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1066 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1067 $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1068 $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
1069 $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
1070 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
1071 $sql .= " date_reception=".(dol_strlen($this->date_reception) != 0 ? "'".$this->db->idate($this->date_reception)."'" : 'null').",";
1072 $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
1073 $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
1074 $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
1075 $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1076 $sql .= " height=".(($this->trueHeight != '') ? (float) $this->trueHeight : "null").",";
1077 $sql .= " width=".(($this->trueWidth != '') ? (float) $this->trueWidth : "null").",";
1078 $sql .= " size_units=".((int) $this->size_units).",";
1079 $sql .= " size=".(($this->trueDepth != '') ? (float) $this->trueDepth : "null").",";
1080 $sql .= " weight_units=".((int) $this->weight_units).",";
1081 $sql .= " weight=".(($this->trueWeight != '') ? (float) $this->trueWeight : "null").",";
1082 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1083 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1084 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1085 $sql .= " entity = ".((int) $conf->entity);
1086 $sql .= " WHERE rowid=".((int) $this->id);
1087
1088 $this->db->begin();
1089
1090 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1091 $resql = $this->db->query($sql);
1092 if (!$resql) {
1093 $error++;
1094 $this->errors[] = "Error ".$this->db->lasterror();
1095 }
1096
1097 if (!$error) {
1098 if (!$notrigger) {
1099 // Call trigger
1100 $result = $this->call_trigger('RECEPTION_MODIFY', $user);
1101 if ($result < 0) {
1102 $error++;
1103 }
1104 // End call triggers
1105 }
1106 }
1107
1108 // Commit or rollback
1109 if ($error) {
1110 foreach ($this->errors as $errmsg) {
1111 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1112 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1113 }
1114 $this->db->rollback();
1115 return -1 * $error;
1116 } else {
1117 $this->db->commit();
1118 return 1;
1119 }
1120 }
1121
1128 public function delete(User $user)
1129 {
1130 global $conf, $langs, $user;
1131 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1132
1133 $error = 0;
1134 $this->error = '';
1135
1136
1137 $this->db->begin();
1138
1139 // Stock control
1140 if (isModEnabled('stock') && ((getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION') && $this->status > Reception::STATUS_DRAFT)
1141 || (getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE') && $this->status == Reception::STATUS_CLOSED))
1142 ) {
1143 require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1144
1145 $langs->load("agenda");
1146
1147 // Loop on each product line to add a stock movement
1148 $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.eatby, ed.sellby, ed.batch, ed.rowid as receptiondet_batch_id";
1149 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1150 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1151 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1152 $sql .= " AND cd.rowid = ed.fk_elementdet";
1153
1154 dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1155 $resql = $this->db->query($sql);
1156 if ($resql) {
1157 $cpt = $this->db->num_rows($resql);
1158 for ($i = 0; $i < $cpt; $i++) {
1159 dol_syslog(get_class($this)."::delete movement index ".$i);
1160 $obj = $this->db->fetch_object($resql);
1161
1162 $mouvS = new MouvementStock($this->db);
1163 // we do not log origin because it will be deleted
1164 $mouvS->origin = null;
1165
1166 $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
1167 if ($result < 0) {
1168 $error++;
1169 $this->error = $mouvS->error;
1170 $this->errors = $mouvS->errors;
1171 }
1172 }
1173 } else {
1174 $error++;
1175 $this->errors[] = "Error ".$this->db->lasterror();
1176 }
1177 }
1178
1179 if (!$error) {
1180 $main = MAIN_DB_PREFIX.'receptiondet_batch';
1181 $ef = $main."_extrafields";
1182
1183 $sqlef = "DELETE FROM ".$ef." WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_reception = ".((int) $this->id).")";
1184
1185 $sql = "DELETE FROM ".MAIN_DB_PREFIX."receptiondet_batch";
1186 $sql .= " WHERE fk_reception = ".((int) $this->id);
1187
1188 if ($this->db->query($sqlef) && $this->db->query($sql)) {
1189 // Delete linked object
1190 $res = $this->deleteObjectLinked();
1191 if ($res < 0) {
1192 $error++;
1193 }
1194
1195 if (!$error) {
1196 $sql = "DELETE FROM ".MAIN_DB_PREFIX."reception";
1197 $sql .= " WHERE rowid = ".((int) $this->id);
1198
1199 if ($this->db->query($sql)) {
1200 // Call trigger
1201 $result = $this->call_trigger('RECEPTION_DELETE', $user);
1202 if ($result < 0) {
1203 $error++;
1204 }
1205 // End call triggers
1206
1207 if (!empty($this->origin) && $this->origin_id > 0) {
1208 $this->fetch_origin();
1210 '@phan-var-force CommandeFournisseur $origin_object';
1211 if ($origin_object->statut == 4) { // If order source of reception is "partially received"
1212 // Check if there is no more reception. If not, we can move back status of order to "validated" instead of "reception in progress"
1213 $origin_object->loadReceptions();
1214 //var_dump($this->$origin->receptions);exit;
1215 if (count($origin_object->receptions) <= 0) {
1216 $origin_object->setStatut(3); // ordered
1217 }
1218 }
1219 }
1220
1221 if (!$error) {
1222 $this->db->commit();
1223
1224 // We delete PDFs
1225 $ref = dol_sanitizeFileName($this->ref);
1226 if (!empty($conf->reception->dir_output)) {
1227 $dir = $conf->reception->dir_output.'/'.$ref;
1228 $file = $dir.'/'.$ref.'.pdf';
1229 if (file_exists($file)) {
1230 if (!dol_delete_file($file)) {
1231 return 0;
1232 }
1233 }
1234 if (file_exists($dir)) {
1235 if (!dol_delete_dir_recursive($dir)) {
1236 $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1237 return 0;
1238 }
1239 }
1240 }
1241
1242 return 1;
1243 } else {
1244 $this->db->rollback();
1245 return -1;
1246 }
1247 } else {
1248 $this->error = $this->db->lasterror()." - sql=$sql";
1249 $this->db->rollback();
1250 return -3;
1251 }
1252 } else {
1253 $this->error = $this->db->lasterror()." - sql=$sql";
1254 $this->db->rollback();
1255 return -2;
1256 }
1257 } else {
1258 $this->error = $this->db->lasterror()." - sql=$sql";
1259 $this->db->rollback();
1260 return -1;
1261 }
1262 } else {
1263 $this->db->rollback();
1264 return -1;
1265 }
1266 }
1267
1268 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1274 public function fetch_lines()
1275 {
1276 // phpcs:enable
1277 $this->lines = array();
1278
1279 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
1280
1281 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."receptiondet_batch";
1282 $sql .= " WHERE fk_reception = ".((int) $this->id);
1283
1284 $resql = $this->db->query($sql);
1285
1286 if (!empty($resql)) {
1287 while ($obj = $this->db->fetch_object($resql)) {
1288 $line = new CommandeFournisseurDispatch($this->db);
1289
1290 $line->fetch($obj->rowid);
1291
1292 // TODO Remove or keep this ?
1293 $line->fetch_product();
1294
1295 $sql_commfourndet = 'SELECT qty, ref, label, description, tva_tx, vat_src_code, localtax1_tx, localtax2_tx, subprice, multicurrency_subprice, remise_percent, total_ht, total_ttc, total_tva, date_start, date_end, product_type';
1296 $sql_commfourndet .= ' FROM '.MAIN_DB_PREFIX.'commande_fournisseurdet';
1297 $sql_commfourndet .= ' WHERE rowid = '.((int) $line->fk_commandefourndet);
1298 $sql_commfourndet .= ' ORDER BY rang';
1299
1300 $resql_commfourndet = $this->db->query($sql_commfourndet);
1301 if (!empty($resql_commfourndet)) {
1302 $obj = $this->db->fetch_object($resql_commfourndet);
1303 $line->qty_asked = $obj->qty;
1304 $line->description = $obj->description;
1305 $line->desc = $obj->description;
1306 $line->tva_tx = $obj->tva_tx;
1307 $line->vat_src_code = $obj->vat_src_code;
1308 $line->localtax1_tx = $obj->localtax1_tx;
1309 $line->localtax2_tx = $obj->localtax2_tx;
1310 $line->subprice = $obj->subprice;
1311 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1312 $line->remise_percent = $obj->remise_percent;
1313 $line->label = !empty($obj->label) ? $obj->label : (is_object($line->product) ? $line->product->label : '');
1314 $line->ref_supplier = $obj->ref;
1315 $line->total_ht = $obj->total_ht;
1316 $line->total_ttc = $obj->total_ttc;
1317 $line->total_tva = $obj->total_tva;
1318 $line->date_start = $this->db->jdate($obj->date_start);
1319 $line->date_end = $this->db->jdate($obj->date_end);
1320 $line->product_type = $obj->product_type;
1321 } else {
1322 $line->qty_asked = 0;
1323 $line->description = '';
1324 $line->desc = '';
1325 $line->label = $obj->label;
1326 }
1327
1328 $pu_ht = ($line->subprice * $line->qty) * (100 - $line->remise_percent) / 100;
1329 $tva = $pu_ht * $line->tva_tx / 100;
1330 $this->total_ht += $pu_ht;
1331 $this->total_tva += $pu_ht * $line->tva_tx / 100;
1332
1333 $this->total_ttc += $pu_ht + $tva;
1334
1335 if (isModEnabled('productbatch') && !empty($line->batch)) {
1336 $detail_batch = new stdClass();
1337 $detail_batch->eatby = $line->eatby;
1338 $detail_batch->sellby = $line->sellby;
1339 $detail_batch->batch = $line->batch;
1340 $detail_batch->qty = $line->qty;
1341
1342 $line->detail_batch[] = $detail_batch;
1343 }
1344
1345 $this->lines[] = $line;
1346 }
1347
1348 return 1;
1349 } else {
1350 return -1;
1351 }
1352 }
1353
1364 public function getNomUrl($withpicto = 0, $option = 0, $max = 0, $short = 0, $notooltip = 0)
1365 {
1366 global $langs, $hookmanager;
1367
1368 $result = '';
1369 $label = img_picto('', $this->picto).' <u>'.$langs->trans("Reception").'</u>';
1370 $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1371 $label .= '<br><b>'.$langs->trans('RefSupplier').':</b> '.($this->ref_supplier ? $this->ref_supplier : '');
1372
1373 $url = DOL_URL_ROOT.'/reception/card.php?id='.$this->id;
1374
1375 if ($short) {
1376 return $url;
1377 }
1378
1379 $linkclose = '';
1380 if (empty($notooltip)) {
1381 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1382 $label = $langs->trans("Reception");
1383 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1384 }
1385 $linkclose .= ' title="'.dolPrintHTMLForAttribute($label).'"';
1386 $linkclose .= ' class="classfortooltip"';
1387 }
1388
1389 $linkstart = '<a href="'.$url.'"';
1390 $linkstart .= $linkclose.'>';
1391 $linkend = '</a>';
1392
1393 $result .= $linkstart;
1394 if ($withpicto) {
1395 $result .= img_object(($notooltip ? '' : $label), $this->picto, '', 0, 0, $notooltip ? 0 : 1);
1396 }
1397 if ($withpicto != 2) {
1398 $result .= $this->ref;
1399 }
1400
1401 $result .= $linkend;
1402
1403 global $action;
1404 $hookmanager->initHooks(array($this->element . 'dao'));
1405 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1406 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1407 if ($reshook > 0) {
1408 $result = $hookmanager->resPrint;
1409 } else {
1410 $result .= $hookmanager->resPrint;
1411 }
1412 return $result;
1413 }
1414
1421 public function getLibStatut($mode = 0)
1422 {
1423 return $this->LibStatut($this->statut, $mode);
1424 }
1425
1426 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1434 public function LibStatut($status, $mode)
1435 {
1436 // phpcs:enable
1437 global $langs;
1438
1439 // List of long language codes for status
1440 $this->labelStatus[-1] = 'StatusReceptionCanceled';
1441 $this->labelStatus[0] = 'StatusReceptionDraft';
1442 // product to receive if stock increase is on close or already received if stock increase is on validation
1443 $this->labelStatus[1] = 'StatusReceptionValidated';
1444 if (getDolGlobalInt("STOCK_CALCULATE_ON_RECEPTION")) {
1445 $this->labelStatus[1] = 'StatusReceptionValidatedReceived';
1446 }
1447 if (getDolGlobalInt("STOCK_CALCULATE_ON_RECEPTION_CLOSE")) {
1448 $this->labelStatus[1] = 'StatusReceptionValidatedToReceive';
1449 }
1450 $this->labelStatus[2] = 'StatusReceptionProcessed';
1451
1452 // List of short language codes for status
1453 $this->labelStatusShort[-1] = 'StatusReceptionCanceledShort';
1454 $this->labelStatusShort[0] = 'StatusReceptionDraftShort';
1455 $this->labelStatusShort[1] = 'StatusReceptionValidatedShort';
1456 $this->labelStatusShort[2] = 'StatusReceptionProcessedShort';
1457
1458 $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
1459 $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
1460
1461 $statusType = 'status'.$status;
1462 if ($status == self::STATUS_VALIDATED) {
1463 $statusType = 'status4';
1464 }
1465 if ($status == self::STATUS_CLOSED) {
1466 $statusType = 'status6';
1467 }
1468
1469 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
1470 }
1471
1479 public function getKanbanView($option = '', $arraydata = null)
1480 {
1481 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1482
1483 $return = '<div class="box-flex-item box-flex-grow-zero">';
1484 $return .= '<div class="info-box info-box-sm">';
1485 $return .= '<div class="info-box-icon bg-infobox-action">';
1486 $return .= img_picto('', 'order');
1487 $return .= '</div>';
1488 $return .= '<div class="info-box-content">';
1489 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
1490 if ($selected >= 0) {
1491 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1492 }
1493 if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
1494 $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
1495 }
1496 /*if (property_exists($this, 'total_ht')) {
1497 $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'</div>';
1498 }*/
1499 if (method_exists($this, 'getLibStatut')) {
1500 $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1501 }
1502 $return .= '</div>';
1503 $return .= '</div>';
1504 $return .= '</div>';
1505
1506 return $return;
1507 }
1508
1516 public function initAsSpecimen()
1517 {
1518 global $langs;
1519
1520 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
1521 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
1522 $now = dol_now();
1523
1524 dol_syslog(get_class($this)."::initAsSpecimen");
1525
1526 $order = new CommandeFournisseur($this->db);
1527 $order->initAsSpecimen();
1528
1529 // Initialise parameters
1530 $this->id = 0;
1531 $this->ref = 'SPECIMEN';
1532 $this->specimen = 1;
1533 $this->statut = 1;
1534 $this->status = 1;
1535 $this->date = $now;
1536 $this->date_creation = $now;
1537 $this->date_valid = $now;
1538 $this->date_delivery = $now;
1539 $this->date_reception = $now + 24 * 3600;
1540
1541 $this->entrepot_id = 0;
1542 $this->socid = 1;
1543
1544 $this->origin_id = 1;
1545 $this->origin_type = 'supplier_order';
1546 $this->origin_object = $order;
1547
1548 $this->note_private = 'Private note';
1549 $this->note_public = 'Public note';
1550
1551 $this->tracking_number = 'TRACKID-ABC123';
1552
1553 $this->fk_incoterms = 1;
1554
1555 $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)
1556 $xnbp = 0;
1557 while ($xnbp < $nbp) {
1558 $line = new CommandeFournisseurDispatch($this->db);
1559 $line->desc = $langs->trans("Description")." ".$xnbp;
1560 $line->libelle = $langs->trans("Description")." ".$xnbp; // deprecated
1561 $line->label = $langs->trans("Description")." ".$xnbp;
1562 $line->qty = 10;
1563
1564 $line->fk_product = $this->origin_object->lines[$xnbp]->fk_product;
1565
1566 $this->lines[] = $line;
1567 $xnbp++;
1568 }
1569
1570 return 1;
1571 }
1572
1580 public function setDeliveryDate($user, $delivery_date)
1581 {
1582 // phpcs:enable
1583 if ($user->hasRight('reception', 'creer')) {
1584 $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
1585 $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
1586 $sql .= " WHERE rowid = ".((int) $this->id);
1587
1588 dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
1589 $resql = $this->db->query($sql);
1590 if ($resql) {
1591 $this->date_delivery = $delivery_date;
1592 return 1;
1593 } else {
1594 $this->error = $this->db->error();
1595 return -1;
1596 }
1597 } else {
1598 return -2;
1599 }
1600 }
1601
1602 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1608 public function fetch_delivery_methods()
1609 {
1610 // phpcs:enable
1611 global $langs;
1612 $this->meths = array();
1613
1614 $sql = "SELECT em.rowid, em.code, em.libelle";
1615 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1616 $sql .= " WHERE em.active = 1";
1617 $sql .= " ORDER BY em.libelle ASC";
1618
1619 $resql = $this->db->query($sql);
1620 if ($resql) {
1621 while ($obj = $this->db->fetch_object($resql)) {
1622 $label = $langs->trans('ReceptionMethod'.$obj->code);
1623 $this->meths[$obj->rowid] = ($label != 'ReceptionMethod'.$obj->code ? $label : $obj->libelle);
1624 }
1625 }
1626 }
1627
1628 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1635 public function list_delivery_methods($id = 0)
1636 {
1637 // phpcs:enable
1638 global $langs;
1639
1640 $this->listmeths = array();
1641 $i = 0;
1642
1643 $sql = "SELECT em.rowid, em.code, em.libelle, em.description, em.tracking, em.active";
1644 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1645 if (!empty($id)) {
1646 $sql .= " WHERE em.rowid = ".((int) $id);
1647 }
1648
1649 $resql = $this->db->query($sql);
1650 if ($resql) {
1651 while ($obj = $this->db->fetch_object($resql)) {
1652 $this->listmeths[$i]['rowid'] = $obj->rowid;
1653 $this->listmeths[$i]['code'] = $obj->code;
1654 $label = $langs->trans('ReceptionMethod'.$obj->code);
1655 $this->listmeths[$i]['libelle'] = ($label != 'ReceptionMethod'.$obj->code ? $label : $obj->libelle);
1656 $this->listmeths[$i]['description'] = $obj->description;
1657 $this->listmeths[$i]['tracking'] = $obj->tracking;
1658 $this->listmeths[$i]['active'] = $obj->active;
1659 $i++;
1660 }
1661 }
1662 }
1663
1670 public function getUrlTrackingStatus($value = '')
1671 {
1672 if (!empty($this->shipping_method_id)) {
1673 $sql = "SELECT em.code, em.tracking";
1674 $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1675 $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
1676
1677 $resql = $this->db->query($sql);
1678 if ($resql) {
1679 if ($obj = $this->db->fetch_object($resql)) {
1680 $tracking = $obj->tracking;
1681 }
1682 }
1683 }
1684
1685 if (!empty($tracking) && !empty($value)) {
1686 $url = str_replace('{TRACKID}', $value, $tracking);
1687 $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
1688 } else {
1689 $this->tracking_url = $value;
1690 }
1691 }
1692
1698 public function setClosed()
1699 {
1700 global $conf, $langs, $user;
1701
1702 $error = 0;
1703
1704 // Protection. This avoid to move stock later when we should not
1705 if ($this->statut == Reception::STATUS_CLOSED) {
1706 dol_syslog(get_class($this)."::setClosed already in closed status", LOG_WARNING);
1707 return 0;
1708 }
1709
1710 $this->db->begin();
1711
1712 $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET fk_statut = '.self::STATUS_CLOSED;
1713 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
1714
1715 $resql = $this->db->query($sql);
1716 if ($resql) {
1717 // Set order billed if 100% of order is received (qty in reception lines match qty in order lines)
1718 if ($this->origin == 'order_supplier' && $this->origin_id > 0) {
1719 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
1720
1721 $order = new CommandeFournisseur($this->db);
1722 $order->fetch($this->origin_id);
1723
1724 $order->loadReceptions(self::STATUS_CLOSED); // Fill $order->receptions = array(orderlineid => qty)
1725
1726 $receptions_match_order = 1;
1727 foreach ($order->lines as $line) {
1728 $lineid = $line->id;
1729 $qty = $line->qty;
1730 if (($line->product_type == 0 || getDolGlobalInt('STOCK_SUPPORTS_SERVICES')) && $order->receptions[$lineid] < $qty) {
1731 $receptions_match_order = 0;
1732 $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';
1733 dol_syslog($text);
1734 break;
1735 }
1736 }
1737 if ($receptions_match_order) {
1738 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');
1739 $order->Livraison($user, dol_now(), 'tot', 'Reception '.$this->ref);
1740 }
1741 }
1742
1743 $this->statut = self::STATUS_CLOSED;
1744 $this->status = self::STATUS_CLOSED;
1745
1746 // If stock increment is done on closing
1747 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
1748 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1749
1750 $langs->load("agenda");
1751
1752 // Loop on each product line to add a stock movement
1753 // TODO possibilite de receptionner a partir d'une propale ou autre origine ?
1754 $sql = "SELECT cd.fk_product, cd.subprice,";
1755 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
1756 $sql .= " ed.eatby, ed.sellby, ed.batch,";
1757 $sql .= " ed.cost_price";
1758 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1759 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1760 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1761 $sql .= " AND cd.rowid = ed.fk_elementdet";
1762
1763 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
1764 $resql = $this->db->query($sql);
1765
1766 if ($resql) {
1767 $cpt = $this->db->num_rows($resql);
1768 for ($i = 0; $i < $cpt; $i++) {
1769 $obj = $this->db->fetch_object($resql);
1770
1771 $qty = $obj->qty;
1772
1773 if ($qty == 0 || ($qty < 0 && !getDolGlobalInt('RECEPTION_ALLOW_NEGATIVE_QTY'))) {
1774 continue;
1775 }
1776
1777 dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid);
1778
1779 $mouvS = new MouvementStock($this->db);
1780 $mouvS->origin = &$this;
1781 $mouvS->setOrigin($this->element, $this->id);
1782
1783 if (empty($obj->batch)) {
1784 // line without batch detail
1785
1786 // 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
1787 $inventorycode = '';
1788 $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionClassifyClosedInDolibarr", $this->ref), '', '', '', '', 0, $inventorycode);
1789 if ($result < 0) {
1790 $this->error = $mouvS->error;
1791 $this->errors = $mouvS->errors;
1792 $error++;
1793 break;
1794 }
1795 } else {
1796 // line with batch detail
1797
1798 // 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
1799 $inventorycode = '';
1800 $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);
1801
1802 if ($result < 0) {
1803 $this->error = $mouvS->error;
1804 $this->errors = $mouvS->errors;
1805 $error++;
1806 break;
1807 }
1808 }
1809 }
1810 } else {
1811 $this->error = $this->db->lasterror();
1812 $error++;
1813 }
1814 }
1815
1816 // Call trigger
1817 if (!$error) {
1818 $result = $this->call_trigger('RECEPTION_CLOSED', $user);
1819 if ($result < 0) {
1820 $error++;
1821 }
1822 }
1823 } else {
1824 dol_print_error($this->db);
1825 $error++;
1826 }
1827
1828 if (!$error) {
1829 $this->db->commit();
1830 return 1;
1831 } else {
1832 $this->statut = self::STATUS_VALIDATED;
1833 $this->status = self::STATUS_VALIDATED;
1834 $this->db->rollback();
1835 return -1;
1836 }
1837 }
1838
1844 public function setBilled()
1845 {
1846 global $user;
1847 $error = 0;
1848
1849 $this->db->begin();
1850
1851 if ($this->statut == Reception::STATUS_VALIDATED) {
1852 // do not close if already closed
1853 $this->setClosed();
1854 }
1855
1856 $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET billed=1';
1857 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
1858
1859 $resql = $this->db->query($sql);
1860 if ($resql) {
1861 $this->billed = 1;
1862
1863 // Call trigger
1864 $result = $this->call_trigger('RECEPTION_BILLED', $user);
1865 if ($result < 0) {
1866 $this->billed = 0;
1867 $error++;
1868 }
1869 } else {
1870 $error++;
1871 $this->errors[] = $this->db->lasterror;
1872 }
1873
1874 if (empty($error)) {
1875 $this->db->commit();
1876 return 1;
1877 } else {
1878 $this->db->rollback();
1879 return -1;
1880 }
1881 }
1882
1888 public function reOpen()
1889 {
1890 global $conf, $langs, $user;
1891
1892 $error = 0;
1893
1894 $this->db->begin();
1895
1896 $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET fk_statut=1, billed=0';
1897 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
1898
1899 $resql = $this->db->query($sql);
1900 if ($resql) {
1901 $this->statut = self::STATUS_VALIDATED;
1902 $this->status = self::STATUS_VALIDATED;
1903 $this->billed = 0;
1904
1905 // If stock increment is done on closing
1906 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
1907 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1908 $numref = $this->ref;
1909 $langs->load("agenda");
1910
1911 // Loop on each product line to add a stock movement
1912 // TODO possibilite de receptionner a partir d'une propale ou autre origine
1913 $sql = "SELECT ed.fk_product, cd.subprice,";
1914 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
1915 $sql .= " ed.eatby, ed.sellby, ed.batch,";
1916 $sql .= " ed.cost_price";
1917 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1918 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1919 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1920 $sql .= " AND cd.rowid = ed.fk_elementdet";
1921
1922 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
1923 $resql = $this->db->query($sql);
1924 if ($resql) {
1925 $cpt = $this->db->num_rows($resql);
1926 for ($i = 0; $i < $cpt; $i++) {
1927 $obj = $this->db->fetch_object($resql);
1928
1929 $qty = $obj->qty;
1930
1931 if ($qty == 0 || ($qty < 0 && !getDolGlobalInt('RECEPTION_ALLOW_NEGATIVE_QTY'))) {
1932 continue;
1933 }
1934 dol_syslog(get_class($this)."::reopen reception movement index ".$i." ed.rowid=".$obj->rowid);
1935
1936 //var_dump($this->lines[$i]);
1937 $mouvS = new MouvementStock($this->db);
1938 $mouvS->origin = &$this;
1939 $mouvS->setOrigin($this->element, $this->id);
1940
1941 if (empty($obj->batch)) {
1942 // line without batch detail
1943
1944 // 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
1945 $inventorycode = '';
1946 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionUnClassifyCloseddInDolibarr", $numref), '', '', '', '', 0, $inventorycode);
1947
1948 if ($result < 0) {
1949 $this->error = $mouvS->error;
1950 $this->errors = $mouvS->errors;
1951 $error++;
1952 break;
1953 }
1954 } else {
1955 // line with batch detail
1956
1957 // 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
1958 $inventorycode = '';
1959 $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);
1960 if ($result < 0) {
1961 $this->error = $mouvS->error;
1962 $this->errors = $mouvS->errors;
1963 $error++;
1964 break;
1965 }
1966 }
1967 }
1968 } else {
1969 $this->error = $this->db->lasterror();
1970 $error++;
1971 }
1972 }
1973
1974 if (!$error) {
1975 // Call trigger
1976 $result = $this->call_trigger('RECEPTION_REOPEN', $user);
1977 if ($result < 0) {
1978 $error++;
1979 }
1980 }
1981
1982 if (!$error && $this->origin == 'order_supplier') {
1983 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
1984
1985 $commande = new CommandeFournisseur($this->db);
1986 $commande->fetch($this->origin_id);
1987 $result = $commande->setStatus($user, 4);
1988 if ($result < 0) {
1989 $error++;
1990 $this->error = $commande->error;
1991 $this->errors = $commande->errors;
1992 }
1993 }
1994 } else {
1995 $error++;
1996 $this->errors[] = $this->db->lasterror();
1997 }
1998
1999 if (!$error) {
2000 $this->db->commit();
2001 return 1;
2002 } else {
2003 $this->db->rollback();
2004 return -1;
2005 }
2006 }
2007
2014 public function setDraft($user)
2015 {
2016 // phpcs:enable
2017 global $conf, $langs;
2018
2019 $error = 0;
2020
2021 // Protection
2022 if ($this->statut <= self::STATUS_DRAFT) {
2023 return 0;
2024 }
2025
2026 if (!((!getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'creer'))
2027 || (getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'reception_advance', 'validate')))) {
2028 $this->error = 'Permission denied';
2029 return -1;
2030 }
2031
2032 $this->db->begin();
2033
2034 $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
2035 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2036 $sql .= " WHERE rowid = ".((int) $this->id);
2037
2038 dol_syslog(__METHOD__, LOG_DEBUG);
2039 if ($this->db->query($sql)) {
2040 // If stock increment is done on closing
2041 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION')) {
2042 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2043
2044 $langs->load("agenda");
2045
2046 // Loop on each product line to add a stock movement
2047 // TODO possibilite de receptionner a partir d'une propale ou autre origine
2048 $sql = "SELECT cd.fk_product, cd.subprice,";
2049 $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2050 $sql .= " ed.eatby, ed.sellby, ed.batch,";
2051 $sql .= " ed.cost_price";
2052 $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
2053 $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
2054 $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
2055 $sql .= " AND cd.rowid = ed.fk_elementdet";
2056
2057 dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
2058 $resql = $this->db->query($sql);
2059 if ($resql) {
2060 $cpt = $this->db->num_rows($resql);
2061 for ($i = 0; $i < $cpt; $i++) {
2062 $obj = $this->db->fetch_object($resql);
2063
2064 $qty = $obj->qty;
2065
2066 if ($qty == 0 || ($qty < 0 && !getDolGlobalInt('RECEPTION_ALLOW_NEGATIVE_QTY'))) {
2067 continue;
2068 }
2069
2070 dol_syslog(get_class($this)."::reopen reception movement index ".$i." ed.rowid=".$obj->rowid);
2071
2072 //var_dump($this->lines[$i]);
2073 $mouvS = new MouvementStock($this->db);
2074 $mouvS->origin = &$this;
2075 $mouvS->setOrigin($this->element, $this->id);
2076
2077 if (empty($obj->batch)) {
2078 // line without batch detail
2079
2080 // 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
2081 $inventorycode = '';
2082 $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionBackToDraftInDolibarr", $this->ref), '', '', '', '', 0, $inventorycode);
2083 if ($result < 0) {
2084 $this->error = $mouvS->error;
2085 $this->errors = $mouvS->errors;
2086 $error++;
2087 break;
2088 }
2089 } else {
2090 // line with 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->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);
2095 if ($result < 0) {
2096 $this->error = $mouvS->error;
2097 $this->errors = $mouvS->errors;
2098 $error++;
2099 break;
2100 }
2101 }
2102 }
2103 } else {
2104 $this->error = $this->db->lasterror();
2105 $error++;
2106 }
2107 }
2108
2109 if (!$error) {
2110 // Call trigger
2111 $result = $this->call_trigger('RECEPTION_UNVALIDATE', $user);
2112 if ($result < 0) {
2113 $error++;
2114 }
2115 }
2116 if ($this->origin == 'order_supplier') {
2117 if (!empty($this->origin) && $this->origin_id > 0) {
2118 $this->fetch_origin();
2119 if ($this->origin_object->statut == 4) { // If order source of reception is "partially received"
2120 // Check if there is no more reception validated.
2121 $this->origin_object->fetchObjectLinked();
2122 $setStatut = 1;
2123 if (!empty($this->origin_object->linkedObjects['reception'])) {
2124 foreach ($this->origin_object->linkedObjects['reception'] as $rcption) {
2125 if ($rcption->statut > 0) {
2126 $setStatut = 0;
2127 break;
2128 }
2129 }
2130 //var_dump($this->$origin->receptions);exit;
2131 if ($setStatut) {
2132 $this->origin_object->setStatut(3); // ordered
2133 }
2134 }
2135 }
2136 }
2137 }
2138
2139 if (!$error) {
2140 $this->statut = self::STATUS_DRAFT;
2141 $this->status = self::STATUS_DRAFT;
2142 $this->db->commit();
2143 return 1;
2144 } else {
2145 $this->db->rollback();
2146 return -1;
2147 }
2148 } else {
2149 $this->error = $this->db->error();
2150 $this->db->rollback();
2151 return -1;
2152 }
2153 }
2154
2165 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
2166 {
2167 global $conf, $langs;
2168
2169 $langs->load("receptions");
2170
2171 if (!dol_strlen($modele)) {
2172 $modele = 'squille';
2173
2174 if ($this->model_pdf) {
2175 $modele = $this->model_pdf;
2176 } elseif (getDolGlobalString('RECEPTION_ADDON_PDF')) {
2177 $modele = getDolGlobalString('RECEPTION_ADDON_PDF');
2178 }
2179 }
2180
2181 $modelpath = "core/modules/reception/doc/";
2182
2183 $this->fetch_origin();
2184
2185 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
2186 }
2187
2196 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2197 {
2198 $tables = array('reception');
2199
2200 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2201 }
2202
2211 public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
2212 {
2213 $tables = array(
2214 'receptiondet_batch'
2215 );
2216
2217 return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
2218 }
2219}
$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