CommandeFournisseur::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
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
namespace Dolibarr\Code\Fourn\Classes;
36
37
use Dolibarr\Code\Core\Classes\CommonOrder;
38
use Dolibarr\Code\Core\Classes\WorkboardResponse;
39
use Dolibarr\Code\MultiCurrency\Classes\MultiCurrency;
40
use DoliDB;
41
42
/**
43
 *  \file       htdocs/fourn/class/fournisseur.commande.class.php
44
 *  \ingroup    fournisseur,commande
45
 *  \brief      File of class to manage suppliers orders
46
 */
47
48
/**
49
 *  Class to manage predefined suppliers products
50
 */
51
class CommandeFournisseur extends CommonOrder
52
{
53
    /**
54
     * @var string ID to identify managed object
55
     */
56
    public $element = 'order_supplier';
57
58
    /**
59
     * @var string Name of table without prefix where object is stored
60
     */
61
    public $table_element = 'commande_fournisseur';
62
63
    /**
64
     * @var string    Name of subtable line
65
     */
66
    public $table_element_line = 'commande_fournisseurdet';
67
68
    /**
69
     * @var string Name of class line
70
     */
71
    public $class_element_line = 'CommandeFournisseurLigne';
72
73
    /**
74
     * @var string Field with ID of parent key if this field has a parent
75
     */
76
    public $fk_element = 'fk_commande';
77
78
    /**
79
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
80
     */
81
    public $picto = 'supplier_order';
82
83
    /**
84
     * 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
85
     * @var integer
86
     */
87
    public $restrictiononfksoc = 1;
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    protected $table_ref_field = 'ref';
93
94
    /**
95
     * @var int Purchase Order ID
96
     */
97
    public $id;
98
99
    /**
100
     * @var string Supplier order reference
101
     */
102
    public $ref;
103
104
    /**
105
     * @var string Supplier reference
106
     */
107
    public $ref_supplier;
108
109
    /**
110
     * @var string ref supplier
111
     * @deprecated
112
     * @see $ref_supplier
113
     */
114
    public $ref_fourn;
115
116
    /**
117
     * @var int
118
     */
119
    public $statut; // 0=Draft -> 1=Validated -> 2=Approved -> 3=Ordered/Process running -> 4=Received partially -> 5=Received totally -> (reopen) 4=Received partially
120
    //                                                                                          -> 7=Canceled/Never received -> (reopen) 3=Process running
121
    //                                                              -> 6=Canceled -> (reopen) 2=Approved
122
    //                                                -> 9=Refused  -> (reopen) 1=Validated
123
    //  Note: billed or not is on another field "billed"
124
125
    public $billed;
126
127
    /**
128
     * @var int Company ID
129
     */
130
    public $socid;
131
132
    /**
133
     * @var int Supplier ID
134
     */
135
    public $fourn_id;
136
137
    /**
138
     * @var int Date
139
     */
140
    public $date;
141
142
    /**
143
     * @var int Date of the purchase order creation
144
     */
145
    public $date_creation;
146
147
    /**
148
     * @var int Date of the purchase order validation
149
     */
150
    public $date_valid;
151
152
    /**
153
     * @var int Date of the purchase order approval
154
     */
155
    public $date_approve;
156
157
    /**
158
     * @var int Date of the purchase order second approval
159
     * Used when SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set
160
     */
161
    public $date_approve2;
162
163
    /**
164
     * @var int Date of the purchase order ordering
165
     */
166
    public $date_commande;
167
168
    //For backward compatibility
169
    public $remise_percent;
170
    public $methode_commande_id;
171
    public $methode_commande;
172
173
    /**
174
     *  @var int Expected Delivery Date
175
     */
176
    public $delivery_date;
177
178
    /**
179
     *  @var float Total value, excluding taxes (HT = "Hors Taxe" in French)
180
     */
181
    public $total_ht;
182
183
    /**
184
     *  @var float Total VAT
185
     */
186
    public $total_tva;
187
188
    /**
189
     *  @var float Total Local tax 1
190
     */
191
    public $total_localtax1;
192
193
    /**
194
     *  @var float Total Local tax 2
195
     */
196
    public $total_localtax2;
197
198
    /**
199
     *  @var float Total value, including taxes (TTC = "Toutes Taxes Comprises" in French)
200
     */
201
    public $total_ttc;
202
203
    public $source;
204
205
    /**
206
     * @var int ID
207
     */
208
    public $fk_project;
209
210
    /**
211
     * @var int Payment conditions ID
212
     */
213
    public $cond_reglement_id;
214
215
    /**
216
     * @var string Payment conditions code
217
     */
218
    public $cond_reglement_code;
219
220
    /**
221
     * @var string Payment conditions label
222
     */
223
    public $cond_reglement_label;
224
225
    /**
226
     * @var string Payment conditions label on documents
227
     */
228
    public $cond_reglement_doc;
229
230
    /**
231
     * @var int Account ID
232
     */
233
    public $fk_account;
234
235
    /**
236
     * @var int Payment choice ID
237
     */
238
    public $mode_reglement_id;
239
240
    /**
241
     * @var string Payment choice code
242
     */
243
    public $mode_reglement_code;
244
245
    /**
246
     * @var string Payment choice label
247
     */
248
    public $mode_reglement;
249
250
    /**
251
     * @var int User ID of the purchase order author
252
     */
253
    public $user_author_id;
254
255
    /**
256
     * @var int User ID of the purchase order approver
257
     */
258
    public $user_approve_id;
259
260
    /**
261
     * @var int User ID of the purchase order second approver
262
     * Used when SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set
263
     */
264
    public $user_approve_id2;
265
266
    public $refuse_note;
267
268
    public $extraparams = array();
269
270
    /**
271
     * @var CommandeFournisseurLigne[]
272
     */
273
    public $lines = array();
274
275
    /**
276
     * @var CommandeFournisseurLigne
277
     */
278
    public $line;
279
280
    // Add for supplier_proposal
281
    public $origin;
282
    public $origin_id;
283
    public $linked_objects = array();
284
285
    /**
286
     * @var int Date of the purchase order payment deadline
287
     */
288
    public $date_lim_reglement;
289
    public $receptions = array();
290
291
    // Multicurrency
292
    /**
293
     * @var int ID
294
     */
295
    public $fk_multicurrency;
296
297
    /**
298
     * @var string
299
     */
300
    public $multicurrency_code;
301
302
    /**
303
     * @var float Rate
304
     */
305
    public $multicurrency_tx;
306
307
    /**
308
     * @var float Total value in the other currency, excluding taxes (HT = "Hors Taxes" in French)
309
     */
310
    public $multicurrency_total_ht;
311
312
    /**
313
     * @var float Total VAT in the other currency (TVA = "Taxe sur la Valeur Ajoutée" in French)
314
     */
315
    public $multicurrency_total_tva;
316
317
    /**
318
     * @var float Total value in the other currency, including taxes (TTC = "Toutes Taxes Comprises in French)
319
     */
320
    public $multicurrency_total_ttc;
321
322
    /**
323
     *  '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')
324
     *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
325
     *  'label' the translation key.
326
     *  'picto' is code of a picto to show before value in forms
327
     *  'enabled' is a condition when the field must be managed (Example: 1 or 'getDolGlobalString("MY_SETUP_PARAM")' or 'isModEnabled("multicurrency")' ...)
328
     *  'position' is the sort order of field.
329
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
330
     *  '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)
331
     *  'noteditable' says if field is not editable (1 or 0)
332
     *  '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.
333
     *  'index' if we want an index in database.
334
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
335
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
336
     *  '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)
337
     *  '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'
338
     *  'help' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
339
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
340
     *  '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.
341
     *  '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'
342
     *  'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
343
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
344
     *  'validate' is 1 if need to validate with $this->validateField()
345
     *  'copytoclipboard' is 1 or 2 to allow to add a picto to copy value into clipboard (1=picto after label, 2=picto after value)
346
     *
347
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
348
     */
349
    public $fields = array(
350
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 10),
351
        'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 25, 'searchall' => 1),
352
        'ref_ext' => array('type' => 'varchar(255)', 'label' => 'Ref ext', 'enabled' => 1, 'visible' => 0, 'position' => 35),
353
        'ref_supplier' => array('type' => 'varchar(255)', 'label' => 'RefOrderSupplierShort', 'enabled' => 1, 'visible' => 1, 'position' => 40, 'searchall' => 1),
354
        'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 45),
355
        'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 710),
356
        'date_approve' => array('type' => 'datetime', 'label' => 'DateApprove', 'enabled' => 1, 'visible' => -1, 'position' => 720),
357
        'date_approve2' => array('type' => 'datetime', 'label' => 'DateApprove2', 'enabled' => 1, 'visible' => 3, 'position' => 725),
358
        'date_commande' => array('type' => 'date', 'label' => 'OrderDateShort', 'enabled' => 1, 'visible' => 1, 'position' => 70),
359
        'date_livraison' => array('type' => 'datetime', 'label' => 'DeliveryDate', 'enabled' => 'empty($conf->global->ORDER_DISABLE_DELIVERY_DATE)', 'visible' => 1, 'position' => 74),
360
        'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => 3, 'position' => 41),
361
        'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => 3, 'notnull' => -1, 'position' => 80),
362
        'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => 3, 'position' => 711),
363
        'fk_user_approve' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval', 'enabled' => 1, 'visible' => 3, 'position' => 721),
364
        'fk_user_approve2' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval2', 'enabled' => 1, 'visible' => 3, 'position' => 726),
365
        'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 100),
366
        'billed' => array('type' => 'smallint(6)', 'label' => 'Billed', 'enabled' => 1, 'visible' => 1, 'position' => 710),
367
        'total_ht' => array('type' => 'double(24,8)', 'label' => 'AmountHT', 'enabled' => 1, 'visible' => 1, 'position' => 130, 'isameasure' => 1),
368
        'total_tva' => array('type' => 'double(24,8)', 'label' => 'AmountVAT', 'enabled' => 1, 'visible' => 1, 'position' => 135, 'isameasure' => 1),
369
        'localtax1' => array('type' => 'double(24,8)', 'label' => 'LT1', 'enabled' => 1, 'visible' => 3, 'position' => 140, 'isameasure' => 1),
370
        'localtax2' => array('type' => 'double(24,8)', 'label' => 'LT2', 'enabled' => 1, 'visible' => 3, 'position' => 145, 'isameasure' => 1),
371
        'total_ttc' => array('type' => 'double(24,8)', 'label' => 'AmountTTC', 'enabled' => 1, 'visible' => -1, 'position' => 150, 'isameasure' => 1),
372
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 750, 'searchall' => 1),
373
        'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 760, 'searchall' => 1),
374
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPDF', 'enabled' => 1, 'visible' => 0, 'position' => 165),
375
        'fk_input_method' => array('type' => 'integer', 'label' => 'OrderMode', 'enabled' => 1, 'visible' => 3, 'position' => 170),
376
        'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => 3, 'position' => 175),
377
        'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => 3, 'position' => 180),
378
        'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => 0, 'position' => 190),
379
        'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => 3, 'position' => 200),
380
        'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => 3, 'position' => 205),
381
        'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => 3, 'position' => 210),
382
        'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 1, 'visible' => 0, 'position' => 215),
383
        'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Currency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 220),
384
        'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'CurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 225),
385
        'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 230),
386
        'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
387
        'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
388
        'date_creation' => array('type' => 'datetime', 'label' => 'Date creation', 'enabled' => 1, 'visible' => -1, 'position' => 500),
389
        'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => 1, 'notnull' => 1, 'position' => 50),
390
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 1000, 'index' => 1),
391
        'tms' => array('type' => 'datetime', 'label' => "DateModificationShort", 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 501),
392
        'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => 0, 'position' => 700),
393
        'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => 1, 'position' => 701),
394
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => 0, 'position' => 900),
395
    );
396
397
398
    /**
399
     * Draft status
400
     */
401
    const STATUS_DRAFT = 0;
402
403
    /**
404
     * Validated status
405
     */
406
    const STATUS_VALIDATED = 1;
407
408
    /**
409
     * Accepted
410
     */
411
    const STATUS_ACCEPTED = 2;
412
413
    /**
414
     * Order sent, shipment on process
415
     */
416
    const STATUS_ORDERSENT = 3;
417
418
    /**
419
     * Received partially
420
     */
421
    const STATUS_RECEIVED_PARTIALLY = 4;
422
423
    /**
424
     * Received completely
425
     */
426
    const STATUS_RECEIVED_COMPLETELY = 5;
427
428
    /**
429
     * Order canceled
430
     */
431
    const STATUS_CANCELED = 6;
432
433
    /**
434
     * Order canceled/never received
435
     */
436
    const STATUS_CANCELED_AFTER_ORDER = 7;
437
438
    /**
439
     * Refused
440
     */
441
    const STATUS_REFUSED = 9;
442
443
444
    /**
445
     * The constant used into source field to track the order was generated by the replenishement feature
446
     */
447
    const SOURCE_ID_REPLENISHMENT = 42;
448
449
    /**
450
     *  Constructor
451
     *
452
     *  @param      DoliDB      $db      Database handler
453
     */
454
    public function __construct($db)
455
    {
456
        $this->db = $db;
457
458
        $this->ismultientitymanaged = 1;
459
    }
460
461
462
    /**
463
     *  Get object and lines from database
464
     *
465
     *  @param  int     $id         Id of order to load
466
     *  @param  string  $ref        Ref of object
467
     *  @return int                 >0 if OK, <0 if KO, 0 if not found
468
     */
469
    public function fetch($id, $ref = '')
470
    {
471
        // Check parameters
472
        if (empty($id) && empty($ref)) {
473
            return -1;
474
        }
475
476
        $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,";
477
        $sql .= " c.localtax1, c.localtax2, ";
478
        $sql .= " c.date_creation, c.date_valid, c.date_approve, c.date_approve2,";
479
        $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,";
480
        $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,";
481
        $sql .= " c.fk_account,";
482
        $sql .= " c.note_private, c.note_public, c.model_pdf, c.extraparams, c.billed,";
483
        $sql .= " c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc,";
484
        $sql .= " cm.libelle as methode_commande,";
485
        $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc,";
486
        $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_libelle";
487
        $sql .= ', c.fk_incoterms, c.location_incoterms';
488
        $sql .= ', i.libelle as label_incoterms';
489
        $sql .= " FROM " . $this->db->prefix() . "commande_fournisseur as c";
490
        $sql .= " LEFT JOIN " . $this->db->prefix() . "c_payment_term as cr ON c.fk_cond_reglement = cr.rowid";
491
        $sql .= " LEFT JOIN " . $this->db->prefix() . "c_paiement as p ON c.fk_mode_reglement = p.id";
492
        $sql .= " LEFT JOIN " . $this->db->prefix() . "c_input_method as cm ON cm.rowid = c.fk_input_method";
493
        $sql .= ' LEFT JOIN ' . $this->db->prefix() . 'c_incoterms as i ON c.fk_incoterms = i.rowid';
494
495
        if (empty($id)) {
496
            $sql .= " WHERE c.entity IN (" . getEntity('supplier_order') . ")";
497
        } else {
498
            $sql .= " WHERE c.rowid=" . ((int) $id);
499
        }
500
501
        if ($ref) {
502
            $sql .= " AND c.ref='" . $this->db->escape($ref) . "'";
503
        }
504
505
        dol_syslog(get_only_class($this) . "::fetch", LOG_DEBUG);
506
        $resql = $this->db->query($sql);
507
        if ($resql) {
508
            $obj = $this->db->fetch_object($resql);
509
            if (!$obj) {
510
                $this->error = 'Bill with id ' . $id . ' not found';
511
                dol_syslog(get_only_class($this) . '::fetch ' . $this->error);
512
                return 0;
513
            }
514
515
            $this->id = $obj->rowid;
516
            $this->entity = $obj->entity;
517
518
            $this->ref = $obj->ref;
519
            $this->ref_supplier = $obj->ref_supplier;
520
            $this->socid = $obj->fk_soc;
521
            $this->fourn_id = $obj->fk_soc;
522
            $this->statut = $obj->status;   // deprecated
523
            $this->status = $obj->status;
524
            $this->billed = $obj->billed;
525
            $this->user_author_id = $obj->user_author_id;
526
            $this->user_validation_id = $obj->user_validation_id;
527
            $this->user_approve_id = $obj->user_approve_id;
528
            $this->user_approve_id2 = $obj->user_approve_id2;
529
            $this->total_ht             = $obj->total_ht;
530
            $this->total_tva            = $obj->total_tva;
531
            $this->total_localtax1      = $obj->localtax1;
532
            $this->total_localtax2      = $obj->localtax2;
533
            $this->total_ttc            = $obj->total_ttc;
534
            $this->date_creation = $this->db->jdate($obj->date_creation);
535
            $this->date_valid = $this->db->jdate($obj->date_valid);
536
            $this->date_approve         = $this->db->jdate($obj->date_approve);
537
            $this->date_approve2        = $this->db->jdate($obj->date_approve2);
538
            $this->date_commande        = $this->db->jdate($obj->date_commande); // date we make the order to supplier
539
            if (isset($obj->date_commande)) {
540
                $this->date = $this->date_commande;
541
            } else {
542
                $this->date = $this->date_creation;
543
            }
544
            $this->delivery_date = $this->db->jdate($obj->delivery_date);
545
            $this->remise_percent = $obj->remise_percent;
546
            $this->methode_commande_id = $obj->fk_input_method;
547
            $this->methode_commande = $obj->methode_commande;
548
549
            $this->source = $obj->source;
550
            $this->fk_project = $obj->fk_project;
551
            $this->cond_reglement_id = $obj->fk_cond_reglement;
552
            $this->cond_reglement_code = $obj->cond_reglement_code;
553
            $this->cond_reglement = $obj->cond_reglement_label;         // deprecated
554
            $this->cond_reglement_label = $obj->cond_reglement_label;
555
            $this->cond_reglement_doc = $obj->cond_reglement_doc;
556
            $this->fk_account = $obj->fk_account;
557
            $this->mode_reglement_id = $obj->fk_mode_reglement;
558
            $this->mode_reglement_code = $obj->mode_reglement_code;
559
            $this->mode_reglement = $obj->mode_reglement_libelle;
560
            $this->note = $obj->note_private; // deprecated
561
            $this->note_private = $obj->note_private;
562
            $this->note_public = $obj->note_public;
563
            $this->model_pdf = $obj->model_pdf;
564
565
            //Incoterms
566
            $this->fk_incoterms = $obj->fk_incoterms;
567
            $this->location_incoterms = $obj->location_incoterms;
568
            $this->label_incoterms = $obj->label_incoterms;
569
570
            // Multicurrency
571
            $this->fk_multicurrency         = $obj->fk_multicurrency;
572
            $this->multicurrency_code = $obj->multicurrency_code;
573
            $this->multicurrency_tx         = $obj->multicurrency_tx;
574
            $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
575
            $this->multicurrency_total_tva  = $obj->multicurrency_total_tva;
576
            $this->multicurrency_total_ttc  = $obj->multicurrency_total_ttc;
577
578
            $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
579
580
            $this->db->free($resql);
581
582
            // Retrieve all extrafield
583
            // fetch optionals attributes and labels
584
            $this->fetch_optionals();
585
586
            // Lines
587
            $result = $this->fetch_lines();
588
589
            if ($result < 0) {
590
                return -1;
591
            } else {
592
                return 1;
593
            }
594
        } else {
595
            $this->error = $this->db->error() . " sql=" . $sql;
596
            return -1;
597
        }
598
    }
