Test Failed
Branch main (a69845)
by Rafael
63:42
created

CommandeFournisseur::approve()   F

Complexity

Conditions 28
Paths 1821

Size

Total Lines 133
Code Lines 87

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
eloc 87
c 0
b 0
f 0
nc 1821
nop 3
dl 0
loc 133
rs 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* Copyright (C) 2003-2006  Rodolphe Quiedeville    <[email protected]>
4
 * Copyright (C) 2004-2017	Laurent Destailleur		<[email protected]>
5
 * Copyright (C) 2005-2012	Regis Houssin			<[email protected]>
6
 * Copyright (C) 2007		Franky Van Liedekerke	<[email protected]>
7
 * Copyright (C) 2010-2020	Juanjo Menent			<[email protected]>
8
 * Copyright (C) 2010-2018	Philippe Grand			<[email protected]>
9
 * Copyright (C) 2012-2015	Marcos García			<[email protected]>
10
 * Copyright (C) 2013		Florian Henry			<[email protected]>
11
 * Copyright (C) 2013		Cédric Salvador			<[email protected]>
12
 * Copyright (C) 2018		Nicolas ZABOURI			<[email protected]>
13
 * Copyright (C) 2018-2024	Frédéric France			<[email protected]>
14
 * Copyright (C) 2018-2022	Ferran Marcet			<[email protected]>
15
 * Copyright (C) 2021		Josep Lluís Amador		<[email protected]>
16
 * Copyright (C) 2022		Gauthier VERDOL			<[email protected]>
17
 * Copyright (C) 2024		Solution Libre SAS		<[email protected]>
18
 * Copyright (C) 2024		MDW							<[email protected]>
19
 * Copyright (C) 2024       Rafael San José             <[email protected]>
20
 *
21
 * This program is free software; you can redistribute it and/or modify
22
 * it under the terms of the GNU General Public License as published by
23
 * the Free Software Foundation; either version 3 of the License, or
24
 * (at your option) any later version.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29
 * GNU General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU General Public License
32
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
33
 */
34
35
/**
36
 *  \file       htdocs/fourn/class/fournisseur.commande.class.php
37
 *  \ingroup    fournisseur,commande
38
 *  \brief      File of class to manage suppliers orders
39
 */
40
41
require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/commonorder.class.php';
42
require_once constant('DOL_DOCUMENT_ROOT') . '/fourn/class/fournisseur.product.class.php';
43
require_once constant('DOL_DOCUMENT_ROOT') . '/multicurrency/class/multicurrency.class.php';
44
if (isModEnabled('productbatch')) {
45
    require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/productbatch.class.php';
46
}
47
48
49
/**
50
 *  Class to manage predefined suppliers products
51
 */
52
class CommandeFournisseur extends CommonOrder
53
{
54
    /**
55
     * @var string ID to identify managed object
56
     */
57
    public $element = 'order_supplier';
58
59
    /**
60
     * @var string Name of table without prefix where object is stored
61
     */
62
    public $table_element = 'commande_fournisseur';
63
64
    /**
65
     * @var string    Name of subtable line
66
     */
67
    public $table_element_line = 'commande_fournisseurdet';
68
69
    /**
70
     * @var string Name of class line
71
     */
72
    public $class_element_line = 'CommandeFournisseurLigne';
73
74
    /**
75
     * @var string Field with ID of parent key if this field has a parent
76
     */
77
    public $fk_element = 'fk_commande';
78
79
    /**
80
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
81
     */
82
    public $picto = 'supplier_order';
83
84
    /**
85
     * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user
86
     * @var integer
87
     */
88
    public $restrictiononfksoc = 1;
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    protected $table_ref_field = 'ref';
94
95
    /**
96
     * @var int Purchase Order ID
97
     */
98
    public $id;
99
100
    /**
101
     * @var string Supplier order reference
102
     */
103
    public $ref;
104
105
    /**
106
     * @var string Supplier reference
107
     */
108
    public $ref_supplier;
109
110
    /**
111
     * @var string ref supplier
112
     * @deprecated
113
     * @see $ref_supplier
114
     */
115
    public $ref_fourn;
116
117
    /**
118
     * @var int
119
     */
120
    public $statut; // 0=Draft -> 1=Validated -> 2=Approved -> 3=Ordered/Process running -> 4=Received partially -> 5=Received totally -> (reopen) 4=Received partially
121
    //                                                                                          -> 7=Canceled/Never received -> (reopen) 3=Process running
122
    //                                                              -> 6=Canceled -> (reopen) 2=Approved
123
    //                                                -> 9=Refused  -> (reopen) 1=Validated
124
    //  Note: billed or not is on another field "billed"
125
126
    public $billed;
127
128
    /**
129
     * @var int Company ID
130
     */
131
    public $socid;
132
133
    /**
134
     * @var int Supplier ID
135
     */
136
    public $fourn_id;
137
138
    /**
139
     * @var int Date
140
     */
141
    public $date;
142
143
    /**
144
     * @var int Date of the purchase order creation
145
     */
146
    public $date_creation;
147
148
    /**
149
     * @var int Date of the purchase order validation
150
     */
151
    public $date_valid;
152
153
    /**
154
     * @var int Date of the purchase order approval
155
     */
156
    public $date_approve;
157
158
    /**
159
     * @var int Date of the purchase order second approval
160
     * Used when SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set
161
     */
162
    public $date_approve2;
163
164
    /**
165
     * @var int Date of the purchase order ordering
166
     */
167
    public $date_commande;
168
169
    //For backward compatibility
170
    public $remise_percent;
171
    public $methode_commande_id;
172
    public $methode_commande;
173
174
    /**
175
     *  @var int Expected Delivery Date
176
     */
177
    public $delivery_date;
178
179
    /**
180
     *  @var float Total value, excluding taxes (HT = "Hors Taxe" in French)
181
     */
182
    public $total_ht;
183
184
    /**
185
     *  @var float Total VAT
186
     */
187
    public $total_tva;
188
189
    /**
190
     *  @var float Total Local tax 1
191
     */
192
    public $total_localtax1;
193
194
    /**
195
     *  @var float Total Local tax 2
196
     */
197
    public $total_localtax2;
198
199
    /**
200
     *  @var float Total value, including taxes (TTC = "Toutes Taxes Comprises" in French)
201
     */
202
    public $total_ttc;
203
204
    public $source;
205
206
    /**
207
     * @var int ID
208
     */
209
    public $fk_project;
210
211
    /**
212
     * @var int Payment conditions ID
213
     */
214
    public $cond_reglement_id;
215
216
    /**
217
     * @var string Payment conditions code
218
     */
219
    public $cond_reglement_code;
220
221
    /**
222
     * @var string Payment conditions label
223
     */
224
    public $cond_reglement_label;
225
226
    /**
227
     * @var string Payment conditions label on documents
228
     */
229
    public $cond_reglement_doc;
230
231
    /**
232
     * @var int Account ID
233
     */
234
    public $fk_account;
235
236
    /**
237
     * @var int Payment choice ID
238
     */
239
    public $mode_reglement_id;
240
241
    /**
242
     * @var string Payment choice code
243
     */
244
    public $mode_reglement_code;
245
246
    /**
247
     * @var string Payment choice label
248
     */
249
    public $mode_reglement;
250
251
    /**
252
     * @var int User ID of the purchase order author
253
     */
254
    public $user_author_id;
255
256
    /**
257
     * @var int User ID of the purchase order approver
258
     */
259
    public $user_approve_id;
260
261
    /**
262
     * @var int User ID of the purchase order second approver
263
     * Used when SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set
264
     */
265
    public $user_approve_id2;
266
267
    public $refuse_note;
268
269
    public $extraparams = array();
270
271
    /**
272
     * @var CommandeFournisseurLigne[]
273
     */
274
    public $lines = array();
275
276
    /**
277
     * @var CommandeFournisseurLigne
278
     */
279
    public $line;
280
281
    // Add for supplier_proposal
282
    public $origin;
283
    public $origin_id;
284
    public $linked_objects = array();
285
286
    /**
287
     * @var int Date of the purchase order payment deadline
288
     */
289
    public $date_lim_reglement;
290
    public $receptions = array();
291
292
    // Multicurrency
293
    /**
294
     * @var int ID
295
     */
296
    public $fk_multicurrency;
297
298
    /**
299
     * @var string
300
     */
301
    public $multicurrency_code;
302
303
    /**
304
     * @var float Rate
305
     */
306
    public $multicurrency_tx;
307
308
    /**
309
     * @var float Total value in the other currency, excluding taxes (HT = "Hors Taxes" in French)
310
     */
311
    public $multicurrency_total_ht;
312
313
    /**
314
     * @var float Total VAT in the other currency (TVA = "Taxe sur la Valeur Ajoutée" in French)
315
     */
316
    public $multicurrency_total_tva;
317
318
    /**
319
     * @var float Total value in the other currency, including taxes (TTC = "Toutes Taxes Comprises in French)
320
     */
321
    public $multicurrency_total_ttc;
322
323
    /**
324
     *  'type' field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]', 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:Sortfield]]]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'text:none', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
325
     *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
326
     *  'label' the translation key.
327
     *  'picto' is code of a picto to show before value in forms
328
     *  'enabled' is a condition when the field must be managed (Example: 1 or 'getDolGlobalString("MY_SETUP_PARAM")' or 'isModEnabled("multicurrency")' ...)
329
     *  'position' is the sort order of field.
330
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
331
     *  '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)
332
     *  'noteditable' says if field is not editable (1 or 0)
333
     *  '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.
334
     *  'index' if we want an index in database.
335
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
336
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
337
     *  'isameasure' must be set to 1 or 2 if field can be used for measure. Field type must be summable like integer or double(24,8). Use 1 in most cases, or 2 if you don't want to see the column total into list (for example for percentage)
338
     *  'css' and 'cssview' and 'csslist' is the CSS style to use on field. 'css' is used in creation and update. 'cssview' is used in view mode. 'csslist' is used for columns in lists. For example: 'css'=>'minwidth300 maxwidth500 widthcentpercentminusx', 'cssview'=>'wordbreak', 'csslist'=>'tdoverflowmax200'
339
     *  'help' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
340
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
341
     *  '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.
342
     *  'arrayofkeyval' to set a list of values if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel"). Note that type can be 'integer' or 'varchar'
343
     *  'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
344
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
345
     *  'validate' is 1 if need to validate with $this->validateField()
346
     *  'copytoclipboard' is 1 or 2 to allow to add a picto to copy value into clipboard (1=picto after label, 2=picto after value)
347
     *
348
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
349
     */
350
    public $fields = array(
351
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 10),
352
        'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 25, 'searchall' => 1),
353
        'ref_ext' => array('type' => 'varchar(255)', 'label' => 'Ref ext', 'enabled' => 1, 'visible' => 0, 'position' => 35),
354
        'ref_supplier' => array('type' => 'varchar(255)', 'label' => 'RefOrderSupplierShort', 'enabled' => 1, 'visible' => 1, 'position' => 40, 'searchall' => 1),
355
        'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 45),
356
        'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 710),
357
        'date_approve' => array('type' => 'datetime', 'label' => 'DateApprove', 'enabled' => 1, 'visible' => -1, 'position' => 720),
358
        'date_approve2' => array('type' => 'datetime', 'label' => 'DateApprove2', 'enabled' => 1, 'visible' => 3, 'position' => 725),
359
        'date_commande' => array('type' => 'date', 'label' => 'OrderDateShort', 'enabled' => 1, 'visible' => 1, 'position' => 70),
360
        'date_livraison' => array('type' => 'datetime', 'label' => 'DeliveryDate', 'enabled' => 'empty($conf->global->ORDER_DISABLE_DELIVERY_DATE)', 'visible' => 1, 'position' => 74),
361
        'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => 3, 'position' => 41),
362
        'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => 3, 'notnull' => -1, 'position' => 80),
363
        'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => 3, 'position' => 711),
364
        'fk_user_approve' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval', 'enabled' => 1, 'visible' => 3, 'position' => 721),
365
        'fk_user_approve2' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval2', 'enabled' => 1, 'visible' => 3, 'position' => 726),
366
        'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 100),
367
        'billed' => array('type' => 'smallint(6)', 'label' => 'Billed', 'enabled' => 1, 'visible' => 1, 'position' => 710),
368
        'total_ht' => array('type' => 'double(24,8)', 'label' => 'AmountHT', 'enabled' => 1, 'visible' => 1, 'position' => 130, 'isameasure' => 1),
369
        'total_tva' => array('type' => 'double(24,8)', 'label' => 'AmountVAT', 'enabled' => 1, 'visible' => 1, 'position' => 135, 'isameasure' => 1),
370
        'localtax1' => array('type' => 'double(24,8)', 'label' => 'LT1', 'enabled' => 1, 'visible' => 3, 'position' => 140, 'isameasure' => 1),
371
        'localtax2' => array('type' => 'double(24,8)', 'label' => 'LT2', 'enabled' => 1, 'visible' => 3, 'position' => 145, 'isameasure' => 1),
372
        'total_ttc' => array('type' => 'double(24,8)', 'label' => 'AmountTTC', 'enabled' => 1, 'visible' => -1, 'position' => 150, 'isameasure' => 1),
373
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 750, 'searchall' => 1),
374
        'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 760, 'searchall' => 1),
375
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPDF', 'enabled' => 1, 'visible' => 0, 'position' => 165),
376
        'fk_input_method' => array('type' => 'integer', 'label' => 'OrderMode', 'enabled' => 1, 'visible' => 3, 'position' => 170),
377
        'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => 3, 'position' => 175),
378
        'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => 3, 'position' => 180),
379
        'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => 0, 'position' => 190),
380
        'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => 3, 'position' => 200),
381
        'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => 3, 'position' => 205),
382
        'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => 3, 'position' => 210),
383
        'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 1, 'visible' => 0, 'position' => 215),
384
        'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Currency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 220),
385
        'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'CurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 225),
386
        'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 230),
387
        'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
388
        'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
389
        'date_creation' => array('type' => 'datetime', 'label' => 'Date creation', 'enabled' => 1, 'visible' => -1, 'position' => 500),
390
        'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => 1, 'notnull' => 1, 'position' => 50),
391
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 1000, 'index' => 1),
392
        'tms' => array('type' => 'datetime', 'label' => "DateModificationShort", 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 501),
393
        'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => 0, 'position' => 700),
394
        'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => 1, 'position' => 701),
395
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => 0, 'position' => 900),
396
    );
397
398
399
    /**
400
     * Draft status
401
     */
402
    const STATUS_DRAFT = 0;
403
404
    /**
405
     * Validated status
406
     */
407
    const STATUS_VALIDATED = 1;
408
409
    /**
410
     * Accepted
411
     */
412
    const STATUS_ACCEPTED = 2;
413
414
    /**
415
     * Order sent, shipment on process
416
     */
417
    const STATUS_ORDERSENT = 3;
418
419
    /**
420
     * Received partially
421
     */
422
    const STATUS_RECEIVED_PARTIALLY = 4;
423
424
    /**
425
     * Received completely
426
     */
427
    const STATUS_RECEIVED_COMPLETELY = 5;
428
429
    /**
430
     * Order canceled
431
     */
432
    const STATUS_CANCELED = 6;
433
434
    /**
435
     * Order canceled/never received
436
     */
437
    const STATUS_CANCELED_AFTER_ORDER = 7;
438
439
    /**
440
     * Refused
441
     */
442
    const STATUS_REFUSED = 9;
443
444
445
    /**
446
     * The constant used into source field to track the order was generated by the replenishement feature
447
     */
448
    const SOURCE_ID_REPLENISHMENT = 42;
449
450
    /**
451
     *  Constructor
452
     *
453
     *  @param      DoliDB      $db      Database handler
454
     */
455
    public function __construct($db)
456
    {
457
        $this->db = $db;
458
459
        $this->ismultientitymanaged = 1;
460
    }
461
462
463
    /**
464
     *  Get object and lines from database
465
     *
466
     *  @param  int     $id         Id of order to load
467
     *  @param  string  $ref        Ref of object
468
     *  @return int                 >0 if OK, <0 if KO, 0 if not found
469
     */
470
    public function fetch($id, $ref = '')
471
    {
472
        // Check parameters
473
        if (empty($id) && empty($ref)) {
474
            return -1;
475
        }
476
477
        $sql = "SELECT c.rowid, c.entity, c.ref, ref_supplier, c.fk_soc, c.fk_statut as status, c.amount_ht, c.total_ht, c.total_ttc, c.total_tva,";
478
        $sql .= " c.localtax1, c.localtax2, ";
479
        $sql .= " c.date_creation, c.date_valid, c.date_approve, c.date_approve2,";
480
        $sql .= " c.fk_user_author as user_author_id, c.fk_user_valid as user_validation_id, c.fk_user_approve as user_approve_id, c.fk_user_approve2 as user_approve_id2,";
481
        $sql .= " c.date_commande as date_commande, c.date_livraison as delivery_date, c.fk_cond_reglement, c.fk_mode_reglement, c.fk_projet as fk_project, c.remise_percent, c.source, c.fk_input_method,";
482
        $sql .= " c.fk_account,";
483
        $sql .= " c.note_private, c.note_public, c.model_pdf, c.extraparams, c.billed,";
484
        $sql .= " c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc,";
485
        $sql .= " cm.libelle as methode_commande,";
486
        $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc,";
487
        $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_libelle";
488
        $sql .= ', c.fk_incoterms, c.location_incoterms';
489
        $sql .= ', i.libelle as label_incoterms';
490
        $sql .= " FROM " . $this->db->prefix() . "commande_fournisseur as c";
491
        $sql .= " LEFT JOIN " . $this->db->prefix() . "c_payment_term as cr ON c.fk_cond_reglement = cr.rowid";
492
        $sql .= " LEFT JOIN " . $this->db->prefix() . "c_paiement as p ON c.fk_mode_reglement = p.id";
493
        $sql .= " LEFT JOIN " . $this->db->prefix() . "c_input_method as cm ON cm.rowid = c.fk_input_method";
494
        $sql .= ' LEFT JOIN ' . $this->db->prefix() . 'c_incoterms as i ON c.fk_incoterms = i.rowid';
495
496
        if (empty($id)) {
497
            $sql .= " WHERE c.entity IN (" . getEntity('supplier_order') . ")";
498
        } else {
499
            $sql .= " WHERE c.rowid=" . ((int) $id);
500
        }
501
502
        if ($ref) {
503
            $sql .= " AND c.ref='" . $this->db->escape($ref) . "'";
504
        }
505
506
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
507
        $resql = $this->db->query($sql);
508
        if ($resql) {
509
            $obj = $this->db->fetch_object($resql);
510
            if (!$obj) {
511
                $this->error = 'Bill with id ' . $id . ' not found';
512
                dol_syslog(get_class($this) . '::fetch ' . $this->error);
513
                return 0;
514
            }
515
516
            $this->id = $obj->rowid;
517
            $this->entity = $obj->entity;
518
519
            $this->ref = $obj->ref;
520
            $this->ref_supplier = $obj->ref_supplier;
521
            $this->socid = $obj->fk_soc;
522
            $this->fourn_id = $obj->fk_soc;
523
            $this->statut = $obj->status;   // deprecated
524
            $this->status = $obj->status;
525
            $this->billed = $obj->billed;
526
            $this->user_author_id = $obj->user_author_id;
527
            $this->user_validation_id = $obj->user_validation_id;
528
            $this->user_approve_id = $obj->user_approve_id;
529
            $this->user_approve_id2 = $obj->user_approve_id2;
530
            $this->total_ht             = $obj->total_ht;
531
            $this->total_tva            = $obj->total_tva;
532
            $this->total_localtax1      = $obj->localtax1;
533
            $this->total_localtax2      = $obj->localtax2;
534
            $this->total_ttc            = $obj->total_ttc;
535
            $this->date_creation = $this->db->jdate($obj->date_creation);
536
            $this->date_valid = $this->db->jdate($obj->date_valid);
537
            $this->date_approve         = $this->db->jdate($obj->date_approve);
538
            $this->date_approve2        = $this->db->jdate($obj->date_approve2);
539
            $this->date_commande        = $this->db->jdate($obj->date_commande); // date we make the order to supplier
540
            if (isset($obj->date_commande)) {
541
                $this->date = $this->date_commande;
542
            } else {
543
                $this->date = $this->date_creation;
544
            }
545
            $this->delivery_date = $this->db->jdate($obj->delivery_date);
546
            $this->remise_percent = $obj->remise_percent;
547
            $this->methode_commande_id = $obj->fk_input_method;
548
            $this->methode_commande = $obj->methode_commande;
549
550
            $this->source = $obj->source;
551
            $this->fk_project = $obj->fk_project;
552
            $this->cond_reglement_id = $obj->fk_cond_reglement;
553
            $this->cond_reglement_code = $obj->cond_reglement_code;
554
            $this->cond_reglement = $obj->cond_reglement_label;         // deprecated
555
            $this->cond_reglement_label = $obj->cond_reglement_label;
556
            $this->cond_reglement_doc = $obj->cond_reglement_doc;
557
            $this->fk_account = $obj->fk_account;
558
            $this->mode_reglement_id = $obj->fk_mode_reglement;
559
            $this->mode_reglement_code = $obj->mode_reglement_code;
560
            $this->mode_reglement = $obj->mode_reglement_libelle;
561
            $this->note = $obj->note_private; // deprecated
562
            $this->note_private = $obj->note_private;
563
            $this->note_public = $obj->note_public;
564
            $this->model_pdf = $obj->model_pdf;
565
566
            //Incoterms
567
            $this->fk_incoterms = $obj->fk_incoterms;
568
            $this->location_incoterms = $obj->location_incoterms;
569
            $this->label_incoterms = $obj->label_incoterms;
570
571
            // Multicurrency
572
            $this->fk_multicurrency         = $obj->fk_multicurrency;
573
            $this->multicurrency_code = $obj->multicurrency_code;
574
            $this->multicurrency_tx         = $obj->multicurrency_tx;
575
            $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
576
            $this->multicurrency_total_tva  = $obj->multicurrency_total_tva;
577
            $this->multicurrency_total_ttc  = $obj->multicurrency_total_ttc;
578
579
            $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
580
581
            $this->db->free($resql);
582
583
            // Retrieve all extrafield
584
            // fetch optionals attributes and labels
585
            $this->fetch_optionals();
586
587
            // Lines
588
            $result = $this->fetch_lines();
589
590
            if ($result < 0) {
591
                return -1;
592
            } else {
593
                return 1;
594
            }
595
        } else {
596
            $this->error = $this->db->error() . " sql=" . $sql;
597
            return -1;
598
        }
599
    }
