Passed
Pull Request — dev (#8)
by Rafael
58:47
created

Expedition   F

Complexity

Total Complexity 412

Size/Duplication

Total Lines 2551
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1394
dl 0
loc 2551
rs 0.8
c 0
b 0
f 0
wmc 412

33 Methods

Rating   Name   Duplication   Size   Complexity  
A replaceThirdparty() 0 7 1
A __construct() 0 22 1
B create_line_batch() 0 38 10
A setDeliveryDate() 0 18 4
F valid() 0 142 29
A set_date_livraison() 0 4 1
A getLibStatut() 0 3 1
A generateDocument() 0 19 4
B manageStockMvtOnEvt() 0 79 10
C setClosed() 0 75 17
A fetch_delivery_methods() 0 16 4
A LibStatut() 0 20 4
A create_delivery() 0 22 5
B getNextNumRef() 0 39 6
B getUrlTrackingStatus() 0 20 7
F fetch() 0 128 16
D getNomUrl() 0 70 21
A setBilled() 0 31 4
A setDraft() 0 8 2
A list_delivery_methods() 0 25 5
F delete() 0 195 40
A deleteLine() 0 24 3
F cancel() 0 183 39
B getKanbanView() 0 30 9
D addline() 0 74 22
A getTooltipContentArray() 0 24 5
C addline_batch() 0 59 16
C reOpen() 0 112 16
A create_line() 0 18 2
F create() 0 149 42
C fetch_lines() 0 195 12
A initAsSpecimen() 0 52 2
D update() 0 126 52

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
/* Copyright (C) 2003-2008  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2005-2012	Regis Houssin			    <[email protected]>
5
 * Copyright (C) 2007		Franky Van Liedekerke	    <[email protected]>
6
 * Copyright (C) 2006-2012	Laurent Destailleur		    <[email protected]>
7
 * Copyright (C) 2011-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\Code\Commande\Classes\Commande;
37
use Dolibarr\Code\Core\Traits\CommonIncoterm;
38
use Dolibarr\Code\Societe\Classes\Societe;
39
use Dolibarr\Code\User\Classes\User;
40
use Dolibarr\Core\Base\CommonObject;
41
use DoliDB;
42
43
/**
44
 *  \file       htdocs/expedition/class/expedition.class.php
45
 *  \ingroup    expedition
46
 *  \brief      File of class managing the shipments
47
 */
48
49
/**
50
 *  Class to manage shipments
51
 */
52
class Expedition extends CommonObject
53
{
54
    use CommonIncoterm;
0 ignored issues
show
Bug introduced by
The trait Dolibarr\Code\Core\Traits\CommonIncoterm requires the property $code which is not provided by Dolibarr\Code\Expedition\Classes\Expedition.
Loading history...
55
56
    /**
57
     * Draft status
58
     */
59
    const STATUS_DRAFT = 0;
60
    /**
61
     * Validated status
62
     * -> parcel is ready to be sent
63
     * prev status : draft
64
     * next status : closed or shipment_in_progress
65
     */
66
    const STATUS_VALIDATED = 1;
67
    /**
68
     * Closed status
69
     * -> parcel was received by customer / end of process
70
     * prev status : validated or shipment_in_progress
71
     *
72
     */
73
    const STATUS_CLOSED = 2;
74
    /**
75
     * Canceled status
76
     */
77
    const STATUS_CANCELED = -1;
78
    /**
79
     * Expedition in progress
80
     * -> package exit the warehouse and is now
81
     *    in the truck or into the hand of the deliverer
82
     * prev status : validated
83
     * next status : closed
84
     */
85
    const STATUS_SHIPMENT_IN_PROGRESS = 3;
86
    /**
87
     * No signature
88
     */
89
    const STATUS_NO_SIGNATURE = 0;
90
    /**
91
     * Signed status
92
     */
93
    const STATUS_SIGNED = 1;
94
    /**
95
     * @var string ID to identify managed object
96
     */
97
    public $element = "shipping";
98
    /**
99
     * @var string Field with ID of parent key if this field has a parent
100
     */
101
    public $fk_element = "fk_expedition";
102
    /**
103
     * @var string Name of table without prefix where object is stored
104
     */
105
    public $table_element = "expedition";
106
    /**
107
     * @var string    Name of subtable line
108
     */
109
    public $table_element_line = "expeditiondet";
110
    /**
111
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
112
     */
113
    public $picto = 'dolly';
114
    /**
115
     * @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...
116
     */
117
    public $fields = array();
118
    /**
119
     * @var int ID of user author
120
     */
121
    public $user_author_id;
122
    /**
123
     * @var int ID of user author
124
     */
125
    public $fk_user_author;
126
    public $socid;
127
    /**
128
     * @var string Customer ref
129
     * @deprecated
130
     * @see $ref_customer
131
     */
132
    public $ref_client;
133
    /**
134
     * @var string Customer ref
135
     */
136
    public $ref_customer;
137
    /**
138
     * @var int warehouse id
139
     */
140
    public $entrepot_id;
141
    /**
142
     * @var string Tracking number
143
     */
144
    public $tracking_number;
145
    /**
146
     * @var string Tracking url
147
     */
148
    public $tracking_url;
149
    public $billed;
150
    /**
151
     * @var string name of pdf model
152
     */
153
    public $model_pdf;
154
    public $trueWeight;
155
    // A denormalized value
156
    public $weight_units;
157
    public $trueWidth;
158
    public $width_units;
159
    public $trueHeight;
160
    public $height_units;
161
    public $trueDepth;
162
    public $depth_units;
163
    public $trueSize;
164
    public $livraison_id;
165
    /**
166
     * @var double
167
     */
168
    public $multicurrency_subprice;
169
    public $size_units;
170
    public $sizeH;
171
    public $sizeS;
172
    public $sizeW;
173
    public $weight;
174
    /**
175
     * @var integer|string Date delivery planned
176
     */
177
    public $date_delivery; // List of carriers
178
    /**
179
     * @deprecated
180
     * @see $date_shipping
181
     */
182
    public $date;
183
    /**
184
     * @deprecated
185
     * @see $date_shipping
186
     */
187
    public $date_expedition;
188
    /**
189
     * Effective delivery date
190
     * @var integer|string
191
     */
192
    public $date_shipping;
193
194
    // Multicurrency
195
    /**
196
     * @var integer|string date_creation
197
     */
198
    public $date_creation;
199
    /**
200
     * @var integer|string date_valid
201
     */
202
    public $date_valid;
203
    public $meths;
204
    public $listmeths;
205
    /**
206
     * @var int ID of order
207
     */
208
    public $commande_id;
209
    /**
210
     * @var Commande order
211
     */
212
    public $commande;
213
    /**
214
     * @var ExpeditionLigne[] array of shipping lines
215
     */
216
    public $lines = array();
217
    /**
218
     * @var int Currency ID
219
     */
220
    public $fk_multicurrency;
221
    /**
222
     * @var string multicurrency code
223
     */
224
    public $multicurrency_code;
225
    public $multicurrency_tx;
226
    public $multicurrency_total_ht;
227
    public $multicurrency_total_tva;
228
    public $multicurrency_total_ttc;
229
    /**
230
     * @var int
231
     */
232
    public $signed_status = 0;
233
234
    /**
235
     *  Constructor
236
     *
237
     * @param DoliDB $db Database handler
238
     */
239
    public function __construct($db)
240
    {
241
        global $conf;
242
243
        $this->db = $db;
244
245
        $this->ismultientitymanaged = 1;
246
        $this->isextrafieldmanaged = 1;
247
248
        // List of long language codes for status
249
        $this->labelStatus = array();
250
        $this->labelStatus[-1] = 'StatusSendingCanceled';
251
        $this->labelStatus[0] = 'StatusSendingDraft';
252
        $this->labelStatus[1] = 'StatusSendingValidated';
253
        $this->labelStatus[2] = 'StatusSendingProcessed';
254
255
        // List of short language codes for status
256
        $this->labelStatusShort = array();
257
        $this->labelStatusShort[-1] = 'StatusSendingCanceledShort';
258
        $this->labelStatusShort[0] = 'StatusSendingDraftShort';
259
        $this->labelStatusShort[1] = 'StatusSendingValidatedShort';
260
        $this->labelStatusShort[2] = 'StatusSendingProcessedShort';
261
    }
262
263
    /**
264
     * Function used to replace a thirdparty id with another one.
265
     *
266
     * @param DoliDB $dbs Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
267
     * @param int $origin_id Old thirdparty id
268
     * @param int $dest_id New thirdparty id
269
     * @return  bool
270
     */
271
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
272
    {
273
        $tables = array(
274
            'expedition'
275
        );
276
277
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
278
    }
279
280
    /**
281
     *  Create expedition en base
282
     *
283
     * @param User $user Object du user qui cree
284
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
285
     * @return int                 Return integer <0 si erreur, id expedition creee si ok
286
     */
287
    public function create($user, $notrigger = 0)
288
    {
289
        global $conf, $hookmanager;
290
291
        $now = dol_now();
292
293
        $error = 0;
294
295
        // Clean parameters
296
        $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
297
        if (empty($this->fk_project)) {
298
            $this->fk_project = 0;
299
        }
300
        if (empty($this->date_shipping) && !empty($this->date_expedition)) {
301
            $this->date_shipping = $this->date_expedition;
302
        }
303
304
        $this->user = $user;
305
306
        $this->db->begin();
307
308
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "expedition (";
309
        $sql .= "ref";
310
        $sql .= ", entity";
311
        $sql .= ", ref_customer";
312
        $sql .= ", ref_ext";
313
        $sql .= ", date_creation";
314
        $sql .= ", fk_user_author";
315
        $sql .= ", date_expedition";
316
        $sql .= ", date_delivery";
317
        $sql .= ", fk_soc";
318
        $sql .= ", fk_projet";
319
        $sql .= ", fk_address";
320
        $sql .= ", fk_shipping_method";
321
        $sql .= ", tracking_number";
322
        $sql .= ", weight";
323
        $sql .= ", size";
324
        $sql .= ", width";
325
        $sql .= ", height";
326
        $sql .= ", weight_units";
327
        $sql .= ", size_units";
328
        $sql .= ", note_private";
329
        $sql .= ", note_public";
330
        $sql .= ", model_pdf";
331
        $sql .= ", fk_incoterms, location_incoterms";
332
        $sql .= ") VALUES (";
333
        $sql .= "'(PROV)'";
334
        $sql .= ", " . ((int)$conf->entity);
335
        $sql .= ", " . ($this->ref_customer ? "'" . $this->db->escape($this->ref_customer) . "'" : "null");
336
        $sql .= ", " . ($this->ref_ext ? "'" . $this->db->escape($this->ref_ext) . "'" : "null");
337
        $sql .= ", '" . $this->db->idate($now) . "'";
338
        $sql .= ", " . ((int)$user->id);
339
        $sql .= ", " . ($this->date_shipping > 0 ? "'" . $this->db->idate($this->date_shipping) . "'" : "null");
340
        $sql .= ", " . ($this->date_delivery > 0 ? "'" . $this->db->idate($this->date_delivery) . "'" : "null");
341
        $sql .= ", " . ($this->socid > 0 ? ((int)$this->socid) : "null");
342
        $sql .= ", " . ($this->fk_project > 0 ? ((int)$this->fk_project) : "null");
343
        $sql .= ", " . ($this->fk_delivery_address > 0 ? $this->fk_delivery_address : "null");
344
        $sql .= ", " . ($this->shipping_method_id > 0 ? ((int)$this->shipping_method_id) : "null");
345
        $sql .= ", '" . $this->db->escape($this->tracking_number) . "'";
346
        $sql .= ", " . (is_numeric($this->weight) ? $this->weight : 'NULL');
347
        $sql .= ", " . (is_numeric($this->sizeS) ? $this->sizeS : 'NULL'); // TODO Should use this->trueDepth
348
        $sql .= ", " . (is_numeric($this->sizeW) ? $this->sizeW : 'NULL'); // TODO Should use this->trueWidth
349
        $sql .= ", " . (is_numeric($this->sizeH) ? $this->sizeH : 'NULL'); // TODO Should use this->trueHeight
350
        $sql .= ", " . ($this->weight_units != '' ? (int)$this->weight_units : 'NULL');
351
        $sql .= ", " . ($this->size_units != '' ? (int)$this->size_units : 'NULL');
352
        $sql .= ", " . (!empty($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null");
353
        $sql .= ", " . (!empty($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null");
354
        $sql .= ", " . (!empty($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null");
355
        $sql .= ", " . (int)$this->fk_incoterms;
356
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
357
        $sql .= ")";
358
359
        dol_syslog(get_class($this) . "::create", LOG_DEBUG);
360
        $resql = $this->db->query($sql);
361
        if ($resql) {
362
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . "expedition");
363
364
            $sql = "UPDATE " . MAIN_DB_PREFIX . "expedition";
365
            $sql .= " SET ref = '(PROV" . $this->id . ")'";
366
            $sql .= " WHERE rowid = " . ((int)$this->id);
367
368
            dol_syslog(get_class($this) . "::create", LOG_DEBUG);
369
            if ($this->db->query($sql)) {
370
                // Insert of lines
371
                $num = count($this->lines);
372
                for ($i = 0; $i < $num; $i++) {
373
                    if (empty($this->lines[$i]->product_type) || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
374
                        if (!isset($this->lines[$i]->detail_batch)) {   // no batch management
375
                            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) {
376
                                $error++;
377
                            }
378
                        } else {    // with batch management
379
                            if ($this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) <= 0) {
380
                                $error++;
381
                            }
382
                        }
383
                    }
384
                }
385
386
                if (!$error && $this->id && $this->origin_id) {
387
                    $ret = $this->add_object_linked();
388
                    if (!$ret) {
389
                        $error++;
390
                    }
391
                }
392
393
                // Actions on extra fields
394
                if (!$error) {
395
                    $result = $this->insertExtraFields();
396
                    if ($result < 0) {
397
                        $error++;
398
                    }
399
                }
400
401
                if (!$error && !$notrigger) {
402
                    // Call trigger
403
                    $result = $this->call_trigger('SHIPPING_CREATE', $user);
404
                    if ($result < 0) {
405
                        $error++;
406
                    }
407
                    // End call triggers
408
409
                    if (!$error) {
410
                        $this->db->commit();
411
                        return $this->id;
412
                    } else {
413
                        foreach ($this->errors as $errmsg) {
414
                            dol_syslog(get_class($this) . "::create " . $errmsg, LOG_ERR);
415
                            $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
416
                        }
417
                        $this->db->rollback();
418
                        return -1 * $error;
419
                    }
420
                } else {
421
                    $error++;
422
                    $this->db->rollback();
423
                    return -3;
424
                }
425
            } else {
426
                $error++;
427
                $this->error = $this->db->lasterror() . " - sql=$sql";
428
                $this->db->rollback();
429
                return -2;
430
            }
431
        } else {
432
            $error++;
433
            $this->error = $this->db->error() . " - sql=$sql";
434
            $this->db->rollback();
435
            return -1;
436
        }
437
    }
438
439
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
440
441
    /**
442
     * Create a expedition line
443
     *
444
     * @param int $entrepot_id Id of warehouse
445
     * @param int $origin_line_id Id of source line
446
     * @param float $qty Quantity
447
     * @param int $rang Rang
448
     * @param array $array_options extrafields array
449
     * @return  int                         Return integer <0 if KO, line_id if OK
450
     */
451
    public function create_line($entrepot_id, $origin_line_id, $qty, $rang = 0, $array_options = [])
452
    {
453
        //phpcs:enable
454
        global $user;
455
456
        $expeditionline = new ExpeditionLigne($this->db);
457
        $expeditionline->fk_expedition = $this->id;
458
        $expeditionline->entrepot_id = $entrepot_id;
459
        $expeditionline->fk_elementdet = $origin_line_id;
460
        $expeditionline->element_type = $this->origin;
0 ignored issues
show
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...
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

460
        $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...
461
        $expeditionline->qty = $qty;
462
        $expeditionline->rang = $rang;
463
        $expeditionline->array_options = $array_options;
464
465
        if (($lineId = $expeditionline->insert($user)) < 0) {
466
            $this->errors[] = $expeditionline->error;
467
        }
468
        return $lineId;
469
    }
470
471
472
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
473
474
    /**
475
     * 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.
476
     *
477
     * @param object $line_ext Object with full information of line. $line_ext->detail_batch must be an array of ExpeditionLineBatch
478
     * @param array $array_options extrafields array
479
     * @return  int                             Return integer <0 if KO, >0 if OK
480
     */
481
    public function create_line_batch($line_ext, $array_options = [])
482
    {
483
        // phpcs:enable
484
        $error = 0;
485
        $stockLocationQty = array(); // associated array with batch qty in stock location
486
487
        $tab = $line_ext->detail_batch;
488
        // create stockLocation Qty array
489
        foreach ($tab as $detbatch) {
490
            if (!empty($detbatch->entrepot_id)) {
491
                if (empty($stockLocationQty[$detbatch->entrepot_id])) {
492
                    $stockLocationQty[$detbatch->entrepot_id] = 0;
493
                }
494
                $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
495
            }
496
        }
497
        // create shipment lines
498
        foreach ($stockLocationQty as $stockLocation => $qty) {
499
            $line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $line_ext->rang, $array_options);
500
            if ($line_id < 0) {
501
                $error++;
502
            } else {
503
                // create shipment batch lines for stockLocation
504
                foreach ($tab as $detbatch) {
505
                    if ($detbatch->entrepot_id == $stockLocation) {
506
                        if (!($detbatch->create($line_id) > 0)) {       // Create an ExpeditionLineBatch
507
                            $this->errors = $detbatch->errors;
508
                            $error++;
509
                        }
510
                    }
511
                }
512
            }
513
        }
514
515
        if (!$error) {
516
            return 1;
517
        } else {
518
            return -1;
519
        }
520
    }
521
522
    /**
523
     *  Validate object and update stock if option enabled
524
     *
525
     * @param User $user Object user that validate
526
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
527
     * @return     int                     Return integer <0 if OK, >0 if KO
528
     */
529
    public function valid($user, $notrigger = 0)
530
    {
531
        global $conf;
532
533
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
534
535
        dol_syslog(get_class($this) . "::valid");
536
537
        // Protection
538
        if ($this->status) {
539
            dol_syslog(get_class($this) . "::valid not in draft status", LOG_WARNING);
540
            return 0;
541
        }
542
543
        if (
544
            !((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'creer'))
545
                || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('expedition', 'shipping_advance', 'validate')))
546
        ) {
547
            $this->error = 'Permission denied';
548
            dol_syslog(get_class($this) . "::valid " . $this->error, LOG_ERR);
549
            return -1;
550
        }
551
552
        $this->db->begin();
553
554
        $error = 0;
555
556
        // Define new ref
557
        $soc = new Societe($this->db);
558
        $soc->fetch($this->socid);
559
560
        // Class of company linked to order
561
        $result = $soc->setAsCustomer();
562
563
        // Define new ref
564
        if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
565
            $numref = $this->getNextNumRef($soc);
566
        } elseif (!empty($this->ref)) {
567
            $numref = $this->ref;
568
        } else {
569
            $numref = "EXP" . $this->id;
570
        }
571
        $this->newref = dol_sanitizeFileName($numref);
572
573
        $now = dol_now();
574
575
        // Validate
576
        $sql = "UPDATE " . MAIN_DB_PREFIX . "expedition SET";
577
        $sql .= " ref='" . $this->db->escape($numref) . "'";
578
        $sql .= ", fk_statut = 1";
579
        $sql .= ", date_valid = '" . $this->db->idate($now) . "'";
580
        $sql .= ", fk_user_valid = " . $user->id;
581
        $sql .= " WHERE rowid = " . ((int)$this->id);
582
583
        dol_syslog(get_class($this) . "::valid update expedition", LOG_DEBUG);
584
        $resql = $this->db->query($sql);
585
        if (!$resql) {
586
            $this->error = $this->db->lasterror();
587
            $error++;
588
        }
589
590
        // If stock increment is done on sending (recommended choice)
591
        if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
592
            $result = $this->manageStockMvtOnEvt($user, "ShipmentValidatedInDolibarr");
593
            if ($result < 0) {
594
                return -2;
595
            }
596
        }
597
598
        // Change status of order to "shipment in process"
599
        $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

599
        $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...
600
        if (!$ret) {
601
            $error++;
602
        }
603
604
        if (!$error && !$notrigger) {
605
            // Call trigger
606
            $result = $this->call_trigger('SHIPPING_VALIDATE', $user);
607
            if ($result < 0) {
608
                $error++;
609
            }
610
            // End call triggers
611
        }
612
613
        if (!$error) {
614
            $this->oldref = $this->ref;
615
616
            // Rename directory if dir was a temporary ref
617
            if (preg_match('/^[\(]?PROV/i', $this->ref)) {
618
                // Now we rename also files into index
619
                $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) . "'";
620
                $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'expedition/sending/" . $this->db->escape($this->ref) . "' and entity = " . ((int)$conf->entity);
621
                $resql = $this->db->query($sql);
622
                if (!$resql) {
623
                    $error++;
624
                    $this->error = $this->db->lasterror();
625
                }
626
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'expedition/sending/" . $this->db->escape($this->newref) . "'";
627
                $sql .= " WHERE filepath = 'expedition/sending/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
628
                $resql = $this->db->query($sql);
629
                if (!$resql) {
630
                    $error++;
631
                    $this->error = $this->db->lasterror();
632
                }
633
634
                // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
635
                $oldref = dol_sanitizeFileName($this->ref);
636
                $newref = dol_sanitizeFileName($numref);
637
                $dirsource = $conf->expedition->dir_output . '/sending/' . $oldref;
638
                $dirdest = $conf->expedition->dir_output . '/sending/' . $newref;
639
                if (!$error && file_exists($dirsource)) {
640
                    dol_syslog(get_class($this) . "::valid rename dir " . $dirsource . " into " . $dirdest);
641
642
                    if (@rename($dirsource, $dirdest)) {
643
                        dol_syslog("Rename ok");
644
                        // Rename docs starting with $oldref with $newref
645
                        $listoffiles = dol_dir_list($conf->expedition->dir_output . '/sending/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
646
                        foreach ($listoffiles as $fileentry) {
647
                            $dirsource = $fileentry['name'];
648
                            $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
649
                            $dirsource = $fileentry['path'] . '/' . $dirsource;
650
                            $dirdest = $fileentry['path'] . '/' . $dirdest;
651
                            @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

651
                            /** @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...
652
                        }
653
                    }
654
                }
655
            }
656
        }
657
658
        // Set new ref and current status
659
        if (!$error) {
660
            $this->ref = $numref;
661
            $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

661
            /** @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...
662
            $this->status = self::STATUS_VALIDATED;
663
        }
664
665
        if (!$error) {
666
            $this->db->commit();
667
            return 1;
668
        } else {
669
            $this->db->rollback();
670
            return -1 * $error;
671
        }
672
    }
673
674
    /**
675
     *  Get object and lines from database
676
     *
677
     * @param int $id Id of object to load
678
     * @param string $ref Ref of object
679
     * @param string $ref_ext External reference of object
680
     * @param string $notused Internal reference of other object
681
     * @return int                 >0 if OK, 0 if not found, <0 if KO
682
     */
683
    public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
684
    {
685
        global $conf;
686
687
        // Check parameters
688
        if (empty($id) && empty($ref) && empty($ref_ext)) {
689
            return -1;
690
        }
691
692
        $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";
693
        $sql .= ", e.date_valid";
694
        $sql .= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
695
        $sql .= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
696
        $sql .= ", e.fk_shipping_method, e.tracking_number";
697
        $sql .= ", e.note_private, e.note_public";
698
        $sql .= ', e.fk_incoterms, e.location_incoterms';
699
        $sql .= ', e.signed_status';
700
        $sql .= ', i.libelle as label_incoterms';
701
        $sql .= ', s.libelle as shipping_method';
702
        $sql .= ", el.fk_source as origin_id, el.sourcetype as origin_type";
703
        $sql .= " FROM " . MAIN_DB_PREFIX . "expedition as e";
704
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "element_element as el ON el.fk_target = e.rowid AND el.targettype = '" . $this->db->escape($this->element) . "'";
705
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_incoterms as i ON e.fk_incoterms = i.rowid';
706
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
707
        $sql .= " WHERE e.entity IN (" . getEntity('expedition') . ")";
708
        if ($id) {
709
            $sql .= " AND e.rowid = " . ((int)$id);
710
        }
711
        if ($ref) {
712
            $sql .= " AND e.ref='" . $this->db->escape($ref) . "'";
713
        }
714
        if ($ref_ext) {
715
            $sql .= " AND e.ref_ext='" . $this->db->escape($ref_ext) . "'";
716
        }
717
718
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
719
        $result = $this->db->query($sql);
720
        if ($result) {
721
            if ($this->db->num_rows($result)) {
722
                $obj = $this->db->fetch_object($result);
723
724
                $this->id = $obj->rowid;
725
                $this->entity = $obj->entity;
726
                $this->ref = $obj->ref;
727
                $this->socid = $obj->socid;
728
                $this->ref_customer = $obj->ref_customer;
729
                $this->ref_ext = $obj->ref_ext;
730
                $this->status = $obj->fk_statut;
731
                $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

731
                /** @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...
732
                $this->user_author_id = $obj->fk_user_author;
733
                $this->fk_user_author = $obj->fk_user_author;
734
                $this->date_creation = $this->db->jdate($obj->date_creation);
735
                $this->date_valid = $this->db->jdate($obj->date_valid);
736
                $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
737
                $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
738
                $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
739
                $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planned
740
                $this->fk_delivery_address = $obj->fk_address;
741
                $this->model_pdf = $obj->model_pdf;
742
                $this->shipping_method_id = $obj->fk_shipping_method;
743
                $this->shipping_method = $obj->shipping_method;
744
                $this->tracking_number = $obj->tracking_number;
745
                $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

745
                /** @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...
746
                $this->origin_type = ($obj->origin_type ? $obj->origin_type : 'commande');
747
                $this->origin_id = $obj->origin_id;
748
                $this->billed = $obj->billed;
749
                $this->fk_project = $obj->fk_project;
750
                $this->signed_status = $obj->signed_status;
751
                $this->trueWeight = $obj->weight;
752
                $this->weight_units = $obj->weight_units;
753
754
                $this->trueWidth = $obj->width;
755
                $this->width_units = $obj->size_units;
756
                $this->trueHeight = $obj->height;
757
                $this->height_units = $obj->size_units;
758
                $this->trueDepth = $obj->size;
759
                $this->depth_units = $obj->size_units;
760
761
                $this->note_public = $obj->note_public;
762
                $this->note_private = $obj->note_private;
763
764
                // A denormalized value
765
                $this->trueSize = $obj->size . "x" . $obj->width . "x" . $obj->height;
766
                $this->size_units = $obj->size_units;
767
768
                //Incoterms
769
                $this->fk_incoterms = $obj->fk_incoterms;
770
                $this->location_incoterms = $obj->location_incoterms;
771
                $this->label_incoterms = $obj->label_incoterms;
772
773
                $this->db->free($result);
774
775
                // Tracking url
776
                $this->getUrlTrackingStatus($obj->tracking_number);
777
778
                // Thirdparty
779
                $result = $this->fetch_thirdparty(); // TODO Remove this
780
781
                // Retrieve extrafields
782
                $this->fetch_optionals();
783
784
                // Fix Get multicurrency param for transmitted
785
                if (isModEnabled('multicurrency')) {
786
                    if (!empty($this->multicurrency_code)) {
787
                        $this->multicurrency_code = $this->thirdparty->multicurrency_code;
788
                    }
789
                    if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($this->thirdparty->multicurrency_tx)) {
790
                        $this->multicurrency_tx = $this->thirdparty->multicurrency_tx;
791
                    }
792
                }
793
794
                /*
795
                 * Lines
796
                 */
797
                $result = $this->fetch_lines();
798
                if ($result < 0) {
799
                    return -3;
800
                }
801
802
                return 1;
803
            } else {
804
                dol_syslog(get_class($this) . '::Fetch no expedition found', LOG_ERR);
805
                $this->error = 'Shipment with id ' . $id . ' not found';
806
                return 0;
807
            }
808
        } else {
809
            $this->error = $this->db->error();
810
            return -1;
811
        }
812
    }
813
814
815
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
816
817
    /**
818
     * Forge an set tracking url
819
     *
820
     * @param string $value Value
821
     * @return  void
822
     */
823
    public function getUrlTrackingStatus($value = '')
824
    {
825
        if (!empty($this->shipping_method_id)) {
826
            $sql = "SELECT em.code, em.tracking";
827
            $sql .= " FROM " . MAIN_DB_PREFIX . "c_shipment_mode as em";
828
            $sql .= " WHERE em.rowid = " . ((int)$this->shipping_method_id);
829
830
            $resql = $this->db->query($sql);
831
            if ($resql) {
832
                if ($obj = $this->db->fetch_object($resql)) {
833
                    $tracking = $obj->tracking;
834
                }
835
            }
836
        }
837
838
        if (!empty($tracking) && !empty($value)) {
839
            $url = str_replace('{TRACKID}', $value, $tracking);
840
            $this->tracking_url = sprintf('<a target="_blank" rel="noopener noreferrer" href="%s">%s</a>', $url, ($value ? $value : 'url'));
841
        } else {
842
            $this->tracking_url = $value;
843
        }
844
    }
845
846
    /**
847
     *  Load lines
848
     *
849
     * @return int     >0 if OK, Otherwise if KO
850
     */
851
    public function fetch_lines()
852
    {
853
        // phpcs:enable
854
        global $mysoc;
855
856
        $this->lines = array();
857
858
        // NOTE: This fetch_lines is special because it groups all lines with the same origin_line_id into one line.
859
        // TODO: See if we can restore a common fetch_lines (one line = one record)
860
861
        $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";
862
        $sql .= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
863
        $sql .= ", cd.fk_remise_except, cd.fk_product_fournisseur_price as fk_fournprice";
864
        $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";
865
        $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";
866
        $sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot";
867
        $sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
868
        $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";
869
        $sql .= " FROM " . MAIN_DB_PREFIX . "expeditiondet as ed, " . MAIN_DB_PREFIX . "commandedet as cd";
870
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "product as p ON p.rowid = cd.fk_product";
871
        $sql .= " WHERE ed.fk_expedition = " . ((int)$this->id);
872
        $sql .= " AND ed.fk_elementdet = cd.rowid";
873
        $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.
874
875
        dol_syslog(get_class($this) . "::fetch_lines", LOG_DEBUG);
876
        $resql = $this->db->query($sql);
877
        if ($resql) {
878
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
879
880
            $num = $this->db->num_rows($resql);
881
            $i = 0;
882
            $lineindex = 0;
883
            $originline = 0;
884
885
            $this->total_ht = 0;
886
            $this->total_tva = 0;
887
            $this->total_ttc = 0;
888
            $this->total_localtax1 = 0;
889
            $this->total_localtax2 = 0;
890
891
            $this->multicurrency_total_ht = 0;
892
            $this->multicurrency_total_tva = 0;
893
            $this->multicurrency_total_ttc = 0;
894
895
            $shipmentlinebatch = new ExpeditionLineBatch($this->db);
896
897
            while ($i < $num) {
898
                $obj = $this->db->fetch_object($resql);
899
900
901
                if ($originline > 0 && $originline == $obj->fk_elementdet) {
902
                    '@phan-var-force ExpeditionLigne $line';  // $line from previous loop
903
                    $line->entrepot_id = 0; // entrepod_id in details_entrepot
904
                    $line->qty_shipped += $obj->qty_shipped;
905
                } else {
906
                    $line = new ExpeditionLigne($this->db);     // new group to start
907
                    $line->entrepot_id = $obj->fk_entrepot;    // this is a property of a shipment line
908
                    $line->qty_shipped = $obj->qty_shipped;    // this is a property of a shipment line
909
                }
910
911
                $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...
912
                $detail_entrepot->entrepot_id = $obj->fk_entrepot;
913
                $detail_entrepot->qty_shipped = $obj->qty_shipped;
914
                $detail_entrepot->line_id = $obj->line_id;
915
                $line->details_entrepot[] = $detail_entrepot;
916
917
                $line->line_id = $obj->line_id; // TODO deprecated
918
                $line->rowid = $obj->line_id; // TODO deprecated
919
                $line->id = $obj->line_id;
920
921
                $line->fk_origin = 'orderline'; // TODO deprecated, we already have element_type that can be use to guess type of line
922
923
                $line->fk_element = $obj->fk_element;
924
                $line->origin_id = $obj->fk_element;
925
                $line->fk_elementdet = $obj->fk_elementdet;
926
                $line->origin_line_id = $obj->fk_elementdet;
927
                $line->element_type = $obj->element_type;
928
929
                $line->fk_expedition = $this->id; // id of parent
930
931
                $line->product_type = $obj->product_type;
932
                $line->fk_product = $obj->fk_product;
933
                $line->fk_product_type = $obj->fk_product_type;
934
                $line->ref = $obj->product_ref; // TODO deprecated
935
                $line->product_ref = $obj->product_ref;
936
                $line->product_label = $obj->product_label;
937
                $line->libelle = $obj->product_label; // TODO deprecated
938
                $line->product_barcode = $obj->product_barcode; // Barcode number product
939
                $line->product_tosell = $obj->product_tosell;
940
                $line->product_tobuy = $obj->product_tobuy;
941
                $line->product_tobatch = $obj->product_tobatch;
942
                $line->fk_fournprice = $obj->fk_fournprice;
943
                $line->label = $obj->custom_label;
944
                $line->description = $obj->description;
945
                $line->qty_asked = $obj->qty_asked;
946
                $line->rang = $obj->rang;
947
                $line->weight = $obj->weight;
948
                $line->weight_units = $obj->weight_units;
949
                $line->length = $obj->length;
950
                $line->length_units = $obj->length_units;
951
                $line->width = $obj->width;
952
                $line->width_units = $obj->width_units;
953
                $line->height = $obj->height;
954
                $line->height_units = $obj->height_units;
955
                $line->surface = $obj->surface;
956
                $line->surface_units = $obj->surface_units;
957
                $line->volume = $obj->volume;
958
                $line->volume_units = $obj->volume_units;
959
                $line->fk_unit = $obj->fk_unit;
960
961
                $line->pa_ht = $obj->pa_ht;
962
963
                // Local taxes
964
                $localtax_array = array(0 => $obj->localtax1_type, 1 => $obj->localtax1_tx, 2 => $obj->localtax2_type, 3 => $obj->localtax2_tx);
965
                $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
966
                $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
967
968
                // For invoicing
969
                $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
970
                $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
971
                $line->qty = $line->qty_shipped;
972
                $line->total_ht = $tabprice[0];
973
                $line->total_localtax1 = $tabprice[9];
974
                $line->total_localtax2 = $tabprice[10];
975
                $line->total_ttc = $tabprice[2];
976
                $line->total_tva = $tabprice[1];
977
                $line->vat_src_code = $obj->vat_src_code;
978
                $line->tva_tx = $obj->tva_tx;
979
                $line->localtax1_tx = $obj->localtax1_tx;
980
                $line->localtax2_tx = $obj->localtax2_tx;
981
                $line->info_bits = $obj->info_bits;
982
                $line->price = $obj->price;
983
                $line->subprice = $obj->subprice;
984
                $line->fk_remise_except = $obj->fk_remise_except;
985
                $line->remise_percent = $obj->remise_percent;
986
987
                $this->total_ht += $tabprice[0];
988
                $this->total_tva += $tabprice[1];
989
                $this->total_ttc += $tabprice[2];
990
                $this->total_localtax1 += $tabprice[9];
991
                $this->total_localtax2 += $tabprice[10];
992
993
                $line->date_start = $this->db->jdate($obj->date_start);
994
                $line->date_end = $this->db->jdate($obj->date_end);
995
996
                // Multicurrency
997
                $this->fk_multicurrency = $obj->fk_multicurrency;
998
                $this->multicurrency_code = $obj->multicurrency_code;
999
                $line->multicurrency_subprice = $obj->multicurrency_subprice;
1000
                $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1001
                $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1002
                $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1003
1004
                $this->multicurrency_total_ht += $obj->multicurrency_total_ht;
1005
                $this->multicurrency_total_tva += $obj->multicurrency_total_tva;
1006
                $this->multicurrency_total_ttc += $obj->multicurrency_total_ttc;
1007
1008
                if ($originline != $obj->fk_elementdet) {
1009
                    $line->detail_batch = array();
1010
                }
1011
1012
                // Detail of batch
1013
                if (isModEnabled('productbatch') && $obj->line_id > 0 && $obj->product_tobatch > 0) {
1014
                    $newdetailbatch = $shipmentlinebatch->fetchAll($obj->line_id, $obj->fk_product);
1015
1016
                    if (is_array($newdetailbatch)) {
1017
                        if ($originline != $obj->fk_elementdet) {
1018
                            $line->detail_batch = $newdetailbatch;
1019
                        } else {
1020
                            $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
1021
                        }
1022
                    }
1023
                }
1024
1025
                $line->fetch_optionals();
1026
1027
                if ($originline != $obj->fk_elementdet) {
1028
                    $this->lines[$lineindex] = $line;
1029
                    $lineindex++;
1030
                } else {
1031
                    $line->total_ht += $tabprice[0];
1032
                    $line->total_localtax1 += $tabprice[9];
1033
                    $line->total_localtax2 += $tabprice[10];
1034
                    $line->total_ttc += $tabprice[2];
1035
                    $line->total_tva += $tabprice[1];
1036
                }
1037
1038
                $i++;
1039
                $originline = $obj->fk_elementdet;
1040
            }
1041
            $this->db->free($resql);
1042
            return 1;
1043
        } else {
1044
            $this->error = $this->db->error();
1045
            return -3;
1046
        }
1047
    }
1048
1049
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1050
1051
    /**
1052
     *  Return next expedition ref
1053
     *
1054
     * @param Societe $soc Thirdparty object
1055
     * @return string              Free reference for expedition
1056
     */
1057
    public function getNextNumRef($soc)
1058
    {
1059
        global $langs, $conf;
1060
        $langs->load("sendings");
1061
1062
        if (getDolGlobalString('EXPEDITION_ADDON_NUMBER')) {
1063
            $mybool = false;
1064
1065
            $file = getDolGlobalString('EXPEDITION_ADDON_NUMBER') . ".php";
1066
            $classname = getDolGlobalString('EXPEDITION_ADDON_NUMBER');
1067
1068
            // Include file with class
1069
            $dirmodels = array_merge(array('/'), (array)$conf->modules_parts['models']);
1070
1071
            foreach ($dirmodels as $reldir) {
1072
                $dir = dol_buildpath($reldir . "core/modules/expedition/");
1073
1074
                // Load file with numbering class (if found)
1075
                $mybool = ((bool)@include_once $dir . $file) || $mybool;
1076
            }
1077
1078
            if (!$mybool) {
1079
                dol_print_error(null, "Failed to include file " . $file);
1080
                return '';
1081
            }
1082
1083
            $obj = new $classname();
1084
            $numref = "";
1085
            $numref = $obj->getNextValue($soc, $this);
1086
1087
            if ($numref != "") {
1088
                return $numref;
1089
            } else {
1090
                dol_print_error($this->db, get_class($this) . "::getNextNumRef " . $obj->error);
1091
                return "";
1092
            }
1093
        } else {
1094
            print $langs->trans("Error") . " " . $langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
1095
            return "";
1096
        }
1097
    }
1098
1099
    /**
1100
     * Manage Stock MVt onb Close or valid Shipment
1101
     *
1102
     * @param User $user Object user that modify
1103
     * @param string $labelmovement Label of movement
1104
     * @return      int                         Return integer <0 if KO, >0 if OK
1105
     * @throws Exception
1106
     *
1107
     */
1108
    private function manageStockMvtOnEvt($user, $labelmovement = 'ShipmentClassifyClosedInDolibarr')
1109
    {
1110
        global $langs;
1111
1112
        $error = 0;
1113
1114
1115
        $langs->load("agenda");
1116
1117
        // Loop on each product line to add a stock movement
1118
        $sql = "SELECT cd.fk_product, cd.subprice,";
1119
        $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
1120
        $sql .= " e.ref,";
1121
        $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock,";
1122
        $sql .= " cd.rowid as cdid, ed.rowid as edid";
1123
        $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
1124
        $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
1125
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
1126
        $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "expedition as e ON ed.fk_expedition = e.rowid";
1127
        $sql .= " WHERE ed.fk_expedition = " . ((int)$this->id);
1128
        $sql .= " AND cd.rowid = ed.fk_elementdet";
1129
1130
        dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
1131
        $resql = $this->db->query($sql);
1132
        if ($resql) {
1133
            $cpt = $this->db->num_rows($resql);
1134
            for ($i = 0; $i < $cpt; $i++) {
1135
                $obj = $this->db->fetch_object($resql);
1136
                if (empty($obj->edbrowid)) {
1137
                    $qty = $obj->qty;
1138
                } else {
1139
                    $qty = $obj->edbqty;
1140
                }
1141
                if ($qty <= 0 || ($qty < 0 && !getDolGlobalInt('SHIPMENT_ALLOW_NEGATIVE_QTY'))) {
1142
                    continue;
1143
                }
1144
                dol_syslog(get_class($this) . "::valid movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
1145
1146
                $mouvS = new MouvementStock($this->db);
1147
                $mouvS->origin = &$this;
1148
                $mouvS->setOrigin($this->element, $this->id, $obj->cdid, $obj->edid);
1149
1150
                if (empty($obj->edbrowid)) {
1151
                    // line without batch detail
1152
1153
                    // 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
1154
                    $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans($labelmovement, $obj->ref));
1155
                    if ($result < 0) {
1156
                        $this->error = $mouvS->error;
1157
                        $this->errors = $mouvS->errors;
1158
                        $error++;
1159
                        break;
1160
                    }
1161
                } else {
1162
                    // line with batch detail
1163
1164
                    // 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
1165
                    $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);
1166
                    if ($result < 0) {
1167
                        $this->error = $mouvS->error;
1168
                        $this->errors = $mouvS->errors;
1169
                        $error++;
1170
                        break;
1171
                    }
1172
                }
1173
1174
                // 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
1175
                // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
1176
                $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)";
1177
                $resqldelete = $this->db->query($sqldelete);
1178
                // We do not test error, it can fails if there is child in batch details
1179
            }
1180
        } else {
1181
            $this->error = $this->db->lasterror();
1182
            $this->errors[] = $this->db->lasterror();
1183
            $error++;
1184
        }
1185
1186
        return $error;
1187
    }
1188
1189
    /**
1190
     *  Create a delivery receipt from a shipment
1191
     *
1192
     * @param User $user User
1193
     * @return int                 Return integer <0 if KO, >=0 if OK
1194
     */
1195
    public function create_delivery($user)
1196
    {
1197
        // phpcs:enable
1198
        global $conf;
1199
1200
        if (getDolGlobalInt('MAIN_SUBMODULE_DELIVERY')) {
1201
            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

1201
            if (/** @scrutinizer ignore-deprecated */ $this->statut == self::STATUS_VALIDATED || $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...
1202
                // Expedition validee
1203
                include_once DOL_DOCUMENT_ROOT . '/delivery/class/delivery.class.php';
1204
                $delivery = new Delivery($this->db);
1205
                $result = $delivery->create_from_sending($user, $this->id);
1206
                if ($result > 0) {
1207
                    return $result;
1208
                } else {
1209
                    $this->error = $delivery->error;
1210
                    return $result;
1211
                }
1212
            } else {
1213
                return 0;
1214
            }
1215
        } else {
1216
            return 0;
1217
        }
1218
    }
1219
1220
    /**
1221
     * Add an expedition line.
1222
     * If STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS is set, you can add a shipment line, with no stock source defined
1223
     * If STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT is not set, you can add a shipment line, even if not enough into stock
1224
     * Note: For product that need a batch number, you must use addline_batch()
1225
     *
1226
     * @param int $entrepot_id Id of warehouse
1227
     * @param int $id Id of source line (order line)
1228
     * @param float $qty Quantity
1229
     * @param array $array_options extrafields array
1230
     * @return  int                         Return integer <0 if KO, >0 if OK
1231
     */
1232
    public function addline($entrepot_id, $id, $qty, $array_options = [])
1233
    {
1234
        global $conf, $langs;
1235
1236
        $num = count($this->lines);
1237
        $line = new ExpeditionLigne($this->db);
1238
1239
        $line->entrepot_id = $entrepot_id;
1240
        $line->origin_line_id = $id;
1241
        $line->fk_elementdet = $id;
1242
        $line->element_type = 'order';
1243
        $line->qty = $qty;
1244
1245
        $orderline = new OrderLine($this->db);
1246
        $orderline->fetch($id);
1247
1248
        // Copy the rang of the order line to the expedition line
1249
        $line->rang = $orderline->rang;
1250
        $line->product_type = $orderline->product_type;
1251
1252
        if (isModEnabled('stock') && !empty($orderline->fk_product)) {
1253
            $fk_product = $orderline->fk_product;
1254
1255
            if (!($entrepot_id > 0) && !getDolGlobalString('STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS') && !(getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES') && $line->product_type == Product::TYPE_SERVICE)) {
1256
                $langs->load("errors");
1257
                $this->error = $langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
1258
                return -1;
1259
            }
1260
1261
            if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT')) {
1262
                $product = new Product($this->db);
1263
                $product->fetch($fk_product);
1264
1265
                // Check must be done for stock of product into warehouse if $entrepot_id defined
1266
                if ($entrepot_id > 0) {
1267
                    $product->load_stock('warehouseopen');
1268
                    $product_stock = $product->stock_warehouse[$entrepot_id]->real;
1269
                } else {
1270
                    $product_stock = $product->stock_reel;
1271
                }
1272
1273
                $product_type = $product->type;
1274
                if ($product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
1275
                    $isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
1276
                    // The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
1277
                    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.
1278
                        if ($product_stock < $qty) {
1279
                            $langs->load("errors");
1280
                            $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
1281
                            $this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
1282
1283
                            $this->db->rollback();
1284
                            return -3;
1285
                        }
1286
                    }
1287
                }
1288
            }
1289
        }
1290
1291
        // If product need a batch number, we should not have called this function but addline_batch instead.
1292
        // If this happen, we may have a bug in card.php page
1293
        if (isModEnabled('productbatch') && !empty($orderline->fk_product) && !empty($orderline->product_tobatch)) {
1294
            $this->error = 'ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH ' . $orderline->id . ' ' . $orderline->fk_product; //
1295
            return -4;
1296
        }
1297
1298
        // extrafields
1299
        if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1300
            $line->array_options = $array_options;
1301
        }
1302
1303
        $this->lines[$num] = $line;
1304
1305
        return 1;
1306
    }
1307
1308
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1309
1310
    /**
1311
     * Add a shipment line with batch record
1312
     *
1313
     * @param array $dbatch Array of value (key 'detail' -> Array, key 'qty' total quantity for line, key ix_l : original line index)
1314
     * @param array $array_options extrafields array
1315
     * @return  int                     Return integer <0 if KO, >0 if OK
1316
     */
1317
    public function addline_batch($dbatch, $array_options = [])
1318
    {
1319
        // phpcs:enable
1320
        global $conf, $langs;
1321
1322
        $num = count($this->lines);
1323
        if ($dbatch['qty'] > 0 || ($dbatch['qty'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1324
            $line = new ExpeditionLigne($this->db);
1325
            $tab = array();
1326
            foreach ($dbatch['detail'] as $key => $value) {
1327
                if ($value['q'] > 0 || ($value['q'] == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS'))) {
1328
                    // $value['q']=qty to move
1329
                    // $value['id_batch']=id into llx_product_batch of record to move
1330
                    //var_dump($value);
1331
1332
                    $linebatch = new ExpeditionLineBatch($this->db);
1333
                    $ret = $linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
1334
                    if ($ret < 0) {
1335
                        $this->setErrorsFromObject($linebatch);
1336
                        return -1;
1337
                    }
1338
                    $linebatch->qty = $value['q'];
1339
                    if ($linebatch->qty == 0 && getDolGlobalString('SHIPMENT_GETS_ALL_ORDER_PRODUCTS')) {
1340
                        $linebatch->batch = null;
1341
                    }
1342
                    $tab[] = $linebatch;
1343
1344
                    if (getDolGlobalString("STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", '0')) {
1345
                        $prod_batch = new Productbatch($this->db);
1346
                        $prod_batch->fetch($value['id_batch']);
1347
1348
                        if ($prod_batch->qty < $linebatch->qty) {
1349
                            $langs->load("errors");
1350
                            $this->errors[] = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
1351
                            dol_syslog(get_class($this) . "::addline_batch error=Product " . $prod_batch->batch . ": " . $this->errorsToString(), LOG_ERR);
1352
                            $this->db->rollback();
1353
                            return -1;
1354
                        }
1355
                    }
1356
1357
                    //var_dump($linebatch);
1358
                }
1359
            }
1360
            $line->entrepot_id = $linebatch->entrepot_id;
1361
            $line->origin_line_id = $dbatch['ix_l']; // deprecated
1362
            $line->fk_elementdet = $dbatch['ix_l'];
1363
            $line->qty = $dbatch['qty'];
1364
            $line->detail_batch = $tab;
1365
1366
            // extrafields
1367
            if (!getDolGlobalString('MAIN_EXTRAFIELDS_DISABLED') && is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1368
                $line->array_options = $array_options;
1369
            }
1370
1371
            //var_dump($line);
1372
            $this->lines[$num] = $line;
1373
            return 1;
1374
        }
1375
        return 0;
1376
    }
1377
1378
    /**
1379
     *  Update database
1380
     *
1381
     * @param User $user User that modify
1382
     * @param int $notrigger 0=launch triggers after, 1=disable triggers
1383
     * @return int                     Return integer <0 if KO, >0 if OK
1384
     */
1385
    public function update($user = null, $notrigger = 0)
1386
    {
1387
        global $conf;
1388
        $error = 0;
1389
1390
        // Clean parameters
1391
1392
        if (isset($this->ref)) {
1393
            $this->ref = trim($this->ref);
1394
        }
1395
        if (isset($this->entity)) {
1396
            $this->entity = (int)$this->entity;
1397
        }
1398
        if (isset($this->ref_customer)) {
1399
            $this->ref_customer = trim($this->ref_customer);
1400
        }
1401
        if (isset($this->socid)) {
1402
            $this->socid = (int)$this->socid;
1403
        }
1404
        if (isset($this->fk_user_author)) {
1405
            $this->fk_user_author = (int)$this->fk_user_author;
1406
        }
1407
        if (isset($this->fk_user_valid)) {
1408
            $this->fk_user_valid = (int)$this->fk_user_valid;
1409
        }
1410
        if (isset($this->fk_delivery_address)) {
1411
            $this->fk_delivery_address = (int)$this->fk_delivery_address;
1412
        }
1413
        if (isset($this->shipping_method_id)) {
1414
            $this->shipping_method_id = (int)$this->shipping_method_id;
1415
        }
1416
        if (isset($this->tracking_number)) {
1417
            $this->tracking_number = trim($this->tracking_number);
1418
        }
1419
        if (isset($this->statut)) {
1420
            $this->statut = (int)$this->statut;
1421
        }
1422
        if (isset($this->trueDepth)) {
1423
            $this->trueDepth = trim($this->trueDepth);
1424
        }
1425
        if (isset($this->trueWidth)) {
1426
            $this->trueWidth = trim($this->trueWidth);
1427
        }
1428
        if (isset($this->trueHeight)) {
1429
            $this->trueHeight = trim($this->trueHeight);
1430
        }
1431
        if (isset($this->size_units)) {
1432
            $this->size_units = trim($this->size_units);
1433
        }
1434
        if (isset($this->weight_units)) {
1435
            $this->weight_units = trim($this->weight_units);
1436
        }
1437
        if (isset($this->trueWeight)) {
1438
            $this->weight = trim((string)$this->trueWeight);
1439
        }
1440
        if (isset($this->note_private)) {
1441
            $this->note_private = trim($this->note_private);
1442
        }
1443
        if (isset($this->note_public)) {
1444
            $this->note_public = trim($this->note_public);
1445
        }
1446
        if (isset($this->model_pdf)) {
1447
            $this->model_pdf = trim($this->model_pdf);
1448
        }
1449
1450
        // Check parameters
1451
        // Put here code to add control on parameters values
1452
1453
        // Update request
1454
        $sql = "UPDATE " . MAIN_DB_PREFIX . "expedition SET";
1455
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
1456
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
1457
        $sql .= " ref_customer=" . (isset($this->ref_customer) ? "'" . $this->db->escape($this->ref_customer) . "'" : "null") . ",";
1458
        $sql .= " fk_soc=" . (isset($this->socid) ? $this->socid : "null") . ",";
1459
        $sql .= " date_creation=" . (dol_strlen($this->date_creation) != 0 ? "'" . $this->db->idate($this->date_creation) . "'" : 'null') . ",";
1460
        $sql .= " fk_user_author=" . (isset($this->fk_user_author) ? $this->fk_user_author : "null") . ",";
1461
        $sql .= " date_valid=" . (dol_strlen($this->date_valid) != 0 ? "'" . $this->db->idate($this->date_valid) . "'" : 'null') . ",";
1462
        $sql .= " fk_user_valid=" . (isset($this->fk_user_valid) ? $this->fk_user_valid : "null") . ",";
1463
        $sql .= " date_expedition=" . (dol_strlen($this->date_expedition) != 0 ? "'" . $this->db->idate($this->date_expedition) . "'" : 'null') . ",";
1464
        $sql .= " date_delivery=" . (dol_strlen($this->date_delivery) != 0 ? "'" . $this->db->idate($this->date_delivery) . "'" : 'null') . ",";
1465
        $sql .= " fk_address=" . (isset($this->fk_delivery_address) ? $this->fk_delivery_address : "null") . ",";
1466
        $sql .= " fk_shipping_method=" . ((isset($this->shipping_method_id) && $this->shipping_method_id > 0) ? $this->shipping_method_id : "null") . ",";
1467
        $sql .= " tracking_number=" . (isset($this->tracking_number) ? "'" . $this->db->escape($this->tracking_number) . "'" : "null") . ",";
1468
        $sql .= " fk_statut=" . (isset($this->statut) ? $this->statut : "null") . ",";
1469
        $sql .= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
1470
        $sql .= " height=" . (($this->trueHeight != '') ? $this->trueHeight : "null") . ",";
1471
        $sql .= " width=" . (($this->trueWidth != '') ? $this->trueWidth : "null") . ",";
1472
        $sql .= " size_units=" . (isset($this->size_units) ? $this->size_units : "null") . ",";
1473
        $sql .= " size=" . (($this->trueDepth != '') ? $this->trueDepth : "null") . ",";
1474
        $sql .= " weight_units=" . (isset($this->weight_units) ? $this->weight_units : "null") . ",";
1475
        $sql .= " weight=" . (($this->trueWeight != '') ? $this->trueWeight : "null") . ",";
1476
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
1477
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
1478
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
1479
        $sql .= " entity=" . $conf->entity;
1480
        $sql .= " WHERE rowid=" . ((int)$this->id);
1481
1482
        $this->db->begin();
1483
1484
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
1485
        $resql = $this->db->query($sql);
1486
        if (!$resql) {
1487
            $error++;
1488
            $this->errors[] = "Error " . $this->db->lasterror();
1489
        }
1490
1491
        if (!$error && !$notrigger) {
1492
            // Call trigger
1493
            $result = $this->call_trigger('SHIPPING_MODIFY', $user);
1494
            if ($result < 0) {
1495
                $error++;
1496
            }
1497
            // End call triggers
1498
        }
1499
1500
        // Commit or rollback
1501
        if ($error) {
1502
            foreach ($this->errors as $errmsg) {
1503
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
1504
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1505
            }
1506
            $this->db->rollback();
1507
            return -1 * $error;
1508
        } else {
1509
            $this->db->commit();
1510
            return 1;
1511
        }
1512
    }
1513
1514
    /**
1515
     *  Cancel shipment.
1516
     *
1517
     * @param int $notrigger Disable triggers
1518
     * @param bool $also_update_stock true if the stock should be increased back (false by default)
1519
     * @return int                         >0 if OK, 0 if deletion done but failed to delete files, <0 if KO
1520
     */
1521
    public function cancel($notrigger = 0, $also_update_stock = false)
1522
    {
1523
        global $conf, $langs, $user;
1524
1525
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1526
1527
        $error = 0;
1528
        $this->error = '';
1529
1530
        $this->db->begin();
1531
1532
        // Add a protection to refuse deleting if shipment has at least one delivery
1533
        $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1534
        if (count($this->linkedObjectsIds) > 0) {
1535
            $this->error = 'ErrorThereIsSomeDeliveries';
1536
            $error++;
1537
        }
1538
1539
        if (!$error && !$notrigger) {
1540
            // Call trigger
1541
            $result = $this->call_trigger('SHIPPING_CANCEL', $user);
1542
            if ($result < 0) {
1543
                $error++;
1544
            }
1545
            // End call triggers
1546
        }
1547
1548
        // Stock control
1549
        if (
1550
            !$error && isModEnabled('stock') &&
1551
            ((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

1551
            ((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...
1552
                (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

1552
                (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...
1553
        ) {
1554
            require_once DOL_DOCUMENT_ROOT . "/product/stock/class/mouvementstock.class.php";
1555
1556
            $langs->load("agenda");
1557
1558
            // Loop on each product line to add a stock movement and delete features
1559
            $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1560
            $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
1561
            $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
1562
            $sql .= " WHERE ed.fk_expedition = " . ((int)$this->id);
1563
            $sql .= " AND cd.rowid = ed.fk_elementdet";
1564
1565
            dol_syslog(get_class($this) . "::delete select details", LOG_DEBUG);
1566
            $resql = $this->db->query($sql);
1567
            if ($resql) {
1568
                $cpt = $this->db->num_rows($resql);
1569
1570
                $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1571
1572
                for ($i = 0; $i < $cpt; $i++) {
1573
                    dol_syslog(get_class($this) . "::delete movement index " . $i);
1574
                    $obj = $this->db->fetch_object($resql);
1575
1576
                    $mouvS = new MouvementStock($this->db);
1577
                    // we do not log origin because it will be deleted
1578
                    $mouvS->origin = '';
1579
                    // get lot/serial
1580
                    $lotArray = null;
1581
                    if (isModEnabled('productbatch')) {
1582
                        $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1583
                        if (!is_array($lotArray)) {
1584
                            $error++;
1585
                            $this->errors[] = "Error " . $this->db->lasterror();
1586
                        }
1587
                    }
1588
1589
                    if (empty($lotArray)) {
1590
                        // no lot/serial
1591
                        // We increment stock of product (and sub-products)
1592
                        // We use warehouse selected for each line
1593
                        $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
1594
                        if ($result < 0) {
1595
                            $error++;
1596
                            $this->errors = array_merge($this->errors, $mouvS->errors);
1597
                            break;
1598
                        }
1599
                    } else {
1600
                        // We increment stock of batches
1601
                        // We use warehouse selected for each line
1602
                        foreach ($lotArray as $lot) {
1603
                            $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
1604
                            if ($result < 0) {
1605
                                $error++;
1606
                                $this->errors = array_merge($this->errors, $mouvS->errors);
1607
                                break;
1608
                            }
1609
                        }
1610
                        if ($error) {
1611
                            break; // break for loop in case of error
1612
                        }
1613
                    }
1614
                }
1615
            } else {
1616
                $error++;
1617
                $this->errors[] = "Error " . $this->db->lasterror();
1618
            }
1619
        }
1620
1621
        // delete batch expedition line
1622
        if (!$error && isModEnabled('productbatch')) {
1623
            $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1624
            if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1625
                $error++;
1626
                $this->errors[] = "Error " . $this->db->lasterror();
1627
            }
1628
        }
1629
1630
1631
        if (!$error) {
1632
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "expeditiondet";
1633
            $sql .= " WHERE fk_expedition = " . ((int)$this->id);
1634
1635
            if ($this->db->query($sql)) {
1636
                // Delete linked object
1637
                $res = $this->deleteObjectLinked();
1638
                if ($res < 0) {
1639
                    $error++;
1640
                }
1641
1642
                // No delete expedition
1643
                if (!$error) {
1644
                    $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "expedition";
1645
                    $sql .= " WHERE rowid = " . ((int)$this->id);
1646
1647
                    if ($this->db->query($sql)) {
1648
                        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

1648
                        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...
1649
                            $this->fetch_origin();
1650
                            if ($this->origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) {     // If order source of shipment is "shipment in progress"
1651
                                // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1652
                                $this->origin_object->loadExpeditions();
1653
                                //var_dump($this->$origin->expeditions);exit;
1654
                                if (count($this->origin_object->expeditions) <= 0) {
1655
                                    $this->origin_object->setStatut(Commande::STATUS_VALIDATED);
1656
                                }
1657
                            }
1658
                        }
1659
1660
                        if (!$error) {
1661
                            $this->db->commit();
1662
1663
                            // We delete PDFs
1664
                            $ref = dol_sanitizeFileName($this->ref);
1665
                            if (!empty($conf->expedition->dir_output)) {
1666
                                $dir = $conf->expedition->dir_output . '/sending/' . $ref;
1667
                                $file = $dir . '/' . $ref . '.pdf';
1668
                                if (file_exists($file)) {
1669
                                    if (!dol_delete_file($file)) {
1670
                                        return 0;
1671
                                    }
1672
                                }
1673
                                if (file_exists($dir)) {
1674
                                    if (!dol_delete_dir_recursive($dir)) {
1675
                                        $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1676
                                        return 0;
1677
                                    }
1678
                                }
1679
                            }
1680
1681
                            return 1;
1682
                        } else {
1683
                            $this->db->rollback();
1684
                            return -1;
1685
                        }
1686
                    } else {
1687
                        $this->error = $this->db->lasterror() . " - sql=$sql";
1688
                        $this->db->rollback();
1689
                        return -3;
1690
                    }
1691
                } else {
1692
                    $this->error = $this->db->lasterror() . " - sql=$sql";
1693
                    $this->db->rollback();
1694
                    return -2;
1695
                }//*/
1696
            } else {
1697
                $this->error = $this->db->lasterror() . " - sql=$sql";
1698
                $this->db->rollback();
1699
                return -1;
1700
            }
1701
        } else {
1702
            $this->db->rollback();
1703
            return -1;
1704
        }
1705
    }
1706
1707
    /**
1708
     *  Delete detail line
1709
     *
1710
     * @param User $user User making deletion
1711
     * @param int $lineid Id of line to delete
1712
     * @return     int                     >0 if OK, <0 if KO
1713
     */
1714
    public function deleteLine($user, $lineid)
1715
    {
1716
        global $user;
1717
1718
        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

1718
        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...
1719
            $this->db->begin();
1720
1721
            $line = new ExpeditionLigne($this->db);
1722
1723
            // For triggers
1724
            $line->fetch($lineid);
1725
1726
            if ($line->delete($user) > 0) {
1727
                //$this->update_price(1);
1728
1729
                $this->db->commit();
1730
                return 1;
1731
            } else {
1732
                $this->db->rollback();
1733
                return -1;
1734
            }
1735
        } else {
1736
            $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1737
            return -2;
1738
        }
1739
    }
1740
1741
    /**
1742
     *  Delete shipment.
1743
     *  Warning, do not delete a shipment if a delivery is linked to (with table llx_element_element)
1744
     *
1745
     * @param User $user User making the deletion
1746
     * @param int $notrigger Disable triggers
1747
     * @param bool $also_update_stock true if the stock should be increased back (false by default)
1748
     * @return int                             >0 if OK, 0 if deletion done but failed to delete files, <0 if KO
1749
     */
1750
    public function delete($user = null, $notrigger = 0, $also_update_stock = false)
1751
    {
1752
        global $conf, $langs;
1753
1754
        if (empty($user)) {
1755
            global $user;
1756
        }
1757
1758
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1759
1760
        $error = 0;
1761
        $this->error = '';
1762
1763
        $this->db->begin();
1764
1765
        // Add a protection to refuse deleting if shipment has at least one delivery
1766
        $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
1767
        if (count($this->linkedObjectsIds) > 0) {
1768
            $this->error = 'ErrorThereIsSomeDeliveries';
1769
            $error++;
1770
        }
1771
1772
        if (!$error && !$notrigger) {
1773
            // Call trigger
1774
            $result = $this->call_trigger('SHIPPING_DELETE', $user);
1775
            if ($result < 0) {
1776
                $error++;
1777
            }
1778
            // End call triggers
1779
        }
1780
1781
        // Stock control
1782
        if (
1783
            !$error && isModEnabled('stock') &&
1784
            ((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

1784
            ((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...
1785
                (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

1785
                (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...
1786
        ) {
1787
            require_once DOL_DOCUMENT_ROOT . "/product/stock/class/mouvementstock.class.php";
1788
1789
            $langs->load("agenda");
1790
1791
            // we try deletion of batch line even if module batch not enabled in case of the module were enabled and disabled previously
1792
            $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1793
1794
            // Loop on each product line to add a stock movement
1795
            $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
1796
            $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
1797
            $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
1798
            $sql .= " WHERE ed.fk_expedition = " . ((int)$this->id);
1799
            $sql .= " AND cd.rowid = ed.fk_elementdet";
1800
1801
            dol_syslog(get_class($this) . "::delete select details", LOG_DEBUG);
1802
            $resql = $this->db->query($sql);
1803
            if ($resql) {
1804
                $cpt = $this->db->num_rows($resql);
1805
                for ($i = 0; $i < $cpt; $i++) {
1806
                    dol_syslog(get_class($this) . "::delete movement index " . $i);
1807
                    $obj = $this->db->fetch_object($resql);
1808
1809
                    $mouvS = new MouvementStock($this->db);
1810
                    // we do not log origin because it will be deleted
1811
                    $mouvS->origin = '';
1812
                    // get lot/serial
1813
                    $lotArray = $shipmentlinebatch->fetchAll($obj->expeditiondet_id);
1814
                    if (!is_array($lotArray)) {
1815
                        $error++;
1816
                        $this->errors[] = "Error " . $this->db->lasterror();
1817
                    }
1818
                    if (empty($lotArray)) {
1819
                        // no lot/serial
1820
                        // We increment stock of product (and sub-products)
1821
                        // We use warehouse selected for each line
1822
                        $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
1823
                        if ($result < 0) {
1824
                            $error++;
1825
                            $this->errors = array_merge($this->errors, $mouvS->errors);
1826
                            break;
1827
                        }
1828
                    } else {
1829
                        // We increment stock of batches
1830
                        // We use warehouse selected for each line
1831
                        foreach ($lotArray as $lot) {
1832
                            $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
1833
                            if ($result < 0) {
1834
                                $error++;
1835
                                $this->errors = array_merge($this->errors, $mouvS->errors);
1836
                                break;
1837
                            }
1838
                        }
1839
                        if ($error) {
1840
                            break; // break for loop in case of error
1841
                        }
1842
                    }
1843
                }
1844
            } else {
1845
                $error++;
1846
                $this->errors[] = "Error " . $this->db->lasterror();
1847
            }
1848
        }
1849
1850
        // delete batch expedition line
1851
        if (!$error) {
1852
            $shipmentlinebatch = new ExpeditionLineBatch($this->db);
1853
            if ($shipmentlinebatch->deleteFromShipment($this->id) < 0) {
1854
                $error++;
1855
                $this->errors[] = "Error " . $this->db->lasterror();
1856
            }
1857
        }
1858
1859
        if (!$error) {
1860
            $main = MAIN_DB_PREFIX . 'expeditiondet';
1861
            $ef = $main . "_extrafields";
1862
            $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_expedition = " . ((int)$this->id) . ")";
1863
1864
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "expeditiondet";
1865
            $sql .= " WHERE fk_expedition = " . ((int)$this->id);
1866
1867
            if ($this->db->query($sqlef) && $this->db->query($sql)) {
1868
                // Delete linked object
1869
                $res = $this->deleteObjectLinked();
1870
                if ($res < 0) {
1871
                    $error++;
1872
                }
1873
1874
                // delete extrafields
1875
                $res = $this->deleteExtraFields();
1876
                if ($res < 0) {
1877
                    $error++;
1878
                }
1879
1880
                if (!$error) {
1881
                    $sql = "DELETE FROM " . MAIN_DB_PREFIX . "expedition";
1882
                    $sql .= " WHERE rowid = " . ((int)$this->id);
1883
1884
                    if ($this->db->query($sql)) {
1885
                        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

1885
                        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...
1886
                            $this->fetch_origin();
1887
                            if ($this->origin_object->statut == Commande::STATUS_SHIPMENTONPROCESS) {     // If order source of shipment is "shipment in progress"
1888
                                // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
1889
                                $this->origin_object->loadExpeditions();
1890
                                //var_dump($this->$origin->expeditions);exit;
1891
                                if (count($this->origin_object->expeditions) <= 0) {
1892
                                    $this->origin_object->setStatut(Commande::STATUS_VALIDATED);
1893
                                }
1894
                            }
1895
                        }
1896
1897
                        if (!$error) {
1898
                            $this->db->commit();
1899
1900
                            // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1901
                            $this->deleteEcmFiles(0);    // Deleting files physically is done later with the dol_delete_dir_recursive
1902
                            $this->deleteEcmFiles(1);    // Deleting files physically is done later with the dol_delete_dir_recursive
1903
1904
                            // We delete PDFs
1905
                            $ref = dol_sanitizeFileName($this->ref);
1906
                            if (!empty($conf->expedition->dir_output)) {
1907
                                $dir = $conf->expedition->dir_output . '/sending/' . $ref;
1908
                                $file = $dir . '/' . $ref . '.pdf';
1909
                                if (file_exists($file)) {
1910
                                    if (!dol_delete_file($file)) {
1911
                                        return 0;
1912
                                    }
1913
                                }
1914
                                if (file_exists($dir)) {
1915
                                    if (!dol_delete_dir_recursive($dir)) {
1916
                                        $this->error = $langs->trans("ErrorCanNotDeleteDir", $dir);
1917
                                        return 0;
1918
                                    }
1919
                                }
1920
                            }
1921
1922
                            return 1;
1923
                        } else {
1924
                            $this->db->rollback();
1925
                            return -1;
1926
                        }
1927
                    } else {
1928
                        $this->error = $this->db->lasterror() . " - sql=$sql";
1929
                        $this->db->rollback();
1930
                        return -3;
1931
                    }
1932
                } else {
1933
                    $this->error = $this->db->lasterror() . " - sql=$sql";
1934
                    $this->db->rollback();
1935
                    return -2;
1936
                }
1937
            } else {
1938
                $this->error = $this->db->lasterror() . " - sql=$sql";
1939
                $this->db->rollback();
1940
                return -1;
1941
            }
1942
        } else {
1943
            $this->db->rollback();
1944
            return -1;
1945
        }
1946
    }
1947
1948
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1949
1950
    /**
1951
     * getTooltipContentArray
1952
     *
1953
     * @param array $params ex option, infologin
1954
     * @return array
1955
     * @since v18
1956
     */
1957
    public function getTooltipContentArray($params)
1958
    {
1959
        global $conf, $langs;
1960
1961
        $langs->load('sendings');
1962
1963
        $nofetch = !empty($params['nofetch']);
1964
1965
        $datas = array();
1966
        $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("Shipment") . '</u>';
1967
        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

1967
        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...
1968
            $datas['picto'] .= ' ' . $this->getLibStatut(5);
1969
        }
1970
        $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
1971
        $datas['refcustomer'] = '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . ($this->ref_customer ? $this->ref_customer : $this->ref_client);
1972
        if (!$nofetch) {
1973
            $langs->load('companies');
1974
            if (empty($this->thirdparty)) {
1975
                $this->fetch_thirdparty();
1976
            }
1977
            $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

1977
            $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...
1978
        }
1979
1980
        return $datas;
1981
    }
1982
1983
    /**
1984
     *  Return status label
1985
     *
1986
     * @param int $mode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto
1987
     * @return     string              Label
1988
     */
1989
    public function getLibStatut($mode = 0)
1990
    {
1991
        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

1991
        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...
1992
    }
1993
1994
    /**
1995
     * Return label of a status
1996
     *
1997
     * @param int $status Id statut
1998
     * @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
1999
     * @return  string              Label of status
2000
     */
2001
    public function LibStatut($status, $mode)
2002
    {
2003
        // phpcs:enable
2004
        global $langs;
2005
2006
        $labelStatus = $langs->transnoentitiesnoconv($this->labelStatus[$status]);
2007
        $labelStatusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
2008
2009
        $statusType = 'status' . $status;
2010
        if ($status == self::STATUS_VALIDATED) {
2011
            $statusType = 'status4';
2012
        }
2013
        if ($status == self::STATUS_CLOSED) {
2014
            $statusType = 'status6';
2015
        }
2016
        if ($status == self::STATUS_CANCELED) {
2017
            $statusType = 'status9';
2018
        }
2019
2020
        return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
2021
    }
2022
2023
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2024
2025
    /**
2026
     *  Return clicable link of object (with eventually picto)
2027
     *
2028
     * @param int $withpicto Add picto into link
2029
     * @param string $option Where the link point to
2030
     * @param int $max Max length to show
2031
     * @param int $short Use short labels
2032
     * @param int $notooltip 1=No tooltip
2033
     * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
2034
     * @return     string                                  String with URL
2035
     */
2036
    public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
2037
    {
2038
        global $langs, $hookmanager;
2039
2040
        $result = '';
2041
        $params = [
2042
            'id' => $this->id,
2043
            'objecttype' => $this->element,
2044
            'option' => $option,
2045
            'nofetch' => 1,
2046
        ];
2047
        $classfortooltip = 'classfortooltip';
2048
        $dataparams = '';
2049
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2050
            $classfortooltip = 'classforajaxtooltip';
2051
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
2052
            $label = '';
2053
        } else {
2054
            $label = implode($this->getTooltipContentArray($params));
2055
        }
2056
2057
        $url = constant('BASE_URL') . '/expedition/card.php?id=' . $this->id;
2058
2059
        if ($short) {
2060
            return $url;
2061
        }
2062
2063
        if ($option !== 'nolink') {
2064
            // Add param to save lastsearch_values or not
2065
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2066
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2067
                $add_save_lastsearch_values = 1;
2068
            }
2069
            if ($add_save_lastsearch_values) {
2070
                $url .= '&save_lastsearch_values=1';
2071
            }
2072
        }
2073
2074
        $linkclose = '';
2075
        if (empty($notooltip)) {
2076
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2077
                $label = $langs->trans("Shipment");
2078
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
2079
            }
2080
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
2081
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
2082
        }
2083
2084
        $linkstart = '<a href="' . $url . '"';
2085
        $linkstart .= $linkclose . '>';
2086
        $linkend = '</a>';
2087
2088
        $result .= $linkstart;
2089
        if ($withpicto) {
2090
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="' . (($withpicto != 2) ? 'paddingright ' : '') . '"'), 0, 0, $notooltip ? 0 : 1);
2091
        }
2092
        if ($withpicto != 2) {
2093
            $result .= $this->ref;
2094
        }
2095
        $result .= $linkend;
2096
        global $action;
2097
        $hookmanager->initHooks(array($this->element . 'dao'));
2098
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2099
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2100
        if ($reshook > 0) {
2101
            $result = $hookmanager->resPrint;
2102
        } else {
2103
            $result .= $hookmanager->resPrint;
2104
        }
2105
        return $result;
2106
    }
2107
2108
    /**
2109
     *  Return clicable link of object (with eventually picto)
2110
     *
2111
     * @param string $option Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
2112
     * @param array $arraydata Array of data
2113
     * @return     string                              HTML Code for Kanban thumb.
2114
     */
2115
    public function getKanbanView($option = '', $arraydata = null)
2116
    {
2117
        global $langs, $conf;
2118
2119
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2120
2121
        $return = '<div class="box-flex-item box-flex-grow-zero">';
2122
        $return .= '<div class="info-box info-box-sm">';
2123
        $return .= '<div class="info-box-icon bg-infobox-action">';
2124
        $return .= img_picto('', 'order');
2125
        $return .= '</div>';
2126
        $return .= '<div class="info-box-content">';
2127
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref) . '</span>';
2128
        if ($selected >= 0) {
2129
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
2130
        }
2131
        if (property_exists($this, 'thirdparty') && is_object($this->thirdparty)) {
2132
            $return .= '<br><div class="info-box-ref tdoverflowmax150">' . $this->thirdparty->getNomUrl(1) . '</div>';
2133
        }
2134
        if (property_exists($this, 'total_ht')) {
2135
            $return .= '<div class="info-box-ref amount">' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency) . ' ' . $langs->trans('HT') . '</div>';
2136
        }
2137
        if (method_exists($this, 'getLibStatut')) {
2138
            $return .= '<div class="info-box-status">' . $this->getLibStatut(3) . '</div>';
2139
        }
2140
        $return .= '</div>';
2141
        $return .= '</div>';
2142
        $return .= '</div>';
2143
2144
        return $return;
2145
    }
2146
2147
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2148
2149
    /**
2150
     *  Initialise an instance with random values.
2151
     *  Used to build previews or test instances.
2152
     *  id must be 0 if object instance is a specimen.
2153
     *
2154
     * @return int
2155
     */
2156
    public function initAsSpecimen()
2157
    {
2158
        global $langs;
2159
2160
        $now = dol_now();
2161
2162
        dol_syslog(get_class($this) . "::initAsSpecimen");
2163
2164
        $order = new Commande($this->db);
2165
        $order->initAsSpecimen();
2166
2167
        // Initialise parameters
2168
        $this->id = 0;
2169
        $this->ref = 'SPECIMEN';
2170
        $this->specimen = 1;
2171
        $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

2171
        /** @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...
2172
        $this->livraison_id = 0;
2173
        $this->date = $now;
2174
        $this->date_creation = $now;
2175
        $this->date_valid = $now;
2176
        $this->date_delivery = $now + 24 * 3600;
2177
        $this->date_expedition = $now + 24 * 3600;
2178
2179
        $this->entrepot_id = 0;
2180
        $this->fk_delivery_address = 0;
2181
        $this->socid = 1;
2182
2183
        $this->commande_id = 0;
2184
        $this->commande = $order;
2185
2186
        $this->origin_id = 1;
2187
        $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

2187
        /** @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...
2188
2189
        $this->note_private = 'Private note';
2190
        $this->note_public = 'Public note';
2191
2192
        $nbp = 5;
2193
        $xnbp = 0;
2194
        while ($xnbp < $nbp) {
2195
            $line = new ExpeditionLigne($this->db);
2196
            $line->product_desc = $langs->trans("Description") . " " . $xnbp;
2197
            $line->product_label = $langs->trans("Description") . " " . $xnbp;
2198
            $line->qty = 10;
2199
            $line->qty_asked = 5;
2200
            $line->qty_shipped = 4;
2201
            $line->fk_product = $this->commande->lines[$xnbp]->fk_product;
2202
2203
            $this->lines[] = $line;
2204
            $xnbp++;
2205
        }
2206
2207
        return 1;
2208
    }
2209
2210
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2211
2212
    /**
2213
     *  Set delivery date
2214
     *
2215
     * @param User $user Object user that modify
2216
     * @param int $delivery_date Delivery date
2217
     * @return     int                         Return integer <0 if ko, >0 if ok
2218
     * @deprecated Use  setDeliveryDate
2219
     */
2220
    public function set_date_livraison($user, $delivery_date)
2221
    {
2222
        // phpcs:enable
2223
        return $this->setDeliveryDate($user, $delivery_date);
2224
    }
2225
2226
    /**
2227
     *  Set the planned delivery date
2228
     *
2229
     * @param User $user Object user that modify
2230
     * @param integer $delivery_date Date of delivery
2231
     * @return     int                                 Return integer <0 if KO, >0 if OK
2232
     */
2233
    public function setDeliveryDate($user, $delivery_date)
2234
    {
2235
        if ($user->hasRight('expedition', 'creer')) {
2236
            $sql = "UPDATE " . MAIN_DB_PREFIX . "expedition";
2237
            $sql .= " SET date_delivery = " . ($delivery_date ? "'" . $this->db->idate($delivery_date) . "'" : 'null');
2238
            $sql .= " WHERE rowid = " . ((int)$this->id);
2239
2240
            dol_syslog(get_class($this) . "::setDeliveryDate", LOG_DEBUG);
2241
            $resql = $this->db->query($sql);
2242
            if ($resql) {
2243
                $this->date_delivery = $delivery_date;
2244
                return 1;
2245
            } else {
2246
                $this->error = $this->db->error();
2247
                return -1;
2248
            }
2249
        } else {
2250
            return -2;
2251
        }
2252
    }
2253
2254
    /**
2255
     *  Fetch deliveries method and return an array. Load array this->meths(rowid=>label).
2256
     *
2257
     * @return void
2258
     */
2259
    public function fetch_delivery_methods()
2260
    {
2261
        // phpcs:enable
2262
        global $langs;
2263
        $this->meths = array();
2264
2265
        $sql = "SELECT em.rowid, em.code, em.libelle as label";
2266
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_shipment_mode as em";
2267
        $sql .= " WHERE em.active = 1";
2268
        $sql .= " ORDER BY em.libelle ASC";
2269
2270
        $resql = $this->db->query($sql);
2271
        if ($resql) {
2272
            while ($obj = $this->db->fetch_object($resql)) {
2273
                $label = $langs->trans('SendingMethod' . $obj->code);
2274
                $this->meths[$obj->rowid] = ($label != 'SendingMethod' . $obj->code ? $label : $obj->label);
2275
            }
2276
        }
2277
    }
2278
2279
    /**
2280
     *  Fetch all deliveries method and return an array. Load array this->listmeths.
2281
     *
2282
     * @param int $id only this carrier, all if none
2283
     * @return void
2284
     */
2285
    public function list_delivery_methods($id = 0)
2286
    {
2287
        // phpcs:enable
2288
        global $langs;
2289
2290
        $this->listmeths = array();
2291
        $i = 0;
2292
2293
        $sql = "SELECT em.rowid, em.code, em.libelle as label, em.description, em.tracking, em.active";
2294
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_shipment_mode as em";
2295
        if (!empty($id)) {
2296
            $sql .= " WHERE em.rowid=" . ((int)$id);
2297
        }
2298
2299
        $resql = $this->db->query($sql);
2300
        if ($resql) {
2301
            while ($obj = $this->db->fetch_object($resql)) {
2302
                $this->listmeths[$i]['rowid'] = $obj->rowid;
2303
                $this->listmeths[$i]['code'] = $obj->code;
2304
                $label = $langs->trans('SendingMethod' . $obj->code);
2305
                $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod' . $obj->code ? $label : $obj->label);
2306
                $this->listmeths[$i]['description'] = $obj->description;
2307
                $this->listmeths[$i]['tracking'] = $obj->tracking;
2308
                $this->listmeths[$i]['active'] = $obj->active;
2309
                $i++;
2310
            }
2311
        }
2312
    }
2313
2314
    /**
2315
     *  Classify the shipping as closed (this records also the stock movement)
2316
     *
2317
     * @return     int     Return integer <0 if KO, >0 if OK
2318
     */
2319
    public function setClosed()
2320
    {
2321
        global $user;
2322
2323
        $error = 0;
2324
2325
        // Protection. This avoid to move stock later when we should not
2326
        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

2326
        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...
2327
            return 0;
2328
        }
2329
2330
        $this->db->begin();
2331
2332
        $sql = "UPDATE " . MAIN_DB_PREFIX . "expedition SET fk_statut = " . self::STATUS_CLOSED;
2333
        $sql .= " WHERE rowid = " . ((int)$this->id) . " AND fk_statut > 0";
2334
2335
        $resql = $this->db->query($sql);
2336
        if ($resql) {
2337
            // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
2338
            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

2338
            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...
2339
                $order = new Commande($this->db);
2340
                $order->fetch($this->origin_id);
2341
2342
                $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
2343
2344
                $shipments_match_order = 1;
2345
                foreach ($order->lines as $line) {
2346
                    $lineid = $line->id;
2347
                    $qty = $line->qty;
2348
                    if (($line->product_type == 0 || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) && $order->expeditions[$lineid] != $qty) {
2349
                        $shipments_match_order = 0;
2350
                        $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';
2351
                        dol_syslog($text);
2352
                        break;
2353
                    }
2354
                }
2355
                if ($shipments_match_order) {
2356
                    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');
2357
                    // We close the order
2358
                    $order->cloture($user);     // Note this may also create an invoice if module workflow ask it
2359
                }
2360
            }
2361
2362
            $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

2362
            /** @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...
2363
            $this->status = self::STATUS_CLOSED;    // Will be revert to STATUS_VALIDATED at end if there is a rollback
2364
2365
            // If stock increment is done on closing
2366
            if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2367
                $result = $this->manageStockMvtOnEvt($user);
2368
                if ($result < 0) {
2369
                    $error++;
2370
                }
2371
            }
2372
2373
            // Call trigger
2374
            if (!$error) {
2375
                $result = $this->call_trigger('SHIPPING_CLOSED', $user);
2376
                if ($result < 0) {
2377
                    $error++;
2378
                }
2379
            }
2380
        } else {
2381
            dol_print_error($this->db);
2382
            $error++;
2383
        }
2384
2385
        if (!$error) {
2386
            $this->db->commit();
2387
            return 1;
2388
        } else {
2389
            $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

2389
            /** @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...
2390
            $this->status = self::STATUS_VALIDATED;
2391
2392
            $this->db->rollback();
2393
            return -1;
2394
        }
2395
    }
2396
2397
    /**
2398
     *  Classify the shipping as invoiced (used for example by trigger when WORKFLOW_SHIPPING_CLASSIFY_BILLED_INVOICE is on)
2399
     *
2400
     * @return     int     Return integer <0 if ko, >0 if ok
2401
     */
2402
    public function setBilled()
2403
    {
2404
        global $user;
2405
        $error = 0;
2406
2407
        $this->db->begin();
2408
2409
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'expedition SET billed = 1';
2410
        $sql .= " WHERE rowid = " . ((int)$this->id) . ' AND fk_statut > 0';
2411
2412
        $resql = $this->db->query($sql);
2413
        if ($resql) {
2414
            $this->billed = 1;
2415
2416
            // Call trigger
2417
            $result = $this->call_trigger('SHIPPING_BILLED', $user);
2418
            if ($result < 0) {
2419
                $this->billed = 0;
2420
                $error++;
2421
            }
2422
        } else {
2423
            $error++;
2424
            $this->errors[] = $this->db->lasterror;
2425
        }
2426
2427
        if (empty($error)) {
2428
            $this->db->commit();
2429
            return 1;
2430
        } else {
2431
            $this->db->rollback();
2432
            return -1;
2433
        }
2434
    }
2435
2436
    /**
2437
     *  Set draft status
2438
     *
2439
     * @param User $user Object user that modify
2440
     * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
2441
     * @return int                     Return integer <0 if KO, >0 if OK
2442
     */
2443
    public function setDraft($user, $notrigger = 0)
2444
    {
2445
        // Protection
2446
        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

2446
        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...
2447
            return 0;
2448
        }
2449
2450
        return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'SHIPMENT_UNVALIDATE');
2451
    }
2452
2453
    /**
2454
     *  Classify the shipping as validated/opened
2455
     *
2456
     * @return     int     Return integer <0 if KO, 0 if already open, >0 if OK
2457
     */
2458
    public function reOpen()
2459
    {
2460
        global $langs, $user;
2461
2462
        $error = 0;
2463
2464
        // Protection. This avoid to move stock later when we should not
2465
        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

2465
        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...
2466
            return 0;
2467
        }
2468
2469
        $this->db->begin();
2470
2471
        $oldbilled = $this->billed;
2472
2473
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'expedition SET fk_statut = 1';
2474
        $sql .= " WHERE rowid = " . ((int)$this->id) . ' AND fk_statut > 0';
2475
2476
        $resql = $this->db->query($sql);
2477
        if ($resql) {
2478
            $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

2478
            /** @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...
2479
            $this->status = self::STATUS_VALIDATED;
2480
            $this->billed = 0;
2481
2482
            // If stock increment is done on closing
2483
            if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
2484
2485
                $langs->load("agenda");
2486
2487
                // Loop on each product line to add a stock movement
2488
                // TODO possibilite d'expedier a partir d'une propale ou autre origine
2489
                $sql = "SELECT cd.fk_product, cd.subprice,";
2490
                $sql .= " ed.rowid, ed.qty, ed.fk_entrepot,";
2491
                $sql .= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
2492
                $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd,";
2493
                $sql .= " " . MAIN_DB_PREFIX . "expeditiondet as ed";
2494
                $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
2495
                $sql .= " WHERE ed.fk_expedition = " . ((int)$this->id);
2496
                $sql .= " AND cd.rowid = ed.fk_elementdet";
2497
2498
                dol_syslog(get_class($this) . "::valid select details", LOG_DEBUG);
2499
                $resql = $this->db->query($sql);
2500
                if ($resql) {
2501
                    $cpt = $this->db->num_rows($resql);
2502
                    for ($i = 0; $i < $cpt; $i++) {
2503
                        $obj = $this->db->fetch_object($resql);
2504
                        if (empty($obj->edbrowid)) {
2505
                            $qty = $obj->qty;
2506
                        } else {
2507
                            $qty = $obj->edbqty;
2508
                        }
2509
                        if ($qty <= 0) {
2510
                            continue;
2511
                        }
2512
                        dol_syslog(get_class($this) . "::reopen expedition movement index " . $i . " ed.rowid=" . $obj->rowid . " edb.rowid=" . $obj->edbrowid);
2513
2514
                        //var_dump($this->lines[$i]);
2515
                        $mouvS = new MouvementStock($this->db);
2516
                        $mouvS->origin = &$this;
2517
                        $mouvS->setOrigin($this->element, $this->id);
2518
2519
                        if (empty($obj->edbrowid)) {
2520
                            // line without batch detail
2521
2522
                            // 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
2523
                            $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $this->ref));
2524
                            if ($result < 0) {
2525
                                $this->error = $mouvS->error;
2526
                                $this->errors = $mouvS->errors;
2527
                                $error++;
2528
                                break;
2529
                            }
2530
                        } else {
2531
                            // line with batch detail
2532
2533
                            // 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
2534
                            $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);
2535
                            if ($result < 0) {
2536
                                $this->error = $mouvS->error;
2537
                                $this->errors = $mouvS->errors;
2538
                                $error++;
2539
                                break;
2540
                            }
2541
                        }
2542
                    }
2543
                } else {
2544
                    $this->error = $this->db->lasterror();
2545
                    $error++;
2546
                }
2547
            }
2548
2549
            if (!$error) {
2550
                // Call trigger
2551
                $result = $this->call_trigger('SHIPPING_REOPEN', $user);
2552
                if ($result < 0) {
2553
                    $error++;
2554
                }
2555
            }
2556
        } else {
2557
            $error++;
2558
            $this->errors[] = $this->db->lasterror();
2559
        }
2560
2561
        if (!$error) {
2562
            $this->db->commit();
2563
            return 1;
2564
        } else {
2565
            $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

2565
            /** @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...
2566
            $this->status = self::STATUS_CLOSED;
2567
            $this->billed = $oldbilled;
2568
            $this->db->rollback();
2569
            return -1;
2570
        }
2571
    }
2572
2573
    /**
2574
     *  Create a document onto disk according to template module.
2575
     *
2576
     * @param string $modele Force the model to using ('' to not force)
2577
     * @param Translate $outputlangs object lang to use for translations
2578
     * @param int $hidedetails Hide details of lines
2579
     * @param int $hidedesc Hide description
2580
     * @param int $hideref Hide ref
2581
     * @param null|array $moreparams Array to provide more information
2582
     * @return     int                         0 if KO, 1 if OK
2583
     */
2584
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2585
    {
2586
        $outputlangs->load("products");
2587
2588
        if (!dol_strlen($modele)) {
2589
            $modele = 'rouget';
2590
2591
            if (!empty($this->model_pdf)) {
2592
                $modele = $this->model_pdf;
2593
            } elseif (getDolGlobalString('EXPEDITION_ADDON_PDF')) {
2594
                $modele = getDolGlobalString('EXPEDITION_ADDON_PDF');
2595
            }
2596
        }
2597
2598
        $modelpath = "core/modules/expedition/doc/";
2599
2600
        $this->fetch_origin();
2601
2602
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2603
    }
2604
}
2605