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