599
600
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
601
    /**
602
     * Load array lines
603
     *
604
     * @param       int     $only_product   Return only physical products
605
     * @return      int                     Return integer <0 if KO, >0 if OK
606
     */
607
    public function fetch_lines($only_product = 0)
608
    {
609
		// phpcs:enable
610
611
        $this->lines = array();
612
613
        $sql = "SELECT l.rowid, l.fk_commande, l.ref as ref_supplier, l.fk_product, l.product_type, l.label, l.description, l.qty,";
614
        $sql .= " l.vat_src_code, l.tva_tx, l.remise_percent, l.subprice,";
615
        $sql .= " l.localtax1_tx, l. localtax2_tx, l.localtax1_type, l. localtax2_type, l.total_localtax1, l.total_localtax2,";
616
        $sql .= " l.total_ht, l.total_tva, l.total_ttc, l.special_code, l.fk_parent_line, l.rang,";
617
        $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,";
618
        $sql .= " l.fk_unit,";
619
        $sql .= " l.date_start, l.date_end,";
620
        $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc';
621
        $sql .= " FROM " . $this->db->prefix() . "commande_fournisseurdet as l";
622
        $sql .= ' LEFT JOIN ' . $this->db->prefix() . 'product as p ON l.fk_product = p.rowid';
623
        $sql .= " WHERE l.fk_commande = " . ((int) $this->id);
624
        if ($only_product) {
625
            $sql .= ' AND p.fk_product_type = 0';
626
        }
627
        $sql .= " ORDER BY l.rang, l.rowid";
628
        //print $sql;
629
630
        dol_syslog(get_only_class($this) . "::fetch_lines", LOG_DEBUG);
631
632
        $result = $this->db->query($sql);
633
        if ($result) {
634
            $num = $this->db->num_rows($result);
635
            $i = 0;
636
637
            while ($i < $num) {
638
                $objp = $this->db->fetch_object($result);
639
640
                $line = new CommandeFournisseurLigne($this->db);
641
642
                $line->id                  = $objp->rowid;
643
                $line->fk_commande         = $objp->fk_commande;
644
                $line->desc                = $objp->description;
645
                $line->description         = $objp->description;
646
                $line->qty                 = $objp->qty;
647
                $line->tva_tx              = $objp->tva_tx;
648
                $line->localtax1_tx        = $objp->localtax1_tx;
649
                $line->localtax2_tx        = $objp->localtax2_tx;
650
                $line->localtax1_type      = $objp->localtax1_type;
651
                $line->localtax2_type      = $objp->localtax2_type;
652
                $line->subprice            = $objp->subprice;
653
                $line->pu_ht = $objp->subprice;
654
                $line->remise_percent      = $objp->remise_percent;
655
656
                $line->vat_src_code        = $objp->vat_src_code;
657
                $line->total_ht            = $objp->total_ht;
658
                $line->total_tva           = $objp->total_tva;
659
                $line->total_localtax1     = $objp->total_localtax1;
660
                $line->total_localtax2     = $objp->total_localtax2;
661
                $line->total_ttc           = $objp->total_ttc;
662
                $line->product_type        = $objp->product_type;
663
664
                $line->fk_product          = $objp->fk_product;
665
666
                $line->libelle             = $objp->product_label; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Core\Class...mmonOrderLine::$libelle has been deprecated: Use product_label ( Ignorable by Annotation )

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

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

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

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

Loading history...
667
                $line->product_label       = $objp->product_label;
668
                $line->product_desc        = $objp->product_desc;
669
                $line->product_tobatch     = $objp->product_tobatch;
670
                $line->product_barcode     = $objp->product_barcode;
671
672
                $line->ref                 = $objp->product_ref; // Ref of product
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Core\Classes\CommonOrderLine::$ref has been deprecated: Use product_ref ( Ignorable by Annotation )

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

672
                /** @scrutinizer ignore-deprecated */ $line->ref                 = $objp->product_ref; // Ref of product

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...
673
                $line->product_ref         = $objp->product_ref; // Ref of product
674
                $line->ref_fourn           = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
675
                $line->ref_supplier        = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
676
677
                if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
678
                    // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
679
                    // Move this into another method and call it when required.
680
681
                    // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
682
                    $sqlsearchpackage = 'SELECT rowid, packaging FROM ' . $this->db->prefix() . "product_fournisseur_price";
683
                    $sqlsearchpackage .= ' WHERE entity IN (' . getEntity('product_fournisseur_price') . ")";
684
                    $sqlsearchpackage .= " AND fk_product = " . ((int) $objp->fk_product);
685
                    $sqlsearchpackage .= " AND ref_fourn = '" . $this->db->escape($objp->ref_supplier) . "'";
686
                    $sqlsearchpackage .= " AND quantity <= " . ((float) $objp->qty);  // required to be qualified
687
                    $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= " . ((float) $objp->qty) . ")";  // required to be qualified
688
                    $sqlsearchpackage .= " AND fk_soc = " . ((int) $this->socid);
689
                    $sqlsearchpackage .= " ORDER BY packaging ASC";     // Take the smaller package first
690
                    $sqlsearchpackage .= " LIMIT 1";
691
692
                    $resqlsearchpackage = $this->db->query($sqlsearchpackage);
693
                    if ($resqlsearchpackage) {
694
                        $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
695
                        if ($objsearchpackage) {
696
                            $line->fk_fournprice = $objsearchpackage->rowid;
697
                            $line->packaging     = $objsearchpackage->packaging;
698
                        }
699
                    } else {
700
                        $this->error = $this->db->lasterror();
701
                        return -1;
702
                    }
703
                }
704
705
                $line->date_start          = $this->db->jdate($objp->date_start);
706
                $line->date_end            = $this->db->jdate($objp->date_end);
707
                $line->fk_unit             = $objp->fk_unit;
708
709
                // Multicurrency
710
                $line->fk_multicurrency = $objp->fk_multicurrency;
711
                $line->multicurrency_code = $objp->multicurrency_code;
712
                $line->multicurrency_subprice = $objp->multicurrency_subprice;
713
                $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
714
                $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
715
                $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
716
717
                $line->special_code        = $objp->special_code;
718
                $line->fk_parent_line      = $objp->fk_parent_line;
719
720
                $line->rang                = $objp->rang;
721
722
                // Retrieve all extrafield
723
                // fetch optionals attributes and labels
724
                $line->fetch_optionals();
725
726
                $this->lines[$i] = $line;
727
728
                $i++;
729
            }
730
            $this->db->free($result);
731
732
            return $num;
733
        } else {
734
            $this->error = $this->db->error() . " sql=" . $sql;
735
            return -1;
736
        }
737
    }
738
739
    /**
740
     *  Validate an order
741
     *
742
     *  @param  User    $user           Validator User
743
     *  @param  int     $idwarehouse    Id of warehouse to use for stock decrease
744
     *  @param  int     $notrigger      1=Does not execute triggers, 0= execute triggers
745
     *  @return int                     Return integer <0 if KO, >0 if OK
746
     */
747
    public function valid($user, $idwarehouse = 0, $notrigger = 0)
