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