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