748
    {
749
        global $conf;
750
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
751
752
        $error = 0;
753
754
        dol_syslog(get_only_class($this) . "::valid");
755
        $result = 0;
756
        if (
757
            (!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...
758
            || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight("fournisseur", "supplier_order_advance", "validate"))
759
        ) {
760
            $this->db->begin();
761
762
            // Definition of supplier order numbering model name
763
            $soc = new Societe($this->db);
764
            $soc->fetch($this->fourn_id);
765
766
            // Check if object has a temporary ref
767
            if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
768
                $num = $this->getNextNumRef($soc);
769
            } else {
770
                $num = $this->ref;
771
            }
772
            $this->newref = dol_sanitizeFileName($num);
773
774
            $sql = 'UPDATE ' . $this->db->prefix() . "commande_fournisseur";
775
            $sql .= " SET ref='" . $this->db->escape($num) . "',";
776
            $sql .= " fk_statut = " . ((int) self::STATUS_VALIDATED) . ",";
777
            $sql .= " date_valid='" . $this->db->idate(dol_now()) . "',";
778
            $sql .= " fk_user_valid = " . ((int) $user->id);
779
            $sql .= " WHERE rowid = " . ((int) $this->id);
780
            $sql .= " AND fk_statut = " . ((int) self::STATUS_DRAFT);
781
782
            $resql = $this->db->query($sql);
783
            if (!$resql) {
784
                dol_print_error($this->db);
785
                $error++;
786
            }
787
788
            if (!$error && !$notrigger) {
789
                // Call trigger
790
                $result = $this->call_trigger('ORDER_SUPPLIER_VALIDATE', $user);
791
                if ($result < 0) {
792
                    $error++;
793
                }
794
                // End call triggers
795
            }
796
797
            if (!$error) {
798
                $this->oldref = $this->ref;
799
800
                // Rename directory if dir was a temporary ref
801
                if (preg_match('/^[\(]?PROV/i', $this->ref)) {
802
                    // Now we rename also files into index
803
                    $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) . "'";
804
                    $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'fournisseur/commande/" . $this->db->escape($this->ref) . "' and entity = " . ((int) $conf->entity);
805
                    $resql = $this->db->query($sql);
806
                    if (!$resql) {
807
                        $error++;
808
                        $this->error = $this->db->lasterror();
809
                    }
810
                    $sql = 'UPDATE ' . $this->db->prefix() . "ecm_files set filepath = 'fournisseur/commande/" . $this->db->escape($this->newref) . "'";
811
                    $sql .= " WHERE filepath = 'fournisseur/commande/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
812
                    $resql = $this->db->query($sql);
813
                    if (!$resql) {
814
                        $error++;
815
                        $this->error = $this->db->lasterror();
816
                    }
817
818
                    // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
819
                    $oldref = dol_sanitizeFileName($this->ref);
820
                    $newref = dol_sanitizeFileName($num);
821
                    $dirsource = $conf->fournisseur->commande->dir_output . '/' . $oldref;
822
                    $dirdest = $conf->fournisseur->commande->dir_output . '/' . $newref;
823
                    if (!$error && file_exists($dirsource)) {
824
                        dol_syslog(get_only_class($this) . "::valid rename dir " . $dirsource . " into " . $dirdest);
825
826
                        if (@rename($dirsource, $dirdest)) {
827
                            dol_syslog("Rename ok");
828
                            // Rename docs starting with $oldref with $newref
829
                            $listoffiles = dol_dir_list($conf->fournisseur->commande->dir_output . '/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
830
                            foreach ($listoffiles as $fileentry) {
831
                                $dirsource = $fileentry['name'];
832
                                $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
833
                                $dirsource = $fileentry['path'] . '/' . $dirsource;
834
                                $dirdest = $fileentry['path'] . '/' . $dirdest;
835
                                @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

835
                                /** @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...
836
                            }
837
                        }
838
                    }
839
                }
840
            }
841
842
            if (!$error) {
843
                $result = 1;
844
                $this->status = self::STATUS_VALIDATED;
845
                $this->statut = self::STATUS_VALIDATED; // deprecated
846
                $this->ref = $num;
847
            }
848
849
            if (!$error) {
850
                $this->db->commit();
851
                return 1;
852
            } else {
853
                $this->db->rollback();
854
                return -1;
855
            }
856
        } else {
857
            $this->error = 'NotAuthorized';
858
            dol_syslog(get_only_class($this) . "::valid " . $this->error, LOG_ERR);
859
            return -1;
860
        }
861
    }
862
863
    /**
864
     *  Return label of the status of object
865
     *
866
     *  @param      int     $mode           0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=short label + picto
867
     *  @return     string                  Label
868
     */
869
    public function getLibStatut($mode = 0)
870
    {
871
        return $this->LibStatut($this->statut, $mode, $this->billed);
872
    }
873
874
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
875
    /**
876
     *  Return label of a status
877
     *
878
     *  @param  int     $status     Id statut
879
     *  @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
880
     *  @param  int     $billed     1=Billed
881
     *  @return string              Label of status
882
     */
883
    public function LibStatut($status, $mode = 0, $billed = 0)
884
    {
885
		// phpcs:enable
886
        global $langs, $hookmanager;
887
888
        if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
889
            $langs->load('orders');
890
891
            $this->labelStatus[0] = 'StatusSupplierOrderDraft';
892
            $this->labelStatus[1] = 'StatusSupplierOrderValidated';
893
            $this->labelStatus[2] = 'StatusSupplierOrderApproved';
894
            if (!getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
895
                $this->labelStatus[3] = 'StatusSupplierOrderOnProcess';
896
            } else {
897
                $this->labelStatus[3] = 'StatusSupplierOrderOnProcessWithValidation';
898
            }
899
            $this->labelStatus[4] = 'StatusSupplierOrderReceivedPartially';
900
            $this->labelStatus[5] = 'StatusSupplierOrderReceivedAll';
901
            $this->labelStatus[6] = 'StatusSupplierOrderCanceled'; // Approved->Canceled
902
            $this->labelStatus[7] = 'StatusSupplierOrderCanceled'; // Process running->canceled
903
            $this->labelStatus[9] = 'StatusSupplierOrderRefused';
904
905
            // List of language codes for status
906
            $this->labelStatusShort[0] = 'StatusSupplierOrderDraftShort';
907
            $this->labelStatusShort[1] = 'StatusSupplierOrderValidatedShort';
908
            $this->labelStatusShort[2] = 'StatusSupplierOrderApprovedShort';
909
            $this->labelStatusShort[3] = 'StatusSupplierOrderOnProcessShort';
910
            $this->labelStatusShort[4] = 'StatusSupplierOrderReceivedPartiallyShort';
911
            $this->labelStatusShort[5] = 'StatusSupplierOrderReceivedAllShort';
912
            $this->labelStatusShort[6] = 'StatusSupplierOrderCanceledShort';
913
            $this->labelStatusShort[7] = 'StatusSupplierOrderCanceledShort';
914
            $this->labelStatusShort[9] = 'StatusSupplierOrderRefusedShort';
915
        }
916
917
        $statustrans = array(
918
            0 => 'status0',
919
            1 => 'status1b',
920
            2 => 'status1',
921
            3 => 'status4',
922
            4 => 'status4b',
923
            5 => 'status6',
924
            6 => 'status9',
925
            7 => 'status9',
926
            9 => 'status9',
927
        );
928
929
        $statusClass = 'status0';
930
        if (!empty($statustrans[$status])) {
931
            $statusClass = $statustrans[$status];
932
        }
933
934
        $billedtext = '';
935
        if ($billed) {
936
            $billedtext = ' - ' . $langs->trans("Billed");
937
        }
938
        if ($status == 5 && $billed) {
939
            $statusClass = 'status6';
940
        }
941
942
        $statusLong = $langs->transnoentitiesnoconv($this->labelStatus[$status]) . $billedtext;
943
        $statusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
944
945
        $parameters = array('status' => $status, 'mode' => $mode, 'billed' => $billed);
946
        $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
947
        if ($reshook > 0) {
948
            return $hookmanager->resPrint;
949
        }
950
951
        return dolGetStatus($statusLong, $statusShort, '', $statusClass, $mode);
952
    }
953
954
    /**
955
     * getTooltipContentArray
956
     *
957
     * @param array $params ex option, infologin
958
     * @since v18
959
     * @return array
960
     */
961
    public function getTooltipContentArray($params)
962
    {
963
        global $conf, $langs, $user;
964
965
        $langs->loadLangs(['bills', 'orders']);
966
967
        $datas = [];
968
        $nofetch = !empty($params['nofetch']);
969
970
        if ($user->hasRight("fournisseur", "commande", "read")) {
971
            $datas['picto'] = '<u class="paddingrightonly">' . $langs->trans("SupplierOrder") . '</u>';
972
            if (isset($this->statut)) {
973
                $datas['picto'] .= ' ' . $this->getLibStatut(5);
974
            }
975
            if (!empty($this->ref)) {
976
                $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
977
            }
978
            if (!empty($this->ref_supplier)) {
979
                $datas['refsupplier'] = '<br><b>' . $langs->trans('RefSupplier') . ':</b> ' . $this->ref_supplier;
980
            }
981
            if (!$nofetch) {
982
                $langs->load('companies');
983
                if (empty($this->thirdparty)) {
984
                    $this->fetch_thirdparty();
985
                }
986
                $datas['supplier'] = '<br><b>' . $langs->trans('Supplier') . ':</b> ' . $this->thirdparty->getNomUrl(1, '', 0, 1);
987
            }
988
            if (!empty($this->total_ht)) {
989
                $datas['totalht'] = '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
990
            }
991
            if (!empty($this->total_tva)) {
992
                $datas['totaltva'] = '<br><b>' . $langs->trans('VAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
993
            }
994
            if (!empty($this->total_ttc)) {
995
                $datas['totalttc'] = '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
996
            }
997
            if (!empty($this->date)) {
998
                $datas['date'] = '<br><b>' . $langs->trans('Date') . ':</b> ' . dol_print_date($this->date, 'day');
999
            }
1000
            if (!empty($this->delivery_date)) {
1001
                $datas['deliverydate'] = '<br><b>' . $langs->trans('DeliveryDate') . ':</b> ' . dol_print_date($this->delivery_date, 'dayhour');
1002
            }
1003
        }
1004
        return $datas;
1005
    }
1006
1007
    /**
1008
     *  Return clicable name (with picto eventually)
1009
     *
1010
     *  @param      int     $withpicto                  0=No picto, 1=Include picto into link, 2=Only picto
1011
     *  @param      string  $option                     On what the link points
1012
     *  @param      int     $notooltip                  1=Disable tooltip
1013
     *  @param      int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1014
     *  @param      int     $addlinktonotes             Add link to show notes
1015
     *  @return     string                              Chain with URL
1016
     */
1017
    public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
1018
    {
1019
        global $langs, $user, $hookmanager;
1020
1021
        $result = '';
1022
        $params = [
1023
            'id' => $this->id,
1024
            'objecttype' => $this->element,
1025
            'option' => $option,
1026
            'nofetch' => 1
1027
        ];
1028
        $classfortooltip = 'classfortooltip';
1029
        $dataparams = '';
1030
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1031
            $classfortooltip = 'classforajaxtooltip';
1032
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1033
            $label = '';
1034
        } else {
1035
            $label = implode($this->getTooltipContentArray($params));
1036
        }
1037
1038
        $url = constant('BASE_URL') . '/fourn/commande/card.php?id=' . $this->id;
1039
1040
        if ($option !== 'nolink') {
1041
            // Add param to save lastsearch_values or not
1042
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1043
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1044
                $add_save_lastsearch_values = 1;
1045
            }
1046
            if ($add_save_lastsearch_values) {
1047
                $url .= '&save_lastsearch_values=1';
1048
            }
1049
        }
1050
1051
        $linkclose = '';
1052
        if (empty($notooltip)) {
1053
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1054
                $label = $langs->trans("ShowOrder");
1055
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1056
            }
1057
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1058
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
1059
        }
1060
1061
        $linkstart = '<a href="' . $url . '"';
1062
        $linkstart .= $linkclose . '>';
1063
        $linkend = '</a>';
1064
1065
        $result .= $linkstart;
1066
        if ($withpicto) {
1067
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1068
        }
1069
        if ($withpicto != 2) {
1070
            $result .= $this->ref;
1071
        }
1072
        $result .= $linkend;
1073
1074
        if ($addlinktonotes) {
1075
            $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
1076
            if ($txttoshow) {
1077
                $notetoshow = $langs->trans("ViewPrivateNote") . ':<br>' . dol_string_nohtmltag($txttoshow, 1);
1078
                $result .= ' <span class="note inline-block">';
1079
                $result .= '<a href="' . constant('BASE_URL') . '/fourn/commande/note.php?id=' . $this->id . '" class="classfortooltip" title="' . dol_escape_htmltag($notetoshow) . '">';
1080
                $result .= img_picto('', 'note');
1081
                $result .= '</a>';
1082
                //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1083
                //$result.='</a>';
1084
                $result .= '</span>';
1085
            }
1086
        }
1087
1088
        global $action;
1089
        $hookmanager->initHooks(array($this->element . 'dao'));
1090
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1091
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1092
        if ($reshook > 0) {
1093
            $result = $hookmanager->resPrint;
1094
        } else {
1095
            $result .= $hookmanager->resPrint;
1096
        }
1097
        return $result;
1098
    }
1099
1100
1101
    /**
1102
     *  Returns the next order reference not used, based on the
1103
     *  numbering model defined within COMMANDE_SUPPLIER_ADDON_NUMBER
1104
     *
1105
     *  @param      Societe     $soc        company object
1106
     *  @return     string|int              free reference for the invoice. '', -1 or -2 if error.
1107
     */
1108
    public function getNextNumRef($soc)
1109
    {
1110
        global $langs, $conf;
1111
        $langs->load("orders");
1112
1113
        if (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER')) {
1114
            $mybool = false;
1115
1116
            $file = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER') . '.php';
1117
            $classname = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER');
1118
1119
            // Include file with class
1120
            $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1121
1122
            foreach ($dirmodels as $reldir) {
1123
                $dir = dol_buildpath($reldir . "core/modules/supplier_order/");
1124
1125
                // Load file with numbering class (if found)
1126
                $mybool = ((bool) @include_once $dir . $file) || $mybool;
1127
            }
1128
1129
            if ($mybool === false) {
1130
                dol_print_error(null, "Failed to include file " . $file);
1131
                return '';
1132
            }
1133
1134
            $obj = new $classname();
1135
            $numref = $obj->getNextValue($soc, $this);
1136
1137
            if ($numref != "") {
1138
                return $numref;
1139
            } else {
1140
                $this->error = $obj->error;
1141
                return -1;
1142
            }
1143
        } else {
1144
            $this->error = "Error_COMMANDE_SUPPLIER_ADDON_NotDefined";
1145
            return -2;
1146
        }
1147
    }
1148
    /**
1149
     *  Class invoiced the supplier order
1150
     *
1151
     *  @param      User        $user       Object user making the change
1152
     *  @return     int                     Return integer <0 if KO, 0 if already billed,  >0 if OK
1153
     */
1154
    public function classifyBilled(User $user)
1155
    {
1156
        $error = 0;
1157
1158
        if ($this->billed) {
1159
            return 0;
1160
        }
1161
1162
        $this->db->begin();
1163
1164
        $sql = 'UPDATE ' . $this->db->prefix() . 'commande_fournisseur SET billed = 1';
1165
        $sql .= " WHERE rowid = " . ((int) $this->id) . ' AND fk_statut > ' . self::STATUS_DRAFT;
1166
1167
        if ($this->db->query($sql)) {
1168
            if (!$error) {
1169
                // Call trigger
1170
                $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_BILLED', $user);
1171
                if ($result < 0) {
1172
                    $error++;
1173
                }
1174
                // End call triggers
1175
            }
1176
1177
            if (!$error) {
1178
                $this->billed = 1;
1179
1180
                $this->db->commit();
1181
                return 1;
1182
            } else {
1183
                $this->db->rollback();
1184
                return -1;
1185
            }
1186
        } else {
1187
            dol_print_error($this->db);
1188
1189
            $this->db->rollback();
1190
            return -1;
1191
        }
1192
    }
1193
1194
    /**
1195
     *  Approve a supplier order
1196
     *
1197
     *  @param  User    $user           Object user
1198
     *  @param  int     $idwarehouse    Id of warhouse for stock change
1199
     *  @param  int     $secondlevel    0=Standard approval, 1=Second level approval (used when option SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set)
1200
     *  @return int                     Return integer <0 if KO, >0 if OK
1201
     */
1202
    public function approve($user, $idwarehouse = 0, $secondlevel = 0)
1203
    {
1204
        global $langs, $conf;
1205
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1206
1207
        $error = 0;
1208
1209
        dol_syslog(get_only_class($this) . "::approve");
1210
1211
        if ($user->hasRight("fournisseur", "commande", "approuver")) {
1212
            $now = dol_now();
1213
1214
            $this->db->begin();
1215
1216
            // Definition of order numbering model name
1217
            $soc = new Societe($this->db);
1218
            $soc->fetch($this->fourn_id);
1219
1220
            // Check if object has a temporary ref
1221
            if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1222
                $num = $this->getNextNumRef($soc);
1223
            } else {
1224
                $num = $this->ref;
1225
            }
1226
            $this->newref = dol_sanitizeFileName($num);
1227
1228
            // Do we have to change status now ? (If double approval is required and first approval, we keep status to 1 = validated)
1229
            $movetoapprovestatus = true;
1230
            $comment = '';
1231
1232
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
1233
            $sql .= " SET ref='" . $this->db->escape($num) . "',";
1234
            if (empty($secondlevel)) {  // standard or first level approval
1235
                $sql .= " date_approve='" . $this->db->idate($now) . "',";
1236
                $sql .= " fk_user_approve = " . $user->id;
1237
                if (getDolGlobalString('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED') && $this->total_ht >= $conf->global->SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED) {
1238
                    if (empty($this->user_approve_id2)) {
1239
                        $movetoapprovestatus = false; // second level approval not done
1240
                        $comment = ' (first level)';
1241
                    }
1242
                }
1243
            } else { // request a second level approval
1244
                $sql .= " date_approve2='" . $this->db->idate($now) . "',";
1245
                $sql .= " fk_user_approve2 = " . ((int) $user->id);
1246
                if (empty($this->user_approve_id)) {
1247
                    $movetoapprovestatus = false; // first level approval not done
1248
                }
1249
                $comment = ' (second level)';
1250
            }
1251
            // If double approval is required and first approval, we keep status to 1 = validated
1252
            if ($movetoapprovestatus) {
1253
                $sql .= ", fk_statut = " . self::STATUS_ACCEPTED;
1254
            } else {
1255
                $sql .= ", fk_statut = " . self::STATUS_VALIDATED;
1256
            }
1257
            $sql .= " WHERE rowid = " . ((int) $this->id);
1258
            $sql .= " AND fk_statut = " . self::STATUS_VALIDATED;
1259
1260
            if ($this->db->query($sql)) {
1261
                if (getDolGlobalString('SUPPLIER_ORDER_AUTOADD_USER_CONTACT')) {
1262
                    $result = $this->add_contact($user->id, 'SALESREPFOLL', 'internal', 1);
1263
                    if ($result < 0 && $result != -2) { // -2 means already exists
1264
                        $error++;
1265
                    }
1266
                }
1267
1268
                // If stock is incremented on validate order, we must increment it
1269
                if (!$error && $movetoapprovestatus && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
1270
                    $langs->load("agenda");
1271
1272
                    $cpt = count($this->lines);
1273
                    for ($i = 0; $i < $cpt; $i++) {
1274
                        // Product with reference
1275
                        if ($this->lines[$i]->fk_product > 0) {
1276
                            $this->line = $this->lines[$i];
1277
                            $mouvP = new MouvementStock($this->db);
1278
                            $mouvP->origin = &$this;
1279
                            $mouvP->setOrigin($this->element, $this->id);
1280
                            // We decrement stock of product (and sub-products)
1281
                            $up_ht_disc = $this->lines[$i]->subprice;
1282
                            if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1283
                                $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1284
                            }
1285
                            $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("OrderApprovedInDolibarr", $this->ref));
1286
                            if ($result < 0) {
1287
                                $error++;
1288
                            }
1289
                            unset($this->line);
1290
                        }
1291
                    }
1292
                }
1293
1294
                if (!$error) {
1295
                    // Call trigger
1296
                    $result = $this->call_trigger('ORDER_SUPPLIER_APPROVE', $user);
1297
                    if ($result < 0) {
1298
                        $error++;
1299
                    }
1300
                    // End call triggers
1301
                }
1302
1303
                if (!$error) {
1304
                    $this->ref = $this->newref;
1305
1306
                    if ($movetoapprovestatus) {
1307
                        $this->statut = self::STATUS_ACCEPTED;
1308
                    } else {
1309
                        $this->statut = self::STATUS_VALIDATED;
1310
                    }
1311
                    if (empty($secondlevel)) {  // standard or first level approval
1312
                        $this->date_approve = $now;
1313
                        $this->user_approve_id = $user->id;
1314
                    } else { // request a second level approval
1315
                        $this->date_approve2 = $now;
1316
                        $this->user_approve_id2 = $user->id;
1317
                    }
1318
1319
                    $this->db->commit();
1320
                    return 1;
1321
                } else {
1322
                    $this->db->rollback();
1323
                    return -1;
1324
                }
1325
            } else {
1326
                $this->db->rollback();
1327
                $this->error = $this->db->lasterror();
1328
                return -1;
1329
            }
1330
        } else {
1331
            dol_syslog(get_only_class($this) . "::approve Not Authorized", LOG_ERR);
1332
        }
1333
        return -1;
1334
    }
1335
1336
    /**
1337
     *  Refuse an order
1338
     *
1339
     *  @param      User    $user       User making action
1340
     *  @return     int                 0 if Ok, <0 if Ko
1341
     */
1342
    public function refuse($user)
1343
    {
1344
        global $conf, $langs;
1345
1346
        $error = 0;
1347
1348
        dol_syslog(get_only_class($this) . "::refuse");
1349
        $result = 0;
1350
        if ($user->hasRight("fournisseur", "commande", "approuver")) {
1351
            $this->db->begin();
1352
1353
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur SET fk_statut = " . self::STATUS_REFUSED;
1354
            $sql .= " WHERE rowid = " . ((int) $this->id);
1355
1356
            if ($this->db->query($sql)) {
1357
                $result = 0;
1358
1359
                if ($error == 0) {
1360
                    // Call trigger
1361
                    $result = $this->call_trigger('ORDER_SUPPLIER_REFUSE', $user);
1362
                    if ($result < 0) {
1363
                        $error++;
1364
                        $this->db->rollback();
1365
                    } else {
1366
                        $this->db->commit();
1367
                    }
1368
                    // End call triggers
1369
                }
1370
            } else {
1371
                $this->db->rollback();
1372
                $this->error = $this->db->lasterror();
1373
                dol_syslog(get_only_class($this) . "::refuse Error -1");
1374
                $result = -1;
1375
            }
1376
        } else {
1377
            dol_syslog(get_only_class($this) . "::refuse Not Authorized");
1378
        }
1379
        return $result;
1380
    }
1381
1382
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1383
    /**
1384
     *  Cancel an approved order.
1385
     *  The cancellation is done after approval
1386
     *
1387
     *  @param  User    $user           User making action
1388
     *  @param  int     $idwarehouse    Id warehouse to use for stock change (not used for supplier orders).
1389
     *  @return int                     >0 if Ok, <0 if Ko
1390
     */
1391
    public function Cancel($user, $idwarehouse = -1)
1392
    {
1393
		// phpcs:enable
1394
        global $langs, $conf;
1395
1396
        $error = 0;
1397
1398
        //dol_syslog("CommandeFournisseur::Cancel");
1399
        $result = 0;
1400
        if ($user->hasRight("fournisseur", "commande", "commander")) {
1401
            $statut = self::STATUS_CANCELED;
1402
1403
            $this->db->begin();
1404
1405
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur SET fk_statut = " . ((int) $statut);
1406
            $sql .= " WHERE rowid = " . ((int) $this->id);
1407
            dol_syslog(get_only_class($this) . "::cancel", LOG_DEBUG);
1408
            if ($this->db->query($sql)) {
1409
                $result = 0;
1410
1411
                // Call trigger
1412
                $result = $this->call_trigger('ORDER_SUPPLIER_CANCEL', $user);
1413
                if ($result < 0) {
1414
                    $error++;
1415
                }
1416
                // End call triggers
1417
1418
                if ($error == 0) {
1419
                    $this->db->commit();
1420
                    return 1;
1421
                } else {
1422
                    $this->db->rollback();
1423
                    return -1;
1424
                }
1425
            } else {
1426
                $this->db->rollback();
1427
                $this->error = $this->db->lasterror();
1428
                dol_syslog(get_only_class($this) . "::cancel " . $this->error);
1429
                return -1;
1430
            }
1431
        } else {
1432
            dol_syslog(get_only_class($this) . "::cancel Not Authorized");
1433
            return -1;
1434
        }
1435
    }