600
601
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
602
    /**
603
     * Load array lines
604
     *
605
     * @param       int     $only_product   Return only physical products
606
     * @return      int                     Return integer <0 if KO, >0 if OK
607
     */
608
    public function fetch_lines($only_product = 0)
609
    {
610
		// phpcs:enable
611
612
        $this->lines = array();
613
614
        $sql = "SELECT l.rowid, l.fk_commande, l.ref as ref_supplier, l.fk_product, l.product_type, l.label, l.description, l.qty,";
615
        $sql .= " l.vat_src_code, l.tva_tx, l.remise_percent, l.subprice,";
616
        $sql .= " l.localtax1_tx, l. localtax2_tx, l.localtax1_type, l. localtax2_type, l.total_localtax1, l.total_localtax2,";
617
        $sql .= " l.total_ht, l.total_tva, l.total_ttc, l.special_code, l.fk_parent_line, l.rang,";
618
        $sql .= " p.rowid as product_id, p.ref as product_ref, p.label as product_label, p.description as product_desc, p.tobatch as product_tobatch, p.barcode as product_barcode,";
619
        $sql .= " l.fk_unit,";
620
        $sql .= " l.date_start, l.date_end,";
621
        $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc';
622
        $sql .= " FROM " . $this->db->prefix() . "commande_fournisseurdet as l";
623
        $sql .= ' LEFT JOIN ' . $this->db->prefix() . 'product as p ON l.fk_product = p.rowid';
624
        $sql .= " WHERE l.fk_commande = " . ((int) $this->id);
625
        if ($only_product) {
626
            $sql .= ' AND p.fk_product_type = 0';
627
        }
628
        $sql .= " ORDER BY l.rang, l.rowid";
629
        //print $sql;
630
631
        dol_syslog(get_class($this) . "::fetch_lines", LOG_DEBUG);
632
633
        $result = $this->db->query($sql);
634
        if ($result) {
635
            $num = $this->db->num_rows($result);
636
            $i = 0;
637
638
            while ($i < $num) {
639
                $objp = $this->db->fetch_object($result);
640
641
                $line = new CommandeFournisseurLigne($this->db);
642
643
                $line->id                  = $objp->rowid;
644
                $line->fk_commande         = $objp->fk_commande;
645
                $line->desc                = $objp->description;
646
                $line->description         = $objp->description;
647
                $line->qty                 = $objp->qty;
648
                $line->tva_tx              = $objp->tva_tx;
649
                $line->localtax1_tx        = $objp->localtax1_tx;
650
                $line->localtax2_tx        = $objp->localtax2_tx;
651
                $line->localtax1_type      = $objp->localtax1_type;
652
                $line->localtax2_type      = $objp->localtax2_type;
653
                $line->subprice            = $objp->subprice;
654
                $line->pu_ht = $objp->subprice;
655
                $line->remise_percent      = $objp->remise_percent;
656
657
                $line->vat_src_code        = $objp->vat_src_code;
658
                $line->total_ht            = $objp->total_ht;
659
                $line->total_tva           = $objp->total_tva;
660
                $line->total_localtax1     = $objp->total_localtax1;
661
                $line->total_localtax2     = $objp->total_localtax2;
662
                $line->total_ttc           = $objp->total_ttc;
663
                $line->product_type        = $objp->product_type;
664
665
                $line->fk_product          = $objp->fk_product;
666
667
                $line->libelle             = $objp->product_label; // deprecated
668
                $line->product_label       = $objp->product_label;
669
                $line->product_desc        = $objp->product_desc;
670
                $line->product_tobatch     = $objp->product_tobatch;
671
                $line->product_barcode     = $objp->product_barcode;
672
673
                $line->ref                 = $objp->product_ref; // Ref of product
674
                $line->product_ref         = $objp->product_ref; // Ref of product
675
                $line->ref_fourn           = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
676
                $line->ref_supplier        = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
677
678
                if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
679
                    // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
680
                    // Move this into another method and call it when required.
681
682
                    // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
683
                    $sqlsearchpackage = 'SELECT rowid, packaging FROM ' . $this->db->prefix() . "product_fournisseur_price";
684
                    $sqlsearchpackage .= ' WHERE entity IN (' . getEntity('product_fournisseur_price') . ")";
685
                    $sqlsearchpackage .= " AND fk_product = " . ((int) $objp->fk_product);
686
                    $sqlsearchpackage .= " AND ref_fourn = '" . $this->db->escape($objp->ref_supplier) . "'";
687
                    $sqlsearchpackage .= " AND quantity <= " . ((float) $objp->qty);  // required to be qualified
688
                    $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= " . ((float) $objp->qty) . ")";  // required to be qualified
689
                    $sqlsearchpackage .= " AND fk_soc = " . ((int) $this->socid);
690
                    $sqlsearchpackage .= " ORDER BY packaging ASC";     // Take the smaller package first
691
                    $sqlsearchpackage .= " LIMIT 1";
692
693
                    $resqlsearchpackage = $this->db->query($sqlsearchpackage);
694
                    if ($resqlsearchpackage) {
695
                        $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
696
                        if ($objsearchpackage) {
697
                            $line->fk_fournprice = $objsearchpackage->rowid;
698
                            $line->packaging     = $objsearchpackage->packaging;
699
                        }
700
                    } else {
701
                        $this->error = $this->db->lasterror();
702
                        return -1;
703
                    }
704
                }
705
706
                $line->date_start          = $this->db->jdate($objp->date_start);
707
                $line->date_end            = $this->db->jdate($objp->date_end);
708
                $line->fk_unit             = $objp->fk_unit;
709
710
                // Multicurrency
711
                $line->fk_multicurrency = $objp->fk_multicurrency;
712
                $line->multicurrency_code = $objp->multicurrency_code;
713
                $line->multicurrency_subprice = $objp->multicurrency_subprice;
714
                $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
715
                $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
716
                $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
717
718
                $line->special_code        = $objp->special_code;
719
                $line->fk_parent_line      = $objp->fk_parent_line;
720
721
                $line->rang                = $objp->rang;
722
723
                // Retrieve all extrafield
724
                // fetch optionals attributes and labels
725
                $line->fetch_optionals();
726
727
                $this->lines[$i] = $line;
728
729
                $i++;
730
            }
731
            $this->db->free($result);
732
733
            return $num;
734
        } else {
735
            $this->error = $this->db->error() . " sql=" . $sql;
736
            return -1;
737
        }
738
    }
739
740
    /**
741
     *  Validate an order
742
     *
743
     *  @param  User    $user           Validator User
744
     *  @param  int     $idwarehouse    Id of warehouse to use for stock decrease
745
     *  @param  int     $notrigger      1=Does not execute triggers, 0= execute triggers
746
     *  @return int                     Return integer <0 if KO, >0 if OK
747
     */
748
    public function valid($user, $idwarehouse = 0, $notrigger = 0)
749
    {
750
        global $conf;
751
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
752
753
        $error = 0;
754
755
        dol_syslog(get_class($this) . "::valid");
756
        $result = 0;
757
        if (
758
            (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! getDolGlobalString('M...r_advance', 'validate'), Probably Intended Meaning: ! getDolGlobalString('MA..._advance', 'validate'))
Loading history...
759
            || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight("fournisseur", "supplier_order_advance", "validate"))
760
        ) {
761
            $this->db->begin();
762
763
            // Definition of supplier order numbering model name
764
            $soc = new Societe($this->db);
765
            $soc->fetch($this->fourn_id);
766
767
            // Check if object has a temporary ref
768
            if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
769
                $num = $this->getNextNumRef($soc);
770
            } else {
771
                $num = $this->ref;
772
            }
773
            $this->newref = dol_sanitizeFileName($num);
774
775
            $sql = 'UPDATE ' . $this->db->prefix() . "commande_fournisseur";
776
            $sql .= " SET ref='" . $this->db->escape($num) . "',";
777
            $sql .= " fk_statut = " . ((int) self::STATUS_VALIDATED) . ",";
778
            $sql .= " date_valid='" . $this->db->idate(dol_now()) . "',";
779
            $sql .= " fk_user_valid = " . ((int) $user->id);
780
            $sql .= " WHERE rowid = " . ((int) $this->id);
781
            $sql .= " AND fk_statut = " . ((int) self::STATUS_DRAFT);
782
783
            $resql = $this->db->query($sql);
784
            if (!$resql) {
785
                dol_print_error($this->db);
786
                $error++;
787
            }
788
789
            if (!$error && !$notrigger) {
790
                // Call trigger
791
                $result = $this->call_trigger('ORDER_SUPPLIER_VALIDATE', $user);
792
                if ($result < 0) {
793
                    $error++;
794
                }
795
                // End call triggers
796
            }
