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