1436
1437
    /**
1438
     *  Submit a supplier order to supplier
1439
     *
1440
     *  @param      User    $user       User making change
1441
     *  @param      integer $date       Date
1442
     *  @param      int     $methode    Method
1443
     *  @param      string  $comment    Comment
1444
     *  @return     int                 Return integer <0 if KO, >0 if OK
1445
     */
1446
    public function commande($user, $date, $methode, $comment = '')
1447
    {
1448
        global $langs;
1449
        dol_syslog(get_only_class($this) . "::commande");
1450
        $error = 0;
1451
        if ($user->hasRight("fournisseur", "commande", "commander")) {
1452
            $this->db->begin();
1453
1454
            $newnoteprivate = $this->note_private;
1455
            if ($comment) {
1456
                $newnoteprivate = dol_concatdesc($newnoteprivate, $langs->trans("Comment") . ': ' . $comment);
1457
            }
1458
1459
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
1460
            $sql .= " SET fk_statut=" . self::STATUS_ORDERSENT . ", fk_input_method=" . $methode . ", date_commande='" . $this->db->idate($date) . "', ";
1461
            $sql .= " note_private='" . $this->db->escape($newnoteprivate) . "'";
1462
            $sql .= " WHERE rowid=" . ((int) $this->id);
1463
1464
            dol_syslog(get_only_class($this) . "::commande", LOG_DEBUG);
1465
            if ($this->db->query($sql)) {
1466
                $this->statut = self::STATUS_ORDERSENT;
1467
                $this->methode_commande_id = $methode;
1468
                $this->date_commande = $date;
1469
                $this->context = array('comments' => $comment);
1470
1471
                // Call trigger
1472
                $result = $this->call_trigger('ORDER_SUPPLIER_SUBMIT', $user);
1473
                if ($result < 0) {
1474
                    $error++;
1475
                }
1476
                // End call triggers
1477
            } else {
1478
                $error++;
1479
                $this->error = $this->db->lasterror();
1480
                $this->errors[] = $this->db->lasterror();
1481
            }
1482
1483
            if (!$error) {
1484
                $this->db->commit();
1485
            } else {
1486
                $this->db->rollback();
1487
            }
1488
        } else {
1489
            $error++;
1490
            $this->error = $langs->trans('NotAuthorized');
1491
            $this->errors[] = $langs->trans('NotAuthorized');
1492
            dol_syslog(get_only_class($this) . "::commande User not Authorized", LOG_WARNING);
1493
        }
1494
1495
        return ($error ? -1 : 1);
1496
    }
1497
1498
    /**
1499
     *  Create order with draft status
1500
     *
1501
     *  @param      User    $user       User making creation
1502
     *  @param      int     $notrigger  Disable all triggers
1503
     *  @return     int                 Return integer <0 if KO, Id of supplier order if OK
1504
     */
1505
    public function create($user, $notrigger = 0)
1506
    {
1507
        global $langs, $conf, $hookmanager;
1508
1509
        $this->db->begin();
1510
1511
        $error = 0;
1512
        $now = dol_now();
1513
1514
        // set tmp vars
1515
        $date = ($this->date_commande ? $this->date_commande : $this->date); // in case of date is set
1516
        if (empty($date)) {
1517
            $date = $now;
1518
        }
1519
        $delivery_date = $this->delivery_date;
1520
1521
        // Clean parameters
1522
        if (empty($this->source)) {
1523
            $this->source = 0;
1524
        }
1525
1526
        // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1527
        if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1528
            list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
1529
        } else {
1530
            $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1531
        }
1532
        if (empty($this->fk_multicurrency)) {
1533
            $this->multicurrency_code = $conf->currency;
1534
            $this->fk_multicurrency = 0;
1535
            $this->multicurrency_tx = 1;
1536
        }
1537
1538
        $this->statut = self::STATUS_DRAFT; // deprecated
1539
        $this->status = self::STATUS_DRAFT;
1540
1541
        $sql = "INSERT INTO " . $this->db->prefix() . "commande_fournisseur (";
1542
        $sql .= "ref";
1543
        $sql .= ", ref_supplier";
1544
        $sql .= ", note_private";
1545
        $sql .= ", note_public";
1546
        $sql .= ", entity";
1547
        $sql .= ", fk_soc";
1548
        $sql .= ", fk_projet";
1549
        $sql .= ", date_creation";
1550
        $sql .= ", date_livraison";
1551
        $sql .= ", fk_user_author";
1552
        $sql .= ", fk_statut";
1553
        $sql .= ", source";
1554
        $sql .= ", model_pdf";
1555
        $sql .= ", fk_mode_reglement";
1556
        $sql .= ", fk_cond_reglement";
1557
        $sql .= ", fk_account";
1558
        $sql .= ", fk_incoterms, location_incoterms";
1559
        $sql .= ", fk_multicurrency";
1560
        $sql .= ", multicurrency_code";
1561
        $sql .= ", multicurrency_tx";
1562
        $sql .= ") ";
1563
        $sql .= " VALUES (";
1564
        $sql .= "'(PROV)'";
1565
        $sql .= ", " . (isset($this->ref_supplier) ? "'" . $this->db->escape($this->ref_supplier) . "'" : "NULL");
1566
        $sql .= ", '" . $this->db->escape($this->note_private) . "'";
1567
        $sql .= ", '" . $this->db->escape($this->note_public) . "'";
1568
        $sql .= ", " . setEntity($this);
1569
        $sql .= ", " . ((int) $this->socid);
1570
        $sql .= ", " . ($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1571
        $sql .= ", '" . $this->db->idate($date) . "'";
1572
        $sql .= ", " . ($delivery_date ? "'" . $this->db->idate($delivery_date) . "'" : "null");
1573
        $sql .= ", " . ((int) $user->id);
1574
        $sql .= ", " . self::STATUS_DRAFT;
1575
        $sql .= ", " . ((int) $this->source);
1576
        $sql .= ", '" . $this->db->escape(getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) . "'";
1577
        $sql .= ", " . ($this->mode_reglement_id > 0 ? $this->mode_reglement_id : 'null');
1578
        $sql .= ", " . ($this->cond_reglement_id > 0 ? $this->cond_reglement_id : 'null');
1579
        $sql .= ", " . ($this->fk_account > 0 ? $this->fk_account : 'NULL');
1580
        $sql .= ", " . (int) $this->fk_incoterms;
1581
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
1582
        $sql .= ", " . (int) $this->fk_multicurrency;
1583
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
1584
        $sql .= ", " . (float) $this->multicurrency_tx;
1585
        $sql .= ")";
1586
1587
        dol_syslog(get_only_class($this) . "::create", LOG_DEBUG);
1588
        if ($this->db->query($sql)) {
1589
            $this->id = $this->db->last_insert_id($this->db->prefix() . "commande_fournisseur");
1590
1591
            if ($this->id) {
1592
                $num = count($this->lines);
1593
1594
                // insert products details into database
1595
                for ($i = 0; $i < $num; $i++) {
1596
                    $line = $this->lines[$i];
1597
                    if (!is_object($line)) {
1598
                        $line = (object) $line;
1599
                    }
1600
1601
                    //$this->special_code = $line->special_code; // TODO : remove this in 9.0 and add special_code param to addline()
1602
1603
                    // This include test on qty if option SUPPLIER_ORDER_WITH_NOPRICEDEFINED is not set
1604
                    $result = $this->addline(
1605
                        $line->desc,
1606
                        $line->subprice,
1607
                        $line->qty,
1608
                        $line->tva_tx,
1609
                        $line->localtax1_tx,
1610
                        $line->localtax2_tx,
1611
                        $line->fk_product,
1612
                        0,
1613
                        $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
1614
                        $line->remise_percent,
1615
                        'HT',
1616
                        0,
1617
                        $line->product_type,
1618
                        $line->info_bits,
1619
                        false,
1620
                        $line->date_start,
1621
                        $line->date_end,
1622
                        $line->array_options,
1623
                        $line->fk_unit,
1624
                        $line->multicurrency_subprice,  // pu_ht_devise
1625
                        $line->origin,     // origin
1626
                        $line->origin_id,  // origin_id
1627
                        $line->rang,       // rang
1628
                        $line->special_code
1629
                    );
1630
                    if ($result < 0) {
1631
                        dol_syslog(get_only_class($this) . "::create " . $this->error, LOG_WARNING); // do not use dol_print_error here as it may be a functional error
1632
                        $this->db->rollback();
1633
                        return -1;
1634
                    }
1635
                }
1636
1637
                $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
1638
                $sql .= " SET ref='(PROV" . $this->id . ")'";
1639
                $sql .= " WHERE rowid=" . ((int) $this->id);
1640
1641
                dol_syslog(get_only_class($this) . "::create", LOG_DEBUG);
1642
                if ($this->db->query($sql)) {
1643
                    // Add link with price request and supplier order
1644
                    if ($this->id) {
1645
                        $this->ref = "(PROV" . $this->id . ")";
1646
1647
                        if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {  // To use new linkedObjectsIds instead of old linked_objects
1648
                            $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1649
                        }
1650
1651
                        // Add object linked
1652
                        if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1653
                            foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1654
                                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, ...))
1655
                                    foreach ($tmp_origin_id as $origin_id) {
1656
                                        $ret = $this->add_object_linked($origin, $origin_id);
1657
                                        if (!$ret) {
1658
                                            dol_print_error($this->db);
1659
                                            $error++;
1660
                                        }
1661
                                    }
1662
                                } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1663
                                    $origin_id = $tmp_origin_id;
1664
                                    $ret = $this->add_object_linked($origin, $origin_id);
1665
                                    if (!$ret) {
1666
                                        dol_print_error($this->db);
1667
                                        $error++;
1668
                                    }
1669
                                }
1670
                            }
1671
                        }
1672
                    }
1673
1674
                    if (!$error) {
1675
                        $result = $this->insertExtraFields();
1676
                        if ($result < 0) {
1677
                            $error++;
1678
                        }
1679
                    }
1680
1681
                    if (!$error && !$notrigger) {
1682
                        // Call trigger
1683
                        $result = $this->call_trigger('ORDER_SUPPLIER_CREATE', $user);
1684
                        if ($result < 0) {
1685
                            $this->db->rollback();
1686
1687
                            return -1;
1688
                        }
1689
                        // End call triggers
1690
                    }
1691
1692
                    $this->db->commit();
1693
                    return $this->id;
1694
                } else {
1695
                    $this->error = $this->db->lasterror();
1696
                    $this->db->rollback();
1697
1698
                    return -2;
1699
                }
1700
            } else {
1701
                $this->error = 'Failed to get ID of inserted line';
1702
1703
                return -1;
1704
            }
1705
        } else {
1706
            $this->error = $this->db->lasterror();
1707
            $this->db->rollback();
1708
1709
            return -1;
1710
        }
1711
    }
1712
1713
    /**
1714
     *  Update Supplier Order
1715
     *
1716
     *  @param      User    $user           User that modify
1717
     *  @param      int     $notrigger      0=launch triggers after, 1=disable triggers
1718
     *  @return     int                     Return integer <0 if KO, >0 if OK
1719
     */
1720
    public function update(User $user, $notrigger = 0)
1721
    {
1722
        global $conf;
1723
1724
        $error = 0;
1725
1726
        // Clean parameters
1727
        if (isset($this->ref)) {
1728
            $this->ref = trim($this->ref);
1729
        }
1730
        if (isset($this->ref_supplier)) {
1731
            $this->ref_supplier = trim($this->ref_supplier);
1732
        }
1733
        if (isset($this->note_private)) {
1734
            $this->note_private = trim($this->note_private);
1735
        }
1736
        if (isset($this->note_public)) {
1737
            $this->note_public = trim($this->note_public);
1738
        }
1739
        if (isset($this->model_pdf)) {
1740
            $this->model_pdf = trim($this->model_pdf);
1741
        }
1742
        if (isset($this->import_key)) {
1743
            $this->import_key = trim($this->import_key);
1744
        }
1745
1746
        // Update request
1747
        $sql = "UPDATE " . $this->db->prefix() . $this->table_element . " SET";
1748
1749
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
1750
        $sql .= " ref_supplier=" . (isset($this->ref_supplier) ? "'" . $this->db->escape($this->ref_supplier) . "'" : "null") . ",";
1751
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
1752
        $sql .= " fk_soc=" . (isset($this->socid) ? $this->socid : "null") . ",";
1753
        $sql .= " date_commande=" . (strval($this->date_commande) != '' ? "'" . $this->db->idate($this->date_commande) . "'" : 'null') . ",";
1754
        $sql .= " date_valid=" . (strval($this->date_validation) != '' ? "'" . $this->db->idate($this->date_validation) . "'" : 'null') . ",";
1755
        $sql .= " total_tva=" . (isset($this->total_tva) ? $this->total_tva : "null") . ",";
1756
        $sql .= " localtax1=" . (isset($this->total_localtax1) ? $this->total_localtax1 : "null") . ",";
1757
        $sql .= " localtax2=" . (isset($this->total_localtax2) ? $this->total_localtax2 : "null") . ",";
1758
        $sql .= " total_ht=" . (isset($this->total_ht) ? $this->total_ht : "null") . ",";
1759
        $sql .= " total_ttc=" . (isset($this->total_ttc) ? $this->total_ttc : "null") . ",";
1760
        $sql .= " fk_statut=" . (isset($this->statut) ? $this->statut : "null") . ",";
1761
        $sql .= " fk_user_author=" . (isset($this->user_author_id) ? $this->user_author_id : "null") . ",";
1762
        $sql .= " fk_user_valid=" . (isset($this->user_validation_id) && $this->user_validation_id > 0 ? $this->user_validation_id : "null") . ",";
1763
        $sql .= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
1764
        $sql .= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null") . ",";
1765
        $sql .= " fk_mode_reglement=" . (isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null") . ",";
1766
        $sql .= " date_livraison=" . (strval($this->delivery_date) != '' ? "'" . $this->db->idate($this->delivery_date) . "'" : 'null') . ",";
1767
        //$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
1768
        $sql .= " fk_account=" . ($this->fk_account > 0 ? $this->fk_account : "null") . ",";
1769
        //$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
1770
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
1771
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
1772
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
1773
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null");
1774
1775
        $sql .= " WHERE rowid=" . ((int) $this->id);
1776
1777
        $this->db->begin();
1778
1779
        dol_syslog(get_only_class($this) . "::update", LOG_DEBUG);
1780
        $resql = $this->db->query($sql);
1781
        if (!$resql) {
1782
            $error++;
1783
            $this->errors[] = "Error " . $this->db->lasterror();
1784
        }
1785
1786
        if (!$error) {
1787
            $result = $this->insertExtraFields();
1788
            if ($result < 0) {
1789
                $error++;
1790
            }
1791
        }
1792
1793
        if (!$error && !$notrigger) {
1794
            // Call trigger
1795
            $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
1796
            if ($result < 0) {
1797
                $error++;
1798
            }
1799
            // End call triggers
1800
        }
1801
1802
        // Commit or rollback
1803
        if ($error) {
1804
            foreach ($this->errors as $errmsg) {
1805
                dol_syslog(get_only_class($this) . "::update " . $errmsg, LOG_ERR);
1806
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1807
            }
1808
            $this->db->rollback();
1809
            return -1 * $error;
1810
        } else {
1811
            $this->db->commit();
1812
            return 1;
1813
        }
1814
    }
1815
1816
    /**
1817
     *  Load an object from its id and create a new one in database
1818
     *
1819
     *  @param      User    $user       User making the clone
1820
     *  @param      int     $socid      Id of thirdparty
1821
     *  @param      int     $notrigger  Disable all triggers
1822
     *  @return     int                 New id of clone
1823
     */
1824
    public function createFromClone(User $user, $socid = 0, $notrigger = 0)
1825
    {
1826
        global $conf, $user, $hookmanager;
1827
1828
        $error = 0;
1829
1830
        $this->db->begin();
1831
1832
        // get extrafields so they will be clone
1833
        foreach ($this->lines as $line) {
1834
            $line->fetch_optionals();
1835
        }
1836
1837
        // Load source object
1838
        $objFrom = clone $this;
1839
1840
        // Change socid if needed
1841
        if (!empty($socid) && $socid != $this->socid) {
1842
            $objsoc = new Societe($this->db);
1843
1844
            if ($objsoc->fetch($socid) > 0) {
1845
                $this->socid = $objsoc->id;
1846
                $this->cond_reglement_id    = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1847
                $this->mode_reglement_id    = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1848
                $this->fk_project = 0;
1849
                $this->fk_delivery_address = 0;
1850
            }
1851
1852
            // TODO Change product price if multi-prices
1853
        }
1854
1855
        $this->id = 0;
1856
        $this->statut = self::STATUS_DRAFT;
1857
1858
        // Clear fields
1859
        $this->user_author_id     = $user->id;
1860
        $this->user_validation_id = 0;
1861
1862
        $this->date               = dol_now();
1863
        $this->date_creation      = 0;
1864
        $this->date_validation    = 0;
1865
        $this->date_commande      = 0;
1866
        $this->ref_supplier       = '';
1867
        $this->user_approve_id    = 0;
1868
        $this->user_approve_id2   = 0;
1869
        $this->date_approve       = 0;
1870
        $this->date_approve2      = 0;
1871
1872
        // Create clone
1873
        $this->context['createfromclone'] = 'createfromclone';
1874
        $result = $this->create($user, $notrigger);
1875
        if ($result < 0) {
1876
            $error++;
1877
        }
1878
1879
        if (!$error) {
1880
            // Hook of thirdparty module
1881
            if (is_object($hookmanager)) {
1882
                $parameters = array('objFrom' => $objFrom);
1883
                $action = '';
1884
                $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1885
                if ($reshook < 0) {
1886
                    $this->setErrorsFromObject($hookmanager);
1887
                    $error++;
1888
                }
1889
            }
1890
        }
1891
1892
        unset($this->context['createfromclone']);
1893
1894
        // End
1895
        if (!$error) {
1896
            $this->db->commit();
1897
            return $this->id;
1898
        } else {
1899
            $this->db->rollback();
1900
            return -1;
1901
        }
1902
    }
1903
1904
    /**
1905
     *  Add order line
1906
     *
1907
     *  @param      string      $desc                   Description
1908
     *  @param      float       $pu_ht                  Unit price (used if $price_base_type is 'HT')
1909
     *  @param      float       $qty                    Quantity
1910
     *  @param      float       $txtva                  VAT Rate
1911
     *  @param      float       $txlocaltax1            Localtax1 tax
1912
     *  @param      float       $txlocaltax2            Localtax2 tax
1913
     *  @param      int         $fk_product             Id product
1914
     *  @param      int         $fk_prod_fourn_price    Id supplier price
1915
     *  @param      string      $ref_supplier           Supplier reference price
1916
     *  @param      float       $remise_percent         Remise
1917
     *  @param      string      $price_base_type        HT or TTC
1918
     *  @param      float       $pu_ttc                 Unit price TTC (used if $price_base_type is 'TTC')
1919
     *  @param      int         $type                   Type of line (0=product, 1=service)
1920
     *  @param      int         $info_bits              More information
1921
     *  @param      int         $notrigger              Disable triggers
1922
     *  @param      int         $date_start             Date start of service
1923
     *  @param      int         $date_end               Date end of service
1924
     *  @param      array       $array_options          extrafields array
1925
     *  @param      int|null    $fk_unit                Code of the unit to use. Null to use the default one
1926
     *  @param      int|string      $pu_ht_devise           Amount in currency
1927
     *  @param      string      $origin                 'order', ...
1928
     *  @param      int         $origin_id              Id of origin object
1929
     *  @param      int         $rang                   Rank
1930
     *  @param      int         $special_code           Special code
1931
     *  @return     int                                 Return integer <=0 if KO, >0 if OK
1932
     */
1933
    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)
