Passed
Push — EXTRACT_CLASSES ( 0382f2...c25e41 )
by Rafael
52:18
created

Commande::create()   F

Complexity

Conditions 74

Size

Total Lines 291
Code Lines 200

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 74
eloc 200
nop 2
dl 0
loc 291
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* Copyright (C) 2003-2006  Rodolphe Quiedeville    <[email protected]>
4
 * Copyright (C) 2004-2012  Laurent Destailleur     <[email protected]>
5
 * Copyright (C) 2005-2014  Regis Houssin           <[email protected]>
6
 * Copyright (C) 2006       Andre Cianfarani        <[email protected]>
7
 * Copyright (C) 2010-2020  Juanjo Menent           <[email protected]>
8
 * Copyright (C) 2011       Jean Heimburger         <[email protected]>
9
 * Copyright (C) 2012-2014  Christophe Battarel     <[email protected]>
10
 * Copyright (C) 2012       Cedric Salvador         <[email protected]>
11
 * Copyright (C) 2013       Florian Henry		    <[email protected]>
12
 * Copyright (C) 2014-2015  Marcos García           <[email protected]>
13
 * Copyright (C) 2018       Nicolas ZABOURI	        <[email protected]>
14
 * Copyright (C) 2016-2022  Ferran Marcet           <[email protected]>
15
 * Copyright (C) 2021-2024  Frédéric France         <[email protected]>
16
 * Copyright (C) 2022       Gauthier VERDOL         <[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\Commande\Classes;
35
36
/**
37
 *  \file       htdocs/commande/class/commande.class.php
38
 *  \ingroup    order
39
 *  \brief      class for orders
40
 */
41
42
use Dolibarr\Code\MultiCurrency\Classes\MultiCurrency;
43
44
include_once constant('DOL_DOCUMENT_ROOT') . '/core/class/commonorder.class.php';
45
require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/product.class.php';
46
require_once constant('DOL_DOCUMENT_ROOT') . '/margin/lib/margins.lib.php';
47
require_once constant('DOL_DOCUMENT_ROOT') . '/societe/class/societe.class.php';
48
49
/**
50
 *  Class to manage customers orders
51
 */
52
class Commande extends CommonOrder
53
{
54
    /**
55
     * @var string ID to identify managed object
56
     */
57
    public $element = 'commande';
58
59
    /**
60
     * @var string Name of table without prefix where object is stored
61
     */
62
    public $table_element = 'commande';
63
64
    /**
65
     * @var string Name of subtable line
66
     */
67
    public $table_element_line = 'commandedet';
68
69
    /**
70
     * @var string Name of class line
71
     */
72
    public $class_element_line = 'OrderLine';
73
74
    /**
75
     * @var string Field name with ID of parent key if this field has a parent
76
     */
77
    public $fk_element = 'fk_commande';
78
79
    /**
80
     * @var string String with name of icon for commande class. Here is object_order.png
81
     */
82
    public $picto = 'order';
83
84
    /**
85
     * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user
86
     * @var integer
87
     */
88
    public $restrictiononfksoc = 1;
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    protected $table_ref_field = 'ref';
94
95
    /**
96
     * @var int Thirdparty ID
97
     */
98
    public $socid;
99
100
    /**
101
     * @var string Thirdparty ref of order
102
     */
103
    public $ref_client;
104
105
    /**
106
     * @var string Thirdparty ref of order
107
     */
108
    public $ref_customer;
109
110
    /**
111
     * @var int Contact ID
112
     */
113
    public $contactid;
114
115
    /**
116
     * Status of the order
117
     * @var int
118
     */
119
    public $statut;
120
121
    /**
122
     * @var int Status Billed or not
123
     */
124
    public $billed;
125
126
    /**
127
     * @var int Deadline for payment
128
     */
129
    public $date_lim_reglement;
130
    /**
131
     * @var string Condition payment code
132
     */
133
    public $cond_reglement_code;
134
135
    /**
136
     * @var string Condition payment label
137
     */
138
    public $cond_reglement_doc;
139
140
    /**
141
     * @var float   Deposit percent for payment terms.
142
     *              Populated by $CommonObject->setPaymentTerms().
143
     * @see setPaymentTerms()
144
     */
145
    public $deposit_percent;
146
147
    /**
148
     * @var int bank account ID
149
     */
150
    public $fk_account;
151
152
    /**
153
     * @var string It holds the label of the payment mode. Use it in case translation cannot be found.
154
     */
155
    public $mode_reglement;
156
157
    /**
158
     * @var int Payment mode id
159
     */
160
    public $mode_reglement_id;
161
162
    /**
163
     * @var string Payment mode code
164
     */
165
    public $mode_reglement_code;
166
167
    /**
168
     * Availability delivery time id
169
     * @var int
170
     */
171
    public $availability_id;
172
173
    /**
174
     * Availability delivery time code
175
     * @var string
176
     */
177
    public $availability_code;
178
179
    /**
180
     * Label of availability delivery time. Use it in case translation cannot be found.
181
     * @var string
182
     */
183
    public $availability;
184
185
    /**
186
     * @var int Source demand reason Id
187
     */
188
    public $demand_reason_id;
189
190
    /**
191
     * @var string Source reason code. Why we receive order (after a phone campaign, ...)
192
     */
193
    public $demand_reason_code;
194
195
    /**
196
     * @var int Date of order
197
     */
198
    public $date;
199
200
    /**
201
     * @var int Date of order
202
     * @deprecated
203
     * @see $date
204
     */
205
    public $date_commande;
206
207
    /**
208
     * @var int Date expected of shipment (date of start of shipment, not the reception that occurs some days after)
209
     */
210
    public $delivery_date;
211
212
    /**
213
     * @var int ID
214
     */
215
    public $fk_remise_except;
216
217
    /**
218
     * @deprecated
219
     */
220
    public $remise_percent;
221
222
    public $source; // Order mode. How we received order (by phone, by email, ...)
223
224
    /**
225
     * @var int Warehouse Id
226
     */
227
    public $warehouse_id;
228
229
    public $extraparams = array();
230
231
    public $linked_objects = array();
232
233
    /**
234
     * @var int User author ID
235
     */
236
    public $user_author_id;
237
238
    /**
239
     * @var OrderLine one line of an order
240
     */
241
    public $line;
242
243
    /**
244
     * @var OrderLine[]
245
     */
246
    public $lines = array();
247
248
249
    //! key of module source when order generated from a dedicated module ('cashdesk', 'takepos', ...)
250
    public $module_source;
251
    //! key of pos source ('0', '1', ...)
252
    public $pos_source;
253
254
    /**
255
     * @var array   Array with line of all shipments
256
     */
257
    public $expeditions;
258
259
    /**
260
     * @var string payment url
261
     */
262
    public $online_payment_url;
263
264
265
266
    /**
267
     *  'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
268
     *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
269
     *  'label' the translation key.
270
     *  'enabled' is a condition when the field must be managed.
271
     *  'position' is the sort order of field.
272
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
273
     *  'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
274
     *  'noteditable' says if field is not editable (1 or 0)
275
     *  'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
276
     *  'index' if we want an index in database.
277
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
278
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
279
     *  'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
280
     *  'css' is the CSS style to use on field. For example: 'maxwidth200'
281
     *  'help' is a string visible as a tooltip on field
282
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
283
     *  'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
284
     *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
285
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
286
     *
287
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
288
     */
289
290
    // BEGIN MODULEBUILDER PROPERTIES
291
    /**
292
     * @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...
293
     */
294
    public $fields = array(
295
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
296
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 20, 'index' => 1),
297
        'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 25),
298
        'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 26),
299
        'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 28),
300
        'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 20),
301
        'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 25),
302
        'date_commande' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => 1, 'position' => 60, 'csslist' => 'nowraponall'),
303
        'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 62, 'csslist' => 'nowraponall'),
304
        'date_cloture' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => -1, 'position' => 65, 'csslist' => 'nowraponall'),
305
        'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 85),
306
        'fk_user_cloture' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserClosing', 'enabled' => 1, 'visible' => -1, 'position' => 90),
307
        'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => -1, 'position' => 95),
308
        'total_tva' => array('type' => 'double(24,8)', 'label' => 'VAT', 'enabled' => 1, 'visible' => -1, 'position' => 125, 'isameasure' => 1),
309
        'localtax1' => array('type' => 'double(24,8)', 'label' => 'LocalTax1', 'enabled' => 1, 'visible' => -1, 'position' => 130, 'isameasure' => 1),
310
        'localtax2' => array('type' => 'double(24,8)', 'label' => 'LocalTax2', 'enabled' => 1, 'visible' => -1, 'position' => 135, 'isameasure' => 1),
311
        'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 140, 'isameasure' => 1),
312
        'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 145, 'isameasure' => 1),
313
        'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 150),
314
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 155),
315
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 160),
316
        'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 170),
317
        'fk_currency' => array('type' => 'varchar(3)', 'label' => 'MulticurrencyID', 'enabled' => 1, 'visible' => -1, 'position' => 175),
318
        'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 180),
319
        'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 181),
320
        'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 185),
321
        'date_livraison' => array('type' => 'date', 'label' => 'DateDeliveryPlanned', 'enabled' => 1, 'visible' => -1, 'position' => 190, 'csslist' => 'nowraponall'),
322
        'fk_shipping_method' => array('type' => 'integer', 'label' => 'ShippingMethod', 'enabled' => 1, 'visible' => -1, 'position' => 195),
323
        'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'Fk warehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 200),
324
        'fk_availability' => array('type' => 'integer', 'label' => 'Availability', 'enabled' => 1, 'visible' => -1, 'position' => 205),
325
        'fk_input_reason' => array('type' => 'integer', 'label' => 'InputReason', 'enabled' => 1, 'visible' => -1, 'position' => 210),
326
        //'fk_delivery_address' =>array('type'=>'integer', 'label'=>'DeliveryAddress', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
327
        'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 225),
328
        'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => '$conf->incoterm->enabled', 'visible' => -1, 'position' => 230),
329
        'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => '$conf->incoterm->enabled', 'visible' => -1, 'position' => 235),
330
        'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
331
        'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 245),
332
        'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 250, 'isameasure' => 1),
333
        'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 255, 'isameasure' => 1),
334
        'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 260, 'isameasure' => 1),
335
        'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 265, 'isameasure' => 1),
336
        'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 270),
337
        'module_source' => array('type' => 'varchar(32)', 'label' => 'POSModule', 'enabled' => 1, 'visible' => -1, 'position' => 275),
338
        'pos_source' => array('type' => 'varchar(32)', 'label' => 'POSTerminal', 'enabled' => 1, 'visible' => -1, 'position' => 280),
339
        'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 300),
340
        'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 302),
341
        'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'position' => 304, 'csslist' => 'nowraponall'),
342
        'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 306),
343
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 400),
344
        'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'position' => 500),
345
    );
346
    // END MODULEBUILDER PROPERTIES
347
348
    /**
349
     * ERR Not enough stock
350
     */
351
    const STOCK_NOT_ENOUGH_FOR_ORDER = -3;
352
353
    /**
354
     * Canceled status
355
     */
356
    const STATUS_CANCELED = -1;
357
    /**
358
     * Draft status
359
     */
360
    const STATUS_DRAFT = 0;
361
    /**
362
     * Validated status
363
     */
364
    const STATUS_VALIDATED = 1;
365
    /**
366
     * Shipment on process
367
     */
368
    const STATUS_SHIPMENTONPROCESS = 2;     // We set this status when a shipment is validated
369
    const STATUS_ACCEPTED = 2;              // For backward compatibility. Use key STATUS_SHIPMENTONPROCESS instead.
370
371
    /**
372
     * Closed (Sent, billed or not)
373
     */
374
    const STATUS_CLOSED = 3;
375
376
377
    /**
378
     *  Constructor
379
     *
380
     *  @param      DoliDB      $db      Database handler
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Commande\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
381
     */
382
    public function __construct($db)
383
    {
384
        $this->db = $db;
385
386
        $this->ismultientitymanaged = 1;
387
        $this->isextrafieldmanaged = 1;
388
    }
389
390
    /**
391
     *  Returns the reference to the following non used Order depending on the active numbering module
392
     *  defined into COMMANDE_ADDON
393
     *
394
     *  @param  Societe     $soc    Object thirdparty
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Commande\Classes\Societe was not found. Did you mean Societe? If so, make sure to prefix the type with \.
Loading history...
395
     *  @return string              Order free reference
396
     */
397
    public function getNextNumRef($soc)
398
    {
399
        global $langs, $conf;
400
        $langs->load("order");
401
402
        if (getDolGlobalString('COMMANDE_ADDON')) {
403
            $mybool = false;
404
405
            $file = getDolGlobalString('COMMANDE_ADDON') . ".php";
406
            $classname = getDolGlobalString('COMMANDE_ADDON');
407
408
            // Include file with class
409
            $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
410
            foreach ($dirmodels as $reldir) {
411
                $dir = dol_buildpath($reldir . "core/modules/commande/");
412
413
                // Load file with numbering class (if found)
414
                $mybool = ((bool) @include_once $dir . $file) || $mybool;
415
            }
416
417
            if ($mybool === false) {
418
                dol_print_error(null, "Failed to include file " . $file);
419
                return '';
420
            }
421
422
            $obj = new $classname();
423
            $numref = $obj->getNextValue($soc, $this);
424
425
            if ($numref != "") {
426
                return $numref;
427
            } else {
428
                $this->error = $obj->error;
429
                //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
430
                return "";
431
            }
432
        } else {
433
            print $langs->trans("Error") . " " . $langs->trans("Error_COMMANDE_ADDON_NotDefined");
434
            return "";
435
        }
436
    }
437
438
439
    /**
440
     *  Validate order
441
     *
442
     *  @param      User    $user           User making status change
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Commande\Classes\User was not found. Did you mean User? If so, make sure to prefix the type with \.
Loading history...
443
     *  @param      int     $idwarehouse    Id of warehouse to use for stock decrease
444
     *  @param      int     $notrigger      1=Does not execute triggers, 0= execute triggers
445
     *  @return     int                     Return integer <0 if KO, 0=Nothing done, >0 if OK
446
     */
447
    public function valid($user, $idwarehouse = 0, $notrigger = 0)
448
    {
449
        global $conf, $langs;
450
451
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
452
453
        $error = 0;
454
455
        // Protection
456
        if ($this->statut == self::STATUS_VALIDATED) {
457
            dol_syslog(get_class($this) . "::valid action abandoned: already validated", LOG_WARNING);
458
            return 0;
459
        }
460
461
        if (
462
            !((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
463
            || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'validate')))
464
        ) {
465
            $this->error = 'NotEnoughPermissions';
466
            dol_syslog(get_class($this) . "::valid " . $this->error, LOG_ERR);
467
            return -1;
468
        }
469
470
        $now = dol_now();
471
472
        $this->db->begin();
473
474
        // Definition du nom de module de numerotation de commande
475
        $soc = new Societe($this->db);
476
        $soc->fetch($this->socid);
477
478
        // Class of company linked to order
479
        $result = $soc->setAsCustomer();
480
481
        // Define new ref
482
        if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
483
            $num = $this->getNextNumRef($soc);
484
        } else {
485
            $num = $this->ref;
486
        }
487
        $this->newref = dol_sanitizeFileName($num);
488
489
        // Validate
490
        $sql = "UPDATE " . MAIN_DB_PREFIX . "commande";
491
        $sql .= " SET ref = '" . $this->db->escape($num) . "',";
492
        $sql .= " fk_statut = " . self::STATUS_VALIDATED . ",";
493
        $sql .= " date_valid='" . $this->db->idate($now) . "',";
494
        $sql .= " fk_user_valid = " . ($user->id > 0 ? (int) $user->id : "null") . ",";
495
        $sql .= " fk_user_modif = " . ((int) $user->id);
496
        $sql .= " WHERE rowid = " . ((int) $this->id);
497
498
        dol_syslog(get_class($this) . "::valid", LOG_DEBUG);
499
        $resql = $this->db->query($sql);
500
        if (!$resql) {
501
            dol_print_error($this->db);
502
            $this->error = $this->db->lasterror();
503
            $error++;
504
        }
505
506
        if (!$error) {
507
            // If stock is incremented on validate order, we must increment it
508
            if ($result >= 0 && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
509
                require_once constant('DOL_DOCUMENT_ROOT') . '/product/stock/class/mouvementstock.class.php';
510
                $langs->load("agenda");
511
512
                // Loop on each line
513
                $cpt = count($this->lines);
514
                for ($i = 0; $i < $cpt; $i++) {
515
                    if ($this->lines[$i]->fk_product > 0) {
516
                        $mouvP = new MouvementStock($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Commande\Classes\MouvementStock was not found. Did you mean MouvementStock? If so, make sure to prefix the type with \.
Loading history...
517
                        $mouvP->origin = &$this;
518
                        $mouvP->setOrigin($this->element, $this->id);
519
                        // We decrement stock of product (and sub-products)
520
                        $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("OrderValidatedInDolibarr", $num));
521
                        if ($result < 0) {
522
                            $error++;
523
                            $this->error = $mouvP->error;
524
                        }
525
                    }
526
                    if ($error) {
527
                        break;
528
                    }
529
                }
530
            }
531
        }
532
533
        if (!$error && !$notrigger) {
534
            // Call trigger
535
            $result = $this->call_trigger('ORDER_VALIDATE', $user);
536
            if ($result < 0) {
537
                $error++;
538
            }
539
            // End call triggers
540
        }