797
798
            if (!$error) {
799
                $this->oldref = $this->ref;
800
801
                // Rename directory if dir was a temporary ref
802
                if (preg_match('/^[\(]?PROV/i', $this->ref)) {
803
                    // Now we rename also files into index
804
                    $sql = 'UPDATE ' . $this->db->prefix() . "ecm_files set filename = CONCAT('" . $this->db->escape($this->newref) . "', SUBSTR(filename, " . (strlen($this->ref) + 1) . ")), filepath = 'fournisseur/commande/" . $this->db->escape($this->newref) . "'";
805
                    $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'fournisseur/commande/" . $this->db->escape($this->ref) . "' and entity = " . ((int) $conf->entity);
806
                    $resql = $this->db->query($sql);
807
                    if (!$resql) {
808
                        $error++;
809
                        $this->error = $this->db->lasterror();
810
                    }
811
                    $sql = 'UPDATE ' . $this->db->prefix() . "ecm_files set filepath = 'fournisseur/commande/" . $this->db->escape($this->newref) . "'";
812
                    $sql .= " WHERE filepath = 'fournisseur/commande/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
813
                    $resql = $this->db->query($sql);
814
                    if (!$resql) {
815
                        $error++;
816
                        $this->error = $this->db->lasterror();
817
                    }
818
819
                    // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
820
                    $oldref = dol_sanitizeFileName($this->ref);
821
                    $newref = dol_sanitizeFileName($num);
822
                    $dirsource = $conf->fournisseur->commande->dir_output . '/' . $oldref;
823
                    $dirdest = $conf->fournisseur->commande->dir_output . '/' . $newref;
824
                    if (!$error && file_exists($dirsource)) {
825
                        dol_syslog(get_class($this) . "::valid rename dir " . $dirsource . " into " . $dirdest);
826
827
                        if (@rename($dirsource, $dirdest)) {
828
                            dol_syslog("Rename ok");
829
                            // Rename docs starting with $oldref with $newref
830
                            $listoffiles = dol_dir_list($conf->fournisseur->commande->dir_output . '/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
831
                            foreach ($listoffiles as $fileentry) {
832
                                $dirsource = $fileentry['name'];
833
                                $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
834
                                $dirsource = $fileentry['path'] . '/' . $dirsource;
835
                                $dirdest = $fileentry['path'] . '/' . $dirdest;
836
                                @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

836
                                /** @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...
837
                            }
838
                        }
839
                    }
840
                }
841
            }
842
843
            if (!$error) {
844
                $result = 1;
845
                $this->status = self::STATUS_VALIDATED;
846
                $this->statut = self::STATUS_VALIDATED; // deprecated
847
                $this->ref = $num;
848
            }
849
850
            if (!$error) {
851
                $this->db->commit();
852
                return 1;
853
            } else {
854
                $this->db->rollback();
855
                return -1;
856
            }
857
        } else {
858
            $this->error = 'NotAuthorized';
859
            dol_syslog(get_class($this) . "::valid " . $this->error, LOG_ERR);
860
            return -1;
861
        }
862
    }
863
864
    /**
865
     *  Return label of the status of object
866
     *
867
     *  @param      int     $mode           0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=short label + picto
868
     *  @return     string                  Label
869
     */
870
    public function getLibStatut($mode = 0)
871
    {
872
        return $this->LibStatut($this->statut, $mode, $this->billed);
873
    }
874
875
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
876
    /**
877
     *  Return label of a status
878
     *
879
     *  @param  int     $status     Id statut
880
     *  @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
881
     *  @param  int     $billed     1=Billed
882
     *  @return string              Label of status
883
     */
884
    public function LibStatut($status, $mode = 0, $billed = 0)
885
    {
886
		// phpcs:enable
887
        global $langs, $hookmanager;
888
889
        if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
890
            $langs->load('orders');
891
892
            $this->labelStatus[0] = 'StatusSupplierOrderDraft';
893
            $this->labelStatus[1] = 'StatusSupplierOrderValidated';
894
            $this->labelStatus[2] = 'StatusSupplierOrderApproved';
895
            if (!getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
896
                $this->labelStatus[3] = 'StatusSupplierOrderOnProcess';
897
            } else {
898
                $this->labelStatus[3] = 'StatusSupplierOrderOnProcessWithValidation';
899
            }
900
            $this->labelStatus[4] = 'StatusSupplierOrderReceivedPartially';
901
            $this->labelStatus[5] = 'StatusSupplierOrderReceivedAll';
902
            $this->labelStatus[6] = 'StatusSupplierOrderCanceled'; // Approved->Canceled
903
            $this->labelStatus[7] = 'StatusSupplierOrderCanceled'; // Process running->canceled
904
            $this->labelStatus[9] = 'StatusSupplierOrderRefused';
905
906
            // List of language codes for status
907
            $this->labelStatusShort[0] = 'StatusSupplierOrderDraftShort';
908
            $this->labelStatusShort[1] = 'StatusSupplierOrderValidatedShort';
909
            $this->labelStatusShort[2] = 'StatusSupplierOrderApprovedShort';
910
            $this->labelStatusShort[3] = 'StatusSupplierOrderOnProcessShort';
911
            $this->labelStatusShort[4] = 'StatusSupplierOrderReceivedPartiallyShort';
912
            $this->labelStatusShort[5] = 'StatusSupplierOrderReceivedAllShort';
913
            $this->labelStatusShort[6] = 'StatusSupplierOrderCanceledShort';
914
            $this->labelStatusShort[7] = 'StatusSupplierOrderCanceledShort';
915
            $this->labelStatusShort[9] = 'StatusSupplierOrderRefusedShort';
916
        }
917
918
        $statustrans = array(
919
            0 => 'status0',
920
            1 => 'status1b',
921
            2 => 'status1',
922
            3 => 'status4',
923
            4 => 'status4b',
924
            5 => 'status6',
925
            6 => 'status9',
926
            7 => 'status9',
927
            9 => 'status9',
928
        );
929
930
        $statusClass = 'status0';
931
        if (!empty($statustrans[$status])) {
932
            $statusClass = $statustrans[$status];
933
        }
934
935
        $billedtext = '';
936
        if ($billed) {
937
            $billedtext = ' - ' . $langs->trans("Billed");
938
        }
939
        if ($status == 5 && $billed) {
940
            $statusClass = 'status6';
941
        }
942
943
        $statusLong = $langs->transnoentitiesnoconv($this->labelStatus[$status]) . $billedtext;
944
        $statusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
945
946
        $parameters = array('status' => $status, 'mode' => $mode, 'billed' => $billed);
947
        $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
948
        if ($reshook > 0) {
949
            return $hookmanager->resPrint;
950
        }
951
952
        return dolGetStatus($statusLong, $statusShort, '', $statusClass, $mode);
953
    }
954
955
    /**
956
     * getTooltipContentArray
957
     *
958
     * @param array $params ex option, infologin
959
     * @since v18
960
     * @return array
961
     */
962
    public function getTooltipContentArray($params)
963
    {
964
        global $conf, $langs, $user;
965
966
        $langs->loadLangs(['bills', 'orders']);
967
968
        $datas = [];
969
        $nofetch = !empty($params['nofetch']);
970
971
        if ($user->hasRight("fournisseur", "commande", "read")) {
972
            $datas['picto'] = '<u class="paddingrightonly">' . $langs->trans("SupplierOrder") . '</u>';
973
            if (isset($this->statut)) {
974
                $datas['picto'] .= ' ' . $this->getLibStatut(5);
975
            }
976
            if (!empty($this->ref)) {
977
                $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
978
            }
979
            if (!empty($this->ref_supplier)) {
980
                $datas['refsupplier'] = '<br><b>' . $langs->trans('RefSupplier') . ':</b> ' . $this->ref_supplier;
981
            }
982
            if (!$nofetch) {
983
                $langs->load('companies');
984
                if (empty($this->thirdparty)) {
985
                    $this->fetch_thirdparty();
986
                }
987
                $datas['supplier'] = '<br><b>' . $langs->trans('Supplier') . ':</b> ' . $this->thirdparty->getNomUrl(1, '', 0, 1);
988
            }
989
            if (!empty($this->total_ht)) {
990
                $datas['totalht'] = '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
991
            }
992
            if (!empty($this->total_tva)) {
993
                $datas['totaltva'] = '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
994
            }
995
            if (!empty($this->total_ttc)) {
996
                $datas['totalttc'] = '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
997
            }
998
            if (!empty($this->date)) {
999
                $datas['date'] = '<br><b>' . $langs->trans('Date') . ':</b> ' . dol_print_date($this->date, 'day');
1000
            }
1001
            if (!empty($this->delivery_date)) {
1002
                $datas['deliverydate'] = '<br><b>' . $langs->trans('DeliveryDate') . ':</b> ' . dol_print_date($this->delivery_date, 'dayhour');
1003
            }
1004
        }
1005
        return $datas;
1006
    }
1007
1008
    /**
1009
     *  Return clicable name (with picto eventually)
1010
     *
1011
     *  @param      int     $withpicto                  0=No picto, 1=Include picto into link, 2=Only picto
1012
     *  @param      string  $option                     On what the link points
1013
     *  @param      int     $notooltip                  1=Disable tooltip
1014
     *  @param      int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1015
     *  @param      int     $addlinktonotes             Add link to show notes
1016
     *  @return     string                              Chain with URL
1017
     */
1018
    public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
1019
    {
1020
        global $langs, $user, $hookmanager;
1021
1022
        $result = '';
1023
        $params = [
1024
            'id' => $this->id,
1025
            'objecttype' => $this->element,
1026
            'option' => $option,
1027
            'nofetch' => 1
1028
        ];
1029
        $classfortooltip = 'classfortooltip';
1030
        $dataparams = '';
1031
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1032
            $classfortooltip = 'classforajaxtooltip';
1033
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1034
            $label = '';
1035
        } else {
1036
            $label = implode($this->getTooltipContentArray($params));
1037
        }
1038
1039
        $url = constant('BASE_URL') . '/fourn/commande/card.php?id=' . $this->id;
1040
1041
        if ($option !== 'nolink') {
1042
            // Add param to save lastsearch_values or not
1043
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1044
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1045
                $add_save_lastsearch_values = 1;
1046
            }
1047
            if ($add_save_lastsearch_values) {
1048
                $url .= '&save_lastsearch_values=1';
1049
            }
1050
        }
1051
1052
        $linkclose = '';
1053
        if (empty($notooltip)) {
1054
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1055
                $label = $langs->trans("ShowOrder");
1056
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1057
            }
1058
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1059
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
1060
        }
1061
1062
        $linkstart = '<a href="' . $url . '"';
1063
        $linkstart .= $linkclose . '>';
1064
        $linkend = '</a>';
1065
1066
        $result .= $linkstart;
1067
        if ($withpicto) {
1068
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1069
        }
1070
        if ($withpicto != 2) {
1071
            $result .= $this->ref;
1072
        }
1073
        $result .= $linkend;
1074
1075
        if ($addlinktonotes) {
1076
            $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
1077
            if ($txttoshow) {
1078
                $notetoshow = $langs->trans("ViewPrivateNote") . ':<br>' . dol_string_nohtmltag($txttoshow, 1);
1079
                $result .= ' <span class="note inline-block">';
1080
                $result .= '<a href="' . constant('BASE_URL') . '/fourn/commande/note.php?id=' . $this->id . '" class="classfortooltip" title="' . dol_escape_htmltag($notetoshow) . '">';
1081
                $result .= img_picto('', 'note');
1082
                $result .= '</a>';
1083
                //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1084
                //$result.='</a>';
1085
                $result .= '</span>';
1086
            }
1087
        }
1088
1089
        global $action;
1090
        $hookmanager->initHooks(array($this->element . 'dao'));
1091
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1092
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1093
        if ($reshook > 0) {
1094
            $result = $hookmanager->resPrint;
1095
        } else {
1096
            $result .= $hookmanager->resPrint;
1097
        }
1098
        return $result;
1099
    }
1100
1101
1102
    /**
1103
     *  Returns the next order reference not used, based on the
1104
     *  numbering model defined within COMMANDE_SUPPLIER_ADDON_NUMBER
1105
     *
1106
     *  @param      Societe     $soc        company object
1107
     *  @return     string|int              free reference for the invoice. '', -1 or -2 if error.
1108
     */
1109
    public function getNextNumRef($soc)
1110
    {
1111
        global $langs, $conf;
1112
        $langs->load("orders");
1113
1114
        if (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER')) {
1115
            $mybool = false;
1116
1117
            $file = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER') . '.php';
1118
            $classname = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER');
1119
1120
            // Include file with class
1121
            $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1122
1123
            foreach ($dirmodels as $reldir) {
1124
                $dir = dol_buildpath($reldir . "core/modules/supplier_order/");
1125
1126
                // Load file with numbering class (if found)
1127
                $mybool = ((bool) @include_once $dir . $file) || $mybool;
1128
            }
1129
1130
            if ($mybool === false) {
1131
                dol_print_error(null, "Failed to include file " . $file);
1132
                return '';
1133
            }
1134
1135
            $obj = new $classname();
1136
            $numref = $obj->getNextValue($soc, $this);
1137
1138
            if ($numref != "") {
1139
                return $numref;
1140
            } else {
1141
                $this->error = $obj->error;
1142
                return -1;
1143
            }
1144
        } else {
1145
            $this->error = "Error_COMMANDE_SUPPLIER_ADDON_NotDefined";
1146
            return -2;
1147
        }
1148
    }
1149
    /**
1150
     *  Class invoiced the supplier order
1151
     *
1152
     *  @param      User        $user       Object user making the change
1153
     *  @return     int                     Return integer <0 if KO, 0 if already billed,  >0 if OK
1154
     */
1155
    public function classifyBilled(User $user)
1156
    {
1157
        $error = 0;
1158
1159
        if ($this->billed) {
1160
            return 0;
1161
        }
1162
1163
        $this->db->begin();
1164
1165
        $sql = 'UPDATE ' . $this->db->prefix() . 'commande_fournisseur SET billed = 1';
1166
        $sql .= " WHERE rowid = " . ((int) $this->id) . ' AND fk_statut > ' . self::STATUS_DRAFT;
1167
1168
        if ($this->db->query($sql)) {
1169
            if (!$error) {
1170
                // Call trigger
1171
                $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_BILLED', $user);
1172
                if ($result < 0) {
1173
                    $error++;
1174
                }
1175
                // End call triggers
1176
            }
1177
1178
            if (!$error) {
1179
                $this->billed = 1;
1180
1181
                $this->db->commit();
1182
                return 1;
1183
            } else {
1184
                $this->db->rollback();
1185
                return -1;
1186
            }
1187
        } else {
1188
            dol_print_error($this->db);
1189
1190
            $this->db->rollback();
1191
            return -1;
1192
        }
1193
    }
1194
1195
    /**
1196
     *  Approve a supplier order
1197
     *
1198
     *  @param  User    $user           Object user
1199
     *  @param  int     $idwarehouse    Id of warhouse for stock change
1200
     *  @param  int     $secondlevel    0=Standard approval, 1=Second level approval (used when option SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set)
1201
     *  @return int                     Return integer <0 if KO, >0 if OK
1202
     */
1203
    public function approve($user, $idwarehouse = 0, $secondlevel = 0)
1204
    {
1205
        global $langs, $conf;
1206
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1207
1208
        $error = 0;
1209
1210
        dol_syslog(get_class($this) . "::approve");
1211
1212
        if ($user->hasRight("fournisseur", "commande", "approuver")) {
1213
            $now = dol_now();
1214
1215
            $this->db->begin();
1216
1217
            // Definition of order numbering model name
1218
            $soc = new Societe($this->db);
1219
            $soc->fetch($this->fourn_id);
1220
1221
            // Check if object has a temporary ref
1222
            if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1223
                $num = $this->getNextNumRef($soc);
1224
            } else {
1225
                $num = $this->ref;
1226
            }
1227
            $this->newref = dol_sanitizeFileName($num);
1228
1229
            // Do we have to change status now ? (If double approval is required and first approval, we keep status to 1 = validated)
1230
            $movetoapprovestatus = true;
1231
            $comment = '';
1232
1233
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
1234
            $sql .= " SET ref='" . $this->db->escape($num) . "',";
1235
            if (empty($secondlevel)) {  // standard or first level approval
1236
                $sql .= " date_approve='" . $this->db->idate($now) . "',";
1237
                $sql .= " fk_user_approve = " . $user->id;
1238
                if (getDolGlobalString('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED') && $this->total_ht >= $conf->global->SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED) {
1239
                    if (empty($this->user_approve_id2)) {
1240
                        $movetoapprovestatus = false; // second level approval not done
1241
                        $comment = ' (first level)';
1242
                    }
1243
                }
1244
            } else { // request a second level approval
1245
                $sql .= " date_approve2='" . $this->db->idate($now) . "',";
1246
                $sql .= " fk_user_approve2 = " . ((int) $user->id);
1247
                if (empty($this->user_approve_id)) {
1248
                    $movetoapprovestatus = false; // first level approval not done
1249
                }
1250
                $comment = ' (second level)';
1251
            }
1252
            // If double approval is required and first approval, we keep status to 1 = validated
1253
            if ($movetoapprovestatus) {
1254
                $sql .= ", fk_statut = " . self::STATUS_ACCEPTED;
1255
            } else {
1256
                $sql .= ", fk_statut = " . self::STATUS_VALIDATED;
1257
            }
1258
            $sql .= " WHERE rowid = " . ((int) $this->id);
1259
            $sql .= " AND fk_statut = " . self::STATUS_VALIDATED;
1260
1261
            if ($this->db->query($sql)) {
1262
                if (getDolGlobalString('SUPPLIER_ORDER_AUTOADD_USER_CONTACT')) {
1263
                    $result = $this->add_contact($user->id, 'SALESREPFOLL', 'internal', 1);
1264
                    if ($result < 0 && $result != -2) { // -2 means already exists
1265
                        $error++;
1266
                    }
1267
                }
1268
1269
                // If stock is incremented on validate order, we must increment it
1270
                if (!$error && $movetoapprovestatus && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
1271
                    require_once constant('DOL_DOCUMENT_ROOT') . '/product/stock/class/mouvementstock.class.php';
1272
                    $langs->load("agenda");
1273
1274
                    $cpt = count($this->lines);
1275
                    for ($i = 0; $i < $cpt; $i++) {
1276
                        // Product with reference
1277
                        if ($this->lines[$i]->fk_product > 0) {
1278
                            $this->line = $this->lines[$i];
1279
                            $mouvP = new MouvementStock($this->db);
1280
                            $mouvP->origin = &$this;
0 ignored issues
show
Deprecated Code introduced by
The property 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

1280
                            /** @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...
1281
                            $mouvP->setOrigin($this->element, $this->id);
1282
                            // We decrement stock of product (and sub-products)
1283
                            $up_ht_disc = $this->lines[$i]->subprice;
1284
                            if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1285
                                $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1286
                            }
1287
                            $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("OrderApprovedInDolibarr", $this->ref));
1288
                            if ($result < 0) {
1289
                                $error++;
1290
                            }
1291
                            unset($this->line);
1292
                        }
1293
                    }
1294
                }
1295
1296
                if (!$error) {
1297
                    // Call trigger
1298
                    $result = $this->call_trigger('ORDER_SUPPLIER_APPROVE', $user);
1299
                    if ($result < 0) {
1300
                        $error++;
1301
                    }
1302
                    // End call triggers
1303
                }
1304
1305
                if (!$error) {
1306
                    $this->ref = $this->newref;
1307
1308
                    if ($movetoapprovestatus) {
1309
                        $this->statut = self::STATUS_ACCEPTED;
1310
                    } else {
1311
                        $this->statut = self::STATUS_VALIDATED;
1312
                    }
1313
                    if (empty($secondlevel)) {  // standard or first level approval
1314
                        $this->date_approve = $now;
1315
                        $this->user_approve_id = $user->id;
1316
                    } else { // request a second level approval
1317
                        $this->date_approve2 = $now;
1318
                        $this->user_approve_id2 = $user->id;
1319
                    }
1320
1321
                    $this->db->commit();
1322
                    return 1;
1323
                } else {
1324
                    $this->db->rollback();
1325
                    return -1;
1326
                }
1327
            } else {
1328
                $this->db->rollback();
1329
                $this->error = $this->db->lasterror();
1330
                return -1;
1331
            }
1332
        } else {
1333
            dol_syslog(get_class($this) . "::approve Not Authorized", LOG_ERR);
1334
        }
1335
        return -1;
1336
    }
1337
1338
    /**
1339
     *  Refuse an order
1340
     *
1341
     *  @param      User    $user       User making action
1342
     *  @return     int                 0 if Ok, <0 if Ko
1343
     */
1344
    public function refuse($user)
1345
    {
1346
        global $conf, $langs;
1347
1348
        $error = 0;
1349
1350
        dol_syslog(get_class($this) . "::refuse");
1351
        $result = 0;
1352
        if ($user->hasRight("fournisseur", "commande", "approuver")) {
1353
            $this->db->begin();
1354
1355
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur SET fk_statut = " . self::STATUS_REFUSED;
1356
            $sql .= " WHERE rowid = " . ((int) $this->id);
1357
1358
            if ($this->db->query($sql)) {
1359
                $result = 0;
1360
1361
                if ($error == 0) {
1362
                    // Call trigger
1363
                    $result = $this->call_trigger('ORDER_SUPPLIER_REFUSE', $user);
1364
                    if ($result < 0) {
1365
                        $error++;
1366
                        $this->db->rollback();
1367
                    } else {
1368
                        $this->db->commit();
1369
                    }
1370
                    // End call triggers
1371
                }
1372
            } else {
1373
                $this->db->rollback();
1374
                $this->error = $this->db->lasterror();
1375
                dol_syslog(get_class($this) . "::refuse Error -1");
1376
                $result = -1;
1377
            }
1378
        } else {
1379
            dol_syslog(get_class($this) . "::refuse Not Authorized");
1380
        }
1381
        return $result;
1382
    }
1383
1384
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1385
    /**
1386
     *  Cancel an approved order.
1387
     *  The cancellation is done after approval
1388
     *
1389
     *  @param  User    $user           User making action
1390
     *  @param  int     $idwarehouse    Id warehouse to use for stock change (not used for supplier orders).
1391
     *  @return int                     >0 if Ok, <0 if Ko
1392
     */
1393
    public function Cancel($user, $idwarehouse = -1)
1394
    {
1395
		// phpcs:enable
1396
        global $langs, $conf;
1397
1398
        $error = 0;
1399
1400
        //dol_syslog("CommandeFournisseur::Cancel");
1401
        $result = 0;
1402
        if ($user->hasRight("fournisseur", "commande", "commander")) {
1403
            $statut = self::STATUS_CANCELED;
1404
1405
            $this->db->begin();
1406
1407
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur SET fk_statut = " . ((int) $statut);
1408
            $sql .= " WHERE rowid = " . ((int) $this->id);
1409
            dol_syslog(get_class($this) . "::cancel", LOG_DEBUG);
1410
            if ($this->db->query($sql)) {
1411
                $result = 0;
1412
1413
                // Call trigger
1414
                $result = $this->call_trigger('ORDER_SUPPLIER_CANCEL', $user);
1415
                if ($result < 0) {
1416
                    $error++;
1417
                }
1418
                // End call triggers
1419
1420
                if ($error == 0) {
1421
                    $this->db->commit();
1422
                    return 1;
1423
                } else {
1424
                    $this->db->rollback();
1425
                    return -1;
1426
                }
1427
            } else {
1428
                $this->db->rollback();
1429
                $this->error = $this->db->lasterror();
1430
                dol_syslog(get_class($this) . "::cancel " . $this->error);
1431
                return -1;
1432
            }
1433
        } else {
1434
            dol_syslog(get_class($this) . "::cancel Not Authorized");
1435
            return -1;
1436
        }
1437
    }
1438
1439
    /**
1440
     *  Submit a supplier order to supplier
1441
     *
1442
     *  @param      User    $user       User making change
1443
     *  @param      integer $date       Date
1444
     *  @param      int     $methode    Method
1445
     *  @param      string  $comment    Comment
1446
     *  @return     int                 Return integer <0 if KO, >0 if OK
1447
     */
1448
    public function commande($user, $date, $methode, $comment = '')
1449
    {
1450
        global $langs;
1451
        dol_syslog(get_class($this) . "::commande");
1452
        $error = 0;
1453
        if ($user->hasRight("fournisseur", "commande", "commander")) {
1454
            $this->db->begin();
1455
1456
            $newnoteprivate = $this->note_private;
1457
            if ($comment) {
1458
                $newnoteprivate = dol_concatdesc($newnoteprivate, $langs->trans("Comment") . ': ' . $comment);
1459
            }
1460
1461
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
1462
            $sql .= " SET fk_statut=" . self::STATUS_ORDERSENT . ", fk_input_method=" . $methode . ", date_commande='" . $this->db->idate($date) . "', ";
1463
            $sql .= " note_private='" . $this->db->escape($newnoteprivate) . "'";
1464
            $sql .= " WHERE rowid=" . ((int) $this->id);
1465
1466
            dol_syslog(get_class($this) . "::commande", LOG_DEBUG);
1467
            if ($this->db->query($sql)) {
1468
                $this->statut = self::STATUS_ORDERSENT;
1469
                $this->methode_commande_id = $methode;
1470
                $this->date_commande = $date;
1471
                $this->context = array('comments' => $comment);
1472
1473
                // Call trigger
1474
                $result = $this->call_trigger('ORDER_SUPPLIER_SUBMIT', $user);
1475
                if ($result < 0) {
1476
                    $error++;
1477
                }
1478
                // End call triggers
1479
            } else {
1480
                $error++;
1481
                $this->error = $this->db->lasterror();
1482
                $this->errors[] = $this->db->lasterror();
1483
            }
1484
1485
            if (!$error) {
1486
                $this->db->commit();
1487
            } else {
1488
                $this->db->rollback();
1489
            }
1490
        } else {
1491
            $error++;
1492
            $this->error = $langs->trans('NotAuthorized');
1493
            $this->errors[] = $langs->trans('NotAuthorized');
1494
            dol_syslog(get_class($this) . "::commande User not Authorized", LOG_WARNING);
1495
        }
1496
1497
        return ($error ? -1 : 1);
1498
    }
1499
1500
    /**
1501
     *  Create order with draft status
1502
     *
1503
     *  @param      User    $user       User making creation
1504
     *  @param      int     $notrigger  Disable all triggers
1505
     *  @return     int                 Return integer <0 if KO, Id of supplier order if OK
1506
     */
1507
    public function create($user, $notrigger = 0)
1508
    {
1509
        global $langs, $conf, $hookmanager;
1510
1511
        $this->db->begin();
1512
1513
        $error = 0;
1514
        $now = dol_now();
1515
1516
        // set tmp vars
1517
        $date = ($this->date_commande ? $this->date_commande : $this->date); // in case of date is set
1518
        if (empty($date)) {
1519
            $date = $now;
1520
        }
1521
        $delivery_date = $this->delivery_date;
1522
1523
        // Clean parameters
1524
        if (empty($this->source)) {
1525
            $this->source = 0;
1526
        }
1527
1528
        // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1529
        if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1530
            list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
1531
        } else {
1532
            $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1533
        }
1534
        if (empty($this->fk_multicurrency)) {
1535
            $this->multicurrency_code = $conf->currency;
1536
            $this->fk_multicurrency = 0;
1537
            $this->multicurrency_tx = 1;
1538
        }
1539
1540
        $this->statut = self::STATUS_DRAFT; // deprecated
1541
        $this->status = self::STATUS_DRAFT;
1542
1543
        $sql = "INSERT INTO " . $this->db->prefix() . "commande_fournisseur (";
1544
        $sql .= "ref";
1545
        $sql .= ", ref_supplier";
1546
        $sql .= ", note_private";
1547
        $sql .= ", note_public";
1548
        $sql .= ", entity";
1549
        $sql .= ", fk_soc";
1550
        $sql .= ", fk_projet";
1551
        $sql .= ", date_creation";
1552
        $sql .= ", date_livraison";
1553
        $sql .= ", fk_user_author";
1554
        $sql .= ", fk_statut";
1555
        $sql .= ", source";
1556
        $sql .= ", model_pdf";
1557
        $sql .= ", fk_mode_reglement";
1558
        $sql .= ", fk_cond_reglement";
1559
        $sql .= ", fk_account";
1560
        $sql .= ", fk_incoterms, location_incoterms";
1561
        $sql .= ", fk_multicurrency";
1562
        $sql .= ", multicurrency_code";
1563
        $sql .= ", multicurrency_tx";
1564
        $sql .= ") ";
1565
        $sql .= " VALUES (";
1566
        $sql .= "'(PROV)'";
1567
        $sql .= ", " . (isset($this->ref_supplier) ? "'" . $this->db->escape($this->ref_supplier) . "'" : "NULL");
1568
        $sql .= ", '" . $this->db->escape($this->note_private) . "'";
1569
        $sql .= ", '" . $this->db->escape($this->note_public) . "'";
1570
        $sql .= ", " . setEntity($this);
1571
        $sql .= ", " . ((int) $this->socid);
1572
        $sql .= ", " . ($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1573
        $sql .= ", '" . $this->db->idate($date) . "'";
1574
        $sql .= ", " . ($delivery_date ? "'" . $this->db->idate($delivery_date) . "'" : "null");
1575
        $sql .= ", " . ((int) $user->id);
1576
        $sql .= ", " . self::STATUS_DRAFT;
1577
        $sql .= ", " . ((int) $this->source);
1578
        $sql .= ", '" . $this->db->escape(getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) . "'";
1579
        $sql .= ", " . ($this->mode_reglement_id > 0 ? $this->mode_reglement_id : 'null');
1580
        $sql .= ", " . ($this->cond_reglement_id > 0 ? $this->cond_reglement_id : 'null');
1581
        $sql .= ", " . ($this->fk_account > 0 ? $this->fk_account : 'NULL');
1582
        $sql .= ", " . (int) $this->fk_incoterms;
1583
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
1584
        $sql .= ", " . (int) $this->fk_multicurrency;
1585
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
1586
        $sql .= ", " . (float) $this->multicurrency_tx;
1587
        $sql .= ")";
1588
1589
        dol_syslog(get_class($this) . "::create", LOG_DEBUG);
1590
        if ($this->db->query($sql)) {
1591
            $this->id = $this->db->last_insert_id($this->db->prefix() . "commande_fournisseur");
1592
1593
            if ($this->id) {
1594
                $num = count($this->lines);
1595
1596
                // insert products details into database
1597
                for ($i = 0; $i < $num; $i++) {
1598
                    $line = $this->lines[$i];
1599
                    if (!is_object($line)) {
1600
                        $line = (object) $line;
1601
                    }
1602
1603
                    //$this->special_code = $line->special_code; // TODO : remove this in 9.0 and add special_code param to addline()
1604
1605
                    // This include test on qty if option SUPPLIER_ORDER_WITH_NOPRICEDEFINED is not set
1606
                    $result = $this->addline(
1607
                        $line->desc,
1608
                        $line->subprice,
1609
                        $line->qty,
1610
                        $line->tva_tx,
1611
                        $line->localtax1_tx,
1612
                        $line->localtax2_tx,
1613
                        $line->fk_product,
1614
                        0,
1615
                        $line->ref_fourn, // $line->ref_fourn comes from field ref into table of lines. Value may ba a ref that does not exists anymore, so we first try with value of product
1616
                        $line->remise_percent,
1617
                        'HT',
1618
                        0,
1619
                        $line->product_type,
1620
                        $line->info_bits,
1621
                        false,
1622
                        $line->date_start,
1623
                        $line->date_end,
1624
                        $line->array_options,
1625
                        $line->fk_unit,
1626
                        $line->multicurrency_subprice,  // pu_ht_devise
1627
                        $line->origin,     // origin
1628
                        $line->origin_id,  // origin_id
1629
                        $line->rang,       // rang
1630
                        $line->special_code
1631
                    );
1632
                    if ($result < 0) {
1633
                        dol_syslog(get_class($this) . "::create " . $this->error, LOG_WARNING); // do not use dol_print_error here as it may be a functional error
1634
                        $this->db->rollback();
1635
                        return -1;
1636
                    }
1637
                }
1638
1639
                $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
1640
                $sql .= " SET ref='(PROV" . $this->id . ")'";
1641
                $sql .= " WHERE rowid=" . ((int) $this->id);
1642
1643
                dol_syslog(get_class($this) . "::create", LOG_DEBUG);
1644
                if ($this->db->query($sql)) {
1645
                    // Add link with price request and supplier order
1646
                    if ($this->id) {
1647
                        $this->ref = "(PROV" . $this->id . ")";
1648
1649
                        if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {  // To use new linkedObjectsIds instead of old linked_objects
1650
                            $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1651
                        }
1652
1653
                        // Add object linked
1654
                        if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1655
                            foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1656
                                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, ...))
1657
                                    foreach ($tmp_origin_id as $origin_id) {
1658
                                        $ret = $this->add_object_linked($origin, $origin_id);
1659
                                        if (!$ret) {
1660
                                            dol_print_error($this->db);
1661
                                            $error++;
1662
                                        }
1663
                                    }
1664
                                } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1665
                                    $origin_id = $tmp_origin_id;
1666
                                    $ret = $this->add_object_linked($origin, $origin_id);
1667
                                    if (!$ret) {
1668
                                        dol_print_error($this->db);
1669
                                        $error++;
1670
                                    }
1671
                                }
1672
                            }
1673
                        }
1674
                    }
1675
1676
                    if (!$error) {
1677
                        $result = $this->insertExtraFields();
1678
                        if ($result < 0) {
1679
                            $error++;
1680
                        }
1681
                    }
1682
1683
                    if (!$error && !$notrigger) {
1684
                        // Call trigger
1685
                        $result = $this->call_trigger('ORDER_SUPPLIER_CREATE', $user);
1686
                        if ($result < 0) {
1687
                            $this->db->rollback();
1688
1689
                            return -1;
1690
                        }
1691
                        // End call triggers
1692
                    }
1693
1694
                    $this->db->commit();
1695
                    return $this->id;
1696
                } else {
1697
                    $this->error = $this->db->lasterror();
1698
                    $this->db->rollback();
1699
1700
                    return -2;
1701
                }
1702
            } else {
1703
                $this->error = 'Failed to get ID of inserted line';
1704
1705
                return -1;
1706
            }
1707
        } else {
1708
            $this->error = $this->db->lasterror();
1709
            $this->db->rollback();
1710
1711
            return -1;
1712
        }
1713
    }
1714
1715
    /**
1716
     *  Update Supplier Order
1717
     *
1718
     *  @param      User    $user           User that modify
1719
     *  @param      int     $notrigger      0=launch triggers after, 1=disable triggers
1720
     *  @return     int                     Return integer <0 if KO, >0 if OK
1721
     */
1722
    public function update(User $user, $notrigger = 0)
1723
    {
1724
        global $conf;
1725
1726
        $error = 0;
1727
1728
        // Clean parameters
1729
        if (isset($this->ref)) {
1730
            $this->ref = trim($this->ref);
1731
        }
1732
        if (isset($this->ref_supplier)) {
1733
            $this->ref_supplier = trim($this->ref_supplier);
1734
        }
1735
        if (isset($this->note_private)) {
1736
            $this->note_private = trim($this->note_private);
1737
        }
1738
        if (isset($this->note_public)) {
1739
            $this->note_public = trim($this->note_public);
1740
        }
1741
        if (isset($this->model_pdf)) {
1742
            $this->model_pdf = trim($this->model_pdf);
1743
        }
1744
        if (isset($this->import_key)) {
1745
            $this->import_key = trim($this->import_key);
1746
        }
1747
1748
        // Update request
1749
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element . " SET";
1750
1751
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
1752
        $sql .= " ref_supplier=" . (isset($this->ref_supplier) ? "'" . $this->db->escape($this->ref_supplier) . "'" : "null") . ",";
1753
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
1754
        $sql .= " fk_soc=" . (isset($this->socid) ? $this->socid : "null") . ",";
1755
        $sql .= " date_commande=" . (strval($this->date_commande) != '' ? "'" . $this->db->idate($this->date_commande) . "'" : 'null') . ",";
1756
        $sql .= " date_valid=" . (strval($this->date_validation) != '' ? "'" . $this->db->idate($this->date_validation) . "'" : 'null') . ",";
1757
        $sql .= " total_tva=" . (isset($this->total_tva) ? $this->total_tva : "null") . ",";
1758
        $sql .= " localtax1=" . (isset($this->total_localtax1) ? $this->total_localtax1 : "null") . ",";
1759
        $sql .= " localtax2=" . (isset($this->total_localtax2) ? $this->total_localtax2 : "null") . ",";
1760
        $sql .= " total_ht=" . (isset($this->total_ht) ? $this->total_ht : "null") . ",";
1761
        $sql .= " total_ttc=" . (isset($this->total_ttc) ? $this->total_ttc : "null") . ",";
1762
        $sql .= " fk_statut=" . (isset($this->statut) ? $this->statut : "null") . ",";
1763
        $sql .= " fk_user_author=" . (isset($this->user_author_id) ? $this->user_author_id : "null") . ",";
1764
        $sql .= " fk_user_valid=" . (isset($this->user_validation_id) && $this->user_validation_id > 0 ? $this->user_validation_id : "null") . ",";
1765
        $sql .= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
1766
        $sql .= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null") . ",";
1767
        $sql .= " fk_mode_reglement=" . (isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null") . ",";
1768
        $sql .= " date_livraison=" . (strval($this->delivery_date) != '' ? "'" . $this->db->idate($this->delivery_date) . "'" : 'null') . ",";
1769
        //$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
1770
        $sql .= " fk_account=" . ($this->fk_account > 0 ? $this->fk_account : "null") . ",";
1771
        //$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
1772
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
1773
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
1774
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
1775
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null");
1776
1777
        $sql .= " WHERE rowid=" . ((int) $this->id);
1778
1779
        $this->db->begin();
1780
1781
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
1782
        $resql = $this->db->query($sql);
1783
        if (!$resql) {
1784
            $error++;
1785
            $this->errors[] = "Error " . $this->db->lasterror();
1786
        }
1787
1788
        if (!$error) {
1789
            $result = $this->insertExtraFields();
1790
            if ($result < 0) {
1791
                $error++;
1792
            }
1793
        }
1794
1795
        if (!$error && !$notrigger) {
1796
            // Call trigger
1797
            $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
1798
            if ($result < 0) {
1799
                $error++;
1800
            }
1801
            // End call triggers
1802
        }
1803
1804
        // Commit or rollback
1805
        if ($error) {
1806
            foreach ($this->errors as $errmsg) {
1807
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
1808
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1809
            }
1810
            $this->db->rollback();
1811
            return -1 * $error;
1812
        } else {
1813
            $this->db->commit();
1814
            return 1;
1815
        }
1816
    }
1817
1818
    /**
1819
     *  Load an object from its id and create a new one in database
1820
     *
1821
     *  @param      User    $user       User making the clone
1822
     *  @param      int     $socid      Id of thirdparty
1823
     *  @param      int     $notrigger  Disable all triggers
1824
     *  @return     int                 New id of clone
1825
     */
1826
    public function createFromClone(User $user, $socid = 0, $notrigger = 0)
1827
    {
1828
        global $conf, $user, $hookmanager;
1829
1830
        $error = 0;
1831
1832
        $this->db->begin();
1833
1834
        // get extrafields so they will be clone
1835
        foreach ($this->lines as $line) {
1836
            $line->fetch_optionals();
1837
        }
1838
1839
        // Load source object
1840
        $objFrom = clone $this;
1841
1842
        // Change socid if needed
1843
        if (!empty($socid) && $socid != $this->socid) {
1844
            $objsoc = new Societe($this->db);
1845
1846
            if ($objsoc->fetch($socid) > 0) {
1847
                $this->socid = $objsoc->id;
1848
                $this->cond_reglement_id    = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1849
                $this->mode_reglement_id    = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1850
                $this->fk_project = 0;
1851
                $this->fk_delivery_address = 0;
1852
            }
1853
1854
            // TODO Change product price if multi-prices
1855
        }
1856
1857
        $this->id = 0;
1858
        $this->statut = self::STATUS_DRAFT;
1859
1860
        // Clear fields
1861
        $this->user_author_id     = $user->id;
1862
        $this->user_validation_id = 0;
1863
1864
        $this->date               = dol_now();
1865
        $this->date_creation      = 0;
1866
        $this->date_validation    = 0;
1867
        $this->date_commande      = 0;
1868
        $this->ref_supplier       = '';
1869
        $this->user_approve_id    = 0;
1870
        $this->user_approve_id2   = 0;
1871
        $this->date_approve       = 0;
1872
        $this->date_approve2      = 0;
1873
1874
        // Create clone
1875
        $this->context['createfromclone'] = 'createfromclone';
1876
        $result = $this->create($user, $notrigger);
1877
        if ($result < 0) {
1878
            $error++;
1879
        }
1880
1881
        if (!$error) {
1882
            // Hook of thirdparty module
1883
            if (is_object($hookmanager)) {
1884
                $parameters = array('objFrom' => $objFrom);
1885
                $action = '';
1886
                $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1887
                if ($reshook < 0) {
1888
                    $this->setErrorsFromObject($hookmanager);
1889
                    $error++;
1890
                }
1891
            }
1892
        }
1893
1894
        unset($this->context['createfromclone']);
1895
1896
        // End
1897
        if (!$error) {
1898
            $this->db->commit();
1899
            return $this->id;
1900
        } else {
1901
            $this->db->rollback();
1902
            return -1;
1903
        }
1904
    }
1905
1906
    /**
1907
     *  Add order line
1908
     *
1909
     *  @param      string      $desc                   Description
1910
     *  @param      float       $pu_ht                  Unit price (used if $price_base_type is 'HT')
1911
     *  @param      float       $qty                    Quantity
1912
     *  @param      float       $txtva                  VAT Rate
1913
     *  @param      float       $txlocaltax1            Localtax1 tax
1914
     *  @param      float       $txlocaltax2            Localtax2 tax
1915
     *  @param      int         $fk_product             Id product
1916
     *  @param      int         $fk_prod_fourn_price    Id supplier price
1917
     *  @param      string      $ref_supplier           Supplier reference price
1918
     *  @param      float       $remise_percent         Remise
1919
     *  @param      string      $price_base_type        HT or TTC
1920
     *  @param      float       $pu_ttc                 Unit price TTC (used if $price_base_type is 'TTC')
1921
     *  @param      int         $type                   Type of line (0=product, 1=service)
1922
     *  @param      int         $info_bits              More information
1923
     *  @param      int         $notrigger              Disable triggers
1924
     *  @param      int         $date_start             Date start of service
1925
     *  @param      int         $date_end               Date end of service
1926
     *  @param      array       $array_options          extrafields array
1927
     *  @param      int|null    $fk_unit                Code of the unit to use. Null to use the default one
1928
     *  @param      int|string      $pu_ht_devise           Amount in currency
1929
     *  @param      string      $origin                 'order', ...
1930
     *  @param      int         $origin_id              Id of origin object
1931
     *  @param      int         $rang                   Rank
1932
     *  @param      int         $special_code           Special code
1933
     *  @return     int                                 Return integer <=0 if KO, >0 if OK
1934
     */
1935
    public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $fk_product = 0, $fk_prod_fourn_price = 0, $ref_supplier = '', $remise_percent = 0.0, $price_base_type = 'HT', $pu_ttc = 0.0, $type = 0, $info_bits = 0, $notrigger = 0, $date_start = null, $date_end = null, $array_options = [], $fk_unit = null, $pu_ht_devise = 0, $origin = '', $origin_id = 0, $rang = -1, $special_code = 0)
1936
    {
1937
        global $langs, $mysoc, $conf;
1938
1939
        dol_syslog(get_class($this) . "::addline $desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2, $fk_product, $fk_prod_fourn_price, $ref_supplier, $remise_percent, $price_base_type, $pu_ttc, $type, $info_bits, $notrigger, $date_start, $date_end, $fk_unit, $pu_ht_devise, $origin, $origin_id");
1940
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1941
1942
        if ($this->statut == self::STATUS_DRAFT) {
1943
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1944
1945
            // Clean parameters
1946
            if (empty($qty)) {
1947
                $qty = 0;
1948
            }
1949
            if (!$info_bits) {
1950
                $info_bits = 0;
1951
            }
1952
            if (empty($txtva)) {
1953
                $txtva = 0;
1954
            }
1955
            if (empty($rang)) {
1956
                $rang = 0;
1957
            }
1958
            if (empty($txlocaltax1)) {
1959
                $txlocaltax1 = 0;
1960
            }
1961
            if (empty($txlocaltax2)) {
1962
                $txlocaltax2 = 0;
1963
            }
1964
            if (empty($remise_percent)) {
1965
                $remise_percent = 0;
1966
            }
1967
1968
            $remise_percent = price2num($remise_percent);
1969
            $qty = price2num($qty);
1970
            $pu_ht = price2num($pu_ht);
1971
            $pu_ht_devise = price2num($pu_ht_devise);
1972
            $pu_ttc = price2num($pu_ttc);
1973
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
1974
                $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
1975
            }
1976
            $txlocaltax1 = price2num($txlocaltax1);
1977
            $txlocaltax2 = price2num($txlocaltax2);
1978
            if ($price_base_type == 'HT') {
1979
                $pu = $pu_ht;
1980
            } else {
1981
                $pu = $pu_ttc;
1982
            }
1983
            $desc = trim($desc);
1984
1985
            // Check parameters
1986
            if ($qty < 0 && !$fk_product) {
1987
                $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product"));
1988
                return -1;
1989
            }
1990
            if ($type < 0) {
1991
                return -1;
1992
            }
1993
            if ($date_start && $date_end && $date_start > $date_end) {
1994
                $langs->load("errors");
1995
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1996
                return -1;
1997
            }
1998
1999
2000
            $this->db->begin();
2001
2002
            $product_type = $type;
2003
            $label = '';    // deprecated
2004
2005
            if ($fk_product > 0) {
2006
                if (getDolGlobalString('SUPPLIER_ORDER_WITH_PREDEFINED_PRICES_ONLY')) { // Not the common case
2007
                    // Check quantity is enough
2008
                    dol_syslog(get_class($this) . "::addline we check supplier prices fk_product=" . $fk_product . " fk_prod_fourn_price=" . $fk_prod_fourn_price . " qty=" . $qty . " ref_supplier=" . $ref_supplier);
2009
                    $prod = new ProductFournisseur($this->db);
2010
                    if ($prod->fetch($fk_product) > 0) {
2011
                        $product_type = $prod->type;
2012
                        $label = $prod->label;
2013
2014
                        // We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
2015
                        // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2016
                        $result = $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', (isset($this->fk_soc) ? $this->fk_soc : $this->socid)); // Search on couple $fk_prod_fourn_price/$qty first, then on triplet $qty/$fk_product/$ref_supplier/$this->fk_soc
2017
2018
                        // If supplier order created from sales order, we take best supplier price
2019
                        // If $pu (defined previously from pu_ht or pu_ttc) is not defined at all, we also take the best supplier price
2020
                        if ($result > 0 && ($origin == 'commande' || $pu === '')) {
2021
                            $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2022
                            $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2023
                            // is remise percent not keyed but present for the product we add it
2024
                            if ($remise_percent == 0 && $prod->remise_percent != 0) {
2025
                                $remise_percent = $prod->remise_percent;
2026
                            }
2027
                        }
2028
                        if ($result == 0) {                   // If result == 0, we failed to found the supplier reference price
2029
                            $langs->load("errors");
2030
                            $this->error = "Ref " . $prod->ref . " " . $langs->trans("ErrorQtyTooLowForThisSupplier");
2031
                            $this->db->rollback();
2032
                            dol_syslog(get_class($this) . "::addline we did not found supplier price, so we can't guess unit price");
2033
                            //$pu    = $prod->fourn_pu;     // We do not overwrite unit price
2034
                            //$ref   = $prod->ref_fourn;    // We do not overwrite ref supplier price
2035
                            return -1;
2036
                        }
2037
                        if ($result == -1) {
2038
                            $langs->load("errors");
2039
                            $this->error = "Ref " . $prod->ref . " " . $langs->trans("ErrorQtyTooLowForThisSupplier");
2040
                            $this->db->rollback();
2041
                            dol_syslog(get_class($this) . "::addline result=" . $result . " - " . $this->error, LOG_DEBUG);
2042
                            return -1;
2043
                        }
2044
                        if ($result < -1) {
2045
                            $this->error = $prod->error;
2046
                            $this->db->rollback();
2047
                            dol_syslog(get_class($this) . "::addline result=" . $result . " - " . $this->error, LOG_ERR);
2048
                            return -1;
2049
                        }
2050
                    } else {
2051
                        $this->error = $prod->error;
2052
                        $this->db->rollback();
2053
                        return -1;
2054
                    }
2055
                }
2056
2057
                // Predefine quantity according to packaging
2058
                if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2059
                    $prod = new Product($this->db);
2060
                    $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc));
2061
2062
                    if ($qty < $prod->packaging) {
2063
                        $qty = $prod->packaging;
2064
                    } else {
2065
                        if (!empty($prod->packaging) && ($qty % $prod->packaging) > 0) {
2066
                            $coeff = intval($qty / $prod->packaging) + 1;
2067
                            $qty = $prod->packaging * $coeff;
2068
                            setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
2069
                        }
2070
                    }
2071
                }
