Passed
Push — EXTRACT_CLASSES ( a2ff75...ae6b5c )
by Rafael
34:15
created

Reception   F

Complexity

Total Complexity 339

Size/Duplication

Total Lines 2074
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1158
dl 0
loc 2074
rs 0.968
c 0
b 0
f 0
wmc 339

How to fix   Complexity   

Complex Class

Complex classes like Reception often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Reception, and based on these observations, apply Extract Interface, too.

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