dolibarr  20.0.0-beta
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 
37 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
38 require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
39 require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
40 if (isModEnabled("propal")) {
41  require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
42 }
43 if (isModEnabled('order')) {
44  require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
45 }
46 
47 
51 class Reception extends CommonObject
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 
77  public $socid;
78  public $ref_supplier;
79 
80  public $entrepot_id;
81  public $tracking_number;
82  public $tracking_url;
83  public $billed;
84  public $model_pdf;
85 
86  public $weight;
87  public $trueWeight;
88  public $weight_units;
89  public $trueWidth;
90  public $width_units;
91  public $trueHeight;
92  public $height_units;
93  public $trueDepth;
94  public $depth_units;
95  // A denormalized value
96  public $trueSize;
97  public $size_units;
98  public $user_author_id;
99 
100  public $date_delivery; // Date delivery planned
101 
107  public $date;
108 
112  public $date_reception;
113 
117  public $date_creation;
118 
122  public $date_valid;
123 
124  public $meths;
125  public $listmeths; // List of carriers
126 
130  public $lines = array();
131 
132 
133  // detail of lot and qty = array(id in receptiondet_batch, batch, qty)
134  // We can use this to know warehouse planned to be used for each lot.
135  public $detail_batch;
136 
137  const STATUS_DRAFT = 0;
138  const STATUS_VALIDATED = 1;
139  const STATUS_CLOSED = 2;
140 
141 
142 
148  public function __construct($db)
149  {
150  $this->db = $db;
151 
152  $this->ismultientitymanaged = 1; // 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
153  }
154 
161  public function getNextNumRef($soc)
162  {
163  global $langs, $conf;
164  $langs->load("receptions");
165 
166  if (getDolGlobalString('RECEPTION_ADDON_NUMBER')) {
167  $mybool = false;
168 
169  $file = getDolGlobalString('RECEPTION_ADDON_NUMBER') . ".php";
170  $classname = getDolGlobalString('RECEPTION_ADDON_NUMBER');
171 
172  // Include file with class
173  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
174 
175  foreach ($dirmodels as $reldir) {
176  $dir = dol_buildpath($reldir."core/modules/reception/");
177 
178  // Load file with numbering class (if found)
179  $mybool = ((bool) @include_once $dir.$file) || $mybool;
180  }
181 
182  if (!$mybool) {
183  dol_print_error(null, "Failed to include file ".$file);
184  return '';
185  }
186 
187  $obj = new $classname();
188 
189  $numref = "";
190  $numref = $obj->getNextValue($soc, $this);
191 
192  if ($numref != "") {
193  return $numref;
194  } else {
195  dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
196  return "";
197  }
198  } else {
199  print $langs->trans("Error")." ".$langs->trans("Error_RECEPTION_ADDON_NUMBER_NotDefined");
200  return "";
201  }
202  }
203 
211  public function create($user, $notrigger = 0)
212  {
213  global $conf;
214 
215  $now = dol_now();
216 
217  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
218  $error = 0;
219 
220  // Clean parameters
221  $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
222  if (empty($this->fk_project)) {
223  $this->fk_project = 0;
224  }
225  if (empty($this->weight_units)) {
226  $this->weight_units = 0;
227  }
228  if (empty($this->size_units)) {
229  $this->size_units = 0;
230  }
231 
232  $this->user = $user;
233 
234  $this->db->begin();
235 
236  $sql = "INSERT INTO ".MAIN_DB_PREFIX."reception (";
237  $sql .= "ref";
238  $sql .= ", entity";
239  $sql .= ", ref_supplier";
240  $sql .= ", date_creation";
241  $sql .= ", fk_user_author";
242  $sql .= ", date_reception";
243  $sql .= ", date_delivery";
244  $sql .= ", fk_soc";
245  $sql .= ", fk_projet";
246  $sql .= ", fk_shipping_method";
247  $sql .= ", tracking_number";
248  $sql .= ", weight";
249  $sql .= ", size";
250  $sql .= ", width";
251  $sql .= ", height";
252  $sql .= ", weight_units";
253  $sql .= ", size_units";
254  $sql .= ", note_private";
255  $sql .= ", note_public";
256  $sql .= ", model_pdf";
257  $sql .= ", fk_incoterms, location_incoterms";
258  $sql .= ") VALUES (";
259  $sql .= "'(PROV)'";
260  $sql .= ", ".((int) $conf->entity);
261  $sql .= ", ".($this->ref_supplier ? "'".$this->db->escape($this->ref_supplier)."'" : "null");
262  $sql .= ", '".$this->db->idate($now)."'";
263  $sql .= ", ".((int) $user->id);
264  $sql .= ", ".($this->date_reception > 0 ? "'".$this->db->idate($this->date_reception)."'" : "null");
265  $sql .= ", ".($this->date_delivery > 0 ? "'".$this->db->idate($this->date_delivery)."'" : "null");
266  $sql .= ", ".((int) $this->socid);
267  $sql .= ", ".((int) $this->fk_project);
268  $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
269  $sql .= ", '".$this->db->escape($this->tracking_number)."'";
270  $sql .= ", ".(is_null($this->weight) ? "NULL" : ((float) $this->weight));
271  $sql .= ", ".(is_null($this->trueDepth) ? "NULL" : ((float) $this->trueDepth));
272  $sql .= ", ".(is_null($this->trueWidth) ? "NULL" : ((float) $this->trueWidth));
273  $sql .= ", ".(is_null($this->trueHeight) ? "NULL" : ((float) $this->trueHeight));
274  $sql .= ", ".(is_null($this->weight_units) ? "NULL" : ((float) $this->weight_units));
275  $sql .= ", ".(is_null($this->size_units) ? "NULL" : ((float) $this->size_units));
276  $sql .= ", ".(!empty($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null");
277  $sql .= ", ".(!empty($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null");
278  $sql .= ", ".(!empty($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
279  $sql .= ", ".(int) $this->fk_incoterms;
280  $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
281  $sql .= ")";
282 
283  dol_syslog(get_class($this)."::create", LOG_DEBUG);
284 
285  $resql = $this->db->query($sql);
286 
287  if ($resql) {
288  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."reception");
289 
290  $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
291  $sql .= " SET ref = '(PROV".((int) $this->id).")'";
292  $sql .= " WHERE rowid = ".((int) $this->id);
293 
294  dol_syslog(get_class($this)."::create", LOG_DEBUG);
295  if ($this->db->query($sql)) {
296  // Insert of lines
297  $num = count($this->lines);
298  for ($i = 0; $i < $num; $i++) {
299  $this->lines[$i]->fk_reception = $this->id;
300 
301  if (!$this->lines[$i]->create($user) > 0) {
302  $error++;
303  }
304  }
305 
306  if (!$error && $this->id && $this->origin_id) {
307  $ret = $this->add_object_linked();
308  if (!$ret) {
309  $error++;
310  }
311  }
312 
313  // Create extrafields
314  if (!$error) {
315  $result = $this->insertExtraFields();
316  if ($result < 0) {
317  $error++;
318  }
319  }
320 
321  if (!$error && !$notrigger) {
322  // Call trigger
323  $result = $this->call_trigger('RECEPTION_CREATE', $user);
324  if ($result < 0) {
325  $error++;
326  }
327  // End call triggers
328  }
329 
330  if (!$error) {
331  $this->db->commit();
332  return $this->id;
333  } else {
334  foreach ($this->errors as $errmsg) {
335  dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
336  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
337  }
338  $this->db->rollback();
339  return -1 * $error;
340  }
341  } else {
342  $error++;
343  $this->error = $this->db->lasterror()." - sql=$sql";
344  $this->db->rollback();
345  return -2;
346  }
347  } else {
348  $error++;
349  $this->error = $this->db->error()." - sql=$sql";
350  $this->db->rollback();
351  return -1;
352  }
353  }
354 
355 
356 
365  public function fetch($id, $ref = '', $ref_ext = '')
366  {
367  // Check parameters
368  if (empty($id) && empty($ref) && empty($ref_ext)) {
369  return -1;
370  }
371 
372  $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";
373  $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
374  $sql .= ", e.date_reception as date_reception, e.model_pdf, e.date_delivery";
375  $sql .= ", e.fk_shipping_method, e.tracking_number";
376  $sql .= ", el.fk_source as origin_id, el.sourcetype as origin";
377  $sql .= ", e.note_private, e.note_public";
378  $sql .= ', e.fk_incoterms, e.location_incoterms';
379  $sql .= ', i.libelle as label_incoterms';
380  $sql .= " FROM ".MAIN_DB_PREFIX."reception as e";
381  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
382  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
383  $sql .= " WHERE e.entity IN (".getEntity('reception').")";
384  if ($id) {
385  $sql .= " AND e.rowid = ".((int) $id);
386  }
387  if ($ref) {
388  $sql .= " AND e.ref = '".$this->db->escape($ref)."'";
389  }
390  if ($ref_ext) {
391  $sql .= " AND e.ref_ext = '".$this->db->escape($ref_ext)."'";
392  }
393 
394  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
395  $result = $this->db->query($sql);
396  if ($result) {
397  if ($this->db->num_rows($result)) {
398  $obj = $this->db->fetch_object($result);
399 
400  $this->id = $obj->rowid;
401  $this->entity = $obj->entity;
402  $this->ref = $obj->ref;
403  $this->socid = $obj->socid;
404  $this->ref_supplier = $obj->ref_supplier;
405  $this->ref_ext = $obj->ref_ext;
406  $this->statut = $obj->status;
407  $this->status = $obj->status;
408  $this->billed = $obj->billed;
409 
410  $this->user_author_id = $obj->fk_user_author;
411  $this->date_creation = $this->db->jdate($obj->date_creation);
412  $this->date = $this->db->jdate($obj->date_reception); // TODO deprecated
413  $this->date_reception = $this->db->jdate($obj->date_reception); // Date real
414  $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
415  $this->model_pdf = $obj->model_pdf;
416  $this->shipping_method_id = $obj->fk_shipping_method;
417  $this->tracking_number = $obj->tracking_number;
418  $this->origin = ($obj->origin ? $obj->origin : 'commande'); // For compatibility
419  $this->origin_type = ($obj->origin ? $obj->origin : 'commande'); // For compatibility
420  $this->origin_id = $obj->origin_id;
421 
422  $this->trueWeight = $obj->weight;
423  $this->weight_units = $obj->weight_units;
424 
425  $this->trueWidth = $obj->width;
426  $this->width_units = $obj->size_units;
427  $this->trueHeight = $obj->height;
428  $this->height_units = $obj->size_units;
429  $this->trueDepth = $obj->size;
430  $this->depth_units = $obj->size_units;
431 
432  $this->note_public = $obj->note_public;
433  $this->note_private = $obj->note_private;
434 
435  // A denormalized value
436  $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
437  $this->size_units = $obj->size_units;
438 
439  //Incoterms
440  $this->fk_incoterms = $obj->fk_incoterms;
441  $this->location_incoterms = $obj->location_incoterms;
442  $this->label_incoterms = $obj->label_incoterms;
443 
444  $this->db->free($result);
445 
446  //$file = $conf->reception->dir_output."/".get_exdir(0, 0, 0, 1, $this, 'reception')."/".$this->id.".pdf";
447  //$this->pdf_filename = $file;
448 
449  // Tracking url
450  $this->getUrlTrackingStatus($obj->tracking_number);
451 
452  /*
453  * Thirdparty
454  */
455  $result = $this->fetch_thirdparty();
456 
457 
458  // Retrieve all extrafields for reception
459  // fetch optionals attributes and labels
460  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
461  $extrafields = new ExtraFields($this->db);
462  $extrafields->fetch_name_optionals_label($this->table_element, true);
463  $this->fetch_optionals();
464 
465  /*
466  * Lines
467  */
468  $result = $this->fetch_lines();
469  if ($result < 0) {
470  return -3;
471  }
472 
473  return 1;
474  } else {
475  dol_syslog(get_class($this).'::Fetch no reception found', LOG_ERR);
476  $this->error = 'Reception with id '.$id.' not found';
477  return 0;
478  }
479  } else {
480  $this->error = $this->db->error();
481  return -1;
482  }
483  }
484 
492  public function valid($user, $notrigger = 0)
493  {
494  global $conf, $langs;
495 
496  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
497 
498  dol_syslog(get_class($this)."::valid");
499 
500  // Protection
501  if ($this->statut) {
502  dol_syslog(get_class($this)."::valid no draft status", LOG_WARNING);
503  return 0;
504  }
505 
506  if (!((!getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'creer'))
507  || (getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'reception_advance', 'validate')))) {
508  $this->error = 'Permission denied';
509  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
510  return -1;
511  }
512 
513  $this->db->begin();
514 
515  $error = 0;
516 
517  // Define new ref
518  $soc = new Societe($this->db);
519  $soc->fetch($this->socid);
520 
521 
522  // Define new ref
523  if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
524  $numref = $this->getNextNumRef($soc);
525  } else {
526  $numref = $this->ref;
527  }
528 
529  $this->newref = dol_sanitizeFileName($numref);
530 
531  $now = dol_now();
532 
533  // Validate
534  $sql = "UPDATE ".MAIN_DB_PREFIX."reception SET";
535  $sql .= " ref='".$this->db->escape($numref)."'";
536  $sql .= ", fk_statut = 1";
537  $sql .= ", date_valid = '".$this->db->idate($now)."'";
538  $sql .= ", fk_user_valid = ".$user->id;
539  $sql .= " WHERE rowid = ".((int) $this->id);
540  dol_syslog(get_class($this)."::valid update reception", LOG_DEBUG);
541  $resql = $this->db->query($sql);
542  if (!$resql) {
543  $this->error = $this->db->lasterror();
544  $error++;
545  }
546 
547  // If stock increment is done on reception (recommended choice)
548  if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION')) {
549  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
550 
551  $langs->load("agenda");
552 
553  // Loop on each product line to add a stock movement
554  // TODO in future, reception lines may not be linked to order line
555  $sql = "SELECT cd.fk_product, cd.subprice, cd.remise_percent,";
556  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
557  $sql .= " ed.eatby, ed.sellby, ed.batch,";
558  $sql .= " ed.cost_price";
559  $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
560  $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
561  $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
562  $sql .= " AND cd.rowid = ed.fk_elementdet";
563 
564  dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
565  $resql = $this->db->query($sql);
566  if ($resql) {
567  $cpt = $this->db->num_rows($resql);
568  for ($i = 0; $i < $cpt; $i++) {
569  $obj = $this->db->fetch_object($resql);
570 
571  $qty = $obj->qty;
572 
573  if ($qty == 0 || ($qty < 0 && !getDolGlobalInt('RECEPTION_ALLOW_NEGATIVE_QTY'))) {
574  continue;
575  }
576  dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
577 
578  //var_dump($this->lines[$i]);
579  $mouvS = new MouvementStock($this->db);
580  $mouvS->origin = &$this;
581  $mouvS->setOrigin($this->element, $this->id);
582 
583  if (empty($obj->batch)) {
584  // line without batch detail
585 
586  // 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.
587  $inventorycode = '';
588  $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionValidatedInDolibarr", $numref), '', '', '', '', 0, $inventorycode);
589 
590  if (intval($result) < 0) {
591  $error++;
592  $this->errors[] = $mouvS->error;
593  $this->errors = array_merge($this->errors, $mouvS->errors);
594  break;
595  }
596  } else {
597  // line with batch detail
598 
599  // 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.
600  // Note: ->fk_origin_stock = id into table llx_product_batch (may be rename into llx_product_stock_batch in another version)
601  $inventorycode = '';
602  $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);
603 
604  if (intval($result) < 0) {
605  $error++;
606  $this->errors[] = $mouvS->error;
607  $this->errors = array_merge($this->errors, $mouvS->errors);
608  break;
609  }
610  }
611  }
612  } else {
613  $this->db->rollback();
614  $this->error = $this->db->error();
615  return -2;
616  }
617  }
618 
619  if (!$error) {
620  // Change status of purchase order to "reception in process" or "totally received"
621  $status = $this->getStatusDispatch();
622  if ($status < 0) {
623  $error++;
624  } else {
625  $trigger_key = '';
626  if ($this->origin_object instanceof CommandeFournisseur && $status == CommandeFournisseur::STATUS_RECEIVED_COMPLETELY) {
627  $ret = $this->origin_object->Livraison($user, dol_now(), 'tot', '');
628  if ($ret < 0) {
629  $error++;
630  $this->errors = array_merge($this->errors, $this->origin_object->errors);
631  }
632  } else {
633  $ret = $this->setStatut($status, $this->origin_id, 'commande_fournisseur', $trigger_key);
634  if ($ret < 0) {
635  $error++;
636  }
637  }
638  }
639  }
640 
641  if (!$error && !$notrigger) {
642  // Call trigger
643  $result = $this->call_trigger('RECEPTION_VALIDATE', $user);
644  if ($result < 0) {
645  $error++;
646  }
647  // End call triggers
648  }
649 
650  if (!$error) {
651  $this->oldref = $this->ref;
652 
653  // Rename directory if dir was a temporary ref
654  if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
655  // Now we rename also files into index
656  $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)."'";
657  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'reception/".$this->db->escape($this->ref)."' AND entity = ".((int) $conf->entity);
658  $resql = $this->db->query($sql);
659  if (!$resql) {
660  $error++;
661  $this->error = $this->db->lasterror();
662  }
663  $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'reception/".$this->db->escape($this->newref)."'";
664  $sql .= " WHERE filepath = 'reception/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
665  $resql = $this->db->query($sql);
666  if (!$resql) {
667  $error++;
668  $this->error = $this->db->lasterror();
669  }
670 
671  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
672  $oldref = dol_sanitizeFileName($this->ref);
673  $newref = dol_sanitizeFileName($numref);
674  $dirsource = $conf->reception->dir_output.'/'.$oldref;
675  $dirdest = $conf->reception->dir_output.'/'.$newref;
676  if (!$error && file_exists($dirsource)) {
677  dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
678 
679  if (@rename($dirsource, $dirdest)) {
680  dol_syslog("Rename ok");
681  // Rename docs starting with $oldref with $newref
682  $listoffiles = dol_dir_list($conf->reception->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
683  foreach ($listoffiles as $fileentry) {
684  $dirsource = $fileentry['name'];
685  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
686  $dirsource = $fileentry['path'].'/'.$dirsource;
687  $dirdest = $fileentry['path'].'/'.$dirdest;
688  @rename($dirsource, $dirdest);
689  }
690  }
691  }
692  }
693  }
694 
695  // Set new ref and current status
696  if (!$error) {
697  $this->ref = $numref;
698  $this->statut = self::STATUS_VALIDATED;
699  $this->status = self::STATUS_VALIDATED;
700  }
701 
702  if (!$error) {
703  $this->db->commit();
704  return 1;
705  } else {
706  foreach ($this->errors as $errmsg) {
707  dol_syslog(get_class($this)."::valid ".$errmsg, LOG_ERR);
708  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
709  }
710  $this->db->rollback();
711  return -1 * $error;
712  }
713  }
714 
720  public function getStatusDispatch()
721  {
722  require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
723  require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
724 
726 
727  if (!empty($this->origin) && $this->origin_id > 0 && ($this->origin == 'order_supplier' || $this->origin == 'commandeFournisseur')) {
728  if (empty($this->origin_object)) {
729  $this->fetch_origin();
730  if ($this->origin_object instanceof CommonObject && empty($this->origin_object->lines)) {
731  $res = $this->origin_object->fetch_lines();
732  if ($this->origin_object instanceof CommandeFournisseur) {
733  $this->commandeFournisseur = $this->origin_object; // deprecated
734  } else {
735  $this->commandeFournisseur = null; // deprecated
736  }
737  if ($res < 0) {
738  return $res;
739  }
740  }
741  }
742 
743  $qty_received = array();
744  $qty_wished = array();
745 
746  $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
747  $filter = array('t.fk_element' => $this->origin_id);
748  if (getDolGlobalInt('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
749  $filter['t.status'] = 1; // Restrict to lines with status validated
750  }
751 
752  $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
753  if ($ret < 0) {
754  $this->error = $supplierorderdispatch->error;
755  $this->errors = $supplierorderdispatch->errors;
756  return $ret;
757  } else {
758  // build array with quantity received by product in all supplier orders (origin)
759  foreach ($supplierorderdispatch->lines as $dispatch_line) {
760  if (array_key_exists($dispatch_line->fk_product, $qty_received)) {
761  $qty_received[$dispatch_line->fk_product] += $dispatch_line->qty;
762  } else {
763  $qty_received[$dispatch_line->fk_product] = $dispatch_line->qty;
764  }
765  }
766 
767  // qty wished in origin (purchase order, ...)
768  foreach ($this->origin_object->lines as $origin_line) {
769  // exclude lines not qualified for reception
770  if ((!getDolGlobalInt('STOCK_SUPPORTS_SERVICES') && $origin_line->product_type > 0) || $origin_line->product_type > 1) {
771  continue;
772  }
773 
774  $qty_wished[$origin_line->fk_product] += $origin_line->qty;
775  }
776 
777  // compare array
778  $diff_array = array_diff_assoc($qty_received, $qty_wished); // Warning: $diff_array is done only on common keys.
779  $keys_in_wished_not_in_received = array_diff(array_keys($qty_wished), array_keys($qty_received));
780  $keys_in_received_not_in_wished = array_diff(array_keys($qty_received), array_keys($qty_wished));
781 
782  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
784  } elseif (getDolGlobalInt('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
785  // set totally received if more products received than ordered
786  $close = 0;
787 
788  if (count($diff_array) > 0) {
789  // there are some difference between the two arrays
790  // scan the array of results
791  foreach ($diff_array as $key => $value) {
792  // if the quantity delivered is greater or equal to ordered quantity
793  if ($qty_received[$key] >= $qty_wished[$key]) {
794  $close++;
795  }
796  }
797  }
798 
799  if ($close == count($diff_array)) {
800  // all the products are received equal or more than the ordered quantity
802  }
803  }
804  }
805  }
806 
807  return $status;
808  }
809 
826  public function addline($entrepot_id, $id, $qty, $array_options = [], $comment = '', $eatby = null, $sellby = null, $batch = '', $cost_price = 0)
827  {
828  global $conf, $langs, $user;
829 
830  $num = count($this->lines);
831  $line = new CommandeFournisseurDispatch($this->db);
832 
833  $line->fk_entrepot = $entrepot_id;
834  $line->fk_commandefourndet = $id;
835  $line->qty = $qty;
836 
837  $supplierorderline = new CommandeFournisseurLigne($this->db);
838  $result = $supplierorderline->fetch($id);
839  if ($result <= 0) {
840  $this->error = $supplierorderline->error;
841  $this->errors = $supplierorderline->errors;
842  return -1;
843  }
844 
845  $fk_product = 0;
846  if (isModEnabled('stock') && !empty($supplierorderline->fk_product)) {
847  $fk_product = $supplierorderline->fk_product;
848 
849  if (!($entrepot_id > 0) && !getDolGlobalInt('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_RECEPTIONS')) {
850  $langs->load("errors");
851  $this->error = $langs->trans("ErrorWarehouseRequiredIntoReceptionLine");
852  return -1;
853  }
854  }
855 
856  // Check batch is set
857  $product = new Product($this->db);
858  $product->fetch($fk_product);
859  if (isModEnabled('productbatch')) {
860  $langs->load("errors");
861  if (!empty($product->status_batch) && empty($batch)) {
862  $this->error = $langs->trans('ErrorProductNeedBatchNumber', $product->ref);
863  return -1;
864  } elseif (empty($product->status_batch) && !empty($batch)) {
865  $this->error = $langs->trans('ErrorProductDoesNotNeedBatchNumber', $product->ref);
866  return -1;
867  }
868 
869  // check sell-by / eat-by date is mandatory
870  $errorMsgArr = Productlot::checkSellOrEatByMandatoryFromProductAndDates($product, $sellby, $eatby);
871  if (!empty($errorMsgArr)) {
872  $errorMessage = '<b>' . $product->ref . '</b> : ';
873  $errorMessage .= '<ul>';
874  foreach ($errorMsgArr as $errorMsg) {
875  $errorMessage .= '<li>' . $errorMsg . '</li>';
876  }
877  $errorMessage .= '</ul>';
878  $this->error = $errorMessage;
879  return -1;
880  }
881  }
882  unset($product);
883 
884  // extrafields
885  $line->array_options = $supplierorderline->array_options;
886  if (!getDolGlobalInt('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) {
887  foreach ($array_options as $key => $value) {
888  $line->array_options[$key] = $value;
889  }
890  }
891 
892  $line->fk_product = $fk_product;
893  $line->fk_commande = $supplierorderline->fk_commande;
894  $line->fk_user = $user->id;
895  $line->comment = $comment;
896  $line->batch = $batch;
897  $line->eatby = $eatby;
898  $line->sellby = $sellby;
899  $line->status = 1;
900  $line->cost_price = $cost_price;
901  $line->fk_reception = $this->id;
902 
903  $this->lines[$num] = $line;
904 
905  return $num;
906  }
907 
908 
916  public function update($user = null, $notrigger = 0)
917  {
918  global $conf;
919  $error = 0;
920 
921  // Clean parameters
922 
923  if (isset($this->ref)) {
924  $this->ref = trim($this->ref);
925  }
926  if (isset($this->entity)) {
927  $this->entity = (int) $this->entity;
928  }
929  if (isset($this->ref_supplier)) {
930  $this->ref_supplier = trim($this->ref_supplier);
931  }
932  if (isset($this->socid)) {
933  $this->socid = trim($this->socid);
934  }
935  if (isset($this->fk_user_author)) {
936  $this->fk_user_author = (int) $this->fk_user_author;
937  }
938  if (isset($this->fk_user_valid)) {
939  $this->fk_user_valid = (int) $this->fk_user_valid;
940  }
941  if (isset($this->shipping_method_id)) {
942  $this->shipping_method_id = (int) $this->shipping_method_id;
943  }
944  if (isset($this->tracking_number)) {
945  $this->tracking_number = trim($this->tracking_number);
946  }
947  if (isset($this->statut)) {
948  $this->statut = (int) $this->statut;
949  }
950  if (isset($this->trueDepth)) {
951  $this->trueDepth = trim($this->trueDepth);
952  }
953  if (isset($this->trueWidth)) {
954  $this->trueWidth = trim($this->trueWidth);
955  }
956  if (isset($this->trueHeight)) {
957  $this->trueHeight = trim($this->trueHeight);
958  }
959  if (isset($this->size_units)) {
960  $this->size_units = trim((string) $this->size_units);
961  }
962  if (isset($this->weight_units)) {
963  $this->weight_units = trim((string) $this->weight_units);
964  }
965  if (isset($this->trueWeight)) {
966  $this->weight = trim((string) $this->trueWeight);
967  }
968  if (isset($this->note_private)) {
969  $this->note_private = trim($this->note_private);
970  }
971  if (isset($this->note_public)) {
972  $this->note_public = trim($this->note_public);
973  }
974  if (isset($this->model_pdf)) {
975  $this->model_pdf = trim($this->model_pdf);
976  }
977 
978 
979  // Check parameters
980  // Put here code to add control on parameters values
981 
982  // Update request
983  $sql = "UPDATE ".MAIN_DB_PREFIX."reception SET";
984 
985  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
986  $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
987  $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
988  $sql .= " date_creation=".(dol_strlen($this->date_creation) != 0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
989  $sql .= " fk_user_author=".(isset($this->fk_user_author) ? $this->fk_user_author : "null").",";
990  $sql .= " date_valid=".(dol_strlen($this->date_valid) != 0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
991  $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->fk_user_valid : "null").",";
992  $sql .= " date_reception=".(dol_strlen($this->date_reception) != 0 ? "'".$this->db->idate($this->date_reception)."'" : 'null').",";
993  $sql .= " date_delivery=".(dol_strlen($this->date_delivery) != 0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
994  $sql .= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null").",";
995  $sql .= " tracking_number=".(isset($this->tracking_number) ? "'".$this->db->escape($this->tracking_number)."'" : "null").",";
996  $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
997  $sql .= " height=".(($this->trueHeight != '') ? $this->trueHeight : "null").",";
998  $sql .= " width=".(($this->trueWidth != '') ? $this->trueWidth : "null").",";
999  $sql .= " size_units=".(isset($this->size_units) ? $this->size_units : "null").",";
1000  $sql .= " size=".(($this->trueDepth != '') ? $this->trueDepth : "null").",";
1001  $sql .= " weight_units=".(isset($this->weight_units) ? $this->weight_units : "null").",";
1002  $sql .= " weight=".(($this->trueWeight != '') ? $this->trueWeight : "null").",";
1003  $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1004  $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1005  $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1006  $sql .= " entity = ".((int) $conf->entity);
1007  $sql .= " WHERE rowid=".((int) $this->id);
1008 
1009  $this->db->begin();
1010 
1011  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1012  $resql = $this->db->query($sql);
1013  if (!$resql) {
1014  $error++;
1015  $this->errors[] = "Error ".$this->db->lasterror();
1016  }
1017 
1018  if (!$error) {
1019  if (!$notrigger) {
1020  // Call trigger
1021  $result = $this->call_trigger('RECEPTION_MODIFY', $user);
1022  if ($result < 0) {
1023  $error++;
1024  }
1025  // End call triggers
1026  }
1027  }
1028 
1029  // Commit or rollback
1030  if ($error) {
1031  foreach ($this->errors as $errmsg) {
1032  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1033  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1034  }
1035  $this->db->rollback();
1036  return -1 * $error;
1037  } else {
1038  $this->db->commit();
1039  return 1;
1040  }
1041  }
1042 
1049  public function delete(User $user)
1050  {
1051  global $conf, $langs, $user;
1052  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1053 
1054  $error = 0;
1055  $this->error = '';
1056 
1057 
1058  $this->db->begin();
1059 
1060  // Stock control
1061  if (isModEnabled('stock') && !getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION') && $this->statut > 0) {
1062  require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
1063 
1064  $langs->load("agenda");
1065 
1066  // Loop on each product line to add a stock movement
1067  $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.eatby, ed.sellby, ed.batch, ed.rowid as receptiondet_batch_id";
1068  $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1069  $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1070  $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1071  $sql .= " AND cd.rowid = ed.fk_elementdet";
1072 
1073  dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
1074  $resql = $this->db->query($sql);
1075  if ($resql) {
1076  $cpt = $this->db->num_rows($resql);
1077  for ($i = 0; $i < $cpt; $i++) {
1078  dol_syslog(get_class($this)."::delete movement index ".$i);
1079  $obj = $this->db->fetch_object($resql);
1080 
1081  $mouvS = new MouvementStock($this->db);
1082  // we do not log origin because it will be deleted
1083  $mouvS->origin = null;
1084 
1085  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ReceptionDeletedInDolibarr", $this->ref), '', $obj->eatby, $obj->sellby, $obj->batch); // Price is set to 0, because we don't want to see WAP changed
1086  }
1087  } else {
1088  $error++;
1089  $this->errors[] = "Error ".$this->db->lasterror();
1090  }
1091  }
1092 
1093  if (!$error) {
1094  $main = MAIN_DB_PREFIX.'receptiondet_batch';
1095  $ef = $main."_extrafields";
1096 
1097  $sqlef = "DELETE FROM ".$ef." WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_reception = ".((int) $this->id).")";
1098 
1099  $sql = "DELETE FROM ".MAIN_DB_PREFIX."receptiondet_batch";
1100  $sql .= " WHERE fk_reception = ".((int) $this->id);
1101 
1102  if ($this->db->query($sqlef) && $this->db->query($sql)) {
1103  // Delete linked object
1104  $res = $this->deleteObjectLinked();
1105  if ($res < 0) {
1106  $error++;
1107  }
1108 
1109  if (!$error) {
1110  $sql = "DELETE FROM ".MAIN_DB_PREFIX."reception";
1111  $sql .= " WHERE rowid = ".((int) $this->id);
1112 
1113  if ($this->db->query($sql)) {
1114  // Call trigger
1115  $result = $this->call_trigger('RECEPTION_DELETE', $user);
1116  if ($result < 0) {
1117  $error++;
1118  }
1119  // End call triggers
1120 
1121  if (!empty($this->origin) && $this->origin_id > 0) {
1122  $this->fetch_origin();
1123  if ($this->origin_object->statut == 4) { // If order source of reception is "partially received"
1124  // Check if there is no more reception. If not, we can move back status of order to "validated" instead of "reception in progress"
1125  $this->origin_object->loadReceptions();
1126  //var_dump($this->$origin->receptions);exit;
1127  if (count($this->origin_object->receptions) <= 0) {
1128  $this->origin_object->setStatut(3); // ordered
1129  }
1130  }
1131  }
1132 
1133  if (!$error) {
1134  $this->db->commit();
1135 
1136  // We delete PDFs
1137  $ref = dol_sanitizeFileName($this->ref);
1138  if (!empty($conf->reception->dir_output)) {
1139  $dir = $conf->reception->dir_output.'/'.$ref;
1140  $file = $dir.'/'.$ref.'.pdf';
1141  if (file_exists($file)) {
1142  if (!dol_delete_file($file)) {
1143  return 0;
1144  }
1145  }
1146  if (file_exists($dir)) {
1147  if (!dol_delete_dir_recursive($dir)) {
1148  $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1149  return 0;
1150  }
1151  }
1152  }
1153 
1154  return 1;
1155  } else {
1156  $this->db->rollback();
1157  return -1;
1158  }
1159  } else {
1160  $this->error = $this->db->lasterror()." - sql=$sql";
1161  $this->db->rollback();
1162  return -3;
1163  }
1164  } else {
1165  $this->error = $this->db->lasterror()." - sql=$sql";
1166  $this->db->rollback();
1167  return -2;
1168  }
1169  } else {
1170  $this->error = $this->db->lasterror()." - sql=$sql";
1171  $this->db->rollback();
1172  return -1;
1173  }
1174  } else {
1175  $this->db->rollback();
1176  return -1;
1177  }
1178  }
1179 
1180  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1186  public function fetch_lines()
1187  {
1188  // phpcs:enable
1189  $this->lines = array();
1190 
1191  require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
1192 
1193  $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."receptiondet_batch";
1194  $sql .= " WHERE fk_reception = ".((int) $this->id);
1195 
1196  $resql = $this->db->query($sql);
1197 
1198  if (!empty($resql)) {
1199  while ($obj = $this->db->fetch_object($resql)) {
1200  $line = new CommandeFournisseurDispatch($this->db);
1201 
1202  $line->fetch($obj->rowid);
1203 
1204  // TODO Remove or keep this ?
1205  $line->fetch_product();
1206 
1207  $sql_commfourndet = 'SELECT qty, ref, label, description, tva_tx, vat_src_code, subprice, multicurrency_subprice, remise_percent, total_ht, total_ttc, total_tva';
1208  $sql_commfourndet .= ' FROM '.MAIN_DB_PREFIX.'commande_fournisseurdet';
1209  $sql_commfourndet .= ' WHERE rowid = '.((int) $line->fk_commandefourndet);
1210  $sql_commfourndet .= ' ORDER BY rang';
1211 
1212  $resql_commfourndet = $this->db->query($sql_commfourndet);
1213  if (!empty($resql_commfourndet)) {
1214  $obj = $this->db->fetch_object($resql_commfourndet);
1215  $line->qty_asked = $obj->qty;
1216  $line->description = $obj->description;
1217  $line->desc = $obj->description;
1218  $line->tva_tx = $obj->tva_tx;
1219  $line->vat_src_code = $obj->vat_src_code;
1220  $line->subprice = $obj->subprice;
1221  $line->multicurrency_subprice = $obj->multicurrency_subprice;
1222  $line->remise_percent = $obj->remise_percent;
1223  $line->label = !empty($obj->label) ? $obj->label : (is_object($line->product) ? $line->product->label : '');
1224  $line->ref_supplier = $obj->ref;
1225  $line->total_ht = $obj->total_ht;
1226  $line->total_ttc = $obj->total_ttc;
1227  $line->total_tva = $obj->total_tva;
1228  } else {
1229  $line->qty_asked = 0;
1230  $line->description = '';
1231  $line->desc = '';
1232  $line->label = $obj->label;
1233  }
1234 
1235  $pu_ht = ($line->subprice * $line->qty) * (100 - $line->remise_percent) / 100;
1236  $tva = $pu_ht * $line->tva_tx / 100;
1237  $this->total_ht += $pu_ht;
1238  $this->total_tva += $pu_ht * $line->tva_tx / 100;
1239 
1240  $this->total_ttc += $pu_ht + $tva;
1241 
1242  if (isModEnabled('productbatch') && !empty($line->batch)) {
1243  $detail_batch = new stdClass();
1244  $detail_batch->eatby = $line->eatby;
1245  $detail_batch->sellby = $line->sellby;
1246  $detail_batch->batch = $line->batch;
1247  $detail_batch->qty = $line->qty;
1248 
1249  $line->detail_batch[] = $detail_batch;
1250  }
1251 
1252  $this->lines[] = $line;
1253  }
1254 
1255  return 1;
1256  } else {
1257  return -1;
1258  }
1259  }
1260 
1271  public function getNomUrl($withpicto = 0, $option = 0, $max = 0, $short = 0, $notooltip = 0)
1272  {
1273  global $langs, $hookmanager;
1274 
1275  $result = '';
1276  $label = img_picto('', $this->picto).' <u>'.$langs->trans("Reception").'</u>';
1277  $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1278  $label .= '<br><b>'.$langs->trans('RefSupplier').':</b> '.($this->ref_supplier ? $this->ref_supplier : '');
1279 
1280  $url = DOL_URL_ROOT.'/reception/card.php?id='.$this->id;
1281 
1282  if ($short) {
1283  return $url;
1284  }
1285 
1286  $linkclose = '';
1287  if (empty($notooltip)) {
1288  if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1289  $label = $langs->trans("Reception");
1290  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1291  }
1292  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1293  $linkclose .= ' class="classfortooltip"';
1294  }
1295 
1296  $linkstart = '<a href="'.$url.'"';
1297  $linkstart .= $linkclose.'>';
1298  $linkend = '</a>';
1299 
1300  $result .= $linkstart;
1301  if ($withpicto) {
1302  $result .= img_object(($notooltip ? '' : $label), $this->picto, '', 0, 0, $notooltip ? 0 : 1);
1303  }
1304  if ($withpicto != 2) {
1305  $result .= $this->ref;
1306  }
1307 
1308  $result .= $linkend;
1309 
1310  global $action;
1311  $hookmanager->initHooks(array($this->element . 'dao'));
1312  $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1313  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1314  if ($reshook > 0) {
1315  $result = $hookmanager->resPrint;
1316  } else {
1317  $result .= $hookmanager->resPrint;
1318  }
1319  return $result;
1320  }
1321 
1328  public function getLibStatut($mode = 0)
1329  {
1330  return $this->LibStatut($this->statut, $mode);
1331  }
1332 
1333  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1341  public function LibStatut($status, $mode)
1342  {
1343  // phpcs:enable
1344  global $langs;
1345 
1346  // List of long language codes for status
1347  $this->labelStatus[-1] = 'StatusReceptionCanceled';
1348  $this->labelStatus[0] = 'StatusReceptionDraft';
1349  // product to receive if stock increase is on close or already received if stock increase is on validation
1350  $this->labelStatus[1] = 'StatusReceptionValidated';
1351  if (getDolGlobalInt("STOCK_CALCULATE_ON_RECEPTION")) {
1352  $this->labelStatus[1] = 'StatusReceptionValidatedReceived';
1353  }
1354  if (getDolGlobalInt("STOCK_CALCULATE_ON_RECEPTION_CLOSE")) {
1355  $this->labelStatus[1] = 'StatusReceptionValidatedToReceive';
1356  }
1357  $this->labelStatus[2] = 'StatusReceptionProcessed';
1358 
1359  // List of short language codes for status
1360  $this->labelStatusShort[-1] = 'StatusReceptionCanceledShort';
1361  $this->labelStatusShort[0] = 'StatusReceptionDraftShort';
1362  $this->labelStatusShort[1] = 'StatusReceptionValidatedShort';
1363  $this->labelStatusShort[2] = 'StatusReceptionProcessedShort';
1364 
1365  $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
1366  $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
1367 
1368  $statusType = 'status'.$status;
1369  if ($status == self::STATUS_VALIDATED) {
1370  $statusType = 'status4';
1371  }
1372  if ($status == self::STATUS_CLOSED) {
1373  $statusType = 'status6';
1374  }
1375 
1376  return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
1377  }
1378 
1386  public function getKanbanView($option = '', $arraydata = null)
1387  {
1388  $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
1389 
1390  $return = '<div class="box-flex-item box-flex-grow-zero">';
1391  $return .= '<div class="info-box info-box-sm">';
1392  $return .= '<div class="info-box-icon bg-infobox-action">';
1393  $return .= img_picto('', 'order');
1394  $return .= '</div>';
1395  $return .= '<div class="info-box-content">';
1396  $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
1397  if ($selected >= 0) {
1398  $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
1399  }
1400  if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
1401  $return .= '<br><div class="info-box-ref tdoverflowmax150">'.$this->thirdparty->getNomUrl(1).'</div>';
1402  }
1403  /*if (property_exists($this, 'total_ht')) {
1404  $return .= '<div class="info-box-ref amount">'.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency).' '.$langs->trans('HT').'</div>';
1405  }*/
1406  if (method_exists($this, 'getLibStatut')) {
1407  $return .= '<div class="info-box-status">'.$this->getLibStatut(3).'</div>';
1408  }
1409  $return .= '</div>';
1410  $return .= '</div>';
1411  $return .= '</div>';
1412 
1413  return $return;
1414  }
1415 
1423  public function initAsSpecimen()
1424  {
1425  global $langs;
1426 
1427  include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
1428  include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
1429  $now = dol_now();
1430 
1431  dol_syslog(get_class($this)."::initAsSpecimen");
1432 
1433  $order = new CommandeFournisseur($this->db);
1434  $order->initAsSpecimen();
1435 
1436  // Initialise parameters
1437  $this->id = 0;
1438  $this->ref = 'SPECIMEN';
1439  $this->specimen = 1;
1440  $this->statut = 1;
1441  $this->status = 1;
1442  $this->date = $now;
1443  $this->date_creation = $now;
1444  $this->date_valid = $now;
1445  $this->date_delivery = $now;
1446  $this->date_reception = $now + 24 * 3600;
1447 
1448  $this->entrepot_id = 0;
1449  $this->socid = 1;
1450 
1451  $this->origin_id = 1;
1452  $this->origin_type = 'supplier_order';
1453  $this->origin_object = $order;
1454 
1455  $this->note_private = 'Private note';
1456  $this->note_public = 'Public note';
1457 
1458  $this->tracking_number = 'TRACKID-ABC123';
1459 
1460  $this->fk_incoterms = 1;
1461 
1462  $nbp = 5;
1463  $xnbp = 0;
1464  while ($xnbp < $nbp) {
1465  $line = new CommandeFournisseurDispatch($this->db);
1466  $line->desc = $langs->trans("Description")." ".$xnbp;
1467  $line->libelle = $langs->trans("Description")." ".$xnbp; // deprecated
1468  $line->label = $langs->trans("Description")." ".$xnbp;
1469  $line->qty = 10;
1470 
1471  $line->fk_product = $this->origin_object->lines[$xnbp]->fk_product;
1472 
1473  $this->lines[] = $line;
1474  $xnbp++;
1475  }
1476 
1477  return 1;
1478  }
1479 
1487  public function setDeliveryDate($user, $delivery_date)
1488  {
1489  // phpcs:enable
1490  if ($user->hasRight('reception', 'creer')) {
1491  $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
1492  $sql .= " SET date_delivery = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
1493  $sql .= " WHERE rowid = ".((int) $this->id);
1494 
1495  dol_syslog(get_class($this)."::setDeliveryDate", LOG_DEBUG);
1496  $resql = $this->db->query($sql);
1497  if ($resql) {
1498  $this->date_delivery = $delivery_date;
1499  return 1;
1500  } else {
1501  $this->error = $this->db->error();
1502  return -1;
1503  }
1504  } else {
1505  return -2;
1506  }
1507  }
1508 
1509  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1515  public function fetch_delivery_methods()
1516  {
1517  // phpcs:enable
1518  global $langs;
1519  $this->meths = array();
1520 
1521  $sql = "SELECT em.rowid, em.code, em.libelle";
1522  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1523  $sql .= " WHERE em.active = 1";
1524  $sql .= " ORDER BY em.libelle ASC";
1525 
1526  $resql = $this->db->query($sql);
1527  if ($resql) {
1528  while ($obj = $this->db->fetch_object($resql)) {
1529  $label = $langs->trans('ReceptionMethod'.$obj->code);
1530  $this->meths[$obj->rowid] = ($label != 'ReceptionMethod'.$obj->code ? $label : $obj->libelle);
1531  }
1532  }
1533  }
1534 
1535  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1542  public function list_delivery_methods($id = 0)
1543  {
1544  // phpcs:enable
1545  global $langs;
1546 
1547  $this->listmeths = array();
1548  $i = 0;
1549 
1550  $sql = "SELECT em.rowid, em.code, em.libelle, em.description, em.tracking, em.active";
1551  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1552  if (!empty($id)) {
1553  $sql .= " WHERE em.rowid = ".((int) $id);
1554  }
1555 
1556  $resql = $this->db->query($sql);
1557  if ($resql) {
1558  while ($obj = $this->db->fetch_object($resql)) {
1559  $this->listmeths[$i]['rowid'] = $obj->rowid;
1560  $this->listmeths[$i]['code'] = $obj->code;
1561  $label = $langs->trans('ReceptionMethod'.$obj->code);
1562  $this->listmeths[$i]['libelle'] = ($label != 'ReceptionMethod'.$obj->code ? $label : $obj->libelle);
1563  $this->listmeths[$i]['description'] = $obj->description;
1564  $this->listmeths[$i]['tracking'] = $obj->tracking;
1565  $this->listmeths[$i]['active'] = $obj->active;
1566  $i++;
1567  }
1568  }
1569  }
1570 
1577  public function getUrlTrackingStatus($value = '')
1578  {
1579  if (!empty($this->shipping_method_id)) {
1580  $sql = "SELECT em.code, em.tracking";
1581  $sql .= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
1582  $sql .= " WHERE em.rowid = ".((int) $this->shipping_method_id);
1583 
1584  $resql = $this->db->query($sql);
1585  if ($resql) {
1586  if ($obj = $this->db->fetch_object($resql)) {
1587  $tracking = $obj->tracking;
1588  }
1589  }
1590  }
1591 
1592  if (!empty($tracking) && !empty($value)) {
1593  $url = str_replace('{TRACKID}', $value, $tracking);
1594  $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
1595  } else {
1596  $this->tracking_url = $value;
1597  }
1598  }
1599 
1605  public function setClosed()
1606  {
1607  global $conf, $langs, $user;
1608 
1609  $error = 0;
1610 
1611  // Protection. This avoid to move stock later when we should not
1612  if ($this->statut == Reception::STATUS_CLOSED) {
1613  dol_syslog(get_class($this)."::setClosed already in closed status", LOG_WARNING);
1614  return 0;
1615  }
1616 
1617  $this->db->begin();
1618 
1619  $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET fk_statut = '.self::STATUS_CLOSED;
1620  $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
1621 
1622  $resql = $this->db->query($sql);
1623  if ($resql) {
1624  // Set order billed if 100% of order is received (qty in reception lines match qty in order lines)
1625  if ($this->origin == 'order_supplier' && $this->origin_id > 0) {
1626  $order = new CommandeFournisseur($this->db);
1627  $order->fetch($this->origin_id);
1628 
1629  $order->loadReceptions(self::STATUS_CLOSED); // Fill $order->receptions = array(orderlineid => qty)
1630 
1631  $receptions_match_order = 1;
1632  foreach ($order->lines as $line) {
1633  $lineid = $line->id;
1634  $qty = $line->qty;
1635  if (($line->product_type == 0 || getDolGlobalInt('STOCK_SUPPORTS_SERVICES')) && $order->receptions[$lineid] < $qty) {
1636  $receptions_match_order = 0;
1637  $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';
1638  dol_syslog($text);
1639  break;
1640  }
1641  }
1642  if ($receptions_match_order) {
1643  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');
1644  $order->Livraison($user, dol_now(), 'tot', 'Reception '.$this->ref);
1645  }
1646  }
1647 
1648  $this->statut = self::STATUS_CLOSED;
1649  $this->status = self::STATUS_CLOSED;
1650 
1651  // If stock increment is done on closing
1652  if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
1653  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1654 
1655  $langs->load("agenda");
1656 
1657  // Loop on each product line to add a stock movement
1658  // TODO possibilite de receptionner a partir d'une propale ou autre origine ?
1659  $sql = "SELECT cd.fk_product, cd.subprice,";
1660  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
1661  $sql .= " ed.eatby, ed.sellby, ed.batch,";
1662  $sql .= " ed.cost_price";
1663  $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1664  $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1665  $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1666  $sql .= " AND cd.rowid = ed.fk_elementdet";
1667 
1668  dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
1669  $resql = $this->db->query($sql);
1670 
1671  if ($resql) {
1672  $cpt = $this->db->num_rows($resql);
1673  for ($i = 0; $i < $cpt; $i++) {
1674  $obj = $this->db->fetch_object($resql);
1675 
1676  $qty = $obj->qty;
1677 
1678  if ($qty <= 0) {
1679  continue;
1680  }
1681  dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
1682 
1683  $mouvS = new MouvementStock($this->db);
1684  $mouvS->origin = &$this;
1685  $mouvS->setOrigin($this->element, $this->id);
1686 
1687  if (empty($obj->batch)) {
1688  // line without batch detail
1689 
1690  // 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
1691  $inventorycode = '';
1692  $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionClassifyClosedInDolibarr", $this->ref), '', '', '', '', 0, $inventorycode);
1693  if ($result < 0) {
1694  $this->error = $mouvS->error;
1695  $this->errors = $mouvS->errors;
1696  $error++;
1697  break;
1698  }
1699  } else {
1700  // line with batch detail
1701 
1702  // 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
1703  $inventorycode = '';
1704  $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);
1705 
1706  if ($result < 0) {
1707  $this->error = $mouvS->error;
1708  $this->errors = $mouvS->errors;
1709  $error++;
1710  break;
1711  }
1712  }
1713  }
1714  } else {
1715  $this->error = $this->db->lasterror();
1716  $error++;
1717  }
1718  }
1719 
1720  // Call trigger
1721  if (!$error) {
1722  $result = $this->call_trigger('RECEPTION_CLOSED', $user);
1723  if ($result < 0) {
1724  $error++;
1725  }
1726  }
1727  } else {
1728  dol_print_error($this->db);
1729  $error++;
1730  }
1731 
1732  if (!$error) {
1733  $this->db->commit();
1734  return 1;
1735  } else {
1736  $this->statut = self::STATUS_VALIDATED;
1737  $this->status = self::STATUS_VALIDATED;
1738  $this->db->rollback();
1739  return -1;
1740  }
1741  }
1742 
1748  public function setBilled()
1749  {
1750  global $user;
1751  $error = 0;
1752 
1753  $this->db->begin();
1754 
1755  if ($this->statut == Reception::STATUS_VALIDATED) {
1756  // do not close if already closed
1757  $this->setClosed();
1758  }
1759 
1760  $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET billed=1';
1761  $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
1762 
1763  $resql = $this->db->query($sql);
1764  if ($resql) {
1765  $this->billed = 1;
1766 
1767  // Call trigger
1768  $result = $this->call_trigger('RECEPTION_BILLED', $user);
1769  if ($result < 0) {
1770  $this->billed = 0;
1771  $error++;
1772  }
1773  } else {
1774  $error++;
1775  $this->errors[] = $this->db->lasterror;
1776  }
1777 
1778  if (empty($error)) {
1779  $this->db->commit();
1780  return 1;
1781  } else {
1782  $this->db->rollback();
1783  return -1;
1784  }
1785  }
1786 
1792  public function reOpen()
1793  {
1794  global $conf, $langs, $user;
1795 
1796  $error = 0;
1797 
1798  $this->db->begin();
1799 
1800  $sql = 'UPDATE '.MAIN_DB_PREFIX.'reception SET fk_statut=1, billed=0';
1801  $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > 0';
1802 
1803  $resql = $this->db->query($sql);
1804  if ($resql) {
1805  $this->statut = self::STATUS_VALIDATED;
1806  $this->status = self::STATUS_VALIDATED;
1807  $this->billed = 0;
1808 
1809  // If stock increment is done on closing
1810  if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
1811  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1812  $numref = $this->ref;
1813  $langs->load("agenda");
1814 
1815  // Loop on each product line to add a stock movement
1816  // TODO possibilite de receptionner a partir d'une propale ou autre origine
1817  $sql = "SELECT ed.fk_product, cd.subprice,";
1818  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
1819  $sql .= " ed.eatby, ed.sellby, ed.batch,";
1820  $sql .= " ed.cost_price";
1821  $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1822  $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1823  $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1824  $sql .= " AND cd.rowid = ed.fk_elementdet";
1825 
1826  dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
1827  $resql = $this->db->query($sql);
1828  if ($resql) {
1829  $cpt = $this->db->num_rows($resql);
1830  for ($i = 0; $i < $cpt; $i++) {
1831  $obj = $this->db->fetch_object($resql);
1832 
1833  $qty = $obj->qty;
1834 
1835  if ($qty <= 0) {
1836  continue;
1837  }
1838 
1839  dol_syslog(get_class($this)."::reopen reception movement index ".$i." ed.rowid=".$obj->rowid);
1840 
1841  //var_dump($this->lines[$i]);
1842  $mouvS = new MouvementStock($this->db);
1843  $mouvS->origin = &$this;
1844  $mouvS->setOrigin($this->element, $this->id);
1845 
1846  if (empty($obj->batch)) {
1847  // line without batch detail
1848 
1849  // 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
1850  $inventorycode = '';
1851  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionUnClassifyCloseddInDolibarr", $numref), '', '', '', '', 0, $inventorycode);
1852 
1853  if ($result < 0) {
1854  $this->error = $mouvS->error;
1855  $this->errors = $mouvS->errors;
1856  $error++;
1857  break;
1858  }
1859  } else {
1860  // line with batch detail
1861 
1862  // 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
1863  $inventorycode = '';
1864  $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);
1865  if ($result < 0) {
1866  $this->error = $mouvS->error;
1867  $this->errors = $mouvS->errors;
1868  $error++;
1869  break;
1870  }
1871  }
1872  }
1873  } else {
1874  $this->error = $this->db->lasterror();
1875  $error++;
1876  }
1877  }
1878 
1879  if (!$error) {
1880  // Call trigger
1881  $result = $this->call_trigger('RECEPTION_REOPEN', $user);
1882  if ($result < 0) {
1883  $error++;
1884  }
1885  }
1886 
1887  if (!$error && $this->origin == 'order_supplier') {
1888  $commande = new CommandeFournisseur($this->db);
1889  $commande->fetch($this->origin_id);
1890  $result = $commande->setStatus($user, 4);
1891  if ($result < 0) {
1892  $error++;
1893  $this->error = $commande->error;
1894  $this->errors = $commande->errors;
1895  }
1896  }
1897  } else {
1898  $error++;
1899  $this->errors[] = $this->db->lasterror();
1900  }
1901 
1902  if (!$error) {
1903  $this->db->commit();
1904  return 1;
1905  } else {
1906  $this->db->rollback();
1907  return -1;
1908  }
1909  }
1910 
1917  public function setDraft($user)
1918  {
1919  // phpcs:enable
1920  global $conf, $langs;
1921 
1922  $error = 0;
1923 
1924  // Protection
1925  if ($this->statut <= self::STATUS_DRAFT) {
1926  return 0;
1927  }
1928 
1929  if (!((!getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'creer'))
1930  || (getDolGlobalInt('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('reception', 'reception_advance', 'validate')))) {
1931  $this->error = 'Permission denied';
1932  return -1;
1933  }
1934 
1935  $this->db->begin();
1936 
1937  $sql = "UPDATE ".MAIN_DB_PREFIX."reception";
1938  $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
1939  $sql .= " WHERE rowid = ".((int) $this->id);
1940 
1941  dol_syslog(__METHOD__, LOG_DEBUG);
1942  if ($this->db->query($sql)) {
1943  // If stock increment is done on closing
1944  if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_RECEPTION')) {
1945  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1946 
1947  $langs->load("agenda");
1948 
1949  // Loop on each product line to add a stock movement
1950  // TODO possibilite de receptionner a partir d'une propale ou autre origine
1951  $sql = "SELECT cd.fk_product, cd.subprice,";
1952  $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
1953  $sql .= " ed.eatby, ed.sellby, ed.batch,";
1954  $sql .= " ed.cost_price";
1955  $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd,";
1956  $sql .= " ".MAIN_DB_PREFIX."receptiondet_batch as ed";
1957  $sql .= " WHERE ed.fk_reception = ".((int) $this->id);
1958  $sql .= " AND cd.rowid = ed.fk_elementdet";
1959 
1960  dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
1961  $resql = $this->db->query($sql);
1962  if ($resql) {
1963  $cpt = $this->db->num_rows($resql);
1964  for ($i = 0; $i < $cpt; $i++) {
1965  $obj = $this->db->fetch_object($resql);
1966 
1967  $qty = $obj->qty;
1968 
1969 
1970  if ($qty <= 0) {
1971  continue;
1972  }
1973  dol_syslog(get_class($this)."::reopen reception movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
1974 
1975  //var_dump($this->lines[$i]);
1976  $mouvS = new MouvementStock($this->db);
1977  $mouvS->origin = &$this;
1978  $mouvS->setOrigin($this->element, $this->id);
1979 
1980  if (empty($obj->batch)) {
1981  // line without batch detail
1982 
1983  // 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
1984  $inventorycode = '';
1985  $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->cost_price, $langs->trans("ReceptionBackToDraftInDolibarr", $this->ref), '', '', '', '', 0, $inventorycode);
1986  if ($result < 0) {
1987  $this->error = $mouvS->error;
1988  $this->errors = $mouvS->errors;
1989  $error++;
1990  break;
1991  }
1992  } else {
1993  // line with batch detail
1994 
1995  // 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
1996  $inventorycode = '';
1997  $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);
1998  if ($result < 0) {
1999  $this->error = $mouvS->error;
2000  $this->errors = $mouvS->errors;
2001  $error++;
2002  break;
2003  }
2004  }
2005  }
2006  } else {
2007  $this->error = $this->db->lasterror();
2008  $error++;
2009  }
2010  }
2011 
2012  if (!$error) {
2013  // Call trigger
2014  $result = $this->call_trigger('RECEPTION_UNVALIDATE', $user);
2015  if ($result < 0) {
2016  $error++;
2017  }
2018  }
2019  if ($this->origin == 'order_supplier') {
2020  if (!empty($this->origin) && $this->origin_id > 0) {
2021  $this->fetch_origin();
2022  if ($this->origin_object->statut == 4) { // If order source of reception is "partially received"
2023  // Check if there is no more reception validated.
2024  $this->origin_object->fetchObjectLinked();
2025  $setStatut = 1;
2026  if (!empty($this->origin_object->linkedObjects['reception'])) {
2027  foreach ($this->origin_object->linkedObjects['reception'] as $rcption) {
2028  if ($rcption->statut > 0) {
2029  $setStatut = 0;
2030  break;
2031  }
2032  }
2033  //var_dump($this->$origin->receptions);exit;
2034  if ($setStatut) {
2035  $this->origin_object->setStatut(3); // ordered
2036  }
2037  }
2038  }
2039  }
2040  }
2041 
2042  if (!$error) {
2043  $this->statut = self::STATUS_DRAFT;
2044  $this->status = self::STATUS_DRAFT;
2045  $this->db->commit();
2046  return 1;
2047  } else {
2048  $this->db->rollback();
2049  return -1;
2050  }
2051  } else {
2052  $this->error = $this->db->error();
2053  $this->db->rollback();
2054  return -1;
2055  }
2056  }
2057 
2068  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
2069  {
2070  global $conf, $langs;
2071 
2072  $langs->load("receptions");
2073 
2074  if (!dol_strlen($modele)) {
2075  $modele = 'squille';
2076 
2077  if ($this->model_pdf) {
2078  $modele = $this->model_pdf;
2079  } elseif (getDolGlobalString('RECEPTION_ADDON_PDF')) {
2080  $modele = getDolGlobalString('RECEPTION_ADDON_PDF');
2081  }
2082  }
2083 
2084  $modelpath = "core/modules/reception/doc/";
2085 
2086  $this->fetch_origin();
2087 
2088  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
2089  }
2090 
2099  public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2100  {
2101  $tables = array('reception');
2102 
2103  return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2104  }
2105 
2114  public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
2115  {
2116  $tables = array(
2117  'receptiondet_batch'
2118  );
2119 
2120  return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
2121  }
2122 }
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition: security.php:607
$object ref
Definition: info.php:79
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.
setStatut($status, $elementId=null, $elementType='', $trigkey='', $fieldstatus='fk_statut')
Set status of an object.
fetch_origin()
Read linked origin 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.
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 clicable 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 clicable 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.
Definition: user.class.php:50
trait CommonIncoterm
Superclass for incoterm classes.
if(isModEnabled('invoice') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') && $user->hasRight('tax', 'charges', 'lire')) if(isModEnabled('invoice') &&isModEnabled('order') && $user->hasRight("commande", "lire") &&!getDolGlobalString('WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER')) $sql
Social contributions to pay.
Definition: index.php:745
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
Definition: files.lib.php:1609
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.
Definition: files.lib.php:1458
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_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (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_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
div float
Buy price without taxes.
Definition: style.css.php:960
$conf db user
Active Directory does not allow anonymous connections.
Definition: repair.php:127