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

Commande   F

Complexity

Total Complexity 600

Size/Duplication

Total Lines 4142
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 2179
dl 0
loc 4142
rs 0.8
c 0
b 0
f 0
wmc 600

50 Methods

Rating   Name   Duplication   Size   Complexity  
B setDiscount() 0 51 11
A hasDelay() 0 11 3
B loadExpeditions() 0 41 7
B info() 0 32 6
B deleteLine() 0 41 6
A loadStateBoard() 0 28 4
A getNbOfShipments() 0 25 3
B set_date() 0 46 11
A set_date_livraison() 0 4 1
F updateline() 0 202 32
B setDeliveryDate() 0 45 11
D update() 0 101 46
B initAsSpecimen() 0 84 6
F create() 0 290 74
B fetch_lines() 0 122 7
A getNbOfProductsLines() 0 9 3
C liste_array() 0 57 12
D createFromClone() 0 99 20
A replaceThirdparty() 0 7 1
D getNomUrl() 0 107 29
B add_product() 0 58 8
B cloture() 0 51 10
C setDraft() 0 77 16
A __construct() 0 6 1
B classifyBilled() 0 44 10
B insert_discount() 0 58 5
A getLabelSource() 0 10 2
A getNbOfServicesLines() 0 9 3
A generateDocument() 0 20 4
B classifyUnBilled() 0 42 9
B set_ref_client() 0 46 11
A replaceProduct() 0 7 1
A set_remise() 0 6 1
B LibStatut() 0 57 10
D getTooltipContentArray() 0 53 19
C fetch() 0 144 13
C cancel() 0 61 12
F valid() 0 152 32
B set_reopen() 0 45 8
A countNbOfShipments() 0 19 2
B getNextNumRef() 0 38 6
F addline() 0 236 40
F createFromProposal() 0 147 20
C load_board() 0 80 13
D delete() 0 120 26
B availability() 0 52 10
A getLinesArray() 0 3 1
B demand_reason() 0 53 10
A getLibStatut() 0 3 1
A showDelay() 0 12 3

How to fix   Complexity   

Complex Class

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

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

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

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

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

522
                        /** @scrutinizer ignore-deprecated */ $mouvP->origin = &$this;

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...
523
                        $mouvP->setOrigin($this->element, $this->id);
524
                        // We decrement stock of product (and sub-products)
525
                        $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("OrderValidatedInDolibarr", $num));
526
                        if ($result < 0) {
527
                            $error++;
528
                            $this->error = $mouvP->error;
529
                        }
530
                    }
531
                    if ($error) {
532
                        break;
533
                    }
534
                }
535
            }
536
        }
537
538
        if (!$error && !$notrigger) {
539
            // Call trigger
540
            $result = $this->call_trigger('ORDER_VALIDATE', $user);
541
            if ($result < 0) {
542
                $error++;
543
            }
544
            // End call triggers
545
        }
546
547
        if (!$error) {
548
            $this->oldref = $this->ref;
549
550
            // Rename directory if dir was a temporary ref
551
            if (preg_match('/^[\(]?PROV/i', $this->ref)) {
552
                // Now we rename also files into index
553
                $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) . "'";
554
                $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'commande/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
555
                $resql = $this->db->query($sql);
556
                if (!$resql) {
557
                    $error++;
558
                    $this->error = $this->db->lasterror();
559
                }
560
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'commande/" . $this->db->escape($this->newref) . "'";
561
                $sql .= " WHERE filepath = 'commande/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
562
                $resql = $this->db->query($sql);
563
                if (!$resql) {
564
                    $error++;
565
                    $this->error = $this->db->lasterror();
566
                }
567
568
                // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
569
                $oldref = dol_sanitizeFileName($this->ref);
570
                $newref = dol_sanitizeFileName($num);
571
                $dirsource = $conf->commande->multidir_output[$this->entity] . '/' . $oldref;
572
                $dirdest = $conf->commande->multidir_output[$this->entity] . '/' . $newref;
573
                if (!$error && file_exists($dirsource)) {
574
                    dol_syslog(get_class($this) . "::valid rename dir " . $dirsource . " into " . $dirdest);
575
576
                    if (@rename($dirsource, $dirdest)) {
577
                        dol_syslog("Rename ok");
578
                        // Rename docs starting with $oldref with $newref
579
                        $listoffiles = dol_dir_list($conf->commande->multidir_output[$this->entity] . '/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
580
                        foreach ($listoffiles as $fileentry) {
581
                            $dirsource = $fileentry['name'];
582
                            $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
583
                            $dirsource = $fileentry['path'] . '/' . $dirsource;
584
                            $dirdest = $fileentry['path'] . '/' . $dirdest;
585
                            @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

585
                            /** @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...
586
                        }
587
                    }
588
                }
589
            }
590
        }
591
592
        // Set new ref and current status
593
        if (!$error) {
594
            $this->ref = $num;
595
            $this->statut = self::STATUS_VALIDATED; // deprecated
596
            $this->status = self::STATUS_VALIDATED;
597
        }
598
599
        if (!$error) {
600
            $this->db->commit();
601
            return 1;
602
        } else {
603
            $this->db->rollback();
604
            return -1;
605
        }
606
    }
607
608
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
609
    /**
610
     *  Set draft status
611
     *
612
     *  @param  User    $user           Object user that modify
613
     *  @param  int     $idwarehouse    Warehouse ID to use for stock change (Used only if option STOCK_CALCULATE_ON_VALIDATE_ORDER is on)
614
     *  @return int                     Return integer <0 if KO, >0 if OK
615
     */
616
    public function setDraft($user, $idwarehouse = -1)
617
    {
618
		//phpcs:enable
619
        global $conf, $langs;
620
621
        $error = 0;
622
623
        // Protection
624
        if ($this->statut <= self::STATUS_DRAFT) {
625
            return 0;
626
        }
627
628
        if (
629
            !((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
630
            || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'validate')))
631
        ) {
632
            $this->error = 'Permission denied';
633
            return -1;
634
        }
635
636
        dol_syslog(__METHOD__, LOG_DEBUG);
637
638
        $this->db->begin();
639
640
        $sql = "UPDATE " . MAIN_DB_PREFIX . "commande";
641
        $sql .= " SET fk_statut = " . self::STATUS_DRAFT . ",";
642
        $sql .= " fk_user_modif = " . ((int) $user->id);
643
        $sql .= " WHERE rowid = " . ((int) $this->id);
644
645
        if ($this->db->query($sql)) {
646
            if (!$error) {
647
                $this->oldcopy = clone $this;
648
            }
649
650
            // If stock is decremented on validate order, we must reincrement it
651
            if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
652
                $result = 0;
653
654
                $langs->load("agenda");
655
656
                $num = count($this->lines);
657
                for ($i = 0; $i < $num; $i++) {
658
                    if ($this->lines[$i]->fk_product > 0) {
659
                        $mouvP = new MouvementStock($this->db);
660
                        $mouvP->origin = &$this;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

660
                        /** @scrutinizer ignore-deprecated */ $mouvP->origin = &$this;

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...
661
                        $mouvP->setOrigin($this->element, $this->id);
662
                        // We increment stock of product (and sub-products)
663
                        $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("OrderBackToDraftInDolibarr", $this->ref));
664
                        if ($result < 0) {
665
                            $error++;
666
                            $this->error = $mouvP->error;
667
                            break;
668
                        }
669
                    }
670
                }
671
            }
672
673
            if (!$error) {
674
                // Call trigger
675
                $result = $this->call_trigger('ORDER_UNVALIDATE', $user);
676
                if ($result < 0) {
677
                    $error++;
678
                }
679
            }
680
681
            if (!$error) {
682
                $this->statut = self::STATUS_DRAFT;
683
                $this->db->commit();
684
                return 1;
685
            } else {
686
                $this->db->rollback();
687
                return -1;
688
            }
689
        } else {
690
            $this->error = $this->db->error();
691
            $this->db->rollback();
692
            return -1;
693
        }
694
    }
695
696
697
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
698
    /**
699
     *  Tag the order as validated (opened)
700
     *  Function used when order is reopend after being closed.
701
     *
702
     *  @param      User    $user       Object user that change status
703
     *  @return     int                 Return integer <0 if KO, 0 if nothing is done, >0 if OK
704
     */
705
    public function set_reopen($user)
706
    {
707
		// phpcs:enable
708
        $error = 0;
709
710
        if ($this->statut != self::STATUS_CANCELED && $this->statut != self::STATUS_CLOSED) {
711
            dol_syslog(get_class($this) . "::set_reopen order has not status closed", LOG_WARNING);
712
            return 0;
713
        }
714
715
        $this->db->begin();
716
717
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande';
718
        $sql .= ' SET fk_statut=' . self::STATUS_VALIDATED . ', facture=0,';
719
        $sql .= " fk_user_modif = " . ((int) $user->id);
720
        $sql .= " WHERE rowid = " . ((int) $this->id);
721
722
        dol_syslog(get_class($this) . "::set_reopen", LOG_DEBUG);
723
        $resql = $this->db->query($sql);
724
        if ($resql) {
725
            // Call trigger
726
            $result = $this->call_trigger('ORDER_REOPEN', $user);
727
            if ($result < 0) {
728
                $error++;
729
            }
730
            // End call triggers
731
        } else {
732
            $error++;
733
            $this->error = $this->db->lasterror();
734
            dol_print_error($this->db);
735
        }
736
737
        if (!$error) {
738
            $this->statut = self::STATUS_VALIDATED;
739
            $this->billed = 0;
740
741
            $this->db->commit();
742
            return 1;
743
        } else {
744
            foreach ($this->errors as $errmsg) {
745
                dol_syslog(get_class($this) . "::set_reopen " . $errmsg, LOG_ERR);
746
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
747
            }
748
            $this->db->rollback();
749
            return -1 * $error;
750
        }
751
    }
752
753
    /**
754
     *  Close order
755
     *
756
     *  @param      User    $user       Object user that close
757
     *  @param      int     $notrigger  1=Does not execute triggers, 0=Execute triggers
758
     *  @return     int                 Return integer <0 if KO, >0 if OK
759
     */
760
    public function cloture($user, $notrigger = 0)
761
    {
762
        global $conf;
763
764
        $error = 0;
765
766
        $usercanclose = ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
767
            || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'close')));
768
769
        if ($usercanclose) {
770
            if ($this->statut == self::STATUS_CLOSED) {
771
                return 0;
772
            }
773
            $this->db->begin();
774
775
            $now = dol_now();
776
777
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
778
            $sql .= ' SET fk_statut = ' . self::STATUS_CLOSED . ',';
779
            $sql .= ' fk_user_cloture = ' . ((int) $user->id) . ',';
780
            $sql .= " date_cloture = '" . $this->db->idate($now) . "',";
781
            $sql .= " fk_user_modif = " . ((int) $user->id);
782
            $sql .= " WHERE rowid = " . ((int) $this->id) . ' AND fk_statut > ' . self::STATUS_DRAFT;
783
784
            if ($this->db->query($sql)) {
785
                if (!$notrigger) {
786
                    // Call trigger
787
                    $result = $this->call_trigger('ORDER_CLOSE', $user);
788
                    if ($result < 0) {
789
                        $error++;
790
                    }
791
                    // End call triggers
792
                }
793
794
                if (!$error) {
795
                    $this->statut = self::STATUS_CLOSED;
796
797
                    $this->db->commit();
798
                    return 1;
799
                } else {
800
                    $this->db->rollback();
801
                    return -1;
802
                }
803
            } else {
804
                $this->error = $this->db->lasterror();
805
806
                $this->db->rollback();
807
                return -1;
808
            }
809
        }
810
        return 0;
811
    }
812
813
    /**
814
     *  Cancel an order
815
     *  If stock is decremented on order validation, we must reincrement it
816
     *
817
     *  @param  int     $idwarehouse    Id warehouse to use for stock change.
818
     *  @return int                     Return integer <0 if KO, >0 if OK
819
     */
820
    public function cancel($idwarehouse = -1)
821
    {
822
        global $conf, $user, $langs;
823
824
        $error = 0;
825
826
        $this->db->begin();
827
828
        $sql = "UPDATE " . MAIN_DB_PREFIX . "commande";
829
        $sql .= " SET fk_statut = " . self::STATUS_CANCELED . ",";
830
        $sql .= " fk_user_modif = " . ((int) $user->id);
831
        $sql .= " WHERE rowid = " . ((int) $this->id);
832
        $sql .= " AND fk_statut = " . self::STATUS_VALIDATED;
833
834
        dol_syslog(get_class($this) . "::cancel", LOG_DEBUG);
835
        if ($this->db->query($sql)) {
836
            // If stock is decremented on validate order, we must reincrement it
837
            if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
838
                $langs->load("agenda");
839
840
                $num = count($this->lines);
841
                for ($i = 0; $i < $num; $i++) {
842
                    if ($this->lines[$i]->fk_product > 0) {
843
                        $mouvP = new MouvementStock($this->db);
844
                        $mouvP->setOrigin($this->element, $this->id);
845
                        // We increment stock of product (and sub-products)
846
                        $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
847
                        if ($result < 0) {
848
                            $error++;
849
                            $this->error = $mouvP->error;
850
                            break;
851
                        }
852
                    }
853
                }
854
            }
855
856
            if (!$error) {
857
                // Call trigger
858
                $result = $this->call_trigger('ORDER_CANCEL', $user);
859
                if ($result < 0) {
860
                    $error++;
861
                }
862
                // End call triggers
863
            }
864
865
            if (!$error) {
866
                $this->statut = self::STATUS_CANCELED;
867
                $this->db->commit();
868
                return 1;
869
            } else {
870
                foreach ($this->errors as $errmsg) {
871
                    dol_syslog(get_class($this) . "::cancel " . $errmsg, LOG_ERR);
872
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
873
                }
874
                $this->db->rollback();
875
                return -1 * $error;
876
            }
877
        } else {
878
            $this->error = $this->db->error();
879
            $this->db->rollback();
880
            return -1;
881
        }
882
    }
883
884
    /**
885
     *  Create order
886
     *  Note that this->ref can be set or empty. If empty, we will use "(PROV)"
887
     *
888
     *  @param      User    $user       Object user that make creation
889
     *  @param      int     $notrigger  Disable all triggers
890
     *  @return     int                 Return integer <0 if KO, >0 if OK
891
     */
892
    public function create($user, $notrigger = 0)
893
    {
894
        global $conf, $langs, $mysoc;
895
        $error = 0;
896
897
        // Clean parameters
898
899
        // Set tmp vars
900
        $date = ($this->date_commande ? $this->date_commande : $this->date);
901
        $delivery_date = $this->delivery_date;
902
903
        // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
904
        if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
905
            list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
906
        } else {
907
            $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
908
        }
909
        if (empty($this->fk_multicurrency)) {
910
            $this->multicurrency_code = $conf->currency;
911
            $this->fk_multicurrency = 0;
912
            $this->multicurrency_tx = 1;
913
        }
914
915
        dol_syslog(get_class($this) . "::create user=" . $user->id);
916
917
        // Check parameters
918
        if (!empty($this->ref)) {   // We check that ref is not already used
919
            $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
920
            if ($result > 0) {
921
                $this->error = 'ErrorRefAlreadyExists';
922
                dol_syslog(get_class($this) . "::create " . $this->error, LOG_WARNING);
923
                $this->db->rollback();
924
                return -1;
925
            }
926
        }
927
928
        $soc = new Societe($this->db);
929
        $result = $soc->fetch($this->socid);
930
        if ($result < 0) {
931
            $this->error = "Failed to fetch company";
932
            dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
933
            return -2;
934
        }
935
        if (getDolGlobalString('ORDER_REQUIRE_SOURCE') && $this->source < 0) {
936
            $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Source"));
937
            dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
938
            return -1;
939
        }
940
941
        $now = dol_now();
942
943
        $this->db->begin();
944
945
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "commande (";
946
        $sql .= " ref, fk_soc, date_creation, fk_user_author, fk_projet, date_commande, source, note_private, note_public, ref_ext, ref_client";
947
        $sql .= ", model_pdf, fk_cond_reglement, deposit_percent, fk_mode_reglement, fk_account, fk_availability, fk_input_reason, date_livraison, fk_delivery_address";
948
        $sql .= ", fk_shipping_method";
949
        $sql .= ", fk_warehouse";
950
        $sql .= ", fk_incoterms, location_incoterms";
951
        $sql .= ", entity, module_source, pos_source";
952
        $sql .= ", fk_multicurrency";
953
        $sql .= ", multicurrency_code";
954
        $sql .= ", multicurrency_tx";
955
        $sql .= ")";
956
        $sql .= " VALUES ('(PROV)', " . ((int) $this->socid) . ", '" . $this->db->idate($now) . "', " . ((int) $user->id);