541
542
        if (!$error) {
543
            $this->oldref = $this->ref;
544
545
            // Rename directory if dir was a temporary ref
546
            if (preg_match('/^[\(]?PROV/i', $this->ref)) {
547
                // Now we rename also files into index
548
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filename = CONCAT('" . $this->db->escape($this->newref) . "', SUBSTR(filename, " . (strlen($this->ref) + 1) . ")), filepath = 'commande/" . $this->db->escape($this->newref) . "'";
549
                $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'commande/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
550
                $resql = $this->db->query($sql);
551
                if (!$resql) {
552
                    $error++;
553
                    $this->error = $this->db->lasterror();
554
                }
555
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'commande/" . $this->db->escape($this->newref) . "'";
556
                $sql .= " WHERE filepath = 'commande/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
557
                $resql = $this->db->query($sql);
558
                if (!$resql) {
559
                    $error++;
560
                    $this->error = $this->db->lasterror();
561
                }
562
563
                // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
564
                $oldref = dol_sanitizeFileName($this->ref);
565
                $newref = dol_sanitizeFileName($num);
566
                $dirsource = $conf->commande->multidir_output[$this->entity] . '/' . $oldref;
567
                $dirdest = $conf->commande->multidir_output[$this->entity] . '/' . $newref;
568
                if (!$error && file_exists($dirsource)) {
569
                    dol_syslog(get_class($this) . "::valid rename dir " . $dirsource . " into " . $dirdest);
570
571
                    if (@rename($dirsource, $dirdest)) {
572
                        dol_syslog("Rename ok");
573
                        // Rename docs starting with $oldref with $newref
574
                        $listoffiles = dol_dir_list($conf->commande->multidir_output[$this->entity] . '/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
575
                        foreach ($listoffiles as $fileentry) {
576
                            $dirsource = $fileentry['name'];
577
                            $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
578
                            $dirsource = $fileentry['path'] . '/' . $dirsource;
579
                            $dirdest = $fileentry['path'] . '/' . $dirdest;
580
                            @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

580
                            /** @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...
581
                        }
582
                    }
583
                }
584
            }
585
        }
586
587
        // Set new ref and current status
588
        if (!$error) {
589
            $this->ref = $num;
590
            $this->statut = self::STATUS_VALIDATED; // deprecated
591
            $this->status = self::STATUS_VALIDATED;
592
        }
593
594
        if (!$error) {
595
            $this->db->commit();
596
            return 1;
597
        } else {
598
            $this->db->rollback();
599
            return -1;
600
        }
601
    }
602
603
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
604
    /**
605
     *  Set draft status
606
     *
607
     *  @param  User    $user           Object user that modify
608
     *  @param  int     $idwarehouse    Warehouse ID to use for stock change (Used only if option STOCK_CALCULATE_ON_VALIDATE_ORDER is on)
609
     *  @return int                     Return integer <0 if KO, >0 if OK
610
     */
611
    public function setDraft($user, $idwarehouse = -1)
612
    {
613
		//phpcs:enable
614
        global $conf, $langs;
615
616
        $error = 0;
617
618
        // Protection
619
        if ($this->statut <= self::STATUS_DRAFT) {
620
            return 0;
621
        }
622
623
        if (
624
            !((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
625
            || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'validate')))
626
        ) {
627
            $this->error = 'Permission denied';
628
            return -1;
629
        }
630
631
        dol_syslog(__METHOD__, LOG_DEBUG);
632
633
        $this->db->begin();
634
635
        $sql = "UPDATE " . MAIN_DB_PREFIX . "commande";
636
        $sql .= " SET fk_statut = " . self::STATUS_DRAFT . ",";
637
        $sql .= " fk_user_modif = " . ((int) $user->id);
638
        $sql .= " WHERE rowid = " . ((int) $this->id);
639
640
        if ($this->db->query($sql)) {
641
            if (!$error) {
642
                $this->oldcopy = clone $this;
643
            }
644
645
            // If stock is decremented on validate order, we must reincrement it
646
            if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
647
                $result = 0;
648
649
                require_once constant('DOL_DOCUMENT_ROOT') . '/product/stock/class/mouvementstock.class.php';
650
                $langs->load("agenda");
651
652
                $num = count($this->lines);
653
                for ($i = 0; $i < $num; $i++) {
654
                    if ($this->lines[$i]->fk_product > 0) {
655
                        $mouvP = new MouvementStock($this->db);
656
                        $mouvP->origin = &$this;
657
                        $mouvP->setOrigin($this->element, $this->id);
658
                        // We increment stock of product (and sub-products)
659
                        $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("OrderBackToDraftInDolibarr", $this->ref));
660
                        if ($result < 0) {
661
                            $error++;
662
                            $this->error = $mouvP->error;
663
                            break;
664
                        }
665
                    }
666
                }
667
            }
668
669
            if (!$error) {
670
                // Call trigger
671
                $result = $this->call_trigger('ORDER_UNVALIDATE', $user);
672
                if ($result < 0) {
673
                    $error++;
674
                }
675
            }
676
677
            if (!$error) {
678
                $this->statut = self::STATUS_DRAFT;
679
                $this->db->commit();
680
                return 1;
681
            } else {
682
                $this->db->rollback();
683
                return -1;
684
            }
685
        } else {
686
            $this->error = $this->db->error();
687
            $this->db->rollback();
688
            return -1;
689
        }
690
    }
691
692
693
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
694
    /**
695
     *  Tag the order as validated (opened)
696
     *  Function used when order is reopend after being closed.
697
     *
698
     *  @param      User    $user       Object user that change status
699
     *  @return     int                 Return integer <0 if KO, 0 if nothing is done, >0 if OK
700
     */
701
    public function set_reopen($user)
702
    {
703
		// phpcs:enable
704
        $error = 0;
705
706
        if ($this->statut != self::STATUS_CANCELED && $this->statut != self::STATUS_CLOSED) {
707
            dol_syslog(get_class($this) . "::set_reopen order has not status closed", LOG_WARNING);
708
            return 0;
709
        }
710
711
        $this->db->begin();
712
713
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande';
714
        $sql .= ' SET fk_statut=' . self::STATUS_VALIDATED . ', facture=0,';
715
        $sql .= " fk_user_modif = " . ((int) $user->id);
716
        $sql .= " WHERE rowid = " . ((int) $this->id);
717
718
        dol_syslog(get_class($this) . "::set_reopen", LOG_DEBUG);
719
        $resql = $this->db->query($sql);
720
        if ($resql) {
721
            // Call trigger
722
            $result = $this->call_trigger('ORDER_REOPEN', $user);
723
            if ($result < 0) {
724
                $error++;
725
            }
726
            // End call triggers
727
        } else {
728
            $error++;
729
            $this->error = $this->db->lasterror();
730
            dol_print_error($this->db);
731
        }
732
733
        if (!$error) {
734
            $this->statut = self::STATUS_VALIDATED;
735
            $this->billed = 0;
736
737
            $this->db->commit();
738
            return 1;
739
        } else {
740
            foreach ($this->errors as $errmsg) {
741
                dol_syslog(get_class($this) . "::set_reopen " . $errmsg, LOG_ERR);
742
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
743
            }
744
            $this->db->rollback();
745
            return -1 * $error;
746
        }
747
    }
748
749
    /**
750
     *  Close order
751
     *
752
     *  @param      User    $user       Object user that close
753
     *  @param      int     $notrigger  1=Does not execute triggers, 0=Execute triggers
754
     *  @return     int                 Return integer <0 if KO, >0 if OK
755
     */
756
    public function cloture($user, $notrigger = 0)
757
    {
758
        global $conf;
759
760
        $error = 0;
761
762
        $usercanclose = ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
763
            || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'close')));
764
765
        if ($usercanclose) {
766
            if ($this->statut == self::STATUS_CLOSED) {
767
                return 0;
768
            }
769
            $this->db->begin();
770
771
            $now = dol_now();
772
773
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
774
            $sql .= ' SET fk_statut = ' . self::STATUS_CLOSED . ',';
775
            $sql .= ' fk_user_cloture = ' . ((int) $user->id) . ',';
776
            $sql .= " date_cloture = '" . $this->db->idate($now) . "',";
777
            $sql .= " fk_user_modif = " . ((int) $user->id);
778
            $sql .= " WHERE rowid = " . ((int) $this->id) . ' AND fk_statut > ' . self::STATUS_DRAFT;
779
780
            if ($this->db->query($sql)) {
781
                if (!$notrigger) {
782
                    // Call trigger
783
                    $result = $this->call_trigger('ORDER_CLOSE', $user);
784
                    if ($result < 0) {
785
                        $error++;
786
                    }
787
                    // End call triggers
788
                }
789
790
                if (!$error) {
791
                    $this->statut = self::STATUS_CLOSED;
792
793
                    $this->db->commit();
794
                    return 1;
795
                } else {
796
                    $this->db->rollback();
797
                    return -1;
798
                }
799
            } else {
800
                $this->error = $this->db->lasterror();
801
802
                $this->db->rollback();
803
                return -1;
804
            }
805
        }
806
        return 0;
807
    }
808
809
    /**
810
     *  Cancel an order
811
     *  If stock is decremented on order validation, we must reincrement it
812
     *
813
     *  @param  int     $idwarehouse    Id warehouse to use for stock change.
814
     *  @return int                     Return integer <0 if KO, >0 if OK
815
     */
816
    public function cancel($idwarehouse = -1)
817
    {
818
        global $conf, $user, $langs;
819
820
        $error = 0;
821
822
        $this->db->begin();
823
824
        $sql = "UPDATE " . MAIN_DB_PREFIX . "commande";
825
        $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
826
        $sql .= " fk_user_modif = " . ((int) $user->id);
827
        $sql .= " WHERE rowid = " . ((int) $this->id);
828
        $sql .= " AND fk_statut = " . self::STATUS_VALIDATED;
829
830
        dol_syslog(get_class($this) . "::cancel", LOG_DEBUG);
831
        if ($this->db->query($sql)) {
832
            // If stock is decremented on validate order, we must reincrement it
833
            if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
834
                require_once constant('DOL_DOCUMENT_ROOT') . '/product/stock/class/mouvementstock.class.php';
835
                $langs->load("agenda");
836
837
                $num = count($this->lines);
838
                for ($i = 0; $i < $num; $i++) {
839
                    if ($this->lines[$i]->fk_product > 0) {
840
                        $mouvP = new MouvementStock($this->db);
841
                        $mouvP->setOrigin($this->element, $this->id);
842
                        // We increment stock of product (and sub-products)
843
                        $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("OrderCanceledInDolibarr", $this->ref)); // price is 0, we don't want WAP to be changed
844
                        if ($result < 0) {
845
                            $error++;
846
                            $this->error = $mouvP->error;
847
                            break;
848
                        }
849
                    }
850
                }
851
            }
852
853
            if (!$error) {
854
                // Call trigger
855
                $result = $this->call_trigger('ORDER_CANCEL', $user);
856
                if ($result < 0) {
857
                    $error++;
858
                }
859
                // End call triggers
860
            }
861
862
            if (!$error) {
863
                $this->statut = self::STATUS_CANCELED;
864
                $this->db->commit();
865
                return 1;
866
            } else {
867
                foreach ($this->errors as $errmsg) {
868
                    dol_syslog(get_class($this) . "::cancel " . $errmsg, LOG_ERR);
869
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
870
                }
871
                $this->db->rollback();
872
                return -1 * $error;
873
            }
874
        } else {
875
            $this->error = $this->db->error();
876
            $this->db->rollback();
877
            return -1;
878
        }
879
    }
880
881
    /**
882
     *  Create order
883
     *  Note that this->ref can be set or empty. If empty, we will use "(PROV)"
884
     *
885
     *  @param      User    $user       Object user that make creation
886
     *  @param      int     $notrigger  Disable all triggers
887
     *  @return     int                 Return integer <0 if KO, >0 if OK
888
     */
889
    public function create($user, $notrigger = 0)
890
    {
891
        global $conf, $langs, $mysoc;
892
        $error = 0;
893
894
        // Clean parameters
895
896
        // Set tmp vars
897
        $date = ($this->date_commande ? $this->date_commande : $this->date);
898
        $delivery_date = $this->delivery_date;
899
900
        // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
901
        if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
902
            list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
903
        } else {
904
            $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
905
        }
906
        if (empty($this->fk_multicurrency)) {
907
            $this->multicurrency_code = $conf->currency;
908
            $this->fk_multicurrency = 0;
909
            $this->multicurrency_tx = 1;
910
        }
911
912
        dol_syslog(get_class($this) . "::create user=" . $user->id);
913
914
        // Check parameters
915
        if (!empty($this->ref)) {   // We check that ref is not already used
916
            $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
917
            if ($result > 0) {
918
                $this->error = 'ErrorRefAlreadyExists';
919
                dol_syslog(get_class($this) . "::create " . $this->error, LOG_WARNING);
920
                $this->db->rollback();
921
                return -1;
922
            }
923
        }
924
925
        $soc = new Societe($this->db);
926
        $result = $soc->fetch($this->socid);
927
        if ($result < 0) {
928
            $this->error = "Failed to fetch company";
929
            dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
930
            return -2;
931
        }
932
        if (getDolGlobalString('ORDER_REQUIRE_SOURCE') && $this->source < 0) {
933
            $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Source"));
934
            dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
935
            return -1;
936
        }
937
938
        $now = dol_now();
939
940
        $this->db->begin();
941
942
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "commande (";
943
        $sql .= " ref, fk_soc, date_creation, fk_user_author, fk_projet, date_commande, source, note_private, note_public, ref_ext, ref_client";
944
        $sql .= ", model_pdf, fk_cond_reglement, deposit_percent, fk_mode_reglement, fk_account, fk_availability, fk_input_reason, date_livraison, fk_delivery_address";
945
        $sql .= ", fk_shipping_method";
946
        $sql .= ", fk_warehouse";
947
        $sql .= ", fk_incoterms, location_incoterms";
948
        $sql .= ", entity, module_source, pos_source";
949
        $sql .= ", fk_multicurrency";
950
        $sql .= ", multicurrency_code";
951
        $sql .= ", multicurrency_tx";
952
        $sql .= ")";
953
        $sql .= " VALUES ('(PROV)', " . ((int) $this->socid) . ", '" . $this->db->idate($now) . "', " . ((int) $user->id);
954
        $sql .= ", " . ($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
955
        $sql .= ", '" . $this->db->idate($date) . "'";
956
        $sql .= ", " . ($this->source >= 0 && $this->source != '' ? $this->db->escape($this->source) : 'null');
957
        $sql .= ", '" . $this->db->escape($this->note_private) . "'";
958
        $sql .= ", '" . $this->db->escape($this->note_public) . "'";
959
        $sql .= ", " . ($this->ref_ext ? "'" . $this->db->escape($this->ref_ext) . "'" : "null");
960
        $sql .= ", " . ($this->ref_client ? "'" . $this->db->escape($this->ref_client) . "'" : "null");
961
        $sql .= ", '" . $this->db->escape($this->model_pdf) . "'";
962
        $sql .= ", " . ($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
963
        $sql .= ", " . (!empty($this->deposit_percent) ? "'" . $this->db->escape($this->deposit_percent) . "'" : "null");
964
        $sql .= ", " . ($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
965
        $sql .= ", " . ($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
966
        $sql .= ", " . ($this->availability_id > 0 ? ((int) $this->availability_id) : "null");
967
        $sql .= ", " . ($this->demand_reason_id > 0 ? ((int) $this->demand_reason_id) : "null");
968
        $sql .= ", " . ($delivery_date ? "'" . $this->db->idate($delivery_date) . "'" : "null");
969
        $sql .= ", " . ($this->fk_delivery_address > 0 ? ((int) $this->fk_delivery_address) : 'NULL');
970
        $sql .= ", " . (!empty($this->shipping_method_id) && $this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : 'NULL');
971
        $sql .= ", " . (!empty($this->warehouse_id) && $this->warehouse_id > 0 ? ((int) $this->warehouse_id) : 'NULL');
972
        $sql .= ", " . (int) $this->fk_incoterms;
973
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
974
        $sql .= ", " . setEntity($this);
975
        $sql .= ", " . ($this->module_source ? "'" . $this->db->escape($this->module_source) . "'" : "null");
976
        $sql .= ", " . ($this->pos_source != '' ? "'" . $this->db->escape($this->pos_source) . "'" : "null");
977
        $sql .= ", " . (int) $this->fk_multicurrency;
978
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
979
        $sql .= ", " . (float) $this->multicurrency_tx;
980
        $sql .= ")";
981
982
        dol_syslog(get_class($this) . "::create", LOG_DEBUG);
983
        $resql = $this->db->query($sql);
984
        if ($resql) {
985
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . 'commande');
986
987
            if ($this->id) {
988
                $fk_parent_line = 0;
989
                $num = count($this->lines);
990
991
                /*
992
                 *  Insert products details into db
993
                 */
994
                for ($i = 0; $i < $num; $i++) {
995
                    $line = $this->lines[$i];
996
997
                    // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
998
                    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
999
                    if (!is_object($line)) {
1000
                        $line = (object) $line;
1001
                    }
1002
1003
                    // Reset fk_parent_line for no child products and special product
1004
                    if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1005
                        $fk_parent_line = 0;
1006
                    }
1007
1008
                    // Complete vat rate with code
1009
                    $vatrate = $line->tva_tx;
1010
                    if ($line->vat_src_code && !preg_match('/\(.*\)/', (string) $vatrate)) {
1011
                        $vatrate .= ' (' . $line->vat_src_code . ')';
1012
                    }
1013
1014
                    if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1015
                        $originid = $line->origin_id;
1016
                        $origintype = $line->origin;
1017
                    } else {
1018
                        $originid = $line->id;
1019
                        $origintype = $this->element;
1020
                    }
1021
1022
                    // ref_ext
1023
                    if (empty($line->ref_ext)) {
1024
                        $line->ref_ext = '';
1025
                    }
1026
1027
                    $result = $this->addline(
1028
                        $line->desc,
1029
                        $line->subprice,
1030
                        $line->qty,
1031
                        $vatrate,
1032
                        $line->localtax1_tx,
1033
                        $line->localtax2_tx,
1034
                        $line->fk_product,
1035
                        $line->remise_percent,
1036
                        $line->info_bits,
1037
                        $line->fk_remise_except,
1038
                        'HT',
1039
                        0,
1040
                        $line->date_start,
1041
                        $line->date_end,
1042
                        $line->product_type,
1043
                        $line->rang,
1044
                        $line->special_code,
1045
                        $fk_parent_line,
1046
                        $line->fk_fournprice,
1047
                        $line->pa_ht,
1048
                        $line->label,
1049
                        $line->array_options,
1050
                        $line->fk_unit,
1051
                        $origintype,
1052
                        $originid,
1053
                        0,
1054
                        $line->ref_ext,
1055
                        1
1056
                    );
1057
                    if ($result < 0) {
1058
                        if ($result != self::STOCK_NOT_ENOUGH_FOR_ORDER) {
1059
                            $this->error = $this->db->lasterror();
1060
                            $this->errors[] = $this->error;
1061
                            dol_print_error($this->db);
1062
                        }
1063
                        $this->db->rollback();
1064
                        return -1;
1065
                    }
1066
                    // Defined the new fk_parent_line
1067
                    if ($result > 0 && $line->product_type == 9) {
1068
                        $fk_parent_line = $result;
1069
                    }
1070
                }
1071
1072
                $result = $this->update_price(1, 'auto', 0, $mysoc); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
1073
1074
                // update ref
1075
                $initialref = '(PROV' . $this->id . ')';
1076
                if (!empty($this->ref)) {
1077
                    $initialref = $this->ref;
1078
                }
1079
1080
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "commande SET ref='" . $this->db->escape($initialref) . "' WHERE rowid=" . ((int) $this->id);
1081
                if ($this->db->query($sql)) {
1082
                    $this->ref = $initialref;
1083
1084
                    if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {  // To use new linkedObjectsIds instead of old linked_objects
1085
                        $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1086
                    }
1087
1088
                    // Add object linked
1089
                    if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1090
                        foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1091
                            if (is_array($tmp_origin_id)) {       // New behaviour, if linked_object can have several links per type, so is something like array('contract'=>array(id1, id2, ...))
1092
                                foreach ($tmp_origin_id as $origin_id) {
1093
                                    $ret = $this->add_object_linked($origin, $origin_id);
1094
                                    if (!$ret) {
1095
                                        $this->error = $this->db->lasterror();
1096
                                        $error++;
1097
                                    }
1098
                                }
1099
                            } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1100
                                $origin_id = $tmp_origin_id;
1101
                                $ret = $this->add_object_linked($origin, $origin_id);
1102
                                if (!$ret) {
1103
                                    $this->error = $this->db->lasterror();
1104
                                    $error++;
1105
                                }
1106
                            }
1107
                        }
1108
                    }
1109
1110
                    if (!$error && $this->id && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) {   // Get contact from origin object
1111
                        $originforcontact = $this->origin;
1112
                        $originidforcontact = $this->origin_id;
1113
                        if ($originforcontact == 'shipping') {     // shipment and order share the same contacts. If creating from shipment we take data of order
1114
                            require_once constant('DOL_DOCUMENT_ROOT') . '/expedition/class/expedition.class.php';
1115
                            $exp = new Expedition($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Commande\Classes\Expedition was not found. Did you mean Expedition? If so, make sure to prefix the type with \.
Loading history...
1116
                            $exp->fetch($this->origin_id);
1117
                            $exp->fetchObjectLinked();
1118
                            if (count($exp->linkedObjectsIds['commande']) > 0) {
1119
                                foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
1120
                                    $originforcontact = 'commande';
1121
                                    if (is_object($value)) {
1122
                                        $originidforcontact = $value->id;
1123
                                    } else {
1124
                                        $originidforcontact = $value;
1125
                                    }
1126
                                    break; // We take first one
1127
                                }
1128
                            }
1129
                        }
1130
1131
                        $sqlcontact = "SELECT ctc.code, ctc.source, ec.fk_socpeople FROM " . MAIN_DB_PREFIX . "element_contact as ec, " . MAIN_DB_PREFIX . "c_type_contact as ctc";
1132
                        $sqlcontact .= " WHERE element_id = " . ((int) $originidforcontact) . " AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '" . $this->db->escape($originforcontact) . "'";
1133
1134
                        $resqlcontact = $this->db->query($sqlcontact);
1135
                        if ($resqlcontact) {
1136
                            while ($objcontact = $this->db->fetch_object($resqlcontact)) {
1137
                                //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
1138
                                $this->add_contact($objcontact->fk_socpeople, $objcontact->code, $objcontact->source); // May failed because of duplicate key or because code of contact type does not exists for new object
1139
                            }
1140
                        } else {
1141
                            dol_print_error($resqlcontact);
1142
                        }
1143
                    }
1144
1145
                    if (!$error) {
1146
                        $result = $this->insertExtraFields();
1147
                        if ($result < 0) {
1148
                            $error++;
1149
                        }
1150
                    }
1151
1152
                    if (!$error && !$notrigger) {
1153
                        // Call trigger
1154
                        $result = $this->call_trigger('ORDER_CREATE', $user);
1155
                        if ($result < 0) {
1156
                            $error++;
1157
                        }
1158
                        // End call triggers
1159
                    }
1160
1161
                    if (!$error) {
1162
                        $this->db->commit();
1163
                        return $this->id;
1164
                    } else {
1165
                        $this->db->rollback();
1166
                        return -1 * $error;
1167
                    }
1168
                } else {
1169
                    $this->error = $this->db->lasterror();
1170
                    $this->db->rollback();
1171
                    return -1;
1172
                }
