Passed
Push — EXTRACT_CLASSES ( c25e41...9f3ede )
by Rafael
55:18
created

Expedition::valid()   F

Complexity

Conditions 29
Paths 4040

Size

Total Lines 142
Code Lines 87

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 29
eloc 87
nc 4040
nop 2
dl 0
loc 142
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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-2020	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-2017  Francis Appels              <[email protected]>
12
 * Copyright (C) 2015       Claudio Aschieri            <[email protected]>
13
 * Copyright (C) 2016-2024	Ferran Marcet			    <[email protected]>
14
 * Copyright (C) 2018       Nicolas ZABOURI			    <[email protected]>
15
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
16
 * Copyright (C) 2020       Lenin Rivas         	    <[email protected]>
17
 * Copyright (C) 2024		MDW							<[email protected]>
18
 * Copyright (C) 2024       Rafael San José             <[email protected]>
19
 *
20
 * This program is free software; you can redistribute it and/or modify
21
 * it under the terms of the GNU General Public License as published by
22
 * the Free Software Foundation; either version 3 of the License, or
23
 * (at your option) any later version.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28
 * GNU General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU General Public License
31
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
32
 */
33
34
namespace Dolibarr\Code\Expedition\Classes;
35
36
use Dolibarr\Core\Base\CommonObject;
37
38
/**
39
 *  \file       htdocs/expedition/class/expedition.class.php
40
 *  \ingroup    expedition
41
 *  \brief      File of class managing the shipments
42
 */
43
44
require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/commonincoterm.class.php';
45
if (isModEnabled("propal")) {
46
    require_once constant('DOL_DOCUMENT_ROOT') . '/comm/propal/class/propal.class.php';
47
}
48
if (isModEnabled('order')) {
49
    require_once constant('DOL_DOCUMENT_ROOT') . '/commande/class/commande.class.php';
50
}
51
require_once constant('DOL_DOCUMENT_ROOT') . '/expedition/class/expeditionlinebatch.class.php';
52
53
54
/**
55
 *  Class to manage shipments
56
 */