957
        $sql .= ", " . ($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
958
        $sql .= ", '" . $this->db->idate($date) . "'";
959
        $sql .= ", " . ($this->source >= 0 && $this->source != '' ? $this->db->escape($this->source) : 'null');
960
        $sql .= ", '" . $this->db->escape($this->note_private) . "'";
961
        $sql .= ", '" . $this->db->escape($this->note_public) . "'";
962
        $sql .= ", " . ($this->ref_ext ? "'" . $this->db->escape($this->ref_ext) . "'" : "null");
963
        $sql .= ", " . ($this->ref_client ? "'" . $this->db->escape($this->ref_client) . "'" : "null");
964
        $sql .= ", '" . $this->db->escape($this->model_pdf) . "'";
965
        $sql .= ", " . ($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
966
        $sql .= ", " . (!empty($this->deposit_percent) ? "'" . $this->db->escape($this->deposit_percent) . "'" : "null");
967
        $sql .= ", " . ($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
968
        $sql .= ", " . ($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
969
        $sql .= ", " . ($this->availability_id > 0 ? ((int) $this->availability_id) : "null");
970
        $sql .= ", " . ($this->demand_reason_id > 0 ? ((int) $this->demand_reason_id) : "null");
971
        $sql .= ", " . ($delivery_date ? "'" . $this->db->idate($delivery_date) . "'" : "null");
972
        $sql .= ", " . ($this->fk_delivery_address > 0 ? ((int) $this->fk_delivery_address) : 'NULL');
973
        $sql .= ", " . (!empty($this->shipping_method_id) && $this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : 'NULL');
974
        $sql .= ", " . (!empty($this->warehouse_id) && $this->warehouse_id > 0 ? ((int) $this->warehouse_id) : 'NULL');
975
        $sql .= ", " . (int) $this->fk_incoterms;
976
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
977
        $sql .= ", " . setEntity($this);
978
        $sql .= ", " . ($this->module_source ? "'" . $this->db->escape($this->module_source) . "'" : "null");
979
        $sql .= ", " . ($this->pos_source != '' ? "'" . $this->db->escape($this->pos_source) . "'" : "null");
980
        $sql .= ", " . (int) $this->fk_multicurrency;
981
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
982
        $sql .= ", " . (float) $this->multicurrency_tx;
983
        $sql .= ")";
984
985
        dol_syslog(get_class($this) . "::create", LOG_DEBUG);
986
        $resql = $this->db->query($sql);
987
        if ($resql) {
988
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . 'commande');
989
990
            if ($this->id) {
991
                $fk_parent_line = 0;
992
                $num = count($this->lines);
993
994
                /*
995
                 *  Insert products details into db
996
                 */
997
                for ($i = 0; $i < $num; $i++) {
998
                    $line = $this->lines[$i];
999
1000
                    // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
1001
                    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
1002
                    if (!is_object($line)) {
1003
                        $line = (object) $line;
1004
                    }
1005
1006
                    // Reset fk_parent_line for no child products and special product
1007
                    if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1008
                        $fk_parent_line = 0;
1009
                    }
1010
1011
                    // Complete vat rate with code
1012
                    $vatrate = $line->tva_tx;
1013
                    if ($line->vat_src_code && !preg_match('/\(.*\)/', (string) $vatrate)) {
1014
                        $vatrate .= ' (' . $line->vat_src_code . ')';
1015
                    }
1016
1017
                    if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1018
                        $originid = $line->origin_id;
1019
                        $origintype = $line->origin;
1020
                    } else {
1021
                        $originid = $line->id;
1022
                        $origintype = $this->element;
1023
                    }
1024
1025
                    // ref_ext
1026
                    if (empty($line->ref_ext)) {
1027
                        $line->ref_ext = '';
1028
                    }
1029
1030
                    $result = $this->addline(
1031
                        $line->desc,
1032
                        $line->subprice,
1033
                        $line->qty,
1034
                        $vatrate,
1035
                        $line->localtax1_tx,
1036
                        $line->localtax2_tx,
1037
                        $line->fk_product,
1038
                        $line->remise_percent,
1039
                        $line->info_bits,
1040
                        $line->fk_remise_except,
1041
                        'HT',
1042
                        0,
1043
                        $line->date_start,
1044
                        $line->date_end,
1045
                        $line->product_type,
1046
                        $line->rang,
1047
                        $line->special_code,
1048
                        $fk_parent_line,
1049
                        $line->fk_fournprice,
1050
                        $line->pa_ht,
1051
                        $line->label,
1052
                        $line->array_options,
1053
                        $line->fk_unit,
1054
                        $origintype,
1055
                        $originid,
1056
                        0,
1057
                        $line->ref_ext,
1058
                        1
1059
                    );
1060
                    if ($result < 0) {
1061
                        if ($result != self::STOCK_NOT_ENOUGH_FOR_ORDER) {
1062
                            $this->error = $this->db->lasterror();
1063
                            $this->errors[] = $this->error;
1064
                            dol_print_error($this->db);
1065
                        }
1066
                        $this->db->rollback();
1067
                        return -1;
1068
                    }
1069
                    // Defined the new fk_parent_line
1070
                    if ($result > 0 && $line->product_type == 9) {
1071
                        $fk_parent_line = $result;
1072
                    }
1073
                }
1074
1075
                $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.
1076
1077
                // update ref
1078
                $initialref = '(PROV' . $this->id . ')';
1079
                if (!empty($this->ref)) {
1080
                    $initialref = $this->ref;
1081
                }
1082
1083
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . "commande SET ref='" . $this->db->escape($initialref) . "' WHERE rowid=" . ((int) $this->id);
1084
                if ($this->db->query($sql)) {
1085
                    $this->ref = $initialref;
1086
1087
                    if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {  // To use new linkedObjectsIds instead of old linked_objects
1088
                        $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1089
                    }
1090
1091
                    // Add object linked
1092
                    if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1093
                        foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1094
                            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, ...))
1095
                                foreach ($tmp_origin_id as $origin_id) {
1096
                                    $ret = $this->add_object_linked($origin, $origin_id);
1097
                                    if (!$ret) {
1098
                                        $this->error = $this->db->lasterror();
1099
                                        $error++;
1100
                                    }
1101
                                }
1102
                            } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1103
                                $origin_id = $tmp_origin_id;
1104
                                $ret = $this->add_object_linked($origin, $origin_id);
1105
                                if (!$ret) {
1106
                                    $this->error = $this->db->lasterror();
1107
                                    $error++;
1108
                                }
1109
                            }
1110
                        }
1111
                    }
1112
1113
                    if (!$error && $this->id && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) {   // Get contact from origin object
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1113
                    if (!$error && $this->id && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty(/** @scrutinizer ignore-deprecated */ $this->origin) && !empty($this->origin_id)) {   // Get contact from origin object

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...
1114
                        $originforcontact = $this->origin;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1114
                        $originforcontact = /** @scrutinizer ignore-deprecated */ $this->origin;

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

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

Loading history...
1115
                        $originidforcontact = $this->origin_id;
1116
                        if ($originforcontact == 'shipping') {     // shipment and order share the same contacts. If creating from shipment we take data of order
1117
                            $exp = new Expedition($this->db);
1118
                            $exp->fetch($this->origin_id);
1119
                            $exp->fetchObjectLinked();
1120
                            if (count($exp->linkedObjectsIds['commande']) > 0) {
1121
                                foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
1122
                                    $originforcontact = 'commande';
1123
                                    if (is_object($value)) {
1124
                                        $originidforcontact = $value->id;
1125
                                    } else {
1126
                                        $originidforcontact = $value;
1127
                                    }
1128
                                    break; // We take first one
1129
                                }
1130
                            }
1131
                        }
1132
1133
                        $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";
1134
                        $sqlcontact .= " WHERE element_id = " . ((int) $originidforcontact) . " AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '" . $this->db->escape($originforcontact) . "'";
1135
1136
                        $resqlcontact = $this->db->query($sqlcontact);
1137
                        if ($resqlcontact) {
1138
                            while ($objcontact = $this->db->fetch_object($resqlcontact)) {
1139
                                //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
1140
                                $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
1141
                            }
1142
                        } else {
1143
                            dol_print_error($resqlcontact);
1144
                        }
1145
                    }
1146
1147
                    if (!$error) {
1148
                        $result = $this->insertExtraFields();
1149
                        if ($result < 0) {
1150
                            $error++;
1151
                        }
1152
                    }
1153
1154
                    if (!$error && !$notrigger) {
1155
                        // Call trigger
1156
                        $result = $this->call_trigger('ORDER_CREATE', $user);
1157
                        if ($result < 0) {
1158
                            $error++;
1159
                        }
1160
                        // End call triggers
1161
                    }
1162
1163
                    if (!$error) {
1164
                        $this->db->commit();
1165
                        return $this->id;
1166
                    } else {
1167
                        $this->db->rollback();
1168
                        return -1 * $error;
1169
                    }
1170
                } else {
1171
                    $this->error = $this->db->lasterror();
1172
                    $this->db->rollback();
1173
                    return -1;
1174
                }
1175
            }
1176
1177
            return 0;
1178
        } else {
1179
            $this->error = $this->db->lasterror();
1180
            $this->db->rollback();
1181
            return -1;
1182
        }
1183
    }
1184
1185
1186
    /**
1187
     *  Load an object from its id and create a new one in database
1188
     *
1189
     *  @param      User    $user       User making the clone
1190
     *  @param      int     $socid      Id of thirdparty
1191
     *  @return     int                 New id of clone
1192
     */
1193
    public function createFromClone(User $user, $socid = 0)
1194
    {
1195
        global $conf, $user, $hookmanager;
1196
1197
        $error = 0;
1198
1199
        $this->db->begin();
1200
1201
        // get lines so they will be clone
1202
        foreach ($this->lines as $line) {
1203
            $line->fetch_optionals();
1204
        }
1205
1206
        // Load source object
1207
        $objFrom = clone $this;
1208
1209
        // Change socid if needed
1210
        if (!empty($socid) && $socid != $this->socid) {
1211
            $objsoc = new Societe($this->db);
1212
1213
            if ($objsoc->fetch($socid) > 0) {
1214
                $this->socid = $objsoc->id;
1215
                $this->cond_reglement_id    = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1216
                $this->deposit_percent      = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : 0);
1217
                $this->mode_reglement_id    = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1218
                $this->fk_project = 0;
1219
                $this->fk_delivery_address = 0;
1220
            }
1221
1222
            // TODO Change product price if multi-prices
1223
        }
1224
1225
        $this->id = 0;
1226
        $this->ref = '';
1227
        $this->statut = self::STATUS_DRAFT;
1228
1229
        // Clear fields
1230
        $this->user_author_id     = $user->id;
1231
        $this->user_validation_id = 0;
1232
        $this->date = dol_now();
1233
        $this->date_commande = dol_now();
1234
        $this->date_creation      = '';
1235
        $this->date_validation    = '';
1236
        if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1237
            $this->ref_client = '';
1238
            $this->ref_customer = '';
1239
        }
1240
1241
        // Do not clone ref_ext
1242
        $num = count($this->lines);
1243
        for ($i = 0; $i < $num; $i++) {
1244
            $this->lines[$i]->ref_ext = '';
1245
        }
1246
1247
        // Create clone
1248
        $this->context['createfromclone'] = 'createfromclone';
1249
        $result = $this->create($user);
1250
        if ($result < 0) {
1251
            $error++;
1252
        }
1253
1254
        if (!$error) {
1255
            // copy internal contacts
1256
            if ($this->copy_linked_contact($objFrom, 'internal') < 0) {
1257
                $error++;
1258
            }
1259
        }
1260
1261
        if (!$error) {
1262
            // copy external contacts if same company
1263
            if ($this->socid == $objFrom->socid) {
1264
                if ($this->copy_linked_contact($objFrom, 'external') < 0) {
1265
                    $error++;
1266
                }
1267
            }
1268
        }
1269
1270
        if (!$error) {
1271
            // Hook of thirdparty module
1272
            if (is_object($hookmanager)) {
1273
                $parameters = array('objFrom' => $objFrom);
1274
                $action = '';
1275
                $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1276
                if ($reshook < 0) {
1277
                    $this->setErrorsFromObject($hookmanager);
1278
                    $error++;
1279
                }
1280
            }
1281
        }
1282
1283
        unset($this->context['createfromclone']);
1284
1285
        // End
1286
        if (!$error) {
1287
            $this->db->commit();
1288
            return $this->id;
1289
        } else {
1290
            $this->db->rollback();
1291
            return -1;
1292
        }
1293
    }
1294
1295
1296
    /**
1297
     *  Load an object from a proposal and create a new order into database
1298
     *
1299
     *  @param      Object          $object             Object source
1300
     *  @param      User            $user               User making creation
1301
     *  @return     int                                 Return integer <0 if KO, 0 if nothing done, 1 if OK
1302
     */
1303
    public function createFromProposal($object, User $user)
1304
    {
1305
        global $conf, $hookmanager;
1306
1307
1308
        $error = 0;
1309
1310
        $this->date_commande = dol_now();
1311
        $this->date = dol_now();
1312
        $this->source = 0;
1313
1314
        $num = count($object->lines);
1315
        for ($i = 0; $i < $num; $i++) {
1316
            $line = new OrderLine($this->db);
1317
1318
            $line->libelle           = $object->lines[$i]->libelle;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Core\Class...mmonOrderLine::$libelle has been deprecated: Use product_label ( Ignorable by Annotation )

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

1318
            /** @scrutinizer ignore-deprecated */ $line->libelle           = $object->lines[$i]->libelle;

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...
1319
            $line->label             = $object->lines[$i]->label;
1320
            $line->desc              = $object->lines[$i]->desc;
1321
            $line->price             = $object->lines[$i]->price;
1322
            $line->subprice          = $object->lines[$i]->subprice;
1323
            $line->vat_src_code      = $object->lines[$i]->vat_src_code;
1324
            $line->tva_tx            = $object->lines[$i]->tva_tx;
1325
            $line->localtax1_tx      = $object->lines[$i]->localtax1_tx;
1326
            $line->localtax2_tx      = $object->lines[$i]->localtax2_tx;
1327
            $line->qty               = $object->lines[$i]->qty;
1328
            $line->fk_remise_except  = $object->lines[$i]->fk_remise_except;
1329
            $line->remise_percent    = $object->lines[$i]->remise_percent;
1330
            $line->fk_product        = $object->lines[$i]->fk_product;
1331
            $line->info_bits         = $object->lines[$i]->info_bits;
1332
            $line->product_type      = $object->lines[$i]->product_type;
1333
            $line->rang              = $object->lines[$i]->rang;
1334
            $line->special_code      = $object->lines[$i]->special_code;
1335
            $line->fk_parent_line    = $object->lines[$i]->fk_parent_line;
1336
            $line->fk_unit = $object->lines[$i]->fk_unit;
1337
1338
            $line->date_start       = $object->lines[$i]->date_start;
1339
            $line->date_end         = $object->lines[$i]->date_end;
1340
1341
            $line->fk_fournprice    = $object->lines[$i]->fk_fournprice;
1342
            $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);
1343
            $line->pa_ht            = $marginInfos[0];
1344
            $line->marge_tx         = $marginInfos[1];
1345
            $line->marque_tx        = $marginInfos[2];
1346
1347
            $line->origin           = $object->element;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1347
            /** @scrutinizer ignore-deprecated */ $line->origin           = $object->element;

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...
1348
            $line->origin_id        = $object->lines[$i]->id;
1349
1350
            // get extrafields from original line
1351
            $object->lines[$i]->fetch_optionals();
1352
            foreach ($object->lines[$i]->array_options as $options_key => $value) {
1353
                $line->array_options[$options_key] = $value;
1354
            }
1355
1356
            $this->lines[$i] = $line;
1357
        }
1358
1359
        $this->entity               = $object->entity;
1360
        $this->socid                = $object->socid;
1361
        $this->fk_project           = $object->fk_project;
1362
        $this->cond_reglement_id    = $object->cond_reglement_id;
1363
        $this->deposit_percent      = $object->deposit_percent;
1364
        $this->mode_reglement_id    = $object->mode_reglement_id;
1365
        $this->fk_account           = $object->fk_account;
1366
        $this->availability_id      = $object->availability_id;
1367
        $this->demand_reason_id     = $object->demand_reason_id;
1368
        $this->delivery_date        = $object->delivery_date;
1369
        $this->shipping_method_id   = $object->shipping_method_id;
1370
        $this->warehouse_id         = $object->warehouse_id;
1371
        $this->fk_delivery_address  = $object->fk_delivery_address;
1372
        $this->contact_id           = $object->contact_id;
1373
        $this->ref_client           = $object->ref_client;
1374
        $this->ref_customer         = $object->ref_client;
1375
1376
        if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1377
            $this->note_private         = $object->note_private;
1378
            $this->note_public          = $object->note_public;
1379
        }
1380
1381
        $this->origin = $object->element;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1381
        /** @scrutinizer ignore-deprecated */ $this->origin = $object->element;

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...
1382
        $this->origin_id = $object->id;
1383
1384
        // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1385
        if (!empty($conf->multicurrency->enabled)) {
1386
            if (!empty($object->multicurrency_code)) {
1387
                $this->multicurrency_code = $object->multicurrency_code;
1388
            }
1389
            if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($object->multicurrency_tx)) {
1390
                $this->multicurrency_tx = $object->multicurrency_tx;
1391
            }
1392
1393
            if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1394
                $tmparray = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date_commande);
1395
                $this->fk_multicurrency = $tmparray[0];
1396
                $this->multicurrency_tx = $tmparray[1];
1397
            } else {
1398
                $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1399
            }
1400
            if (empty($this->fk_multicurrency)) {
1401
                $this->multicurrency_code = $conf->currency;
1402
                $this->fk_multicurrency = 0;
1403
                $this->multicurrency_tx = 1;
1404
            }
1405
        }
1406
1407
        // get extrafields from original line
1408
        $object->fetch_optionals();
1409
1410
        $e = new ExtraFields($this->db);
1411
        $element_extrafields = $e->fetch_name_optionals_label($this->table_element);
1412
1413
        foreach ($object->array_options as $options_key => $value) {
1414
            if (array_key_exists(str_replace('options_', '', $options_key), $element_extrafields)) {
1415
                $this->array_options[$options_key] = $value;
1416
            }
1417
        }
1418
        // Possibility to add external linked objects with hooks