2072
            }
2073
2074
            if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
2075
                $pu = 0;
2076
            }
2077
2078
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2079
2080
            // Clean vat code
2081
            $reg = array();
2082
            $vat_src_code = '';
2083
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
2084
                $vat_src_code = $reg[1];
2085
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
2086
            }
2087
2088
            // Calcul du total TTC et de la TVA pour la ligne a partir de
2089
            // qty, pu, remise_percent et txtva
2090
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2091
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2092
2093
            $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
2094
2095
            $total_ht  = $tabprice[0];
2096
            $total_tva = $tabprice[1];
2097
            $total_ttc = $tabprice[2];
2098
            $total_localtax1 = $tabprice[9];
2099
            $total_localtax2 = $tabprice[10];
2100
            $pu = $pu_ht = $tabprice[3];
2101
2102
            // MultiCurrency
2103
            $multicurrency_total_ht = $tabprice[16];
2104
            $multicurrency_total_tva = $tabprice[17];
2105
            $multicurrency_total_ttc = $tabprice[18];
2106
            $pu_ht_devise = $tabprice[19];
2107
2108
            $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2109
            $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2110
2111
            if ($rang < 0) {
2112
                $rangmax = $this->line_max();
2113
                $rang = $rangmax + 1;
2114
            }
2115
2116
            // Insert line