57
class Expedition extends CommonObject
58
{
59
    use CommonIncoterm;
60
61
    /**
62
     * @var string ID to identify managed object
63
     */
64
    public $element = "shipping";
65
66
    /**
67
     * @var string Field with ID of parent key if this field has a parent
68
     */
69
    public $fk_element = "fk_expedition";
70
71
    /**
72
     * @var string Name of table without prefix where object is stored
73
     */
74
    public $table_element = "expedition";
75
76
    /**
77
     * @var string    Name of subtable line
78
     */
79
    public $table_element_line = "expeditiondet";
80
81
    /**
82
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
83
     */
84
    public $picto = 'dolly';
85
86
87
    /**
88
     * @var array<string,array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string}>  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array{type:...ring>,comment?:string}> at position 16 could not be parsed: Expected '}' at position 16, but found 'int'.
Loading history...
89
     */
90
    public $fields = array();
91
92
    /**
93
     * @var int ID of user author
94
     */
95
    public $user_author_id;
96
97
    /**
98
     * @var int ID of user author
99
     */
100
    public $fk_user_author;
101
102
    public $socid;
103
104
    /**
105
     * @var string Customer ref
106
     * @deprecated
107
     * @see $ref_customer
108
     */
109
    public $ref_client;
110
111
    /**
112
     * @var string Customer ref
113
     */
114
    public $ref_customer;
115
116
    /**
117
     * @var int warehouse id
118
     */
119
    public $entrepot_id;
120
121
    /**
122
     * @var string Tracking number
123
     */
124
    public $tracking_number;
125
126
    /**
127
     * @var string Tracking url
128
     */
129
    public $tracking_url;
130
    public $billed;
131
132
    /**
133
     * @var string name of pdf model
134
     */
135
    public $model_pdf;
136
137
    public $trueWeight;
138
    public $weight_units;
139
    public $trueWidth;
140
    public $width_units;
141
    public $trueHeight;
142
    public $height_units;
143
    public $trueDepth;
144
    public $depth_units;
145
    // A denormalized value
146
    public $trueSize;
147
148
    public $livraison_id;
149
150
    /**
151
     * @var double
152
     */
153
    public $multicurrency_subprice;
154
155
    public $size_units;
156
157
    public $sizeH;
158
159
    public $sizeS;
160
161
    public $sizeW;
162
163
    public $weight;
164
165
    /**
166
     * @var integer|string Date delivery planned
167
     */
168
    public $date_delivery;
169
170
    /**
171
     * @deprecated
172
     * @see $date_shipping
173
     */
174
    public $date;
175
176
    /**
177
     * @deprecated
178
     * @see $date_shipping
179
     */
180
    public $date_expedition;
181
182
    /**
183
     * Effective delivery date
184
     * @var integer|string
185
     */
186
    public $date_shipping;
187
188
    /**
189
     * @var integer|string date_creation
190
     */
191
    public $date_creation;
192
193
    /**
194
     * @var integer|string date_valid
195
     */
196
    public $date_valid;
197
198
    public $meths;
199
    public $listmeths; // List of carriers
200
201
    /**
202
     * @var int ID of order
203
     */
204
    public $commande_id;
205
206
    /**
207
     * @var Commande order
208
     */
209
    public $commande;
210
211
    /**
212
     * @var ExpeditionLigne[] array of shipping lines
213
     */
214
    public $lines = array();
215
216
    // Multicurrency
217
    /**
218
     * @var int Currency ID
219
     */
220
    public $fk_multicurrency;
221
222
    /**
223
     * @var string multicurrency code
224
     */
225
    public $multicurrency_code;
226
    public $multicurrency_tx;
227
    public $multicurrency_total_ht;
228
    public $multicurrency_total_tva;
229
    public $multicurrency_total_ttc;
230
231
    /**
232
     * @var int
233
     */
234
    public $signed_status = 0;
235
236
    /**
237
     * Draft status
238
     */
239
    const STATUS_DRAFT = 0;
240
241
    /**
242
     * Validated status
243
     * -> parcel is ready to be sent
244
     * prev status : draft
245
     * next status : closed or shipment_in_progress
246
     */
247
    const STATUS_VALIDATED = 1;
248
249
    /**
250
     * Closed status
251
     * -> parcel was received by customer / end of process
252
     * prev status : validated or shipment_in_progress
253
     *
254
     */
255
    const STATUS_CLOSED = 2;
256
257
    /**
258
     * Canceled status
259
     */
260
    const STATUS_CANCELED = -1;
261
262
    /**
263
     * Expedition in progress
264
     * -> package exit the warehouse and is now
265
     *    in the truck or into the hand of the deliverer
266
     * prev status : validated
267
     * next status : closed
268
     */
269
    const STATUS_SHIPMENT_IN_PROGRESS = 3;
270
271
272
    /**
273
     * No signature
274
     */
275
    const STATUS_NO_SIGNATURE    = 0;
276
277
    /**
278
     * Signed status
279
     */
280
    const STATUS_SIGNED = 1;
281
282
283
284
    /**
285
     *  Constructor
286
     *
287
     *  @param      DoliDB      $db      Database handler
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Expedition\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
288
     */
289
    public function __construct($db)
290
    {
291
        global $conf;
292
293
        $this->db = $db;
294
295
        $this->ismultientitymanaged = 1;
296
        $this->isextrafieldmanaged = 1;
297
298
        // List of long language codes for status
299
        $this->labelStatus = array();
300
        $this->labelStatus[-1] = 'StatusSendingCanceled';
301
        $this->labelStatus[0]  = 'StatusSendingDraft';
302
        $this->labelStatus[1]  = 'StatusSendingValidated';
303
        $this->labelStatus[2]  = 'StatusSendingProcessed';
304
305
        // List of short language codes for status
306
        $this->labelStatusShort = array();
307
        $this->labelStatusShort[-1] = 'StatusSendingCanceledShort';
308
        $this->labelStatusShort[0]  = 'StatusSendingDraftShort';
309
        $this->labelStatusShort[1]  = 'StatusSendingValidatedShort';
310
        $this->labelStatusShort[2]  = 'StatusSendingProcessedShort';
311
    }
312
313
    /**
314
     *  Return next expedition ref
315
     *
316
     *  @param  Societe     $soc    Thirdparty object
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Expedition\Classes\Societe was not found. Did you mean Societe? If so, make sure to prefix the type with \.
Loading history...
317
     *  @return string              Free reference for expedition
318
     */
319
    public function getNextNumRef($soc)
320
    {
321
        global $langs, $conf;
322
        $langs->load("sendings");
323
324
        if (getDolGlobalString('EXPEDITION_ADDON_NUMBER')) {
325
            $mybool = false;
326
327
            $file = getDolGlobalString('EXPEDITION_ADDON_NUMBER') . ".php";
328
            $classname = getDolGlobalString('EXPEDITION_ADDON_NUMBER');
329
330
            // Include file with class
331
            $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
332
333
            foreach ($dirmodels as $reldir) {
334
                $dir = dol_buildpath($reldir . "core/modules/expedition/");
335
336
                // Load file with numbering class (if found)
337
                $mybool = ((bool) @include_once $dir . $file) || $mybool;
338
            }
339
340
            if (!$mybool) {
341
                dol_print_error(null, "Failed to include file " . $file);
342
                return '';
343
            }
344
345
            $obj = new $classname();
346
            $numref = "";
347
            $numref = $obj->getNextValue($soc, $this);
348
349
            if ($numref != "") {
350
                return $numref;
351
            } else {
352
                dol_print_error($this->db, get_class($this) . "::getNextNumRef " . $obj->error);
353
                return "";
354
            }
355
        } else {
356
            print $langs->trans("Error") . " " . $langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
357
            return "";
358
        }
359
    }
360
361
    /**
362
     *  Create expedition en base
363
     *
364
     *  @param  User    $user       Object du user qui cree
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Expedition\Classes\User was not found. Did you mean User? If so, make sure to prefix the type with \.
Loading history...
365
     *  @param      int     $notrigger  1=Does not execute triggers, 0= execute triggers
366
     *  @return int                 Return integer <0 si erreur, id expedition creee si ok
367
     */
368
    public function create($user, $notrigger = 0)
369
    {
370
        global $conf, $hookmanager;
371
372
        $now = dol_now();
373
374
        require_once constant('DOL_DOCUMENT_ROOT') . '/product/stock/class/mouvementstock.class.php';
375
        $error = 0;
376
377
        // Clean parameters
378
        $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
379
        if (empty($this->fk_project)) {
380
            $this->fk_project = 0;
381
        }
382
        if (empty($this->date_shipping) && !empty($this->date_expedition)) {
383
            $this->date_shipping = $this->date_expedition;
384
        }
385
386
        $this->user = $user;
387
388
        $this->db->begin();
389
390
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "expedition (";
391
        $sql .= "ref";
392
        $sql .= ", entity";
393
        $sql .= ", ref_customer";
394
        $sql .= ", ref_ext";
395
        $sql .= ", date_creation";
396
        $sql .= ", fk_user_author";
397
        $sql .= ", date_expedition";
398
        $sql .= ", date_delivery";
399
        $sql .= ", fk_soc";
400
        $sql .= ", fk_projet";
401
        $sql .= ", fk_address";
402
        $sql .= ", fk_shipping_method";
403
        $sql .= ", tracking_number";
404
        $sql .= ", weight";
405
        $sql .= ", size";
406
        $sql .= ", width";
407
        $sql .= ", height";
408
        $sql .= ", weight_units";
409
        $sql .= ", size_units";
410
        $sql .= ", note_private";
411
        $sql .= ", note_public";
412
        $sql .= ", model_pdf";
413
        $sql .= ", fk_incoterms, location_incoterms";
414
        $sql .= ") VALUES (";
415
        $sql .= "'(PROV)'";
416
        $sql .= ", " . ((int) $conf->entity);
417
        $sql .= ", " . ($this->ref_customer ? "'" . $this->db->escape($this->ref_customer) . "'" : "null");
418
        $sql .= ", " . ($this->ref_ext ? "'" . $this->db->escape($this->ref_ext) . "'" : "null");
419
        $sql .= ", '" . $this->db->idate($now) . "'";
420
        $sql .= ", " . ((int) $user->id);
421
        $sql .= ", " . ($this->date_shipping > 0 ? "'" . $this->db->idate($this->date_shipping) . "'" : "null");
422
        $sql .= ", " . ($this->date_delivery > 0 ? "'" . $this->db->idate($this->date_delivery) . "'" : "null");
423
        $sql .= ", " . ($this->socid > 0 ? ((int) $this->socid) : "null");
424
        $sql .= ", " . ($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
425
        $sql .= ", " . ($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
426
        $sql .= ", " . ($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : "null");
427
        $sql .= ", '" . $this->db->escape($this->tracking_number) . "'";
428
        $sql .= ", " . (is_numeric($this->weight) ? $this->weight : 'NULL');
429
        $sql .= ", " . (is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
430
        $sql .= ", " . (is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
431
        $sql .= ", " . (is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
432
        $sql .= ", " . ($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
433
        $sql .= ", " . ($this->size_units != '' ? (int) $this->size_units : 'NULL');
434
        $sql .= ", " . (!empty($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null");
435
        $sql .= ", " . (!empty($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null");
436
        $sql .= ", " . (!empty($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null");
437
        $sql .= ", " . (int) $this->fk_incoterms;
438
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
439
        $sql .= ")";
440
441
        dol_syslog(get_class($this) . "::create", LOG_DEBUG);
442
        $resql = $this->db->query($sql);
443
        if ($resql) {
444
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . "expedition");
445
446
            $sql = "UPDATE " . MAIN_DB_PREFIX . "expedition";
447
            $sql .= " SET ref = '(PROV" . $this->id . ")'";
448
            $sql .= " WHERE rowid = " . ((int) $this->id);
449
450
            dol_syslog(get_class($this) . "::create", LOG_DEBUG);
451
            if ($this->db->query($sql)) {
452
                // Insert of lines
453
                $num = count($this->lines);
454
                for ($i = 0; $i < $num; $i++) {
455
                    if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
456
                        if (!isset($this->lines[$i]->detail_batch)) {   // no batch management
457
                            if ($this->create_line($this->lines[$i]->entrepot_id, $this->lines[$i]->origin_line_id, $this->lines[$i]->qty, $this->lines[$i]->rang, $this->lines[$i]->array_options) <= 0) {
458
                                $error++;
459
                            }
460
                        } else {    // with batch management
461
                            if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
462
                                $error++;
463
                            }
464
                        }
465
                    }
466
                }
467
468
                if (!$error && $this->id && $this->origin_id) {
469
                    $ret = $this->add_object_linked();
470
                    if (!$ret) {
471
                        $error++;
472
                    }
473
                }
474
475
                // Actions on extra fields
476
                if (!$error) {
477
                    $result = $this->insertExtraFields();
478
                    if ($result < 0) {
479
                        $error++;
480
                    }
481
                }
482
483
                if (!$error && !$notrigger) {
484
                    // Call trigger
485
                    $result = $this->call_trigger('SHIPPING_CREATE', $user);
486
                    if ($result < 0) {
487
                        $error++;
488
                    }
489
                    // End call triggers
490
491
                    if (!$error) {
492
                        $this->db->commit();
493
                        return $this->id;
494
                    } else {
495
                        foreach ($this->errors as $errmsg) {
496
                            dol_syslog(get_class($this) . "::create " . $errmsg, LOG_ERR);
497
                            $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
498
                        }
499
                        $this->db->rollback();
500
                        return -1 * $error;
501
                    }
502
                } else {
503
                    $error++;
504
                    $this->db->rollback();
505
                    return -3;
506
                }
507
            } else {
508
                $error++;
509
                $this->error = $this->db->lasterror() . " - sql=$sql";
510
                $this->db->rollback();
511
                return -2;
512
            }
513
        } else {
514
            $error++;
515
            $this->error = $this->db->error() . " - sql=$sql";
516
            $this->db->rollback();
517
            return -1;
518
        }
519
    }
520
521
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
522
    /**
523
     * Create a expedition line
524
     *
525
     * @param   int     $entrepot_id        Id of warehouse
526
     * @param   int     $origin_line_id     Id of source line
527
     * @param   float   $qty                Quantity
528
     * @param   int     $rang               Rang
529
     * @param   array   $array_options      extrafields array
530
     * @return  int                         Return integer <0 if KO, line_id if OK
531
     */
532
    public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [])
533
    {
534
		//phpcs:enable
535
        global $user;
536
537
        $expeditionline = new ExpeditionLigne($this->db);
538
        $expeditionline->fk_expedition = $this->id;
539
        $expeditionline->entrepot_id = $entrepot_id;
540
        $expeditionline->fk_elementdet = $origin_line_id;
541
        $expeditionline->element_type = $this->origin;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

541
        $expeditionline->element_type = /** @scrutinizer ignore-deprecated */ $this->origin;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
Documentation Bug introduced by
It seems like $this->origin can also be of type Dolibarr\Core\Base\CommonObject. However, the property $element_type is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
542
        $expeditionline->qty = $qty;
543
        $expeditionline->rang = $rang;
544
        $expeditionline->array_options = $array_options;
545
546
        if (($lineId = $expeditionline->insert($user)) < 0) {
547
            $this->errors[] = $expeditionline->error;
548
        }
549
        return $lineId;
550
    }
551
552
553
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
554
    /**
555
     * Create the detail of the expedition line. Create 1 record into expeditiondet for each warehouse and n record for each lot in this warehouse into expeditiondet_batch.
556
     *
557
     * @param   object      $line_ext           Object with full information of line. $line_ext->detail_batch must be an array of ExpeditionLineBatch
558
     * @param   array       $array_options      extrafields array
559
     * @return  int                             Return integer <0 if KO, >0 if OK
560
     */
561
    public function create_line_batch($line_ext, $array_options = [])
562
    {
563
		// phpcs:enable
564
        $error = 0;
565
        $stockLocationQty = array(); // associated array with batch qty in stock location
566
567
        $tab = $line_ext->detail_batch;
568
        // create stockLocation Qty array
569
        foreach ($tab as $detbatch) {
570
            if (!empty($detbatch->entrepot_id)) {
571
                if (empty($stockLocationQty[$detbatch->entrepot_id])) {
572
                    $stockLocationQty[$detbatch->entrepot_id] = 0;
573
                }
574
                $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
575
            }
576
        }
577
        // create shipment lines
578
        foreach ($stockLocationQty as $stockLocation => $qty) {
579
            $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
580
            if ($line_id < 0) {
581
                $error++;
582
            } else {
583
                // create shipment batch lines for stockLocation
584
                foreach ($tab as $detbatch) {
585
                    if ($detbatch->entrepot_id == $stockLocation) {
586
                        if (!($detbatch->create($line_id) > 0)) {       // Create an ExpeditionLineBatch
587
                            $this->errors = $detbatch->errors;
588
                            $error++;
589
                        }
590
                    }
591
                }
592
            }
593
        }
594
595
        if (!$error) {
596
            return 1;
597
        } else {
598
            return -1;
599
        }
600
    }
601
602
    /**
603
     *  Get object and lines from database
604
     *
605
     *  @param  int     $id         Id of object to load
606
     *  @param  string  $ref        Ref of object
607
     *  @param  string  $ref_ext    External reference of object
608
     *  @param  string  $notused    Internal reference of other object
609
     *  @return int                 >0 if OK, 0 if not found, <0 if KO
610
     */
611
    public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
612
    {
613
        global $conf;
614
615
        // Check parameters
616
        if (empty($id) && empty($ref) && empty($ref_ext)) {
617
            return -1;
618
        }
619
620
        $sql = "SELECT e.rowid, e.entity, e.ref, e.fk_soc as socid, e.date_creation, e.ref_customer, e.ref_ext, e.fk_user_author, e.fk_statut, e.fk_projet as fk_project, e.billed";
621
        $sql .= ", e.date_valid";
622
        $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
623
        $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
624
        $sql .= ", e.fk_shipping_method, e.tracking_number";
625
        $sql .= ", e.note_private, e.note_public";
626
        $sql .= ', e.fk_incoterms, e.location_incoterms';
627
        $sql .= ', e.signed_status';
628
        $sql .= ', i.libelle as label_incoterms';
629
        $sql .= ', s.libelle as shipping_method';
630
        $sql .= ", el.fk_source as origin_id, el.sourcetype as origin_type";
631
        $sql .= " FROM " . MAIN_DB_PREFIX . "expedition as e";
632
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "element_element as el ON el.fk_target = e.rowid AND el.targettype = '" . $this->db->escape($this->element) . "'";
633
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_incoterms as i ON e.fk_incoterms = i.rowid';
634
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
635
        $sql .= " WHERE e.entity IN (" . getEntity('expedition') . ")";
636
        if ($id) {
637
            $sql .= " AND e.rowid = " . ((int) $id);
638
        }
639
        if ($ref) {
640
            $sql .= " AND e.ref='" . $this->db->escape($ref) . "'";
641
        }
642
        if ($ref_ext) {
643
            $sql .= " AND e.ref_ext='" . $this->db->escape($ref_ext) . "'";
644
        }
645
646
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
647
        $result = $this->db->query($sql);
648
        if ($result) {
649
            if ($this->db->num_rows($result)) {
650
                $obj = $this->db->fetch_object($result);
651
652
                $this->id                   = $obj->rowid;
653
                $this->entity               = $obj->entity;
654
                $this->ref                  = $obj->ref;
655
                $this->socid                = $obj->socid;
656
                $this->ref_customer = $obj->ref_customer;
657
                $this->ref_ext          = $obj->ref_ext;
658
                $this->status               = $obj->fk_statut;
659
                $this->statut               = $this->status; // Deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

659
                /** @scrutinizer ignore-deprecated */ $this->statut               = $this->status; // Deprecated

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
660
                $this->user_author_id       = $obj->fk_user_author;
661
                $this->fk_user_author       = $obj->fk_user_author;
662
                $this->date_creation        = $this->db->jdate($obj->date_creation);
663
                $this->date_valid = $this->db->jdate($obj->date_valid);
664
                $this->date                 = $this->db->jdate($obj->date_expedition); // TODO deprecated
665
                $this->date_expedition      = $this->db->jdate($obj->date_expedition); // TODO deprecated
666
                $this->date_shipping        = $this->db->jdate($obj->date_expedition); // Date real
667
                $this->date_delivery        = $this->db->jdate($obj->date_delivery); // Date planned
668
                $this->fk_delivery_address  = $obj->fk_address;
669
                $this->model_pdf            = $obj->model_pdf;
670
                $this->shipping_method_id   = $obj->fk_shipping_method;
671
                $this->shipping_method = $obj->shipping_method;
672
                $this->tracking_number      = $obj->tracking_number;
673
                $this->origin               = ($obj->origin_type ? $obj->origin_type : 'commande'); // For compatibility
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

673
                /** @scrutinizer ignore-deprecated */ $this->origin               = ($obj->origin_type ? $obj->origin_type : 'commande'); // For compatibility

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
674
                $this->origin_type          = ($obj->origin_type ? $obj->origin_type : 'commande');
675
                $this->origin_id            = $obj->origin_id;
676
                $this->billed               = $obj->billed;
677
                $this->fk_project = $obj->fk_project;
678
                $this->signed_status        = $obj->signed_status;
679
                $this->trueWeight           = $obj->weight;
680
                $this->weight_units         = $obj->weight_units;
681
682
                $this->trueWidth            = $obj->width;
683
                $this->width_units          = $obj->size_units;
684
                $this->trueHeight           = $obj->height;
685
                $this->height_units         = $obj->size_units;
686
                $this->trueDepth            = $obj->size;
687
                $this->depth_units          = $obj->size_units;
688
689
                $this->note_public          = $obj->note_public;
690
                $this->note_private         = $obj->note_private;
691
692
                // A denormalized value
693
                $this->trueSize             = $obj->size . "x" . $obj->width . "x" . $obj->height;
694
                $this->size_units           = $obj->size_units;
695
696
                //Incoterms
697
                $this->fk_incoterms         = $obj->fk_incoterms;
698
                $this->location_incoterms   = $obj->location_incoterms;
699
                $this->label_incoterms      = $obj->label_incoterms;
700
701
                $this->db->free($result);
702
703
                // Tracking url
704
                $this->getUrlTrackingStatus($obj->tracking_number);
705
706
                // Thirdparty
707
                $result = $this->fetch_thirdparty(); // TODO Remove this
708
709
                // Retrieve extrafields
710
                $this->fetch_optionals();
711
712
                // Fix Get multicurrency param for transmitted
713
                if (isModEnabled('multicurrency')) {
714
                    if (!empty($this->multicurrency_code)) {
715
                        $this->multicurrency_code = $this->thirdparty->multicurrency_code;
716
                    }
717
                    if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($this->thirdparty->multicurrency_tx)) {
718
                        $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
719
                    }
720
                }
721
722
                /*
723
                 * Lines
724
                 */
725
                $result = $this->fetch_lines();
726
                if ($result < 0) {
727
                    return -3;
728
                }
729
730
                return 1;
731
            } else {
732
                dol_syslog(get_class($this) . '::Fetch no expedition found', LOG_ERR);
733
                $this->error = 'Shipment with id ' . $id . ' not found';
734
                return 0;
735
            }
736
        } else {
737
            $this->error = $this->db->error();
738
            return -1;
739
        }
740
    }
741
742
    /**
743
     *  Validate object and update stock if option enabled
744
     *
745
     *  @param      User        $user       Object user that validate
746
     *  @param      int         $notrigger  1=Does not execute triggers, 0= execute triggers
747
     *  @return     int                     Return integer <0 if OK, >0 if KO
748
     */
749
    public function valid($user, $notrigger = 0)
750
    {
751
        global $conf;
752
753
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
754
755
        dol_syslog(get_class($this) . "::valid");
756
757
        // Protection
758
        if ($this->status) {
759
            dol_syslog(get_class($this) . "::valid not in draft status", LOG_WARNING);
760
            return 0;
761
        }
762
763
        if (
764
            !((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'creer'))
765
            || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'shipping_advance', 'validate')))
766
        ) {
767
            $this->error = 'Permission denied';
768
            dol_syslog(get_class($this) . "::valid " . $this->error, LOG_ERR);
769
            return -1;
770
        }
771
772
        $this->db->begin();
773
774
        $error = 0;
775
776
        // Define new ref
777
        $soc = new Societe($this->db);
778
        $soc->fetch($this->socid);
779
780
        // Class of company linked to order
781
        $result = $soc->setAsCustomer();
782
783
        // Define new ref
784
        if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
785
            $numref = $this->getNextNumRef($soc);
786
        } elseif (!empty($this->ref)) {
787
            $numref = $this->ref;
788
        } else {
789
            $numref = "EXP" . $this->id;
790
        }
791
        $this->newref = dol_sanitizeFileName($numref);
792
793
        $now = dol_now();
794
795
        // Validate
796
        $sql = "UPDATE " . MAIN_DB_PREFIX . "expedition SET";
797
        $sql .= " ref='" . $this->db->escape($numref) . "'";
798
        $sql .= ", fk_statut = 1";
799
        $sql .= ", date_valid = '" . $this->db->idate($now) . "'";
800
        $sql .= ", fk_user_valid = " . $user->id;
801
        $sql .= " WHERE rowid = " . ((int) $this->id);
802
803
        dol_syslog(get_class($this) . "::valid update expedition", LOG_DEBUG);
804
        $resql = $this->db->query($sql);
805
        if (!$resql) {
806
            $this->error = $this->db->lasterror();
807
            $error++;
808
        }
809
810
        // If stock increment is done on sending (recommended choice)
811
        if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
812
            $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
813
            if ($result < 0) {
814
                return -2;
815
            }
816
        }
817
818
        // Change status of order to "shipment in process"
819
        $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

819
        $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, /** @scrutinizer ignore-deprecated */ $this->origin);

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
820
        if (!$ret) {
821
            $error++;
822
        }
823
824
        if (!$error && !$notrigger) {
825
            // Call trigger
826
            $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
827
            if ($result < 0) {
828
                $error++;
829
            }
830
            // End call triggers
831
        }
832
833
        if (!$error) {
834
            $this->oldref = $this->ref;
835
836
            // Rename directory if dir was a temporary ref
837
            if (preg_match('/^[\(]?PROV/i', $this->ref)) {
838
                // Now we rename also files into index
839
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filename = CONCAT('" . $this->db->escape($this->newref) . "', SUBSTR(filename, " . (strlen($this->ref) + 1) . ")), filepath = 'expedition/sending/" . $this->db->escape($this->newref) . "'";
840
                $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'expedition/sending/" . $this->db->escape($this->ref) . "' and entity = " . ((int) $conf->entity);
841
                $resql = $this->db->query($sql);
842
                if (!$resql) {
843
                    $error++;
844
                    $this->error = $this->db->lasterror();
845
                }
846
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'expedition/sending/" . $this->db->escape($this->newref) . "'";
847
                $sql .= " WHERE filepath = 'expedition/sending/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
848
                $resql = $this->db->query($sql);
849
                if (!$resql) {
850
                    $error++;
851
                    $this->error = $this->db->lasterror();
852
                }
853
854
                // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
855
                $oldref = dol_sanitizeFileName($this->ref);
856
                $newref = dol_sanitizeFileName($numref);
857
                $dirsource = $conf->expedition->dir_output . '/sending/' . $oldref;
858
                $dirdest = $conf->expedition->dir_output . '/sending/' . $newref;
859
                if (!$error && file_exists($dirsource)) {
860
                    dol_syslog(get_class($this) . "::valid rename dir " . $dirsource . " into " . $dirdest);
861
862
                    if (@rename($dirsource, $dirdest)) {
863
                        dol_syslog("Rename ok");
864
                        // Rename docs starting with $oldref with $newref
865
                        $listoffiles = dol_dir_list($conf->expedition->dir_output . '/sending/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
866
                        foreach ($listoffiles as $fileentry) {
867
                            $dirsource = $fileentry['name'];
868
                            $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
869
                            $dirsource = $fileentry['path'] . '/' . $dirsource;
870
                            $dirdest = $fileentry['path'] . '/' . $dirdest;
871
                            @rename($dirsource, $dirdest);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

871
                            /** @scrutinizer ignore-unhandled */ @rename($dirsource, $dirdest);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
872
                        }
873
                    }
874
                }
875
            }
876
        }
877
878
        // Set new ref and current status
879
        if (!$error) {
880
            $this->ref = $numref;
881
            $this->statut = self::STATUS_VALIDATED;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

881
            /** @scrutinizer ignore-deprecated */ $this->statut = self::STATUS_VALIDATED;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
882
            $this->status = self::STATUS_VALIDATED;
883
        }
884
885
        if (!$error) {
886
            $this->db->commit();
887
            return 1;
888
        } else {
889
            $this->db->rollback();
890
            return -1 * $error;
891
        }
892
    }
893
894
895
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
896
    /**
897
     *  Create a delivery receipt from a shipment
898
     *
899
     *  @param  User    $user       User
900
     *  @return int                 Return integer <0 if KO, >=0 if OK
901
     */
902
    public function create_delivery($user)
903
    {
904
		// phpcs:enable
905
        global $conf;
906
907
        if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
908
            if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

908
            if ($this->statut == self::STATUS_VALIDATED || /** @scrutinizer ignore-deprecated */ $this->statut == self::STATUS_CLOSED) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
909
                // Expedition validee
910
                include_once DOL_DOCUMENT_ROOT . '/delivery/class/delivery.class.php';
911
                $delivery = new Delivery($this->db);
912
                $result = $delivery->create_from_sending($user, $this->id);
913
                if ($result > 0) {
914
                    return $result;
915
                } else {
916
                    $this->error = $delivery->error;
917
                    return $result;
918
                }
919
            } else {
920
                return 0;
921
            }
922
        } else {
923
            return 0;
924
        }
925
    }
926
927
    /**
928
     * Add an expedition line.
929
     * If STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS is set, you can add a shipment line, with no stock source defined
930
     * If STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT is not set, you can add a shipment line, even if not enough into stock
931
     * Note: For product that need a batch number, you must use addline_batch()
932
     *
933
     * @param   int     $entrepot_id        Id of warehouse
934
     * @param   int     $id                 Id of source line (order line)
935
     * @param   float   $qty                Quantity
936
     * @param   array   $array_options      extrafields array
937
     * @return  int                         Return integer <0 if KO, >0 if OK
938
     */
939
    public function addline($entrepot_id, $id, $qty, $array_options = [])
940
    {
941
        global $conf, $langs;
942
943
        $num = count($this->lines);
944
        $line = new ExpeditionLigne($this->db);
945
946
        $line->entrepot_id = $entrepot_id;
947
        $line->origin_line_id = $id;
948
        $line->fk_elementdet = $id;
949
        $line->element_type = 'order';
950
        $line->qty = $qty;
951
952
        $orderline = new OrderLine($this->db);
953
        $orderline->fetch($id);
954
955
        // Copy the rang of the order line to the expedition line
956
        $line->rang = $orderline->rang;
957
        $line->product_type = $orderline->product_type;
958
959
        if (isModEnabled('stock') && !empty($orderline->fk_product)) {
960
            $fk_product = $orderline->fk_product;
961
962
            if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS') && !(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') && $line->product_type == Product::TYPE_SERVICE)) {
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Expedition\Classes\Product was not found. Did you mean Product? If so, make sure to prefix the type with \.
Loading history...
963
                $langs->load("errors");
964
                $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
965
                return -1;
966
            }