1419
        $this->linked_objects[$this->origin] = $this->origin_id;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1419
        $this->linked_objects[/** @scrutinizer ignore-deprecated */ $this->origin] = $this->origin_id;

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...
1420
        if (isset($object->other_linked_objects) && is_array($object->other_linked_objects) && !empty($object->other_linked_objects)) {
1421
            $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1422
        }
1423
1424
        $ret = $this->create($user);
1425
1426
        if ($ret > 0) {
1427
            // Actions hooked (by external module)
1428
            $hookmanager->initHooks(array('orderdao'));
1429
1430
            $parameters = array('objFrom' => $object);
1431
            $action = '';
1432
            $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1433
            if ($reshook < 0) {
1434
                $this->setErrorsFromObject($hookmanager);
1435
                $error++;
1436
            }
1437
1438
            if (!$error) {
1439
                // Validate immediately the order
1440
                if (getDolGlobalString('ORDER_VALID_AFTER_CLOSE_PROPAL')) {
1441
                    $this->fetch($ret);
1442
                    $this->valid($user);
1443
                }
1444
                return $ret;
1445
            } else {
1446
                return -1;
1447
            }
1448
        } else {
1449
            return -1;
1450
        }
1451
    }
1452
1453
1454
    /**
1455
     *  Add an order line into database (linked to product/service or not)
1456
     *
1457
     *  @param      string          $desc               Description of line
1458
     *  @param      float           $pu_ht              Unit price (without tax)
1459
     *  @param      float           $qty                Quantite
1460
     *  @param      float           $txtva              Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
1461
     *  @param      float           $txlocaltax1        Local tax 1 rate (deprecated, use instead txtva with code inside)
1462
     *  @param      float           $txlocaltax2        Local tax 2 rate (deprecated, use instead txtva with code inside)
1463
     *  @param      int             $fk_product         Id of product
1464
     *  @param      float           $remise_percent     Percentage discount of the line
1465
     *  @param      int             $info_bits          Bits of type of lines
1466
     *  @param      int             $fk_remise_except   Id remise
1467
     *  @param      string          $price_base_type    HT or TTC
1468
     *  @param      float           $pu_ttc             Prix unitaire TTC
1469
     *  @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)
1470
     *  @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)
1471
     *  @param      int             $type               Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
1472
     *  @param      int             $rang               Position of line
1473
     *  @param      int             $special_code       Special code (also used by externals modules!)
1474
     *  @param      int             $fk_parent_line     Parent line
1475
     *  @param      int             $fk_fournprice      Id supplier price
1476
     *  @param      int             $pa_ht              Buying price (without tax)
1477
     *  @param      string          $label              Label
1478
     *  @param      array           $array_options      extrafields array. Example array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
1479
     *  @param      int|null        $fk_unit            Code of the unit to use. Null to use the default one
1480
     *  @param      string          $origin             Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
1481
     *  @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
1482
     *  @param      double          $pu_ht_devise       Unit price in currency
1483
     *  @param      string          $ref_ext            line external reference
1484
     *  @param      int             $noupdateafterinsertline    No update after insert of line
1485
     *  @return     int                                 >0 if OK, <0 if KO
1486
     *
1487
     *  @see        add_product()
1488
     *
1489
     *  Les parameters sont deja cense etre juste et avec valeurs finales a l'appel
1490
     *  de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini
1491
     *  par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit)
1492
     *  et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
1493
     */
1494
    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)
1495
    {
1496
        global $mysoc, $conf, $langs, $user;
1497
1498
        $logtext = "::addline commandeid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_percent=$remise_percent";
1499
        $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";
1500
        $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";
1501
        dol_syslog(get_class($this) . $logtext, LOG_DEBUG);
1502
1503
        if ($this->statut == self::STATUS_DRAFT) {
1504
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1505
1506
            // Clean parameters
1507
1508
            if (empty($remise_percent)) {
1509
                $remise_percent = 0;
1510
            }
1511
            if (empty($qty)) {
1512
                $qty = 0;
1513
            }
1514
            if (empty($info_bits)) {
1515
                $info_bits = 0;
1516
            }
1517
            if (empty($rang)) {
1518
                $rang = 0;
1519
            }
1520
            if (empty($txtva)) {
1521
                $txtva = 0;
1522
            }
1523
            if (empty($txlocaltax1)) {
1524
                $txlocaltax1 = 0;
1525
            }
1526
            if (empty($txlocaltax2)) {
1527
                $txlocaltax2 = 0;
1528
            }
1529
            if (empty($fk_parent_line) || $fk_parent_line < 0) {
1530
                $fk_parent_line = 0;
1531
            }
1532
            if (empty($this->fk_multicurrency)) {
1533
                $this->fk_multicurrency = 0;
1534
            }
1535
            if (empty($ref_ext)) {
1536
                $ref_ext = '';
1537
            }
1538
1539
            $remise_percent = (float) price2num($remise_percent);
1540
            $qty = (float) price2num($qty);
1541
            $pu_ht = price2num($pu_ht);
1542
            $pu_ht_devise = price2num($pu_ht_devise);
1543
            $pu_ttc = price2num($pu_ttc);
1544
            $pa_ht = (float) price2num($pa_ht);
1545
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
1546
                $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
1547
            }
1548
            $txlocaltax1 = price2num($txlocaltax1);
1549
            $txlocaltax2 = price2num($txlocaltax2);
1550
            if ($price_base_type == 'HT') {
1551
                $pu = $pu_ht;
1552
            } else {
1553
                $pu = $pu_ttc;
1554
            }
1555
            $label = trim($label);
1556
            $desc = trim($desc);
1557
1558
            // Check parameters
1559
            if ($type < 0) {
1560
                return -1;
1561
            }
1562
1563
            if ($date_start && $date_end && $date_start > $date_end) {
1564
                $langs->load("errors");
1565
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1566
                return -1;
1567
            }
1568
1569
            $this->db->begin();
1570
1571
            $product_type = $type;
1572
            if (!empty($fk_product) && $fk_product > 0) {
1573
                $product = new Product($this->db);
1574
                $result = $product->fetch($fk_product);
1575
                $product_type = $product->type;
1576
1577
                if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product_type == 0 && $product->stock_reel < $qty) {
1578
                    $langs->load("errors");
1579
                    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
1580
                    $this->errors[] = $this->error;
1581
                    dol_syslog(get_class($this) . "::addline error=Product " . $product->ref . ": " . $this->error, LOG_ERR);
1582
                    $this->db->rollback();
1583
                    return self::STOCK_NOT_ENOUGH_FOR_ORDER;
1584
                }
1585
            }
1586
            // Calcul du total TTC et de la TVA pour la ligne a partir de
1587
            // qty, pu, remise_percent et txtva
1588
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
1589
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
1590
1591
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
1592
1593
            // Clean vat code
1594
            $reg = array();
1595
            $vat_src_code = '';
1596
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
1597
                $vat_src_code = $reg[1];
1598
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
1599
            }
1600
1601
            $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);
1602
1603
            /*var_dump($txlocaltax1);
1604
             var_dump($txlocaltax2);
1605
             var_dump($localtaxes_type);
1606
             var_dump($tabprice);
1607
             var_dump($tabprice[9]);
1608
             var_dump($tabprice[10]);
1609
             exit;*/
1610
1611
            $total_ht  = $tabprice[0];
1612
            $total_tva = $tabprice[1];
1613
            $total_ttc = $tabprice[2];
1614
            $total_localtax1 = $tabprice[9];
1615
            $total_localtax2 = $tabprice[10];
1616
            $pu_ht = $tabprice[3];
1617
1618
            // MultiCurrency
1619
            $multicurrency_total_ht  = $tabprice[16];
1620
            $multicurrency_total_tva = $tabprice[17];
1621
            $multicurrency_total_ttc = $tabprice[18];
1622
            $pu_ht_devise = $tabprice[19];
1623
1624
            // Rang to use
1625
            $ranktouse = $rang;
1626
            if ($ranktouse == -1) {
1627
                $rangmax = $this->line_max($fk_parent_line);
1628
                $ranktouse = $rangmax + 1;
1629
            }
1630
1631
            // TODO A virer
1632
            // Anciens indicateurs: $price, $remise (a ne plus utiliser)
1633
            $price = $pu;
1634
            $remise = 0;
1635
            if ($remise_percent > 0) {
1636
                $remise = round(((float) $pu * $remise_percent / 100), 2);
1637
                $price = (float) $pu - $remise;
1638
            }
1639
1640
            // Insert line
1641
            $this->line = new OrderLine($this->db);
1642
1643
            $this->line->context = $this->context;
1644
1645
            $this->line->fk_commande = $this->id;
1646
            $this->line->label = $label;
1647
            $this->line->desc = $desc;
1648
            $this->line->qty = $qty;
1649
            $this->line->ref_ext = $ref_ext;
1650
1651
            $this->line->vat_src_code = $vat_src_code;
1652
            $this->line->tva_tx = $txtva;
0 ignored issues
show
Documentation Bug introduced by
It seems like $txtva can also be of type string. However, the property $tva_tx is declared as type double. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1653
            $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
1654
            $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
1655
            $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1656
            $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1657
            $this->line->fk_product = $fk_product;
1658
            $this->line->product_type = $product_type;
1659
            $this->line->fk_remise_except = $fk_remise_except;
1660
            $this->line->remise_percent = $remise_percent;
1661
            $this->line->subprice = $pu_ht;
1662
            $this->line->rang = $ranktouse;
1663
            $this->line->info_bits = $info_bits;
1664
            $this->line->total_ht = $total_ht;
1665
            $this->line->total_tva = $total_tva;
1666
            $this->line->total_localtax1 = $total_localtax1;
1667
            $this->line->total_localtax2 = $total_localtax2;
1668
            $this->line->total_ttc = $total_ttc;
1669
            $this->line->special_code = $special_code;
1670
            $this->line->origin = $origin;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1670
            /** @scrutinizer ignore-deprecated */ $this->line->origin = $origin;

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

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

Loading history...
1671
            $this->line->origin_id = $origin_id;
1672
            $this->line->fk_parent_line = $fk_parent_line;
1673
            $this->line->fk_unit = $fk_unit;
1674
1675
            $this->line->date_start = $date_start;
1676
            $this->line->date_end = $date_end;
1677
1678
            $this->line->fk_fournprice = $fk_fournprice;
1679
            $this->line->pa_ht = $pa_ht;
1680
1681
            // Multicurrency
1682
            $this->line->fk_multicurrency = $this->fk_multicurrency;
1683
            $this->line->multicurrency_code = $this->multicurrency_code;
1684
            $this->line->multicurrency_subprice     = $pu_ht_devise;
1685
            $this->line->multicurrency_total_ht     = $multicurrency_total_ht;
1686
            $this->line->multicurrency_total_tva    = $multicurrency_total_tva;
1687
            $this->line->multicurrency_total_ttc    = $multicurrency_total_ttc;
1688
1689
            // TODO Ne plus utiliser
1690
            $this->line->price = $price;
1691
1692
            if (is_array($array_options) && count($array_options) > 0) {
1693
                $this->line->array_options = $array_options;
1694
            }
1695
1696
            $result = $this->line->insert($user);
1697
            if ($result > 0) {
1698
                // Reorder if child line
1699
                if (!empty($fk_parent_line)) {
1700
                    $this->line_order(true, 'DESC');
1701
                } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
1702
                    $linecount = count($this->lines);
1703
                    for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
1704
                        $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
1705
                    }
1706
                }
1707
1708
                // Mise a jour information denormalisees au niveau de la commande meme
1709
                if (empty($noupdateafterinsertline)) {
1710
                    $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.
1711
                }
1712
1713
                if ($result > 0) {
1714
                    $this->db->commit();
1715
                    $this->lines[] = $this->line;
1716
                    return $this->line->id;
1717
                } else {
1718
                    $this->db->rollback();
1719
                    return -1;
1720
                }
1721
            } else {
1722
                $this->error = $this->line->error;
1723
                dol_syslog(get_class($this) . "::addline error=" . $this->error, LOG_ERR);
1724
                $this->db->rollback();
1725
                return -2;
1726
            }
1727
        } else {
1728
            dol_syslog(get_class($this) . "::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
1729
            return -3;
1730
        }
1731
    }
1732
1733
1734
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1735
    /**
1736
     *  Add line into array
1737
     *  $this->client must be loaded
1738
     *
1739
     *  @param  int     $idproduct          Product Id
1740
     *  @param  float   $qty                Quantity
1741
     *  @param  float   $remise_percent     Product discount relative
1742
     *  @param  int|string   $date_start         Start date of the line
1743
     *  @param  int|string   $date_end           End date of the line
1744
     *  @return void
1745
     *
1746
     *  TODO    Remplacer les appels a cette fonction par generation object Ligne
1747
     */
1748
    public function add_product($idproduct, $qty, $remise_percent = 0.0, $date_start = '', $date_end = '')
1749
    {
1750
		// phpcs:enable
1751
        global $conf, $mysoc;
1752
1753
        if (!$qty) {
1754
            $qty = 1;
1755
        }
1756
1757
        if ($idproduct > 0) {
1758
            $prod = new Product($this->db);
1759
            $prod->fetch($idproduct);
1760
1761
            $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
1762
            $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
1763
            if (empty($tva_tx)) {
1764
                $tva_npr = 0;
1765
            }
1766
            $vat_src_code = ''; // May be defined into tva_tx
1767
1768
            $localtax1_tx = get_localtax($tva_tx, 1, $this->thirdparty, $mysoc, $tva_npr);
1769
            $localtax2_tx = get_localtax($tva_tx, 2, $this->thirdparty, $mysoc, $tva_npr);
1770
1771
            // multiprix
1772
            if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
1773
                $price = $prod->multiprices[$this->thirdparty->price_level];
1774
            } else {
1775
                $price = $prod->price;
1776
            }
1777
1778
            $line = new OrderLine($this->db);
1779
1780
            $line->context = $this->context;
1781
1782
            $line->fk_product = $idproduct;
1783
            $line->desc = $prod->description;
1784
            $line->qty = $qty;
1785
            $line->subprice = $price;
1786
            $line->remise_percent = $remise_percent;
1787
            $line->vat_src_code = $vat_src_code;
1788
            $line->tva_tx = $tva_tx;
0 ignored issues
show
Documentation Bug introduced by
It seems like $tva_tx can also be of type string. However, the property $tva_tx is declared as type double. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1789
            $line->localtax1_tx = $localtax1_tx;
1790
            $line->localtax2_tx = $localtax2_tx;
1791
1792
            $line->product_ref = $prod->ref;
1793
            $line->product_label = $prod->label;
1794
            $line->product_desc = $prod->description;
1795
            $line->fk_unit = $prod->fk_unit;
1796
1797
            // Save the start and end date of the line in the object
1798
            if ($date_start) {
1799
                $line->date_start = $date_start;
1800
            }
1801
            if ($date_end) {
1802
                $line->date_end = $date_end;
1803
            }
1804
1805
            $this->lines[] = $line;
1806
1807
            /** POUR AJOUTER AUTOMATIQUEMENT LES SOUSPRODUITS a LA COMMANDE
1808
             if (!empty($conf->global->PRODUIT_SOUSPRODUITS))
1809
             {
1810
             $prod = new Product($this->db);
1811
             $prod->fetch($idproduct);
1812
             $prod -> get_sousproduits_arbo();
1813
             $prods_arbo = $prod->get_arbo_each_prod();
1814
             if(count($prods_arbo) > 0)
1815
             {
1816
                 foreach($prods_arbo as $key => $value)
1817
                 {
1818
                     // print "id : ".$value[1].' :qty: '.$value[0].'<br>';
1819
                     if not in lines {
1820
                        $this->add_product($value[1], $value[0]);
1821
                     }
1822
                 }
1823
             }
1824
             **/
1825
        }
1826
    }
1827
1828
1829
    /**
1830
     *  Get object from database. Get also lines.
1831
     *
1832
     *  @param      int         $id             Id of object to load
1833
     *  @param      string      $ref            Ref of object
1834
     *  @param      string      $ref_ext        External reference of object
1835
     *  @param      string      $notused        Internal reference of other object
1836
     *  @return     int                         >0 if OK, <0 if KO, 0 if not found
1837
     */
1838
    public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