2117
            $this->line = new CommandeFournisseurLigne($this->db);
2118
2119
            $this->line->context = $this->context;
2120
2121
            $this->line->fk_commande = $this->id;
2122
            $this->line->label = $label;
2123
            $this->line->ref_fourn = $ref_supplier;
2124
            $this->line->ref_supplier = $ref_supplier;
2125
            $this->line->desc = $desc;
2126
            $this->line->qty = $qty;
2127
            $this->line->tva_tx = $txtva;
2128
            $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2129
            $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2130
            $this->line->localtax1_type = $localtax1_type;
2131
            $this->line->localtax2_type = $localtax2_type;
2132
            $this->line->fk_product = $fk_product;
2133
            $this->line->product_type = $product_type;
2134
            $this->line->remise_percent = $remise_percent;
2135
            $this->line->subprice = $pu_ht;
2136
            $this->line->rang = $rang;
2137
            $this->line->info_bits = $info_bits;
2138
2139
            $this->line->vat_src_code = $vat_src_code;
2140
            $this->line->total_ht = $total_ht;
2141
            $this->line->total_tva = $total_tva;
2142
            $this->line->total_localtax1 = $total_localtax1;
2143
            $this->line->total_localtax2 = $total_localtax2;
2144
            $this->line->total_ttc = $total_ttc;
2145
            $this->line->product_type = $type;
2146
            $this->line->special_code   = (!empty($special_code) ? $special_code : 0);
2147
            $this->line->origin = $origin;
2148
            $this->line->origin_id = $origin_id;
2149
            $this->line->fk_unit = $fk_unit;
2150
2151
            $this->line->date_start = $date_start;
2152
            $this->line->date_end = $date_end;
2153
2154
            // Multicurrency
2155
            $this->line->fk_multicurrency = $this->fk_multicurrency;
2156
            $this->line->multicurrency_code = $this->multicurrency_code;
2157
            $this->line->multicurrency_subprice = $pu_ht_devise;
2158
            $this->line->multicurrency_total_ht     = $multicurrency_total_ht;
2159
            $this->line->multicurrency_total_tva    = $multicurrency_total_tva;
2160
            $this->line->multicurrency_total_ttc    = $multicurrency_total_ttc;
2161
2162
            $this->line->subprice = $pu_ht;
2163
            $this->line->price = $this->line->subprice;
2164
2165
            $this->line->remise_percent = $remise_percent;
2166
2167
            if (is_array($array_options) && count($array_options) > 0) {
2168
                $this->line->array_options = $array_options;
2169
            }
2170
2171
            $result = $this->line->insert($notrigger);
2172
            if ($result > 0) {
2173
                // Reorder if child line
2174
                if (!empty($this->line->fk_parent_line)) {
2175
                    $this->line_order(true, 'DESC');
2176
                } elseif ($rang > 0 && $rang <= count($this->lines)) { // Update all rank of all other lines
2177
                    $linecount = count($this->lines);
2178
                    for ($ii = $rang; $ii <= $linecount; $ii++) {
2179
                        $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2180
                    }
2181
                }
2182
2183
                // Mise a jour information denormalisees au niveau de la commande meme
2184
                $result = $this->update_price(1, 'auto', 0, $this->thirdparty); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
2185
                if ($result > 0) {
2186
                    $this->db->commit();
2187
                    return $this->line->id;
2188
                } else {
2189
                    $this->db->rollback();
2190
                    return -1;
2191
                }
2192
            } else {
2193
                $this->error = $this->line->error;
2194
                $this->errors = $this->line->errors;
2195
                dol_syslog(get_class($this) . "::addline error=" . $this->error, LOG_ERR);
2196
                $this->db->rollback();
2197
                return -1;
2198
            }
2199
        }
2200
        return -1;
2201
    }
2202
2203
2204
    /**
2205
     * Save a receiving into the tracking table of receiving (receptiondet_batch) and add product into stock warehouse.
2206
     *
2207
     * @param   User        $user                   User object making change
2208
     * @param   int         $product                Id of product to dispatch
2209
     * @param   double      $qty                    Qty to dispatch
2210
     * @param   int         $entrepot               Id of warehouse to add product
2211
     * @param   double      $price                  Unit Price for PMP value calculation (Unit price without Tax and taking into account discount)
2212
     * @param   string      $comment                Comment for stock movement
2213
     * @param   int|string  $eatby                  eat-by date
2214
     * @param   int|string  $sellby                 sell-by date
2215
     * @param   string      $batch                  Lot number
2216
     * @param   int         $fk_commandefourndet    Id of supplier order line
2217
     * @param   int         $notrigger              1 = notrigger
2218
     * @param   int         $fk_reception           Id of reception to link
2219
     * @return  int                     Return integer <0 if KO, >0 if OK
2220
     */
2221
    public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0, $fk_reception = 0)
2222
    {
2223
        global $conf, $langs;
2224
2225
        $error = 0;
2226
        require_once constant('DOL_DOCUMENT_ROOT') . '/product/stock/class/mouvementstock.class.php';
2227
2228
        // Check parameters (if test are wrong here, there is bug into caller)
2229
        if ($entrepot <= 0) {
2230
            $this->error = 'ErrorBadValueForParameterWarehouse';
2231
            return -1;
2232
        }
2233
        if ($qty == 0) {
2234
            $this->error = 'ErrorBadValueForParameterQty';
2235
            return -1;
2236
        }
2237
2238
        $dispatchstatus = 1;
2239
        if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2240
            $dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
2241
        }
2242
2243
        $now = dol_now();
2244
2245
        $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
2246
2247
        if (($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY || $this->statut == self::STATUS_RECEIVED_COMPLETELY)) {
2248
            $this->db->begin();
2249
2250
            $sql = "INSERT INTO " . $this->db->prefix() . "receptiondet_batch";
2251
            $sql .= " (fk_element, fk_product, qty, fk_entrepot, fk_user, datec, fk_elementdet, status, comment, eatby, sellby, batch, fk_reception) VALUES";
2252
            $sql .= " ('" . $this->id . "','" . $product . "','" . $qty . "'," . ($entrepot > 0 ? "'" . $entrepot . "'" : "null") . ",'" . $user->id . "','" . $this->db->idate($now) . "','" . $fk_commandefourndet . "', " . $dispatchstatus . ", '" . $this->db->escape($comment) . "', ";
2253
            $sql .= ($eatby ? "'" . $this->db->idate($eatby) . "'" : "null") . ", " . ($sellby ? "'" . $this->db->idate($sellby) . "'" : "null") . ", " . ($batch ? "'" . $this->db->escape($batch) . "'" : "null") . ", " . ($fk_reception > 0 ? "'" . $this->db->escape($fk_reception) . "'" : "null");
2254
            $sql .= ")";
2255
2256
            dol_syslog(get_class($this) . "::dispatchProduct", LOG_DEBUG);
2257
            $resql = $this->db->query($sql);
2258
            if ($resql) {
2259
                if (!$notrigger) {
2260
                    global $conf, $langs, $user;
2261
                    // Call trigger
2262
                    $result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
2263
                    if ($result < 0) {
2264
                        $error++;
2265
                    }
2266
                    // End call triggers
2267
                }
2268
            } else {
2269
                $this->error = $this->db->lasterror();
2270
                $error++;
2271
            }
2272
2273
            // If module stock is enabled and the stock increase is done on purchase order dispatching
2274
            if (!$error && $entrepot > 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
2275
                $mouv = new MouvementStock($this->db);
2276
                if ($product > 0) {
2277
                    // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
2278
                    $mouv->origin = &$this;
0 ignored issues
show
Deprecated Code introduced by
The property 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

2278
                    /** @scrutinizer ignore-deprecated */ $mouv->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...
2279
                    $mouv->setOrigin($this->element, $this->id);
2280
2281
                    // Method change if qty < 0
2282
                    if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN') && $qty < 0) {
2283
                        $result = $mouv->livraison($user, $product, $entrepot, $qty * (-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
2284
                    } else {
2285
                        $result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
2286
                    }
2287
2288
                    if ($result < 0) {
2289
                        $this->error = $mouv->error;
2290
                        $this->errors = $mouv->errors;
2291
                        dol_syslog(get_class($this) . "::dispatchProduct " . $this->error . " " . implode(',', $this->errors), LOG_ERR);
2292
                        $error++;
2293
                    }
2294
                }
2295
            }
2296
2297
            if ($error == 0) {
2298
                $this->db->commit();
2299
                return 1;
2300
            } else {
2301
                $this->db->rollback();
2302
                return -1;
2303
            }
2304
        } else {
2305
            $this->error = 'BadStatusForObject';
2306
            return -2;
2307
        }
2308
    }
2309
2310
    /**
2311
     *  Delete line
2312
     *
2313
     *  @param  int     $idline     Id of line to delete
2314
     *  @param  int     $notrigger  1=Disable call to triggers
2315
     *  @return int                 Return integer <0 if KO, >0 if OK
2316
     */
2317
    public function deleteLine($idline, $notrigger = 0)
2318
    {
2319
        global $user;
2320
2321
        if ($this->statut == 0) {
2322
            $line = new CommandeFournisseurLigne($this->db);
2323
2324
            if ($line->fetch($idline) <= 0) {
2325
                return 0;
2326
            }
2327
2328
            // check if not yet received
2329
            $dispatchedLines = $this->getDispachedLines();
2330
            foreach ($dispatchedLines as $dispatchLine) {
2331
                if ($dispatchLine['orderlineid'] == $idline) {
2332
                    $this->error = "LineAlreadyDispatched";
2333
                    $this->errors[] = $this->error;
2334
                    return -3;
2335
                }
2336
            }
2337
2338
            if ($line->delete($user, $notrigger) > 0) {
2339
                $this->update_price(1);
2340
                return 1;
2341
            } else {
2342
                $this->setErrorsFromObject($line);
2343
                return -1;
2344
            }
2345
        } else {
2346
            return -2;
2347
        }
2348
    }
2349
2350
    /**
2351
     *  Delete an order
2352
     *
2353
     *  @param  User    $user       Object user
2354
     *  @param  int     $notrigger  1=Does not execute triggers, 0= execute triggers
2355
     *  @return int                 Return integer <0 if KO, >0 if OK
2356
     */
2357
    public function delete(User $user, $notrigger = 0)
2358
    {
2359
        global $langs, $conf;
2360
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2361
2362
        $error = 0;
2363
2364
        $this->db->begin();
2365
2366
        if (empty($notrigger)) {
2367
            // Call trigger
2368
            $result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
2369
            if ($result < 0) {
2370
                $this->errors[] = 'ErrorWhenRunningTrigger';
2371
                dol_syslog(get_class($this) . "::delete " . $this->error, LOG_ERR);
2372
                $this->db->rollback();
2373
                return -1;
2374
            }
2375
            // End call triggers
2376
        }
2377
2378
        // Test we can delete
2379
        $this->fetchObjectLinked(null, 'order_supplier');
2380
        if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
2381
            foreach ($this->linkedObjects['reception'] as $element) {
2382
                if ($element->statut >= 0) {
2383
                    $this->errors[] = $langs->trans('ReceptionExist');
2384
                    $error++;
2385
                    break;
2386
                }
2387
            }
2388
        }
2389
2390
        $main = $this->db->prefix() . 'commande_fournisseurdet';
2391
        $ef = $main . "_extrafields";
2392
        $sql = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_commande = " . ((int) $this->id) . ")";
2393
        dol_syslog(get_class($this) . "::delete extrafields lines", LOG_DEBUG);
2394
        if (!$this->db->query($sql)) {
2395
            $this->error = $this->db->lasterror();
2396
            $this->errors[] = $this->db->lasterror();
2397
            $error++;
2398
        }
2399
2400
        $sql = "DELETE FROM " . $this->db->prefix() . "commande_fournisseurdet WHERE fk_commande =" . ((int) $this->id);
2401
        dol_syslog(get_class($this) . "::delete", LOG_DEBUG);
2402
        if (!$this->db->query($sql)) {
2403
            $this->error = $this->db->lasterror();
2404
            $this->errors[] = $this->db->lasterror();
2405
            $error++;
2406
        }
2407
2408
        $sql = "DELETE FROM " . $this->db->prefix() . "commande_fournisseur WHERE rowid =" . ((int) $this->id);
2409
        dol_syslog(get_class($this) . "::delete", LOG_DEBUG);
2410
        if ($resql = $this->db->query($sql)) {
2411
            if ($this->db->affected_rows($resql) < 1) {
2412
                $this->error = $this->db->lasterror();
2413
                $this->errors[] = $this->db->lasterror();
2414
                $error++;
2415
            }
2416
        } else {
2417
            $this->error = $this->db->lasterror();
2418
            $this->errors[] = $this->db->lasterror();
2419
            $error++;
2420
        }
2421
2422
        // Remove extrafields
2423
        if (!$error) {
2424
            $result = $this->deleteExtraFields();
2425
            if ($result < 0) {
2426
                $this->error = 'FailToDeleteExtraFields';
2427
                $this->errors[] = 'FailToDeleteExtraFields';
2428
                $error++;
2429
                dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
2430
            }
2431
        }
2432
2433
        // Delete linked object
2434
        $res = $this->deleteObjectLinked();
2435
        if ($res < 0) {
2436
            $this->error = 'FailToDeleteObjectLinked';
2437
            $this->errors[] = 'FailToDeleteObjectLinked';
2438
            $error++;
2439
        }
2440
2441
        if (!$error) {
2442
            // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2443
            $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2444
            $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2445
2446
            // We remove directory
2447
            $ref = dol_sanitizeFileName($this->ref);
2448
            if ($conf->fournisseur->commande->dir_output) {
2449
                $dir = $conf->fournisseur->commande->dir_output . "/" . $ref;
2450
                $file = $dir . "/" . $ref . ".pdf";
2451
                if (file_exists($file)) {
2452
                    if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2453
                        $this->error = 'ErrorFailToDeleteFile';
2454
                        $this->errors[] = 'ErrorFailToDeleteFile';
2455
                        $error++;
2456
                    }
2457
                }
2458
                if (file_exists($dir)) {
2459
                    $res = @dol_delete_dir_recursive($dir);
2460
                    if (!$res) {
2461
                        $this->error = 'ErrorFailToDeleteDir';
2462
                        $this->errors[] = 'ErrorFailToDeleteDir';
2463
                        $error++;
2464
                    }
2465
                }
2466
            }
2467
        }
2468
2469
        if (!$error) {
2470
            dol_syslog(get_class($this) . "::delete $this->id by $user->id", LOG_DEBUG);
2471
            $this->db->commit();
2472
            return 1;
2473
        } else {
2474
            dol_syslog(get_class($this) . "::delete " . $this->error, LOG_ERR);
2475
            $this->db->rollback();
2476
            return -$error;
2477
        }
2478
    }
2479
2480
2481
    /**
2482
     * Return array of dispatched lines waiting to be approved for this order
2483
     *
2484
     * @since 8.0 Return dispatched quantity (qty).
2485
     *
2486
     * @param   int     $status     Filter on stats (-1 = no filter, 0 = lines draft to be approved, 1 = approved lines)
2487
     * @return  array               Array of lines
2488
     */
2489
    public function getDispachedLines($status = -1)
2490
    {
2491
        $ret = array();
2492
2493
        // List of already dispatched lines
2494
        $sql = "SELECT p.ref, p.label,";
2495
        $sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
2496
        $sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_elementdet";
2497
        $sql .= " FROM " . $this->db->prefix() . "product as p,";
2498
        $sql .= " " . $this->db->prefix() . "receptiondet_batch as cfd";
2499
        $sql .= " LEFT JOIN " . $this->db->prefix() . "entrepot as e ON cfd.fk_entrepot = e.rowid";
2500
        $sql .= " WHERE cfd.fk_element = " . ((int) $this->id);
2501
        $sql .= " AND cfd.fk_product = p.rowid";
2502
        if ($status >= 0) {
2503
            $sql .= " AND cfd.status = " . ((int) $status);
2504
        }
2505
        $sql .= " ORDER BY cfd.rowid ASC";
2506
2507
        $resql = $this->db->query($sql);
2508
        if ($resql) {
2509
            $num = $this->db->num_rows($resql);
2510
            $i = 0;
2511
2512
            while ($i < $num) {
2513
                $objp = $this->db->fetch_object($resql);
2514
                if ($objp) {
2515
                    $ret[] = array(
2516
                        'id' => $objp->dispatchedlineid,
2517
                        'productid' => $objp->fk_product,
2518
                        'warehouseid' => $objp->warehouse_id,
2519
                        'qty' => $objp->qty,
2520
                        'orderlineid' => $objp->fk_elementdet
2521
                    );
2522
                }
2523
2524
                $i++;
2525
            }
2526
        } else {
2527
            dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
2528
        }
2529
2530
        return $ret;
2531
    }
2532
2533
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2534
    /**
2535
     *  Set a delivery in database for this supplier order
2536
     *
2537
     *  @param  User    $user       User that input data
2538
     *  @param  integer $date       Date of reception
2539
     *  @param  string  $type       Type of receipt ('tot' = total/done, 'par' = partial, 'nev' = never, 'can' = cancel)
2540
     *  @param  string  $comment    Comment
2541
     *  @return int                 Return integer <0 if KO, >0 if OK
2542
     */
2543
    public function Livraison($user, $date, $type, $comment)
2544
    {
2545
		// phpcs:enable
2546
        global $conf, $langs;
2547
2548
        $result = 0;
2549
        $error = 0;
2550
2551
        dol_syslog(get_class($this) . "::Livraison");
2552
2553
        $usercanreceive = 0;
2554
        if (!isModEnabled('reception')) {
2555
            $usercanreceive = $user->hasRight("fournisseur", "commande", "receptionner");
2556
        } else {
2557
            $usercanreceive = $user->hasRight("reception", "creer");
2558
        }
2559
2560
        if ($usercanreceive) {
2561
            // Define the new status
2562
            if ($type == 'par') {
2563
                $statut = self::STATUS_RECEIVED_PARTIALLY;
2564
            } elseif ($type == 'tot') {
2565
                $statut = self::STATUS_RECEIVED_COMPLETELY;
2566
            } elseif ($type == 'nev') {
2567
                $statut = self::STATUS_CANCELED_AFTER_ORDER;
2568
            } elseif ($type == 'can') {
2569
                $statut = self::STATUS_CANCELED_AFTER_ORDER;
2570
            } else {
2571
                $error++;
2572
                dol_syslog(get_class($this) . "::Livraison Error -2", LOG_ERR);
2573
                return -2;
2574
            }
2575
2576
            // Some checks to accept the record
2577
            if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2578
                // If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
2579
                if (!$error && ($type == 'tot')) {
2580
                    $dispatchedlinearray = $this->getDispachedLines(0);
2581
                    if (count($dispatchedlinearray) > 0) {
2582
                        $result = -1;
2583
                        $error++;
2584
                        $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
2585
                        dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
2586
                    }
2587
                }
2588
                if (!$error && getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS_NEED_APPROVE') && ($type == 'tot')) { // Accept to move to reception done, only if status of all line are ok (refuse denied)
2589
                    $dispatcheddenied = $this->getDispachedLines(2);
2590
                    if (count($dispatchedlinearray) > 0) {
2591
                        $result = -1;
2592
                        $error++;
2593
                        $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
2594
                        dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
2595
                    }
2596
                }
2597
            }
2598
2599
            // TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
2600
2601
            if (empty($error)) {
2602
                $this->db->begin();
2603
2604
                $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
2605
                $sql .= " SET fk_statut = " . ((int) $statut);
2606
                $sql .= " WHERE rowid = " . ((int) $this->id);
2607
                $sql .= " AND fk_statut IN (" . self::STATUS_ORDERSENT . "," . self::STATUS_RECEIVED_PARTIALLY . ")"; // Process running or Partially received
2608
2609
                dol_syslog(get_class($this) . "::Livraison", LOG_DEBUG);
2610
                $resql = $this->db->query($sql);
2611
                if ($resql) {
2612
                    $result = 1;
2613
                    $old_statut = $this->statut;
2614
                    $this->statut = $statut;
2615
                    $this->context['actionmsg2'] = $comment;
2616
2617
                    // Call trigger
2618
                    $result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
2619
                    if ($result_trigger < 0) {
2620
                        $error++;
2621
                    }
2622
                    // End call triggers
2623
2624
                    if (empty($error)) {
2625
                        $this->db->commit();
2626
                    } else {
2627
                        $this->statut = $old_statut;
2628
                        $this->db->rollback();
2629
                        $this->error = $this->db->lasterror();
2630
                        $result = -1;
2631
                    }
2632
                } else {
2633
                    $this->db->rollback();
2634
                    $this->error = $this->db->lasterror();
2635
                    $result = -1;
2636
                }
2637
            }
2638
        } else {
2639
            $this->error = $langs->trans('NotAuthorized');
2640
            $this->errors[] = $langs->trans('NotAuthorized');
2641
            dol_syslog(get_class($this) . "::Livraison Not Authorized");
2642
            $result = -3;
2643
        }