967
968
            if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
969
                $product = new Product($this->db);
970
                $product->fetch($fk_product);
971
972
                // Check must be done for stock of product into warehouse if $entrepot_id defined
973
                if ($entrepot_id > 0) {
974
                    $product->load_stock('warehouseopen');
975
                    $product_stock = $product->stock_warehouse[$entrepot_id]->real;
976
                } else {
977
                    $product_stock = $product->stock_reel;
978
                }
979
980
                $product_type = $product->type;
981
                if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
982
                    $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
983
                    // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
984
                    if (!$isavirtualproduct || !getDolGlobalString('PRODUIT_SOUSPRODUITS') || ($isavirtualproduct && !getDolGlobalString('STOCK_EXCLUDE_VIRTUAL_PRODUCTS'))) {  // If STOCK_EXCLUDE_VIRTUAL_PRODUCTS is set, we do not manage stock for kits/virtual products.
985
                        if ($product_stock < $qty) {
986
                            $langs->load("errors");
987
                            $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
988
                            $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
989
990
                            $this->db->rollback();
991
                            return -3;
992
                        }
993
                    }
994
                }
995
            }
996
        }
997
998
        // If product need a batch number, we should not have called this function but addline_batch instead.
999
        // If this happen, we may have a bug in card.php page
1000
        if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
1001
            $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH ' . $orderline->id . ' ' . $orderline->fk_product; //
1002
            return -4;
1003
        }
1004
1005
        // extrafields
1006
        if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1007
            $line->array_options = $array_options;
1008
        }
1009
1010
        $this->lines[$num] = $line;
1011
1012
        return 1;
1013
    }
1014
1015
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1016
    /**
1017
     * Add a shipment line with batch record
1018
     *
1019
     * @param   array       $dbatch     Array of value (key 'detail' -> Array, key 'qty' total quantity for line, key ix_l : original line index)
1020
     * @param   array       $array_options      extrafields array
1021
     * @return  int                     Return integer <0 if KO, >0 if OK
1022
     */
1023
    public function addline_batch($dbatch, $array_options = [])
1024
    {
1025
		// phpcs:enable
1026
        global $conf, $langs;
1027
1028
        $num = count($this->lines);
1029
        if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1030
            $line = new ExpeditionLigne($this->db);
1031
            $tab = array();
1032
            foreach ($dbatch['detail'] as $key => $value) {
1033
                if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1034
                    // $value['q']=qty to move
1035
                    // $value['id_batch']=id into llx_product_batch of record to move
1036
                    //var_dump($value);
1037
1038
                    $linebatch = new ExpeditionLineBatch($this->db);
1039
                    $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1040
                    if ($ret < 0) {
1041
                        $this->setErrorsFromObject($linebatch);
1042
                        return -1;
1043
                    }
1044
                    $linebatch->qty = $value['q'];
1045
                    if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1046
                        $linebatch->batch = null;
1047
                    }
1048
                    $tab[] = $linebatch;
1049
1050
                    if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1051
                        require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/productbatch.class.php';
1052
                        $prod_batch = new Productbatch($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Expedition\Classes\Productbatch was not found. Did you mean Productbatch? If so, make sure to prefix the type with \.
Loading history...
1053
                        $prod_batch->fetch($value['id_batch']);
1054
1055
                        if ($prod_batch->qty < $linebatch->qty) {
1056
                            $langs->load("errors");
1057
                            $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1058
                            dol_syslog(get_class($this) . "::addline_batch error=Product " . $prod_batch->batch . ": " . $this->errorsToString(), LOG_ERR);
1059
                            $this->db->rollback();
1060
                            return -1;
1061
                        }
1062
                    }
1063
1064
                    //var_dump($linebatch);
1065
                }