1173
            }
1174
1175
            return 0;
1176
        } else {
1177
            $this->error = $this->db->lasterror();
1178
            $this->db->rollback();
1179
            return -1;
1180
        }
1181
    }
1182
1183
1184
    /**
1185
     *  Load an object from its id and create a new one in database
1186
     *
1187
     *  @param      User    $user       User making the clone
1188
     *  @param      int     $socid      Id of thirdparty
1189
     *  @return     int                 New id of clone
1190
     */
1191
    public function createFromClone(User $user, $socid = 0)
1192
    {
1193
        global $conf, $user, $hookmanager;
1194
1195
        $error = 0;
1196
1197
        $this->db->begin();
1198
1199
        // get lines so they will be clone
1200
        foreach ($this->lines as $line) {
1201
            $line->fetch_optionals();
1202
        }
1203
1204
        // Load source object
1205
        $objFrom = clone $this;
1206
1207
        // Change socid if needed
1208
        if (!empty($socid) && $socid != $this->socid) {
1209
            $objsoc = new Societe($this->db);
1210
1211
            if ($objsoc->fetch($socid) > 0) {
1212
                $this->socid = $objsoc->id;
1213
                $this->cond_reglement_id    = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1214
                $this->deposit_percent      = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : 0);
1215
                $this->mode_reglement_id    = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1216
                $this->fk_project = 0;
1217
                $this->fk_delivery_address = 0;
1218
            }
1219
1220
            // TODO Change product price if multi-prices
1221
        }
1222
1223
        $this->id = 0;
1224
        $this->ref = '';
1225
        $this->statut = self::STATUS_DRAFT;
1226
1227
        // Clear fields
1228
        $this->user_author_id     = $user->id;
1229
        $this->user_validation_id = 0;
1230
        $this->date = dol_now();
1231
        $this->date_commande = dol_now();
1232
        $this->date_creation      = '';
1233
        $this->date_validation    = '';
1234
        if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1235
            $this->ref_client = '';
1236
            $this->ref_customer = '';
1237
        }
1238
1239
        // Do not clone ref_ext
1240
        $num = count($this->lines);
1241
        for ($i = 0; $i < $num; $i++) {
1242
            $this->lines[$i]->ref_ext = '';
1243
        }
1244
1245
        // Create clone
1246
        $this->context['createfromclone'] = 'createfromclone';
1247
        $result = $this->create($user);
1248
        if ($result < 0) {
1249
            $error++;
1250
        }
1251
1252
        if (!$error) {
1253
            // copy internal contacts
1254
            if ($this->copy_linked_contact($objFrom, 'internal') < 0) {
1255
                $error++;
1256
            }
1257
        }
1258
1259
        if (!$error) {
1260
            // copy external contacts if same company
1261
            if ($this->socid == $objFrom->socid) {
1262
                if ($this->copy_linked_contact($objFrom, 'external') < 0) {
1263
                    $error++;
1264
                }
1265
            }
1266
        }
1267
1268
        if (!$error) {
1269
            // Hook of thirdparty module
1270
            if (is_object($hookmanager)) {
1271
                $parameters = array('objFrom' => $objFrom);
1272
                $action = '';
1273
                $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1274
                if ($reshook < 0) {
1275
                    $this->setErrorsFromObject($hookmanager);
1276
                    $error++;
1277
                }
1278
            }
1279
        }
1280
1281
        unset($this->context['createfromclone']);
1282
1283
        // End
1284
        if (!$error) {
1285
            $this->db->commit();
1286
            return $this->id;
1287
        } else {
1288
            $this->db->rollback();
1289
            return -1;
1290
        }
1291
    }
1292
1293
1294
    /**
1295
     *  Load an object from a proposal and create a new order into database
1296
     *
1297
     *  @param      Object          $object             Object source
1298
     *  @param      User            $user               User making creation
1299
     *  @return     int                                 Return integer <0 if KO, 0 if nothing done, 1 if OK
1300
     */
1301
    public function createFromProposal($object, User $user)
1302
    {
1303
        global $conf, $hookmanager;
1304
1305
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/extrafields.class.php';
1306
1307
        $error = 0;
1308
1309
        $this->date_commande = dol_now();
1310
        $this->date = dol_now();
1311
        $this->source = 0;
1312
1313
        $num = count($object->lines);
1314
        for ($i = 0; $i < $num; $i++) {
1315
            $line = new OrderLine($this->db);
1316
1317
            $line->libelle           = $object->lines[$i]->libelle;
1318
            $line->label             = $object->lines[$i]->label;
1319
            $line->desc              = $object->lines[$i]->desc;
1320
            $line->price             = $object->lines[$i]->price;
1321
            $line->subprice          = $object->lines[$i]->subprice;
1322
            $line->vat_src_code      = $object->lines[$i]->vat_src_code;
1323
            $line->tva_tx            = $object->lines[$i]->tva_tx;
1324
            $line->localtax1_tx      = $object->lines[$i]->localtax1_tx;
1325
            $line->localtax2_tx      = $object->lines[$i]->localtax2_tx;
1326
            $line->qty               = $object->lines[$i]->qty;
1327
            $line->fk_remise_except  = $object->lines[$i]->fk_remise_except;
1328
            $line->remise_percent    = $object->lines[$i]->remise_percent;
1329
            $line->fk_product        = $object->lines[$i]->fk_product;
1330
            $line->info_bits         = $object->lines[$i]->info_bits;
1331
            $line->product_type      = $object->lines[$i]->product_type;
1332
            $line->rang              = $object->lines[$i]->rang;
1333
            $line->special_code      = $object->lines[$i]->special_code;
1334
            $line->fk_parent_line    = $object->lines[$i]->fk_parent_line;
1335
            $line->fk_unit = $object->lines[$i]->fk_unit;
1336
1337
            $line->date_start       = $object->lines[$i]->date_start;
1338
            $line->date_end         = $object->lines[$i]->date_end;
1339
1340
            $line->fk_fournprice    = $object->lines[$i]->fk_fournprice;
1341
            $marginInfos            = getMarginInfos($object->lines[$i]->subprice, $object->lines[$i]->remise_percent, $object->lines[$i]->tva_tx, $object->lines[$i]->localtax1_tx, $object->lines[$i]->localtax2_tx, $object->lines[$i]->fk_fournprice, $object->lines[$i]->pa_ht);
1342
            $line->pa_ht            = $marginInfos[0];
1343
            $line->marge_tx         = $marginInfos[1];
1344
            $line->marque_tx        = $marginInfos[2];
1345
1346
            $line->origin           = $object->element;
1347
            $line->origin_id        = $object->lines[$i]->id;
1348
1349
            // get extrafields from original line
1350
            $object->lines[$i]->fetch_optionals();
1351
            foreach ($object->lines[$i]->array_options as $options_key => $value) {
1352
                $line->array_options[$options_key] = $value;
1353
            }
1354
1355
            $this->lines[$i] = $line;
1356
        }
1357
1358
        $this->entity               = $object->entity;
1359
        $this->socid                = $object->socid;
1360
        $this->fk_project           = $object->fk_project;
1361
        $this->cond_reglement_id    = $object->cond_reglement_id;
1362
        $this->deposit_percent      = $object->deposit_percent;
1363
        $this->mode_reglement_id    = $object->mode_reglement_id;
1364
        $this->fk_account           = $object->fk_account;
1365
        $this->availability_id      = $object->availability_id;
1366
        $this->demand_reason_id     = $object->demand_reason_id;
1367
        $this->delivery_date        = $object->delivery_date;
1368
        $this->shipping_method_id   = $object->shipping_method_id;
1369
        $this->warehouse_id         = $object->warehouse_id;
1370
        $this->fk_delivery_address  = $object->fk_delivery_address;
1371
        $this->contact_id           = $object->contact_id;
1372
        $this->ref_client           = $object->ref_client;
1373
        $this->ref_customer         = $object->ref_client;
1374
1375
        if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1376
            $this->note_private         = $object->note_private;
1377
            $this->note_public          = $object->note_public;
1378
        }
1379
1380
        $this->origin = $object->element;
1381
        $this->origin_id = $object->id;
1382
1383
        // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1384
        if (!empty($conf->multicurrency->enabled)) {
1385
            if (!empty($object->multicurrency_code)) {
1386
                $this->multicurrency_code = $object->multicurrency_code;
1387
            }
1388
            if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($object->multicurrency_tx)) {
1389
                $this->multicurrency_tx = $object->multicurrency_tx;
1390
            }
1391
1392
            if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1393
                $tmparray = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date_commande);
1394
                $this->fk_multicurrency = $tmparray[0];
1395
                $this->multicurrency_tx = $tmparray[1];
1396
            } else {
1397
                $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1398
            }
1399
            if (empty($this->fk_multicurrency)) {
1400
                $this->multicurrency_code = $conf->currency;
1401
                $this->fk_multicurrency = 0;
1402
                $this->multicurrency_tx = 1;
1403
            }
1404
        }
1405
1406
        // get extrafields from original line
1407
        $object->fetch_optionals();
1408
1409
        $e = new ExtraFields($this->db);
1410
        $element_extrafields = $e->fetch_name_optionals_label($this->table_element);
1411
1412
        foreach ($object->array_options as $options_key => $value) {
1413
            if (array_key_exists(str_replace('options_', '', $options_key), $element_extrafields)) {
1414
                $this->array_options[$options_key] = $value;
1415
            }
1416
        }
1417
        // Possibility to add external linked objects with hooks
1418
        $this->linked_objects[$this->origin] = $this->origin_id;
1419
        if (isset($object->other_linked_objects) && is_array($object->other_linked_objects) && !empty($object->other_linked_objects)) {
1420
            $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1421
        }
1422
1423
        $ret = $this->create($user);
1424
1425
        if ($ret > 0) {
1426
            // Actions hooked (by external module)
1427
            $hookmanager->initHooks(array('orderdao'));
1428
1429
            $parameters = array('objFrom' => $object);
1430
            $action = '';
1431
            $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1432
            if ($reshook < 0) {
1433
                $this->setErrorsFromObject($hookmanager);
1434
                $error++;
1435
            }
1436
1437
            if (!$error) {
1438
                // Validate immediately the order
1439
                if (getDolGlobalString('ORDER_VALID_AFTER_CLOSE_PROPAL')) {
1440
                    $this->fetch($ret);
1441
                    $this->valid($user);
1442
                }
1443
                return $ret;
1444
            } else {
1445
                return -1;
1446
            }
1447
        } else {
1448
            return -1;
1449
        }
1450
    }
1451
1452
1453
    /**
1454
     *  Add an order line into database (linked to product/service or not)
1455
     *
1456
     *  @param      string          $desc               Description of line
1457
     *  @param      float           $pu_ht              Unit price (without tax)
1458
     *  @param      float           $qty                Quantite
1459
     *  @param      float           $txtva              Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
1460
     *  @param      float           $txlocaltax1        Local tax 1 rate (deprecated, use instead txtva with code inside)
1461
     *  @param      float           $txlocaltax2        Local tax 2 rate (deprecated, use instead txtva with code inside)
1462
     *  @param      int             $fk_product         Id of product
1463
     *  @param      float           $remise_percent     Percentage discount of the line
1464
     *  @param      int             $info_bits          Bits of type of lines
1465
     *  @param      int             $fk_remise_except   Id remise
1466
     *  @param      string          $price_base_type    HT or TTC
1467
     *  @param      float           $pu_ttc             Prix unitaire TTC
1468
     *  @param      int|string      $date_start         Start date of the line - Added by Matelli (See http://matelli.fr/showcases/patchs-dolibarr/add-dates-in-order-lines.html)
1469
     *  @param      int|string      $date_end           End date of the line - Added by Matelli (See http://matelli.fr/showcases/patchs-dolibarr/add-dates-in-order-lines.html)
1470
     *  @param      int             $type               Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
1471
     *  @param      int             $rang               Position of line
1472
     *  @param      int             $special_code       Special code (also used by externals modules!)
1473
     *  @param      int             $fk_parent_line     Parent line
1474
     *  @param      int             $fk_fournprice      Id supplier price
1475
     *  @param      int             $pa_ht              Buying price (without tax)
1476
     *  @param      string          $label              Label
1477
     *  @param      array           $array_options      extrafields array. Example array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
1478
     *  @param      int|null        $fk_unit            Code of the unit to use. Null to use the default one
1479
     *  @param      string          $origin             Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
1480
     *  @param      int             $origin_id          Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be Id of origin object (aka line id), else object id
1481
     *  @param      double          $pu_ht_devise       Unit price in currency
1482
     *  @param      string          $ref_ext            line external reference
1483
     *  @param      int             $noupdateafterinsertline    No update after insert of line
1484
     *  @return     int                                 >0 if OK, <0 if KO
1485
     *
1486
     *  @see        add_product()
1487
     *
1488
     *  Les parameters sont deja cense etre juste et avec valeurs finales a l'appel
1489
     *  de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini
1490
     *  par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit)
1491
     *  et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
1492
     */
1493
    public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $fk_product = 0, $remise_percent = 0, $info_bits = 0, $fk_remise_except = 0, $price_base_type = 'HT', $pu_ttc = 0, $date_start = '', $date_end = '', $type = 0, $rang = -1, $special_code = 0, $fk_parent_line = 0, $fk_fournprice = null, $pa_ht = 0, $label = '', $array_options = array(), $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $ref_ext = '', $noupdateafterinsertline = 0)
1494
    {
1495
        global $mysoc, $conf, $langs, $user;
1496
1497
        $logtext = "::addline commandeid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_percent=$remise_percent";
1498
        $logtext .= ", info_bits=$info_bits, fk_remise_except=$fk_remise_except, price_base_type=$price_base_type, pu_ttc=$pu_ttc, date_start=$date_start";
1499
        $logtext .= ", date_end=$date_end, type=$type special_code=$special_code, fk_unit=$fk_unit, origin=$origin, origin_id=$origin_id, pu_ht_devise=$pu_ht_devise, ref_ext=$ref_ext";
1500
        dol_syslog(get_class($this) . $logtext, LOG_DEBUG);
1501
1502
        if ($this->statut == self::STATUS_DRAFT) {
1503
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1504
1505
            // Clean parameters
1506
1507
            if (empty($remise_percent)) {
1508
                $remise_percent = 0;
1509
            }
1510
            if (empty($qty)) {
1511
                $qty = 0;
1512
            }
1513
            if (empty($info_bits)) {
1514
                $info_bits = 0;
1515
            }
1516
            if (empty($rang)) {
1517
                $rang = 0;
1518
            }
1519
            if (empty($txtva)) {
1520
                $txtva = 0;
1521
            }
1522
            if (empty($txlocaltax1)) {
1523
                $txlocaltax1 = 0;
1524
            }
1525
            if (empty($txlocaltax2)) {
1526
                $txlocaltax2 = 0;
1527
            }
1528
            if (empty($fk_parent_line) || $fk_parent_line < 0) {
1529
                $fk_parent_line = 0;
1530
            }
1531
            if (empty($this->fk_multicurrency)) {
1532
                $this->fk_multicurrency = 0;
1533
            }
1534
            if (empty($ref_ext)) {
1535
                $ref_ext = '';
1536
            }
1537
1538
            $remise_percent = (float) price2num($remise_percent);
1539
            $qty = (float) price2num($qty);
1540
            $pu_ht = price2num($pu_ht);
1541
            $pu_ht_devise = price2num($pu_ht_devise);
1542
            $pu_ttc = price2num($pu_ttc);
1543
            $pa_ht = (float) price2num($pa_ht);
1544
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
1545
                $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
1546
            }
1547
            $txlocaltax1 = price2num($txlocaltax1);
1548
            $txlocaltax2 = price2num($txlocaltax2);
1549
            if ($price_base_type == 'HT') {
1550
                $pu = $pu_ht;
1551
            } else {
1552
                $pu = $pu_ttc;
1553
            }
1554
            $label = trim($label);
1555
            $desc = trim($desc);
1556
1557
            // Check parameters
1558
            if ($type < 0) {
1559
                return -1;
1560
            }
1561
1562
            if ($date_start && $date_end && $date_start > $date_end) {
1563
                $langs->load("errors");
1564
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1565
                return -1;
1566
            }
1567
1568
            $this->db->begin();
1569
1570
            $product_type = $type;
1571
            if (!empty($fk_product) && $fk_product > 0) {
1572
                $product = new Product($this->db);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Commande\Classes\Product was not found. Did you mean Product? If so, make sure to prefix the type with \.
Loading history...
1573
                $result = $product->fetch($fk_product);
1574
                $product_type = $product->type;
1575
1576
                if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product_type == 0 && $product->stock_reel < $qty) {
1577
                    $langs->load("errors");
1578
                    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
1579
                    $this->errors[] = $this->error;
1580
                    dol_syslog(get_class($this) . "::addline error=Product " . $product->ref . ": " . $this->error, LOG_ERR);
1581
                    $this->db->rollback();
1582
                    return self::STOCK_NOT_ENOUGH_FOR_ORDER;
1583
                }
1584
            }
1585
            // Calcul du total TTC et de la TVA pour la ligne a partir de
1586
            // qty, pu, remise_percent et txtva
1587
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
1588
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
1589
1590
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
1591
1592
            // Clean vat code
1593
            $reg = array();
1594
            $vat_src_code = '';
1595
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
1596
                $vat_src_code = $reg[1];
1597
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
1598
            }
1599
1600
            $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
1601
1602
            /*var_dump($txlocaltax1);
1603
             var_dump($txlocaltax2);
1604
             var_dump($localtaxes_type);
1605
             var_dump($tabprice);
1606
             var_dump($tabprice[9]);
1607
             var_dump($tabprice[10]);
1608
             exit;*/
1609
1610
            $total_ht  = $tabprice[0];
1611
            $total_tva = $tabprice[1];
1612
            $total_ttc = $tabprice[2];
1613
            $total_localtax1 = $tabprice[9];
1614
            $total_localtax2 = $tabprice[10];
1615
            $pu_ht = $tabprice[3];
1616
1617
            // MultiCurrency