2644
        return $result;
2645
    }
2646
2647
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2648
    /**
2649
     *  Set delivery date
2650
     *
2651
     *  @param      User    $user               Object user that modify
2652
     *  @param      int     $delivery_date      Delivery date
2653
     *  @param      int     $notrigger          1=Does not execute triggers, 0= execute triggers
2654
     *  @return     int                         Return integer <0 if ko, >0 if ok
2655
     *  @deprecated Use  setDeliveryDate
2656
     */
2657
    public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2658
    {
2659
		// phpcs:enable
2660
        return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2661
    }
2662
2663
    /**
2664
     *  Set the planned delivery date
2665
     *
2666
     *  @param      User            $user               Object user making change
2667
     *  @param      integer         $delivery_date     Planned delivery date
2668
     *  @param      int             $notrigger          1=Does not execute triggers, 0= execute triggers
2669
     *  @return     int                                 Return integer <0 if KO, >0 if OK
2670
     */
2671
    public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2672
    {
2673
        if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2674
            $error = 0;
2675
2676
            $this->db->begin();
2677
2678
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
2679
            $sql .= " SET date_livraison = " . ($delivery_date ? "'" . $this->db->idate($delivery_date) . "'" : 'null');
2680
            $sql .= " WHERE rowid = " . ((int) $this->id);
2681
2682
            dol_syslog(__METHOD__, LOG_DEBUG);
2683
            $resql = $this->db->query($sql);
2684
            if (!$resql) {
2685
                $this->errors[] = $this->db->error();
2686
                $error++;
2687
            }
2688
2689
            if (!$error) {
2690
                $this->oldcopy = clone $this;
2691
                $this->delivery_date = $delivery_date;
2692
            }
2693
2694
            if (!$notrigger && empty($error)) {
2695
                // Call trigger
2696
                $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2697
                if ($result < 0) {
2698
                    $error++;
2699
                }
2700
                // End call triggers
2701
            }
2702
2703
            if (!$error) {
2704
                $this->db->commit();
2705
                return 1;
2706
            } else {
2707
                foreach ($this->errors as $errmsg) {
2708
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2709
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2710
                }
2711
                $this->db->rollback();
2712
                return -1 * $error;
2713
            }
2714
        } else {
2715
            return -2;
2716
        }
2717
    }
2718
2719
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2720
    /**
2721
     *  Set the id projet
2722
     *
2723
     *  @param      User            $user               Object utilisateur qui modifie
2724
     *  @param      int             $id_projet          Delivery date
2725
     *  @param      int             $notrigger          1=Does not execute triggers, 0= execute triggers
2726
     *  @return     int                                 Return integer <0 si ko, >0 si ok
2727
     */
2728
    public function set_id_projet($user, $id_projet, $notrigger = 0)
2729
    {
2730
		// phpcs:enable
2731
        if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2732
            $error = 0;
2733
2734
            $this->db->begin();
2735
2736
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
2737
            $sql .= " SET fk_projet = " . ($id_projet > 0 ? (int) $id_projet : 'null');
2738
            $sql .= " WHERE rowid = " . ((int) $this->id);
2739
2740
            dol_syslog(__METHOD__, LOG_DEBUG);
2741
            $resql = $this->db->query($sql);
2742
            if (!$resql) {
2743
                $this->errors[] = $this->db->error();
2744
                $error++;
2745
            }
2746
2747
            if (!$error) {
2748
                $this->oldcopy = clone $this;
2749
                $this->fk_projet = $id_projet;
2750
                $this->fk_project = $id_projet;
2751
            }
2752
2753
            if (!$notrigger && empty($error)) {
2754
                // Call trigger
2755
                $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2756
                if ($result < 0) {
2757
                    $error++;
2758
                }
2759
                // End call triggers
2760
            }
2761
2762
            if (!$error) {
2763
                $this->db->commit();
2764
                return 1;
2765
            } else {
2766
                foreach ($this->errors as $errmsg) {
2767
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2768
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2769
                }
2770
                $this->db->rollback();
2771
                return -1 * $error;
2772
            }
2773
        } else {
2774
            return -2;
2775
        }
2776
    }
2777
2778
    /**
2779
     *  Update a supplier order from a sales order
2780
     *
2781
     *  @param  User    $user           User that create
2782
     *  @param  int     $idc            Id of purchase order to update
2783
     *  @param  int     $comclientid    Id of sale order to use as template
2784
     *  @return int                     Return integer <0 if KO, >0 if OK
2785
     */
2786
    public function updateFromCommandeClient($user, $idc, $comclientid)
2787
    {
2788
        $comclient = new Commande($this->db);
2789
        $comclient->fetch($comclientid);
2790
2791
        $this->id = $idc;
2792
2793
        $this->lines = array();
2794
2795
        $num = count($comclient->lines);
2796
        for ($i = 0; $i < $num; $i++) {
2797
            $prod = new Product($this->db);
2798
            $label = '';
2799
            $ref = '';
2800
            if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
2801
                $label  = $prod->label;
2802
                $ref    = $prod->ref;
2803
            }
2804
2805
            $sql = "INSERT INTO " . $this->db->prefix() . "commande_fournisseurdet";
2806
            $sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
2807
            $sql .= " VALUES (" . ((int) $idc) . ", '" . $this->db->escape($label) . "', '" . $this->db->escape($comclient->lines[$i]->desc) . "'";
2808
            $sql .= "," . $comclient->lines[$i]->fk_product . ", " . price2num($comclient->lines[$i]->price, 'MU');
2809
            $sql .= ", " . price2num($comclient->lines[$i]->qty, 'MS') . ", " . price2num($comclient->lines[$i]->tva_tx, 5) . ", " . price2num($comclient->lines[$i]->localtax1_tx, 5) . ", " . price2num($comclient->lines[$i]->localtax2_tx, 5) . ", " . price2num($comclient->lines[$i]->remise_percent, 3);
2810
            $sql .= ", '" . price2num($comclient->lines[$i]->subprice, 'MT') . "','0', '" . $this->db->escape($ref) . "');";
2811
            if ($this->db->query($sql)) {
2812
                $this->update_price(1);
2813
            }
2814
        }
2815
2816
        return 1;
2817
    }
2818
2819
    /**
2820
     *  Tag order with a particular status
2821
     *
2822
     *  @param      User    $user       Object user that change status
2823
     *  @param      int     $status     New status
2824
     *  @return     int                 Return integer <0 if KO, >0 if OK
2825
     */
2826
    public function setStatus($user, $status)
2827
    {
2828
        global $conf, $langs;
2829
        $error = 0;
2830
2831
        $this->db->begin();
2832
2833
        $sql = 'UPDATE ' . $this->db->prefix() . 'commande_fournisseur';
2834
        $sql .= " SET fk_statut = " . $status;
2835
        $sql .= " WHERE rowid = " . ((int) $this->id);
2836
2837
        dol_syslog(get_class($this) . "::setStatus", LOG_DEBUG);
2838
        $resql = $this->db->query($sql);
2839
        if ($resql) {
2840
            // Trigger names for each status
2841
            $triggerName = array();
2842
            $triggerName[0] = 'DRAFT';
2843
            $triggerName[1] = 'VALIDATED';
2844
            $triggerName[2] = 'APPROVED';
2845
            $triggerName[3] = 'ORDERED'; // Ordered
2846
            $triggerName[4] = 'RECEIVED_PARTIALLY';
2847
            $triggerName[5] = 'RECEIVED_COMPLETELY';
2848
            $triggerName[6] = 'CANCELED';
2849
            $triggerName[7] = 'CANCELED';
2850
            $triggerName[9] = 'REFUSED';
2851
2852
            // Call trigger
2853
            $result = $this->call_trigger("ORDER_SUPPLIER_STATUS_" . $triggerName[$status], $user);
2854
            if ($result < 0) {
2855
                $error++;
2856
            }
2857
            // End call triggers
2858
        } else {
2859
            $error++;
2860
            $this->error = $this->db->lasterror();
2861
            dol_syslog(get_class($this) . "::setStatus " . $this->error);
2862
        }
2863
2864
        if (!$error) {
2865
            $this->statut = $status;
2866
            $this->db->commit();
2867
            return 1;
2868
        } else {
2869
            $this->db->rollback();
2870
            return -1;
2871
        }
2872
    }
2873
2874
    /**
2875
     *  Update line
2876
     *
2877
     *  @param      int         $rowid              ID de la ligne de facture
2878
     *  @param      string      $desc               Line description
2879
     *  @param      int|float   $pu                 Unit price
2880
     *  @param      int|float   $qty                Quantity
2881
     *  @param      int|float   $remise_percent     Percent discount on line
2882
     *  @param      int|float   $txtva              VAT rate
2883
     *  @param      int|float   $txlocaltax1        Localtax1 tax
2884
     *  @param      int|float   $txlocaltax2        Localtax2 tax
2885
     *  @param      string      $price_base_type    Type of price base
2886
     *  @param      int         $info_bits          Miscellaneous information
2887
     *  @param      int         $type               Type of line (0=product, 1=service)
2888
     *  @param      int         $notrigger          Disable triggers
2889
     *  @param      integer     $date_start         Date start of service
2890
     *  @param      integer     $date_end           Date end of service
2891
     *  @param      array       $array_options      Extrafields array
2892
     *  @param      int|null    $fk_unit            Code of the unit to use. Null to use the default one
2893
     *  @param      int|float   $pu_ht_devise       Unit price in currency
2894
     *  @param      string      $ref_supplier       Supplier ref
2895
     *  @return     int                             Return integer < 0 if error, > 0 if ok
2896
     */
2897
    public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $price_base_type = 'HT', $info_bits = 0, $type = 0, $notrigger = 0, $date_start = 0, $date_end = 0, $array_options = [], $fk_unit = null, $pu_ht_devise = 0, $ref_supplier = '')
2898
    {
2899
        global $mysoc, $conf, $langs;
2900
        dol_syslog(get_class($this) . "::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
2901
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
2902
2903
        $error = 0;
2904
2905
        if ($this->statut == self::STATUS_DRAFT) {
2906
            // Clean parameters
2907
            if (empty($qty)) {
2908
                $qty = 0;
2909
            }
2910
            if (empty($info_bits)) {
2911
                $info_bits = 0;
2912
            }
2913
            if (empty($txtva)) {
2914
                $txtva = 0;
2915
            }
2916
            if (empty($txlocaltax1)) {
2917
                $txlocaltax1 = 0;
2918
            }
2919
            if (empty($txlocaltax2)) {
2920
                $txlocaltax2 = 0;
2921
            }
2922
            if (empty($remise_percent)) {
2923
                $remise_percent = 0;
2924
            }
2925
2926
            $remise_percent = (float) price2num($remise_percent);
2927
            $qty = price2num($qty);
2928
            if (!$qty) {
2929
                $qty = 1;
2930
            }
2931
            $pu = price2num($pu);
2932
            $pu_ht_devise = price2num($pu_ht_devise);
2933
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
2934
                $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
2935
            }
2936
            $txlocaltax1 = (float) price2num($txlocaltax1);
2937
            $txlocaltax2 = (float) price2num($txlocaltax2);
2938
2939
            // Check parameters
2940
            if ($type < 0) {
2941
                return -1;
2942
            }
2943
            if ($date_start && $date_end && $date_start > $date_end) {
2944
                $langs->load("errors");
2945
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2946
                return -1;
2947
            }
2948
2949
            $this->db->begin();
2950
2951
            // Calcul du total TTC et de la TVA pour la ligne a partir de
2952
            // qty, pu, remise_percent et txtva
2953
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2954
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2955
2956
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2957
2958
            // Clean vat code
2959
            $reg = array();
2960
            $vat_src_code = '';
2961
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
2962
                $vat_src_code = $reg[1];
2963
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
2964
            }
2965
2966
            $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
2967
            $total_ht  = $tabprice[0];
2968
            $total_tva = $tabprice[1];
2969
            $total_ttc = $tabprice[2];
2970
            $total_localtax1 = $tabprice[9];
2971
            $total_localtax2 = $tabprice[10];
2972
            $pu_ht  = $tabprice[3];
2973
            $pu_tva = $tabprice[4];
2974
            $pu_ttc = $tabprice[5];
2975
2976
            // MultiCurrency
2977
            $multicurrency_total_ht = $tabprice[16];
2978
            $multicurrency_total_tva = $tabprice[17];
2979
            $multicurrency_total_ttc = $tabprice[18];
2980
            $pu_ht_devise = $tabprice[19];
2981
2982
            $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2983
            $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2984
2985
            //Fetch current line from the database and then clone the object and set it in $oldline property
2986
            $this->line = new CommandeFournisseurLigne($this->db);
2987
            $this->line->fetch($rowid);
2988
2989
            $oldline = clone $this->line;
2990
            $this->line->oldline = $oldline;
2991
2992
            $this->line->context = $this->context;
2993
2994
            $this->line->fk_commande = $this->id;
2995
            //$this->line->label=$label;
2996
            $this->line->desc = $desc;
2997
2998
            // redefine quantity according to packaging
2999
            if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
3000
                if ($qty < $this->line->packaging) {
3001
                    $qty = $this->line->packaging;
3002
                } else {
3003
                    if (!empty($this->line->packaging) && ($qty % $this->line->packaging) > 0) {
3004
                        $coeff = intval($qty / $this->line->packaging) + 1;
3005
                        $qty = $this->line->packaging * $coeff;
3006
                        setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs');
3007
                    }
3008
                }
3009
            }
3010
3011
            $this->line->qty = $qty;
3012
            $this->line->ref_supplier = $ref_supplier;
3013
3014
            $this->line->vat_src_code = $vat_src_code;
3015
            $this->line->tva_tx         = $txtva;
3016
            $this->line->localtax1_tx   = $txlocaltax1;
3017
            $this->line->localtax2_tx   = $txlocaltax2;
3018
            $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3019
            $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3020
            $this->line->remise_percent = $remise_percent;
3021
            $this->line->subprice       = $pu_ht;
3022
            $this->line->info_bits      = $info_bits;
3023
            $this->line->total_ht       = $total_ht;
3024
            $this->line->total_tva      = $total_tva;
3025
            $this->line->total_localtax1 = $total_localtax1;
3026
            $this->line->total_localtax2 = $total_localtax2;
3027
            $this->line->total_ttc      = $total_ttc;
3028
            $this->line->product_type   = $type;
3029
            $this->line->special_code   = $oldline->special_code;
3030
            $this->line->rang           = $oldline->rang;
3031
            $this->line->origin         = $this->origin;
3032
            $this->line->fk_unit        = $fk_unit;
3033
3034
            $this->line->date_start     = $date_start;
3035
            $this->line->date_end       = $date_end;
3036
3037
            // Multicurrency
3038
            $this->line->fk_multicurrency = $this->fk_multicurrency;
3039
            $this->line->multicurrency_code = $this->multicurrency_code;
3040
            $this->line->multicurrency_subprice     = $pu_ht_devise;
3041
            $this->line->multicurrency_total_ht     = $multicurrency_total_ht;
3042
            $this->line->multicurrency_total_tva    = $multicurrency_total_tva;
3043
            $this->line->multicurrency_total_ttc    = $multicurrency_total_ttc;
3044
3045
            $this->line->subprice = $pu_ht;
3046
            $this->line->price = $this->line->subprice;
3047
3048
            $this->line->remise_percent = $remise_percent;
3049
3050
            if (is_array($array_options) && count($array_options) > 0) {
3051
                // We replace values in this->line->array_options only for entries defined into $array_options
3052
                foreach ($array_options as $key => $value) {
3053
                    $this->line->array_options[$key] = $array_options[$key];
3054
                }
3055
            }
3056
3057
            $result = $this->line->update($notrigger);
3058
3059
3060
            // Mise a jour info denormalisees au niveau facture
3061
            if ($result >= 0) {
3062
                $this->update_price('1', 'auto');
3063
                $this->db->commit();
3064
                return $result;
3065
            } else {
3066
                $this->error = $this->db->lasterror();
3067
                $this->db->rollback();
3068
                return -1;
3069
            }
3070
        } else {
3071
            $this->error = "Order status makes operation forbidden";
3072
            dol_syslog(get_class($this) . "::updateline " . $this->error, LOG_ERR);
3073
            return -2;
3074
        }
3075
    }
3076
3077
3078
    /**
3079
     *  Initialise an instance with random values.
3080
     *  Used to build previews or test instances.
3081
     *  id must be 0 if object instance is a specimen.
3082
     *
3083
     *  @return int
3084
     */
3085
    public function initAsSpecimen()
3086
    {
3087
        global $user, $langs, $conf;
3088
3089
        include_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
3090
3091
        dol_syslog(get_class($this) . "::initAsSpecimen");
3092
3093
        $now = dol_now();
3094
3095
        // Find first product
3096
        $prodid = 0;
3097
        $product = new ProductFournisseur($this->db);
3098
        $sql = "SELECT rowid";
3099
        $sql .= " FROM " . $this->db->prefix() . "product";
3100
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
3101
        $sql .= $this->db->order("rowid", "ASC");
3102
        $sql .= $this->db->plimit(1);
3103
        $resql = $this->db->query($sql);
3104
        if ($resql) {
3105
            $obj = $this->db->fetch_object($resql);
3106
            $prodid = $obj->rowid;
3107
        }
3108
3109
        // Initialise parameters
3110
        $this->id = 0;
3111
        $this->ref = 'SPECIMEN';
3112
        $this->specimen = 1;
3113
        $this->socid = 1;
3114
        $this->date = $now;
3115
        $this->date_commande = $now;
3116
        $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3117
        $this->cond_reglement_code = 'RECEP';
3118
        $this->mode_reglement_code = 'CHQ';
3119
3120
        $this->note_public = 'This is a comment (public)';
3121
        $this->note_private = 'This is a comment (private)';
3122
3123
        $this->multicurrency_tx = 1;
3124
        $this->multicurrency_code = $conf->currency;
3125
3126
        $this->statut = 0;
3127
3128
        // Lines
3129
        $nbp = 5;
3130
        $xnbp = 0;
3131
        while ($xnbp < $nbp) {
3132
            $line = new CommandeFournisseurLigne($this->db);
3133
            $line->desc = $langs->trans("Description") . " " . $xnbp;
3134
            $line->qty = 1;
3135
            $line->subprice = 100;
3136
            $line->tva_tx = 19.6;
3137
            $line->localtax1_tx = 0;
3138
            $line->localtax2_tx = 0;
3139
            if ($xnbp == 2) {
3140
                $line->total_ht = 50;
3141
                $line->total_ttc = 59.8;
3142
                $line->total_tva = 9.8;
3143
                $line->remise_percent = 50;
3144
            } else {
3145
                $line->total_ht = 100;
3146
                $line->total_ttc = 119.6;
3147
                $line->total_tva = 19.6;
3148
                $line->remise_percent = 0;
3149
            }
3150
            $line->fk_product = $prodid;
3151
3152
            $this->lines[$xnbp] = $line;
3153
3154
            $this->total_ht       += $line->total_ht;
3155
            $this->total_tva      += $line->total_tva;
3156
            $this->total_ttc      += $line->total_ttc;
3157
3158
            $xnbp++;
3159
        }
3160
3161
        return 1;
3162
    }
3163
3164
    /**
3165
     *  Charge les information d'ordre info dans l'objet facture
3166
     *
3167
     *  @param  int     $id         Id de la facture a charger
3168
     *  @return void
3169
     */
3170
    public function info($id)