1839
    {
1840
        // Check parameters
1841
        if (empty($id) && empty($ref) && empty($ref_ext)) {
1842
            return -1;
1843
        }
1844
1845
        $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';
1846
        $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';
1847
        $sql .= ', c.fk_account';
1848
        $sql .= ', c.date_commande, c.date_valid, c.tms';
1849
        $sql .= ', c.date_livraison as delivery_date';
1850
        $sql .= ', c.fk_shipping_method';
1851
        $sql .= ', c.fk_warehouse';
1852
        $sql .= ', c.fk_projet as fk_project, c.source, c.facture as billed';
1853
        $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';
1854
        $sql .= ', c.fk_incoterms, c.location_incoterms';
1855
        $sql .= ", c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc";
1856
        $sql .= ", c.module_source, c.pos_source";
1857
        $sql .= ", i.libelle as label_incoterms";
1858
        $sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
1859
        $sql .= ', cr.code as cond_reglement_code, cr.libelle as cond_reglement_libelle, cr.libelle_facture as cond_reglement_libelle_doc';
1860
        $sql .= ', ca.code as availability_code, ca.label as availability_label';
1861
        $sql .= ', dr.code as demand_reason_code';
1862
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'commande as c';
1863
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_payment_term as cr ON c.fk_cond_reglement = cr.rowid';
1864
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_paiement as p ON c.fk_mode_reglement = p.id';
1865
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_availability as ca ON c.fk_availability = ca.rowid';
1866
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_input_reason as dr ON c.fk_input_reason = dr.rowid';
1867
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_incoterms as i ON c.fk_incoterms = i.rowid';
1868
1869
        if ($id) {
1870
            $sql .= " WHERE c.rowid=" . ((int) $id);
1871
        } else {
1872
            $sql .= " WHERE c.entity IN (" . getEntity('commande') . ")"; // Don't use entity if you use rowid
1873
        }
1874
1875
        if ($ref) {
1876
            $sql .= " AND c.ref='" . $this->db->escape($ref) . "'";
1877
        }
1878
        if ($ref_ext) {
1879
            $sql .= " AND c.ref_ext='" . $this->db->escape($ref_ext) . "'";
1880
        }
1881
1882
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
1883
        $result = $this->db->query($sql);
1884
        if ($result) {
1885
            $obj = $this->db->fetch_object($result);
1886
            if ($obj) {
1887
                $this->id = $obj->rowid;
1888
                $this->entity = $obj->entity;
1889
1890
                $this->ref = $obj->ref;
1891
                $this->ref_client = $obj->ref_client;
1892
                $this->ref_customer = $obj->ref_client;
1893
                $this->ref_ext = $obj->ref_ext;
1894
1895
                $this->socid = $obj->fk_soc;
1896
                $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1897
1898
                $this->fk_project = $obj->fk_project;
1899
                $this->project = null; // Clear if another value was already set by fetch_projet
1900
1901
                $this->statut = $obj->fk_statut;
1902
                $this->status = $obj->fk_statut;
1903
1904
                $this->user_author_id = $obj->fk_user_author;
1905
                $this->user_creation_id = $obj->fk_user_author;
1906
                $this->user_validation_id = $obj->fk_user_valid;
1907
                $this->user_modification_id = $obj->fk_user_modif;
1908
                $this->total_ht             = $obj->total_ht;
1909
                $this->total_tva            = $obj->total_tva;
1910
                $this->total_localtax1      = $obj->total_localtax1;
1911
                $this->total_localtax2      = $obj->total_localtax2;
1912
                $this->total_ttc            = $obj->total_ttc;
1913
                $this->date = $this->db->jdate($obj->date_commande);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->date_commande) can also be of type string. However, the property $date is declared as type integer. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1914
                $this->date_commande        = $this->db->jdate($obj->date_commande);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->date_commande) can also be of type string. However, the property $date_commande is declared as type integer. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1915
                $this->date_creation        = $this->db->jdate($obj->date_creation);
1916
                $this->date_validation      = $this->db->jdate($obj->date_valid);
1917
                $this->date_modification    = $this->db->jdate($obj->tms);
1918
                $this->source               = $obj->source;
1919
                $this->billed               = $obj->billed;
1920
                $this->note = $obj->note_private; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$note has been deprecated: Use $note_private instead. ( Ignorable by Annotation )

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

1920
                /** @scrutinizer ignore-deprecated */ $this->note = $obj->note_private; // deprecated

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

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

Loading history...
1921
                $this->note_private = $obj->note_private;
1922
                $this->note_public = $obj->note_public;
1923
                $this->model_pdf = $obj->model_pdf;
1924
                $this->last_main_doc = $obj->last_main_doc;
1925
                $this->mode_reglement_id    = $obj->fk_mode_reglement;
1926
                $this->mode_reglement_code  = $obj->mode_reglement_code;
1927
                $this->mode_reglement       = $obj->mode_reglement_libelle;
1928
                $this->cond_reglement_id    = $obj->fk_cond_reglement;
1929
                $this->cond_reglement_code  = $obj->cond_reglement_code;
1930
                $this->cond_reglement       = $obj->cond_reglement_libelle;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$cond_reglement has been deprecated: Use $cond_reglement_id instead - Kept for compatibility ( Ignorable by Annotation )

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

1930
                /** @scrutinizer ignore-deprecated */ $this->cond_reglement       = $obj->cond_reglement_libelle;

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...
Bug Best Practice introduced by
The property $cond_reglement is declared private in Dolibarr\Core\Base\CommonObject. Since you implement __set, consider adding a @property or @property-write.
Loading history...
1931
                $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1932
                $this->deposit_percent = $obj->deposit_percent;
1933
                $this->fk_account = $obj->fk_account;
1934
                $this->availability_id = $obj->fk_availability;
1935
                $this->availability_code    = $obj->availability_code;
1936
                $this->availability         = $obj->availability_label;
1937
                $this->demand_reason_id     = $obj->fk_input_reason;
1938
                $this->demand_reason_code = $obj->demand_reason_code;
1939
                $this->delivery_date = $this->db->jdate($obj->delivery_date);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->delivery_date) can also be of type string. However, the property $delivery_date is declared as type integer. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1940
                $this->shipping_method_id   = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1941
                $this->warehouse_id         = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1942
                $this->fk_delivery_address = $obj->fk_delivery_address;
1943
                $this->module_source        = $obj->module_source;
1944
                $this->pos_source           = $obj->pos_source;
1945
1946
                //Incoterms
1947
                $this->fk_incoterms         = $obj->fk_incoterms;
1948
                $this->location_incoterms   = $obj->location_incoterms;
1949
                $this->label_incoterms    = $obj->label_incoterms;
1950
1951
                // Multicurrency
1952
                $this->fk_multicurrency         = $obj->fk_multicurrency;
1953
                $this->multicurrency_code = $obj->multicurrency_code;
1954
                $this->multicurrency_tx         = $obj->multicurrency_tx;
1955
                $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1956
                $this->multicurrency_total_tva  = $obj->multicurrency_total_tva;
1957
                $this->multicurrency_total_ttc  = $obj->multicurrency_total_ttc;
1958
1959
                $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1960
1961
                $this->lines = array();
1962
1963
                // Retrieve all extrafield
1964
                // fetch optionals attributes and labels
1965
                $this->fetch_optionals();
1966
1967
                $this->db->free($result);
1968
1969
                // Lines
1970
                $result = $this->fetch_lines();
1971
                if ($result < 0) {
1972
                    return -3;
1973
                }
1974
                return 1;
1975
            } else {
1976
                $this->error = 'Order with id ' . $id . ' not found sql=' . $sql;
1977
                return 0;
1978
            }
1979
        } else {
1980
            $this->error = $this->db->error();
1981
            return -1;
1982
        }
1983
    }
1984
1985
1986
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1987
    /**
1988
     *  Add line of fixed discount in the order in DB
1989
     *
1990
     *  @param     int  $idremise           Id for the fixed discount
1991
     *  @return    int                      >0 if OK, <0 if KO
1992
     */
1993
    public function insert_discount($idremise)
1994
    {
1995
		// phpcs:enable
1996
        global $langs;
1997
1998
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1999
        include_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
2000
2001
        $this->db->begin();
2002
2003
        $remise = new DiscountAbsolute($this->db);
2004
        $result = $remise->fetch($idremise);
2005
2006
        if ($result > 0) {
2007
            if ($remise->fk_facture) {  // Protection against multiple submission
2008
                $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2009
                $this->db->rollback();
2010
                return -5;
2011
            }
2012
2013
            $line = new OrderLine($this->db);
2014
2015
            $line->fk_commande = $this->id;
2016
            $line->fk_remise_except = $remise->id;
2017
            $line->desc = $remise->description; // Description ligne
2018
            $line->vat_src_code = $remise->vat_src_code;
2019
            $line->tva_tx = $remise->tva_tx;
2020
            $line->subprice = -$remise->amount_ht;
2021
            $line->price = -$remise->amount_ht;
2022
            $line->fk_product = 0; // Id produit predefini
2023
            $line->qty = 1;
2024
            $line->remise_percent = 0;
2025
            $line->rang = -1;
2026
            $line->info_bits = 2;
2027
2028
            $line->total_ht  = -$remise->amount_ht;
2029
            $line->total_tva = -$remise->amount_tva;
2030
            $line->total_ttc = -$remise->amount_ttc;
2031
2032
            $result = $line->insert();
2033
            if ($result > 0) {
2034
                $result = $this->update_price(1);
2035
                if ($result > 0) {
2036
                    $this->db->commit();
2037
                    return 1;
2038
                } else {
2039
                    $this->db->rollback();
2040
                    return -1;
2041
                }
2042
            } else {
2043
                $this->error = $line->error;
2044
                $this->errors = $line->errors;
2045
                $this->db->rollback();
2046
                return -2;
2047
            }
2048
        } else {
2049
            $this->db->rollback();
2050
            return -2;
2051
        }
2052
    }
2053
2054
2055
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2056
    /**
2057
     *  Load array lines
2058
     *
2059
     *  @param      int     $only_product           Return only physical products, not services
2060
     *  @param      int     $loadalsotranslation    Return translation for products
2061
     *  @return     int                             Return integer <0 if KO, >0 if OK
2062
     */
2063
    public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2064
    {
2065
		// phpcs:enable
2066
        global $langs, $conf;
2067
2068
        $this->lines = array();
2069
2070
        $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,';
2071
        $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,';
2072
        $sql .= ' l.total_ht, l.total_ttc, l.total_tva, l.total_localtax1, l.total_localtax2, l.date_start, l.date_end,';
2073
        $sql .= ' l.fk_unit,';
2074
        $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
2075
        $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,';
2076
        $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units';
2077
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'commandedet as l';
2078
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON (p.rowid = l.fk_product)';
2079
        $sql .= ' WHERE l.fk_commande = ' . ((int) $this->id);
2080
        if ($only_product) {
2081
            $sql .= ' AND p.fk_product_type = 0';
2082
        }
2083
        $sql .= ' ORDER BY l.rang, l.rowid';
2084
2085
        dol_syslog(get_class($this) . "::fetch_lines", LOG_DEBUG);
2086
        $result = $this->db->query($sql);
2087
        if ($result) {
2088
            $num = $this->db->num_rows($result);
2089
2090
            $i = 0;
2091
            while ($i < $num) {
2092
                $objp = $this->db->fetch_object($result);
2093
2094
                $line = new OrderLine($this->db);
2095
2096
                $line->rowid            = $objp->rowid;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObjectLine::$rowid has been deprecated: Try to use id property as possible (even if field into database is still rowid) ( Ignorable by Annotation )

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

2096
                /** @scrutinizer ignore-deprecated */ $line->rowid            = $objp->rowid;

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...
2097
                $line->id               = $objp->rowid;
2098
                $line->fk_commande      = $objp->fk_commande;
2099
                $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

2099
                /** @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...
2100
                $line->label            = $objp->custom_label;
2101
                $line->desc             = $objp->description;
2102
                $line->description      = $objp->description; // Description line
2103
                $line->product_type     = $objp->product_type;
2104
                $line->qty              = $objp->qty;
2105
                $line->ref_ext          = $objp->ref_ext;
2106
2107
                $line->vat_src_code     = $objp->vat_src_code;
2108
                $line->tva_tx           = $objp->tva_tx;
2109
                $line->localtax1_tx     = $objp->localtax1_tx;
2110
                $line->localtax2_tx     = $objp->localtax2_tx;
2111
                $line->localtax1_type   = $objp->localtax1_type;
2112
                $line->localtax2_type   = $objp->localtax2_type;
2113
                $line->total_ht         = $objp->total_ht;
2114
                $line->total_ttc        = $objp->total_ttc;
2115
                $line->total_tva        = $objp->total_tva;
2116
                $line->total_localtax1  = $objp->total_localtax1;
2117
                $line->total_localtax2  = $objp->total_localtax2;
2118
                $line->subprice         = $objp->subprice;
2119
                $line->fk_remise_except = $objp->fk_remise_except;
2120
                $line->remise_percent   = $objp->remise_percent;
2121
                $line->price            = $objp->price;
2122
                $line->fk_product       = $objp->fk_product;
2123
                $line->fk_fournprice = $objp->fk_fournprice;
2124
                $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2125
                $line->pa_ht = $marginInfos[0];
2126
                $line->marge_tx         = $marginInfos[1];
2127
                $line->marque_tx        = $marginInfos[2];
2128
                $line->rang             = $objp->rang;
2129
                $line->info_bits        = $objp->info_bits;
2130
                $line->special_code = $objp->special_code;
2131
                $line->fk_parent_line = $objp->fk_parent_line;
2132
2133
                $line->ref = $objp->product_ref;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Core\Classes\CommonOrderLine::$ref has been deprecated: Use product_ref ( Ignorable by Annotation )

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

2133
                /** @scrutinizer ignore-deprecated */ $line->ref = $objp->product_ref;

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...
2134
                $line->libelle = $objp->product_label;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Core\Class...mmonOrderLine::$libelle has been deprecated: Use product_label ( Ignorable by Annotation )

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

2134
                /** @scrutinizer ignore-deprecated */ $line->libelle = $objp->product_label;

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...
2135
2136
                $line->product_ref = $objp->product_ref;
2137
                $line->product_label = $objp->product_label;
2138
                $line->product_tosell   = $objp->product_tosell;
2139
                $line->product_tobuy    = $objp->product_tobuy;
2140
                $line->product_desc     = $objp->product_desc;
2141
                $line->product_tobatch  = $objp->product_tobatch;
2142
                $line->product_barcode  = $objp->product_barcode;
2143
2144
                $line->fk_product_type  = $objp->fk_product_type; // Produit ou service
2145
                $line->fk_unit          = $objp->fk_unit;
2146
2147
                $line->weight           = $objp->weight;
2148
                $line->weight_units     = $objp->weight_units;
2149
                $line->volume           = $objp->volume;
2150
                $line->volume_units     = $objp->volume_units;
2151
2152
                $line->date_start       = $this->db->jdate($objp->date_start);
2153
                $line->date_end         = $this->db->jdate($objp->date_end);
2154
2155
                // Multicurrency
2156
                $line->fk_multicurrency = $objp->fk_multicurrency;
2157
                $line->multicurrency_code = $objp->multicurrency_code;
2158
                $line->multicurrency_subprice   = $objp->multicurrency_subprice;
2159
                $line->multicurrency_total_ht   = $objp->multicurrency_total_ht;
2160
                $line->multicurrency_total_tva  = $objp->multicurrency_total_tva;
2161
                $line->multicurrency_total_ttc  = $objp->multicurrency_total_ttc;
2162
2163
                $line->fetch_optionals();
2164
2165
                // multilangs
2166
                if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2167
                    $tmpproduct = new Product($this->db);
2168
                    $tmpproduct->fetch($objp->fk_product);
2169
                    $tmpproduct->getMultiLangs();
2170
2171
                    $line->multilangs = $tmpproduct->multilangs;
2172
                }
2173
2174
                $this->lines[$i] = $line;
2175
2176
                $i++;
2177
            }
2178
2179
            $this->db->free($result);
2180
2181
            return 1;
2182
        } else {
2183
            $this->error = $this->db->error();
2184
            return -3;
2185
        }
2186
    }
2187
2188
2189
    /**
2190
     *  Return number of line with type product.
2191
     *
2192
     *  @return     int     Return integer <0 if KO, Nbr of product lines if OK
2193
     */
2194
    public function getNbOfProductsLines()
2195
    {
2196
        $nb = 0;
2197
        foreach ($this->lines as $line) {
2198
            if ($line->product_type == 0) {
2199
                $nb++;
2200
            }
2201
        }
2202
        return $nb;
2203
    }
2204
2205
    /**
2206
     *  Return number of line with type service.
2207
     *
2208
     *  @return     int     Return integer <0 if KO, Nbr of service lines if OK
2209
     */
2210
    public function getNbOfServicesLines()
2211
    {
2212
        $nb = 0;
2213
        foreach ($this->lines as $line) {
2214
            if ($line->product_type == 1) {
2215
                $nb++;
2216
            }
2217
        }
2218
        return $nb;
2219
    }
2220
2221
    /**
2222
     *  Count number of shipments for this order
2223
     *
2224
     *  @return     int                         Return integer <0 if KO, Nb of shipment found if OK
2225
     */
2226
    public function getNbOfShipments()
2227
    {
2228
        $nb = 0;
2229
2230
        $sql = 'SELECT COUNT(DISTINCT ed.fk_expedition) as nb';
2231
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'expeditiondet as ed,';
2232
        $sql .= ' ' . MAIN_DB_PREFIX . 'commandedet as cd';
2233
        $sql .= ' WHERE';
2234
        $sql .= ' ed.fk_elementdet = cd.rowid';
2235
        $sql .= ' AND cd.fk_commande = ' . ((int) $this->id);
2236
        //print $sql;
2237
2238
        dol_syslog(get_class($this) . "::getNbOfShipments", LOG_DEBUG);
2239
        $resql = $this->db->query($sql);
2240
        if ($resql) {
2241
            $obj = $this->db->fetch_object($resql);
2242
            if ($obj) {
2243
                $nb = $obj->nb;
2244
            }
2245
2246
            $this->db->free($resql);
2247
            return $nb;
2248
        } else {
2249
            $this->error = $this->db->lasterror();
2250
            return -1;
2251
        }
2252
    }
2253
2254
    /**
2255
     *  Load array this->expeditions of lines of shipments with nb of products sent for each order line
2256
     *  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
2257
     *
2258
     *  @param      int     $filtre_statut      Filter on shipment status
2259
     *  @param      int     $fk_product         Add a filter on a product
2260
     *  @return     int                         Return integer <0 if KO, Nb of lines found if OK
2261
     */
2262
    public function loadExpeditions($filtre_statut = -1, $fk_product = 0)