1618
            $multicurrency_total_ht  = $tabprice[16];
1619
            $multicurrency_total_tva = $tabprice[17];
1620
            $multicurrency_total_ttc = $tabprice[18];
1621
            $pu_ht_devise = $tabprice[19];
1622
1623
            // Rang to use
1624
            $ranktouse = $rang;
1625
            if ($ranktouse == -1) {
1626
                $rangmax = $this->line_max($fk_parent_line);
1627
                $ranktouse = $rangmax + 1;
1628
            }
1629
1630
            // TODO A virer
1631
            // Anciens indicateurs: $price, $remise (a ne plus utiliser)
1632
            $price = $pu;
1633
            $remise = 0;
1634
            if ($remise_percent > 0) {
1635
                $remise = round(((float) $pu * $remise_percent / 100), 2);
1636
                $price = (float) $pu - $remise;
1637
            }
1638
1639
            // Insert line
1640
            $this->line = new OrderLine($this->db);
1641
1642
            $this->line->context = $this->context;
1643
1644
            $this->line->fk_commande = $this->id;
1645
            $this->line->label = $label;
1646
            $this->line->desc = $desc;
1647
            $this->line->qty = $qty;
1648
            $this->line->ref_ext = $ref_ext;
1649
1650
            $this->line->vat_src_code = $vat_src_code;
1651
            $this->line->tva_tx = $txtva;
1652
            $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
1653
            $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
1654
            $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1655
            $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1656
            $this->line->fk_product = $fk_product;
1657
            $this->line->product_type = $product_type;
1658
            $this->line->fk_remise_except = $fk_remise_except;
1659
            $this->line->remise_percent = $remise_percent;
1660
            $this->line->subprice = $pu_ht;
1661
            $this->line->rang = $ranktouse;
1662
            $this->line->info_bits = $info_bits;
1663
            $this->line->total_ht = $total_ht;
1664
            $this->line->total_tva = $total_tva;
1665
            $this->line->total_localtax1 = $total_localtax1;
1666
            $this->line->total_localtax2 = $total_localtax2;
1667
            $this->line->total_ttc = $total_ttc;
1668
            $this->line->special_code = $special_code;
1669
            $this->line->origin = $origin;
1670
            $this->line->origin_id = $origin_id;
1671
            $this->line->fk_parent_line = $fk_parent_line;
1672
            $this->line->fk_unit = $fk_unit;
1673
1674
            $this->line->date_start = $date_start;
1675
            $this->line->date_end = $date_end;
1676
1677
            $this->line->fk_fournprice = $fk_fournprice;
1678
            $this->line->pa_ht = $pa_ht;
1679
1680
            // Multicurrency
1681
            $this->line->fk_multicurrency = $this->fk_multicurrency;
1682
            $this->line->multicurrency_code = $this->multicurrency_code;
1683
            $this->line->multicurrency_subprice     = $pu_ht_devise;
1684
            $this->line->multicurrency_total_ht     = $multicurrency_total_ht;
1685
            $this->line->multicurrency_total_tva    = $multicurrency_total_tva;
1686
            $this->line->multicurrency_total_ttc    = $multicurrency_total_ttc;
1687
1688
            // TODO Ne plus utiliser
1689
            $this->line->price = $price;
1690
1691
            if (is_array($array_options) && count($array_options) > 0) {
1692
                $this->line->array_options = $array_options;
1693
            }
1694
1695
            $result = $this->line->insert($user);
1696
            if ($result > 0) {
1697
                // Reorder if child line
1698
                if (!empty($fk_parent_line)) {
1699
                    $this->line_order(true, 'DESC');
1700
                } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
1701
                    $linecount = count($this->lines);
1702
                    for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
1703
                        $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
1704
                    }
1705
                }
1706
1707
                // Mise a jour information denormalisees au niveau de la commande meme
1708
                if (empty($noupdateafterinsertline)) {
1709
                    $result = $this->update_price(1, 'auto', 0, $mysoc); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
1710
                }
1711
1712
                if ($result > 0) {
1713
                    $this->db->commit();
1714
                    $this->lines[] = $this->line;
1715
                    return $this->line->id;
1716
                } else {
1717
                    $this->db->rollback();
1718
                    return -1;
1719
                }
1720
            } else {
1721
                $this->error = $this->line->error;
1722
                dol_syslog(get_class($this) . "::addline error=" . $this->error, LOG_ERR);
1723
                $this->db->rollback();
1724
                return -2;
1725
            }
1726
        } else {
1727
            dol_syslog(get_class($this) . "::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
1728
            return -3;
1729
        }
1730
    }
1731
1732
1733
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1734
    /**
1735
     *  Add line into array
1736
     *  $this->client must be loaded
1737
     *
1738
     *  @param  int     $idproduct          Product Id
1739
     *  @param  float   $qty                Quantity
1740
     *  @param  float   $remise_percent     Product discount relative
1741
     *  @param  int|string   $date_start         Start date of the line
1742
     *  @param  int|string   $date_end           End date of the line
1743
     *  @return void
1744
     *
1745
     *  TODO    Remplacer les appels a cette fonction par generation object Ligne
1746
     */
1747
    public function add_product($idproduct, $qty, $remise_percent = 0.0, $date_start = '', $date_end = '')
1748
    {
1749
		// phpcs:enable
1750
        global $conf, $mysoc;
1751
1752
        if (!$qty) {
1753
            $qty = 1;
1754
        }
1755
1756
        if ($idproduct > 0) {
1757
            $prod = new Product($this->db);
1758
            $prod->fetch($idproduct);
1759
1760
            $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
1761
            $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
1762
            if (empty($tva_tx)) {
1763
                $tva_npr = 0;
1764
            }
1765
            $vat_src_code = ''; // May be defined into tva_tx
1766
1767
            $localtax1_tx = get_localtax($tva_tx, 1, $this->thirdparty, $mysoc, $tva_npr);
1768
            $localtax2_tx = get_localtax($tva_tx, 2, $this->thirdparty, $mysoc, $tva_npr);
1769
1770
            // multiprix
1771
            if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
1772
                $price = $prod->multiprices[$this->thirdparty->price_level];
1773
            } else {
1774
                $price = $prod->price;
1775
            }
1776
1777
            $line = new OrderLine($this->db);
1778
1779
            $line->context = $this->context;
1780
1781
            $line->fk_product = $idproduct;
1782
            $line->desc = $prod->description;
1783
            $line->qty = $qty;
1784
            $line->subprice = $price;
1785
            $line->remise_percent = $remise_percent;
1786
            $line->vat_src_code = $vat_src_code;
1787
            $line->tva_tx = $tva_tx;
1788
            $line->localtax1_tx = $localtax1_tx;
1789
            $line->localtax2_tx = $localtax2_tx;
1790
1791
            $line->product_ref = $prod->ref;
1792
            $line->product_label = $prod->label;
1793
            $line->product_desc = $prod->description;
1794
            $line->fk_unit = $prod->fk_unit;
1795
1796
            // Save the start and end date of the line in the object
1797
            if ($date_start) {
1798
                $line->date_start = $date_start;
1799
            }
1800
            if ($date_end) {
1801
                $line->date_end = $date_end;
1802
            }
1803
1804
            $this->lines[] = $line;
1805
1806
            /** POUR AJOUTER AUTOMATIQUEMENT LES SOUSPRODUITS a LA COMMANDE
1807
             if (!empty($conf->global->PRODUIT_SOUSPRODUITS))
1808
             {
1809
             $prod = new Product($this->db);
1810
             $prod->fetch($idproduct);
1811
             $prod -> get_sousproduits_arbo();
1812
             $prods_arbo = $prod->get_arbo_each_prod();
1813
             if(count($prods_arbo) > 0)
1814
             {
1815
                 foreach($prods_arbo as $key => $value)
1816
                 {
1817
                     // print "id : ".$value[1].' :qty: '.$value[0].'<br>';
1818
                     if not in lines {
1819
                        $this->add_product($value[1], $value[0]);
1820
                     }
1821
                 }
1822
             }
1823
             **/
1824
        }
1825
    }
1826
1827
1828
    /**
1829
     *  Get object from database. Get also lines.
1830
     *
1831
     *  @param      int         $id             Id of object to load
1832
     *  @param      string      $ref            Ref of object
1833
     *  @param      string      $ref_ext        External reference of object
1834
     *  @param      string      $notused        Internal reference of other object
1835
     *  @return     int                         >0 if OK, <0 if KO, 0 if not found
1836
     */
1837
    public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
1838
    {
1839
        // Check parameters
1840
        if (empty($id) && empty($ref) && empty($ref_ext)) {
1841
            return -1;
1842
        }
1843
1844
        $sql = 'SELECT c.rowid, c.entity, c.date_creation, c.ref, c.fk_soc, c.fk_user_author, c.fk_user_valid, c.fk_user_modif, c.fk_statut';
1845
        $sql .= ', c.amount_ht, c.total_ht, c.total_ttc, c.total_tva, c.localtax1 as total_localtax1, c.localtax2 as total_localtax2, c.fk_cond_reglement, c.deposit_percent, c.fk_mode_reglement, c.fk_availability, c.fk_input_reason';
1846
        $sql .= ', c.fk_account';
1847
        $sql .= ', c.date_commande, c.date_valid, c.tms';
1848
        $sql .= ', c.date_livraison as delivery_date';
1849
        $sql .= ', c.fk_shipping_method';
1850
        $sql .= ', c.fk_warehouse';
1851
        $sql .= ', c.fk_projet as fk_project, c.source, c.facture as billed';
1852
        $sql .= ', c.note_private, c.note_public, c.ref_client, c.ref_ext, c.model_pdf, c.last_main_doc, c.fk_delivery_address, c.extraparams';
1853
        $sql .= ', c.fk_incoterms, c.location_incoterms';
1854
        $sql .= ", c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc";
1855
        $sql .= ", c.module_source, c.pos_source";
1856
        $sql .= ", i.libelle as label_incoterms";
1857
        $sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
1858
        $sql .= ', cr.code as cond_reglement_code, cr.libelle as cond_reglement_libelle, cr.libelle_facture as cond_reglement_libelle_doc';
1859
        $sql .= ', ca.code as availability_code, ca.label as availability_label';
1860
        $sql .= ', dr.code as demand_reason_code';
1861
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'commande as c';
1862
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_payment_term as cr ON c.fk_cond_reglement = cr.rowid';
1863
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_paiement as p ON c.fk_mode_reglement = p.id';
1864
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_availability as ca ON c.fk_availability = ca.rowid';
1865
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_input_reason as dr ON c.fk_input_reason = dr.rowid';
1866
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_incoterms as i ON c.fk_incoterms = i.rowid';
1867
1868
        if ($id) {
1869
            $sql .= " WHERE c.rowid=" . ((int) $id);
1870
        } else {
1871
            $sql .= " WHERE c.entity IN (" . getEntity('commande') . ")"; // Don't use entity if you use rowid
1872
        }
1873
1874
        if ($ref) {
1875
            $sql .= " AND c.ref='" . $this->db->escape($ref) . "'";
1876
        }
1877
        if ($ref_ext) {
1878
            $sql .= " AND c.ref_ext='" . $this->db->escape($ref_ext) . "'";
1879
        }
1880
1881
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
1882
        $result = $this->db->query($sql);
1883
        if ($result) {
1884
            $obj = $this->db->fetch_object($result);
1885
            if ($obj) {
1886
                $this->id = $obj->rowid;
1887
                $this->entity = $obj->entity;
1888
1889
                $this->ref = $obj->ref;
1890
                $this->ref_client = $obj->ref_client;
1891
                $this->ref_customer = $obj->ref_client;
1892
                $this->ref_ext = $obj->ref_ext;
1893
1894
                $this->socid = $obj->fk_soc;
1895
                $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1896
1897
                $this->fk_project = $obj->fk_project;
1898
                $this->project = null; // Clear if another value was already set by fetch_projet
1899
1900
                $this->statut = $obj->fk_statut;
1901
                $this->status = $obj->fk_statut;
1902
1903
                $this->user_author_id = $obj->fk_user_author;
1904
                $this->user_creation_id = $obj->fk_user_author;
1905
                $this->user_validation_id = $obj->fk_user_valid;
1906
                $this->user_modification_id = $obj->fk_user_modif;
1907
                $this->total_ht             = $obj->total_ht;
1908
                $this->total_tva            = $obj->total_tva;
1909
                $this->total_localtax1      = $obj->total_localtax1;
1910
                $this->total_localtax2      = $obj->total_localtax2;
1911
                $this->total_ttc            = $obj->total_ttc;
1912
                $this->date = $this->db->jdate($obj->date_commande);
1913
                $this->date_commande        = $this->db->jdate($obj->date_commande);
1914
                $this->date_creation        = $this->db->jdate($obj->date_creation);
1915
                $this->date_validation      = $this->db->jdate($obj->date_valid);
1916
                $this->date_modification    = $this->db->jdate($obj->tms);
1917
                $this->source               = $obj->source;
1918
                $this->billed               = $obj->billed;
1919
                $this->note = $obj->note_private; // deprecated
1920
                $this->note_private = $obj->note_private;
1921
                $this->note_public = $obj->note_public;
1922
                $this->model_pdf = $obj->model_pdf;
1923
                $this->last_main_doc = $obj->last_main_doc;
1924
                $this->mode_reglement_id    = $obj->fk_mode_reglement;
1925
                $this->mode_reglement_code  = $obj->mode_reglement_code;
1926
                $this->mode_reglement       = $obj->mode_reglement_libelle;
1927
                $this->cond_reglement_id    = $obj->fk_cond_reglement;
1928
                $this->cond_reglement_code  = $obj->cond_reglement_code;
1929
                $this->cond_reglement       = $obj->cond_reglement_libelle;
1930
                $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1931
                $this->deposit_percent = $obj->deposit_percent;
1932
                $this->fk_account = $obj->fk_account;
1933
                $this->availability_id = $obj->fk_availability;
1934
                $this->availability_code    = $obj->availability_code;
1935
                $this->availability         = $obj->availability_label;
1936
                $this->demand_reason_id     = $obj->fk_input_reason;
1937
                $this->demand_reason_code = $obj->demand_reason_code;
1938
                $this->delivery_date = $this->db->jdate($obj->delivery_date);
1939
                $this->shipping_method_id   = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1940
                $this->warehouse_id         = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1941
                $this->fk_delivery_address = $obj->fk_delivery_address;
1942
                $this->module_source        = $obj->module_source;
1943
                $this->pos_source           = $obj->pos_source;
1944
1945
                //Incoterms
1946
                $this->fk_incoterms         = $obj->fk_incoterms;
1947
                $this->location_incoterms   = $obj->location_incoterms;
1948
                $this->label_incoterms    = $obj->label_incoterms;
1949
1950
                // Multicurrency
1951
                $this->fk_multicurrency         = $obj->fk_multicurrency;
1952
                $this->multicurrency_code = $obj->multicurrency_code;
1953
                $this->multicurrency_tx         = $obj->multicurrency_tx;
1954
                $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1955
                $this->multicurrency_total_tva  = $obj->multicurrency_total_tva;
1956
                $this->multicurrency_total_ttc  = $obj->multicurrency_total_ttc;
1957
1958
                $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1959
1960
                $this->lines = array();
1961
1962
                // Retrieve all extrafield
1963
                // fetch optionals attributes and labels
1964
                $this->fetch_optionals();
1965
1966
                $this->db->free($result);
1967
1968
                // Lines
1969
                $result = $this->fetch_lines();
1970
                if ($result < 0) {
1971
                    return -3;
1972
                }
1973
                return 1;
1974
            } else {
1975
                $this->error = 'Order with id ' . $id . ' not found sql=' . $sql;
1976
                return 0;
1977
            }
1978
        } else {
1979
            $this->error = $this->db->error();
1980
            return -1;
1981
        }
1982
    }
1983
1984
1985
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1986
    /**
1987
     *  Add line of fixed discount in the order in DB
1988
     *
1989
     *  @param     int  $idremise           Id for the fixed discount
1990
     *  @return    int                      >0 if OK, <0 if KO
1991
     */
1992
    public function insert_discount($idremise)
1993
    {
1994
		// phpcs:enable
1995
        global $langs;
1996
1997
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1998
        include_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
1999
2000
        $this->db->begin();
2001
2002
        $remise = new DiscountAbsolute($this->db);
2003
        $result = $remise->fetch($idremise);
2004
2005
        if ($result > 0) {
2006
            if ($remise->fk_facture) {  // Protection against multiple submission
2007
                $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2008
                $this->db->rollback();
2009
                return -5;
2010
            }
2011
2012
            $line = new OrderLine($this->db);
2013
2014
            $line->fk_commande = $this->id;
2015
            $line->fk_remise_except = $remise->id;
2016
            $line->desc = $remise->description; // Description ligne
2017
            $line->vat_src_code = $remise->vat_src_code;
2018
            $line->tva_tx = $remise->tva_tx;
2019
            $line->subprice = -$remise->amount_ht;
2020
            $line->price = -$remise->amount_ht;
2021
            $line->fk_product = 0; // Id produit predefini
2022
            $line->qty = 1;
2023
            $line->remise_percent = 0;
2024
            $line->rang = -1;
2025
            $line->info_bits = 2;
2026
2027
            $line->total_ht  = -$remise->amount_ht;
2028
            $line->total_tva = -$remise->amount_tva;
2029
            $line->total_ttc = -$remise->amount_ttc;
2030
2031
            $result = $line->insert();
2032
            if ($result > 0) {
2033
                $result = $this->update_price(1);
2034
                if ($result > 0) {
2035
                    $this->db->commit();
2036
                    return 1;
2037
                } else {
2038
                    $this->db->rollback();
2039
                    return -1;
2040
                }
2041
            } else {
2042
                $this->error = $line->error;
2043
                $this->errors = $line->errors;
2044
                $this->db->rollback();
2045
                return -2;
2046
            }
2047
        } else {
2048
            $this->db->rollback();
2049
            return -2;
2050
        }
2051
    }
2052
2053
2054
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2055
    /**
2056
     *  Load array lines
2057
     *
2058
     *  @param      int     $only_product           Return only physical products, not services
2059
     *  @param      int     $loadalsotranslation    Return translation for products
2060
     *  @return     int                             Return integer <0 if KO, >0 if OK
2061
     */