3171
    {
3172
        $sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
3173
        $sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
3174
        $sql .= ' FROM ' . $this->db->prefix() . 'commande_fournisseur as c';
3175
        $sql .= ' WHERE c.rowid = ' . ((int) $id);
3176
3177
        $result = $this->db->query($sql);
3178
        if ($result) {
3179
            if ($this->db->num_rows($result)) {
3180
                $obj = $this->db->fetch_object($result);
3181
3182
                $this->id = $obj->rowid;
3183
3184
                $this->user_creation_id = $obj->fk_user_author;
3185
                $this->user_validation_id = $obj->fk_user_valid;
3186
                $this->user_modification_id = $obj->fk_user_modif;
3187
                $this->user_approve_id = $obj->fk_user_approve;
3188
                $this->user_approve_id2 = $obj->fk_user_approve2;
3189
3190
                $this->date_creation     = $this->db->jdate($obj->datec);
3191
                $this->date_modification = $this->db->jdate($obj->datem);
3192
                $this->date_approve      = $this->db->jdate($obj->datea);
3193
                $this->date_approve2     = $this->db->jdate($obj->datea2);
3194
                $this->date_validation   = $this->db->jdate($obj->date_validation);
3195
            }
3196
            $this->db->free($result);
3197
        } else {
3198
            dol_print_error($this->db);
3199
        }
3200
    }
3201
3202
    /**
3203
     *  Load the indicators this->nb for the state board
3204
     *
3205
     *  @return     int         Return integer <0 si ko, >0 si ok
3206
     */
3207
    public function loadStateBoard()
3208
    {
3209
        global $conf, $user;
3210
3211
        $this->nb = array();
3212
        $clause = "WHERE";
3213
3214
        $sql = "SELECT count(co.rowid) as nb";
3215
        $sql .= " FROM " . $this->db->prefix() . "commande_fournisseur as co";
3216
        $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON co.fk_soc = s.rowid";
3217
        if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
3218
            $sql .= " LEFT JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3219
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
3220
            $clause = "AND";
3221
        }
3222
        $sql .= " " . $clause . " co.entity IN (" . getEntity('supplier_order') . ")";
3223
3224
        $resql = $this->db->query($sql);
3225
        if ($resql) {
3226
            while ($obj = $this->db->fetch_object($resql)) {
3227
                $this->nb["supplier_orders"] = $obj->nb;
3228
            }
3229
            $this->db->free($resql);
3230
            return 1;
3231
        } else {
3232
            dol_print_error($this->db);
3233
            $this->error = $this->db->error();
3234
            return -1;
3235
        }
3236
    }
3237
3238
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3239
    /**
3240
     *  Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3241
     *
3242
     *  @param  User    $user   Object user
3243
     *  @param  string  $mode   "opened", "awaiting" for orders awaiting reception
3244
     *  @return WorkboardResponse|int   Return integer <0 if KO, WorkboardResponse if OK
3245
     */
3246
    public function load_board($user, $mode = 'opened')
3247
    {
3248
		// phpcs:enable
3249
        global $conf, $langs;
3250
3251
        $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
3252
        $sql .= " FROM " . $this->db->prefix() . "commande_fournisseur as c";
3253
        if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
3254
            $sql .= " JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
3255
        }
3256
        $sql .= " WHERE c.entity = " . $conf->entity;
3257
        if ($mode === 'awaiting') {
3258
            $sql .= " AND c.fk_statut IN (" . self::STATUS_ORDERSENT . ", " . self::STATUS_RECEIVED_PARTIALLY . ")";
3259
        } else {
3260
            $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . ", " . self::STATUS_ACCEPTED . ")";
3261
        }
3262
        if ($user->socid) {
3263
            $sql .= " AND c.fk_soc = " . ((int) $user->socid);
3264
        }
3265
3266
        $resql = $this->db->query($sql);
3267
        if ($resql) {
3268
            $commandestatic = new CommandeFournisseur($this->db);
3269
3270
            $response = new WorkboardResponse();
3271
            $response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
3272
            $response->label = $langs->trans("SuppliersOrdersToProcess");
3273
            $response->labelShort = $langs->trans("Opened");
3274
            $response->url = constant('BASE_URL') . '/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
3275
            $response->img = img_object('', "order");
3276
3277
            if ($mode === 'awaiting') {
3278
                $response->label = $langs->trans("SuppliersOrdersAwaitingReception");
3279
                $response->labelShort = $langs->trans("AwaitingReception");
3280
                $response->url = constant('BASE_URL') . '/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
3281
            }
3282
3283
            while ($obj = $this->db->fetch_object($resql)) {
3284
                $commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
3285
                $commandestatic->date_commande = $this->db->jdate($obj->date_commande);
3286
                $commandestatic->statut = $obj->fk_statut;
3287
3288
                $response->nbtodo++;
3289
                $response->total += $obj->total_ht;
3290
3291
                if ($commandestatic->hasDelay()) {
3292
                    $response->nbtodolate++;
3293
                }
3294
            }
3295
3296
            return $response;
3297
        } else {
3298
            $this->error = $this->db->error();
3299
            return -1;
3300
        }
3301
    }
3302
3303
    /**
3304
     * Returns the translated input method of object (defined if $this->methode_commande_id > 0).
3305
     * This function make a sql request to get translation. No cache yet, try to not use it inside a loop.
3306
     *
3307
     * @return string
3308
     */
3309
    public function getInputMethod()
3310
    {
3311
        global $langs;
3312
3313
        if ($this->methode_commande_id > 0) {
3314
            $sql = "SELECT rowid, code, libelle as label";
3315
            $sql .= " FROM " . $this->db->prefix() . 'c_input_method';
3316
            $sql .= " WHERE active=1 AND rowid = " . ((int) $this->methode_commande_id);
3317
3318
            $resql = $this->db->query($sql);
3319
            if ($resql) {
3320
                if ($this->db->num_rows($resql)) {
3321
                    $obj = $this->db->fetch_object($resql);
3322
3323
                    $string = $langs->trans($obj->code);
3324
                    if ($string == $obj->code) {
3325
                        $string = $obj->label != '-' ? $obj->label : '';
3326
                    }
3327
                    return $string;
3328
                }
3329
            } else {
3330
                dol_print_error($this->db);
3331
            }
3332
        }
3333
3334
        return '';
3335
    }
3336
3337
    /**
3338
     *  Create a document onto disk according to template model.
3339
     *
3340
     *  @param      string      $modele         Force template to use ('' to not force)
3341
     *  @param      Translate   $outputlangs    Object lang to use for traduction
3342
     *  @param      int         $hidedetails    Hide details of lines
3343
     *  @param      int         $hidedesc       Hide description
3344
     *  @param      int         $hideref        Hide ref
3345
     *  @param      null|array  $moreparams     Array to provide more information
3346
     *  @return     int                         Return integer < 0 if KO, 0 = no doc generated, > 0 if OK
3347
     */
3348
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3349
    {
3350
        global $conf, $langs;
3351
3352
        if (!dol_strlen($modele)) {
3353
            $modele = '';   // No doc template/generation by default
3354
3355
            if (!empty($this->model_pdf)) {
3356
                $modele = $this->model_pdf;
3357
            } elseif (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) {
3358
                $modele = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF');
3359
            }
3360
        }
3361
3362
        if (empty($modele)) {
3363
            return 0;
3364
        } else {
3365
            $langs->load("suppliers");
3366
            $outputlangs->load("products");
3367
3368
            $modelpath = "core/modules/supplier_order/doc/";
3369
            $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3370
            return $result;
3371
        }
3372
    }
3373
3374
    /**
3375
     * Return the max number delivery delay in day
3376
     *
3377
     * @param   Translate   $langs      Language object
3378
     * @return  string                  Translated string
3379
     */
3380
    public function getMaxDeliveryTimeDay($langs)
3381
    {
3382
        if (empty($this->lines)) {
3383
            return '';
3384
        }
3385
3386
        $obj = new ProductFournisseur($this->db);
3387
3388
        $nb = 0;
3389
        foreach ($this->lines as $line) {
3390
            if ($line->fk_product > 0) {
3391
                $idp = $obj->find_min_price_product_fournisseur($line->fk_product, $line->qty);
3392
                if ($idp) {
3393
                    $obj->fetch($idp);
3394
                    if ($obj->delivery_time_days > $nb) {
3395
                        $nb = $obj->delivery_time_days;
3396
                    }
3397
                }
3398
            }
3399
        }
3400
3401
        if ($nb === 0) {
3402
            return '';
3403
        } else {
3404
            return $nb . ' ' . $langs->trans('Days');
3405
        }
3406
    }
3407
3408
    /**
3409
     * Returns the rights used for this class
3410
     * @return int
3411
     */
3412
    public function getRights()
3413
    {
3414
        global $user;
3415
3416
        return $user->hasRight("fournisseur", "commande");
3417
    }
3418
3419
3420
    /**
3421
     * Function used to replace a thirdparty id with another one.
3422
     *
3423
     * @param   DoliDB  $dbs        Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
3424
     * @param   int     $origin_id  Old thirdparty id
3425
     * @param   int     $dest_id    New thirdparty id
3426
     * @return  bool
3427
     */
3428
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3429
    {
3430
        $tables = array(
3431
            'commande_fournisseur'
3432
        );
3433
3434
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3435
    }
3436
3437
    /**
3438
     * Function used to replace a product id with another one.
3439
     *
3440
     * @param DoliDB    $dbs        Database handler
3441
     * @param int       $origin_id  Old product id
3442
     * @param int       $dest_id    New product id
3443
     * @return bool
3444
     */
3445
    public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
3446
    {
3447
        $tables = array(
3448
            'commande_fournisseurdet'
3449
        );
3450
3451
        return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
3452
    }
3453
3454
    /**
3455
     * Is the supplier order delayed?
3456
     * We suppose a purchase ordered as late if a the purchase order has been sent and the delivery date is set and before the delay.
3457
     * If order has not been sent, we use the order date.
3458
     *
3459
     * @return  bool                    True if object is delayed
3460
     */
3461
    public function hasDelay()
3462
    {
3463
        global $conf;
3464
3465
        if ($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY) {
3466
            $now = dol_now();
3467
            if (!empty($this->delivery_date)) {
3468
                $date_to_test = $this->delivery_date;
3469
                return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3470
            } else {
3471
                //$date_to_test = $this->date_commande;
3472
                //return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3473
                return false;
3474
            }
3475
        } else {
3476
            $now = dol_now();
3477
            $date_to_test = $this->date_commande;
3478
3479
            return ($this->statut > 0 && $this->statut < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3480
        }
3481
    }
3482
3483
    /**
3484
     * Show the customer delayed info.
3485
     * We suppose a purchase ordered as late if a the purchase order has been sent and the delivery date is set and before the delay.
3486
     * If order has not been sent, we use the order date.
3487
     *
3488
     * @return string       Show delayed information
3489
     */
3490
    public function showDelay()
3491
    {
3492
        global $conf, $langs;
3493
3494
        $langs->load('orders');
3495
3496
        $text = '';
3497
3498
        if ($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY) {
3499
            if (!empty($this->delivery_date)) {
3500
                $text = $langs->trans("DeliveryDate") . ' ' . dol_print_date($this->delivery_date, 'day');
3501
            } else {
3502
                $text = $langs->trans("OrderDate") . ' ' . dol_print_date($this->date_commande, 'day');
3503
            }
3504
        } else {
3505
            $text = $langs->trans("OrderDate") . ' ' . dol_print_date($this->date_commande, 'day');
3506
        }
3507
        if ($text) {
3508
            $text .= ' ' . ($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-') . ' ' . round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1) . ' ' . $langs->trans("days") . ' < ' . $langs->trans("Today");
3509
        }
3510
3511
        return $text;
3512
    }
3513
3514
3515
    /**
3516
     * Calc status regarding to dispatched stock
3517
     *
3518
     * @param       User    $user                   User action
3519
     * @param       int     $closeopenorder         Close if received
3520
     * @param       string  $comment                Comment
3521
     * @return      int                             Return integer <0 if KO, 0 if not applicable, >0 if OK
3522
     */
3523
    public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
3524
    {
3525
        if (isModEnabled("supplier_order")) {
3526
            require_once constant('DOL_DOCUMENT_ROOT') . '/fourn/class/fournisseur.commande.dispatch.class.php';
3527
3528
            $qtydelivered = array();
3529
            $qtywished = array();
3530
3531
            $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
3532
3533
            $filter = array('t.fk_element' => $this->id);
3534
            if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
3535
                $filter['t.status'] = 1; // Restrict to lines with status validated
3536
            }
3537
3538
            $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
3539
            if ($ret < 0) {
3540
                $this->error = $supplierorderdispatch->error;
3541
                $this->errors = $supplierorderdispatch->errors;
3542
                return $ret;
3543
            } else {
3544
                if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
3545
                    require_once constant('DOL_DOCUMENT_ROOT') . '/product/class/product.class.php';
3546
                    $date_liv = dol_now();
3547
3548
                    // Build array with quantity deliverd by product
3549
                    foreach ($supplierorderdispatch->lines as $line) {
3550
                        $qtydelivered[$line->fk_product] += $line->qty;
3551
                    }
3552
                    foreach ($this->lines as $line) {
3553
                        // Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
3554
                        if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') && $line->product_type > 0) {
3555
                            continue;
3556
                        }
3557
                        $qtywished[$line->fk_product] += $line->qty;
3558
                    }
3559
3560
                    //Compare array
3561
                    $diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
3562
                    $keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
3563
                    $keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
3564
                    //var_dump(array_keys($qtydelivered));
3565
                    //var_dump(array_keys($qtywished));
3566
                    //var_dump($diff_array);
3567
                    //var_dump($keysinwishednotindelivered);
3568
                    //var_dump($keysindeliverednotinwished);
3569
                    //exit;
3570
3571
                    if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everything is received
3572
                        if ($closeopenorder) {
3573
                            //$ret=$this->setStatus($user,5);
3574
                            $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3575
                            if ($ret < 0) {
3576
                                return -1;
3577
                            }
3578
                            return 5;
3579
                        } else {
3580
                            //Diff => received partially
3581
                            //$ret=$this->setStatus($user,4);
3582
                            $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3583
                            if ($ret < 0) {
3584
                                return -1;
3585
                            }
3586
                            return 4;
3587
                        }
3588
                    } elseif (getDolGlobalString('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
3589
                        //set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
3590
3591
                        $close = 0;
3592
3593
                        if (count($diff_array) > 0) {
3594
                            //there are some difference between  the two arrays
3595
3596
                            //scan the array of results
3597
                            foreach ($diff_array as $key => $value) {
3598
                                //if the quantity delivered is greater or equal to wish quantity
3599
                                if ($qtydelivered[$key] >= $qtywished[$key]) {
3600
                                    $close++;
3601
                                }
3602
                            }
3603
                        }
3604
3605
3606
                        if ($close == count($diff_array)) {
3607
                            //all the products are received equal or more than the wished quantity
3608
                            if ($closeopenorder) {
3609
                                $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3610
                                if ($ret < 0) {
3611
                                    return -1;
3612
                                }
3613
                                return 5;
3614
                            } else {
3615
                                //Diff => received partially
3616
                                $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3617
                                if ($ret < 0) {
3618
                                    return -1;
3619
                                }
3620
                                return 4;
3621
                            }
3622
                        } else {
3623
                            //all the products are not received
3624
                            $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3625
                            if ($ret < 0) {
3626
                                return -1;
3627
                            }
3628
                            return 4;
3629
                        }
3630
                    } else {
3631
                        //Diff => received partially
3632
                        $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3633
                        if ($ret < 0) {
3634
                            return -1;
3635
                        }
3636
                        return 4;
3637
                    }
3638
                }
3639
                return 1;
3640
            }
3641
        }
3642
        return 0;
3643
    }
3644
3645
    /**
3646
     *  Load array this->receptions of lines of shipments with nb of products sent for each order line
3647
     *  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
3648
     *
3649
     *  @param      int     $filtre_statut      Filter on shipment status
3650
     *  @return     int                         Return integer <0 if KO, Nb of lines found if OK
3651
     */
3652
    public function loadReceptions($filtre_statut = -1)
3653
    {
3654
        $this->receptions = array();
3655
3656
        dol_syslog(get_class($this) . "::loadReceptions", LOG_DEBUG);
3657
3658
        $sql = 'SELECT cd.rowid, cd.fk_product,';
3659
        $sql .= ' sum(cfd.qty) as qty';
3660
        $sql .= ' FROM ' . $this->db->prefix() . 'receptiondet_batch as cfd,';
3661
        if ($filtre_statut >= 0) {
3662
            $sql .= ' ' . $this->db->prefix() . 'reception as e,';
3663
        }
3664
        $sql .= ' ' . $this->db->prefix() . 'commande_fournisseurdet as cd';
3665
        $sql .= ' WHERE';
3666
        if ($filtre_statut >= 0) {
3667
            $sql .= ' cfd.fk_reception = e.rowid AND';
3668
        }
3669
        $sql .= ' cfd.fk_elementdet = cd.rowid';
3670
        $sql .= ' AND cd.fk_commande =' . ((int) $this->id);
3671
        if (isset($this->fk_product) && !empty($this->fk_product) > 0) {
3672
            $sql .= ' AND cd.fk_product = ' . ((int) $this->fk_product);
3673
        }
3674
        if ($filtre_statut >= 0) {
3675
            $sql .= ' AND e.fk_statut >= ' . ((int) $filtre_statut);
3676
        }
3677
        $sql .= ' GROUP BY cd.rowid, cd.fk_product';
3678
3679
        $resql = $this->db->query($sql);
3680
        if ($resql) {
3681
            $num = $this->db->num_rows($resql);
3682
            $i = 0;
3683
            while ($i < $num) {
3684
                $obj = $this->db->fetch_object($resql);
3685
                empty($this->receptions[$obj->rowid]) ? $this->receptions[$obj->rowid] = $obj->qty : $this->receptions[$obj->rowid] += $obj->qty;
3686
                $i++;
3687
            }
3688
            $this->db->free($resql);
3689
3690
            return $num;
3691
        } else {
3692
            $this->error = $this->db->lasterror();
3693
            return -1;
3694
        }
3695
    }
3696
3697
    /**
3698
     *  Return clicable link of object (with eventually picto)
3699
     *
3700
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3701
     *  @param      array       $arraydata              Array of data
3702
     *  @return     string                              HTML Code for Kanban thumb.
3703
     */
3704
    public function getKanbanView($option = '', $arraydata = null)
3705
    {
3706
        global $langs;
3707
3708
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3709
3710
        $return = '<div class="box-flex-item box-flex-grow-zero">';
3711
        $return .= '<div class="info-box info-box-sm">';
3712
        $return .= '<span class="info-box-icon bg-infobox-action">';
3713
        $return .= img_picto('', $this->picto);
3714
        $return .= '</span>';
3715
        $return .= '<div class="info-box-content">';
3716
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref) . '</span>';
3717
        if ($selected >= 0) {
3718
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
3719
        }
3720
        if (property_exists($this, 'socid') || property_exists($this, 'total_tva')) {
3721
            $return .= '<br><span class="info-box-label amount">' . $this->socid . '</span>';
3722
        }
3723
        if (property_exists($this, 'billed')) {
3724
            $return .= '<br><span class="opacitymedium">' . $langs->trans("Billed") . ' : </span><span class="info-box-label">' . yn($this->billed) . '</span>';
3725
        }
3726
        if (method_exists($this, 'getLibStatut')) {
3727
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3) . '</div>';
3728
        }
3729
        $return .= '</div>';
3730
        $return .= '</div>';
3731
        $return .= '</div>';
3732
        return $return;
3733
    }
3734
}
3735
3736
3737
3738
/**
3739
 *  Class to manage line orders
3740
 */