1066
            }
1067
            $line->entrepot_id = $linebatch->entrepot_id;
1068
            $line->origin_line_id = $dbatch['ix_l']; // deprecated
1069
            $line->fk_elementdet = $dbatch['ix_l'];
1070
            $line->qty = $dbatch['qty'];
1071
            $line->detail_batch = $tab;
1072
1073
            // extrafields
1074
            if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1075
                $line->array_options = $array_options;
1076
            }
1077
1078
            //var_dump($line);
1079
            $this->lines[$num] = $line;
1080
            return 1;
1081
        }
1082
        return 0;
1083
    }
1084
1085
    /**
1086
     *  Update database
1087
     *
1088
     *  @param  User    $user           User that modify
1089
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
1090
     *  @return int                     Return integer <0 if KO, >0 if OK
1091
     */
1092
    public function update($user = null, $notrigger = 0)
1093
    {
1094
        global $conf;
1095
        $error = 0;
1096
1097
        // Clean parameters
1098
1099
        if (isset($this->ref)) {
1100
            $this->ref = trim($this->ref);
1101
        }
1102
        if (isset($this->entity)) {
1103
            $this->entity = (int) $this->entity;
1104
        }
1105
        if (isset($this->ref_customer)) {
1106
            $this->ref_customer = trim($this->ref_customer);
1107
        }
1108
        if (isset($this->socid)) {
1109
            $this->socid = (int) $this->socid;
1110
        }
1111
        if (isset($this->fk_user_author)) {
1112
            $this->fk_user_author = (int) $this->fk_user_author;
1113
        }
1114
        if (isset($this->fk_user_valid)) {
1115
            $this->fk_user_valid = (int) $this->fk_user_valid;
1116
        }
1117
        if (isset($this->fk_delivery_address)) {
1118
            $this->fk_delivery_address = (int) $this->fk_delivery_address;
1119
        }
1120
        if (isset($this->shipping_method_id)) {
1121
            $this->shipping_method_id = (int) $this->shipping_method_id;
1122
        }
1123
        if (isset($this->tracking_number)) {
1124
            $this->tracking_number = trim($this->tracking_number);
1125
        }
1126
        if (isset($this->statut)) {
1127
            $this->statut = (int) $this->statut;
1128
        }
1129
        if (isset($this->trueDepth)) {
1130
            $this->trueDepth = trim($this->trueDepth);
1131
        }
1132
        if (isset($this->trueWidth)) {
1133
            $this->trueWidth = trim($this->trueWidth);
1134
        }
1135
        if (isset($this->trueHeight)) {
1136
            $this->trueHeight = trim($this->trueHeight);
1137
        }
1138
        if (isset($this->size_units)) {
1139
            $this->size_units = trim($this->size_units);
1140
        }
1141
        if (isset($this->weight_units)) {
1142
            $this->weight_units = trim($this->weight_units);
1143
        }
1144
        if (isset($this->trueWeight)) {
1145
            $this->weight = trim((string) $this->trueWeight);
1146
        }
1147
        if (isset($this->note_private)) {
1148
            $this->note_private = trim($this->note_private);
1149
        }
1150
        if (isset($this->note_public)) {
1151
            $this->note_public = trim($this->note_public);
1152
        }
1153
        if (isset($this->model_pdf)) {
1154
            $this->model_pdf = trim($this->model_pdf);
1155
        }
1156
1157
        // Check parameters
1158
        // Put here code to add control on parameters values
1159
1160
        // Update request
1161
        $sql = "UPDATE " . MAIN_DB_PREFIX . "expedition SET";
1162
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
1163
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
1164
        $sql .= " ref_customer=" . (isset($this->ref_customer) ? "'" . $this->db->escape($this->ref_customer) . "'" : "null") . ",";
1165
        $sql .= " fk_soc=" . (isset($this->socid) ? $this->socid : "null") . ",";
1166
        $sql .= " date_creation=" . (dol_strlen($this->date_creation) != 0 ? "'" . $this->db->idate($this->date_creation) . "'" : 'null') . ",";
1167
        $sql .= " fk_user_author=" . (isset($this->fk_user_author) ? $this->fk_user_author : "null") . ",";
1168
        $sql .= " date_valid=" . (dol_strlen($this->date_valid) != 0 ? "'" . $this->db->idate($this->date_valid) . "'" : 'null') . ",";
1169
        $sql .= " fk_user_valid=" . (isset($this->fk_user_valid) ? $this->fk_user_valid : "null") . ",";
1170
        $sql .= " date_expedition=" . (dol_strlen($this->date_expedition) != 0 ? "'" . $this->db->idate($this->date_expedition) . "'" : 'null') . ",";
1171
        $sql .= " date_delivery=" . (dol_strlen($this->date_delivery) != 0 ? "'" . $this->db->idate($this->date_delivery) . "'" : 'null') . ",";
1172
        $sql .= " fk_address=" . (isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null") . ",";
1173
        $sql .= " fk_shipping_method=" . ((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null") . ",";
1174
        $sql .= " tracking_number=" . (isset($this->tracking_number) ? "'" . $this->db->escape($this->tracking_number) . "'" : "null") . ",";
1175
        $sql .= " fk_statut=" . (isset($this->statut) ? $this->statut : "null") . ",";
1176
        $sql .= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
1177
        $sql .= " height=" . (($this->trueHeight != '') ? $this->trueHeight : "null") . ",";
1178
        $sql .= " width=" . (($this->trueWidth != '') ? $this->trueWidth : "null") . ",";
1179
        $sql .= " size_units=" . (isset($this->size_units) ? $this->size_units : "null") . ",";
1180
        $sql .= " size=" . (($this->trueDepth != '') ? $this->trueDepth : "null") . ",";
1181
        $sql .= " weight_units=" . (isset($this->weight_units) ? $this->weight_units : "null") . ",";
1182
        $sql .= " weight=" . (($this->trueWeight != '') ? $this->trueWeight : "null") . ",";
1183
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
1184
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
1185
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
1186
        $sql .= " entity=" . $conf->entity;
1187
        $sql .= " WHERE rowid=" . ((int) $this->id);
1188
1189
        $this->db->begin();
1190
1191
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
1192
        $resql = $this->db->query($sql);
1193
        if (!$resql) {
1194
            $error++;
1195
            $this->errors[] = "Error " . $this->db->lasterror();
1196
        }
1197
1198
        if (!$error && !$notrigger) {
1199
            // Call trigger
1200
            $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1201
            if ($result < 0) {
1202
                $error++;
1203
            }
1204
            // End call triggers
1205
        }
1206
1207
        // Commit or rollback
1208
        if ($error) {
1209
            foreach ($this->errors as $errmsg) {
1210
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
1211
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1212
            }
1213
            $this->db->rollback();
1214
            return -1 * $error;
1215
        } else {
1216
            $this->db->commit();
1217
            return 1;
1218
        }
1219
    }
1220
1221
1222
    /**
1223
     *  Cancel shipment.
1224
     *
1225
     *  @param  int  $notrigger             Disable triggers
1226
     *  @param  bool $also_update_stock     true if the stock should be increased back (false by default)
1227
     *  @return int                         >0 if OK, 0 if deletion done but failed to delete files, <0 if KO
1228
     */
1229
    public function cancel($notrigger = 0, $also_update_stock = false)
1230
    {
1231
        global $conf, $langs, $user;
1232
1233
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1234
1235
        $error = 0;
1236
        $this->error = '';
1237
1238
        $this->db->begin();
1239
1240
        // Add a protection to refuse deleting if shipment has at least one delivery
1241
        $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1242
        if (count($this->linkedObjectsIds) > 0) {
1243
            $this->error = 'ErrorThereIsSomeDeliveries';
1244
            $error++;
1245
        }
1246
1247
        if (!$error && !$notrigger) {
1248
            // Call trigger
1249
            $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1250
            if ($result < 0) {
1251
                $error++;
1252
            }
1253
            // End call triggers
1254
        }
1255
1256
        // Stock control
1257
        if (
1258
            !$error && isModEnabled('stock') &&
1259
            ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1259
            ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && /** @scrutinizer ignore-deprecated */ $this->statut > self::STATUS_DRAFT) ||

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1260
                (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1260
                (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && /** @scrutinizer ignore-deprecated */ $this->statut == self::STATUS_CLOSED && $also_update_stock))

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1261
        ) {
1262
            require_once DOL_DOCUMENT_ROOT . "/product/stock/class/mouvementstock.class.php";
1263
1264
            $langs->load("agenda");
1265
1266
            // Loop on each product line to add a stock movement and delete features
1267
            $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1268
            $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
1269
            $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
1270
            $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
1271
            $sql .= " AND cd.rowid = ed.fk_elementdet";
1272
1273
            dol_syslog(get_class($this) . "::delete select details", LOG_DEBUG);
1274
            $resql = $this->db->query($sql);
1275
            if ($resql) {
1276
                $cpt = $this->db->num_rows($resql);
1277
1278
                $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1279
1280
                for ($i = 0; $i < $cpt; $i++) {
1281
                    dol_syslog(get_class($this) . "::delete movement index " . $i);
1282
                    $obj = $this->db->fetch_object($resql);
1283
1284
                    $mouvS = new MouvementStock($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Expedition\Classes\MouvementStock was not found. Did you mean MouvementStock? If so, make sure to prefix the type with \.
Loading history...
1285
                    // we do not log origin because it will be deleted
1286
                    $mouvS->origin = '';
1287
                    // get lot/serial
1288
                    $lotArray = null;
1289
                    if (isModEnabled('productbatch')) {
1290
                        $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1291
                        if (!is_array($lotArray)) {
1292
                            $error++;
1293
                            $this->errors[] = "Error " . $this->db->lasterror();
1294
                        }
1295
                    }
1296
1297
                    if (empty($lotArray)) {
1298
                        // no lot/serial
1299
                        // We increment stock of product (and sub-products)
1300
                        // We use warehouse selected for each line
1301
                        $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref)); // Price is set to 0, because we don't want to see WAP changed
1302
                        if ($result < 0) {
1303
                            $error++;
1304
                            $this->errors = array_merge($this->errors, $mouvS->errors);
1305
                            break;
1306
                        }
1307
                    } else {
1308
                        // We increment stock of batches
1309
                        // We use warehouse selected for each line
1310
                        foreach ($lotArray as $lot) {
1311
                            $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentCanceledInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch); // Price is set to 0, because we don't want to see WAP changed
1312
                            if ($result < 0) {
1313
                                $error++;
1314
                                $this->errors = array_merge($this->errors, $mouvS->errors);
1315
                                break;
1316
                            }
1317
                        }
1318
                        if ($error) {
1319
                            break; // break for loop in case of error
1320
                        }
1321
                    }
1322
                }
1323
            } else {
1324
                $error++;
1325
                $this->errors[] = "Error " . $this->db->lasterror();
1326
            }
1327
        }
1328
1329
        // delete batch expedition line
1330
        if (!$error && isModEnabled('productbatch')) {
1331
            $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1332
            if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1333
                $error++;
1334
                $this->errors[] = "Error " . $this->db->lasterror();
1335
            }
1336
        }