1934
    {
1935
        global $langs, $mysoc, $conf;
1936
1937
        dol_syslog(get_only_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");
1938
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1939
1940
        if ($this->statut == self::STATUS_DRAFT) {
1941
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1942
1943
            // Clean parameters
1944
            if (empty($qty)) {
1945
                $qty = 0;
1946
            }
1947
            if (!$info_bits) {
1948
                $info_bits = 0;
1949
            }
1950
            if (empty($txtva)) {
1951
                $txtva = 0;
1952
            }
1953
            if (empty($rang)) {
1954
                $rang = 0;
1955
            }
1956
            if (empty($txlocaltax1)) {
1957
                $txlocaltax1 = 0;
1958
            }
1959
            if (empty($txlocaltax2)) {
1960
                $txlocaltax2 = 0;
1961
            }
1962
            if (empty($remise_percent)) {
1963
                $remise_percent = 0;
1964
            }
1965
1966
            $remise_percent = price2num($remise_percent);
1967
            $qty = price2num($qty);
1968
            $pu_ht = price2num($pu_ht);
1969
            $pu_ht_devise = price2num($pu_ht_devise);
1970
            $pu_ttc = price2num($pu_ttc);
1971
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
1972
                $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
1973
            }
1974
            $txlocaltax1 = price2num($txlocaltax1);
1975
            $txlocaltax2 = price2num($txlocaltax2);
1976
            if ($price_base_type == 'HT') {
1977
                $pu = $pu_ht;
1978
            } else {
1979
                $pu = $pu_ttc;
1980
            }
1981
            $desc = trim($desc);
1982
1983
            // Check parameters
1984
            if ($qty < 0 && !$fk_product) {
1985
                $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product"));
1986
                return -1;
1987
            }
1988
            if ($type < 0) {
1989
                return -1;
1990
            }
1991
            if ($date_start && $date_end && $date_start > $date_end) {
1992
                $langs->load("errors");
1993
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1994
                return -1;
1995
            }
1996
1997
1998
            $this->db->begin();
1999
2000
            $product_type = $type;
2001
            $label = '';    // deprecated
2002
2003
            if ($fk_product > 0) {
2004
                if (getDolGlobalString('SUPPLIER_ORDER_WITH_PREDEFINED_PRICES_ONLY')) { // Not the common case
2005
                    // Check quantity is enough
2006
                    dol_syslog(get_only_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);
2007
                    $prod = new ProductFournisseur($this->db);
2008
                    if ($prod->fetch($fk_product) > 0) {
2009
                        $product_type = $prod->type;
2010
                        $label = $prod->label;
2011
2012
                        // We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
2013
                        // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2014
                        $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
2015
2016
                        // If supplier order created from sales order, we take best supplier price
2017
                        // If $pu (defined previously from pu_ht or pu_ttc) is not defined at all, we also take the best supplier price
2018
                        if ($result > 0 && ($origin == 'commande' || $pu === '')) {
2019
                            $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2020
                            $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2021
                            // is remise percent not keyed but present for the product we add it
2022
                            if ($remise_percent == 0 && $prod->remise_percent != 0) {
2023
                                $remise_percent = $prod->remise_percent;
2024
                            }
2025
                        }
2026
                        if ($result == 0) {                   // If result == 0, we failed to found the supplier reference price
2027
                            $langs->load("errors");
2028
                            $this->error = "Ref " . $prod->ref . " " . $langs->trans("ErrorQtyTooLowForThisSupplier");
2029
                            $this->db->rollback();
2030
                            dol_syslog(get_only_class($this) . "::addline we did not found supplier price, so we can't guess unit price");
2031
                            //$pu    = $prod->fourn_pu;     // We do not overwrite unit price
2032
                            //$ref   = $prod->ref_fourn;    // We do not overwrite ref supplier price
2033
                            return -1;
2034
                        }
2035
                        if ($result == -1) {
2036
                            $langs->load("errors");
2037
                            $this->error = "Ref " . $prod->ref . " " . $langs->trans("ErrorQtyTooLowForThisSupplier");
2038
                            $this->db->rollback();
2039
                            dol_syslog(get_only_class($this) . "::addline result=" . $result . " - " . $this->error, LOG_DEBUG);
2040
                            return -1;
2041
                        }
2042
                        if ($result < -1) {
2043
                            $this->error = $prod->error;
2044
                            $this->db->rollback();
2045
                            dol_syslog(get_only_class($this) . "::addline result=" . $result . " - " . $this->error, LOG_ERR);
2046
                            return -1;
2047
                        }
2048
                    } else {
2049
                        $this->error = $prod->error;
2050
                        $this->db->rollback();
2051
                        return -1;
2052
                    }
2053
                }
2054
2055
                // Predefine quantity according to packaging
2056
                if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2057
                    $prod = new Product($this->db);
2058
                    $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc));
2059
2060
                    if ($qty < $prod->packaging) {
2061
                        $qty = $prod->packaging;
2062
                    } else {
2063
                        if (!empty($prod->packaging) && ($qty % $prod->packaging) > 0) {
2064
                            $coeff = intval($qty / $prod->packaging) + 1;
2065
                            $qty = $prod->packaging * $coeff;
2066
                            setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
2067
                        }
2068
                    }
2069
                }
2070
            }
2071
2072
            if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
2073
                $pu = 0;
2074
            }
2075
2076
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2077
2078
            // Clean vat code
2079
            $reg = array();
2080
            $vat_src_code = '';
2081
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
2082
                $vat_src_code = $reg[1];
2083
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
2084
            }
2085
2086
            // Calcul du total TTC et de la TVA pour la ligne a partir de
2087
            // qty, pu, remise_percent et txtva
2088
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2089
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2090
2091
            $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);
2092
2093
            $total_ht  = $tabprice[0];
2094
            $total_tva = $tabprice[1];
2095
            $total_ttc = $tabprice[2];
2096
            $total_localtax1 = $tabprice[9];
2097
            $total_localtax2 = $tabprice[10];
2098
            $pu = $pu_ht = $tabprice[3];
2099
2100
            // MultiCurrency
2101
            $multicurrency_total_ht = $tabprice[16];
2102
            $multicurrency_total_tva = $tabprice[17];
2103
            $multicurrency_total_ttc = $tabprice[18];
2104
            $pu_ht_devise = $tabprice[19];
2105
2106
            $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2107
            $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2108
2109
            if ($rang < 0) {
2110
                $rangmax = $this->line_max();
2111
                $rang = $rangmax + 1;
2112
            }
2113
2114
            // Insert line
2115
            $this->line = new CommandeFournisseurLigne($this->db);
2116
2117
            $this->line->context = $this->context;
2118
2119
            $this->line->fk_commande = $this->id;
2120
            $this->line->label = $label;
2121
            $this->line->ref_fourn = $ref_supplier;
2122
            $this->line->ref_supplier = $ref_supplier;
2123
            $this->line->desc = $desc;
2124
            $this->line->qty = $qty;
2125
            $this->line->tva_tx = $txtva;
2126
            $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2127
            $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2128
            $this->line->localtax1_type = $localtax1_type;
2129
            $this->line->localtax2_type = $localtax2_type;
2130
            $this->line->fk_product = $fk_product;
2131
            $this->line->product_type = $product_type;
2132
            $this->line->remise_percent = $remise_percent;
2133
            $this->line->subprice = $pu_ht;
2134
            $this->line->rang = $rang;
2135
            $this->line->info_bits = $info_bits;
2136
2137
            $this->line->vat_src_code = $vat_src_code;
2138
            $this->line->total_ht = $total_ht;
2139
            $this->line->total_tva = $total_tva;
2140
            $this->line->total_localtax1 = $total_localtax1;
2141
            $this->line->total_localtax2 = $total_localtax2;
2142
            $this->line->total_ttc = $total_ttc;
2143
            $this->line->product_type = $type;
2144
            $this->line->special_code   = (!empty($special_code) ? $special_code : 0);
2145
            $this->line->origin = $origin;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

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

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

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

Loading history...
2146
            $this->line->origin_id = $origin_id;
2147
            $this->line->fk_unit = $fk_unit;
2148
2149
            $this->line->date_start = $date_start;
2150
            $this->line->date_end = $date_end;
2151
2152
            // Multicurrency
2153
            $this->line->fk_multicurrency = $this->fk_multicurrency;
2154
            $this->line->multicurrency_code = $this->multicurrency_code;
2155
            $this->line->multicurrency_subprice = $pu_ht_devise;
2156
            $this->line->multicurrency_total_ht     = $multicurrency_total_ht;
2157
            $this->line->multicurrency_total_tva    = $multicurrency_total_tva;
2158
            $this->line->multicurrency_total_ttc    = $multicurrency_total_ttc;
2159
2160
            $this->line->subprice = $pu_ht;
2161
            $this->line->price = $this->line->subprice;
2162
2163
            $this->line->remise_percent = $remise_percent;
2164
2165
            if (is_array($array_options) && count($array_options) > 0) {
2166
                $this->line->array_options = $array_options;
2167
            }
2168
2169
            $result = $this->line->insert($notrigger);
2170
            if ($result > 0) {
2171
                // Reorder if child line
2172
                if (!empty($this->line->fk_parent_line)) {
2173
                    $this->line_order(true, 'DESC');
2174
                } elseif ($rang > 0 && $rang <= count($this->lines)) { // Update all rank of all other lines
2175
                    $linecount = count($this->lines);
2176
                    for ($ii = $rang; $ii <= $linecount; $ii++) {
2177
                        $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2178
                    }
2179
                }
2180
2181
                // Mise a jour information denormalisees au niveau de la commande meme
2182
                $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.
2183
                if ($result > 0) {
2184
                    $this->db->commit();
2185
                    return $this->line->id;
2186
                } else {
2187
                    $this->db->rollback();
2188
                    return -1;
2189
                }
2190
            } else {
2191
                $this->error = $this->line->error;
2192
                $this->errors = $this->line->errors;
2193
                dol_syslog(get_only_class($this) . "::addline error=" . $this->error, LOG_ERR);
2194
                $this->db->rollback();
2195
                return -1;
2196
            }
2197
        }
2198
        return -1;
2199
    }
2200
2201
2202
    /**
2203
     * Save a receiving into the tracking table of receiving (receptiondet_batch) and add product into stock warehouse.
2204
     *
2205
     * @param   User        $user                   User object making change
2206
     * @param   int         $product                Id of product to dispatch
2207
     * @param   double      $qty                    Qty to dispatch
2208
     * @param   int         $entrepot               Id of warehouse to add product
2209
     * @param   double      $price                  Unit Price for PMP value calculation (Unit price without Tax and taking into account discount)
2210
     * @param   string      $comment                Comment for stock movement
2211
     * @param   int|string  $eatby                  eat-by date
2212
     * @param   int|string  $sellby                 sell-by date
2213
     * @param   string      $batch                  Lot number
2214
     * @param   int         $fk_commandefourndet    Id of supplier order line
2215
     * @param   int         $notrigger              1 = notrigger
2216
     * @param   int         $fk_reception           Id of reception to link
2217
     * @return  int                     Return integer <0 if KO, >0 if OK
2218
     */
2219
    public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0, $fk_reception = 0)
2220
    {
2221
        global $conf, $langs;
2222
2223
        $error = 0;
2224
2225
        // Check parameters (if test are wrong here, there is bug into caller)
2226
        if ($entrepot <= 0) {
2227
            $this->error = 'ErrorBadValueForParameterWarehouse';
2228
            return -1;
2229
        }
2230
        if ($qty == 0) {
2231
            $this->error = 'ErrorBadValueForParameterQty';
2232
            return -1;
2233
        }
2234
2235
        $dispatchstatus = 1;
2236
        if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2237
            $dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
2238
        }
2239
2240
        $now = dol_now();
2241
2242
        $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
2243
2244
        if (($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY || $this->statut == self::STATUS_RECEIVED_COMPLETELY)) {
2245
            $this->db->begin();
2246
2247
            $sql = "INSERT INTO " . $this->db->prefix() . "receptiondet_batch";
2248
            $sql .= " (fk_element, fk_product, qty, fk_entrepot, fk_user, datec, fk_elementdet, status, comment, eatby, sellby, batch, fk_reception) VALUES";
2249
            $sql .= " ('" . $this->id . "','" . $product . "','" . $qty . "'," . ($entrepot > 0 ? "'" . $entrepot . "'" : "null") . ",'" . $user->id . "','" . $this->db->idate($now) . "','" . $fk_commandefourndet . "', " . $dispatchstatus . ", '" . $this->db->escape($comment) . "', ";
2250
            $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");
2251
            $sql .= ")";
2252
2253
            dol_syslog(get_only_class($this) . "::dispatchProduct", LOG_DEBUG);
2254
            $resql = $this->db->query($sql);
2255
            if ($resql) {
2256
                if (!$notrigger) {
2257
                    global $conf, $langs, $user;
2258
                    // Call trigger
2259
                    $result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
2260
                    if ($result < 0) {
2261
                        $error++;
2262
                    }
2263
                    // End call triggers
2264
                }
2265
            } else {
2266
                $this->error = $this->db->lasterror();
2267
                $error++;
2268
            }
2269
2270
            // If module stock is enabled and the stock increase is done on purchase order dispatching
2271
            if (!$error && $entrepot > 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
2272
                $mouv = new MouvementStock($this->db);
2273
                if ($product > 0) {
2274
                    // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
2275
                    $mouv->origin = &$this;
2276
                    $mouv->setOrigin($this->element, $this->id);
2277
2278
                    // Method change if qty < 0
2279
                    if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN') && $qty < 0) {
2280
                        $result = $mouv->livraison($user, $product, $entrepot, $qty * (-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
2281
                    } else {
2282
                        $result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
2283
                    }
2284
2285
                    if ($result < 0) {
2286
                        $this->error = $mouv->error;
2287
                        $this->errors = $mouv->errors;
2288
                        dol_syslog(get_only_class($this) . "::dispatchProduct " . $this->error . " " . implode(',', $this->errors), LOG_ERR);
2289
                        $error++;
2290
                    }
2291
                }
2292
            }
2293
2294
            if ($error == 0) {
2295
                $this->db->commit();
2296
                return 1;
2297
            } else {
2298
                $this->db->rollback();
2299
                return -1;
2300
            }
2301
        } else {
2302
            $this->error = 'BadStatusForObject';
2303
            return -2;
2304
        }
2305
    }