2263
    {
2264
        $this->expeditions = array();
2265
2266
        $sql = 'SELECT cd.rowid, cd.fk_product,';
2267
        $sql .= ' sum(ed.qty) as qty';
2268
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'expeditiondet as ed,';
2269
        if ($filtre_statut >= 0) {
2270
            $sql .= ' ' . MAIN_DB_PREFIX . 'expedition as e,';
2271
        }
2272
        $sql .= ' ' . MAIN_DB_PREFIX . 'commandedet as cd';
2273
        $sql .= ' WHERE';
2274
        if ($filtre_statut >= 0) {
2275
            $sql .= ' ed.fk_expedition = e.rowid AND';
2276
        }
2277
        $sql .= ' ed.fk_elementdet = cd.rowid';
2278
        $sql .= ' AND cd.fk_commande = ' . ((int) $this->id);
2279
        if ($fk_product > 0) {
2280
            $sql .= ' AND cd.fk_product = ' . ((int) $fk_product);
2281
        }
2282
        if ($filtre_statut >= 0) {
2283
            $sql .= ' AND e.fk_statut >= ' . ((int) $filtre_statut);
2284
        }
2285
        $sql .= ' GROUP BY cd.rowid, cd.fk_product';
2286
        //print $sql;
2287
2288
        dol_syslog(get_class($this) . "::loadExpeditions", LOG_DEBUG);
2289
        $resql = $this->db->query($sql);
2290
        if ($resql) {
2291
            $num = $this->db->num_rows($resql);
2292
            $i = 0;
2293
            while ($i < $num) {
2294
                $obj = $this->db->fetch_object($resql);
2295
                $this->expeditions[$obj->rowid] = $obj->qty;
2296
                $i++;
2297
            }
2298
            $this->db->free($resql);
2299
            return $num;
2300
        } else {
2301
            $this->error = $this->db->lasterror();
2302
            return -1;
2303
        }
2304
    }
2305
2306
    /**
2307
     * Returns an array with expeditions lines number
2308
     *
2309
     * @return  int     Nb of shipments
2310
     */
2311
    public function countNbOfShipments()
2312
    {
2313
        $sql = 'SELECT count(*)';
2314
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'expedition as e';
2315
        $sql .= ', ' . MAIN_DB_PREFIX . 'element_element as el';
2316
        $sql .= ' WHERE el.fk_source = ' . ((int) $this->id);
2317
        $sql .= " AND el.sourcetype = 'commande'";
2318
        $sql .= " AND el.fk_target = e.rowid";
2319
        $sql .= " AND el.targettype = 'shipping'";
2320
2321
        $resql = $this->db->query($sql);
2322
        if ($resql) {
2323
            $row = $this->db->fetch_row($resql);
2324
            return $row[0];
2325
        } else {
2326
            dol_print_error($this->db);
2327
        }
2328
2329
        return 0;
2330
    }
2331
2332
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2333
    /**
2334
     *  Return a array with the pending stock by product
2335
     *
2336
     *  @param      int     $filtre_statut      Filtre sur statut
2337
     *  @return     int                         0 si OK, <0 si KO
2338
     *
2339
     *  TODO        FONCTION NON FINIE A FINIR
2340
     */
2341
    /*public function stock_array($filtre_statut = self::STATUS_CANCELED)
2342
    {
2343
		// phpcs:enable
2344
        $this->stocks = array();
2345
2346
        // Tableau des id de produit de la commande
2347
        $array_of_product = array();
2348
2349
        // Recherche total en stock pour chaque produit
2350
        // TODO $array_of_product est défini vide juste au dessus !!
2351
        if (count($array_of_product)) {
2352
            $sql = "SELECT fk_product, sum(ps.reel) as total";
2353
            $sql .= " FROM ".MAIN_DB_PREFIX."product_stock as ps";
2354
            $sql .= " WHERE ps.fk_product IN (".$this->db->sanitize(join(',', $array_of_product)).")";
2355
            $sql .= ' GROUP BY fk_product';
2356
            $resql = $this->db->query($sql);
2357
            if ($resql) {
2358
                $num = $this->db->num_rows($resql);
2359
                $i = 0;
2360
                while ($i < $num) {
2361
                    $obj = $this->db->fetch_object($resql);
2362
                    $this->stocks[$obj->fk_product] = $obj->total;
2363
                    $i++;
2364
                }
2365
                $this->db->free($resql);
2366
            }
2367
        }
2368
        return 0;
2369
    }*/
2370
2371
    /**
2372
     *  Delete an order line
2373
     *
2374
     *  @param      User    $user       User object
2375
     *  @param      int     $lineid     Id of line to delete
2376
     *  @param      int     $id         Id of object (for a check)
2377
     *  @return     int                 >0 if OK, 0 if nothing to do, <0 if KO
2378
     */
2379
    public function deleteLine($user = null, $lineid = 0, $id = 0)
2380
    {
2381
        if ($this->statut == self::STATUS_DRAFT) {
2382
            $this->db->begin();
2383
2384
            // Delete line
2385
            $line = new OrderLine($this->db);
2386
2387
            $line->context = $this->context;
2388
2389
            // Load data
2390
            $line->fetch($lineid);
2391
2392
            if ($id > 0 && $line->fk_commande != $id) {
2393
                $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2394
                return -1;
2395
            }
2396
2397
            // Memorize previous line for triggers
2398
            $staticline = clone $line;
2399
            $line->oldline = $staticline;
2400
2401
            if ($line->delete($user) > 0) {
2402
                $result = $this->update_price(1);
2403
2404
                if ($result > 0) {
2405
                    $this->db->commit();
2406
                    return 1;
2407
                } else {
2408
                    $this->db->rollback();
2409
                    $this->error = $this->db->lasterror();
2410
                    return -1;
2411
                }
2412
            } else {
2413
                $this->db->rollback();
2414
                $this->error = $line->error;
2415
                return -1;
2416
            }
2417
        } else {
2418
            $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
2419
            return -1;
2420
        }
2421
    }
2422
2423
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2424
    /**
2425
     *  Applique une remise relative
2426
     *
2427
     *  @deprecated Use setDiscount() instead.
2428
     *  @see setDiscount()
2429
     *  @param      User        $user       User qui positionne la remise
2430
     *  @param      float       $remise     Discount (percent)
2431
     *  @param      int         $notrigger  1=Does not execute triggers, 0= execute triggers
2432
     *  @return     int                     Return integer <0 if KO, >0 if OK
2433
     */
2434
    public function set_remise($user, $remise, $notrigger = 0)
2435
    {
2436
		// phpcs:enable
2437
        dol_syslog(get_class($this) . "::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
2438
        // @phan-suppress-next-line PhanDeprecatedFunction
2439
        return $this->setDiscount($user, $remise, $notrigger);
2440
    }
2441
2442
    /**
2443
     *  Set a percentage discount
2444
     *
2445
     *  @param      User        $user       User setting the discount
2446
     *  @param      float|string    $remise     Discount (percent)
2447
     *  @param      int<0,1>    $notrigger  1=Does not execute triggers, 0= execute triggers
2448
     *  @return     int<-1,1>                   Return integer <0 if KO, >0 if OK
2449
     */
2450
    public function setDiscount($user, $remise, $notrigger = 0)
2451
    {
2452
        $remise = trim((string) $remise) ? trim((string) $remise) : 0;
2453
2454
        if ($user->hasRight('commande', 'creer')) {
2455
            $error = 0;
2456
2457
            $this->db->begin();
2458
2459
            $remise = price2num($remise, 2);
2460
2461
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande';
2462
            $sql .= ' SET remise_percent = ' . ((float) $remise);
2463
            $sql .= ' WHERE rowid = ' . ((int) $this->id) . ' AND fk_statut = ' . ((int) self::STATUS_DRAFT);
2464
2465
            dol_syslog(__METHOD__, LOG_DEBUG);
2466
            $resql = $this->db->query($sql);
2467
            if (!$resql) {
2468
                $this->errors[] = $this->db->error();
2469
                $error++;
2470
            }
2471
2472
            if (!$error) {
2473
                $this->oldcopy = clone $this;
2474
                $this->remise_percent = $remise;
2475
                $this->update_price(1);
2476
            }
2477
2478
            if (!$notrigger && empty($error)) {
2479
                // Call trigger
2480
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2481
                if ($result < 0) {
2482
                    $error++;
2483
                }
2484
                // End call triggers
2485
            }
2486
2487
            if (!$error) {
2488
                $this->db->commit();
2489
                return 1;
2490
            } else {
2491
                foreach ($this->errors as $errmsg) {
2492
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2493
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2494
                }
2495
                $this->db->rollback();
2496
                return -1 * $error;
2497
            }
2498
        }
2499
2500
        return 0;
2501
    }
2502
2503
2504
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2505
    /**
2506
     *      Set a fixed amount discount
2507
     *
2508
     *      @param      User        $user       User qui positionne la remise
2509
     *      @param      float       $remise     Discount
2510
     *      @param      int         $notrigger  1=Does not execute triggers, 0= execute triggers
2511
     *      @return     int                     Return integer <0 if KO, >0 if OK
2512
     */
2513
    /*
2514
    public function set_remise_absolue($user, $remise, $notrigger = 0)
2515
    {
2516
		// phpcs:enable
2517
        if (empty($remise)) {
2518
            $remise = 0;
2519
        }
2520
2521
        $remise = price2num($remise);
2522
2523
        if ($user->hasRight('commande', 'creer')) {
2524
            $error = 0;
2525
2526
            $this->db->begin();
2527
2528
            $sql = 'UPDATE '.MAIN_DB_PREFIX.'commande';
2529
            $sql .= ' SET remise_absolue = '.((float) $remise);
2530
            $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.self::STATUS_DRAFT;
2531
2532
            dol_syslog(__METHOD__, LOG_DEBUG);
2533
            $resql = $this->db->query($sql);
2534
            if (!$resql) {
2535
                $this->errors[] = $this->db->error();
2536
                $error++;
2537
            }
2538
2539
            if (!$error) {
2540
                $this->oldcopy = clone $this;
2541
                $this->remise_absolue = $remise;
2542
                $this->update_price(1);
2543
            }
2544
2545
            if (!$notrigger && empty($error)) {
2546
                // Call trigger
2547
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2548
                if ($result < 0) {
2549
                    $error++;
2550
                }
2551
                // End call triggers
2552
            }
2553
2554
            if (!$error) {
2555
                $this->db->commit();
2556
                return 1;
2557
            } else {
2558
                foreach ($this->errors as $errmsg) {
2559
                    dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2560
                    $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2561
                }
2562
                $this->db->rollback();
2563
                return -1 * $error;
2564
            }
2565
        }
2566
2567
        return 0;
2568
    }
2569
    */
2570
2571
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2572
    /**
2573
     *  Set the order date
2574
     *
2575
     *  @param      User    $user       Object user making change
2576
     *  @param      int     $date       Date
2577
     *  @param      int     $notrigger  1=Does not execute triggers, 0= execute triggers
2578
     *  @return     int                 Return integer <0 if KO, >0 if OK
2579
     */
2580
    public function set_date($user, $date, $notrigger = 0)
2581
    {
2582
		// phpcs:enable
2583
        if ($user->hasRight('commande', 'creer')) {
2584
            $error = 0;
2585
2586
            $this->db->begin();
2587
2588
            $sql = "UPDATE " . MAIN_DB_PREFIX . "commande";
2589
            $sql .= " SET date_commande = " . ($date ? "'" . $this->db->idate($date) . "'" : 'null');
2590
            $sql .= " WHERE rowid = " . ((int) $this->id) . " AND fk_statut = " . ((int) self::STATUS_DRAFT);
2591
2592
            dol_syslog(__METHOD__, LOG_DEBUG);
2593
            $resql = $this->db->query($sql);
2594
            if (!$resql) {
2595
                $this->errors[] = $this->db->error();
2596
                $error++;
2597
            }
2598
2599
            if (!$error) {
2600
                $this->oldcopy = clone $this;
2601
                $this->date = $date;
2602
            }
2603
2604
            if (!$notrigger && empty($error)) {
2605
                // Call trigger
2606
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2607
                if ($result < 0) {
2608
                    $error++;
2609
                }
2610
                // End call triggers
2611
            }
2612
2613
            if (!$error) {
2614
                $this->db->commit();
2615
                return 1;
2616
            } else {
2617
                foreach ($this->errors as $errmsg) {
2618
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2619
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2620
                }
2621
                $this->db->rollback();
2622
                return -1 * $error;
2623
            }
2624
        } else {
2625
            return -2;
2626
        }
2627
    }
2628
2629
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2630
    /**
2631
     *  Set delivery date
2632
     *
2633
     *  @param      User    $user               Object user that modify
2634
     *  @param      int     $delivery_date      Delivery date
2635
     *  @param      int     $notrigger          1=Does not execute triggers, 0= execute triggers
2636
     *  @return     int                         Return integer <0 if ko, >0 if ok
2637
     *  @deprecated Use  setDeliveryDate
2638
     */
2639
    public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2640
    {
2641
		// phpcs:enable
2642
        return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2643
    }
2644
2645
    /**
2646
     *  Set the planned delivery date
2647
     *
2648
     *  @param      User    $user               Object utilisateur qui modifie
2649
     *  @param      int     $delivery_date     Delivery date
2650
     *  @param      int     $notrigger          1=Does not execute triggers, 0= execute triggers
2651
     *  @return     int                         Return integer <0 si ko, >0 si ok
2652
     */
2653
    public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2654
    {
2655
        if ($user->hasRight('commande', 'creer')) {
2656
            $error = 0;
2657
2658
            $this->db->begin();
2659
2660
            $sql = "UPDATE " . MAIN_DB_PREFIX . "commande";
2661
            $sql .= " SET date_livraison = " . ($delivery_date ? "'" . $this->db->idate($delivery_date) . "'" : 'null');
2662
            $sql .= " WHERE rowid = " . ((int) $this->id);
2663
2664
            dol_syslog(__METHOD__, LOG_DEBUG);
2665
            $resql = $this->db->query($sql);
2666
            if (!$resql) {
2667
                $this->errors[] = $this->db->error();
2668
                $error++;
2669
            }
2670
2671
            if (!$error) {
2672
                $this->oldcopy = clone $this;
2673
                $this->delivery_date = $delivery_date;
2674
            }
2675
2676
            if (!$notrigger && empty($error)) {
2677
                // Call trigger
2678
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2679
                if ($result < 0) {
2680
                    $error++;
2681
                }
2682
                // End call triggers
2683
            }
2684
2685
            if (!$error) {
2686
                $this->db->commit();
2687
                return 1;
2688
            } else {
2689
                foreach ($this->errors as $errmsg) {
2690
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2691
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2692
                }
2693
                $this->db->rollback();
2694
                return -1 * $error;
2695
            }
2696
        } else {
2697
            return -2;
2698
        }
2699
    }
2700
2701
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2702
    /**
2703
     *  Return list of orders (eventuelly filtered on a user) into an array
2704
     *
2705
     *  @param      int     $shortlist      0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
2706
     *  @param      int     $draft          0=not draft, 1=draft
2707
     *  @param      User    $excluser       Object user to exclude
2708
     *  @param      int     $socid          Id third party
2709
     *  @param      int     $limit          For pagination
2710
     *  @param      int     $offset         For pagination
2711
     *  @param      string  $sortfield      Sort criteria
2712
     *  @param      string  $sortorder      Sort order
2713
     *  @return     int|array                   -1 if KO, array with result if OK
2714
     */
2715
    public function liste_array($shortlist = 0, $draft = 0, $excluser = null, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'c.date_commande', $sortorder = 'DESC')
2716
    {
2717
		// phpcs:enable
2718
        global $user;
2719
2720
        $ga = array();
2721
2722
        $sql = "SELECT s.rowid, s.nom as name, s.client,";
2723
        $sql .= " c.rowid as cid, c.ref";
2724
        if (!$user->hasRight('societe', 'client', 'voir')) {
2725
            $sql .= ", sc.fk_soc, sc.fk_user";
2726
        }
2727
        $sql .= " FROM " . MAIN_DB_PREFIX . "societe as s, " . MAIN_DB_PREFIX . "commande as c";
2728
        if (!$user->hasRight('societe', 'client', 'voir')) {
2729
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2730
        }
2731
        $sql .= " WHERE c.entity IN (" . getEntity('commande') . ")";
2732
        $sql .= " AND c.fk_soc = s.rowid";
2733
        if (!$user->hasRight('societe', 'client', 'voir')) {
2734
            $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
2735
        }
2736
        if ($socid) {
2737
            $sql .= " AND s.rowid = " . ((int) $socid);
2738
        }
2739
        if ($draft) {
2740
            $sql .= " AND c.fk_statut = " . self::STATUS_DRAFT;
2741
        }
2742
        if (is_object($excluser)) {
2743
            $sql .= " AND c.fk_user_author <> " . ((int) $excluser->id);
2744
        }
2745
        $sql .= $this->db->order($sortfield, $sortorder);
2746
        $sql .= $this->db->plimit($limit, $offset);
2747
2748
        $result = $this->db->query($sql);
2749
        if ($result) {
2750
            $numc = $this->db->num_rows($result);
2751
            if ($numc) {
2752
                $i = 0;
2753
                while ($i < $numc) {
2754
                    $obj = $this->db->fetch_object($result);
2755
2756
                    if ($shortlist == 1) {
2757
                        $ga[$obj->cid] = $obj->ref;
2758
                    } elseif ($shortlist == 2) {
2759
                        $ga[$obj->cid] = $obj->ref . ' (' . $obj->name . ')';
2760
                    } else {
2761
                        $ga[$i]['id'] = $obj->cid;
2762
                        $ga[$i]['ref']  = $obj->ref;
2763
                        $ga[$i]['name'] = $obj->name;
2764
                    }
2765
                    $i++;
2766
                }
2767
            }
2768
            return $ga;
2769
        } else {
2770
            dol_print_error($this->db);
2771
            return -1;
2772
        }
2773
    }
2774
2775
    /**
2776
     *  Update delivery delay
2777
     *
2778
     *  @param      int     $availability_id    Id du nouveau mode
2779
     *  @param      int     $notrigger          1=Does not execute triggers, 0= execute triggers
2780
     *  @return     int                         >0 if OK, <0 if KO
2781
     */
2782
    public function availability($availability_id, $notrigger = 0)
2783
    {
2784
        global $user;
2785
2786
        dol_syslog('Commande::availability(' . $availability_id . ')');
2787
        if ($this->statut >= self::STATUS_DRAFT) {
2788
            $error = 0;
2789
2790
            $this->db->begin();
2791
2792
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande';
2793
            $sql .= ' SET fk_availability = ' . ((int) $availability_id);
2794
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2795
2796
            dol_syslog(__METHOD__, LOG_DEBUG);
2797
            $resql = $this->db->query($sql);
2798
            if (!$resql) {
2799
                $this->errors[] = $this->db->error();
2800
                $error++;
2801
            }
2802
2803
            if (!$error) {
2804
                $this->oldcopy = clone $this;
2805
                $this->availability_id = $availability_id;
2806
            }
2807
2808
            if (!$notrigger && empty($error)) {
2809
                // Call trigger
2810
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2811
                if ($result < 0) {
2812
                    $error++;
2813
                }
2814
                // End call triggers
2815
            }
2816
2817
            if (!$error) {
2818
                $this->db->commit();
2819
                return 1;
2820
            } else {
2821
                foreach ($this->errors as $errmsg) {
2822
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2823
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2824
                }
2825
                $this->db->rollback();
2826
                return -1 * $error;
2827
            }
2828
        } else {
2829
            $error_str = 'Command status do not meet requirement ' . $this->statut;
2830
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
2831
            $this->error = $error_str;
2832
            $this->errors[] = $this->error;
2833
            return -2;
2834
        }
2835
    }
2836
2837
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2838
    /**
2839
     *  Update order demand_reason
2840
     *
2841
     *  @param      int     $demand_reason_id   Id of new demand
2842
     *  @param      int     $notrigger          1=Does not execute triggers, 0= execute triggers
2843
     *  @return     int                         >0 if ok, <0 if ko
2844
     */
2845
    public function demand_reason($demand_reason_id, $notrigger = 0)
2846
    {
2847
		// phpcs:enable
2848
        global $user;
2849
2850
        dol_syslog('Commande::demand_reason(' . $demand_reason_id . ')');
2851
        if ($this->statut >= self::STATUS_DRAFT) {
2852
            $error = 0;
2853
2854
            $this->db->begin();
2855
2856
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande';
2857
            $sql .= ' SET fk_input_reason = ' . ((int) $demand_reason_id);
2858
            $sql .= ' WHERE rowid=' . ((int) $this->id);
2859
2860
            dol_syslog(__METHOD__, LOG_DEBUG);
2861
            $resql = $this->db->query($sql);
2862
            if (!$resql) {
2863
                $this->errors[] = $this->db->error();
2864
                $error++;
2865
            }
2866
2867
            if (!$error) {
2868
                $this->oldcopy = clone $this;
2869
                $this->demand_reason_id = $demand_reason_id;
2870
            }
2871
2872
            if (!$notrigger && empty($error)) {
2873
                // Call trigger
2874
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2875
                if ($result < 0) {
2876
                    $error++;
2877
                }
2878
                // End call triggers
2879
            }
2880
2881
            if (!$error) {
2882
                $this->db->commit();
2883
                return 1;
2884
            } else {
2885
                foreach ($this->errors as $errmsg) {
2886
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2887
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2888
                }
2889
                $this->db->rollback();
2890
                return -1 * $error;
2891
            }
2892
        } else {
2893
            $error_str = 'order status do not meet requirement ' . $this->statut;
2894
            dol_syslog(__METHOD__ . $error_str, LOG_ERR);
2895
            $this->error = $error_str;
2896
            $this->errors[] = $this->error;
2897
            return -2;
2898
        }
2899
    }
2900
2901
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2902
    /**
2903
     *  Set customer ref
2904
     *
2905
     *  @param      User    $user           User that make change
2906
     *  @param      string  $ref_client     Customer ref
2907
     *  @param      int     $notrigger      1=Does not execute triggers, 0= execute triggers
2908
     *  @return     int                     Return integer <0 if KO, >0 if OK
2909
     */
2910
    public function set_ref_client($user, $ref_client, $notrigger = 0)
2911
    {
2912
		// phpcs:enable
2913
        if ($user->hasRight('commande', 'creer')) {
2914
            $error = 0;
2915
2916
            $this->db->begin();
2917
2918
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande SET';
2919
            $sql .= ' ref_client = ' . (empty($ref_client) ? 'NULL' : "'" . $this->db->escape($ref_client) . "'");
2920
            $sql .= ' WHERE rowid = ' . ((int) $this->id);
2921
2922
            dol_syslog(__METHOD__ . ' this->id=' . $this->id . ', ref_client=' . $ref_client, LOG_DEBUG);
2923
            $resql = $this->db->query($sql);
2924
            if (!$resql) {
2925
                $this->errors[] = $this->db->error();
2926
                $error++;
2927
            }
2928
2929
            if (!$error) {
2930
                $this->oldcopy = clone $this;
2931
                $this->ref_client = $ref_client;
2932
                $this->ref_customer = $ref_client;
2933
            }
2934
2935
            if (!$notrigger && empty($error)) {
2936
                // Call trigger
2937
                $result = $this->call_trigger('ORDER_MODIFY', $user);
2938
                if ($result < 0) {
2939
                    $error++;
2940
                }
2941
                // End call triggers
2942
            }
2943
            if (!$error) {
2944
                $this->db->commit();
2945
                return 1;
2946
            } else {
2947
                foreach ($this->errors as $errmsg) {
2948
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2949
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2950
                }
2951
                $this->db->rollback();
2952
                return -1 * $error;
2953
            }
2954
        } else {
2955
            return -1;
2956
        }
2957
    }
2958
2959
    /**
2960
     * Classify the order as invoiced
2961
     *
2962
     * @param   User    $user       Object user making the change
2963
     * @param   int     $notrigger  1=Does not execute triggers, 0=execute triggers
2964
     * @return  int                 Return integer <0 if KO, 0 if already billed,  >0 if OK
2965
     */
2966
    public function classifyBilled(User $user, $notrigger = 0)
2967
    {
2968
        $error = 0;
2969
2970
        if ($this->billed) {
2971
            return 0;
2972
        }
2973
2974
        $this->db->begin();
2975
2976
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande SET facture = 1';
2977
        $sql .= " WHERE rowid = " . ((int) $this->id) . ' AND fk_statut > ' . self::STATUS_DRAFT;
2978
2979
        dol_syslog(get_class($this) . "::classifyBilled", LOG_DEBUG);
2980
        if ($this->db->query($sql)) {
2981
            if (!$error) {
2982
                $this->oldcopy = clone $this;
2983
                $this->billed = 1;
2984
            }
2985
2986
            if (!$notrigger && empty($error)) {
2987
                // Call trigger
2988
                $result = $this->call_trigger('ORDER_CLASSIFY_BILLED', $user);
2989
                if ($result < 0) {
2990
                    $error++;
2991
                }
2992
                // End call triggers
2993
            }
2994
2995
            if (!$error) {
2996
                $this->db->commit();
2997
                return 1;
2998
            } else {
2999
                foreach ($this->errors as $errmsg) {
3000
                    dol_syslog(get_class($this) . "::classifyBilled " . $errmsg, LOG_ERR);
3001
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
3002
                }
3003
                $this->db->rollback();
3004
                return -1 * $error;
3005
            }
3006
        } else {
3007
            $this->error = $this->db->error();
3008
            $this->db->rollback();
3009
            return -1;
3010
        }
3011
    }
3012
3013
    /**
3014
     * Classify the order as not invoiced
3015
     *
3016
     * @param   User    $user       Object user making the change
3017
     * @param   int     $notrigger  1=Does not execute triggers, 0=execute triggers
3018
     * @return  int                 Return integer <0 if ko, >0 if ok
3019
     */
3020
    public function classifyUnBilled(User $user, $notrigger = 0)
3021
    {
3022
        $error = 0;
3023
3024
        $this->db->begin();
3025
3026
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'commande SET facture = 0';
3027
        $sql .= " WHERE rowid = " . ((int) $this->id) . ' AND fk_statut > ' . self::STATUS_DRAFT;
3028
3029
        dol_syslog(get_class($this) . "::classifyUnBilled", LOG_DEBUG);
3030
        if ($this->db->query($sql)) {
3031
            if (!$error) {
3032
                $this->oldcopy = clone $this;
3033
                $this->billed = 1;
3034
            }
3035
3036
            if (!$notrigger && empty($error)) {
3037
                // Call trigger
3038
                $result = $this->call_trigger('ORDER_CLASSIFY_UNBILLED', $user);
3039
                if ($result < 0) {
3040
                    $error++;
3041
                }
3042
                // End call triggers
3043
            }
3044
3045
            if (!$error) {
3046
                $this->billed = 0;
3047
3048
                $this->db->commit();
3049
                return 1;
3050
            } else {
3051
                foreach ($this->errors as $errmsg) {
3052
                    dol_syslog(get_class($this) . "::classifyUnBilled " . $errmsg, LOG_ERR);
3053
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
3054
                }
3055
                $this->db->rollback();
3056
                return -1 * $error;
3057
            }
3058
        } else {
3059
            $this->error = $this->db->error();
3060
            $this->db->rollback();
3061
            return -1;
3062
        }
3063
    }
3064
3065
3066
    /**
3067
     *  Update a line in database
3068
     *
3069
     *  @param      int             $rowid              Id of line to update
3070
     *  @param      string          $desc               Description of line
3071
     *  @param      float           $pu                 Unit price
3072
     *  @param      float           $qty                Quantity
3073
     *  @param      float           $remise_percent     Percent of discount
3074
     *  @param      float           $txtva              Taux TVA
3075
     *  @param      float           $txlocaltax1        Local tax 1 rate
3076
     *  @param      float           $txlocaltax2        Local tax 2 rate
3077
     *  @param      string          $price_base_type    HT or TTC
3078
     *  @param      int             $info_bits          Miscellaneous information on line
3079
     *  @param      int|string      $date_start         Start date of the line
3080
     *  @param      int|string      $date_end           End date of the line
3081
     *  @param      int             $type               Type of line (0=product, 1=service)
3082
     *  @param      int             $fk_parent_line     Id of parent line (0 in most cases, used by modules adding sublevels into lines).
3083
     *  @param      int             $skip_update_total  Keep fields total_xxx to 0 (used for special lines by some modules)
3084
     *  @param      int             $fk_fournprice      Id of origin supplier price
3085
     *  @param      int             $pa_ht              Price (without tax) of product when it was bought
3086
     *  @param      string          $label              Label
3087
     *  @param      int             $special_code       Special code (also used by externals modules!)
3088
     *  @param      array           $array_options      extrafields array
3089
     *  @param      int|null        $fk_unit            Code of the unit to use. Null to use the default one
3090
     *  @param      double          $pu_ht_devise       Amount in currency
3091
     *  @param      int             $notrigger          disable line update trigger
3092
     *  @param      string          $ref_ext            external reference
3093
     * @param       integer $rang   line rank
3094
     *  @return     int                                 Return integer < 0 if KO, > 0 if OK
3095
     */
3096
    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)