2062
    public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2063
    {
2064
		// phpcs:enable
2065
        global $langs, $conf;
2066
2067
        $this->lines = array();
2068
2069
        $sql = 'SELECT l.rowid, l.fk_product, l.fk_parent_line, l.product_type, l.fk_commande, l.label as custom_label, l.description, l.price, l.qty, l.vat_src_code, l.tva_tx, l.ref_ext,';
2070
        $sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.fk_remise_except, l.remise_percent, l.subprice, l.fk_product_fournisseur_price as fk_fournprice, l.buy_price_ht as pa_ht, l.rang, l.info_bits, l.special_code,';
2071
        $sql .= ' l.total_ht, l.total_ttc, l.total_tva, l.total_localtax1, l.total_localtax2, l.date_start, l.date_end,';
2072
        $sql .= ' l.fk_unit,';
2073
        $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
2074
        $sql .= ' p.ref as product_ref, p.description as product_desc, p.fk_product_type, p.label as product_label, p.tosell as product_tosell, p.tobuy as product_tobuy, p.tobatch as product_tobatch, p.barcode as product_barcode,';
2075
        $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units';
2076
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'commandedet as l';
2077
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON (p.rowid = l.fk_product)';
2078
        $sql .= ' WHERE l.fk_commande = ' . ((int) $this->id);
2079
        if ($only_product) {
2080
            $sql .= ' AND p.fk_product_type = 0';
2081
        }
2082
        $sql .= ' ORDER BY l.rang, l.rowid';
2083
2084
        dol_syslog(get_class($this) . "::fetch_lines", LOG_DEBUG);
2085
        $result = $this->db->query($sql);
2086
        if ($result) {
2087
            $num = $this->db->num_rows($result);
2088
2089
            $i = 0;
2090
            while ($i < $num) {
2091
                $objp = $this->db->fetch_object($result);
2092
2093
                $line = new OrderLine($this->db);
2094
2095
                $line->rowid            = $objp->rowid;
2096
                $line->id               = $objp->rowid;
2097
                $line->fk_commande      = $objp->fk_commande;
2098
                $line->commande_id      = $objp->fk_commande;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Commande\C...OrderLine::$commande_id has been deprecated: Use fk_commande ( Ignorable by Annotation )

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

2098
                /** @scrutinizer ignore-deprecated */ $line->commande_id      = $objp->fk_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...
2099
                $line->label            = $objp->custom_label;
2100
                $line->desc             = $objp->description;
2101
                $line->description      = $objp->description; // Description line
2102
                $line->product_type     = $objp->product_type;
2103
                $line->qty              = $objp->qty;
2104
                $line->ref_ext          = $objp->ref_ext;
2105
2106
                $line->vat_src_code     = $objp->vat_src_code;
2107
                $line->tva_tx           = $objp->tva_tx;
2108
                $line->localtax1_tx     = $objp->localtax1_tx;
2109
                $line->localtax2_tx     = $objp->localtax2_tx;
2110
                $line->localtax1_type   = $objp->localtax1_type;
2111
                $line->localtax2_type   = $objp->localtax2_type;
2112
                $line->total_ht         = $objp->total_ht;
2113
                $line->total_ttc        = $objp->total_ttc;
2114
                $line->total_tva        = $objp->total_tva;
2115
                $line->total_localtax1  = $objp->total_localtax1;
2116
                $line->total_localtax2  = $objp->total_localtax2;
2117
                $line->subprice         = $objp->subprice;
2118
                $line->fk_remise_except = $objp->fk_remise_except;
2119
                $line->remise_percent   = $objp->remise_percent;
2120
                $line->price            = $objp->price;
2121
                $line->fk_product       = $objp->fk_product;
2122
                $line->fk_fournprice = $objp->fk_fournprice;
2123
                $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2124
                $line->pa_ht = $marginInfos[0];
2125
                $line->marge_tx         = $marginInfos[1];
2126
                $line->marque_tx        = $marginInfos[2];
2127
                $line->rang             = $objp->rang;
2128
                $line->info_bits        = $objp->info_bits;
2129
                $line->special_code = $objp->special_code;
2130
                $line->fk_parent_line = $objp->fk_parent_line;
2131
2132
                $line->ref = $objp->product_ref;
2133
                $line->libelle = $objp->product_label;
2134
2135
                $line->product_ref = $objp->product_ref;
2136
                $line->product_label = $objp->product_label;
2137
                $line->product_tosell   = $objp->product_tosell;
2138
                $line->product_tobuy    = $objp->product_tobuy;
2139
                $line->product_desc     = $objp->product_desc;
2140
                $line->product_tobatch  = $objp->product_tobatch;
2141
                $line->product_barcode  = $objp->product_barcode;
2142
2143
                $line->fk_product_type  = $objp->fk_product_type; // Produit ou service
2144
                $line->fk_unit          = $objp->fk_unit;
2145
2146
                $line->weight           = $objp->weight;
2147
                $line->weight_units     = $objp->weight_units;
2148
                $line->volume           = $objp->volume;
2149
                $line->volume_units     = $objp->volume_units;
2150
2151
                $line->date_start       = $this->db->jdate($objp->date_start);
2152
                $line->date_end         = $this->db->jdate($objp->date_end);
2153
2154
                // Multicurrency
2155
                $line->fk_multicurrency = $objp->fk_multicurrency;
2156
                $line->multicurrency_code = $objp->multicurrency_code;
2157
                $line->multicurrency_subprice   = $objp->multicurrency_subprice;
2158
                $line->multicurrency_total_ht   = $objp->multicurrency_total_ht;
2159
                $line->multicurrency_total_tva  = $objp->multicurrency_total_tva;
2160
                $line->multicurrency_total_ttc  = $objp->multicurrency_total_ttc;
2161
2162
                $line->fetch_optionals();
2163
2164
                // multilangs
2165
                if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2166
                    $tmpproduct = new Product($this->db);
2167
                    $tmpproduct->fetch($objp->fk_product);
2168
                    $tmpproduct->getMultiLangs();
2169
2170
                    $line->multilangs = $tmpproduct->multilangs;
2171
                }
2172
2173
                $this->lines[$i] = $line;
2174
2175
                $i++;
2176
            }
2177
2178
            $this->db->free($result);
2179
2180
            return 1;
2181
        } else {
2182
            $this->error = $this->db->error();
2183
            return -3;
2184
        }
2185
    }
2186
2187
2188
    /**
2189
     *  Return number of line with type product.
2190
     *
2191
     *  @return     int     Return integer <0 if KO, Nbr of product lines if OK
2192
     */
2193
    public function getNbOfProductsLines()
2194
    {
2195
        $nb = 0;
2196
        foreach ($this->lines as $line) {
2197
            if ($line->product_type == 0) {
2198
                $nb++;
2199
            }
2200
        }
2201
        return $nb;
2202
    }
2203
2204
    /**
2205
     *  Return number of line with type service.
2206
     *
2207
     *  @return     int     Return integer <0 if KO, Nbr of service lines if OK
2208
     */
2209
    public function getNbOfServicesLines()
2210
    {
2211
        $nb = 0;
2212
        foreach ($this->lines as $line) {
2213
            if ($line->product_type == 1) {
2214
                $nb++;
2215
            }
2216
        }
2217
        return $nb;
2218
    }
2219
2220
    /**
2221
     *  Count number of shipments for this order
2222
     *
2223
     *  @return     int                         Return integer <0 if KO, Nb of shipment found if OK
2224
     */
2225
    public function getNbOfShipments()
2226
    {
2227
        $nb = 0;
2228
2229
        $sql = 'SELECT COUNT(DISTINCT ed.fk_expedition) as nb';
2230
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'expeditiondet as ed,';
2231
        $sql .= ' ' . MAIN_DB_PREFIX . 'commandedet as cd';
2232
        $sql .= ' WHERE';
2233
        $sql .= ' ed.fk_elementdet = cd.rowid';
2234
        $sql .= ' AND cd.fk_commande = ' . ((int) $this->id);
2235
        //print $sql;
2236
2237
        dol_syslog(get_class($this) . "::getNbOfShipments", LOG_DEBUG);
2238
        $resql = $this->db->query($sql);
2239
        if ($resql) {
2240
            $obj = $this->db->fetch_object($resql);
2241
            if ($obj) {
2242
                $nb = $obj->nb;
2243
            }
2244
2245
            $this->db->free($resql);
2246
            return $nb;
2247
        } else {
2248
            $this->error = $this->db->lasterror();
2249
            return -1;
2250
        }
2251
    }
2252
2253
    /**
2254
     *  Load array this->expeditions of lines of shipments with nb of products sent for each order line
2255
     *  Note: For a dedicated shipment, the fetch_lines can be used to load the qty_asked and qty_shipped. This function is use to return qty_shipped cumulated for the order
2256
     *
2257
     *  @param      int     $filtre_statut      Filter on shipment status
2258
     *  @param      int     $fk_product         Add a filter on a product
2259
     *  @return     int                         Return integer <0 if KO, Nb of lines found if OK
2260
     */
2261
    public function loadExpeditions($filtre_statut = -1, $fk_product = 0)
2262
    {
2263
        $this->expeditions = array();
2264
2265
        $sql = 'SELECT cd.rowid, cd.fk_product,';
2266
        $sql .= ' sum(ed.qty) as qty';
2267
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'expeditiondet as ed,';
2268
        if ($filtre_statut >= 0) {
2269
            $sql .= ' ' . MAIN_DB_PREFIX . 'expedition as e,';
2270
        }
2271
        $sql .= ' ' . MAIN_DB_PREFIX . 'commandedet as cd';
2272
        $sql .= ' WHERE';
2273
        if ($filtre_statut >= 0) {
2274
            $sql .= ' ed.fk_expedition = e.rowid AND';
2275
        }
2276
        $sql .= ' ed.fk_elementdet = cd.rowid';
2277
        $sql .= ' AND cd.fk_commande = ' . ((int) $this->id);
2278
        if ($fk_product > 0) {
2279
            $sql .= ' AND cd.fk_product = ' . ((int) $fk_product);
2280
        }
2281
        if ($filtre_statut >= 0) {
2282
            $sql .= ' AND e.fk_statut >= ' . ((int) $filtre_statut);
2283
        }
2284
        $sql .= ' GROUP BY cd.rowid, cd.fk_product';
2285
        //print $sql;
2286
2287
        dol_syslog(get_class($this) . "::loadExpeditions", LOG_DEBUG);
2288
        $resql = $this->db->query($sql);
2289
        if ($resql) {
2290
            $num = $this->db->num_rows($resql);
2291
            $i = 0;
2292
            while ($i < $num) {
2293
                $obj = $this->db->fetch_object($resql);
2294
                $this->expeditions[$obj->rowid] = $obj->qty;
2295
                $i++;
2296
            }
2297
            $this->db->free($resql);
2298
            return $num;
2299
        } else {
2300
            $this->error = $this->db->lasterror();
2301
            return -1;
2302
        }
2303
    }
2304
2305
    /**
2306
     * Returns an array with expeditions lines number
2307
     *
2308
     * @return  int     Nb of shipments
2309
     */
2310
    public function countNbOfShipments()
2311
    {
2312
        $sql = 'SELECT count(*)';
2313
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'expedition as e';
2314
        $sql .= ', ' . MAIN_DB_PREFIX . 'element_element as el';
2315
        $sql .= ' WHERE el.fk_source = ' . ((int) $this->id);
2316
        $sql .= " AND el.sourcetype = 'commande'";
2317
        $sql .= " AND el.fk_target = e.rowid";
2318
        $sql .= " AND el.targettype = 'shipping'";
2319
2320
        $resql = $this->db->query($sql);
2321
        if ($resql) {
2322
            $row = $this->db->fetch_row($resql);
2323
            return $row[0];
2324
        } else {
2325
            dol_print_error($this->db);
2326
        }
2327
2328
        return 0;
2329
    }
2330
2331
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2332
    /**
2333
     *  Return a array with the pending stock by product
2334
     *
2335
     *  @param      int     $filtre_statut      Filtre sur statut
2336
     *  @return     int                         0 si OK, <0 si KO
2337
     *
2338
     *  TODO        FONCTION NON FINIE A FINIR
2339
     */
2340
    /*public function stock_array($filtre_statut = self::STATUS_CANCELED)
2341
    {
2342
		// phpcs:enable
2343
        $this->stocks = array();
2344
2345
        // Tableau des id de produit de la commande
2346
        $array_of_product = array();
2347
2348
        // Recherche total en stock pour chaque produit
2349
        // TODO $array_of_product est défini vide juste au dessus !!
2350
        if (count($array_of_product)) {
2351
            $sql = "SELECT fk_product, sum(ps.reel) as total";
2352
            $sql .= " FROM ".MAIN_DB_PREFIX."product_stock as ps";
2353
            $sql .= " WHERE ps.fk_product IN (".$this->db->sanitize(join(',', $array_of_product)).")";
2354
            $sql .= ' GROUP BY fk_product';
2355
            $resql = $this->db->query($sql);
2356
            if ($resql) {
2357
                $num = $this->db->num_rows($resql);
2358
                $i = 0;
2359
                while ($i < $num) {
2360
                    $obj = $this->db->fetch_object($resql);
2361
                    $this->stocks[$obj->fk_product] = $obj->total;
2362
                    $i++;
2363
                }
2364
                $this->db->free($resql);
2365
            }
2366
        }
2367
        return 0;
2368
    }*/
2369
2370
    /**
2371
     *  Delete an order line
2372
     *
2373
     *  @param      User    $user       User object
2374
     *  @param      int     $lineid     Id of line to delete
2375
     *  @param      int     $id         Id of object (for a check)
2376
     *  @return     int                 >0 if OK, 0 if nothing to do, <0 if KO
2377
     */
2378
    public function deleteLine($user = null, $lineid = 0, $id = 0)
2379
    {
2380
        if ($this->statut == self::STATUS_DRAFT) {
2381
            $this->db->begin();
2382
2383
            // Delete line
2384
            $line = new OrderLine($this->db);
2385
2386
            $line->context = $this->context;
2387
2388
            // Load data
2389
            $line->fetch($lineid);
2390
2391
            if ($id > 0 && $line->fk_commande != $id) {
2392
                $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2393
                return -1;
2394
            }
2395
2396
            // Memorize previous line for triggers
2397
            $staticline = clone $line;
2398
            $line->oldline = $staticline;
2399
2400
            if ($line->delete($user) > 0) {
2401
                $result = $this->update_price(1);
2402
2403
                if ($result > 0) {
2404
                    $this->db->commit();
2405
                    return 1;
2406
                } else {
2407
                    $this->db->rollback();
2408
                    $this->error = $this->db->lasterror();
2409
                    return -1;
2410
                }
2411
            } else {
2412
                $this->db->rollback();
2413
                $this->error = $line->error;
2414
                return -1;
2415
            }
2416
        } else {
2417
            $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
2418
            return -1;
2419
        }
2420
    }
2421
2422
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2423
    /**
2424
     *  Applique une remise relative
2425
     *
2426
     *  @deprecated Use setDiscount() instead.
2427
     *  @see setDiscount()
2428
     *  @param      User        $user       User qui positionne la remise
2429
     *  @param      float       $remise     Discount (percent)
2430
     *  @param      int         $notrigger  1=Does not execute triggers, 0= execute triggers
2431
     *  @return     int                     Return integer <0 if KO, >0 if OK
2432
     */
2433
    public function set_remise($user, $remise, $notrigger = 0)