2306
2307
    /**
2308
     *  Delete line
2309
     *
2310
     *  @param  int     $idline     Id of line to delete
2311
     *  @param  int     $notrigger  1=Disable call to triggers
2312
     *  @return int                 Return integer <0 if KO, >0 if OK
2313
     */
2314
    public function deleteLine($idline, $notrigger = 0)
2315
    {
2316
        global $user;
2317
2318
        if ($this->statut == 0) {
2319
            $line = new CommandeFournisseurLigne($this->db);
2320
2321
            if ($line->fetch($idline) <= 0) {
2322
                return 0;
2323
            }
2324
2325
            // check if not yet received
2326
            $dispatchedLines = $this->getDispachedLines();
2327
            foreach ($dispatchedLines as $dispatchLine) {
2328
                if ($dispatchLine['orderlineid'] == $idline) {
2329
                    $this->error = "LineAlreadyDispatched";
2330
                    $this->errors[] = $this->error;
2331
                    return -3;
2332
                }
2333
            }
2334
2335
            if ($line->delete($user, $notrigger) > 0) {
2336
                $this->update_price(1);
2337
                return 1;
2338
            } else {
2339
                $this->setErrorsFromObject($line);
2340
                return -1;
2341
            }
2342
        } else {
2343
            return -2;
2344
        }
2345
    }
2346
2347
    /**
2348
     *  Delete an order
2349
     *
2350
     *  @param  User    $user       Object user
2351
     *  @param  int     $notrigger  1=Does not execute triggers, 0= execute triggers
2352
     *  @return int                 Return integer <0 if KO, >0 if OK
2353
     */
2354
    public function delete(User $user, $notrigger = 0)
2355
    {
2356
        global $langs, $conf;
2357
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2358
2359
        $error = 0;
2360
2361
        $this->db->begin();
2362
2363
        if (empty($notrigger)) {
2364
            // Call trigger
2365
            $result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
2366
            if ($result < 0) {
2367
                $this->errors[] = 'ErrorWhenRunningTrigger';
2368
                dol_syslog(get_only_class($this) . "::delete " . $this->error, LOG_ERR);
2369
                $this->db->rollback();
2370
                return -1;
2371
            }
2372
            // End call triggers
2373
        }
2374
2375
        // Test we can delete
2376
        $this->fetchObjectLinked(null, 'order_supplier');
2377
        if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
2378
            foreach ($this->linkedObjects['reception'] as $element) {
2379
                if ($element->statut >= 0) {
2380
                    $this->errors[] = $langs->trans('ReceptionExist');
2381
                    $error++;
2382
                    break;
2383
                }
2384
            }
2385
        }
2386
2387
        $main = $this->db->prefix() . 'commande_fournisseurdet';
2388
        $ef = $main . "_extrafields";
2389
        $sql = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_commande = " . ((int) $this->id) . ")";
2390
        dol_syslog(get_only_class($this) . "::delete extrafields lines", LOG_DEBUG);
2391
        if (!$this->db->query($sql)) {
2392
            $this->error = $this->db->lasterror();
2393
            $this->errors[] = $this->db->lasterror();
2394
            $error++;
2395
        }
2396
2397
        $sql = "DELETE FROM " . $this->db->prefix() . "commande_fournisseurdet WHERE fk_commande =" . ((int) $this->id);
2398
        dol_syslog(get_only_class($this) . "::delete", LOG_DEBUG);
2399
        if (!$this->db->query($sql)) {
2400
            $this->error = $this->db->lasterror();
2401
            $this->errors[] = $this->db->lasterror();
2402
            $error++;
2403
        }
2404
2405
        $sql = "DELETE FROM " . $this->db->prefix() . "commande_fournisseur WHERE rowid =" . ((int) $this->id);
2406
        dol_syslog(get_only_class($this) . "::delete", LOG_DEBUG);
2407
        if ($resql = $this->db->query($sql)) {
2408
            if ($this->db->affected_rows($resql) < 1) {
2409
                $this->error = $this->db->lasterror();
2410
                $this->errors[] = $this->db->lasterror();
2411
                $error++;
2412
            }
2413
        } else {
2414
            $this->error = $this->db->lasterror();
2415
            $this->errors[] = $this->db->lasterror();
2416
            $error++;
2417
        }
2418
2419
        // Remove extrafields
2420
        if (!$error) {
2421
            $result = $this->deleteExtraFields();
2422
            if ($result < 0) {
2423
                $this->error = 'FailToDeleteExtraFields';
2424
                $this->errors[] = 'FailToDeleteExtraFields';
2425
                $error++;
2426
                dol_syslog(get_only_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
2427
            }
2428
        }
2429
2430
        // Delete linked object
2431
        $res = $this->deleteObjectLinked();
2432
        if ($res < 0) {
2433
            $this->error = 'FailToDeleteObjectLinked';
2434
            $this->errors[] = 'FailToDeleteObjectLinked';
2435
            $error++;
2436
        }
2437
2438
        if (!$error) {
2439
            // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2440
            $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2441
            $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2442
2443
            // We remove directory
2444
            $ref = dol_sanitizeFileName($this->ref);
2445
            if ($conf->fournisseur->commande->dir_output) {
2446
                $dir = $conf->fournisseur->commande->dir_output . "/" . $ref;
2447
                $file = $dir . "/" . $ref . ".pdf";
2448
                if (file_exists($file)) {
2449
                    if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2450
                        $this->error = 'ErrorFailToDeleteFile';
2451
                        $this->errors[] = 'ErrorFailToDeleteFile';
2452
                        $error++;
2453
                    }
2454
                }
2455
                if (file_exists($dir)) {
2456
                    $res = @dol_delete_dir_recursive($dir);
2457
                    if (!$res) {
2458
                        $this->error = 'ErrorFailToDeleteDir';
2459
                        $this->errors[] = 'ErrorFailToDeleteDir';
2460
                        $error++;
2461
                    }
2462
                }
2463
            }
2464
        }
2465
2466
        if (!$error) {
2467
            dol_syslog(get_only_class($this) . "::delete $this->id by $user->id", LOG_DEBUG);
2468
            $this->db->commit();
2469
            return 1;
2470
        } else {
2471
            dol_syslog(get_only_class($this) . "::delete " . $this->error, LOG_ERR);
2472
            $this->db->rollback();
2473
            return -$error;
2474
        }
2475
    }
2476
2477
2478
    /**
2479
     * Return array of dispatched lines waiting to be approved for this order
2480
     *
2481
     * @since 8.0 Return dispatched quantity (qty).
2482
     *
2483
     * @param   int     $status     Filter on stats (-1 = no filter, 0 = lines draft to be approved, 1 = approved lines)
2484
     * @return  array               Array of lines
2485
     */
2486
    public function getDispachedLines($status = -1)
2487
    {
2488
        $ret = array();
2489
2490
        // List of already dispatched lines
2491
        $sql = "SELECT p.ref, p.label,";
2492
        $sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
2493
        $sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_elementdet";
2494
        $sql .= " FROM " . $this->db->prefix() . "product as p,";
2495
        $sql .= " " . $this->db->prefix() . "receptiondet_batch as cfd";
2496
        $sql .= " LEFT JOIN " . $this->db->prefix() . "entrepot as e ON cfd.fk_entrepot = e.rowid";
2497
        $sql .= " WHERE cfd.fk_element = " . ((int) $this->id);
2498
        $sql .= " AND cfd.fk_product = p.rowid";
2499
        if ($status >= 0) {
2500
            $sql .= " AND cfd.status = " . ((int) $status);
2501
        }
2502
        $sql .= " ORDER BY cfd.rowid ASC";
2503
2504
        $resql = $this->db->query($sql);
2505
        if ($resql) {
2506
            $num = $this->db->num_rows($resql);
2507
            $i = 0;
2508
2509
            while ($i < $num) {
2510
                $objp = $this->db->fetch_object($resql);
2511
                if ($objp) {
2512
                    $ret[] = array(
2513
                        'id' => $objp->dispatchedlineid,
2514
                        'productid' => $objp->fk_product,
2515
                        'warehouseid' => $objp->warehouse_id,
2516
                        'qty' => $objp->qty,
2517
                        'orderlineid' => $objp->fk_elementdet
2518
                    );
2519
                }
2520
2521
                $i++;
2522
            }
2523
        } else {
2524
            dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
2525
        }
2526
2527
        return $ret;
2528
    }
2529
2530
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2531
    /**
2532
     *  Set a delivery in database for this supplier order
2533
     *
2534
     *  @param  User    $user       User that input data
2535
     *  @param  integer $date       Date of reception
2536
     *  @param  string  $type       Type of receipt ('tot' = total/done, 'par' = partial, 'nev' = never, 'can' = cancel)
2537
     *  @param  string  $comment    Comment
2538
     *  @return int                 Return integer <0 if KO, >0 if OK
2539
     */
2540
    public function Livraison($user, $date, $type, $comment)
2541
    {
2542
		// phpcs:enable
2543
        global $conf, $langs;
2544
2545
        $result = 0;
2546
        $error = 0;
2547
2548
        dol_syslog(get_only_class($this) . "::Livraison");
2549
2550
        $usercanreceive = 0;
2551
        if (!isModEnabled('reception')) {
2552
            $usercanreceive = $user->hasRight("fournisseur", "commande", "receptionner");
2553
        } else {
2554
            $usercanreceive = $user->hasRight("reception", "creer");
2555
        }
2556
2557
        if ($usercanreceive) {
2558
            // Define the new status
2559
            if ($type == 'par') {
2560
                $statut = self::STATUS_RECEIVED_PARTIALLY;
2561
            } elseif ($type == 'tot') {
2562
                $statut = self::STATUS_RECEIVED_COMPLETELY;
2563
            } elseif ($type == 'nev') {
2564
                $statut = self::STATUS_CANCELED_AFTER_ORDER;
2565
            } elseif ($type == 'can') {
2566
                $statut = self::STATUS_CANCELED_AFTER_ORDER;
2567
            } else {
2568
                $error++;
2569
                dol_syslog(get_only_class($this) . "::Livraison Error -2", LOG_ERR);
2570
                return -2;
2571
            }
2572
2573
            // Some checks to accept the record
2574
            if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2575
                // If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
2576
                if (!$error && ($type == 'tot')) {
2577
                    $dispatchedlinearray = $this->getDispachedLines(0);
2578
                    if (count($dispatchedlinearray) > 0) {
2579
                        $result = -1;
2580
                        $error++;
2581
                        $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
2582
                        dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
2583
                    }
2584
                }
2585
                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)
2586
                    $dispatcheddenied = $this->getDispachedLines(2);
2587
                    if (count($dispatchedlinearray) > 0) {
2588
                        $result = -1;
2589
                        $error++;
2590
                        $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
2591
                        dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
2592
                    }
2593
                }
2594
            }
2595
2596
            // TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
2597
2598
            if (empty($error)) {
2599
                $this->db->begin();
2600
2601
                $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
2602
                $sql .= " SET fk_statut = " . ((int) $statut);
2603
                $sql .= " WHERE rowid = " . ((int) $this->id);
2604
                $sql .= " AND fk_statut IN (" . self::STATUS_ORDERSENT . "," . self::STATUS_RECEIVED_PARTIALLY . ")"; // Process running or Partially received
2605
2606
                dol_syslog(get_only_class($this) . "::Livraison", LOG_DEBUG);
2607
                $resql = $this->db->query($sql);
2608
                if ($resql) {
2609
                    $result = 1;
2610
                    $old_statut = $this->statut;
2611
                    $this->statut = $statut;
2612
                    $this->context['actionmsg2'] = $comment;
2613
2614
                    // Call trigger
2615
                    $result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
2616
                    if ($result_trigger < 0) {
2617
                        $error++;
2618
                    }
2619
                    // End call triggers
2620
2621
                    if (empty($error)) {
2622
                        $this->db->commit();
2623
                    } else {
2624
                        $this->statut = $old_statut;
2625
                        $this->db->rollback();
2626
                        $this->error = $this->db->lasterror();
2627
                        $result = -1;
2628
                    }
2629
                } else {
2630
                    $this->db->rollback();
2631
                    $this->error = $this->db->lasterror();
2632
                    $result = -1;
2633
                }
2634
            }
2635
        } else {
2636
            $this->error = $langs->trans('NotAuthorized');
2637
            $this->errors[] = $langs->trans('NotAuthorized');
2638
            dol_syslog(get_only_class($this) . "::Livraison Not Authorized");
2639
            $result = -3;
2640
        }
2641
        return $result;
2642
    }
2643
2644
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2645
    /**
2646
     *  Set delivery date
2647
     *
2648
     *  @param      User    $user               Object user that modify
2649
     *  @param      int     $delivery_date      Delivery date
2650
     *  @param      int     $notrigger          1=Does not execute triggers, 0= execute triggers
2651
     *  @return     int                         Return integer <0 if ko, >0 if ok
2652
     *  @deprecated Use  setDeliveryDate
2653
     */
2654
    public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2655
    {
2656
		// phpcs:enable
2657
        return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2658
    }
2659
2660
    /**
2661
     *  Set the planned delivery date
2662
     *
2663
     *  @param      User            $user               Object user making change
2664
     *  @param      integer         $delivery_date     Planned delivery date
2665
     *  @param      int             $notrigger          1=Does not execute triggers, 0= execute triggers
2666
     *  @return     int                                 Return integer <0 if KO, >0 if OK
2667
     */
2668
    public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2669
    {
2670
        if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2671
            $error = 0;
2672
2673
            $this->db->begin();
2674
2675
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
2676
            $sql .= " SET date_livraison = " . ($delivery_date ? "'" . $this->db->idate($delivery_date) . "'" : 'null');
2677
            $sql .= " WHERE rowid = " . ((int) $this->id);
2678
2679
            dol_syslog(__METHOD__, LOG_DEBUG);
2680
            $resql = $this->db->query($sql);
2681
            if (!$resql) {
2682
                $this->errors[] = $this->db->error();
2683
                $error++;
2684
            }
2685
2686
            if (!$error) {
2687
                $this->oldcopy = clone $this;
2688
                $this->delivery_date = $delivery_date;
2689
            }
2690
2691
            if (!$notrigger && empty($error)) {
2692
                // Call trigger
2693
                $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2694
                if ($result < 0) {
2695
                    $error++;
2696
                }
2697
                // End call triggers
2698
            }
2699
2700
            if (!$error) {
2701
                $this->db->commit();
2702
                return 1;
2703
            } else {
2704
                foreach ($this->errors as $errmsg) {
2705
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2706
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2707
                }
2708
                $this->db->rollback();
2709
                return -1 * $error;
2710
            }
2711
        } else {
2712
            return -2;
2713
        }
2714
    }
2715
2716
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2717
    /**
2718
     *  Set the id projet
2719
     *
2720
     *  @param      User            $user               Object utilisateur qui modifie
2721
     *  @param      int             $id_projet          Delivery date
2722
     *  @param      int             $notrigger          1=Does not execute triggers, 0= execute triggers
2723
     *  @return     int                                 Return integer <0 si ko, >0 si ok
2724
     */
2725
    public function set_id_projet($user, $id_projet, $notrigger = 0)
2726
    {
2727
		// phpcs:enable
2728
        if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2729
            $error = 0;
2730
2731
            $this->db->begin();
2732
2733
            $sql = "UPDATE " . $this->db->prefix() . "commande_fournisseur";
2734
            $sql .= " SET fk_projet = " . ($id_projet > 0 ? (int) $id_projet : 'null');
2735
            $sql .= " WHERE rowid = " . ((int) $this->id);
2736
2737
            dol_syslog(__METHOD__, LOG_DEBUG);
2738
            $resql = $this->db->query($sql);
2739
            if (!$resql) {
2740
                $this->errors[] = $this->db->error();
2741
                $error++;
2742
            }
2743
2744
            if (!$error) {
2745
                $this->oldcopy = clone $this;
2746
                $this->fk_projet = $id_projet;
2747
                $this->fk_project = $id_projet;
2748
            }
2749
2750
            if (!$notrigger && empty($error)) {
2751
                // Call trigger
2752
                $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2753
                if ($result < 0) {
2754
                    $error++;
2755
                }
2756
                // End call triggers
2757
            }
2758
2759
            if (!$error) {
2760
                $this->db->commit();
2761
                return 1;
2762
            } else {
2763
                foreach ($this->errors as $errmsg) {
2764
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2765
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2766
                }
2767
                $this->db->rollback();
2768
                return -1 * $error;
2769
            }
2770
        } else {
2771
            return -2;
2772
        }
2773
    }