1337
1338
1339
        if (!$error) {
1340
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "expeditiondet";
1341
            $sql .= " WHERE fk_expedition = " . ((int) $this->id);
1342
1343
            if ($this->db->query($sql)) {
1344
                // Delete linked object
1345
                $res = $this->deleteObjectLinked();
1346
                if ($res < 0) {
1347
                    $error++;
1348
                }
1349
1350
                // No delete expedition
1351
                if (!$error) {
1352
                    $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "expedition";
1353
                    $sql .= " WHERE rowid = " . ((int) $this->id);
1354
1355
                    if ($this->db->query($sql)) {
1356
                        if (!empty($this->origin) && $this->origin_id > 0) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1356
                        if (!empty(/** @scrutinizer ignore-deprecated */ $this->origin) && $this->origin_id > 0) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1357
                            $this->fetch_origin();
1358
                            if ($this->origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) {     // If order source of shipment is "shipment in progress"
1359
                                // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1360
                                $this->origin_object->loadExpeditions();
1361
                                //var_dump($this->$origin->expeditions);exit;
1362
                                if (count($this->origin_object->expeditions) <= 0) {
1363
                                    $this->origin_object->setStatut(Commande::STATUS_VALIDATED);
1364
                                }
1365
                            }
1366
                        }
1367
1368
                        if (!$error) {
1369
                            $this->db->commit();
1370
1371
                            // We delete PDFs
1372
                            $ref = dol_sanitizeFileName($this->ref);
1373
                            if (!empty($conf->expedition->dir_output)) {
1374
                                $dir = $conf->expedition->dir_output . '/sending/' . $ref;
1375
                                $file = $dir . '/' . $ref . '.pdf';
1376
                                if (file_exists($file)) {
1377
                                    if (!dol_delete_file($file)) {
1378
                                        return 0;
1379
                                    }
1380
                                }
1381
                                if (file_exists($dir)) {
1382
                                    if (!dol_delete_dir_recursive($dir)) {
1383
                                        $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1384
                                        return 0;
1385
                                    }
1386
                                }
1387
                            }
1388
1389
                            return 1;
1390
                        } else {
1391
                            $this->db->rollback();
1392
                            return -1;
1393
                        }
1394
                    } else {
1395
                        $this->error = $this->db->lasterror() . " - sql=$sql";
1396
                        $this->db->rollback();
1397
                        return -3;
1398
                    }
1399
                } else {
1400
                    $this->error = $this->db->lasterror() . " - sql=$sql";
1401
                    $this->db->rollback();
1402
                    return -2;
1403
                }//*/
1404
            } else {
1405
                $this->error = $this->db->lasterror() . " - sql=$sql";
1406
                $this->db->rollback();
1407
                return -1;
1408
            }
1409
        } else {
1410
            $this->db->rollback();
1411
            return -1;
1412
        }
1413
    }
1414
1415
    /**
1416
     *  Delete shipment.
1417
     *  Warning, do not delete a shipment if a delivery is linked to (with table llx_element_element)
1418
     *
1419
     *  @param  User    $user                   User making the deletion
1420
     *  @param  int     $notrigger              Disable triggers
1421
     *  @param  bool    $also_update_stock      true if the stock should be increased back (false by default)
1422
     *  @return int                             >0 if OK, 0 if deletion done but failed to delete files, <0 if KO
1423
     */
1424
    public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1425
    {
1426
        global $conf, $langs;
1427
1428
        if (empty($user)) {
1429
            global $user;
1430
        }
1431
1432
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1433
1434
        $error = 0;
1435
        $this->error = '';
1436
1437
        $this->db->begin();
1438
1439
        // Add a protection to refuse deleting if shipment has at least one delivery
1440
        $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1441
        if (count($this->linkedObjectsIds) > 0) {
1442
            $this->error = 'ErrorThereIsSomeDeliveries';
1443
            $error++;
1444
        }
1445
1446
        if (!$error && !$notrigger) {
1447
            // Call trigger
1448
            $result = $this->call_trigger('SHIPPING_DELETE', $user);
1449
            if ($result < 0) {
1450
                $error++;
1451
            }
1452
            // End call triggers
1453
        }
1454
1455
        // Stock control
1456
        if (
1457
            !$error && isModEnabled('stock') &&
1458
            ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && $this->statut > self::STATUS_DRAFT) ||
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1458
            ((getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') && /** @scrutinizer ignore-deprecated */ $this->statut > self::STATUS_DRAFT) ||

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1459
                (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && $this->statut == self::STATUS_CLOSED && $also_update_stock))
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1459
                (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE') && /** @scrutinizer ignore-deprecated */ $this->statut == self::STATUS_CLOSED && $also_update_stock))

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1460
        ) {
1461
            require_once DOL_DOCUMENT_ROOT . "/product/stock/class/mouvementstock.class.php";
1462
1463
            $langs->load("agenda");
1464
1465
            // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1466
            $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1467
1468
            // Loop on each product line to add a stock movement
1469
            $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1470
            $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
1471
            $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
1472
            $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
1473
            $sql .= " AND cd.rowid = ed.fk_elementdet";
1474
1475
            dol_syslog(get_class($this) . "::delete select details", LOG_DEBUG);
1476
            $resql = $this->db->query($sql);
1477
            if ($resql) {
1478
                $cpt = $this->db->num_rows($resql);
1479
                for ($i = 0; $i < $cpt; $i++) {
1480
                    dol_syslog(get_class($this) . "::delete movement index " . $i);
1481
                    $obj = $this->db->fetch_object($resql);
1482
1483
                    $mouvS = new MouvementStock($this->db);
1484
                    // we do not log origin because it will be deleted
1485
                    $mouvS->origin = '';
1486
                    // get lot/serial
1487
                    $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1488
                    if (!is_array($lotArray)) {
1489
                        $error++;
1490
                        $this->errors[] = "Error " . $this->db->lasterror();
1491
                    }
1492
                    if (empty($lotArray)) {
1493
                        // no lot/serial
1494
                        // We increment stock of product (and sub-products)
1495
                        // We use warehouse selected for each line
1496
                        $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref)); // Price is set to 0, because we don't want to see WAP changed
1497
                        if ($result < 0) {
1498
                            $error++;
1499
                            $this->errors = array_merge($this->errors, $mouvS->errors);
1500
                            break;
1501
                        }
1502
                    } else {
1503
                        // We increment stock of batches
1504
                        // We use warehouse selected for each line
1505
                        foreach ($lotArray as $lot) {
1506
                            $result = $mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch); // Price is set to 0, because we don't want to see WAP changed
1507
                            if ($result < 0) {
1508
                                $error++;
1509
                                $this->errors = array_merge($this->errors, $mouvS->errors);
1510
                                break;
1511
                            }
1512
                        }
1513
                        if ($error) {
1514
                            break; // break for loop in case of error
1515
                        }
1516
                    }
1517
                }
1518
            } else {
1519
                $error++;
1520
                $this->errors[] = "Error " . $this->db->lasterror();
1521
            }
1522
        }
1523
1524
        // delete batch expedition line
1525
        if (!$error) {
1526
            $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1527
            if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1528
                $error++;
1529
                $this->errors[] = "Error " . $this->db->lasterror();
1530
            }
1531
        }
1532
1533
        if (!$error) {
1534
            $main = MAIN_DB_PREFIX . 'expeditiondet';
1535
            $ef = $main . "_extrafields";
1536
            $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = " . ((int) $this->id) . ")";
1537
1538
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "expeditiondet";
1539
            $sql .= " WHERE fk_expedition = " . ((int) $this->id);
1540
1541
            if ($this->db->query($sqlef) && $this->db->query($sql)) {
1542
                // Delete linked object
1543
                $res = $this->deleteObjectLinked();
1544
                if ($res < 0) {
1545
                    $error++;
1546
                }
1547
1548
                // delete extrafields
1549
                $res = $this->deleteExtraFields();
1550
                if ($res < 0) {
1551
                    $error++;
1552
                }
1553
1554
                if (!$error) {
1555
                    $sql = "DELETE FROM " . MAIN_DB_PREFIX . "expedition";
1556
                    $sql .= " WHERE rowid = " . ((int) $this->id);
1557
1558
                    if ($this->db->query($sql)) {
1559
                        if (!empty($this->origin) && $this->origin_id > 0) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1559
                        if (!empty(/** @scrutinizer ignore-deprecated */ $this->origin) && $this->origin_id > 0) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1560
                            $this->fetch_origin();
1561
                            if ($this->origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) {     // If order source of shipment is "shipment in progress"
1562
                                // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1563
                                $this->origin_object->loadExpeditions();
1564
                                //var_dump($this->$origin->expeditions);exit;
1565
                                if (count($this->origin_object->expeditions) <= 0) {
1566
                                    $this->origin_object->setStatut(Commande::STATUS_VALIDATED);
1567
                                }
1568
                            }
1569
                        }
1570
1571
                        if (!$error) {
1572
                            $this->db->commit();
1573
1574
                            // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1575
                            $this->deleteEcmFiles(0);    // Deleting files physically is done later with the dol_delete_dir_recursive
1576
                            $this->deleteEcmFiles(1);    // Deleting files physically is done later with the dol_delete_dir_recursive
1577
1578
                            // We delete PDFs
1579
                            $ref = dol_sanitizeFileName($this->ref);
1580
                            if (!empty($conf->expedition->dir_output)) {
1581
                                $dir = $conf->expedition->dir_output . '/sending/' . $ref;
1582
                                $file = $dir . '/' . $ref . '.pdf';
1583
                                if (file_exists($file)) {
1584
                                    if (!dol_delete_file($file)) {
1585
                                        return 0;
1586
                                    }
1587
                                }
1588
                                if (file_exists($dir)) {
1589
                                    if (!dol_delete_dir_recursive($dir)) {
1590
                                        $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1591
                                        return 0;
1592
                                    }
1593
                                }
1594
                            }
1595
1596
                            return 1;
1597
                        } else {
1598
                            $this->db->rollback();
1599
                            return -1;
1600
                        }
1601
                    } else {
1602
                        $this->error = $this->db->lasterror() . " - sql=$sql";
1603
                        $this->db->rollback();
1604
                        return -3;
1605
                    }
1606
                } else {
1607
                    $this->error = $this->db->lasterror() . " - sql=$sql";
1608
                    $this->db->rollback();
1609
                    return -2;
1610
                }
1611
            } else {
1612
                $this->error = $this->db->lasterror() . " - sql=$sql";
1613
                $this->db->rollback();
1614
                return -1;
1615
            }
1616
        } else {
1617
            $this->db->rollback();
1618
            return -1;
1619
        }
1620
    }
1621
1622
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1623
    /**
1624
     *  Load lines
1625
     *
1626
     *  @return int     >0 if OK, Otherwise if KO
1627
     */
1628
    public function fetch_lines()