2434
    {
2435
		// phpcs:enable
2436
        dol_syslog(get_class($this) . "::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
2437
        // @phan-suppress-next-line PhanDeprecatedFunction
2438
        return $this->setDiscount($user, $remise, $notrigger);
2439
    }
2440
2441
    /**
2442
     *  Set a percentage discount
2443
     *
2444
     *  @param      User        $user       User setting the discount
2445
     *  @param      float|string    $remise     Discount (percent)
2446
     *  @param      int<0,1>    $notrigger  1=Does not execute triggers, 0= execute triggers
2447
     *  @return     int<-1,1>                   Return integer <0 if KO, >0 if OK
2448
     */
2449
    public function setDiscount($user, $remise, $notrigger = 0)
2450
    {
2451
        $remise = trim((string) $remise) ? trim((string) $remise) : 0;
2452
2453
        if ($user->hasRight('commande', 'creer')) {
2454
            $error = 0;
2455
2456
            $this->db->begin();
2457
2458
            $remise = price2num($remise, 2);
2459
2460
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande';
2461
            $sql .= ' SET remise_percent = ' . ((float) $remise);
2462
            $sql .= ' WHERE rowid = ' . ((int) $this->id) . ' AND fk_statut = ' . ((int) self::STATUS_DRAFT);
2463
2464
            dol_syslog(__METHOD__, LOG_DEBUG);
2465
            $resql = $this->db->query($sql);
2466
            if (!$resql) {
2467
                $this->errors[] = $this->db->error();
2468
                $error++;
2469
            }
2470
2471
            if (!$error) {
2472
                $this->oldcopy = clone $this;
2473
                $this->remise_percent = $remise;
2474
                $this->update_price(1);
2475
            }
2476
2477
            if (!$notrigger && empty($error)) {
2478
                // Call trigger
2479
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2480
                if ($result < 0) {
2481
                    $error++;
2482
                }
2483
                // End call triggers
2484
            }
2485
2486
            if (!$error) {
2487
                $this->db->commit();
2488
                return 1;
2489
            } else {
2490
                foreach ($this->errors as $errmsg) {
2491
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2492
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2493
                }
2494
                $this->db->rollback();
2495
                return -1 * $error;
2496
            }
2497
        }
2498
2499
        return 0;
2500
    }
2501
2502
2503
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2504
    /**
2505
     *      Set a fixed amount discount
2506
     *
2507
     *      @param      User        $user       User qui positionne la remise
2508
     *      @param      float       $remise     Discount
2509
     *      @param      int         $notrigger  1=Does not execute triggers, 0= execute triggers
2510
     *      @return     int                     Return integer <0 if KO, >0 if OK
2511
     */
2512
    /*
2513
    public function set_remise_absolue($user, $remise, $notrigger = 0)
2514
    {
2515
		// phpcs:enable
2516
        if (empty($remise)) {
2517
            $remise = 0;
2518
        }
2519
2520
        $remise = price2num($remise);
2521
2522
        if ($user->hasRight('commande', 'creer')) {
2523
            $error = 0;
2524
2525
            $this->db->begin();
2526
2527
            $sql = 'UPDATE '.MAIN_DB_PREFIX.'commande';
2528
            $sql .= ' SET remise_absolue = '.((float) $remise);
2529
            $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.self::STATUS_DRAFT;
2530
2531
            dol_syslog(__METHOD__, LOG_DEBUG);
2532
            $resql = $this->db->query($sql);
2533
            if (!$resql) {
2534
                $this->errors[] = $this->db->error();
2535
                $error++;
2536
            }
2537
2538
            if (!$error) {
2539
                $this->oldcopy = clone $this;
2540
                $this->remise_absolue = $remise;
2541
                $this->update_price(1);
2542
            }
2543
2544
            if (!$notrigger && empty($error)) {
2545
                // Call trigger
2546
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2547
                if ($result < 0) {
2548
                    $error++;
2549
                }
2550
                // End call triggers
2551
            }
2552
2553
            if (!$error) {
2554
                $this->db->commit();
2555
                return 1;
2556
            } else {
2557
                foreach ($this->errors as $errmsg) {
2558
                    dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2559
                    $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2560
                }
2561
                $this->db->rollback();
2562
                return -1 * $error;
2563
            }
2564
        }
2565
2566
        return 0;
2567
    }
2568
    */
2569
2570
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2571
    /**
2572
     *  Set the order date
2573
     *
2574
     *  @param      User    $user       Object user making change
2575
     *  @param      int     $date       Date
2576
     *  @param      int     $notrigger  1=Does not execute triggers, 0= execute triggers
2577
     *  @return     int                 Return integer <0 if KO, >0 if OK
2578
     */
2579
    public function set_date($user, $date, $notrigger = 0)
2580
    {
2581
		// phpcs:enable
2582
        if ($user->hasRight('commande', 'creer')) {
2583
            $error = 0;
2584
2585
            $this->db->begin();
2586
2587
            $sql = "UPDATE " . MAIN_DB_PREFIX . "commande";
2588
            $sql .= " SET date_commande = " . ($date ? "'" . $this->db->idate($date) . "'" : 'null');
2589
            $sql .= " WHERE rowid = " . ((int) $this->id) . " AND fk_statut = " . ((int) self::STATUS_DRAFT);
2590
2591
            dol_syslog(__METHOD__, LOG_DEBUG);
2592
            $resql = $this->db->query($sql);
2593
            if (!$resql) {
2594
                $this->errors[] = $this->db->error();
2595
                $error++;
2596
            }
2597
2598
            if (!$error) {
2599
                $this->oldcopy = clone $this;
2600
                $this->date = $date;
2601
            }
2602
2603
            if (!$notrigger && empty($error)) {
2604
                // Call trigger
2605
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2606
                if ($result < 0) {
2607
                    $error++;
2608
                }
2609
                // End call triggers
2610
            }
2611
2612
            if (!$error) {
2613
                $this->db->commit();
2614
                return 1;
2615
            } else {
2616
                foreach ($this->errors as $errmsg) {
2617
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2618
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2619
                }
2620
                $this->db->rollback();
2621
                return -1 * $error;
2622
            }
2623
        } else {
2624
            return -2;
2625
        }
2626
    }
2627
2628
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2629
    /**
2630
     *  Set delivery date
2631
     *
2632
     *  @param      User    $user               Object user that modify
2633
     *  @param      int     $delivery_date      Delivery date
2634
     *  @param      int     $notrigger          1=Does not execute triggers, 0= execute triggers
2635
     *  @return     int                         Return integer <0 if ko, >0 if ok
2636
     *  @deprecated Use  setDeliveryDate
2637
     */
2638
    public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2639
    {
2640
		// phpcs:enable
2641
        return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2642
    }
2643
2644
    /**
2645
     *  Set the planned delivery date
2646
     *
2647
     *  @param      User    $user               Object utilisateur qui modifie
2648
     *  @param      int     $delivery_date     Delivery date
2649
     *  @param      int     $notrigger          1=Does not execute triggers, 0= execute triggers
2650
     *  @return     int                         Return integer <0 si ko, >0 si ok
2651
     */
2652
    public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2653
    {
2654
        if ($user->hasRight('commande', 'creer')) {
2655
            $error = 0;
2656
2657
            $this->db->begin();
2658
2659
            $sql = "UPDATE " . MAIN_DB_PREFIX . "commande";
2660
            $sql .= " SET date_livraison = " . ($delivery_date ? "'" . $this->db->idate($delivery_date) . "'" : 'null');
2661
            $sql .= " WHERE rowid = " . ((int) $this->id);
2662
2663
            dol_syslog(__METHOD__, LOG_DEBUG);
2664
            $resql = $this->db->query($sql);
2665
            if (!$resql) {
2666
                $this->errors[] = $this->db->error();
2667
                $error++;
2668
            }
2669
2670
            if (!$error) {
2671
                $this->oldcopy = clone $this;
2672
                $this->delivery_date = $delivery_date;
2673
            }
2674
2675
            if (!$notrigger && empty($error)) {
2676
                // Call trigger
2677
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2678
                if ($result < 0) {
2679
                    $error++;
2680
                }
2681
                // End call triggers
2682
            }
2683
2684
            if (!$error) {
2685
                $this->db->commit();
2686
                return 1;
2687
            } else {
2688
                foreach ($this->errors as $errmsg) {
2689
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2690
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2691
                }
2692
                $this->db->rollback();
2693
                return -1 * $error;
2694
            }
2695
        } else {
2696
            return -2;
2697
        }
2698
    }
2699
2700
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2701
    /**
2702
     *  Return list of orders (eventuelly filtered on a user) into an array
2703
     *
2704
     *  @param      int     $shortlist      0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
2705
     *  @param      int     $draft          0=not draft, 1=draft
2706
     *  @param      User    $excluser       Object user to exclude
2707
     *  @param      int     $socid          Id third party
2708
     *  @param      int     $limit          For pagination
2709
     *  @param      int     $offset         For pagination
2710
     *  @param      string  $sortfield      Sort criteria
2711
     *  @param      string  $sortorder      Sort order
2712
     *  @return     int|array                   -1 if KO, array with result if OK
2713
     */
2714
    public function liste_array($shortlist = 0, $draft = 0, $excluser = null, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'c.date_commande', $sortorder = 'DESC')
2715
    {
2716
		// phpcs:enable
2717
        global $user;
2718
2719
        $ga = array();
2720
2721
        $sql = "SELECT s.rowid, s.nom as name, s.client,";
2722
        $sql .= " c.rowid as cid, c.ref";
2723
        if (!$user->hasRight('societe', 'client', 'voir')) {
2724
            $sql .= ", sc.fk_soc, sc.fk_user";
2725
        }
2726
        $sql .= " FROM " . MAIN_DB_PREFIX . "societe as s, " . MAIN_DB_PREFIX . "commande as c";
2727
        if (!$user->hasRight('societe', 'client', 'voir')) {
2728
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2729
        }
2730
        $sql .= " WHERE c.entity IN (" . getEntity('commande') . ")";
2731
        $sql .= " AND c.fk_soc = s.rowid";
2732
        if (!$user->hasRight('societe', 'client', 'voir')) {
2733
            $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
2734
        }
2735
        if ($socid) {
2736
            $sql .= " AND s.rowid = " . ((int) $socid);
2737
        }
2738
        if ($draft) {
2739
            $sql .= " AND c.fk_statut = " . self::STATUS_DRAFT;
2740
        }
2741
        if (is_object($excluser)) {
2742
            $sql .= " AND c.fk_user_author <> " . ((int) $excluser->id);
2743
        }
2744
        $sql .= $this->db->order($sortfield, $sortorder);
2745
        $sql .= $this->db->plimit($limit, $offset);
2746
2747
        $result = $this->db->query($sql);
2748
        if ($result) {
2749
            $numc = $this->db->num_rows($result);
2750
            if ($numc) {
2751
                $i = 0;
2752
                while ($i < $numc) {
2753
                    $obj = $this->db->fetch_object($result);
2754
2755
                    if ($shortlist == 1) {
2756
                        $ga[$obj->cid] = $obj->ref;
2757
                    } elseif ($shortlist == 2) {
2758
                        $ga[$obj->cid] = $obj->ref . ' (' . $obj->name . ')';
2759
                    } else {
2760
                        $ga[$i]['id'] = $obj->cid;
2761
                        $ga[$i]['ref']  = $obj->ref;
2762
                        $ga[$i]['name'] = $obj->name;
2763
                    }
2764
                    $i++;
2765
                }
2766
            }
2767
            return $ga;
2768
        } else {
2769
            dol_print_error($this->db);
2770
            return -1;
2771
        }
2772
    }
2773
2774
    /**
2775
     *  Update delivery delay
2776
     *
2777
     *  @param      int     $availability_id    Id du nouveau mode
2778
     *  @param      int     $notrigger          1=Does not execute triggers, 0= execute triggers
2779
     *  @return     int                         >0 if OK, <0 if KO
2780
     */
2781
    public function availability($availability_id, $notrigger = 0)
2782
    {
2783
        global $user;
2784
2785
        dol_syslog('Commande::availability(' . $availability_id . ')');
2786
        if ($this->statut >= self::STATUS_DRAFT) {
2787
            $error = 0;
2788
2789
            $this->db->begin();
2790
2791
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande';
2792
            $sql .= ' SET fk_availability = ' . ((int) $availability_id);
2793
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2794
2795
            dol_syslog(__METHOD__, LOG_DEBUG);
2796
            $resql = $this->db->query($sql);
2797
            if (!$resql) {
2798
                $this->errors[] = $this->db->error();
2799
                $error++;
2800
            }
2801
2802
            if (!$error) {
2803
                $this->oldcopy = clone $this;
2804
                $this->availability_id = $availability_id;
2805
            }
2806
2807
            if (!$notrigger && empty($error)) {
2808
                // Call trigger
2809
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2810
                if ($result < 0) {
2811
                    $error++;
2812
                }
2813
                // End call triggers
2814
            }
2815
2816
            if (!$error) {
2817
                $this->db->commit();
2818
                return 1;
2819
            } else {
2820
                foreach ($this->errors as $errmsg) {
2821
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2822
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2823
                }
2824
                $this->db->rollback();
2825
                return -1 * $error;
2826
            }
2827
        } else {
2828
            $error_str = 'Command status do not meet requirement ' . $this->statut;
2829
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
2830
            $this->error = $error_str;
2831
            $this->errors[] = $this->error;
2832
            return -2;
2833
        }
2834
    }
2835
2836
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2837
    /**
2838
     *  Update order demand_reason
2839
     *
2840
     *  @param      int     $demand_reason_id   Id of new demand
2841
     *  @param      int     $notrigger          1=Does not execute triggers, 0= execute triggers
2842
     *  @return     int                         >0 if ok, <0 if ko
2843
     */
2844
    public function demand_reason($demand_reason_id, $notrigger = 0)
2845
    {
2846
		// phpcs:enable
2847
        global $user;
2848
2849
        dol_syslog('Commande::demand_reason(' . $demand_reason_id . ')');
2850
        if ($this->statut >= self::STATUS_DRAFT) {
2851
            $error = 0;
2852
2853
            $this->db->begin();
2854
2855
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande';
2856
            $sql .= ' SET fk_input_reason = ' . ((int) $demand_reason_id);
2857
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2858
2859
            dol_syslog(__METHOD__, LOG_DEBUG);
2860
            $resql = $this->db->query($sql);
2861
            if (!$resql) {
2862
                $this->errors[] = $this->db->error();
2863
                $error++;
2864
            }
2865
2866
            if (!$error) {
2867
                $this->oldcopy = clone $this;
2868
                $this->demand_reason_id = $demand_reason_id;
2869
            }
2870
2871
            if (!$notrigger && empty($error)) {
2872
                // Call trigger
2873
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2874
                if ($result < 0) {
2875
                    $error++;
2876
                }
2877
                // End call triggers
2878
            }
2879
2880
            if (!$error) {
2881
                $this->db->commit();
2882
                return 1;
2883
            } else {
2884
                foreach ($this->errors as $errmsg) {
2885
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2886
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2887
                }
2888
                $this->db->rollback();
2889
                return -1 * $error;
2890
            }
2891
        } else {
2892
            $error_str = 'order status do not meet requirement ' . $this->statut;
2893
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
2894
            $this->error = $error_str;
2895
            $this->errors[] = $this->error;
2896
            return -2;
2897
        }
2898
    }
2899
2900
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2901
    /**
2902
     *  Set customer ref
2903
     *
2904
     *  @param      User    $user           User that make change
2905
     *  @param      string  $ref_client     Customer ref
2906
     *  @param      int     $notrigger      1=Does not execute triggers, 0= execute triggers
2907
     *  @return     int                     Return integer <0 if KO, >0 if OK
2908
     */
2909
    public function set_ref_client($user, $ref_client, $notrigger = 0)
2910
    {
2911
		// phpcs:enable
2912
        if ($user->hasRight('commande', 'creer')) {
2913
            $error = 0;
2914
2915
            $this->db->begin();
2916
2917
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande SET';
2918
            $sql .= ' ref_client = ' . (empty($ref_client) ? 'NULL' : "'" . $this->db->escape($ref_client) . "'");
2919
            $sql .= ' WHERE rowid = ' . ((int) $this->id);
2920
2921
            dol_syslog(__METHOD__ . ' this->id=' . $this->id . ', ref_client=' . $ref_client, LOG_DEBUG);
2922
            $resql = $this->db->query($sql);
2923
            if (!$resql) {
2924
                $this->errors[] = $this->db->error();
2925
                $error++;
2926
            }
2927
2928
            if (!$error) {
2929
                $this->oldcopy = clone $this;
2930
                $this->ref_client = $ref_client;
2931
                $this->ref_customer = $ref_client;
2932
            }
2933
2934
            if (!$notrigger && empty($error)) {
2935
                // Call trigger
2936
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2937
                if ($result < 0) {
2938
                    $error++;
2939
                }
2940
                // End call triggers
2941
            }
2942
            if (!$error) {
2943
                $this->db->commit();
2944
                return 1;
2945
            } else {
2946
                foreach ($this->errors as $errmsg) {
2947
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2948
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2949
                }
2950
                $this->db->rollback();
2951
                return -1 * $error;
2952
            }
2953
        } else {
2954
            return -1;
2955
        }
2956
    }
2957
2958
    /**
2959
     * Classify the order as invoiced
2960
     *
2961
     * @param   User    $user       Object user making the change
2962
     * @param   int     $notrigger  1=Does not execute triggers, 0=execute triggers
2963
     * @return  int                 Return integer <0 if KO, 0 if already billed,  >0 if OK
2964
     */
2965
    public function classifyBilled(User $user, $notrigger = 0)
2966
    {
2967
        $error = 0;
2968
2969
        if ($this->billed) {
2970
            return 0;
2971
        }
2972
2973
        $this->db->begin();
2974
2975
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande SET facture = 1';
2976
        $sql .= " WHERE rowid = " . ((int) $this->id) . ' AND fk_statut > ' . self::STATUS_DRAFT;
2977
2978
        dol_syslog(get_class($this) . "::classifyBilled", LOG_DEBUG);
2979
        if ($this->db->query($sql)) {
2980
            if (!$error) {
2981
                $this->oldcopy = clone $this;
2982
                $this->billed = 1;
2983
            }
2984
2985
            if (!$notrigger && empty($error)) {
2986
                // Call trigger
2987
                $result = $this->call_trigger('ORDER_CLASSIFY_BILLED', $user);
2988
                if ($result < 0) {
2989
                    $error++;
2990
                }
2991
                // End call triggers
2992
            }
2993
2994
            if (!$error) {
2995
                $this->db->commit();
2996
                return 1;
2997
            } else {
2998
                foreach ($this->errors as $errmsg) {
2999
                    dol_syslog(get_class($this) . "::classifyBilled " . $errmsg, LOG_ERR);
3000
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
3001
                }
3002
                $this->db->rollback();
3003
                return -1 * $error;
3004
            }
3005
        } else {
3006
            $this->error = $this->db->error();
3007
            $this->db->rollback();
3008
            return -1;
3009
        }
3010
    }
3011
3012
    /**
3013
     * Classify the order as not invoiced
3014
     *
3015
     * @param   User    $user       Object user making the change
3016
     * @param   int     $notrigger  1=Does not execute triggers, 0=execute triggers
3017
     * @return  int                 Return integer <0 if ko, >0 if ok
3018
     */
3019
    public function classifyUnBilled(User $user, $notrigger = 0)
3020
    {
3021
        $error = 0;
3022
3023
        $this->db->begin();
3024
3025
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande SET facture = 0';
3026
        $sql .= " WHERE rowid = " . ((int) $this->id) . ' AND fk_statut > ' . self::STATUS_DRAFT;
3027
3028
        dol_syslog(get_class($this) . "::classifyUnBilled", LOG_DEBUG);
3029
        if ($this->db->query($sql)) {
3030
            if (!$error) {
3031
                $this->oldcopy = clone $this;
3032
                $this->billed = 1;
3033
            }
3034
3035
            if (!$notrigger && empty($error)) {
3036
                // Call trigger
3037
                $result = $this->call_trigger('ORDER_CLASSIFY_UNBILLED', $user);
3038
                if ($result < 0) {
3039
                    $error++;
3040
                }
3041
                // End call triggers
3042
            }
3043
3044
            if (!$error) {
3045
                $this->billed = 0;
3046
3047
                $this->db->commit();
3048
                return 1;
3049
            } else {
3050
                foreach ($this->errors as $errmsg) {
3051
                    dol_syslog(get_class($this) . "::classifyUnBilled " . $errmsg, LOG_ERR);
3052
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
3053
                }
3054
                $this->db->rollback();
3055
                return -1 * $error;
3056
            }
3057
        } else {
3058
            $this->error = $this->db->error();
3059
            $this->db->rollback();
3060
            return -1;
3061
        }
3062
    }
3063
3064
3065
    /**
3066
     *  Update a line in database
3067
     *
3068
     *  @param      int             $rowid              Id of line to update
3069
     *  @param      string          $desc               Description of line
3070
     *  @param      float           $pu                 Unit price
3071
     *  @param      float           $qty                Quantity
3072
     *  @param      float           $remise_percent     Percent of discount
3073
     *  @param      float           $txtva              Taux TVA
3074
     *  @param      float           $txlocaltax1        Local tax 1 rate
3075
     *  @param      float           $txlocaltax2        Local tax 2 rate
3076
     *  @param      string          $price_base_type    HT or TTC
3077
     *  @param      int             $info_bits          Miscellaneous information on line
3078
     *  @param      int|string      $date_start         Start date of the line
3079
     *  @param      int|string      $date_end           End date of the line
3080
     *  @param      int             $type               Type of line (0=product, 1=service)
3081
     *  @param      int             $fk_parent_line     Id of parent line (0 in most cases, used by modules adding sublevels into lines).
3082
     *  @param      int             $skip_update_total  Keep fields total_xxx to 0 (used for special lines by some modules)
3083
     *  @param      int             $fk_fournprice      Id of origin supplier price
3084
     *  @param      int             $pa_ht              Price (without tax) of product when it was bought
3085
     *  @param      string          $label              Label
3086
     *  @param      int             $special_code       Special code (also used by externals modules!)
3087
     *  @param      array           $array_options      extrafields array
3088
     *  @param      int|null        $fk_unit            Code of the unit to use. Null to use the default one
3089
     *  @param      double          $pu_ht_devise       Amount in currency
3090
     *  @param      int             $notrigger          disable line update trigger
3091
     *  @param      string          $ref_ext            external reference
3092
     * @param       integer $rang   line rank
3093
     *  @return     int                                 Return integer < 0 if KO, > 0 if OK
3094
     */
3095
    public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $price_base_type = 'HT', $info_bits = 0, $date_start = '', $date_end = '', $type = 0, $fk_parent_line = 0, $skip_update_total = 0, $fk_fournprice = null, $pa_ht = 0, $label = '', $special_code = 0, $array_options = array(), $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $ref_ext = '', $rang = 0)
3096
    {
3097
        global $conf, $mysoc, $langs, $user;
3098
3099
        dol_syslog(get_class($this) . "::updateline id=$rowid, desc=$desc, pu=$pu, qty=$qty, remise_percent=$remise_percent, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, price_base_type=$price_base_type, info_bits=$info_bits, date_start=$date_start, date_end=$date_end, type=$type, fk_parent_line=$fk_parent_line, pa_ht=$pa_ht, special_code=$special_code, ref_ext=$ref_ext");
3100
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
3101
3102
        if ($this->statut == Commande::STATUS_DRAFT) {
3103
            // Clean parameters
3104
            if (empty($qty)) {
3105
                $qty = 0;
3106
            }
3107
            if (empty($info_bits)) {
3108
                $info_bits = 0;
3109
            }
3110
            if (empty($txtva)) {
3111
                $txtva = 0;
3112
            }
3113
            if (empty($txlocaltax1)) {
3114
                $txlocaltax1 = 0;
3115
            }
3116
            if (empty($txlocaltax2)) {
3117
                $txlocaltax2 = 0;
3118
            }
3119
            if (empty($remise_percent)) {
3120
                $remise_percent = 0;
3121
            }
3122
            if (empty($special_code) || $special_code == 3) {
3123
                $special_code = 0;
3124
            }
3125
            if (empty($ref_ext)) {
3126
                $ref_ext = '';
3127
            }
3128
3129
            if ($date_start && $date_end && $date_start > $date_end) {
3130
                $langs->load("errors");
3131
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3132
                return -1;
3133
            }
3134
3135
            $remise_percent = (float) price2num($remise_percent);
3136
            $qty = (float) price2num($qty);
3137
            $pu = price2num($pu);
3138
            $pa_ht = (float) price2num($pa_ht);
3139
            $pu_ht_devise = price2num($pu_ht_devise);
3140
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
3141
                $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3142
            }
3143
            $txlocaltax1 = (float) price2num($txlocaltax1);
3144
            $txlocaltax2 = (float) price2num($txlocaltax2);
3145
3146
            $this->db->begin();
3147
3148
            // Calcul du total TTC et de la TVA pour la ligne a partir de
3149
            // qty, pu, remise_percent et txtva
3150
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3151
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3152
3153
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3154
3155
            // Clean vat code
3156
            $vat_src_code = '';
3157
            $reg = array();
3158
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
3159
                $vat_src_code = $reg[1];
3160
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
3161
            }
3162
3163
            $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
3164
3165
            $total_ht = $tabprice[0];