3097
    {
3098
        global $conf, $mysoc, $langs, $user;
3099
3100
        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");
3101
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
3102
3103
        if ($this->statut == Commande::STATUS_DRAFT) {
3104
            // Clean parameters
3105
            if (empty($qty)) {
3106
                $qty = 0;
3107
            }
3108
            if (empty($info_bits)) {
3109
                $info_bits = 0;
3110
            }
3111
            if (empty($txtva)) {
3112
                $txtva = 0;
3113
            }
3114
            if (empty($txlocaltax1)) {
3115
                $txlocaltax1 = 0;
3116
            }
3117
            if (empty($txlocaltax2)) {
3118
                $txlocaltax2 = 0;
3119
            }
3120
            if (empty($remise_percent)) {
3121
                $remise_percent = 0;
3122
            }
3123
            if (empty($special_code) || $special_code == 3) {
3124
                $special_code = 0;
3125
            }
3126
            if (empty($ref_ext)) {
3127
                $ref_ext = '';
3128
            }
3129
3130
            if ($date_start && $date_end && $date_start > $date_end) {
3131
                $langs->load("errors");
3132
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3133
                return -1;
3134
            }
3135
3136
            $remise_percent = (float) price2num($remise_percent);
3137
            $qty = (float) price2num($qty);
3138
            $pu = price2num($pu);
3139
            $pa_ht = (float) price2num($pa_ht);
3140
            $pu_ht_devise = price2num($pu_ht_devise);
3141
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
3142
                $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3143
            }
3144
            $txlocaltax1 = (float) price2num($txlocaltax1);
3145
            $txlocaltax2 = (float) price2num($txlocaltax2);
3146
3147
            $this->db->begin();
3148
3149
            // Calcul du total TTC et de la TVA pour la ligne a partir de
3150
            // qty, pu, remise_percent et txtva
3151
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3152
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3153
3154
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3155
3156
            // Clean vat code
3157
            $vat_src_code = '';
3158
            $reg = array();
3159
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
3160
                $vat_src_code = $reg[1];
3161
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
3162
            }
3163
3164
            $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);
3165
3166
            $total_ht = $tabprice[0];
3167
            $total_tva = $tabprice[1];
3168
            $total_ttc = $tabprice[2];
3169
            $total_localtax1 = $tabprice[9];
3170
            $total_localtax2 = $tabprice[10];
3171
            $pu_ht = $tabprice[3];
3172
            $pu_tva = $tabprice[4];
3173
            $pu_ttc = $tabprice[5];
3174
3175
            // MultiCurrency
3176
            $multicurrency_total_ht = $tabprice[16];
3177
            $multicurrency_total_tva = $tabprice[17];
3178
            $multicurrency_total_ttc = $tabprice[18];
3179
            $pu_ht_devise = $tabprice[19];
3180
3181
            // Anciens indicateurs: $price, $subprice (a ne plus utiliser)
3182
            $price = $pu_ht;
3183
            if ($price_base_type == 'TTC') {
3184
                $subprice = $pu_ttc;
3185
            } else {
3186
                $subprice = $pu_ht;
3187
            }
3188
            $remise = 0;
3189
            if ($remise_percent > 0) {
3190
                $remise = round(((float) $pu * $remise_percent / 100), 2);
3191
                $price = ((float) $pu - $remise);
3192
            }
3193
3194
            //Fetch current line from the database and then clone the object and set it in $oldline property
3195
            $line = new OrderLine($this->db);
3196
            $line->fetch($rowid);
3197
            $line->fetch_optionals();
3198
3199
            if (!empty($line->fk_product)) {
3200
                $product = new Product($this->db);
3201
                $result = $product->fetch($line->fk_product);
3202
                $product_type = $product->type;
3203
3204
                if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product_type == 0 && $product->stock_reel < $qty) {
3205
                    $langs->load("errors");
3206
                    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
3207
                    $this->errors[] = $this->error;
3208
3209
                    dol_syslog(get_class($this) . "::addline error=Product " . $product->ref . ": " . $this->error, LOG_ERR);
3210
3211
                    $this->db->rollback();
3212
                    return self::STOCK_NOT_ENOUGH_FOR_ORDER;
3213
                }
3214
            }
3215
3216
            $staticline = clone $line;
3217
3218
            $line->oldline = $staticline;
3219
            $this->line = $line;
3220
            $this->line->context = $this->context;
3221
            $this->line->rang = $rang;
3222
3223
            // Reorder if fk_parent_line change
3224
            if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
3225
                $rangmax = $this->line_max($fk_parent_line);
3226
                $this->line->rang = $rangmax + 1;
3227
            }
3228
3229
            $this->line->id = $rowid;
3230
            $this->line->label = $label;
3231
            $this->line->desc = $desc;
3232
            $this->line->qty = $qty;
3233
            $this->line->ref_ext = $ref_ext;
3234
3235
            $this->line->vat_src_code = $vat_src_code;
3236
            $this->line->tva_tx         = $txtva;
0 ignored issues
show
Documentation Bug introduced by
It seems like $txtva can also be of type string. However, the property $tva_tx is declared as type double. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3237
            $this->line->localtax1_tx   = $txlocaltax1;
3238
            $this->line->localtax2_tx   = $txlocaltax2;
3239
            $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3240
            $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3241
            $this->line->remise_percent = $remise_percent;
3242
            $this->line->subprice       = $pu_ht;
3243
            $this->line->info_bits      = $info_bits;
3244
            $this->line->special_code   = $special_code;
3245
            $this->line->total_ht       = $total_ht;
3246
            $this->line->total_tva      = $total_tva;
3247
            $this->line->total_localtax1 = $total_localtax1;
3248
            $this->line->total_localtax2 = $total_localtax2;
3249
            $this->line->total_ttc      = $total_ttc;
3250
            $this->line->date_start     = $date_start;
3251
            $this->line->date_end       = $date_end;
3252
            $this->line->product_type   = $type;
3253
            $this->line->fk_parent_line = $fk_parent_line;
3254
            $this->line->skip_update_total = $skip_update_total;
3255
            $this->line->fk_unit        = $fk_unit;
3256
3257
            $this->line->fk_fournprice = $fk_fournprice;
3258
            $this->line->pa_ht = $pa_ht;
3259
3260
            // Multicurrency
3261
            $this->line->multicurrency_subprice     = $pu_ht_devise;
3262
            $this->line->multicurrency_total_ht     = $multicurrency_total_ht;
3263
            $this->line->multicurrency_total_tva    = $multicurrency_total_tva;