1629
    {
1630
		// phpcs:enable
1631
        global $mysoc;
1632
1633
        $this->lines = array();
1634
1635
        // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
1636
        // TODO: See if we can restore a common fetch_lines (one line = one record)
1637
1638
        $sql = "SELECT cd.rowid, cd.fk_product, cd.label as custom_label, cd.description, cd.qty as qty_asked, cd.product_type, cd.fk_unit";
1639
        $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
1640
        $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
1641
        $sql .= ", cd.vat_src_code, cd.tva_tx, cd.localtax1_tx, cd.localtax2_tx, cd.localtax1_type, cd.localtax2_type, cd.info_bits, cd.price, cd.subprice, cd.remise_percent,cd.buy_price_ht as pa_ht";
1642
        $sql .= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc, cd.rang, cd.date_start, cd.date_end";
1643
        $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot";
1644
        $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
1645
        $sql .= ", p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units, p.tosell as product_tosell, p.tobuy as product_tobuy, p.tobatch as product_tobatch";
1646
        $sql .= " FROM " . MAIN_DB_PREFIX . "expeditiondet as ed, " . MAIN_DB_PREFIX . "commandedet as cd";
1647
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "product as p ON p.rowid = cd.fk_product";
1648
        $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
1649
        $sql .= " AND ed.fk_elementdet = cd.rowid";
1650
        $sql .= " ORDER BY cd.rang, ed.fk_elementdet";      // We need after a break on fk_elementdet but when there is no break on fk_elementdet, cd.rang is same so we can add it as first order criteria.
1651
1652
        dol_syslog(get_class($this) . "::fetch_lines", LOG_DEBUG);
1653
        $resql = $this->db->query($sql);
1654
        if ($resql) {
1655
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1656
1657
            $num = $this->db->num_rows($resql);
1658
            $i = 0;
1659
            $lineindex = 0;
1660
            $originline = 0;
1661
1662
            $this->total_ht = 0;
1663
            $this->total_tva = 0;
1664
            $this->total_ttc = 0;
1665
            $this->total_localtax1 = 0;
1666
            $this->total_localtax2 = 0;
1667
1668
            $this->multicurrency_total_ht = 0;
1669
            $this->multicurrency_total_tva = 0;
1670
            $this->multicurrency_total_ttc = 0;
1671
1672
            $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1673
1674
            while ($i < $num) {
1675
                $obj = $this->db->fetch_object($resql);
1676
1677
1678
                if ($originline > 0 && $originline == $obj->fk_elementdet) {
1679
                    '@phan-var-force ExpeditionLigne $line';  // $line from previous loop
1680
                    $line->entrepot_id = 0; // entrepod_id in details_entrepot
1681
                    $line->qty_shipped += $obj->qty_shipped;
1682
                } else {
1683
                    $line = new ExpeditionLigne($this->db);     // new group to start
1684
                    $line->entrepot_id      = $obj->fk_entrepot;    // this is a property of a shipment line
1685
                    $line->qty_shipped      = $obj->qty_shipped;    // this is a property of a shipment line
1686
                }
1687
1688
                $detail_entrepot              = new stdClass();
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Expedition\Classes\stdClass was not found. Did you mean stdClass? If so, make sure to prefix the type with \.
Loading history...
1689
                $detail_entrepot->entrepot_id = $obj->fk_entrepot;
1690
                $detail_entrepot->qty_shipped = $obj->qty_shipped;
1691
                $detail_entrepot->line_id     = $obj->line_id;
1692
                $line->details_entrepot[]     = $detail_entrepot;
1693
1694
                $line->line_id          = $obj->line_id; // TODO deprecated
1695
                $line->rowid            = $obj->line_id; // TODO deprecated
1696
                $line->id               = $obj->line_id;
1697
1698
                $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
1699
1700
                $line->fk_element       = $obj->fk_element;
1701
                $line->origin_id        = $obj->fk_element;
1702
                $line->fk_elementdet    = $obj->fk_elementdet;
1703
                $line->origin_line_id   = $obj->fk_elementdet;
1704
                $line->element_type     = $obj->element_type;
1705
1706
                $line->fk_expedition    = $this->id; // id of parent
1707
1708
                $line->product_type     = $obj->product_type;
1709
                $line->fk_product       = $obj->fk_product;
1710
                $line->fk_product_type  = $obj->fk_product_type;
1711
                $line->ref = $obj->product_ref; // TODO deprecated
1712
                $line->product_ref = $obj->product_ref;
1713
                $line->product_label = $obj->product_label;
1714
                $line->libelle          = $obj->product_label; // TODO deprecated
1715
                $line->product_barcode  = $obj->product_barcode; // Barcode number product
1716
                $line->product_tosell = $obj->product_tosell;
1717
                $line->product_tobuy = $obj->product_tobuy;
1718
                $line->product_tobatch = $obj->product_tobatch;
1719
                $line->fk_fournprice = $obj->fk_fournprice;
1720
                $line->label = $obj->custom_label;
1721
                $line->description      = $obj->description;
1722
                $line->qty_asked        = $obj->qty_asked;
1723
                $line->rang = $obj->rang;
1724
                $line->weight           = $obj->weight;
1725
                $line->weight_units     = $obj->weight_units;
1726
                $line->length           = $obj->length;
1727
                $line->length_units     = $obj->length_units;
1728
                $line->width           = $obj->width;
1729
                $line->width_units     = $obj->width_units;
1730
                $line->height           = $obj->height;
1731
                $line->height_units     = $obj->height_units;
1732
                $line->surface          = $obj->surface;
1733
                $line->surface_units = $obj->surface_units;
1734
                $line->volume           = $obj->volume;
1735
                $line->volume_units     = $obj->volume_units;
1736
                $line->fk_unit = $obj->fk_unit;
1737
1738
                $line->pa_ht = $obj->pa_ht;
1739
1740
                // Local taxes
1741
                $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
1742
                $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
1743
                $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
1744
1745
                // For invoicing
1746
                $tabprice = calcul_price_total($obj->qty_shipped, $obj->subprice, $obj->remise_percent, $obj->tva_tx, $localtax1_tx, $localtax2_tx, 0, 'HT', $obj->info_bits, $obj->fk_product_type, $mysoc, $localtax_array); // We force type to 0
1747
                $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
1748
                $line->qty = $line->qty_shipped;
1749
                $line->total_ht = $tabprice[0];
1750
                $line->total_localtax1  = $tabprice[9];
1751
                $line->total_localtax2  = $tabprice[10];
1752
                $line->total_ttc        = $tabprice[2];
1753
                $line->total_tva        = $tabprice[1];
1754
                $line->vat_src_code = $obj->vat_src_code;
1755
                $line->tva_tx = $obj->tva_tx;
1756
                $line->localtax1_tx     = $obj->localtax1_tx;
1757
                $line->localtax2_tx     = $obj->localtax2_tx;
1758
                $line->info_bits = $obj->info_bits;
1759
                $line->price = $obj->price;
1760
                $line->subprice = $obj->subprice;
1761
                $line->fk_remise_except = $obj->fk_remise_except;
1762
                $line->remise_percent = $obj->remise_percent;
1763
1764
                $this->total_ht += $tabprice[0];
1765
                $this->total_tva += $tabprice[1];
1766
                $this->total_ttc += $tabprice[2];
1767
                $this->total_localtax1 += $tabprice[9];
1768
                $this->total_localtax2 += $tabprice[10];
1769
1770
                $line->date_start       = $this->db->jdate($obj->date_start);
1771
                $line->date_end         = $this->db->jdate($obj->date_end);
1772
1773
                // Multicurrency
1774
                $this->fk_multicurrency = $obj->fk_multicurrency;
1775
                $this->multicurrency_code = $obj->multicurrency_code;
1776
                $line->multicurrency_subprice   = $obj->multicurrency_subprice;
1777
                $line->multicurrency_total_ht   = $obj->multicurrency_total_ht;
1778
                $line->multicurrency_total_tva  = $obj->multicurrency_total_tva;
1779
                $line->multicurrency_total_ttc  = $obj->multicurrency_total_ttc;
1780
1781
                $this->multicurrency_total_ht   += $obj->multicurrency_total_ht;
1782
                $this->multicurrency_total_tva  += $obj->multicurrency_total_tva;
1783
                $this->multicurrency_total_ttc  += $obj->multicurrency_total_ttc;
1784
1785
                if ($originline != $obj->fk_elementdet) {
1786
                    $line->detail_batch = array();
1787
                }
1788
1789
                // Detail of batch
1790
                if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1791
                    $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1792
1793
                    if (is_array($newdetailbatch)) {
1794
                        if ($originline != $obj->fk_elementdet) {
1795
                            $line->detail_batch = $newdetailbatch;
1796
                        } else {
1797
                            $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1798
                        }
1799
                    }
1800
                }
1801
1802
                $line->fetch_optionals();
1803
1804
                if ($originline != $obj->fk_elementdet) {
1805
                    $this->lines[$lineindex] = $line;
1806
                    $lineindex++;
1807
                } else {
1808
                    $line->total_ht += $tabprice[0];
1809
                    $line->total_localtax1  += $tabprice[9];
1810
                    $line->total_localtax2  += $tabprice[10];
1811
                    $line->total_ttc        += $tabprice[2];
1812
                    $line->total_tva        += $tabprice[1];
1813
                }
1814
1815
                $i++;
1816
                $originline = $obj->fk_elementdet;
1817
            }
1818
            $this->db->free($resql);
1819
            return 1;
1820
        } else {
1821
            $this->error = $this->db->error();
1822
            return -3;
1823
        }
1824
    }
1825
1826
    /**
1827
     *  Delete detail line
1828
     *
1829
     *  @param      User    $user           User making deletion
1830
     *  @param      int     $lineid         Id of line to delete
1831
     *  @return     int                     >0 if OK, <0 if KO
1832
     */
1833
    public function deleteLine($user, $lineid)
1834
    {
1835
        global $user;
1836
1837
        if ($this->statut == self::STATUS_DRAFT) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1837
        if (/** @scrutinizer ignore-deprecated */ $this->statut == self::STATUS_DRAFT) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1838
            $this->db->begin();
1839
1840
            $line = new ExpeditionLigne($this->db);
1841
1842
            // For triggers
1843
            $line->fetch($lineid);
1844
1845
            if ($line->delete($user) > 0) {
1846
                //$this->update_price(1);
1847
1848
                $this->db->commit();
1849
                return 1;
1850
            } else {
1851
                $this->db->rollback();
1852
                return -1;
1853
            }
1854
        } else {
1855
            $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1856
            return -2;
1857
        }
1858
    }
1859
1860
1861
    /**
1862
     * getTooltipContentArray
1863
     *
1864
     * @param array $params ex option, infologin
1865
     * @since v18
1866
     * @return array
1867
     */
1868
    public function getTooltipContentArray($params)
1869
    {
1870
        global $conf, $langs;
1871
1872
        $langs->load('sendings');
1873
1874
        $nofetch = !empty($params['nofetch']);
1875
1876
        $datas = array();
1877
        $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("Shipment") . '</u>';
1878
        if (isset($this->statut)) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1878
        if (isset(/** @scrutinizer ignore-deprecated */ $this->statut)) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1879
            $datas['picto'] .= ' ' . $this->getLibStatut(5);
1880
        }
1881
        $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
1882
        $datas['refcustomer'] = '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . ($this->ref_customer ? $this->ref_customer : $this->ref_client);
1883
        if (!$nofetch) {
1884
            $langs->load('companies');
1885
            if (empty($this->thirdparty)) {
1886
                $this->fetch_thirdparty();
1887
            }
1888
            $datas['customer'] = '<br><b>' . $langs->trans('Customer') . ':</b> ' . $this->thirdparty->getNomUrl(1, '', 0, 1);
0 ignored issues
show
Bug introduced by
The method getNomUrl() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1888
            $datas['customer'] = '<br><b>' . $langs->trans('Customer') . ':</b> ' . $this->thirdparty->/** @scrutinizer ignore-call */ getNomUrl(1, '', 0, 1);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1889
        }
1890
1891
        return $datas;
1892
    }
1893
1894
    /**
1895
     *  Return clicable link of object (with eventually picto)
1896
     *
1897
     *  @param      int         $withpicto                  Add picto into link
1898
     *  @param      string      $option                     Where the link point to
1899
     *  @param      int         $max                        Max length to show
1900
     *  @param      int         $short                      Use short labels
1901
     *  @param      int         $notooltip                  1=No tooltip
1902
     *  @param      int         $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1903
     *  @return     string                                  String with URL
1904
     */
1905
    public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
1906
    {
1907
        global $langs, $hookmanager;
1908
1909
        $result = '';
1910
        $params = [
1911
            'id' => $this->id,
1912
            'objecttype' => $this->element,
1913
            'option' => $option,
1914
            'nofetch' => 1,
1915
        ];
1916
        $classfortooltip = 'classfortooltip';
1917
        $dataparams = '';
1918
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1919
            $classfortooltip = 'classforajaxtooltip';
1920
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1921
            $label = '';
1922
        } else {
1923
            $label = implode($this->getTooltipContentArray($params));
1924
        }
1925
1926
        $url = constant('BASE_URL') . '/expedition/card.php?id=' . $this->id;
1927
1928
        if ($short) {
1929
            return $url;
1930
        }
1931
1932
        if ($option !== 'nolink') {
1933
            // Add param to save lastsearch_values or not
1934
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1935
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1936
                $add_save_lastsearch_values = 1;
1937
            }
1938
            if ($add_save_lastsearch_values) {
1939
                $url .= '&save_lastsearch_values=1';
1940
            }
1941
        }
1942
1943
        $linkclose = '';
1944
        if (empty($notooltip)) {
1945
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1946
                $label = $langs->trans("Shipment");
1947
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1948
            }
1949
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1950
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
1951
        }
1952
1953
        $linkstart = '<a href="' . $url . '"';
1954
        $linkstart .= $linkclose . '>';
1955
        $linkend = '</a>';
1956
1957
        $result .= $linkstart;
1958
        if ($withpicto) {
1959
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="' . (($withpicto != 2) ? 'paddingright ' : '') . '"'), 0, 0, $notooltip ? 0 : 1);
1960
        }
1961
        if ($withpicto != 2) {
1962
            $result .= $this->ref;
1963
        }
1964
        $result .= $linkend;