2774
2775
    /**
2776
     *  Update a supplier order from a sales order
2777
     *
2778
     *  @param  User    $user           User that create
2779
     *  @param  int     $idc            Id of purchase order to update
2780
     *  @param  int     $comclientid    Id of sale order to use as template
2781
     *  @return int                     Return integer <0 if KO, >0 if OK
2782
     */
2783
    public function updateFromCommandeClient($user, $idc, $comclientid)
2784
    {
2785
        $comclient = new Commande($this->db);
2786
        $comclient->fetch($comclientid);
2787
2788
        $this->id = $idc;
2789
2790
        $this->lines = array();
2791
2792
        $num = count($comclient->lines);
2793
        for ($i = 0; $i < $num; $i++) {
2794
            $prod = new Product($this->db);
2795
            $label = '';
2796
            $ref = '';
2797
            if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
2798
                $label  = $prod->label;
2799
                $ref    = $prod->ref;
2800
            }
2801
2802
            $sql = "INSERT INTO " . $this->db->prefix() . "commande_fournisseurdet";
2803
            $sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
2804
            $sql .= " VALUES (" . ((int) $idc) . ", '" . $this->db->escape($label) . "', '" . $this->db->escape($comclient->lines[$i]->desc) . "'";
2805
            $sql .= "," . $comclient->lines[$i]->fk_product . ", " . price2num($comclient->lines[$i]->price, 'MU');
2806
            $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);
2807
            $sql .= ", '" . price2num($comclient->lines[$i]->subprice, 'MT') . "','0', '" . $this->db->escape($ref) . "');";
2808
            if ($this->db->query($sql)) {
2809
                $this->update_price(1);
2810
            }
2811
        }
2812
2813
        return 1;
2814
    }
2815
2816
    /**
2817
     *  Tag order with a particular status
2818
     *
2819
     *  @param      User    $user       Object user that change status
2820
     *  @param      int     $status     New status
2821
     *  @return     int                 Return integer <0 if KO, >0 if OK
2822
     */
2823
    public function setStatus($user, $status)
2824
    {
2825
        global $conf, $langs;
2826
        $error = 0;
2827
2828
        $this->db->begin();
2829
2830
        $sql = 'UPDATE ' . $this->db->prefix() . 'commande_fournisseur';
2831
        $sql .= " SET fk_statut = " . $status;
2832
        $sql .= " WHERE rowid = " . ((int) $this->id);
2833
2834
        dol_syslog(get_only_class($this) . "::setStatus", LOG_DEBUG);
2835
        $resql = $this->db->query($sql);
2836
        if ($resql) {
2837
            // Trigger names for each status
2838
            $triggerName = array();
2839
            $triggerName[0] = 'DRAFT';
2840
            $triggerName[1] = 'VALIDATED';
2841
            $triggerName[2] = 'APPROVED';
2842
            $triggerName[3] = 'ORDERED'; // Ordered
2843
            $triggerName[4] = 'RECEIVED_PARTIALLY';
2844
            $triggerName[5] = 'RECEIVED_COMPLETELY';
2845
            $triggerName[6] = 'CANCELED';
2846
            $triggerName[7] = 'CANCELED';
2847
            $triggerName[9] = 'REFUSED';
2848
2849
            // Call trigger
2850
            $result = $this->call_trigger("ORDER_SUPPLIER_STATUS_" . $triggerName[$status], $user);
2851
            if ($result < 0) {
2852
                $error++;
2853
            }
2854
            // End call triggers
2855
        } else {
2856
            $error++;
2857
            $this->error = $this->db->lasterror();
2858
            dol_syslog(get_only_class($this) . "::setStatus " . $this->error);
2859
        }
2860
2861
        if (!$error) {
2862
            $this->statut = $status;
2863
            $this->db->commit();
2864
            return 1;
2865
        } else {
2866
            $this->db->rollback();
2867
            return -1;
2868
        }
2869
    }
2870
2871
    /**
2872
     *  Update line
2873
     *
2874
     *  @param      int         $rowid              ID de la ligne de facture
2875
     *  @param      string      $desc               Line description
2876
     *  @param      int|float   $pu                 Unit price
2877
     *  @param      int|float   $qty                Quantity
2878
     *  @param      int|float   $remise_percent     Percent discount on line
2879
     *  @param      int|float   $txtva              VAT rate
2880
     *  @param      int|float   $txlocaltax1        Localtax1 tax
2881
     *  @param      int|float   $txlocaltax2        Localtax2 tax
2882
     *  @param      string      $price_base_type    Type of price base
2883
     *  @param      int         $info_bits          Miscellaneous information
2884
     *  @param      int         $type               Type of line (0=product, 1=service)
2885
     *  @param      int         $notrigger          Disable triggers
2886
     *  @param      integer     $date_start         Date start of service
2887
     *  @param      integer     $date_end           Date end of service
2888
     *  @param      array       $array_options      Extrafields array
2889
     *  @param      int|null    $fk_unit            Code of the unit to use. Null to use the default one
2890
     *  @param      int|float   $pu_ht_devise       Unit price in currency
2891
     *  @param      string      $ref_supplier       Supplier ref
2892
     *  @return     int                             Return integer < 0 if error, > 0 if ok
2893
     */
2894
    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 = '')
2895
    {
2896
        global $mysoc, $conf, $langs;
2897
        dol_syslog(get_only_class($this) . "::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
2898
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
2899
2900
        $error = 0;
2901
2902
        if ($this->statut == self::STATUS_DRAFT) {
2903
            // Clean parameters
2904
            if (empty($qty)) {
2905
                $qty = 0;
2906
            }
2907
            if (empty($info_bits)) {
2908
                $info_bits = 0;
2909
            }
2910
            if (empty($txtva)) {
2911
                $txtva = 0;
2912
            }
2913
            if (empty($txlocaltax1)) {
2914
                $txlocaltax1 = 0;
2915
            }
2916
            if (empty($txlocaltax2)) {
2917
                $txlocaltax2 = 0;
2918
            }
2919
            if (empty($remise_percent)) {
2920
                $remise_percent = 0;
2921
            }
2922
2923
            $remise_percent = (float) price2num($remise_percent);
2924
            $qty = price2num($qty);
2925
            if (!$qty) {
2926
                $qty = 1;
2927
            }
2928
            $pu = price2num($pu);
2929
            $pu_ht_devise = price2num($pu_ht_devise);
2930
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
2931
                $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
2932
            }
2933
            $txlocaltax1 = (float) price2num($txlocaltax1);
2934
            $txlocaltax2 = (float) price2num($txlocaltax2);
2935
2936
            // Check parameters
2937
            if ($type < 0) {
2938
                return -1;
2939
            }
2940
            if ($date_start && $date_end && $date_start > $date_end) {
2941
                $langs->load("errors");
2942
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2943
                return -1;
2944
            }
2945
2946
            $this->db->begin();
2947
2948
            // Calcul du total TTC et de la TVA pour la ligne a partir de
2949
            // qty, pu, remise_percent et txtva
2950
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2951
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2952
2953
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2954
2955
            // Clean vat code
2956
            $reg = array();
2957
            $vat_src_code = '';
2958
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
2959
                $vat_src_code = $reg[1];
2960
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
2961
            }
2962
2963
            $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);
2964
            $total_ht  = $tabprice[0];
2965
            $total_tva = $tabprice[1];
2966
            $total_ttc = $tabprice[2];
2967
            $total_localtax1 = $tabprice[9];
2968
            $total_localtax2 = $tabprice[10];
2969
            $pu_ht  = $tabprice[3];
2970
            $pu_tva = $tabprice[4];
2971
            $pu_ttc = $tabprice[5];
2972
2973
            // MultiCurrency
2974
            $multicurrency_total_ht = $tabprice[16];
2975
            $multicurrency_total_tva = $tabprice[17];
2976
            $multicurrency_total_ttc = $tabprice[18];
2977
            $pu_ht_devise = $tabprice[19];
2978
2979
            $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2980
            $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2981
2982
            //Fetch current line from the database and then clone the object and set it in $oldline property
2983
            $this->line = new CommandeFournisseurLigne($this->db);
2984
            $this->line->fetch($rowid);
2985
2986
            $oldline = clone $this->line;
2987
            $this->line->oldline = $oldline;
2988
2989
            $this->line->context = $this->context;
2990
2991
            $this->line->fk_commande = $this->id;
2992
            //$this->line->label=$label;
2993
            $this->line->desc = $desc;
2994
2995
            // redefine quantity according to packaging
2996
            if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2997
                if ($qty < $this->line->packaging) {
2998
                    $qty = $this->line->packaging;
2999
                } else {
3000
                    if (!empty($this->line->packaging) && ($qty % $this->line->packaging) > 0) {
3001
                        $coeff = intval($qty / $this->line->packaging) + 1;
3002
                        $qty = $this->line->packaging * $coeff;
3003
                        setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs');
3004
                    }
3005
                }
3006
            }
3007
3008
            $this->line->qty = $qty;
3009
            $this->line->ref_supplier = $ref_supplier;
3010
3011
            $this->line->vat_src_code = $vat_src_code;
3012
            $this->line->tva_tx         = $txtva;
3013
            $this->line->localtax1_tx   = $txlocaltax1;
3014
            $this->line->localtax2_tx   = $txlocaltax2;
3015
            $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3016
            $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3017
            $this->line->remise_percent = $remise_percent;
3018
            $this->line->subprice       = $pu_ht;
3019
            $this->line->info_bits      = $info_bits;
3020
            $this->line->total_ht       = $total_ht;
3021
            $this->line->total_tva      = $total_tva;
3022
            $this->line->total_localtax1 = $total_localtax1;
3023
            $this->line->total_localtax2 = $total_localtax2;
3024
            $this->line->total_ttc      = $total_ttc;
3025
            $this->line->product_type   = $type;
3026
            $this->line->special_code   = $oldline->special_code;
3027
            $this->line->rang           = $oldline->rang;
3028
            $this->line->origin         = $this->origin;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

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

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

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

Loading history...
3029
            $this->line->fk_unit        = $fk_unit;
3030
3031
            $this->line->date_start     = $date_start;
3032
            $this->line->date_end       = $date_end;
3033
3034
            // Multicurrency
3035
            $this->line->fk_multicurrency = $this->fk_multicurrency;
3036
            $this->line->multicurrency_code = $this->multicurrency_code;
3037
            $this->line->multicurrency_subprice     = $pu_ht_devise;
3038
            $this->line->multicurrency_total_ht     = $multicurrency_total_ht;
3039
            $this->line->multicurrency_total_tva    = $multicurrency_total_tva;
3040
            $this->line->multicurrency_total_ttc    = $multicurrency_total_ttc;
3041
3042
            $this->line->subprice = $pu_ht;
3043
            $this->line->price = $this->line->subprice;
3044
3045
            $this->line->remise_percent = $remise_percent;
3046
3047
            if (is_array($array_options) && count($array_options) > 0) {
3048
                // We replace values in this->line->array_options only for entries defined into $array_options
3049
                foreach ($array_options as $key => $value) {
3050
                    $this->line->array_options[$key] = $array_options[$key];
3051
                }
3052
            }
3053
3054
            $result = $this->line->update($notrigger);
3055
3056
3057
            // Mise a jour info denormalisees au niveau facture
3058
            if ($result >= 0) {
3059
                $this->update_price('1', 'auto');
3060
                $this->db->commit();
3061
                return $result;
3062
            } else {
3063
                $this->error = $this->db->lasterror();
3064
                $this->db->rollback();
3065
                return -1;
3066
            }
3067
        } else {
3068
            $this->error = "Order status makes operation forbidden";
3069
            dol_syslog(get_only_class($this) . "::updateline " . $this->error, LOG_ERR);
3070
            return -2;
3071
        }
3072
    }
3073
3074
3075
    /**
3076
     *  Initialise an instance with random values.
3077
     *  Used to build previews or test instances.
3078
     *  id must be 0 if object instance is a specimen.
3079
     *
3080
     *  @return int
3081
     */
3082
    public function initAsSpecimen()
3083
    {
3084
        global $user, $langs, $conf;
3085
3086
        include_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
3087
3088
        dol_syslog(get_only_class($this) . "::initAsSpecimen");
3089
3090
        $now = dol_now();
3091
3092
        // Find first product
3093
        $prodid = 0;
3094
        $product = new ProductFournisseur($this->db);
3095
        $sql = "SELECT rowid";
3096
        $sql .= " FROM " . $this->db->prefix() . "product";
3097
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
3098
        $sql .= $this->db->order("rowid", "ASC");
3099
        $sql .= $this->db->plimit(1);
3100
        $resql = $this->db->query($sql);
3101
        if ($resql) {
3102
            $obj = $this->db->fetch_object($resql);
3103
            $prodid = $obj->rowid;
3104
        }
3105
3106
        // Initialise parameters
3107
        $this->id = 0;
3108
        $this->ref = 'SPECIMEN';
3109
        $this->specimen = 1;
3110
        $this->socid = 1;
3111
        $this->date = $now;
3112
        $this->date_commande = $now;
3113
        $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3114
        $this->cond_reglement_code = 'RECEP';
3115
        $this->mode_reglement_code = 'CHQ';
3116
3117
        $this->note_public = 'This is a comment (public)';
3118
        $this->note_private = 'This is a comment (private)';
3119
3120
        $this->multicurrency_tx = 1;
3121
        $this->multicurrency_code = $conf->currency;
3122
3123
        $this->statut = 0;
3124
3125
        // Lines
3126
        $nbp = 5;
3127
        $xnbp = 0;
3128
        while ($xnbp < $nbp) {
3129
            $line = new CommandeFournisseurLigne($this->db);
3130
            $line->desc = $langs->trans("Description") . " " . $xnbp;
3131
            $line->qty = 1;
3132
            $line->subprice = 100;
3133
            $line->tva_tx = 19.6;
3134
            $line->localtax1_tx = 0;
3135
            $line->localtax2_tx = 0;
3136
            if ($xnbp == 2) {
3137
                $line->total_ht = 50;
3138
                $line->total_ttc = 59.8;
3139
                $line->total_tva = 9.8;
3140
                $line->remise_percent = 50;
3141
            } else {
3142
                $line->total_ht = 100;
3143
                $line->total_ttc = 119.6;
3144
                $line->total_tva = 19.6;
3145
                $line->remise_percent = 0;
3146
            }
3147
            $line->fk_product = $prodid;
3148
3149
            $this->lines[$xnbp] = $line;
3150
3151
            $this->total_ht       += $line->total_ht;
3152
            $this->total_tva      += $line->total_tva;
3153
            $this->total_ttc      += $line->total_ttc;
3154
3155
            $xnbp++;
3156
        }
3157
3158
        return 1;
3159
    }
3160
3161
    /**
3162
     *  Charge les information d'ordre info dans l'objet facture
3163
     *
3164
     *  @param  int     $id         Id de la facture a charger
3165
     *  @return void
3166
     */
3167
    public function info($id)
3168
    {
3169
        $sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
3170
        $sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
3171
        $sql .= ' FROM ' . $this->db->prefix() . 'commande_fournisseur as c';
3172
        $sql .= ' WHERE c.rowid = ' . ((int) $id);
3173
3174
        $result = $this->db->query($sql);
3175
        if ($result) {
3176
            if ($this->db->num_rows($result)) {
3177
                $obj = $this->db->fetch_object($result);
3178
3179
                $this->id = $obj->rowid;
3180
3181
                $this->user_creation_id = $obj->fk_user_author;
3182
                $this->user_validation_id = $obj->fk_user_valid;
3183
                $this->user_modification_id = $obj->fk_user_modif;
3184
                $this->user_approve_id = $obj->fk_user_approve;
3185
                $this->user_approve_id2 = $obj->fk_user_approve2;
3186
3187
                $this->date_creation     = $this->db->jdate($obj->datec);
3188
                $this->date_modification = $this->db->jdate($obj->datem);
3189
                $this->date_approve      = $this->db->jdate($obj->datea);
3190
                $this->date_approve2     = $this->db->jdate($obj->datea2);
3191
                $this->date_validation   = $this->db->jdate($obj->date_validation);
3192
            }
3193
            $this->db->free($result);
3194
        } else {
3195
            dol_print_error($this->db);
3196
        }
3197
    }
3198
3199
    /**
3200
     *  Load the indicators this->nb for the state board
3201
     *
3202
     *  @return     int         Return integer <0 si ko, >0 si ok
3203
     */
3204
    public function loadStateBoard()
3205
    {
3206
        global $conf, $user;
3207
3208
        $this->nb = array();
3209
        $clause = "WHERE";
3210
3211
        $sql = "SELECT count(co.rowid) as nb";
3212
        $sql .= " FROM " . $this->db->prefix() . "commande_fournisseur as co";
3213
        $sql .= " LEFT JOIN " . $this->db->prefix() . "societe as s ON co.fk_soc = s.rowid";
3214
        if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
3215
            $sql .= " LEFT JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3216
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
3217
            $clause = "AND";
3218
        }
3219
        $sql .= " " . $clause . " co.entity IN (" . getEntity('supplier_order') . ")";
3220
3221
        $resql = $this->db->query($sql);