3264
            $this->line->multicurrency_total_ttc    = $multicurrency_total_ttc;
3265
3266
            // TODO deprecated
3267
            $this->line->price = $price;
3268
3269
            if (is_array($array_options) && count($array_options) > 0) {
3270
                // We replace values in this->line->array_options only for entries defined into $array_options
3271
                foreach ($array_options as $key => $value) {
3272
                    $this->line->array_options[$key] = $array_options[$key];
3273
                }
3274
            }
3275
3276
            $result = $this->line->update($user, $notrigger);
3277
            if ($result > 0) {
3278
                // Reorder if child line
3279
                if (!empty($fk_parent_line)) {
3280
                    $this->line_order(true, 'DESC');
3281
                }
3282
3283
                // Mise a jour info denormalisees
3284
                $this->update_price(1, 'auto');
3285
3286
                $this->db->commit();
3287
                return $result;
3288
            } else {
3289
                $this->error = $this->line->error;
3290
3291
                $this->db->rollback();
3292
                return -1;
3293
            }
3294
        } else {
3295
            $this->error = get_class($this) . "::updateline Order status makes operation forbidden";
3296
            $this->errors = array('OrderStatusMakeOperationForbidden');
3297
            return -2;
3298
        }
3299
    }
3300
3301
    /**
3302
     *      Update database
3303
     *
3304
     *      @param      User    $user           User that modify
3305
     *      @param      int     $notrigger      0=launch triggers after, 1=disable triggers
3306
     *      @return     int                     Return integer <0 if KO, >0 if OK
3307
     */
3308
    public function update(User $user, $notrigger = 0)
3309
    {
3310
        global $conf;
3311
3312
        $error = 0;
3313
3314
        // Clean parameters
3315
        if (isset($this->ref)) {
3316
            $this->ref = trim($this->ref);
3317
        }
3318
        if (isset($this->ref_client)) {
3319
            $this->ref_client = trim($this->ref_client);
3320
        }
3321
        if (isset($this->ref_customer)) {
3322
            $this->ref_customer = trim($this->ref_customer);
3323
        }
3324
        if (isset($this->note) || isset($this->note_private)) {
3325
            $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
3326
        }
3327
        if (isset($this->note_public)) {
3328
            $this->note_public = trim($this->note_public);
3329
        }
3330
        if (isset($this->model_pdf)) {
3331
            $this->model_pdf = trim($this->model_pdf);
3332
        }
3333
        if (isset($this->import_key)) {
3334
            $this->import_key = trim($this->import_key);
3335
        }
3336
        $delivery_date = $this->delivery_date;
3337
3338
        // Check parameters
3339
        // Put here code to add control on parameters values
3340
3341
        // Update request
3342
        $sql = "UPDATE " . MAIN_DB_PREFIX . "commande SET";
3343
3344
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
3345
        $sql .= " ref_client=" . (isset($this->ref_client) ? "'" . $this->db->escape($this->ref_client) . "'" : "null") . ",";
3346
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
3347
        $sql .= " fk_soc=" . (isset($this->socid) ? $this->socid : "null") . ",";
3348
        $sql .= " date_commande=" . (strval($this->date_commande) != '' ? "'" . $this->db->idate($this->date_commande) . "'" : 'null') . ",";
3349
        $sql .= " date_valid=" . (strval($this->date_validation) != '' ? "'" . $this->db->idate($this->date_validation) . "'" : 'null') . ",";
3350
        $sql .= " total_tva=" . (isset($this->total_tva) ? $this->total_tva : "null") . ",";
3351
        $sql .= " localtax1=" . (isset($this->total_localtax1) ? $this->total_localtax1 : "null") . ",";
3352
        $sql .= " localtax2=" . (isset($this->total_localtax2) ? $this->total_localtax2 : "null") . ",";
3353
        $sql .= " total_ht=" . (isset($this->total_ht) ? $this->total_ht : "null") . ",";
3354
        $sql .= " total_ttc=" . (isset($this->total_ttc) ? $this->total_ttc : "null") . ",";
3355
        $sql .= " fk_statut=" . (isset($this->statut) ? $this->statut : "null") . ",";
3356
        $sql .= " fk_user_modif=" . (isset($user->id) ? $user->id : "null") . ",";
3357
        $sql .= " fk_user_valid=" . ((isset($this->user_validation_id) && $this->user_validation_id > 0) ? $this->user_validation_id : "null") . ",";
3358
        $sql .= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
3359
        $sql .= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null") . ",";
3360
        $sql .= " deposit_percent=" . (!empty($this->deposit_percent) ? strval($this->deposit_percent) : "null") . ",";
3361
        $sql .= " fk_mode_reglement=" . (isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null") . ",";
3362
        $sql .= " date_livraison=" . (strval($this->delivery_date) != '' ? "'" . $this->db->idate($this->delivery_date) . "'" : 'null') . ",";
3363
        $sql .= " fk_shipping_method=" . (isset($this->shipping_method_id) ? $this->shipping_method_id : "null") . ",";
3364
        $sql .= " fk_account=" . ($this->fk_account > 0 ? $this->fk_account : "null") . ",";
3365
        $sql .= " fk_input_reason=" . ($this->demand_reason_id > 0 ? $this->demand_reason_id : "null") . ",";
3366
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
3367
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
3368
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
3369
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null");
3370
3371
        $sql .= " WHERE rowid=" . ((int) $this->id);
3372
3373
        $this->db->begin();
3374
3375
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
3376
        $resql = $this->db->query($sql);
3377
        if (!$resql) {
3378
            $error++;
3379
            $this->errors[] = "Error " . $this->db->lasterror();
3380
        }
3381
3382
        if (!$error) {
3383
            $result = $this->insertExtraFields();
3384
            if ($result < 0) {
3385
                $error++;
3386
            }
3387
        }
3388
3389
        if (!$error && !$notrigger) {
3390
            // Call trigger
3391
            $result = $this->call_trigger('ORDER_MODIFY', $user);
3392
            if ($result < 0) {
3393
                $error++;
3394
            }
3395
            // End call triggers
3396
        }
3397
3398
        // Commit or rollback
3399
        if ($error) {
3400
            foreach ($this->errors as $errmsg) {
3401
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
3402
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
3403
            }
3404
            $this->db->rollback();
3405
            return -1 * $error;
3406
        } else {
3407
            $this->db->commit();
3408
            return 1;
3409
        }
3410
    }
3411
3412
    /**
3413
     *  Delete the sales order
3414
     *
3415
     *  @param  User    $user       User object
3416
     *  @param  int     $notrigger  1=Does not execute triggers, 0= execute triggers
3417
     *  @return int                 Return integer <=0 if KO, >0 if OK
3418
     */
3419
    public function delete($user, $notrigger = 0)
3420
    {
3421
        global $conf, $langs;
3422
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
3423
3424
        $error = 0;
3425
3426
        dol_syslog(get_class($this) . "::delete " . $this->id, LOG_DEBUG);
3427
3428
        $this->db->begin();
3429
3430
        if (!$notrigger) {
3431
            // Call trigger
3432
            $result = $this->call_trigger('ORDER_DELETE', $user);
3433
            if ($result < 0) {
3434
                $error++;
3435
            }
3436
            // End call triggers
3437
        }
3438
3439
        // Test we can delete
3440
        if ($this->countNbOfShipments() != 0) {
3441
            $this->errors[] = $langs->trans('SomeShipmentExists');
3442
            $error++;
3443
        }
3444
3445
        // Delete extrafields of lines and lines
3446
        if (!$error && !empty($this->table_element_line)) {
3447
            $tabletodelete = $this->table_element_line;
3448
            $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) . ")";
3449
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $tabletodelete . " WHERE " . $this->fk_element . " = " . ((int) $this->id);
3450
            if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3451
                $error++;
3452
                $this->error = $this->db->lasterror();
3453
                $this->errors[] = $this->error;
3454
                dol_syslog(get_class($this) . "::delete error " . $this->error, LOG_ERR);
3455
            }
3456
        }
3457
3458
        if (!$error) {
3459
            // Delete linked object
3460
            $res = $this->deleteObjectLinked();
3461
            if ($res < 0) {
3462
                $error++;
3463
            }
3464
        }
3465
3466
        if (!$error) {
3467
            // Delete linked contacts
3468
            $res = $this->delete_linked_contact();
3469
            if ($res < 0) {
3470
                $error++;
3471
            }
3472
        }
3473
3474
        // Removed extrafields of object
3475
        if (!$error) {
3476
            $result = $this->deleteExtraFields();
3477
            if ($result < 0) {
3478
                $error++;
3479
                dol_syslog(get_class($this) . "::delete error " . $this->error, LOG_ERR);
3480
            }
3481
        }
3482
3483
        // Delete main record
3484
        if (!$error) {
3485
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . $this->table_element . " WHERE rowid = " . ((int) $this->id);
3486
            $res = $this->db->query($sql);
3487
            if (!$res) {
3488
                $error++;
3489
                $this->error = $this->db->lasterror();
3490
                $this->errors[] = $this->error;
3491
                dol_syslog(get_class($this) . "::delete error " . $this->error, LOG_ERR);
3492
            }
3493
        }
3494
3495
        // Delete record into ECM index and physically
3496
        if (!$error) {
3497
            $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3498
            $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3499
            if (!$res) {
3500
                $error++;
3501
            }
3502
        }
3503
3504
        if (!$error) {
3505
            // We remove directory
3506
            $ref = dol_sanitizeFileName($this->ref);
3507
            if ($conf->commande->multidir_output[$this->entity] && !empty($this->ref)) {
3508
                $dir = $conf->commande->multidir_output[$this->entity] . "/" . $ref;
3509
                $file = $dir . "/" . $ref . ".pdf";
3510
                if (file_exists($file)) {
3511
                    dol_delete_preview($this);
3512
3513
                    if (!dol_delete_file($file, 0, 0, 0, $this)) {
3514
                        $this->error = 'ErrorFailToDeleteFile';
3515
                        $this->errors[] = $this->error;
3516
                        $this->db->rollback();
3517
                        return 0;
3518
                    }
3519
                }
3520
                if (file_exists($dir)) {
3521
                    $res = @dol_delete_dir_recursive($dir);
3522
                    if (!$res) {
3523
                        $this->error = 'ErrorFailToDeleteDir';
3524
                        $this->errors[] = $this->error;
3525
                        $this->db->rollback();
3526
                        return 0;
3527
                    }
3528
                }
3529
            }
3530
        }
3531
3532
        if (!$error) {
3533
            dol_syslog(get_class($this) . "::delete " . $this->id . " by " . $user->id, LOG_DEBUG);
3534
            $this->db->commit();
3535
            return 1;
3536
        } else {
3537
            $this->db->rollback();
3538
            return -1;
3539
        }
3540
    }
3541
3542
3543
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3544
    /**
3545
     *  Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3546
     *
3547
     *  @param      User    $user   Object user
3548
     *  @param      string  $mode   Mode ('toship', 'tobill', 'shippedtobill')
3549
     *  @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
3550
     */
3551
    public function load_board($user, $mode)
3552
    {
3553
		// phpcs:enable
3554
        global $conf, $langs;
3555
3556
        $clause = " WHERE";
3557
3558
        $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.date_livraison as delivery_date, c.fk_statut, c.total_ht";
3559
        $sql .= " FROM " . MAIN_DB_PREFIX . "commande as c";
3560
        if (!$user->hasRight('societe', 'client', 'voir')) {
3561
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON c.fk_soc = sc.fk_soc";
3562
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
3563
            $clause = " AND";
3564
        }
3565
        $sql .= $clause . " c.entity IN (" . getEntity('commande') . ")";
3566
        //$sql.= " AND c.fk_statut IN (1,2,3) AND c.facture = 0";
3567
        if ($mode == 'toship') {
3568
            // An order to ship is an open order (validated or in progress)
3569
            $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ")";
3570
        }
3571
        if ($mode == 'tobill') {
3572
            // An order to bill is an order not already billed
3573
            $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ", " . self::STATUS_CLOSED . ") AND c.facture = 0";
3574
        }
3575
        if ($mode == 'shippedtobill') {
3576
            // An order shipped and to bill is a delivered order not already billed
3577
            $sql .= " AND c.fk_statut IN (" . self::STATUS_CLOSED . ") AND c.facture = 0";
3578
        }
3579
        if ($user->socid) {
3580
            $sql .= " AND c.fk_soc = " . ((int) $user->socid);
3581
        }
3582
3583
        $resql = $this->db->query($sql);
3584
        if ($resql) {
3585
            $delay_warning = 0;
3586
            $label = $labelShort = $url = '';
3587
            if ($mode == 'toship') {
3588
                $delay_warning = $conf->commande->client->warning_delay / 60 / 60 / 24;
3589
                $url = constant('BASE_URL') . '/commande/list.php?search_status=-2&mainmenu=commercial&leftmenu=orders';
3590
                $label = $langs->transnoentitiesnoconv("OrdersToProcess");
3591
                $labelShort = $langs->transnoentitiesnoconv("Opened");
3592
            }
3593
            if ($mode == 'tobill') {
3594
                $url = constant('BASE_URL') . '/commande/list.php?search_status=-3&search_billed=0&mainmenu=commercial&leftmenu=orders';
3595
                $label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
3596
                $labelShort = $langs->trans("ToBill");
3597
            }
3598
            if ($mode == 'shippedtobill') {
3599
                $url = constant('BASE_URL') . '/commande/list.php?search_status=3&search_billed=0&mainmenu=commercial&leftmenu=orders';
3600
                $label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
3601
                $labelShort = $langs->trans("StatusOrderDelivered") . ' ' . $langs->trans("and") . ' ' . $langs->trans("ToBill");
3602
            }
3603
3604
            $response = new WorkboardResponse();
3605
            $response->warning_delay = $delay_warning;
3606
            $response->label = $label;
3607
            $response->labelShort = $labelShort;
3608
            $response->url = $url;
3609
            $response->img = img_object('', "order");
3610
3611
            $generic_commande = new Commande($this->db);
3612
3613
            while ($obj = $this->db->fetch_object($resql)) {
3614
                $response->nbtodo++;
3615
                $response->total += $obj->total_ht;
3616
3617
                $generic_commande->statut = $obj->fk_statut;
3618
                $generic_commande->date_commande = $this->db->jdate($obj->date_commande);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->date_commande) can also be of type string. However, the property $date_commande is declared as type integer. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3619
                $generic_commande->date = $this->db->jdate($obj->date_commande);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->date_commande) can also be of type string. However, the property $date is declared as type integer. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3620
                $generic_commande->delivery_date = $this->db->jdate($obj->delivery_date);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->delivery_date) can also be of type string. However, the property $delivery_date is declared as type integer. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3621
3622
                if ($mode == 'toship' && $generic_commande->hasDelay()) {
3623
                    $response->nbtodolate++;
3624
                }
3625
            }
3626
3627
            return $response;
3628
        } else {
3629
            $this->error = $this->db->error();
3630
            return -1;
3631
        }
3632
    }
3633
3634
    /**
3635
     *  Return source label of order
3636
     *
3637
     *  @return     string      Label
3638
     */
3639
    public function getLabelSource()
3640
    {
3641
        global $langs;
3642
3643
        $label = $langs->trans('OrderSource' . $this->source);
3644
3645
        if ($label == 'OrderSource') {
3646
            return '';
3647
        }
3648
        return $label;
3649
    }
3650
3651
    /**
3652
     *  Return status label of Order
3653
     *
3654
     *  @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
3655
     *  @return     string              Label of status
3656
     */
3657
    public function getLibStatut($mode)
3658
    {
3659
        return $this->LibStatut($this->statut, $this->billed, $mode);
3660
    }
3661
3662
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3663
    /**
3664
     *  Return label of status
3665
     *
3666
     *  @param      int     $status           Id status
3667
     *  @param      int     $billed           If invoiced
3668
     *  @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
3669
     *  @param      int     $donotshowbilled  Do not show billed status after order status
3670
     *  @return     string                    Label of status
3671
     */
3672
    public function LibStatut($status, $billed, $mode, $donotshowbilled = 0)
3673
    {
3674
		// phpcs:enable
3675
        global $langs, $hookmanager;
3676
3677
        $billedtext = '';
3678
        if (empty($donotshowbilled)) {
3679
            $billedtext .= ($billed ? ' - ' . $langs->transnoentitiesnoconv("Billed") : '');
3680
        }
3681
3682
        $labelTooltip = '';
3683
3684
        if ($status == self::STATUS_CANCELED) {
3685
            $labelStatus = $langs->transnoentitiesnoconv('StatusOrderCanceled');
3686
            $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderCanceledShort');
3687
            $statusType = 'status9';
3688
        } elseif ($status == self::STATUS_DRAFT) {
3689
            $labelStatus = $langs->transnoentitiesnoconv('StatusOrderDraft');
3690
            $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDraftShort');
3691
            $statusType = 'status0';
3692
        } elseif ($status == self::STATUS_VALIDATED) {
3693
            $labelStatus = $langs->transnoentitiesnoconv('StatusOrderValidated') . $billedtext;
3694
            $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderValidatedShort') . $billedtext;
3695
            $statusType = 'status1';
3696
        } elseif ($status == self::STATUS_SHIPMENTONPROCESS) {
3697
            $labelStatus = $langs->transnoentitiesnoconv('StatusOrderSent') . $billedtext;
3698
            $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderSentShort') . $billedtext;
3699
            $labelTooltip = $langs->transnoentitiesnoconv("StatusOrderSent");
3700
            if (!empty($this->delivery_date)) {
3701
                $labelTooltip .= ' - ' . $langs->transnoentitiesnoconv("DateDeliveryPlanned") . dol_print_date($this->delivery_date, 'day') . $billedtext;
3702
            }
3703
            $statusType = 'status4';
3704
        } elseif ($status == self::STATUS_CLOSED) {
3705
            $labelStatus = $langs->transnoentitiesnoconv('StatusOrderDelivered') . $billedtext;
3706
            $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDeliveredShort') . $billedtext;
3707
            $statusType = 'status6';
3708
        } else {
3709
            $labelStatus = $langs->transnoentitiesnoconv('Unknown');
3710
            $labelStatusShort = '';
3711
            $statusType = '';
3712
            $mode = 0;
3713
        }
3714
3715
        $parameters = array(
3716
            'status'          => $status,
3717
            'mode'            => $mode,
3718
            'billed'          => $billed,
3719
            'donotshowbilled' => $donotshowbilled
3720
        );
3721
3722
        $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3723
3724
        if ($reshook > 0) {
3725
            return $hookmanager->resPrint;
3726
        }
3727
3728
        return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', array('tooltip' => $labelTooltip));
3729
    }