3741
class CommandeFournisseurLigne extends CommonOrderLine
3742
{
3743
    /**
3744
     * @var string ID to identify managed object
3745
     */
3746
    public $element = 'commande_fournisseurdet';
3747
3748
    /**
3749
     * @var string Name of table without prefix where object is stored
3750
     */
3751
    public $table_element = 'commande_fournisseurdet';
3752
3753
    /**
3754
     * @see CommonObjectLine
3755
     */
3756
    public $parent_element = 'commande_fournisseur';
3757
3758
    /**
3759
     * @see CommonObjectLine
3760
     */
3761
    public $fk_parent_attribute = 'fk_commande_fournisseur';
3762
3763
    public $oldline;
3764
3765
    /**
3766
     * Id of parent order
3767
     * @var int
3768
     */
3769
    public $fk_commande;
3770
3771
    // From llx_commande_fournisseurdet
3772
    /**
3773
     * @var int ID
3774
     */
3775
    public $fk_parent_line;
3776
3777
    /**
3778
     * @var int ID
3779
     */
3780
    public $fk_facture;
3781
3782
    public $rang = 0;
3783
3784
    /**
3785
     * @var int special code
3786
     */
3787
    public $special_code = 0;
3788
3789
    /**
3790
     * Unit price without taxes
3791
     * @var float
3792
     */
3793
    public $pu_ht;
3794
3795
    public $date_start;
3796
    public $date_end;
3797
    public $fk_fournprice;
3798
    public $packaging;
3799
    public $pa_ht;
3800
3801
    // From llx_product_fournisseur_price
3802
3803
    /**
3804
     * Supplier reference of price when we added the line. May have been changed after line was added.
3805
     * @var string
3806
     */
3807
    public $ref_supplier;
3808
3809
    /**
3810
     * @var string ref supplier
3811
     * @deprecated
3812
     * @see $ref_supplier
3813
     */
3814
    public $ref_fourn;
3815
3816
    public $remise;
3817
3818
3819
    /**
3820
     *  Constructor
3821
     *
3822
     *  @param      DoliDB      $db      Database handler
3823
     */
3824
    public function __construct($db)
3825
    {
3826
        $this->db = $db;
3827
    }
3828
3829
    /**
3830
     *  Load line order
3831
     *
3832
     *  @param  int     $rowid      Id line order
3833
     *  @return int                 Return integer <0 if KO, >0 if OK
3834
     */
3835
    public function fetch($rowid)
3836
    {
3837
        $sql = 'SELECT cd.rowid, cd.fk_commande, cd.fk_product, cd.product_type, cd.description, cd.qty, cd.tva_tx, cd.special_code,';
3838
        $sql .= ' cd.localtax1_tx, cd.localtax2_tx, cd.localtax1_type, cd.localtax2_type, cd.ref as ref_supplier,';
3839
        $sql .= ' cd.remise, cd.remise_percent, cd.subprice,';
3840
        $sql .= ' cd.info_bits, cd.total_ht, cd.total_tva, cd.total_ttc,';
3841
        $sql .= ' cd.total_localtax1, cd.total_localtax2,';
3842
        $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
3843
        $sql .= ' cd.date_start, cd.date_end, cd.fk_unit,';
3844
        $sql .= ' cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc,';
3845
        $sql .= ' c.fk_soc as socid';
3846
        $sql .= ' FROM ' . $this->db->prefix() . 'commande_fournisseur as c, ' . $this->db->prefix() . 'commande_fournisseurdet as cd';
3847
        $sql .= ' LEFT JOIN ' . $this->db->prefix() . 'product as p ON cd.fk_product = p.rowid';
3848
        $sql .= ' WHERE cd.fk_commande = c.rowid AND cd.rowid = ' . ((int) $rowid);
3849
3850
        $result = $this->db->query($sql);
3851
        if ($result) {
3852
            $objp = $this->db->fetch_object($result);
3853
3854
            if (!empty($objp)) {
3855
                $this->rowid = $objp->rowid;
3856
                $this->id               = $objp->rowid;
3857
                $this->fk_commande      = $objp->fk_commande;
3858
                $this->desc             = $objp->description;
3859
                $this->qty              = $objp->qty;
3860
                $this->ref_fourn        = $objp->ref_supplier;
3861
                $this->ref_supplier     = $objp->ref_supplier;
3862
                $this->subprice         = $objp->subprice;
3863
                $this->tva_tx           = $objp->tva_tx;
3864
                $this->localtax1_tx     = $objp->localtax1_tx;
3865
                $this->localtax2_tx     = $objp->localtax2_tx;
3866
                $this->localtax1_type   = $objp->localtax1_type;
3867
                $this->localtax2_type   = $objp->localtax2_type;
3868
                $this->remise           = $objp->remise;
3869
                $this->remise_percent   = $objp->remise_percent;
3870
                $this->fk_product       = $objp->fk_product;
3871
                $this->info_bits        = $objp->info_bits;
3872
                $this->total_ht         = $objp->total_ht;
3873
                $this->total_tva        = $objp->total_tva;
3874
                $this->total_localtax1  = $objp->total_localtax1;
3875
                $this->total_localtax2  = $objp->total_localtax2;
3876
                $this->total_ttc        = $objp->total_ttc;
3877
                $this->product_type     = $objp->product_type;
3878
                $this->special_code     = $objp->special_code;
3879
3880
                $this->ref = $objp->product_ref;
3881
3882
                $this->product_ref      = $objp->product_ref;
3883
                $this->product_label    = $objp->product_label;
3884
                $this->product_desc     = $objp->product_desc;
3885
3886
                if (getDolGlobalInt('PRODUCT_USE_SUPPLIER_PACKAGING')) {
3887
                    // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
3888
                    // Move this into another method and call it when required.
3889
3890
                    // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
3891
                    $sqlsearchpackage = 'SELECT rowid, packaging FROM ' . $this->db->prefix() . "product_fournisseur_price";
3892
                    $sqlsearchpackage .= ' WHERE entity IN (' . getEntity('product_fournisseur_price') . ")";
3893
                    $sqlsearchpackage .= " AND fk_product = " . ((int) $objp->fk_product);
3894
                    $sqlsearchpackage .= " AND ref_fourn = '" . $this->db->escape($objp->ref_supplier) . "'";
3895
                    $sqlsearchpackage .= " AND quantity <= " . ((float) $objp->qty);  // required to be qualified
3896
                    $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= " . ((float) $objp->qty) . ")";  // required to be qualified
3897
                    $sqlsearchpackage .= " AND fk_soc = " . ((int) $objp->socid);
3898
                    $sqlsearchpackage .= " ORDER BY packaging ASC";     // Take the smaller package first
3899
                    $sqlsearchpackage .= " LIMIT 1";
3900
3901
                    $resqlsearchpackage = $this->db->query($sqlsearchpackage);
3902
                    if ($resqlsearchpackage) {
3903
                        $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
3904
                        if ($objsearchpackage) {
3905
                            $this->fk_fournprice = $objsearchpackage->rowid;
3906
                            $this->packaging     = $objsearchpackage->packaging;
3907
                        }
3908
                    } else {
3909
                        $this->error = $this->db->lasterror();
3910
                        return -1;
3911
                    }
3912
                }
3913
3914
                $this->date_start               = $this->db->jdate($objp->date_start);
3915
                $this->date_end                 = $this->db->jdate($objp->date_end);
3916
                $this->fk_unit = $objp->fk_unit;
3917
3918
                $this->multicurrency_subprice   = $objp->multicurrency_subprice;
3919
                $this->multicurrency_total_ht   = $objp->multicurrency_total_ht;
3920
                $this->multicurrency_total_tva  = $objp->multicurrency_total_tva;
3921
                $this->multicurrency_total_ttc  = $objp->multicurrency_total_ttc;
3922
3923
                $this->fetch_optionals();
3924
3925
                $this->db->free($result);
3926
                return 1;
3927
            } else {
3928
                $this->error = 'Supplier order line  with id=' . $rowid . ' not found';
3929
                dol_syslog(get_class($this) . "::fetch Error " . $this->error, LOG_ERR);
3930
                return 0;
3931
            }
3932
        } else {
3933
            dol_print_error($this->db);
3934
            return -1;
3935
        }
3936
    }
3937
3938
    /**
3939
     *  Insert line into database
3940
     *
3941
     *  @param      int     $notrigger      1 = disable triggers
3942
     *  @return     int                     Return integer <0 if KO, >0 if OK
3943
     */
3944
    public function insert($notrigger = 0)
3945
    {
3946
        global $conf, $user;
3947
3948
        $error = 0;
3949
3950
        dol_syslog(get_class($this) . "::insert rang=" . $this->rang);
3951
3952
        // Clean parameters
3953
        if (empty($this->tva_tx)) {
3954
            $this->tva_tx = 0;
3955
        }
3956
        if (empty($this->localtax1_tx)) {
3957
            $this->localtax1_tx = 0;
3958
        }
3959
        if (empty($this->localtax2_tx)) {
3960
            $this->localtax2_tx = 0;
3961
        }
3962
        if (empty($this->localtax1_type)) {
3963
            $this->localtax1_type = '0';
3964
        }
3965
        if (empty($this->localtax2_type)) {
3966
            $this->localtax2_type = '0';
3967
        }
3968
        if (empty($this->total_localtax1)) {
3969
            $this->total_localtax1 = 0;
3970
        }
3971
        if (empty($this->total_localtax2)) {
3972
            $this->total_localtax2 = 0;
3973
        }
3974
        if (empty($this->rang)) {
3975
            $this->rang = 0;
3976
        }
3977
        if (empty($this->remise_percent)) {
3978
            $this->remise_percent = 0;
3979
        }
3980
        if (empty($this->info_bits)) {
3981
            $this->info_bits = 0;
3982
        }
3983
        if (empty($this->special_code)) {
3984
            $this->special_code = 0;
3985
        }
3986
        if (empty($this->fk_parent_line)) {
3987
            $this->fk_parent_line = 0;
3988
        }
3989
        if (empty($this->pa_ht)) {
3990
            $this->pa_ht = 0;
3991
        }
3992
3993
        // Multicurrency
3994
        if (!empty($this->multicurrency_code)) {
3995
            list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code);
3996
        }
3997
        if (empty($this->fk_multicurrency)) {
3998
            $this->multicurrency_code = $conf->currency;
3999
            $this->fk_multicurrency = 0;
4000
            $this->multicurrency_tx = 1;
4001
        }
4002
4003
        // Check parameters
4004
        if ($this->product_type < 0) {
4005
            return -1;
4006
        }
4007
4008
        $this->db->begin();
4009
4010
        // Insertion dans base de la ligne
4011
        $sql = 'INSERT INTO ' . $this->db->prefix() . $this->table_element;
4012
        $sql .= " (fk_commande, label, description, date_start, date_end,";
4013
        $sql .= " fk_product, product_type, special_code, rang,";
4014
        $sql .= " qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, remise_percent, subprice, ref,";
4015
        $sql .= " total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_unit,";
4016
        $sql .= " fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc,";
4017
        $sql .= " fk_parent_line)";
4018
        $sql .= " VALUES (" . $this->fk_commande . ", '" . $this->db->escape($this->label) . "','" . $this->db->escape($this->desc) . "',";
4019
        $sql .= " " . ($this->date_start ? "'" . $this->db->idate($this->date_start) . "'" : "null") . ",";
4020
        $sql .= " " . ($this->date_end ? "'" . $this->db->idate($this->date_end) . "'" : "null") . ",";
4021
        if ($this->fk_product) {
4022
            $sql .= $this->fk_product . ",";
4023
        } else {
4024
            $sql .= "null,";
4025
        }
4026
        $sql .= "'" . $this->db->escape($this->product_type) . "',";
4027
        $sql .= (int) $this->special_code . ",";
4028
        $sql .= "'" . $this->db->escape($this->rang) . "',";
4029
        $sql .= "'" . $this->db->escape($this->qty) . "', ";
4030
        $sql .= " " . (empty($this->vat_src_code) ? "''" : "'" . $this->db->escape($this->vat_src_code) . "'") . ",";
4031
        $sql .= " " . price2num($this->tva_tx) . ", ";
4032
        $sql .= " " . price2num($this->localtax1_tx) . ",";
4033
        $sql .= " " . price2num($this->localtax2_tx) . ",";
4034
        $sql .= " '" . $this->db->escape($this->localtax1_type) . "',";
4035
        $sql .= " '" . $this->db->escape($this->localtax2_type) . "',";
4036
        $sql .= " " . ((float) $this->remise_percent) . ", " . price2num($this->subprice, 'MU') . ", '" . $this->db->escape($this->ref_supplier) . "',";
4037
        $sql .= " " . price2num($this->total_ht) . ",";
4038
        $sql .= " " . price2num($this->total_tva) . ",";
4039
        $sql .= " " . price2num($this->total_localtax1) . ",";
4040
        $sql .= " " . price2num($this->total_localtax2) . ",";
4041
        $sql .= " " . price2num($this->total_ttc) . ",";
4042
        $sql .= ($this->fk_unit ? "'" . $this->db->escape($this->fk_unit) . "'" : "null");
4043
        $sql .= ", " . ($this->fk_multicurrency ? ((int) $this->fk_multicurrency) : "null");
4044
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
4045
        $sql .= ", " . ($this->multicurrency_subprice ? price2num($this->multicurrency_subprice) : '0');
4046
        $sql .= ", " . ($this->multicurrency_total_ht ? price2num($this->multicurrency_total_ht) : '0');
4047
        $sql .= ", " . ($this->multicurrency_total_tva ? price2num($this->multicurrency_total_tva) : '0');
4048
        $sql .= ", " . ($this->multicurrency_total_ttc ? price2num($this->multicurrency_total_ttc) : '0');
4049
        $sql .= ", " . ((!empty($this->fk_parent_line) && $this->fk_parent_line > 0) ? $this->fk_parent_line : 'null');
4050
        $sql .= ")";
4051
4052
        dol_syslog(get_class($this) . "::insert", LOG_DEBUG);
4053
        $resql = $this->db->query($sql);
4054
        if ($resql) {
4055
            $this->id = $this->db->last_insert_id($this->db->prefix() . $this->table_element);
4056
            $this->rowid = $this->id;
4057
4058
            if (!$error) {
4059
                $result = $this->insertExtraFields();
4060
                if ($result < 0) {
4061
                    $error++;
4062
                }
4063
            }
4064
4065
            if (!$error && !$notrigger) {
4066
                // Call trigger
4067
                $result = $this->call_trigger('LINEORDER_SUPPLIER_CREATE', $user);
4068
                if ($result < 0) {
4069
                    $error++;
4070
                }
4071
                // End call triggers
4072
            }
4073
4074
            if (!$error) {
4075
                $this->db->commit();
4076
                return 1;
4077
            }
4078
4079
            foreach ($this->errors as $errmsg) {
4080
                dol_syslog(get_class($this) . "::delete " . $errmsg, LOG_ERR);
4081
                $this->errors[] = ($this->errors ? ', ' . $errmsg : $errmsg);
4082
            }
4083
            $this->db->rollback();
4084
            return -1 * $error;
4085
        } else {
4086
            $this->errors[] = $this->db->error();
4087
            $this->db->rollback();
4088
            return -2;
4089
        }
4090
    }
4091
    /**
4092
     *  Update the line object into db
4093
     *
4094
     *  @param      int     $notrigger      1 = disable triggers
4095
     *  @return     int     Return integer <0 si ko, >0 si ok
4096
     */
4097
    public function update($notrigger = 0)
4098
    {
4099
        global $user;
4100
4101
        $error = 0;
4102
4103
        $this->db->begin();
4104
4105
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element . " SET";
4106
        $sql .= "  description='" . $this->db->escape($this->desc) . "'";
4107
        $sql .= ", ref='" . $this->db->escape($this->ref_supplier) . "'";
4108
        $sql .= ", subprice='" . price2num($this->subprice) . "'";
4109
        //$sql.= ",remise='".price2num($remise)."'";
4110
        $sql .= ", remise_percent='" . price2num($this->remise_percent) . "'";
4111
4112
        $sql .= ", vat_src_code = '" . (empty($this->vat_src_code) ? '' : $this->vat_src_code) . "'";
4113
        $sql .= ", tva_tx='" . price2num($this->tva_tx) . "'";
4114
        $sql .= ", localtax1_tx='" . price2num($this->localtax1_tx) . "'";
4115
        $sql .= ", localtax2_tx='" . price2num($this->localtax2_tx) . "'";
4116
        $sql .= ", localtax1_type='" . $this->db->escape($this->localtax1_type) . "'";
4117
        $sql .= ", localtax2_type='" . $this->db->escape($this->localtax2_type) . "'";
4118
        $sql .= ", qty='" . price2num($this->qty) . "'";
4119
        $sql .= ", date_start=" . (!empty($this->date_start) ? "'" . $this->db->idate($this->date_start) . "'" : "null");
4120
        $sql .= ", date_end=" . (!empty($this->date_end) ? "'" . $this->db->idate($this->date_end) . "'" : "null");
4121
        $sql .= ", info_bits='" . $this->db->escape($this->info_bits) . "'";
4122
        $sql .= ", total_ht='" . price2num($this->total_ht) . "'";
4123
        $sql .= ", total_tva='" . price2num($this->total_tva) . "'";
4124
        $sql .= ", total_localtax1='" . price2num($this->total_localtax1) . "'";
4125
        $sql .= ", total_localtax2='" . price2num($this->total_localtax2) . "'";
4126
        $sql .= ", total_ttc='" . price2num($this->total_ttc) . "'";
4127
        $sql .= ", product_type=" . $this->product_type;
4128
        $sql .= ", special_code=" . (!empty($this->special_code) ? $this->special_code : 0);
4129
        $sql .= ($this->fk_unit ? ", fk_unit='" . $this->db->escape($this->fk_unit) . "'" : ", fk_unit=null");
4130
4131
        // Multicurrency
4132
        $sql .= ", multicurrency_subprice=" . price2num($this->multicurrency_subprice);
4133
        $sql .= ", multicurrency_total_ht=" . price2num($this->multicurrency_total_ht);
4134
        $sql .= ", multicurrency_total_tva=" . price2num($this->multicurrency_total_tva);
4135
        $sql .= ", multicurrency_total_ttc=" . price2num($this->multicurrency_total_ttc);
4136
4137
        $sql .= " WHERE rowid = " . ((int) $this->id);
4138
4139
        dol_syslog(get_class($this) . "::updateline", LOG_DEBUG);
4140
        $resql = $this->db->query($sql);
4141
        if ($resql) {
4142
            if (!$error) {
4143
                $result = $this->insertExtraFields();
4144
                if ($result < 0) {
4145
                    $error++;
4146
                }
4147
            }
4148
4149
            if (!$error && !$notrigger) {
4150
                // Call trigger
4151
                $result = $this->call_trigger('LINEORDER_SUPPLIER_MODIFY', $user);
4152
                if ($result < 0) {
4153
                    $this->db->rollback();
4154
                    return -1;
4155
                }
4156
                // End call triggers
4157
            }
4158
4159
            if (!$error) {
4160
                $this->db->commit();
4161
                return 1;
4162
            } else {
4163
                $this->db->rollback();
4164
                return -1;
4165
            }
4166
        } else {
4167
            $this->error = $this->db->lasterror();
4168
            $this->db->rollback();
4169
            return -1;
4170
        }
4171
    }
4172
4173
    /**
4174
     *  Delete line in database
4175
     *
4176
     *  @param      User    $user       User making the change
4177
     *  @param      int     $notrigger  1=Disable call to triggers
4178
     *  @return     int                 Return integer <0 if KO, >0 if OK
4179
     */
4180
    public function delete($user, $notrigger = 0)
4181
    {
4182
        if (empty($user)) {
4183
            global $user;
4184
        }
4185
4186
        $error = 0;
4187
4188
        $this->db->begin();
4189
4190
        // extrafields
4191
        $result = $this->deleteExtraFields();
4192
        if ($result < 0) {
4193
            $this->db->rollback();
4194
            return -1;
4195
        }
4196
4197
        $sql1 = 'UPDATE ' . $this->db->prefix() . "commandedet SET fk_commandefourndet = NULL WHERE rowid=" . ((int) $this->id);
4198
        $resql = $this->db->query($sql1);
4199
        if (!$resql) {
4200
            $this->db->rollback();
4201
            return -1;
4202
        }
4203
4204
        $sql2 = 'DELETE FROM ' . $this->db->prefix() . "commande_fournisseurdet WHERE rowid=" . ((int) $this->id);
4205
4206
        dol_syslog(__METHOD__, LOG_DEBUG);
4207
        $resql = $this->db->query($sql2);
4208
        if ($resql) {
4209
            if (!$notrigger) {
4210
                // Call trigger
4211
                $result = $this->call_trigger('LINEORDER_SUPPLIER_DELETE', $user);
4212
                if ($result < 0) {
4213
                    $error++;
4214
                }
4215
                // End call triggers
4216
            }
4217
4218
            if (!$error) {
4219
                $this->db->commit();
4220
                return 1;
4221
            }
4222
4223
            foreach ($this->errors as $errmsg) {
4224
                dol_syslog(get_class($this) . "::delete " . $errmsg, LOG_ERR);
4225
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
4226
            }
4227
            $this->db->rollback();
4228
            return -1 * $error;
4229
        } else {
4230
            $this->error = $this->db->lasterror();
4231
            return -1;
4232
        }
4233
    }
4234
}
4235