1965
        global $action;
1966
        $hookmanager->initHooks(array($this->element . 'dao'));
1967
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1968
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1969
        if ($reshook > 0) {
1970
            $result = $hookmanager->resPrint;
1971
        } else {
1972
            $result .= $hookmanager->resPrint;
1973
        }
1974
        return $result;
1975
    }
1976
1977
    /**
1978
     *  Return status label
1979
     *
1980
     *  @param      int     $mode       0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto
1981
     *  @return     string              Label
1982
     */
1983
    public function getLibStatut($mode = 0)
1984
    {
1985
        return $this->LibStatut($this->statut, $mode);
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1985
        return $this->LibStatut(/** @scrutinizer ignore-deprecated */ $this->statut, $mode);

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1986
    }
1987
1988
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1989
    /**
1990
     * Return label of a status
1991
     *
1992
     * @param   int     $status     Id statut
1993
     * @param   int     $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
1994
     * @return  string              Label of status
1995
     */
1996
    public function LibStatut($status, $mode)
1997
    {
1998
		// phpcs:enable
1999
        global $langs;
2000
2001
        $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
2002
        $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
2003
2004
        $statusType = 'status' . $status;
2005
        if ($status == self::STATUS_VALIDATED) {
2006
            $statusType = 'status4';
2007
        }
2008
        if ($status == self::STATUS_CLOSED) {
2009
            $statusType = 'status6';
2010
        }
2011
        if ($status == self::STATUS_CANCELED) {
2012
            $statusType = 'status9';
2013
        }
2014
2015
        return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
2016
    }
2017
2018
    /**
2019
     *  Return clicable link of object (with eventually picto)
2020
     *
2021
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
2022
     *  @param      array       $arraydata              Array of data
2023
     *  @return     string                              HTML Code for Kanban thumb.
2024
     */
2025
    public function getKanbanView($option = '', $arraydata = null)
2026
    {
2027
        global $langs, $conf;
2028
2029
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2030
2031
        $return = '<div class="box-flex-item box-flex-grow-zero">';
2032
        $return .= '<div class="info-box info-box-sm">';
2033
        $return .= '<div class="info-box-icon bg-infobox-action">';
2034
        $return .= img_picto('', 'order');
2035
        $return .= '</div>';
2036
        $return .= '<div class="info-box-content">';
2037
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref) . '</span>';
2038
        if ($selected >= 0) {
2039
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
2040
        }
2041
        if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
2042
            $return .= '<br><div class="info-box-ref tdoverflowmax150">' . $this->thirdparty->getNomUrl(1) . '</div>';
2043
        }
2044
        if (property_exists($this, 'total_ht')) {
2045
            $return .= '<div class="info-box-ref amount">' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency) . ' ' . $langs->trans('HT') . '</div>';
2046
        }
2047
        if (method_exists($this, 'getLibStatut')) {
2048
            $return .= '<div class="info-box-status">' . $this->getLibStatut(3) . '</div>';
2049
        }
2050
        $return .= '</div>';
2051
        $return .= '</div>';
2052
        $return .= '</div>';
2053
2054
        return $return;
2055
    }
2056
2057
    /**
2058
     *  Initialise an instance with random values.
2059
     *  Used to build previews or test instances.
2060
     *  id must be 0 if object instance is a specimen.
2061
     *
2062
     *  @return int
2063
     */
2064
    public function initAsSpecimen()
2065
    {
2066
        global $langs;
2067
2068
        $now = dol_now();
2069
2070
        dol_syslog(get_class($this) . "::initAsSpecimen");
2071
2072
        $order = new Commande($this->db);
2073
        $order->initAsSpecimen();
2074
2075
        // Initialise parameters
2076
        $this->id = 0;
2077
        $this->ref = 'SPECIMEN';
2078
        $this->specimen = 1;
2079
        $this->statut               = self::STATUS_VALIDATED;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2079
        /** @scrutinizer ignore-deprecated */ $this->statut               = self::STATUS_VALIDATED;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2080
        $this->livraison_id         = 0;
2081
        $this->date                 = $now;
2082
        $this->date_creation        = $now;
2083
        $this->date_valid           = $now;
2084
        $this->date_delivery        = $now + 24 * 3600;
2085
        $this->date_expedition      = $now + 24 * 3600;
2086
2087
        $this->entrepot_id          = 0;
2088
        $this->fk_delivery_address  = 0;
2089
        $this->socid                = 1;
2090
2091
        $this->commande_id          = 0;
2092
        $this->commande             = $order;
2093
2094
        $this->origin_id            = 1;
2095
        $this->origin               = 'commande';
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2095
        /** @scrutinizer ignore-deprecated */ $this->origin               = 'commande';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2096
2097
        $this->note_private = 'Private note';
2098
        $this->note_public = 'Public note';
2099
2100
        $nbp = 5;
2101
        $xnbp = 0;
2102
        while ($xnbp < $nbp) {
2103
            $line = new ExpeditionLigne($this->db);
2104
            $line->product_desc = $langs->trans("Description") . " " . $xnbp;
2105
            $line->product_label = $langs->trans("Description") . " " . $xnbp;
2106
            $line->qty = 10;
2107
            $line->qty_asked = 5;
2108
            $line->qty_shipped = 4;
2109
            $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2110
2111
            $this->lines[] = $line;
2112
            $xnbp++;
2113
        }
2114
2115
        return 1;
2116
    }
2117
2118
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2119
    /**
2120
     *  Set delivery date
2121
     *
2122
     *  @param      User    $user               Object user that modify
2123
     *  @param      int     $delivery_date      Delivery date
2124
     *  @return     int                         Return integer <0 if ko, >0 if ok
2125
     *  @deprecated Use  setDeliveryDate
2126
     */
2127
    public function set_date_livraison($user, $delivery_date)
2128
    {
2129
		// phpcs:enable
2130
        return $this->setDeliveryDate($user, $delivery_date);
2131
    }
2132
2133
    /**
2134
     *  Set the planned delivery date
2135
     *
2136
     *  @param      User            $user               Object user that modify
2137
     *  @param      integer         $delivery_date     Date of delivery
2138
     *  @return     int                                 Return integer <0 if KO, >0 if OK
2139
     */
2140
    public function setDeliveryDate($user, $delivery_date)
2141
    {
2142
        if ($user->hasRight('expedition', 'creer')) {
2143
            $sql = "UPDATE " . MAIN_DB_PREFIX . "expedition";
2144
            $sql .= " SET date_delivery = " . ($delivery_date ? "'" . $this->db->idate($delivery_date) . "'" : 'null');
2145
            $sql .= " WHERE rowid = " . ((int) $this->id);
2146
2147
            dol_syslog(get_class($this) . "::setDeliveryDate", LOG_DEBUG);
2148
            $resql = $this->db->query($sql);
2149
            if ($resql) {
2150
                $this->date_delivery = $delivery_date;
2151
                return 1;
2152
            } else {
2153
                $this->error = $this->db->error();
2154
                return -1;
2155
            }
2156
        } else {
2157
            return -2;
2158
        }
2159
    }
2160
2161
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2162
    /**
2163
     *  Fetch deliveries method and return an array. Load array this->meths(rowid=>label).
2164
     *
2165
     *  @return void
2166
     */
2167
    public function fetch_delivery_methods()
2168
    {
2169
		// phpcs:enable
2170
        global $langs;
2171
        $this->meths = array();
2172
2173
        $sql = "SELECT em.rowid, em.code, em.libelle as label";
2174
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_shipment_mode as em";
2175
        $sql .= " WHERE em.active = 1";
2176
        $sql .= " ORDER BY em.libelle ASC";
2177
2178
        $resql = $this->db->query($sql);
2179
        if ($resql) {
2180
            while ($obj = $this->db->fetch_object($resql)) {
2181
                $label = $langs->trans('SendingMethod' . $obj->code);
2182
                $this->meths[$obj->rowid] = ($label != 'SendingMethod' . $obj->code ? $label : $obj->label);
2183
            }
2184
        }
2185
    }
2186
2187
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2188
    /**
2189
     *  Fetch all deliveries method and return an array. Load array this->listmeths.
2190
     *
2191
     *  @param  int      $id     only this carrier, all if none
2192
     *  @return void
2193
     */
2194
    public function list_delivery_methods($id = 0)
2195
    {
2196
		// phpcs:enable
2197
        global $langs;
2198
2199
        $this->listmeths = array();
2200
        $i = 0;
2201
2202
        $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2203
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_shipment_mode as em";
2204
        if (!empty($id)) {
2205
            $sql .= " WHERE em.rowid=" . ((int) $id);
2206
        }
2207
2208
        $resql = $this->db->query($sql);
2209
        if ($resql) {
2210
            while ($obj = $this->db->fetch_object($resql)) {
2211
                $this->listmeths[$i]['rowid'] = $obj->rowid;
2212
                $this->listmeths[$i]['code'] = $obj->code;
2213
                $label = $langs->trans('SendingMethod' . $obj->code);
2214
                $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod' . $obj->code ? $label : $obj->label);
2215
                $this->listmeths[$i]['description'] = $obj->description;
2216
                $this->listmeths[$i]['tracking'] = $obj->tracking;
2217
                $this->listmeths[$i]['active'] = $obj->active;
2218
                $i++;
2219
            }
2220
        }
2221
    }
2222
2223
    /**
2224
     * Forge an set tracking url
2225
     *
2226
     * @param   string  $value      Value
2227
     * @return  void
2228
     */
2229
    public function getUrlTrackingStatus($value = '')
2230
    {
2231
        if (!empty($this->shipping_method_id)) {
2232
            $sql = "SELECT em.code, em.tracking";
2233
            $sql .= " FROM " . MAIN_DB_PREFIX . "c_shipment_mode as em";
2234
            $sql .= " WHERE em.rowid = " . ((int) $this->shipping_method_id);
2235
2236
            $resql = $this->db->query($sql);
2237
            if ($resql) {
2238
                if ($obj = $this->db->fetch_object($resql)) {
2239
                    $tracking = $obj->tracking;
2240
                }
2241
            }
2242
        }
2243
2244
        if (!empty($tracking) && !empty($value)) {
2245
            $url = str_replace('{TRACKID}', $value, $tracking);
2246
            $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
2247
        } else {
2248
            $this->tracking_url = $value;
2249
        }
2250
    }
2251
2252
    /**
2253
     *  Classify the shipping as closed (this records also the stock movement)
2254
     *
2255
     *  @return     int     Return integer <0 if KO, >0 if OK
2256
     */
2257
    public function setClosed()
2258
    {
2259
        global $user;
2260
2261
        $error = 0;
2262
2263
        // Protection. This avoid to move stock later when we should not
2264
        if ($this->statut == self::STATUS_CLOSED) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2264
        if (/** @scrutinizer ignore-deprecated */ $this->statut == self::STATUS_CLOSED) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2265
            return 0;
2266
        }
2267
2268
        $this->db->begin();
2269
2270
        $sql = "UPDATE " . MAIN_DB_PREFIX . "expedition SET fk_statut = " . self::STATUS_CLOSED;
2271
        $sql .= " WHERE rowid = " . ((int) $this->id) . " AND fk_statut > 0";
2272
2273
        $resql = $this->db->query($sql);
2274
        if ($resql) {
2275
            // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2276
            if ($this->origin == 'commande' && $this->origin_id > 0) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2276
            if (/** @scrutinizer ignore-deprecated */ $this->origin == 'commande' && $this->origin_id > 0) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2277
                $order = new Commande($this->db);
2278
                $order->fetch($this->origin_id);
2279
2280
                $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2281
2282
                $shipments_match_order = 1;
2283
                foreach ($order->lines as $line) {
2284
                    $lineid = $line->id;
2285
                    $qty = $line->qty;
2286
                    if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2287
                        $shipments_match_order = 0;
2288
                        $text = 'Qty for order line id ' . $lineid . ' is ' . $qty . '. However in the shipments with status Expedition::STATUS_CLOSED=' . self::STATUS_CLOSED . ' we have qty = ' . $order->expeditions[$lineid] . ', so we can t close order';
2289
                        dol_syslog($text);
2290
                        break;
2291
                    }
2292
                }