3166
            $total_tva = $tabprice[1];
3167
            $total_ttc = $tabprice[2];
3168
            $total_localtax1 = $tabprice[9];
3169
            $total_localtax2 = $tabprice[10];
3170
            $pu_ht = $tabprice[3];
3171
            $pu_tva = $tabprice[4];
3172
            $pu_ttc = $tabprice[5];
3173
3174
            // MultiCurrency
3175
            $multicurrency_total_ht = $tabprice[16];
3176
            $multicurrency_total_tva = $tabprice[17];
3177
            $multicurrency_total_ttc = $tabprice[18];
3178
            $pu_ht_devise = $tabprice[19];
3179
3180
            // Anciens indicateurs: $price, $subprice (a ne plus utiliser)
3181
            $price = $pu_ht;
3182
            if ($price_base_type == 'TTC') {
3183
                $subprice = $pu_ttc;
3184
            } else {
3185
                $subprice = $pu_ht;
3186
            }
3187
            $remise = 0;
3188
            if ($remise_percent > 0) {
3189
                $remise = round(((float) $pu * $remise_percent / 100), 2);
3190
                $price = ((float) $pu - $remise);
3191
            }
3192
3193
            //Fetch current line from the database and then clone the object and set it in $oldline property
3194
            $line = new OrderLine($this->db);
3195
            $line->fetch($rowid);
3196
            $line->fetch_optionals();
3197
3198
            if (!empty($line->fk_product)) {
3199
                $product = new Product($this->db);
3200
                $result = $product->fetch($line->fk_product);
3201
                $product_type = $product->type;
3202
3203
                if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product_type == 0 && $product->stock_reel < $qty) {
3204
                    $langs->load("errors");
3205
                    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
3206
                    $this->errors[] = $this->error;
3207
3208
                    dol_syslog(get_class($this) . "::addline error=Product " . $product->ref . ": " . $this->error, LOG_ERR);
3209
3210
                    $this->db->rollback();
3211
                    return self::STOCK_NOT_ENOUGH_FOR_ORDER;
3212
                }
3213
            }
3214
3215
            $staticline = clone $line;
3216
3217
            $line->oldline = $staticline;
3218
            $this->line = $line;
3219
            $this->line->context = $this->context;
3220
            $this->line->rang = $rang;
3221
3222
            // Reorder if fk_parent_line change
3223
            if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
3224
                $rangmax = $this->line_max($fk_parent_line);
3225
                $this->line->rang = $rangmax + 1;
3226
            }
3227
3228
            $this->line->id = $rowid;
3229
            $this->line->label = $label;
3230
            $this->line->desc = $desc;
3231
            $this->line->qty = $qty;
3232
            $this->line->ref_ext = $ref_ext;
3233
3234
            $this->line->vat_src_code = $vat_src_code;
3235
            $this->line->tva_tx         = $txtva;
3236
            $this->line->localtax1_tx   = $txlocaltax1;
3237
            $this->line->localtax2_tx   = $txlocaltax2;
3238
            $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3239
            $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3240
            $this->line->remise_percent = $remise_percent;
3241
            $this->line->subprice       = $pu_ht;
3242
            $this->line->info_bits      = $info_bits;
3243
            $this->line->special_code   = $special_code;
3244
            $this->line->total_ht       = $total_ht;
3245
            $this->line->total_tva      = $total_tva;
3246
            $this->line->total_localtax1 = $total_localtax1;
3247
            $this->line->total_localtax2 = $total_localtax2;
3248
            $this->line->total_ttc      = $total_ttc;
3249
            $this->line->date_start     = $date_start;
3250
            $this->line->date_end       = $date_end;
3251
            $this->line->product_type   = $type;
3252
            $this->line->fk_parent_line = $fk_parent_line;
3253
            $this->line->skip_update_total = $skip_update_total;
3254
            $this->line->fk_unit        = $fk_unit;
3255
3256
            $this->line->fk_fournprice = $fk_fournprice;
3257
            $this->line->pa_ht = $pa_ht;
3258
3259
            // Multicurrency
3260
            $this->line->multicurrency_subprice     = $pu_ht_devise;
3261
            $this->line->multicurrency_total_ht     = $multicurrency_total_ht;
3262
            $this->line->multicurrency_total_tva    = $multicurrency_total_tva;
3263
            $this->line->multicurrency_total_ttc    = $multicurrency_total_ttc;
3264
3265
            // TODO deprecated
3266
            $this->line->price = $price;
3267
3268
            if (is_array($array_options) && count($array_options) > 0) {
3269
                // We replace values in this->line->array_options only for entries defined into $array_options
3270
                foreach ($array_options as $key => $value) {
3271
                    $this->line->array_options[$key] = $array_options[$key];
3272
                }
3273
            }
3274
3275
            $result = $this->line->update($user, $notrigger);
3276
            if ($result > 0) {
3277
                // Reorder if child line
3278
                if (!empty($fk_parent_line)) {
3279
                    $this->line_order(true, 'DESC');
3280
                }
3281
3282
                // Mise a jour info denormalisees
3283
                $this->update_price(1, 'auto');
3284
3285
                $this->db->commit();
3286
                return $result;
3287
            } else {
3288
                $this->error = $this->line->error;
3289
3290
                $this->db->rollback();
3291
                return -1;
3292
            }
3293
        } else {
3294
            $this->error = get_class($this) . "::updateline Order status makes operation forbidden";
3295
            $this->errors = array('OrderStatusMakeOperationForbidden');
3296
            return -2;
3297
        }
3298
    }
3299
3300
    /**
3301
     *      Update database
3302
     *
3303
     *      @param      User    $user           User that modify
3304
     *      @param      int     $notrigger      0=launch triggers after, 1=disable triggers
3305
     *      @return     int                     Return integer <0 if KO, >0 if OK
3306
     */
3307
    public function update(User $user, $notrigger = 0)
3308
    {
3309
        global $conf;
3310
3311
        $error = 0;
3312
3313
        // Clean parameters
3314
        if (isset($this->ref)) {
3315
            $this->ref = trim($this->ref);
3316
        }
3317
        if (isset($this->ref_client)) {
3318
            $this->ref_client = trim($this->ref_client);
3319
        }
3320
        if (isset($this->ref_customer)) {
3321
            $this->ref_customer = trim($this->ref_customer);
3322
        }
3323
        if (isset($this->note) || isset($this->note_private)) {
3324
            $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
3325
        }
3326
        if (isset($this->note_public)) {
3327
            $this->note_public = trim($this->note_public);
3328
        }
3329
        if (isset($this->model_pdf)) {
3330
            $this->model_pdf = trim($this->model_pdf);
3331
        }
3332
        if (isset($this->import_key)) {
3333
            $this->import_key = trim($this->import_key);
3334
        }
3335
        $delivery_date = $this->delivery_date;
3336
3337
        // Check parameters
3338
        // Put here code to add control on parameters values
3339
3340
        // Update request
3341
        $sql = "UPDATE " . MAIN_DB_PREFIX . "commande SET";
3342
3343
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
3344
        $sql .= " ref_client=" . (isset($this->ref_client) ? "'" . $this->db->escape($this->ref_client) . "'" : "null") . ",";
3345
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
3346
        $sql .= " fk_soc=" . (isset($this->socid) ? $this->socid : "null") . ",";
3347
        $sql .= " date_commande=" . (strval($this->date_commande) != '' ? "'" . $this->db->idate($this->date_commande) . "'" : 'null') . ",";
3348
        $sql .= " date_valid=" . (strval($this->date_validation) != '' ? "'" . $this->db->idate($this->date_validation) . "'" : 'null') . ",";
3349
        $sql .= " total_tva=" . (isset($this->total_tva) ? $this->total_tva : "null") . ",";
3350
        $sql .= " localtax1=" . (isset($this->total_localtax1) ? $this->total_localtax1 : "null") . ",";
3351
        $sql .= " localtax2=" . (isset($this->total_localtax2) ? $this->total_localtax2 : "null") . ",";
3352
        $sql .= " total_ht=" . (isset($this->total_ht) ? $this->total_ht : "null") . ",";
3353
        $sql .= " total_ttc=" . (isset($this->total_ttc) ? $this->total_ttc : "null") . ",";
3354
        $sql .= " fk_statut=" . (isset($this->statut) ? $this->statut : "null") . ",";
3355
        $sql .= " fk_user_modif=" . (isset($user->id) ? $user->id : "null") . ",";
3356
        $sql .= " fk_user_valid=" . ((isset($this->user_validation_id) && $this->user_validation_id > 0) ? $this->user_validation_id : "null") . ",";
3357
        $sql .= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
3358
        $sql .= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null") . ",";
3359
        $sql .= " deposit_percent=" . (!empty($this->deposit_percent) ? strval($this->deposit_percent) : "null") . ",";
3360
        $sql .= " fk_mode_reglement=" . (isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null") . ",";
3361
        $sql .= " date_livraison=" . (strval($this->delivery_date) != '' ? "'" . $this->db->idate($this->delivery_date) . "'" : 'null') . ",";
3362
        $sql .= " fk_shipping_method=" . (isset($this->shipping_method_id) ? $this->shipping_method_id : "null") . ",";
3363
        $sql .= " fk_account=" . ($this->fk_account > 0 ? $this->fk_account : "null") . ",";
3364
        $sql .= " fk_input_reason=" . ($this->demand_reason_id > 0 ? $this->demand_reason_id : "null") . ",";
3365
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
3366
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
3367
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
3368
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null");
3369
3370
        $sql .= " WHERE rowid=" . ((int) $this->id);
3371
3372
        $this->db->begin();
3373
3374
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
3375
        $resql = $this->db->query($sql);
3376
        if (!$resql) {
3377
            $error++;
3378
            $this->errors[] = "Error " . $this->db->lasterror();
3379
        }
3380
3381
        if (!$error) {
3382
            $result = $this->insertExtraFields();
3383
            if ($result < 0) {
3384
                $error++;
3385
            }
3386
        }
3387
3388
        if (!$error && !$notrigger) {
3389
            // Call trigger
3390
            $result = $this->call_trigger('ORDER_MODIFY', $user);
3391
            if ($result < 0) {
3392
                $error++;
3393
            }
3394
            // End call triggers
3395
        }
3396
3397
        // Commit or rollback
3398
        if ($error) {
3399
            foreach ($this->errors as $errmsg) {
3400
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
3401
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
3402
            }
3403
            $this->db->rollback();
3404
            return -1 * $error;
3405
        } else {
3406
            $this->db->commit();
3407
            return 1;
3408
        }
3409
    }
3410
3411
    /**
3412
     *  Delete the sales order
3413
     *
3414
     *  @param  User    $user       User object
3415
     *  @param  int     $notrigger  1=Does not execute triggers, 0= execute triggers
3416
     *  @return int                 Return integer <=0 if KO, >0 if OK
3417
     */
3418
    public function delete($user, $notrigger = 0)
3419
    {
3420
        global $conf, $langs;
3421
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
3422
3423
        $error = 0;
3424
3425
        dol_syslog(get_class($this) . "::delete " . $this->id, LOG_DEBUG);
3426
3427
        $this->db->begin();
3428
3429
        if (!$notrigger) {
3430
            // Call trigger
3431
            $result = $this->call_trigger('ORDER_DELETE', $user);
3432
            if ($result < 0) {
3433
                $error++;
3434
            }
3435
            // End call triggers
3436
        }
3437
3438
        // Test we can delete
3439
        if ($this->countNbOfShipments() != 0) {
3440
            $this->errors[] = $langs->trans('SomeShipmentExists');
3441
            $error++;
3442
        }
3443
3444
        // Delete extrafields of lines and lines
3445
        if (!$error && !empty($this->table_element_line)) {
3446
            $tabletodelete = $this->table_element_line;
3447
            $sqlef = "DELETE FROM " . MAIN_DB_PREFIX . $tabletodelete . "_extrafields WHERE fk_object IN (SELECT rowid FROM " . MAIN_DB_PREFIX . $tabletodelete . " WHERE " . $this->fk_element . " = " . ((int) $this->id) . ")";
3448
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $tabletodelete . " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3449
            if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3450
                $error++;
3451
                $this->error = $this->db->lasterror();
3452
                $this->errors[] = $this->error;
3453
                dol_syslog(get_class($this) . "::delete error " . $this->error, LOG_ERR);
3454
            }
3455
        }
3456
3457
        if (!$error) {
3458
            // Delete linked object
3459
            $res = $this->deleteObjectLinked();
3460
            if ($res < 0) {
3461
                $error++;
3462
            }
3463
        }
3464
3465
        if (!$error) {
3466
            // Delete linked contacts
3467
            $res = $this->delete_linked_contact();
3468
            if ($res < 0) {
3469
                $error++;
3470
            }
3471
        }
3472
3473
        // Removed extrafields of object
3474
        if (!$error) {
3475
            $result = $this->deleteExtraFields();
3476
            if ($result < 0) {
3477
                $error++;
3478
                dol_syslog(get_class($this) . "::delete error " . $this->error, LOG_ERR);
3479
            }
3480
        }
3481
3482
        // Delete main record
3483
        if (!$error) {
3484
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $this->table_element . " WHERE rowid = " . ((int) $this->id);
3485
            $res = $this->db->query($sql);
3486
            if (!$res) {
3487
                $error++;
3488
                $this->error = $this->db->lasterror();
3489
                $this->errors[] = $this->error;
3490
                dol_syslog(get_class($this) . "::delete error " . $this->error, LOG_ERR);
3491
            }
3492
        }
3493
3494
        // Delete record into ECM index and physically
3495
        if (!$error) {
3496
            $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3497
            $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3498
            if (!$res) {
3499
                $error++;
3500
            }
3501
        }
3502
3503
        if (!$error) {
3504
            // We remove directory
3505
            $ref = dol_sanitizeFileName($this->ref);
3506
            if ($conf->commande->multidir_output[$this->entity] && !empty($this->ref)) {
3507
                $dir = $conf->commande->multidir_output[$this->entity] . "/" . $ref;
3508
                $file = $dir . "/" . $ref . ".pdf";
3509
                if (file_exists($file)) {
3510
                    dol_delete_preview($this);
3511
3512
                    if (!dol_delete_file($file, 0, 0, 0, $this)) {
3513
                        $this->error = 'ErrorFailToDeleteFile';
3514
                        $this->errors[] = $this->error;
3515
                        $this->db->rollback();
3516
                        return 0;
3517
                    }
3518
                }
3519
                if (file_exists($dir)) {
3520
                    $res = @dol_delete_dir_recursive($dir);
3521
                    if (!$res) {
3522
                        $this->error = 'ErrorFailToDeleteDir';
3523
                        $this->errors[] = $this->error;
3524
                        $this->db->rollback();
3525
                        return 0;
3526
                    }
3527
                }
3528
            }
3529
        }
3530
3531
        if (!$error) {
3532
            dol_syslog(get_class($this) . "::delete " . $this->id . " by " . $user->id, LOG_DEBUG);
3533
            $this->db->commit();
3534
            return 1;
3535
        } else {
3536
            $this->db->rollback();
3537
            return -1;
3538
        }
3539
    }
3540
3541
3542
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3543
    /**
3544
     *  Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3545
     *
3546
     *  @param      User    $user   Object user
3547
     *  @param      string  $mode   Mode ('toship', 'tobill', 'shippedtobill')
3548
     *  @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
3549
     */
3550
    public function load_board($user, $mode)
3551
    {
3552
		// phpcs:enable
3553
        global $conf, $langs;
3554
3555
        $clause = " WHERE";
3556
3557
        $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.date_livraison as delivery_date, c.fk_statut, c.total_ht";
3558
        $sql .= " FROM " . MAIN_DB_PREFIX . "commande as c";
3559
        if (!$user->hasRight('societe', 'client', 'voir')) {
3560
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON c.fk_soc = sc.fk_soc";
3561
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
3562
            $clause = " AND";
3563
        }
3564
        $sql .= $clause . " c.entity IN (" . getEntity('commande') . ")";
3565
        //$sql.= " AND c.fk_statut IN (1,2,3) AND c.facture = 0";
3566
        if ($mode == 'toship') {
3567
            // An order to ship is an open order (validated or in progress)
3568
            $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ")";
3569
        }
3570
        if ($mode == 'tobill') {
3571
            // An order to bill is an order not already billed
3572
            $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ", " . self::STATUS_CLOSED . ") AND c.facture = 0";
3573
        }
3574
        if ($mode == 'shippedtobill') {
3575
            // An order shipped and to bill is a delivered order not already billed
3576
            $sql .= " AND c.fk_statut IN (" . self::STATUS_CLOSED . ") AND c.facture = 0";
3577
        }
3578
        if ($user->socid) {
3579
            $sql .= " AND c.fk_soc = " . ((int) $user->socid);
3580
        }
3581
3582
        $resql = $this->db->query($sql);
3583
        if ($resql) {
3584
            $delay_warning = 0;
3585
            $label = $labelShort = $url = '';
3586
            if ($mode == 'toship') {
3587
                $delay_warning = $conf->commande->client->warning_delay / 60 / 60 / 24;
3588
                $url = constant('BASE_URL') . '/commande/list.php?search_status=-2&mainmenu=commercial&leftmenu=orders';
3589
                $label = $langs->transnoentitiesnoconv("OrdersToProcess");
3590
                $labelShort = $langs->transnoentitiesnoconv("Opened");
3591
            }
3592
            if ($mode == 'tobill') {
3593
                $url = constant('BASE_URL') . '/commande/list.php?search_status=-3&search_billed=0&mainmenu=commercial&leftmenu=orders';
3594
                $label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
3595
                $labelShort = $langs->trans("ToBill");
3596
            }
3597
            if ($mode == 'shippedtobill') {
3598
                $url = constant('BASE_URL') . '/commande/list.php?search_status=3&search_billed=0&mainmenu=commercial&leftmenu=orders';
3599
                $label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
3600
                $labelShort = $langs->trans("StatusOrderDelivered") . ' ' . $langs->trans("and") . ' ' . $langs->trans("ToBill");
3601
            }
3602
3603
            $response = new WorkboardResponse();
3604
            $response->warning_delay = $delay_warning;
3605
            $response->label = $label;
3606
            $response->labelShort = $labelShort;
3607
            $response->url = $url;
3608
            $response->img = img_object('', "order");
3609
3610
            $generic_commande = new Commande($this->db);
3611
3612
            while ($obj = $this->db->fetch_object($resql)) {
3613
                $response->nbtodo++;
3614
                $response->total += $obj->total_ht;
3615
3616
                $generic_commande->statut = $obj->fk_statut;
3617
                $generic_commande->date_commande = $this->db->jdate($obj->date_commande);
3618
                $generic_commande->date = $this->db->jdate($obj->date_commande);
3619
                $generic_commande->delivery_date = $this->db->jdate($obj->delivery_date);
3620
3621
                if ($mode == 'toship' && $generic_commande->hasDelay()) {
3622
                    $response->nbtodolate++;
3623
                }
3624
            }
3625
3626
            return $response;
3627
        } else {
3628
            $this->error = $this->db->error();
3629
            return -1;
3630
        }
3631
    }
3632
3633
    /**
3634
     *  Return source label of order
3635
     *
3636
     *  @return     string      Label
3637
     */
3638
    public function getLabelSource()
3639
    {
3640
        global $langs;
3641
3642
        $label = $langs->trans('OrderSource' . $this->source);
3643
3644
        if ($label == 'OrderSource') {
3645
            return '';
3646
        }
3647
        return $label;
3648
    }
3649
3650
    /**
3651
     *  Return status label of Order
3652
     *
3653
     *  @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
3654
     *  @return     string              Label of status
3655
     */
3656
    public function getLibStatut($mode)