3222
        if ($resql) {
3223
            while ($obj = $this->db->fetch_object($resql)) {
3224
                $this->nb["supplier_orders"] = $obj->nb;
3225
            }
3226
            $this->db->free($resql);
3227
            return 1;
3228
        } else {
3229
            dol_print_error($this->db);
3230
            $this->error = $this->db->error();
3231
            return -1;
3232
        }
3233
    }
3234
3235
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3236
    /**
3237
     *  Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3238
     *
3239
     *  @param  User    $user   Object user
3240
     *  @param  string  $mode   "opened", "awaiting" for orders awaiting reception
3241
     *  @return WorkboardResponse|int   Return integer <0 if KO, WorkboardResponse if OK
3242
     */
3243
    public function load_board($user, $mode = 'opened')
3244
    {
3245
		// phpcs:enable
3246
        global $conf, $langs;
3247
3248
        $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
3249
        $sql .= " FROM " . $this->db->prefix() . "commande_fournisseur as c";
3250
        if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
3251
            $sql .= " JOIN " . $this->db->prefix() . "societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
3252
        }
3253
        $sql .= " WHERE c.entity = " . $conf->entity;
3254
        if ($mode === 'awaiting') {
3255
            $sql .= " AND c.fk_statut IN (" . self::STATUS_ORDERSENT . ", " . self::STATUS_RECEIVED_PARTIALLY . ")";
3256
        } else {
3257
            $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . ", " . self::STATUS_ACCEPTED . ")";
3258
        }
3259
        if ($user->socid) {
3260
            $sql .= " AND c.fk_soc = " . ((int) $user->socid);
3261
        }
3262
3263
        $resql = $this->db->query($sql);
3264
        if ($resql) {
3265
            $commandestatic = new CommandeFournisseur($this->db);
3266
3267
            $response = new WorkboardResponse();
3268
            $response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
3269
            $response->label = $langs->trans("SuppliersOrdersToProcess");
3270
            $response->labelShort = $langs->trans("Opened");
3271
            $response->url = constant('BASE_URL') . '/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
3272
            $response->img = img_object('', "order");
3273
3274
            if ($mode === 'awaiting') {
3275
                $response->label = $langs->trans("SuppliersOrdersAwaitingReception");
3276
                $response->labelShort = $langs->trans("AwaitingReception");
3277
                $response->url = constant('BASE_URL') . '/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
3278
            }
3279
3280
            while ($obj = $this->db->fetch_object($resql)) {
3281
                $commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
3282
                $commandestatic->date_commande = $this->db->jdate($obj->date_commande);
3283
                $commandestatic->statut = $obj->fk_statut;
3284
3285
                $response->nbtodo++;
3286
                $response->total += $obj->total_ht;
3287
3288
                if ($commandestatic->hasDelay()) {
3289
                    $response->nbtodolate++;
3290
                }
3291
            }
3292
3293
            return $response;
3294
        } else {
3295
            $this->error = $this->db->error();
3296
            return -1;
3297
        }
3298
    }
3299
3300
    /**
3301
     * Returns the translated input method of object (defined if $this->methode_commande_id > 0).
3302
     * This function make a sql request to get translation. No cache yet, try to not use it inside a loop.
3303
     *
3304
     * @return string
3305
     */
3306
    public function getInputMethod()
3307
    {
3308
        global $langs;
3309
3310
        if ($this->methode_commande_id > 0) {
3311
            $sql = "SELECT rowid, code, libelle as label";
3312
            $sql .= " FROM " . $this->db->prefix() . 'c_input_method';
3313
            $sql .= " WHERE active=1 AND rowid = " . ((int) $this->methode_commande_id);
3314
3315
            $resql = $this->db->query($sql);
3316
            if ($resql) {
3317
                if ($this->db->num_rows($resql)) {
3318
                    $obj = $this->db->fetch_object($resql);
3319
3320
                    $string = $langs->trans($obj->code);
3321
                    if ($string == $obj->code) {
3322
                        $string = $obj->label != '-' ? $obj->label : '';
3323
                    }
3324
                    return $string;
3325
                }
3326
            } else {
3327
                dol_print_error($this->db);
3328
            }
3329
        }
3330
3331
        return '';
3332
    }
3333
3334
    /**
3335
     *  Create a document onto disk according to template model.
3336
     *
3337
     *  @param      string      $modele         Force template to use ('' to not force)
3338
     *  @param      Translate   $outputlangs    Object lang to use for traduction
3339
     *  @param      int         $hidedetails    Hide details of lines
3340
     *  @param      int         $hidedesc       Hide description
3341
     *  @param      int         $hideref        Hide ref
3342
     *  @param      null|array  $moreparams     Array to provide more information
3343
     *  @return     int                         Return integer < 0 if KO, 0 = no doc generated, > 0 if OK
3344
     */
3345
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3346
    {
3347
        global $conf, $langs;
3348
3349
        if (!dol_strlen($modele)) {
3350
            $modele = '';   // No doc template/generation by default
3351
3352
            if (!empty($this->model_pdf)) {
3353
                $modele = $this->model_pdf;
3354
            } elseif (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) {
3355
                $modele = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF');
3356
            }
3357
        }
3358
3359
        if (empty($modele)) {
3360
            return 0;
3361
        } else {
3362
            $langs->load("suppliers");
3363
            $outputlangs->load("products");
3364
3365
            $modelpath = "core/modules/supplier_order/doc/";
3366
            $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3367
            return $result;
3368
        }
3369
    }
3370
3371
    /**
3372
     * Return the max number delivery delay in day
3373
     *
3374
     * @param   Translate   $langs      Language object
3375
     * @return  string                  Translated string
3376
     */
3377
    public function getMaxDeliveryTimeDay($langs)
3378
    {
3379
        if (empty($this->lines)) {
3380
            return '';
3381
        }
3382
3383
        $obj = new ProductFournisseur($this->db);
3384
3385
        $nb = 0;
3386
        foreach ($this->lines as $line) {
3387
            if ($line->fk_product > 0) {
3388
                $idp = $obj->find_min_price_product_fournisseur($line->fk_product, $line->qty);
3389
                if ($idp) {
3390
                    $obj->fetch($idp);
3391
                    if ($obj->delivery_time_days > $nb) {
3392
                        $nb = $obj->delivery_time_days;
3393
                    }
3394
                }
3395
            }
3396
        }
3397
3398
        if ($nb === 0) {
3399
            return '';
3400
        } else {
3401
            return $nb . ' ' . $langs->trans('Days');
3402
        }
3403
    }
3404
3405
    /**
3406
     * Returns the rights used for this class
3407
     * @return int
3408
     */
3409
    public function getRights()
3410
    {
3411
        global $user;
3412
3413
        return $user->hasRight("fournisseur", "commande");
3414
    }
3415
3416
3417
    /**
3418
     * Function used to replace a thirdparty id with another one.
3419
     *
3420
     * @param   DoliDB  $dbs        Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
3421
     * @param   int     $origin_id  Old thirdparty id
3422
     * @param   int     $dest_id    New thirdparty id
3423
     * @return  bool
3424
     */
3425
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3426
    {
3427
        $tables = array(
3428
            'commande_fournisseur'
3429
        );
3430
3431
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3432
    }
3433
3434
    /**
3435
     * Function used to replace a product id with another one.
3436
     *
3437
     * @param DoliDB    $dbs        Database handler
3438
     * @param int       $origin_id  Old product id
3439
     * @param int       $dest_id    New product id
3440
     * @return bool
3441
     */
3442
    public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
3443
    {
3444
        $tables = array(
3445
            'commande_fournisseurdet'
3446
        );
3447
3448
        return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
3449
    }
3450
3451
    /**
3452
     * Is the supplier order delayed?
3453
     * 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.
3454
     * If order has not been sent, we use the order date.
3455
     *
3456
     * @return  bool                    True if object is delayed
3457
     */
3458
    public function hasDelay()
3459
    {
3460
        global $conf;
3461
3462
        if ($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY) {
3463
            $now = dol_now();
3464
            if (!empty($this->delivery_date)) {
3465
                $date_to_test = $this->delivery_date;
3466
                return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3467
            } else {
3468
                //$date_to_test = $this->date_commande;
3469
                //return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3470
                return false;
3471
            }
3472
        } else {
3473
            $now = dol_now();
3474
            $date_to_test = $this->date_commande;
3475
3476
            return ($this->statut > 0 && $this->statut < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3477
        }
3478
    }
3479
3480
    /**
3481
     * Show the customer delayed info.
3482
     * 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.
3483
     * If order has not been sent, we use the order date.
3484
     *
3485
     * @return string       Show delayed information
3486
     */
3487
    public function showDelay()
3488
    {
3489
        global $conf, $langs;
3490
3491
        $langs->load('orders');
3492
3493
        $text = '';
3494
3495
        if ($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY) {
3496
            if (!empty($this->delivery_date)) {
3497
                $text = $langs->trans("DeliveryDate") . ' ' . dol_print_date($this->delivery_date, 'day');
3498
            } else {
3499
                $text = $langs->trans("OrderDate") . ' ' . dol_print_date($this->date_commande, 'day');
3500
            }
3501
        } else {
3502
            $text = $langs->trans("OrderDate") . ' ' . dol_print_date($this->date_commande, 'day');
3503
        }
3504
        if ($text) {
3505
            $text .= ' ' . ($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-') . ' ' . round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1) . ' ' . $langs->trans("days") . ' < ' . $langs->trans("Today");
3506
        }
3507
3508
        return $text;
3509
    }
3510
3511
3512
    /**
3513
     * Calc status regarding to dispatched stock
3514
     *
3515
     * @param       User    $user                   User action
3516
     * @param       int     $closeopenorder         Close if received
3517
     * @param       string  $comment                Comment
3518
     * @return      int                             Return integer <0 if KO, 0 if not applicable, >0 if OK
3519
     */
3520
    public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
3521
    {
3522
        if (isModEnabled("supplier_order")) {
3523
            $qtydelivered = array();
3524
            $qtywished = array();
3525
3526
            $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
3527
3528
            $filter = array('t.fk_element' => $this->id);
3529
            if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
3530
                $filter['t.status'] = 1; // Restrict to lines with status validated
3531
            }
3532
3533
            $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
3534
            if ($ret < 0) {
3535
                $this->error = $supplierorderdispatch->error;
3536
                $this->errors = $supplierorderdispatch->errors;
3537
                return $ret;
3538
            } else {
3539
                if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
3540
                    $date_liv = dol_now();
3541
3542
                    // Build array with quantity deliverd by product
3543
                    foreach ($supplierorderdispatch->lines as $line) {
3544
                        $qtydelivered[$line->fk_product] += $line->qty;
3545
                    }
3546
                    foreach ($this->lines as $line) {
3547
                        // Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
3548
                        if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') && $line->product_type > 0) {
3549
                            continue;
3550
                        }
3551
                        $qtywished[$line->fk_product] += $line->qty;
3552
                    }
3553
3554
                    //Compare array
3555
                    $diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
3556
                    $keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
3557
                    $keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
3558
                    //var_dump(array_keys($qtydelivered));
3559
                    //var_dump(array_keys($qtywished));
3560
                    //var_dump($diff_array);
3561
                    //var_dump($keysinwishednotindelivered);
3562
                    //var_dump($keysindeliverednotinwished);
3563
                    //exit;
3564
3565
                    if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everything is received
3566
                        if ($closeopenorder) {
3567
                            //$ret=$this->setStatus($user,5);
3568
                            $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3569
                            if ($ret < 0) {
3570
                                return -1;
3571
                            }
3572
                            return 5;
3573
                        } else {
3574
                            //Diff => received partially
3575
                            //$ret=$this->setStatus($user,4);
3576
                            $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3577
                            if ($ret < 0) {
3578
                                return -1;
3579
                            }
3580
                            return 4;
3581
                        }
3582
                    } elseif (getDolGlobalString('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
3583
                        //set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
3584
3585
                        $close = 0;
3586
3587
                        if (count($diff_array) > 0) {
3588
                            //there are some difference between  the two arrays
3589
3590
                            //scan the array of results
3591
                            foreach ($diff_array as $key => $value) {
3592
                                //if the quantity delivered is greater or equal to wish quantity
3593
                                if ($qtydelivered[$key] >= $qtywished[$key]) {
3594
                                    $close++;
3595
                                }
3596
                            }
3597
                        }
3598
3599
3600
                        if ($close == count($diff_array)) {
3601
                            //all the products are received equal or more than the wished quantity
3602
                            if ($closeopenorder) {
3603
                                $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3604
                                if ($ret < 0) {
3605
                                    return -1;
3606
                                }
3607
                                return 5;
3608
                            } else {
3609
                                //Diff => received partially
3610
                                $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3611
                                if ($ret < 0) {
3612
                                    return -1;
3613
                                }
3614
                                return 4;
3615
                            }
3616
                        } else {
3617
                            //all the products are not received
3618
                            $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3619
                            if ($ret < 0) {
3620
                                return -1;
3621
                            }
3622
                            return 4;
3623
                        }
3624
                    } else {
3625
                        //Diff => received partially
3626
                        $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3627
                        if ($ret < 0) {
3628
                            return -1;
3629
                        }
3630
                        return 4;
3631
                    }
3632
                }
3633
                return 1;
3634
            }
3635
        }
3636
        return 0;
3637
    }
3638
3639
    /**
3640
     *  Load array this->receptions of lines of shipments with nb of products sent for each order line
3641
     *  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
3642
     *
3643
     *  @param      int     $filtre_statut      Filter on shipment status
3644
     *  @return     int                         Return integer <0 if KO, Nb of lines found if OK
3645
     */
3646
    public function loadReceptions($filtre_statut = -1)
3647
    {
3648
        $this->receptions = array();
3649
3650
        dol_syslog(get_only_class($this) . "::loadReceptions", LOG_DEBUG);
3651
3652
        $sql = 'SELECT cd.rowid, cd.fk_product,';
3653
        $sql .= ' sum(cfd.qty) as qty';
3654
        $sql .= ' FROM ' . $this->db->prefix() . 'receptiondet_batch as cfd,';
3655
        if ($filtre_statut >= 0) {
3656
            $sql .= ' ' . $this->db->prefix() . 'reception as e,';
3657
        }
3658
        $sql .= ' ' . $this->db->prefix() . 'commande_fournisseurdet as cd';
3659
        $sql .= ' WHERE';
3660
        if ($filtre_statut >= 0) {
3661
            $sql .= ' cfd.fk_reception = e.rowid AND';
3662
        }
3663
        $sql .= ' cfd.fk_elementdet = cd.rowid';
3664
        $sql .= ' AND cd.fk_commande =' . ((int) $this->id);
3665
        if (isset($this->fk_product) && !empty($this->fk_product) > 0) {
3666
            $sql .= ' AND cd.fk_product = ' . ((int) $this->fk_product);
3667
        }
3668
        if ($filtre_statut >= 0) {
3669
            $sql .= ' AND e.fk_statut >= ' . ((int) $filtre_statut);
3670
        }
3671
        $sql .= ' GROUP BY cd.rowid, cd.fk_product';
3672
3673
        $resql = $this->db->query($sql);
3674
        if ($resql) {
3675
            $num = $this->db->num_rows($resql);
3676
            $i = 0;
3677
            while ($i < $num) {
3678
                $obj = $this->db->fetch_object($resql);
3679
                empty($this->receptions[$obj->rowid]) ? $this->receptions[$obj->rowid] = $obj->qty : $this->receptions[$obj->rowid] += $obj->qty;
3680
                $i++;
3681
            }
3682
            $this->db->free($resql);
3683
3684
            return $num;
3685
        } else {
3686
            $this->error = $this->db->lasterror();
3687
            return -1;
3688
        }
3689
    }
3690
3691
    /**
3692
     *  Return clicable link of object (with eventually picto)
3693
     *
3694
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3695
     *  @param      array       $arraydata              Array of data
3696
     *  @return     string                              HTML Code for Kanban thumb.
3697
     */
3698
    public function getKanbanView($option = '', $arraydata = null)
3699
    {
3700
        global $langs;
3701
3702
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3703
3704
        $return = '<div class="box-flex-item box-flex-grow-zero">';
3705
        $return .= '<div class="info-box info-box-sm">';
3706
        $return .= '<span class="info-box-icon bg-infobox-action">';
3707
        $return .= img_picto('', $this->picto);
3708
        $return .= '</span>';
3709
        $return .= '<div class="info-box-content">';
3710
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref) . '</span>';
3711
        if ($selected >= 0) {
3712
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
3713
        }
3714
        if (property_exists($this, 'socid') || property_exists($this, 'total_tva')) {
3715
            $return .= '<br><span class="info-box-label amount">' . $this->socid . '</span>';
3716
        }
3717
        if (property_exists($this, 'billed')) {
3718
            $return .= '<br><span class="opacitymedium">' . $langs->trans("Billed") . ' : </span><span class="info-box-label">' . yn($this->billed) . '</span>';
3719
        }
3720
        if (method_exists($this, 'getLibStatut')) {
3721
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3) . '</div>';
3722
        }
3723
        $return .= '</div>';
3724
        $return .= '</div>';
3725
        $return .= '</div>';
3726
        return $return;
3727
    }
3728
}
3729