2293
                if ($shipments_match_order) {
2294
                    dol_syslog("Qty for the " . count($order->lines) . " lines of the origin order is same than qty for lines in the shipment we close (shipments_match_order is true), with new status Expedition::STATUS_CLOSED=" . self::STATUS_CLOSED . ', so we close order');
2295
                    // We close the order
2296
                    $order->cloture($user);     // Note this may also create an invoice if module workflow ask it
2297
                }
2298
            }
2299
2300
            $this->statut = self::STATUS_CLOSED;    // Will be revert to STATUS_VALIDATED at end if there is a rollback
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2300
            /** @scrutinizer ignore-deprecated */ $this->statut = self::STATUS_CLOSED;    // Will be revert to STATUS_VALIDATED at end if there is a rollback

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2301
            $this->status = self::STATUS_CLOSED;    // Will be revert to STATUS_VALIDATED at end if there is a rollback
2302
2303
            // If stock increment is done on closing
2304
            if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2305
                $result = $this->manageStockMvtOnEvt($user);
2306
                if ($result < 0) {
2307
                    $error++;
2308
                }
2309
            }
2310
2311
            // Call trigger
2312
            if (!$error) {
2313
                $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2314
                if ($result < 0) {
2315
                    $error++;
2316
                }
2317
            }
2318
        } else {
2319
            dol_print_error($this->db);
2320
            $error++;
2321
        }
2322
2323
        if (!$error) {
2324
            $this->db->commit();
2325
            return 1;
2326
        } else {
2327
            $this->statut = self::STATUS_VALIDATED;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2327
            /** @scrutinizer ignore-deprecated */ $this->statut = self::STATUS_VALIDATED;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2328
            $this->status = self::STATUS_VALIDATED;
2329
2330
            $this->db->rollback();
2331
            return -1;
2332
        }
2333
    }
2334
2335
    /**
2336
     * Manage Stock MVt onb Close or valid Shipment
2337
     *
2338
     * @param       User    $user               Object user that modify
2339
     * @param       string  $labelmovement      Label of movement
2340
     * @return      int                         Return integer <0 if KO, >0 if OK
2341
     * @throws Exception
2342
     *
2343
     */
2344
    private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
2345
    {
2346
        global $langs;
2347
2348
        $error = 0;
2349
2350
        require_once constant('DOL_DOCUMENT_ROOT') . '/product/stock/class/mouvementstock.class.php';
2351
2352
        $langs->load("agenda");
2353
2354
        // Loop on each product line to add a stock movement
2355
        $sql = "SELECT cd.fk_product, cd.subprice,";
2356
        $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2357
        $sql .= " e.ref,";
2358
        $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
2359
        $sql .= " cd.rowid as cdid, ed.rowid as edid";
2360
        $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2361
        $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2362
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2363
        $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
2364
        $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2365
        $sql .= " AND cd.rowid = ed.fk_elementdet";
2366
2367
        dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2368
        $resql = $this->db->query($sql);
2369
        if ($resql) {
2370
            $cpt = $this->db->num_rows($resql);
2371
            for ($i = 0; $i < $cpt; $i++) {
2372
                $obj = $this->db->fetch_object($resql);
2373
                if (empty($obj->edbrowid)) {
2374
                    $qty = $obj->qty;
2375
                } else {
2376
                    $qty = $obj->edbqty;
2377
                }
2378
                if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
2379
                    continue;
2380
                }
2381
                dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2382
2383
                $mouvS = new MouvementStock($this->db);
2384
                $mouvS->origin = &$this;
2385
                $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
2386
2387
                if (empty($obj->edbrowid)) {
2388
                    // line without batch detail
2389
2390
                    // 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
2391
                    $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
2392
                    if ($result < 0) {
2393
                        $this->error = $mouvS->error;
2394
                        $this->errors = $mouvS->errors;
2395
                        $error++;
2396
                        break;
2397
                    }
2398
                } else {
2399
                    // line with batch detail
2400
2401
                    // 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
2402
                    $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
2403
                    if ($result < 0) {
2404
                        $this->error = $mouvS->error;
2405
                        $this->errors = $mouvS->errors;
2406
                        $error++;
2407
                        break;
2408
                    }
2409
                }
2410
2411
                // If some stock lines are now 0, we can remove entry into llx_product_stock, but only if there is no child lines into llx_product_batch (detail of batch, because we can imagine
2412
                // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
2413
                $sqldelete = "DELETE FROM " . MAIN_DB_PREFIX . "product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM " . MAIN_DB_PREFIX . "product_batch as pb)";
2414
                $resqldelete = $this->db->query($sqldelete);
2415
                // We do not test error, it can fails if there is child in batch details
2416
            }
2417
        } else {
2418
            $this->error = $this->db->lasterror();
2419
            $this->errors[] = $this->db->lasterror();
2420
            $error++;
2421
        }
2422
2423
        return $error;
2424
    }
2425
2426
    /**
2427
     *  Classify the shipping as invoiced (used for example by trigger when WORKFLOW_SHIPPING_CLASSIFY_BILLED_INVOICE is on)
2428
     *
2429
     *  @return     int     Return integer <0 if ko, >0 if ok
2430
     */
2431
    public function setBilled()
2432
    {
2433
        global $user;
2434
        $error = 0;
2435
2436
        $this->db->begin();
2437
2438
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'expedition SET billed = 1';
2439
        $sql .= " WHERE rowid = " . ((int) $this->id) . ' AND fk_statut > 0';
2440
2441
        $resql = $this->db->query($sql);
2442
        if ($resql) {
2443
            $this->billed = 1;
2444
2445
            // Call trigger
2446
            $result = $this->call_trigger('SHIPPING_BILLED', $user);
2447
            if ($result < 0) {
2448
                $this->billed = 0;
2449
                $error++;
2450
            }
2451
        } else {
2452
            $error++;
2453
            $this->errors[] = $this->db->lasterror;
2454
        }
2455
2456
        if (empty($error)) {
2457
            $this->db->commit();
2458
            return 1;
2459
        } else {
2460
            $this->db->rollback();
2461
            return -1;
2462
        }
2463
    }
2464
2465
    /**
2466
     *  Set draft status
2467
     *
2468
     *  @param  User    $user           Object user that modify
2469
     *  @param  int     $notrigger      1=Does not execute triggers, 0=Execute triggers
2470
     *  @return int                     Return integer <0 if KO, >0 if OK
2471
     */
2472
    public function setDraft($user, $notrigger = 0)
2473
    {
2474
        // Protection
2475
        if ($this->statut <= self::STATUS_DRAFT) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2475
        if (/** @scrutinizer ignore-deprecated */ $this->statut <= self::STATUS_DRAFT) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2476
            return 0;
2477
        }
2478
2479
        return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2480
    }
2481
2482
    /**
2483
     *  Classify the shipping as validated/opened
2484
     *
2485
     *  @return     int     Return integer <0 if KO, 0 if already open, >0 if OK
2486
     */
2487
    public function reOpen()
2488
    {
2489
        global $langs, $user;
2490
2491
        $error = 0;
2492
2493
        // Protection. This avoid to move stock later when we should not
2494
        if ($this->statut == self::STATUS_VALIDATED) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2494
        if (/** @scrutinizer ignore-deprecated */ $this->statut == self::STATUS_VALIDATED) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2495
            return 0;
2496
        }
2497
2498
        $this->db->begin();
2499
2500
        $oldbilled = $this->billed;
2501
2502
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'expedition SET fk_statut = 1';
2503
        $sql .= " WHERE rowid = " . ((int) $this->id) . ' AND fk_statut > 0';
2504
2505
        $resql = $this->db->query($sql);
2506
        if ($resql) {
2507
            $this->statut = self::STATUS_VALIDATED;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2507
            /** @scrutinizer ignore-deprecated */ $this->statut = self::STATUS_VALIDATED;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2508
            $this->status = self::STATUS_VALIDATED;
2509
            $this->billed = 0;
2510
2511
            // If stock increment is done on closing
2512
            if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2513
                require_once constant('DOL_DOCUMENT_ROOT') . '/product/stock/class/mouvementstock.class.php';
2514
2515
                $langs->load("agenda");
2516
2517
                // Loop on each product line to add a stock movement
2518
                // TODO possibilite d'expedier a partir d'une propale ou autre origine
2519
                $sql = "SELECT cd.fk_product, cd.subprice,";
2520
                $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2521
                $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2522
                $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2523
                $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2524
                $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2525
                $sql .= " WHERE ed.fk_expedition = " . ((int) $this->id);
2526
                $sql .= " AND cd.rowid = ed.fk_elementdet";
2527
2528
                dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2529
                $resql = $this->db->query($sql);
2530
                if ($resql) {
2531
                    $cpt = $this->db->num_rows($resql);
2532
                    for ($i = 0; $i < $cpt; $i++) {
2533
                        $obj = $this->db->fetch_object($resql);
2534
                        if (empty($obj->edbrowid)) {
2535
                            $qty = $obj->qty;
2536
                        } else {
2537
                            $qty = $obj->edbqty;
2538
                        }
2539
                        if ($qty <= 0) {
2540
                            continue;
2541
                        }
2542
                        dol_syslog(get_class($this) . "::reopen expedition movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2543
2544
                        //var_dump($this->lines[$i]);
2545
                        $mouvS = new MouvementStock($this->db);
2546
                        $mouvS->origin = &$this;
2547
                        $mouvS->setOrigin($this->element, $this->id);
2548
2549
                        if (empty($obj->edbrowid)) {
2550
                            // line without batch detail
2551
2552
                            // 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
2553
                            $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2554
                            if ($result < 0) {
2555
                                $this->error = $mouvS->error;
2556
                                $this->errors = $mouvS->errors;
2557
                                $error++;
2558
                                break;
2559
                            }
2560
                        } else {
2561
                            // line with batch detail
2562
2563
                            // 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
2564
                            $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
2565
                            if ($result < 0) {
2566
                                $this->error = $mouvS->error;
2567
                                $this->errors = $mouvS->errors;
2568
                                $error++;
2569
                                break;
2570
                            }
2571
                        }
2572
                    }
2573
                } else {
2574
                    $this->error = $this->db->lasterror();
2575
                    $error++;
2576
                }
2577
            }
2578
2579
            if (!$error) {
2580
                // Call trigger
2581
                $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2582
                if ($result < 0) {
2583
                    $error++;
2584
                }
2585
            }
2586
        } else {
2587
            $error++;
2588
            $this->errors[] = $this->db->lasterror();
2589
        }
2590
2591
        if (!$error) {
2592
            $this->db->commit();
2593
            return 1;
2594
        } else {
2595
            $this->statut = self::STATUS_CLOSED;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2595
            /** @scrutinizer ignore-deprecated */ $this->statut = self::STATUS_CLOSED;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2596
            $this->status = self::STATUS_CLOSED;
2597
            $this->billed = $oldbilled;
2598
            $this->db->rollback();
2599
            return -1;
2600
        }
2601
    }
2602
2603
    /**
2604
     *  Create a document onto disk according to template module.
2605
     *
2606
     *  @param      string      $modele         Force the model to using ('' to not force)
2607
     *  @param      Translate   $outputlangs    object lang to use for translations
2608
     *  @param      int         $hidedetails    Hide details of lines
2609
     *  @param      int         $hidedesc       Hide description
2610
     *  @param      int         $hideref        Hide ref
2611
     *  @param      null|array  $moreparams     Array to provide more information
2612
     *  @return     int                         0 if KO, 1 if OK
2613
     */
2614
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2615
    {
2616
        $outputlangs->load("products");
2617
2618
        if (!dol_strlen($modele)) {
2619
            $modele = 'rouget';
2620
2621
            if (!empty($this->model_pdf)) {
2622
                $modele = $this->model_pdf;
2623
            } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
2624
                $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
2625
            }
2626
        }
2627
2628
        $modelpath = "core/modules/expedition/doc/";
2629
2630
        $this->fetch_origin();
2631
2632
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2633
    }
2634
2635
    /**
2636
     * Function used to replace a thirdparty id with another one.
2637
     *
2638
     * @param   DoliDB  $dbs        Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
2639
     * @param   int     $origin_id  Old thirdparty id
2640
     * @param   int     $dest_id    New thirdparty id
2641
     * @return  bool
2642
     */
2643
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2644
    {
2645
        $tables = array(
2646
            'expedition'
2647
        );
2648
2649
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2650
    }
2651
}
2652