3657
    {
3658
        return $this->LibStatut($this->statut, $this->billed, $mode);
3659
    }
3660
3661
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3662
    /**
3663
     *  Return label of status
3664
     *
3665
     *  @param      int     $status           Id status
3666
     *  @param      int     $billed           If invoiced
3667
     *  @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
3668
     *  @param      int     $donotshowbilled  Do not show billed status after order status
3669
     *  @return     string                    Label of status
3670
     */
3671
    public function LibStatut($status, $billed, $mode, $donotshowbilled = 0)
3672
    {
3673
		// phpcs:enable
3674
        global $langs, $hookmanager;
3675
3676
        $billedtext = '';
3677
        if (empty($donotshowbilled)) {
3678
            $billedtext .= ($billed ? ' - ' . $langs->transnoentitiesnoconv("Billed") : '');
3679
        }
3680
3681
        $labelTooltip = '';
3682
3683
        if ($status == self::STATUS_CANCELED) {
3684
            $labelStatus = $langs->transnoentitiesnoconv('StatusOrderCanceled');
3685
            $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderCanceledShort');
3686
            $statusType = 'status9';
3687
        } elseif ($status == self::STATUS_DRAFT) {
3688
            $labelStatus = $langs->transnoentitiesnoconv('StatusOrderDraft');
3689
            $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDraftShort');
3690
            $statusType = 'status0';
3691
        } elseif ($status == self::STATUS_VALIDATED) {
3692
            $labelStatus = $langs->transnoentitiesnoconv('StatusOrderValidated') . $billedtext;
3693
            $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderValidatedShort') . $billedtext;
3694
            $statusType = 'status1';
3695
        } elseif ($status == self::STATUS_SHIPMENTONPROCESS) {
3696
            $labelStatus = $langs->transnoentitiesnoconv('StatusOrderSent') . $billedtext;
3697
            $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderSentShort') . $billedtext;
3698
            $labelTooltip = $langs->transnoentitiesnoconv("StatusOrderSent");
3699
            if (!empty($this->delivery_date)) {
3700
                $labelTooltip .= ' - ' . $langs->transnoentitiesnoconv("DateDeliveryPlanned") . dol_print_date($this->delivery_date, 'day') . $billedtext;
3701
            }
3702
            $statusType = 'status4';
3703
        } elseif ($status == self::STATUS_CLOSED) {
3704
            $labelStatus = $langs->transnoentitiesnoconv('StatusOrderDelivered') . $billedtext;
3705
            $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDeliveredShort') . $billedtext;
3706
            $statusType = 'status6';
3707
        } else {
3708
            $labelStatus = $langs->transnoentitiesnoconv('Unknown');
3709
            $labelStatusShort = '';
3710
            $statusType = '';
3711
            $mode = 0;
3712
        }
3713
3714
        $parameters = array(
3715
            'status'          => $status,
3716
            'mode'            => $mode,
3717
            'billed'          => $billed,
3718
            'donotshowbilled' => $donotshowbilled
3719
        );
3720
3721
        $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3722
3723
        if ($reshook > 0) {
3724
            return $hookmanager->resPrint;
3725
        }
3726
3727
        return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', array('tooltip' => $labelTooltip));
3728
    }
3729
3730
    /**
3731
     * getTooltipContentArray
3732
     *
3733
     * @param array $params params to construct tooltip data
3734
     * @return array
3735
     */
3736
    public function getTooltipContentArray($params)
3737
    {
3738
        global $conf, $langs, $user;
3739
3740
        $langs->load('orders');
3741
        $datas = [];
3742
        $nofetch = !empty($params['nofetch']);
3743
3744
        if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3745
            return ['optimize' => $langs->trans("Order")];
3746
        }
3747
3748
        if ($user->hasRight('commande', 'lire')) {
3749
            $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("Order") . '</u>';
3750
            if (isset($this->statut)) {
3751
                $datas['status'] = ' ' . $this->getLibStatut(5);
3752
            }
3753
            $datas['Ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
3754
            if (!$nofetch) {
3755
                $langs->load('companies');
3756
                if (empty($this->thirdparty)) {
3757
                    $this->fetch_thirdparty();
3758
                }
3759
                $datas['customer'] = '<br><b>' . $langs->trans('Customer') . ':</b> ' . $this->thirdparty->getNomUrl(1, '', 0, 1);
3760
            }
3761
            $datas['RefCustomer'] = '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . (empty($this->ref_customer) ? (empty($this->ref_client) ? '' : $this->ref_client) : $this->ref_customer);
3762
            if (!$nofetch) {
3763
                $langs->load('project');
3764
                if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3765
                    $res = $this->fetch_project();
3766
                    if ($res > 0 && $this->project instanceof Project) {
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Commande\Classes\Project was not found. Did you mean Project? If so, make sure to prefix the type with \.
Loading history...
3767
                        $datas['project'] = '<br><b>' . $langs->trans('Project') . ':</b> ' . $this->project->getNomUrl(1, '', 0, 1);
3768
                    }
3769
                }
3770
            }
3771
            if (!empty($this->total_ht)) {
3772
                $datas['AmountHT'] = '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3773
            }
3774
            if (!empty($this->total_tva)) {
3775
                $datas['VAT'] = '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3776
            }
3777
            if (!empty($this->total_ttc)) {
3778
                $datas['AmountTTC'] = '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3779
            }
3780
            if (!empty($this->date)) {
3781
                $datas['Date'] = '<br><b>' . $langs->trans('Date') . ':</b> ' . dol_print_date($this->date, 'day');
3782
            }
3783
            if (!empty($this->delivery_date)) {
3784
                $datas['DeliveryDate'] = '<br><b>' . $langs->trans('DeliveryDate') . ':</b> ' . dol_print_date($this->delivery_date, 'dayhour');
3785
            }
3786
        }
3787
3788
        return $datas;
3789
    }
3790
3791
    /**
3792
     *  Return clicable link of object (with eventually picto)
3793
     *
3794
     *  @param      int         $withpicto                Add picto into link
3795
     *  @param      string      $option                   Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3796
     *  @param      int         $max                      Max length to show
3797
     *  @param      int         $short                    ???
3798
     *  @param      int         $notooltip                1=Disable tooltip
3799
     *  @param      int         $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
3800
     *  @param      int         $addlinktonotes           Add link to notes
3801
     *  @param      string      $target                   attribute target for link
3802
     *  @return     string                                String with URL
3803
     */
3804
    public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0, $target = '')
3805
    {
3806
        global $conf, $langs, $user, $hookmanager;
3807
3808
        if (!empty($conf->dol_no_mouse_hover)) {
3809
            $notooltip = 1; // Force disable tooltips
3810
        }
3811
3812
        $result = '';
3813
3814
        if (isModEnabled("shipping") && ($option == '1' || $option == '2')) {
3815
            $url = constant('BASE_URL') . '/expedition/shipment.php?id=' . $this->id;
3816
        } else {
3817
            $url = constant('BASE_URL') . '/commande/card.php?id=' . $this->id;
3818
        }
3819
3820
        if (!$user->hasRight('commande', 'lire')) {
3821
            $option = 'nolink';
3822
        }
3823
3824
        if ($option !== 'nolink') {
3825
            // Add param to save lastsearch_values or not
3826
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3827
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3828
                $add_save_lastsearch_values = 1;
3829
            }
3830
            if ($add_save_lastsearch_values) {
3831
                $url .= '&save_lastsearch_values=1';
3832
            }
3833
        }
3834
3835
        if ($short) {
3836
            return $url;
3837
        }
3838
        $params = [
3839
            'id' => $this->id,
3840
            'objecttype' => $this->element,
3841
            'option' => $option,
3842
            'nofetch' => 1,
3843
        ];
3844
        $classfortooltip = 'classfortooltip';
3845
        $dataparams = '';
3846
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3847
            $classfortooltip = 'classforajaxtooltip';
3848
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
3849
            $label = '';
3850
        } else {
3851
            $label = implode($this->getTooltipContentArray($params));
3852
        }
3853
3854
        $linkclose = '';
3855
        if (empty($notooltip) && $user->hasRight('commande', 'lire')) {
3856
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3857
                $label = $langs->trans("Order");
3858
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
3859
            }
3860
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
3861
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
3862
3863
            $target_value = array('_self', '_blank', '_parent', '_top');
3864
            if (in_array($target, $target_value)) {
3865
                $linkclose .= ' target="' . dol_escape_htmltag($target) . '"';
3866
            }
3867
        }
3868
3869
        $linkstart = '<a href="' . $url . '"';
3870
        $linkstart .= $linkclose . '>';
3871
        $linkend = '</a>';
3872
3873
        if ($option === 'nolink') {
3874
            $linkstart = '';
3875
            $linkend = '';
3876
        }
3877
3878
        $result .= $linkstart;
3879
        if ($withpicto) {
3880
            $result .= img_object(($notooltip ? '' : $label), $this->picto, (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3881
        }
3882
        if ($withpicto != 2) {
3883
            $result .= $this->ref;
3884
        }
3885
        $result .= $linkend;
3886
3887
        if ($addlinktonotes) {
3888
            $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
3889
            if ($txttoshow) {
3890
                $notetoshow = $langs->trans("ViewPrivateNote") . ':<br>' . dol_string_nohtmltag($txttoshow, 1);
3891
                $result .= ' <span class="note inline-block">';
3892
                $result .= '<a href="' . constant('BASE_URL') . '/commande/note.php?id=' . $this->id . '" class="classfortooltip" title="' . dol_escape_htmltag($notetoshow) . '">';
3893
                $result .= img_picto('', 'note');
3894
                $result .= '</a>';
3895
                //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
3896
                //$result.='</a>';
3897
                $result .= '</span>';
3898
            }
3899
        }
3900
3901
        global $action;
3902
        $hookmanager->initHooks(array($this->element . 'dao'));
3903
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
3904
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3905
        if ($reshook > 0) {
3906
            $result = $hookmanager->resPrint;
3907
        } else {
3908
            $result .= $hookmanager->resPrint;
3909
        }
3910
        return $result;
3911
    }
3912
3913
3914
    /**
3915
     *  Charge les information d'ordre info dans l'objet commande
3916
     *
3917
     *  @param  int     $id       Id of order
3918
     *  @return void
3919
     */
3920
    public function info($id)
3921
    {
3922
        $sql = 'SELECT c.rowid, date_creation as datec, tms as datem,';
3923
        $sql .= ' date_valid as datev,';
3924
        $sql .= ' date_cloture as datecloture,';
3925
        $sql .= ' fk_user_author, fk_user_valid, fk_user_cloture';
3926
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'commande as c';
3927
        $sql .= ' WHERE c.rowid = ' . ((int) $id);
3928
        $result = $this->db->query($sql);
3929
        if ($result) {
3930
            if ($this->db->num_rows($result)) {
3931
                $obj = $this->db->fetch_object($result);
3932
                $this->id = $obj->rowid;
3933
                if ($obj->fk_user_author) {
3934
                    $this->user_creation_id = $obj->fk_user_author;
3935
                }
3936
                if ($obj->fk_user_valid) {
3937
                    $this->user_validation_id = $obj->fk_user_valid;
3938
                }
3939
                if ($obj->fk_user_cloture) {
3940
                    $this->user_closing_id = $obj->fk_user_cloture;
3941
                }
3942
3943
                $this->date_creation     = $this->db->jdate($obj->datec);
3944
                $this->date_modification = $this->db->jdate($obj->datem);
3945
                $this->date_validation   = $this->db->jdate($obj->datev);
3946
                $this->date_cloture      = $this->db->jdate($obj->datecloture);
3947
            }
3948
3949
            $this->db->free($result);
3950
        } else {
3951
            dol_print_error($this->db);
3952
        }
3953
    }
3954
3955
3956
    /**
3957
     *  Initialise an instance with random values.
3958
     *  Used to build previews or test instances.
3959
     *  id must be 0 if object instance is a specimen.
3960
     *
3961
     *  @return int
3962
     */
3963
    public function initAsSpecimen()
3964
    {
3965
        global $conf, $langs;
3966
3967
        dol_syslog(get_class($this) . "::initAsSpecimen");
3968
3969
        // Load array of products prodids
3970
        $num_prods = 0;
3971
        $prodids = array();
3972
        $sql = "SELECT rowid";
3973
        $sql .= " FROM " . MAIN_DB_PREFIX . "product";
3974
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
3975
        $sql .= $this->db->plimit(100);
3976
3977
        $resql = $this->db->query($sql);
3978
        if ($resql) {
3979
            $num_prods = $this->db->num_rows($resql);
3980
            $i = 0;
3981
            while ($i < $num_prods) {
3982
                $i++;
3983
                $row = $this->db->fetch_row($resql);
3984
                $prodids[$i] = $row[0];
3985
            }
3986
        }
3987
3988
        // Initialise parameters
3989
        $this->id = 0;
3990
        $this->ref = 'SPECIMEN';
3991
        $this->specimen = 1;
3992
        $this->entity = $conf->entity;
3993
        $this->socid = 1;
3994
        $this->date = time();
3995
        $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3996
        $this->cond_reglement_code = 'RECEP';
3997
        $this->mode_reglement_code = 'CHQ';
3998
        $this->availability_code   = 'DSP';
3999
        $this->demand_reason_code  = 'SRC_00';
4000
4001
        $this->note_public = 'This is a comment (public)';
4002
        $this->note_private = 'This is a comment (private)';
4003
4004
        $this->multicurrency_tx = 1;
4005
        $this->multicurrency_code = $conf->currency;
4006
4007
        $this->status = $this::STATUS_DRAFT;
4008
4009
        // Lines
4010
        $nbp = 5;
4011
        $xnbp = 0;
4012
        while ($xnbp < $nbp) {
4013
            $line = new OrderLine($this->db);
4014
4015
            $line->desc = $langs->trans("Description") . " " . $xnbp;
4016
            $line->qty = 1;
4017
            $line->subprice = 100;
4018
            $line->price = 100;
4019
            $line->tva_tx = 20;
4020
            if ($xnbp == 2) {
4021
                $line->total_ht = 50;
4022
                $line->total_ttc = 60;
4023
                $line->total_tva = 10;
4024
                $line->remise_percent = 50;
4025
            } else {
4026
                $line->total_ht = 100;
4027
                $line->total_ttc = 120;
4028
                $line->total_tva = 20;
4029
                $line->remise_percent = 0;
4030
            }
4031
            if ($num_prods > 0) {
4032
                $prodid = mt_rand(1, $num_prods);
4033
                $line->fk_product = $prodids[$prodid];
4034
                $line->product_ref = 'SPECIMEN';
4035
            }
4036
4037
            $this->lines[$xnbp] = $line;
4038
4039
            $this->total_ht       += $line->total_ht;
4040
            $this->total_tva      += $line->total_tva;
4041
            $this->total_ttc      += $line->total_ttc;
4042
4043
            $xnbp++;
4044
        }
4045
4046
        return 1;
4047
    }
4048
4049
4050
    /**
4051
     *  Load the indicators this->nb for the state board
4052
     *
4053
     *  @return     int         Return integer <0 if KO, >0 if OK
4054
     */
4055
    public function loadStateBoard()
4056
    {
4057
        global $user;
4058
4059
        $this->nb = array();
4060
        $clause = "WHERE";
4061
4062
        $sql = "SELECT count(co.rowid) as nb";
4063
        $sql .= " FROM " . MAIN_DB_PREFIX . "commande as co";
4064
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON co.fk_soc = s.rowid";
4065
        if (!$user->hasRight('societe', 'client', 'voir')) {
4066
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4067
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
4068
            $clause = "AND";
4069
        }
4070
        $sql .= " " . $clause . " co.entity IN (" . getEntity('commande') . ")";
4071
4072
        $resql = $this->db->query($sql);
4073
        if ($resql) {
4074
            while ($obj = $this->db->fetch_object($resql)) {
4075
                $this->nb["orders"] = $obj->nb;
4076
            }
4077
            $this->db->free($resql);
4078
            return 1;
4079
        } else {
4080
            dol_print_error($this->db);
4081
            $this->error = $this->db->error();
4082
            return -1;
4083
        }
4084
    }
4085
4086
    /**
4087
     *  Create an array of order lines
4088
     *
4089
     *  @return int     >0 if OK, <0 if KO
4090
     */
4091
    public function getLinesArray()
4092
    {
4093
        return $this->fetch_lines();
4094
    }
4095
4096
    /**
4097
     *  Create a document onto disk according to template module.
4098
     *
4099
     *  @param      string      $modele         Force template to use ('' to not force)
4100
     *  @param      Translate   $outputlangs    object lang a utiliser pour traduction
4101
     *  @param      int         $hidedetails    Hide details of lines
4102
     *  @param      int         $hidedesc       Hide description
4103
     *  @param      int         $hideref        Hide ref
4104
     *  @param      null|array  $moreparams     Array to provide more information
4105
     *  @return     int                         0 if KO, 1 if OK
4106
     */
4107
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4108
    {
4109
        global $conf, $langs;
4110
4111
        $langs->load("orders");
4112
        $outputlangs->load("products");
4113
4114
        if (!dol_strlen($modele)) {
4115
            $modele = 'einstein';
4116
4117
            if (!empty($this->model_pdf)) {
4118
                $modele = $this->model_pdf;
4119
            } elseif (getDolGlobalString('COMMANDE_ADDON_PDF')) {
4120
                $modele = getDolGlobalString('COMMANDE_ADDON_PDF');
4121
            }
4122
        }
4123
4124
        $modelpath = "core/modules/commande/doc/";
4125
4126
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4127
    }
4128
4129
4130
    /**
4131
     * Function used to replace a thirdparty id with another one.
4132
     *
4133
     * @param   DoliDB  $dbs        Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
4134
     * @param   int     $origin_id  Old thirdparty id
4135
     * @param   int     $dest_id    New thirdparty id
4136
     * @return  bool
4137
     */
4138
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4139
    {
4140
        $tables = array(
4141
        'commande'
4142
        );
4143
4144
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4145
    }
4146
4147
    /**
4148
     * Function used to replace a product id with another one.
4149
     *
4150
     * @param DoliDB $db Database handler
4151
     * @param int $origin_id Old product id
4152
     * @param int $dest_id New product id
4153
     * @return bool
4154
     */
4155
    public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4156
    {
4157
        $tables = array(
4158
            'commandedet',
4159
        );
4160
4161
        return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4162
    }
4163
4164
    /**
4165
     * Is the sales order delayed?
4166
     *
4167
     * @return bool     true if late, false if not
4168
     */
4169
    public function hasDelay()
4170
    {
4171
        global $conf;
4172
4173
        if (!($this->statut > Commande::STATUS_DRAFT && $this->statut < Commande::STATUS_CLOSED)) {
4174
            return false; // Never late if not inside this status range
4175
        }
4176
4177
        $now = dol_now();
4178
4179
        return max($this->date, $this->delivery_date) < ($now - $conf->commande->client->warning_delay);
4180
    }
4181
4182
    /**
4183
     * Show the customer delayed info
4184
     *
4185
     * @return string       Show delayed information
4186
     */
4187
    public function showDelay()
4188
    {
4189
        global $conf, $langs;
4190
4191
        if (empty($this->delivery_date)) {
4192
            $text = $langs->trans("OrderDate") . ' ' . dol_print_date($this->date, 'day');
4193
        } else {
4194
            $text = $text = $langs->trans("DeliveryDate") . ' ' . dol_print_date($this->delivery_date, 'day');
4195
        }
4196
        $text .= ' ' . ($conf->commande->client->warning_delay > 0 ? '+' : '-') . ' ' . round(abs($conf->commande->client->warning_delay) / 3600 / 24, 1) . ' ' . $langs->trans("days") . ' < ' . $langs->trans("Today");
4197
4198
        return $text;
4199
    }
4200
}
4201