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