3730
3731
    /**
3732
     * getTooltipContentArray
3733
     *
3734
     * @param array $params params to construct tooltip data
3735
     * @return array
3736
     */
3737
    public function getTooltipContentArray($params)
3738
    {
3739
        global $conf, $langs, $user;
3740
3741
        $langs->load('orders');
3742
        $datas = [];
3743
        $nofetch = !empty($params['nofetch']);
3744
3745
        if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3746
            return ['optimize' => $langs->trans("Order")];
3747
        }
3748
3749
        if ($user->hasRight('commande', 'lire')) {
3750
            $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("Order") . '</u>';
3751
            if (isset($this->statut)) {
3752
                $datas['status'] = ' ' . $this->getLibStatut(5);
3753
            }
3754
            $datas['Ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
3755
            if (!$nofetch) {
3756
                $langs->load('companies');
3757
                if (empty($this->thirdparty)) {
3758
                    $this->fetch_thirdparty();
3759
                }
3760
                $datas['customer'] = '<br><b>' . $langs->trans('Customer') . ':</b> ' . $this->thirdparty->getNomUrl(1, '', 0, 1);
0 ignored issues
show
Bug introduced by
The method getNomUrl() does not exist on null. ( Ignorable by Annotation )

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

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

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

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

Loading history...
3761
            }
3762
            $datas['RefCustomer'] = '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . (empty($this->ref_customer) ? (empty($this->ref_client) ? '' : $this->ref_client) : $this->ref_customer);
3763
            if (!$nofetch) {
3764
                $langs->load('project');
3765
                if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3766
                    $res = $this->fetch_project();
3767
                    if ($res > 0 && $this->project instanceof Project) {
3768
                        $datas['project'] = '<br><b>' . $langs->trans('Project') . ':</b> ' . $this->project->getNomUrl(1, '', 0, 1);
3769
                    }
3770
                }
3771
            }
3772
            if (!empty($this->total_ht)) {
3773
                $datas['AmountHT'] = '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3774
            }
3775
            if (!empty($this->total_tva)) {
3776
                $datas['VAT'] = '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3777
            }
3778
            if (!empty($this->total_ttc)) {
3779
                $datas['AmountTTC'] = '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3780
            }
3781
            if (!empty($this->date)) {
3782
                $datas['Date'] = '<br><b>' . $langs->trans('Date') . ':</b> ' . dol_print_date($this->date, 'day');
3783
            }
3784
            if (!empty($this->delivery_date)) {
3785
                $datas['DeliveryDate'] = '<br><b>' . $langs->trans('DeliveryDate') . ':</b> ' . dol_print_date($this->delivery_date, 'dayhour');
3786
            }
3787
        }
3788
3789
        return $datas;
3790
    }
3791
3792
    /**
3793
     *  Return clicable link of object (with eventually picto)
3794
     *
3795
     *  @param      int         $withpicto                Add picto into link
3796
     *  @param      string      $option                   Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3797
     *  @param      int         $max                      Max length to show
3798
     *  @param      int         $short                    ???
3799
     *  @param      int         $notooltip                1=Disable tooltip
3800
     *  @param      int         $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
3801
     *  @param      int         $addlinktonotes           Add link to notes
3802
     *  @param      string      $target                   attribute target for link
3803
     *  @return     string                                String with URL
3804
     */
3805
    public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0, $target = '')
3806
    {
3807
        global $conf, $langs, $user, $hookmanager;
3808
3809
        if (!empty($conf->dol_no_mouse_hover)) {
3810
            $notooltip = 1; // Force disable tooltips
3811
        }
3812
3813
        $result = '';
3814
3815
        if (isModEnabled("shipping") && ($option == '1' || $option == '2')) {
3816
            $url = constant('BASE_URL') . '/expedition/shipment.php?id=' . $this->id;
3817
        } else {
3818
            $url = constant('BASE_URL') . '/commande/card.php?id=' . $this->id;
3819
        }
3820
3821
        if (!$user->hasRight('commande', 'lire')) {
3822
            $option = 'nolink';
3823
        }
3824
3825
        if ($option !== 'nolink') {
3826
            // Add param to save lastsearch_values or not
3827
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3828
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3829
                $add_save_lastsearch_values = 1;
3830
            }
3831
            if ($add_save_lastsearch_values) {
3832
                $url .= '&save_lastsearch_values=1';
3833
            }
3834
        }
3835
3836
        if ($short) {
3837
            return $url;
3838
        }
3839
        $params = [
3840
            'id' => $this->id,
3841
            'objecttype' => $this->element,
3842
            'option' => $option,
3843
            'nofetch' => 1,
3844
        ];
3845
        $classfortooltip = 'classfortooltip';
3846
        $dataparams = '';
3847
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3848
            $classfortooltip = 'classforajaxtooltip';
3849
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
3850
            $label = '';
3851
        } else {
3852
            $label = implode($this->getTooltipContentArray($params));
3853
        }
3854
3855
        $linkclose = '';
3856
        if (empty($notooltip) && $user->hasRight('commande', 'lire')) {
3857
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3858
                $label = $langs->trans("Order");
3859
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
3860
            }
3861
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
3862
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
3863
3864
            $target_value = array('_self', '_blank', '_parent', '_top');
3865
            if (in_array($target, $target_value)) {
3866
                $linkclose .= ' target="' . dol_escape_htmltag($target) . '"';
3867
            }
3868
        }
3869
3870
        $linkstart = '<a href="' . $url . '"';
3871
        $linkstart .= $linkclose . '>';
3872
        $linkend = '</a>';
3873
3874
        if ($option === 'nolink') {
3875
            $linkstart = '';
3876
            $linkend = '';
3877
        }
3878
3879
        $result .= $linkstart;
3880
        if ($withpicto) {
3881
            $result .= img_object(($notooltip ? '' : $label), $this->picto, (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3882
        }
3883
        if ($withpicto != 2) {
3884
            $result .= $this->ref;
3885
        }
3886
        $result .= $linkend;
3887
3888
        if ($addlinktonotes) {
3889
            $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
3890
            if ($txttoshow) {
3891
                $notetoshow = $langs->trans("ViewPrivateNote") . ':<br>' . dol_string_nohtmltag($txttoshow, 1);
3892
                $result .= ' <span class="note inline-block">';
3893
                $result .= '<a href="' . constant('BASE_URL') . '/commande/note.php?id=' . $this->id . '" class="classfortooltip" title="' . dol_escape_htmltag($notetoshow) . '">';
3894
                $result .= img_picto('', 'note');
3895
                $result .= '</a>';
3896
                //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
3897
                //$result.='</a>';
3898
                $result .= '</span>';
3899
            }
3900
        }
3901
3902
        global $action;
3903
        $hookmanager->initHooks(array($this->element . 'dao'));
3904
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
3905
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3906
        if ($reshook > 0) {
3907
            $result = $hookmanager->resPrint;
3908
        } else {
3909
            $result .= $hookmanager->resPrint;
3910
        }
3911
        return $result;
3912
    }
3913
3914
3915
    /**
3916
     *  Charge les information d'ordre info dans l'objet commande
3917
     *
3918
     *  @param  int     $id       Id of order
3919
     *  @return void
3920
     */
3921
    public function info($id)
3922
    {
3923
        $sql = 'SELECT c.rowid, date_creation as datec, tms as datem,';
3924
        $sql .= ' date_valid as datev,';
3925
        $sql .= ' date_cloture as datecloture,';
3926
        $sql .= ' fk_user_author, fk_user_valid, fk_user_cloture';
3927
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'commande as c';
3928
        $sql .= ' WHERE c.rowid = ' . ((int) $id);
3929
        $result = $this->db->query($sql);
3930
        if ($result) {
3931
            if ($this->db->num_rows($result)) {
3932
                $obj = $this->db->fetch_object($result);
3933
                $this->id = $obj->rowid;
3934
                if ($obj->fk_user_author) {
3935
                    $this->user_creation_id = $obj->fk_user_author;
3936
                }
3937
                if ($obj->fk_user_valid) {
3938
                    $this->user_validation_id = $obj->fk_user_valid;
3939
                }
3940
                if ($obj->fk_user_cloture) {
3941
                    $this->user_closing_id = $obj->fk_user_cloture;
3942
                }
3943
3944
                $this->date_creation     = $this->db->jdate($obj->datec);
3945
                $this->date_modification = $this->db->jdate($obj->datem);
3946
                $this->date_validation   = $this->db->jdate($obj->datev);
3947
                $this->date_cloture      = $this->db->jdate($obj->datecloture);
3948
            }
3949
3950
            $this->db->free($result);
3951
        } else {
3952
            dol_print_error($this->db);
3953
        }
3954
    }
3955
3956
3957
    /**
3958
     *  Initialise an instance with random values.
3959
     *  Used to build previews or test instances.
3960
     *  id must be 0 if object instance is a specimen.
3961
     *
3962
     *  @return int
3963
     */
3964
    public function initAsSpecimen()
3965
    {
3966
        global $conf, $langs;
3967
3968
        dol_syslog(get_class($this) . "::initAsSpecimen");
3969
3970
        // Load array of products prodids
3971
        $num_prods = 0;
3972
        $prodids = array();
3973
        $sql = "SELECT rowid";
3974
        $sql .= " FROM " . MAIN_DB_PREFIX . "product";
3975
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
3976
        $sql .= $this->db->plimit(100);
3977
3978
        $resql = $this->db->query($sql);
3979
        if ($resql) {
3980
            $num_prods = $this->db->num_rows($resql);
3981
            $i = 0;
3982
            while ($i < $num_prods) {
3983
                $i++;
3984
                $row = $this->db->fetch_row($resql);
3985
                $prodids[$i] = $row[0];
3986
            }
3987
        }
3988
3989
        // Initialise parameters
3990
        $this->id = 0;
3991
        $this->ref = 'SPECIMEN';
3992
        $this->specimen = 1;
3993
        $this->entity = $conf->entity;
3994
        $this->socid = 1;
3995
        $this->date = time();
3996
        $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3997
        $this->cond_reglement_code = 'RECEP';
3998
        $this->mode_reglement_code = 'CHQ';
3999
        $this->availability_code   = 'DSP';
4000
        $this->demand_reason_code  = 'SRC_00';
4001
4002
        $this->note_public = 'This is a comment (public)';
4003
        $this->note_private = 'This is a comment (private)';
4004
4005
        $this->multicurrency_tx = 1;
4006
        $this->multicurrency_code = $conf->currency;
4007
4008
        $this->status = $this::STATUS_DRAFT;
4009
4010
        // Lines
4011
        $nbp = 5;
4012
        $xnbp = 0;
4013
        while ($xnbp < $nbp) {
4014
            $line = new OrderLine($this->db);
4015
4016
            $line->desc = $langs->trans("Description") . " " . $xnbp;
4017
            $line->qty = 1;
4018
            $line->subprice = 100;
4019
            $line->price = 100;
4020
            $line->tva_tx = 20;
4021
            if ($xnbp == 2) {
4022
                $line->total_ht = 50;
4023
                $line->total_ttc = 60;
4024
                $line->total_tva = 10;
4025
                $line->remise_percent = 50;
4026
            } else {
4027
                $line->total_ht = 100;
4028
                $line->total_ttc = 120;
4029
                $line->total_tva = 20;
4030
                $line->remise_percent = 0;
4031
            }
4032
            if ($num_prods > 0) {
4033
                $prodid = mt_rand(1, $num_prods);
4034
                $line->fk_product = $prodids[$prodid];
4035
                $line->product_ref = 'SPECIMEN';
4036
            }
4037
4038
            $this->lines[$xnbp] = $line;
4039
4040
            $this->total_ht       += $line->total_ht;
4041
            $this->total_tva      += $line->total_tva;
4042
            $this->total_ttc      += $line->total_ttc;
4043
4044
            $xnbp++;
4045
        }
4046
4047
        return 1;
4048
    }
4049
4050
4051
    /**
4052
     *  Load the indicators this->nb for the state board
4053
     *
4054
     *  @return     int         Return integer <0 if KO, >0 if OK
4055
     */
4056
    public function loadStateBoard()
4057
    {
4058
        global $user;
4059
4060
        $this->nb = array();
4061
        $clause = "WHERE";
4062
4063
        $sql = "SELECT count(co.rowid) as nb";
4064
        $sql .= " FROM " . MAIN_DB_PREFIX . "commande as co";
4065
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON co.fk_soc = s.rowid";
4066
        if (!$user->hasRight('societe', 'client', 'voir')) {
4067
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4068
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
4069
            $clause = "AND";
4070
        }
4071
        $sql .= " " . $clause . " co.entity IN (" . getEntity('commande') . ")";
4072
4073
        $resql = $this->db->query($sql);
4074
        if ($resql) {
4075
            while ($obj = $this->db->fetch_object($resql)) {
4076
                $this->nb["orders"] = $obj->nb;
4077
            }
4078
            $this->db->free($resql);
4079
            return 1;
4080
        } else {
4081
            dol_print_error($this->db);
4082
            $this->error = $this->db->error();
4083
            return -1;
4084
        }
4085
    }
4086
4087
    /**
4088
     *  Create an array of order lines
4089
     *
4090
     *  @return int     >0 if OK, <0 if KO
4091
     */
4092
    public function getLinesArray()
4093
    {
4094
        return $this->fetch_lines();
4095
    }
4096
4097
    /**
4098
     *  Create a document onto disk according to template module.
4099
     *
4100
     *  @param      string      $modele         Force template to use ('' to not force)
4101
     *  @param      Translate   $outputlangs    object lang a utiliser pour traduction
4102
     *  @param      int         $hidedetails    Hide details of lines
4103
     *  @param      int         $hidedesc       Hide description
4104
     *  @param      int         $hideref        Hide ref
4105
     *  @param      null|array  $moreparams     Array to provide more information
4106
     *  @return     int                         0 if KO, 1 if OK
4107
     */
4108
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4109
    {
4110
        global $conf, $langs;
4111
4112
        $langs->load("orders");
4113
        $outputlangs->load("products");
4114
4115
        if (!dol_strlen($modele)) {
4116
            $modele = 'einstein';
4117
4118
            if (!empty($this->model_pdf)) {
4119
                $modele = $this->model_pdf;
4120
            } elseif (getDolGlobalString('COMMANDE_ADDON_PDF')) {
4121
                $modele = getDolGlobalString('COMMANDE_ADDON_PDF');
4122
            }
4123
        }
4124
4125
        $modelpath = "core/modules/commande/doc/";
4126
4127
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4128
    }
4129
4130
4131
    /**
4132
     * Function used to replace a thirdparty id with another one.
4133
     *
4134
     * @param   DoliDB  $dbs        Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
4135
     * @param   int     $origin_id  Old thirdparty id
4136
     * @param   int     $dest_id    New thirdparty id
4137
     * @return  bool
4138
     */
4139
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4140
    {
4141
        $tables = array(
4142
        'commande'
4143
        );
4144
4145
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4146
    }
4147
4148
    /**
4149
     * Function used to replace a product id with another one.
4150
     *
4151
     * @param DoliDB $db Database handler
4152
     * @param int $origin_id Old product id
4153
     * @param int $dest_id New product id
4154
     * @return bool
4155
     */
4156
    public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4157
    {
4158
        $tables = array(
4159
            'commandedet',
4160
        );
4161
4162
        return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4163
    }
4164
4165
    /**
4166
     * Is the sales order delayed?
4167
     *
4168
     * @return bool     true if late, false if not
4169
     */
4170
    public function hasDelay()
4171
    {
4172
        global $conf;
4173
4174
        if (!($this->statut > Commande::STATUS_DRAFT && $this->statut < Commande::STATUS_CLOSED)) {
4175
            return false; // Never late if not inside this status range
4176
        }
4177
4178
        $now = dol_now();
4179
4180
        return max($this->date, $this->delivery_date) < ($now - $conf->commande->client->warning_delay);
4181
    }
4182
4183
    /**
4184
     * Show the customer delayed info
4185
     *
4186
     * @return string       Show delayed information
4187
     */
4188
    public function showDelay()
4189
    {
4190
        global $conf, $langs;
4191
4192
        if (empty($this->delivery_date)) {
4193
            $text = $langs->trans("OrderDate") . ' ' . dol_print_date($this->date, 'day');
4194
        } else {
4195
            $text = $text = $langs->trans("DeliveryDate") . ' ' . dol_print_date($this->delivery_date, 'day');
4196
        }
4197
        $text .= ' ' . ($conf->commande->client->warning_delay > 0 ? '+' : '-') . ' ' . round(abs($conf->commande->client->warning_delay) / 3600 / 24, 1) . ' ' . $langs->trans("days") . ' < ' . $langs->trans("Today");
4198
4199
        return $text;
4200
    }
4201
}
4202