Passed
Pull Request — master (#2)
by
unknown
26:19
created

Product::updatePrice()   F

Complexity

Conditions 29
Paths > 20000

Size

Total Lines 148
Code Lines 101

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 29
eloc 101
c 1
b 0
f 0
nc 24984
nop 11
dl 0
loc 148
rs 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/* Copyright (C) 2001-2007    Rodolphe Quiedeville    <[email protected]>
3
 * Copyright (C) 2004-2014	Laurent Destailleur		<[email protected]>
4
 * Copyright (C) 2005-2015	Regis Houssin			<[email protected]>
5
 * Copyright (C) 2006		Andre Cianfarani		<[email protected]>
6
 * Copyright (C) 2007-2011	Jean Heimburger			<[email protected]>
7
 * Copyright (C) 2010-2018	Juanjo Menent			<[email protected]>
8
 * Copyright (C) 2012       Cedric Salvador         <[email protected]>
9
 * Copyright (C) 2013-2014	Cedric GROSS			<[email protected]>
10
 * Copyright (C) 2013-2016	Marcos García			<[email protected]>
11
 * Copyright (C) 2011-2017	Alexandre Spangaro		<[email protected]>
12
 * Copyright (C) 2014		Henry Florian			<[email protected]>
13
 * Copyright (C) 2014-2016	Philippe Grand			<[email protected]>
14
 * Copyright (C) 2014		Ion agorria			    <[email protected]>
15
 * Copyright (C) 2016-2018	Ferran Marcet			<[email protected]>
16
 * Copyright (C) 2017		Gustavo Novaro
17
 *
18
 * This program is free software; you can redistribute it and/or modify
19
 * it under the terms of the GNU General Public License as published by
20
 * the Free Software Foundation; either version 3 of the License, or
21
 * (at your option) any later version.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26
 * GNU General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU General Public License
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30
 */
31
32
/**
33
 *    \file       htdocs/product/class/product.class.php
34
 *    \ingroup    produit
35
 *    \brief      File of class to manage predefined products or services
36
 */
37
require_once DOL_DOCUMENT_ROOT . '/core/class/commonobject.class.php';
38
require_once DOL_DOCUMENT_ROOT . '/product/class/productbatch.class.php';
39
require_once DOL_DOCUMENT_ROOT . '/product/stock/class/entrepot.class.php';
40
41
/**
42
 * Class to manage products or services
43
 */
44
class Product extends CommonObject
45
{
46
47
    /**
48
     * @var string ID to identify managed object
49
     */
50
    public $element = 'product';
51
52
    /**
53
     * @var string Name of table without prefix where object is stored
54
     */
55
    public $table_element = 'product';
56
57
    /**
58
     * @var int Field with ID of parent key if this field has a parent
59
     */
60
    public $fk_element = 'fk_product';
61
    protected $childtables = array('supplier_proposaldet', 'propaldet', 'commandedet', 'facturedet', 'contratdet', 'facture_fourn_det', 'commande_fournisseurdet');    // To test if we can delete object
62
63
    /**
64
     * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
65
     *
66
     * @var int
67
     */
68
    public $ismultientitymanaged = 1;
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    protected $table_ref_field = 'ref';
74
    public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.xpm|\.xbm'; // See also into images.lib.php
75
76
    /*
77
     * @deprecated
78
     * @see label
79
     */
80
    public $libelle;
81
82
    /**
83
     * Product label
84
     *
85
     * @var string
86
     */
87
    public $label;
88
89
    /**
90
     * Product descripion
91
     *
92
     * @var string
93
     */
94
    public $description;
95
96
    /**
97
     * Check TYPE constants
98
     *
99
     * @var int
100
     */
101
    public $type = self::TYPE_PRODUCT;
102
103
    /**
104
     * Selling price
105
     *
106
     * @var float
107
     */
108
    public $price;            // Price net
109
110
    /**
111
     * Price with tax
112
     *
113
     * @var float
114
     */
115
    public $price_ttc;
116
117
    /**
118
     * Minimum price net
119
     *
120
     * @var float
121
     */
122
    public $price_min;
123
124
    /**
125
     * Minimum price with tax
126
     *
127
     * @var float
128
     */
129
    public $price_min_ttc;
130
131
    /*
132
     * Base price ('TTC' for price including tax or 'HT' for net price)
133
     * @var float
134
     */
135
    public $price_base_type;
136
    //! Arrays for multiprices
137
    public $multiprices = array();
138
    public $multiprices_ttc = array();
139
    public $multiprices_base_type = array();
140
    public $multiprices_min = array();
141
    public $multiprices_min_ttc = array();
142
    public $multiprices_tva_tx = array();
143
    public $multiprices_recuperableonly = array();
144
    //! Price by quantity arrays
145
    public $price_by_qty;
146
    public $prices_by_qty = array();
147
    public $prices_by_qty_id = array();
148
    public $prices_by_qty_list = array();
149
    //! Default VAT code for product (link to code into llx_c_tva but without foreign keys)
150
    public $default_vat_code;
151
    //! Default VAT rate of product
152
    public $tva_tx;
153
    //! French VAT NPR (0 or 1)
154
    public $tva_npr = 0;
155
    //! Other local taxes
156
    public $localtax1_tx;
157
    public $localtax2_tx;
158
    public $localtax1_type;
159
    public $localtax2_type;
160
161
    /**
162
     * Stock real
163
     *
164
     * @var int
165
     */
166
    public $stock_reel = 0;
167
168
    /**
169
     * Stock virtual
170
     *
171
     * @var int
172
     */
173
    public $stock_theorique;
174
175
    /**
176
     * Cost price
177
     *
178
     * @var float
179
     */
180
    public $cost_price;
181
    //! Average price value for product entry into stock (PMP)
182
    public $pmp;
183
184
    /**
185
     * Stock alert
186
     *
187
     * @var int
188
     */
189
    public $seuil_stock_alerte = 0;
190
191
    /**
192
     * Ask for replenishment when $desiredstock < $stock_reel
193
     */
194
    public $desiredstock = 0;
195
196
    /*
197
     * Service expiration
198
     */
199
    public $duration_value;
200
201
    /**
202
     * Exoiration unit
203
     */
204
    public $duration_unit;
205
206
    /**
207
     * Status indicates whether the product is on sale '1' or not '0'
208
     *
209
     * @var int
210
     */
211
    public $status = 0;
212
213
    /**
214
     * Status indicate whether the product is available for purchase '1' or not '0'
215
     *
216
     * @var int
217
     */
218
    public $status_buy = 0;
219
220
    /**
221
     * Status indicates whether the product is a finished product '1' or a raw material '0'
222
     *
223
     * @var int
224
     */
225
    public $finished;
226
227
    /**
228
     * We must manage lot/batch number, sell-by date and so on : '1':yes '0':no
229
     *
230
     * @var int
231
     */
232
    public $status_batch = 0;
233
234
    /**
235
     * Customs code
236
     *
237
     * @var
238
     */
239
    public $customcode;
240
241
    /**
242
     * Product URL
243
     *
244
     * @var string
245
     */
246
    public $url;
247
    //! Unites de mesure
248
    public $weight;
249
    public $weight_units;
250
    public $length;
251
    public $length_units;
252
    public $surface;
253
    public $surface_units;
254
    public $volume;
255
    public $volume_units;
256
    public $accountancy_code_sell;
257
    public $accountancy_code_sell_intra;
258
    public $accountancy_code_sell_export;
259
    public $accountancy_code_buy;
260
261
    /**
262
     * Main barcode
263
     * barcode value
264
     *
265
     * @var
266
     */
267
    public $barcode;
268
269
    /**
270
     * Additional barcodes (Some products have different barcodes according to the country of origin of manufacture)
271
     *
272
     * @var array
273
     */
274
    public $barcodes_extra = array();
275
    public $stats_propale = array();
276
    public $stats_commande = array();
277
    public $stats_contrat = array();
278
    public $stats_facture = array();
279
    public $stats_commande_fournisseur = array();
280
    public $multilangs = array();
281
    //! Taille de l'image
282
    public $imgWidth;
283
    public $imgHeight;
284
    public $date_creation;
285
    public $date_modification;
286
    //! Id du fournisseur
287
    public $product_fourn_id;
288
    //! Product ID already linked to a reference supplier
289
    public $product_id_already_linked;
290
    public $nbphoto = 0;
291
    //! Contains detail of stock of product into each warehouse
292
    public $stock_warehouse = array();
293
    public $oldcopy;
294
    public $fk_default_warehouse;
295
296
    /**
297
     * @var int ID
298
     */
299
    public $fk_price_expression;
300
301
    /* To store supplier price found */
302
    public $fourn_pu;
303
    public $fourn_price_base_type;
304
    public $fourn_socid;
305
306
    /**
307
     * @deprecated
308
     * @see        $ref_supplier
309
     */
310
    public $ref_fourn;
311
    public $ref_supplier;
312
313
    /**
314
     * Unit code ('km', 'm', 'l', 'p', ...)
315
     *
316
     * @var string
317
     */
318
    public $fk_unit;
319
320
    /**
321
     * Price is generated using multiprice rules
322
     *
323
     * @var int
324
     */
325
    public $price_autogen = 0;
326
    public $fields = array(
327
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'index' => 1, 'position' => 1, 'comment' => 'Id'),
328
        'ref' => array('type' => 'varchar(128)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'showoncombobox' => 1, 'index' => 1, 'position' => 10, 'searchall' => 1, 'comment' => 'Reference of object'),
329
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'default' => 1, 'notnull' => 1, 'index' => 1, 'position' => 20),
330
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 61),
331
        'note' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 62),
332
        'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 500),
333
        'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 501),
334
        //'date_valid'    =>array('type'=>'datetime',     'label'=>'DateCreation',     'enabled'=>1, 'visible'=>-2, 'position'=>502),
335
        'fk_user_author' => array('type' => 'integer', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 510, 'foreignkey' => 'llx_user.rowid'),
336
        'fk_user_modif' => array('type' => 'integer', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 511),
337
        //'fk_user_valid' =>array('type'=>'integer',      'label'=>'UserValidation',        'enabled'=>1, 'visible'=>-1, 'position'=>512),
338
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'index' => 0, 'position' => 1000),
339
        //'tosell'       =>array('type'=>'integer',      'label'=>'Status',           'enabled'=>1, 'visible'=>1,  'notnull'=>1, 'default'=>0, 'index'=>1,  'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Active', -1=>'Cancel')),
340
        //'tobuy'        =>array('type'=>'integer',      'label'=>'Status',           'enabled'=>1, 'visible'=>1,  'notnull'=>1, 'default'=>0, 'index'=>1,  'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Active', -1=>'Cancel')),
341
    );
342
343
    /**
344
     * Regular product
345
     */
346
    const TYPE_PRODUCT = 0;
347
348
    /**
349
     * Service
350
     */
351
    const TYPE_SERVICE = 1;
352
353
    /**
354
     * Advanced feature: assembly kit
355
     */
356
    const TYPE_ASSEMBLYKIT = 2;
357
358
    /**
359
     * Advanced feature: stock kit
360
     */
361
    const TYPE_STOCKKIT = 3;
362
363
    /**
364
     *  Constructor
365
     *
366
     * @param DoliDB $db Database handler
367
     */
368
    function __construct($db)
369
    {
370
        $this->db = $db;
371
        $this->canvas = '';
372
    }
373
374
    /**
375
     *    Check that ref and label are ok
376
     *
377
     * @return int         >1 if OK, <=0 if KO
378
     */
379
    function check()
380
    {
381
        $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
382
383
        $err = 0;
384
        if (dol_strlen(trim($this->ref)) == 0) {
385
            $err++;
386
        }
387
388
        if (dol_strlen(trim($this->label)) == 0) {
389
            $err++;
390
        }
391
392
        if ($err > 0) {
393
            return 0;
394
        } else {
395
            return 1;
396
        }
397
    }
398
399
    /**
400
     *    Insert product into database
401
     *
402
     * @param  User $user      User making insert
403
     * @param  int  $notrigger Disable triggers
404
     * @return int                         Id of product/service if OK, < 0 if KO
405
     */
406
    function create($user, $notrigger = 0)
407
    {
408
        global $conf, $langs;
409
410
        $error = 0;
411
412
        // Clean parameters
413
        $this->ref = dol_string_nospecial(trim($this->ref));
414
        $this->label = trim($this->label);
415
        $this->price_ttc = price2num($this->price_ttc);
0 ignored issues
show
Documentation Bug introduced by
The property $price_ttc was declared of type double, but price2num($this->price_ttc) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
416
        $this->price = price2num($this->price);
0 ignored issues
show
Documentation Bug introduced by
The property $price was declared of type double, but price2num($this->price) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
417
        $this->price_min_ttc = price2num($this->price_min_ttc);
0 ignored issues
show
Documentation Bug introduced by
The property $price_min_ttc was declared of type double, but price2num($this->price_min_ttc) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
418
        $this->price_min = price2num($this->price_min);
0 ignored issues
show
Documentation Bug introduced by
The property $price_min was declared of type double, but price2num($this->price_min) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
419
        if (empty($this->tva_tx)) {
420
            $this->tva_tx = 0;
421
        }
422
        if (empty($this->tva_npr)) {
423
            $this->tva_npr = 0;
424
        }
425
        //Local taxes
426
        if (empty($this->localtax1_tx)) {
427
            $this->localtax1_tx = 0;
428
        }
429
        if (empty($this->localtax2_tx)) {
430
            $this->localtax2_tx = 0;
431
        }
432
        if (empty($this->localtax1_type)) {
433
            $this->localtax1_type = '0';
434
        }
435
        if (empty($this->localtax2_type)) {
436
            $this->localtax2_type = '0';
437
        }
438
439
        if (empty($this->price)) {
440
            $this->price = 0;
441
        }
442
        if (empty($this->price_min)) {
443
            $this->price_min = 0;
444
        }
445
446
        // Price by quantity
447
        if (empty($this->price_by_qty)) {
448
            $this->price_by_qty = 0;
449
        }
450
451
        if (empty($this->status)) {
452
            $this->status = 0;
453
        }
454
        if (empty($this->status_buy)) {
455
            $this->status_buy = 0;
456
        }
457
458
        $price_ht = 0;
459
        $price_ttc = 0;
460
        $price_min_ht = 0;
461
        $price_min_ttc = 0;
462
463
        //
464
        if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
465
            $price_ttc = price2num($this->price_ttc, 'MU');
466
            $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
467
        }
468
469
        //
470
        if ($this->price_base_type != 'TTC' && $this->price > 0) {
471
            $price_ht = price2num($this->price, 'MU');
472
            $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
473
        }
474
475
        //
476
        if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
477
            $price_min_ttc = price2num($this->price_min_ttc, 'MU');
478
            $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
479
        }
480
481
        //
482
        if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
483
            $price_min_ht = price2num($this->price_min, 'MU');
484
            $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
485
        }
486
487
        $this->accountancy_code_buy = trim($this->accountancy_code_buy);
488
        $this->accountancy_code_sell = trim($this->accountancy_code_sell);
489
        $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
490
        $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
491
492
        // Barcode value
493
        $this->barcode = trim($this->barcode);
494
495
        // Check parameters
496
        if (empty($this->label)) {
497
            $this->error = 'ErrorMandatoryParametersNotProvided';
498
            return -1;
499
        }
500
501
        if (empty($this->ref)) {
502
            // Load object modCodeProduct
503
            $module = (!empty($conf->global->PRODUCT_CODEPRODUCT_ADDON) ? $conf->global->PRODUCT_CODEPRODUCT_ADDON : 'mod_codeproduct_leopard');
504
            if ($module != 'mod_codeproduct_leopard') {    // Do not load module file for leopard
505
                if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
506
                    $module = substr($module, 0, dol_strlen($module) - 4);
507
                }
508
                dol_include_once('/core/modules/product/' . $module . '.php');
509
                $modCodeProduct = new $module;
510
                if (!empty($modCodeProduct->code_auto)) {
511
                    $this->ref = $modCodeProduct->getNextValue($this, $this->type);
512
                }
513
                unset($modCodeProduct);
514
            }
515
516
            if (empty($this->ref)) {
517
                $this->error = 'ProductModuleNotSetupForAutoRef';
518
                return -2;
519
            }
520
        }
521
522
        dol_syslog(get_class($this) . "::create ref=" . $this->ref . " price=" . $this->price . " price_ttc=" . $this->price_ttc . " tva_tx=" . $this->tva_tx . " price_base_type=" . $this->price_base_type, LOG_DEBUG);
523
524
        $now = dol_now();
525
526
        $this->db->begin();
527
528
        // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
529
        if ($this->barcode == -1) {
530
            $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->barcode is correct as $this->get_barcode($this...his->barcode_type_code) targeting Product::get_barcode() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
531
        }
532
533
        // Check more parameters
534
        // If error, this->errors[] is filled
535
        $result = $this->verify();
536
537
        if ($result >= 0) {
538
            $sql = "SELECT count(*) as nb";
539
            $sql .= " FROM " . MAIN_DB_PREFIX . "product";
540
            $sql .= " WHERE entity IN (" . getEntity('product') . ")";
541
            $sql .= " AND ref = '" . $this->db->escape($this->ref) . "'";
542
543
            $result = $this->db->query($sql);
544
            if ($result) {
545
                $obj = $this->db->fetch_object($result);
546
                if ($obj->nb == 0) {
547
                    // Produit non deja existant
548
                    $sql = "INSERT INTO " . MAIN_DB_PREFIX . "product (";
549
                    $sql .= "datec";
550
                    $sql .= ", entity";
551
                    $sql .= ", ref";
552
                    $sql .= ", ref_ext";
553
                    $sql .= ", price_min";
554
                    $sql .= ", price_min_ttc";
555
                    $sql .= ", label";
556
                    $sql .= ", fk_user_author";
557
                    $sql .= ", fk_product_type";
558
                    $sql .= ", price";
559
                    $sql .= ", price_ttc";
560
                    $sql .= ", price_base_type";
561
                    $sql .= ", tobuy";
562
                    $sql .= ", tosell";
563
                    $sql .= ", accountancy_code_buy";
564
                    $sql .= ", accountancy_code_sell";
565
                    $sql .= ", accountancy_code_sell_intra";
566
                    $sql .= ", accountancy_code_sell_export";
567
                    $sql .= ", canvas";
568
                    $sql .= ", finished";
569
                    $sql .= ", tobatch";
570
                    $sql .= ", fk_unit";
571
                    $sql .= ") VALUES (";
572
                    $sql .= "'" . $this->db->idate($now) . "'";
573
                    $sql .= ", " . $conf->entity;
574
                    $sql .= ", '" . $this->db->escape($this->ref) . "'";
575
                    $sql .= ", " . (!empty($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null");
576
                    $sql .= ", " . price2num($price_min_ht);
577
                    $sql .= ", " . price2num($price_min_ttc);
578
                    $sql .= ", " . (!empty($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null");
579
                    $sql .= ", " . $user->id;
580
                    $sql .= ", " . $this->type;
581
                    $sql .= ", " . price2num($price_ht);
582
                    $sql .= ", " . price2num($price_ttc);
583
                    $sql .= ", '" . $this->db->escape($this->price_base_type) . "'";
584
                    $sql .= ", " . $this->status;
585
                    $sql .= ", " . $this->status_buy;
586
                    $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
587
                    $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
588
                    $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
589
                    $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
590
                    $sql .= ", '" . $this->db->escape($this->canvas) . "'";
591
                    $sql .= ", " . ((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'null' : (int) $this->finished);
592
                    $sql .= ", " . ((empty($this->status_batch) || $this->status_batch < 0) ? '0' : $this->status_batch);
593
                    $sql .= ", " . (!$this->fk_unit ? 'NULL' : $this->fk_unit);
594
                    $sql .= ")";
595
596
                    dol_syslog(get_class($this) . "::Create", LOG_DEBUG);
597
                    $result = $this->db->query($sql);
598
                    if ($result) {
599
                        $id = $this->db->last_insert_id(MAIN_DB_PREFIX . "product");
600
601
                        if ($id > 0) {
602
                            $this->id = $id;
603
                            $this->price = $price_ht;
604
                            $this->price_ttc = $price_ttc;
605
                            $this->price_min = $price_min_ht;
606
                            $this->price_min_ttc = $price_min_ttc;
607
608
                            $result = $this->_log_price($user);
609
                            if ($result > 0) {
610
                                if ($this->update($id, $user, true, 'add') <= 0) {
611
                                    $error++;
612
                                }
613
                            } else {
614
                                $error++;
615
                                $this->error = $this->db->lasterror();
616
                            }
617
                        } else {
618
                            $error++;
619
                            $this->error = 'ErrorFailedToGetInsertedId';
620
                        }
621
                    } else {
622
                        $error++;
623
                        $this->error = $this->db->lasterror();
624
                    }
625
                } else {
626
                    // Product already exists with this ref
627
                    $langs->load("products");
628
                    $error++;
629
                    $this->error = "ErrorProductAlreadyExists";
630
                }
631
            } else {
632
                $error++;
633
                $this->error = $this->db->lasterror();
634
            }
635
636
            if (!$error && !$notrigger) {
637
                // Call trigger
638
                $result = $this->call_trigger('PRODUCT_CREATE', $user);
639
                if ($result < 0) {
640
                    $error++;
641
                }
642
                // End call triggers
643
            }
644
645
            if (!$error) {
646
                $this->db->commit();
647
                return $this->id;
648
            } else {
649
                $this->db->rollback();
650
                return -$error;
651
            }
652
        } else {
653
            $this->db->rollback();
654
            dol_syslog(get_class($this) . "::Create fails verify " . join(',', $this->errors), LOG_WARNING);
655
            return -3;
656
        }
657
    }
658
659
    /**
660
     *    Check properties of product are ok (like name, barcode, ...).
661
     *    All properties must be already loaded on object (this->barcode, this->barcode_type_code, ...).
662
     *
663
     * @return int        0 if OK, <0 if KO
664
     */
665
    function verify()
666
    {
667
        $this->errors = array();
668
669
        $result = 0;
670
        $this->ref = trim($this->ref);
671
672
        if (!$this->ref) {
673
            $this->errors[] = 'ErrorBadRef';
674
            $result = -2;
675
        }
676
677
        $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
678
        if ($rescode) {
679
            if ($rescode == -1) {
680
                $this->errors[] = 'ErrorBadBarCodeSyntax';
681
            } elseif ($rescode == -2) {
682
                $this->errors[] = 'ErrorBarCodeRequired';
683
            } elseif ($rescode == -3) {
684
                // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
685
                $this->errors[] = 'ErrorBarCodeAlreadyUsed';
686
            }
687
688
            $result = -3;
689
        }
690
691
        return $result;
692
    }
693
694
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
695
    /**
696
     *  Check barcode
697
     *
698
     * @param  string $valuetotest Value to test
699
     * @param  string $typefortest Type of barcode (ISBN, EAN, ...)
700
     * @return int                        0 if OK
701
     *                                     -1 ErrorBadBarCodeSyntax
702
     *                                     -2 ErrorBarCodeRequired
703
     *                                     -3 ErrorBarCodeAlreadyUsed
704
     */
705
    function check_barcode($valuetotest, $typefortest)
706
    {
707
        // phpcs:enable
708
        global $conf;
709
        if (!empty($conf->barcode->enabled) && !empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
710
            $module = strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
711
712
            $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
713
            foreach ($dirsociete as $dirroot) {
714
                $res = dol_include_once($dirroot . $module . '.php');
715
                if ($res) {
716
                    break;
717
                }
718
            }
719
720
            $mod = new $module();
721
722
            dol_syslog(get_class($this) . "::check_barcode value=" . $valuetotest . " type=" . $typefortest . " module=" . $module);
723
            $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
724
            return $result;
725
        } else {
726
            return 0;
727
        }
728
    }
729
730
    /**
731
     *    Update a record into database.
732
     *  If batch flag is set to on, we create records into llx_product_batch
733
     *
734
     * @param  int    $id        Id of product
735
     * @param  User   $user      Object user making update
736
     * @param  int    $notrigger Disable triggers
737
     * @param  string $action    Current action for hookmanager ('add' or 'update')
738
     * @return int                 1 if OK, -1 if ref already exists, -2 if other error
739
     */
740
    function update($id, $user, $notrigger = false, $action = 'update')
741
    {
742
        global $langs, $conf, $hookmanager;
743
744
        $error = 0;
745
746
        // Check parameters
747
        if (!$this->label) {
748
            $this->label = 'MISSING LABEL';
749
        }
750
751
        // Clean parameters
752
        $this->ref = dol_string_nospecial(trim($this->ref));
753
        $this->label = trim($this->label);
754
        $this->description = trim($this->description);
755
        $this->note = (isset($this->note) ? trim($this->note) : null);
756
        $this->weight = price2num($this->weight);
757
        $this->weight_units = trim($this->weight_units);
758
        $this->length = price2num($this->length);
759
        $this->length_units = trim($this->length_units);
760
        $this->width = price2num($this->width);
761
        $this->width_units = trim($this->width_units);
762
        $this->height = price2num($this->height);
763
        $this->height_units = trim($this->height_units);
764
        // set unit not defined
765
        if ($this->length_units) {
766
            $this->width_units = $this->length_units;    // Not used yet
767
        }
768
        if ($this->length_units) {
769
            $this->height_units = $this->length_units;    // Not used yet
770
        }
771
        // Automated compute surface and volume if not filled
772
        if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
773
            $this->surface = $this->length * $this->width;
774
            $this->surface_units = measuring_units_squared($this->length_units);
775
        }
776
        if (empty($this->volume) && !empty($this->surface_units) && !empty($this->height) && $this->length_units == $this->height_units) {
777
            $this->volume = $this->surface * $this->height;
778
            $this->volume_units = measuring_units_cubed($this->height_units);
779
        }
780
781
        $this->surface = price2num($this->surface);
782
        $this->surface_units = trim($this->surface_units);
783
        $this->volume = price2num($this->volume);
784
        $this->volume_units = trim($this->volume_units);
785
        if (empty($this->tva_tx)) {
786
            $this->tva_tx = 0;
787
        }
788
        if (empty($this->tva_npr)) {
789
            $this->tva_npr = 0;
790
        }
791
        if (empty($this->localtax1_tx)) {
792
            $this->localtax1_tx = 0;
793
        }
794
        if (empty($this->localtax2_tx)) {
795
            $this->localtax2_tx = 0;
796
        }
797
        if (empty($this->localtax1_type)) {
798
            $this->localtax1_type = '0';
799
        }
800
        if (empty($this->localtax2_type)) {
801
            $this->localtax2_type = '0';
802
        }
803
        if (empty($this->status)) {
804
            $this->status = 0;
805
        }
806
        if (empty($this->status_buy)) {
807
            $this->status_buy = 0;
808
        }
809
810
        if (empty($this->country_id)) {
811
            $this->country_id = 0;
812
        }
813
814
        // Barcode value
815
        $this->barcode = trim($this->barcode);
816
817
        $this->accountancy_code_buy = trim($this->accountancy_code_buy);
818
        $this->accountancy_code_sell = trim($this->accountancy_code_sell);
819
        $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
820
        $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
821
822
823
        $this->db->begin();
824
825
        // Check name is required and codes are ok or unique.
826
        // If error, this->errors[] is filled
827
        if ($action != 'add') {
828
            $result = $this->verify();    // We don't check when update called during a create because verify was already done
829
        }
830
831
        if ($result >= 0) {
832
            if (empty($this->oldcopy)) {
833
                $org = new self($this->db);
834
                $org->fetch($this->id);
835
                $this->oldcopy = $org;
836
            }
837
838
            // Test if batch management is activated on existing product
839
            // If yes, we create missing entries into product_batch
840
            if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
841
                //$valueforundefinedlot = 'Undefined';  // In previous version, 39 and lower
842
                $valueforundefinedlot = '000000';
843
844
                dol_syslog("Flag batch of product id=" . $this->id . " is set to ON, so we will create missing records into product_batch");
845
846
                $this->load_stock();
847
                foreach ($this->stock_warehouse as $idW => $ObjW) {   // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
848
                    $qty_batch = 0;
849
                    foreach ($ObjW->detail_batch as $detail) {    // Each lines of detail in product_batch of the current $ObjW = product_stock
850
                        if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
851
                            // We discard this line, we will create it later
852
                            $sqlclean = "DELETE FROM " . MAIN_DB_PREFIX . "product_batch WHERE batch in('Undefined', '" . $valueforundefinedlot . "') AND fk_product_stock = " . $ObjW->id;
853
                            $result = $this->db->query($sqlclean);
854
                            if (!$result) {
855
                                dol_print_error($this->db);
856
                                exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
857
                            }
858
                            continue;
859
                        }
860
861
                        $qty_batch += $detail->qty;
862
                    }
863
                    // Quantities in batch details are not same as stock quantity,
864
                    // so we add a default batch record to complete and get same qty in parent and child table
865
                    if ($ObjW->real <> $qty_batch) {
866
                        $ObjBatch = new Productbatch($this->db);
867
                        $ObjBatch->batch = $valueforundefinedlot;
868
                        $ObjBatch->qty = ($ObjW->real - $qty_batch);
869
                        $ObjBatch->fk_product_stock = $ObjW->id;
870
871
                        if ($ObjBatch->create($user, 1) < 0) {
872
                            $error++;
873
                            $this->errors = $ObjBatch->errors;
874
                        }
875
                    }
876
                }
877
            }
878
879
            // For automatic creation
880
            if ($this->barcode == -1) {
881
                $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->barcode is correct as $this->get_barcode($this...his->barcode_type_code) targeting Product::get_barcode() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
882
            }
883
884
            $sql = "UPDATE " . MAIN_DB_PREFIX . "product";
885
            $sql .= " SET label = '" . $this->db->escape($this->label) . "'";
886
            $sql .= ", ref = '" . $this->db->escape($this->ref) . "'";
887
            $sql .= ", ref_ext = " . (!empty($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null");
888
            $sql .= ", default_vat_code = " . ($this->default_vat_code ? "'" . $this->db->escape($this->default_vat_code) . "'" : "null");
889
            $sql .= ", tva_tx = " . $this->tva_tx;
890
            $sql .= ", recuperableonly = " . $this->tva_npr;
891
            $sql .= ", localtax1_tx = " . $this->localtax1_tx;
892
            $sql .= ", localtax2_tx = " . $this->localtax2_tx;
893
            $sql .= ", localtax1_type = " . ($this->localtax1_type != '' ? "'" . $this->db->escape($this->localtax1_type) . "'" : "'0'");
894
            $sql .= ", localtax2_type = " . ($this->localtax2_type != '' ? "'" . $this->db->escape($this->localtax2_type) . "'" : "'0'");
895
896
            $sql .= ", barcode = " . (empty($this->barcode) ? "null" : "'" . $this->db->escape($this->barcode) . "'");
897
            $sql .= ", fk_barcode_type = " . (empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
898
899
            $sql .= ", tosell = " . $this->status;
900
            $sql .= ", tobuy = " . $this->status_buy;
901
            $sql .= ", tobatch = " . ((empty($this->status_batch) || $this->status_batch < 0) ? '0' : $this->status_batch);
902
            $sql .= ", finished = " . ((!isset($this->finished) || $this->finished < 0) ? "null" : (int) $this->finished);
903
            $sql .= ", weight = " . ($this->weight != '' ? "'" . $this->db->escape($this->weight) . "'" : 'null');
904
            $sql .= ", weight_units = " . ($this->weight_units != '' ? "'" . $this->db->escape($this->weight_units) . "'" : 'null');
905
            $sql .= ", length = " . ($this->length != '' ? "'" . $this->db->escape($this->length) . "'" : 'null');
906
            $sql .= ", length_units = " . ($this->length_units != '' ? "'" . $this->db->escape($this->length_units) . "'" : 'null');
907
            $sql .= ", width= " . ($this->width != '' ? "'" . $this->db->escape($this->width) . "'" : 'null');
908
            $sql .= ", width_units = " . ($this->width_units != '' ? "'" . $this->db->escape($this->width_units) . "'" : 'null');
909
            $sql .= ", height = " . ($this->height != '' ? "'" . $this->db->escape($this->height) . "'" : 'null');
910
            $sql .= ", height_units = " . ($this->height_units != '' ? "'" . $this->db->escape($this->height_units) . "'" : 'null');
911
            $sql .= ", surface = " . ($this->surface != '' ? "'" . $this->db->escape($this->surface) . "'" : 'null');
912
            $sql .= ", surface_units = " . ($this->surface_units != '' ? "'" . $this->db->escape($this->surface_units) . "'" : 'null');
913
            $sql .= ", volume = " . ($this->volume != '' ? "'" . $this->db->escape($this->volume) . "'" : 'null');
914
            $sql .= ", volume_units = " . ($this->volume_units != '' ? "'" . $this->db->escape($this->volume_units) . "'" : 'null');
915
            $sql .= ", fk_default_warehouse = " . ($this->fk_default_warehouse > 0 ? $this->db->escape($this->fk_default_warehouse) : 'null');
916
            $sql .= ", seuil_stock_alerte = " . ((isset($this->seuil_stock_alerte) && $this->seuil_stock_alerte != '') ? "'" . $this->db->escape($this->seuil_stock_alerte) . "'" : "null");
917
            $sql .= ", description = '" . $this->db->escape($this->description) . "'";
918
            $sql .= ", url = " . ($this->url ? "'" . $this->db->escape($this->url) . "'" : 'null');
919
            $sql .= ", customcode = '" . $this->db->escape($this->customcode) . "'";
920
            $sql .= ", fk_country = " . ($this->country_id > 0 ? $this->country_id : 'null');
921
            $sql .= ", note = " . (isset($this->note) ? "'" . $this->db->escape($this->note) . "'" : 'null');
922
            $sql .= ", duration = '" . $this->db->escape($this->duration_value . $this->duration_unit) . "'";
923
            $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
924
            $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
925
            $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
926
            $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
927
            $sql .= ", desiredstock = " . ((isset($this->desiredstock) && $this->desiredstock != '') ? $this->desiredstock : "null");
928
            $sql .= ", cost_price = " . ($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
929
            $sql .= ", fk_unit= " . (!$this->fk_unit ? 'NULL' : $this->fk_unit);
930
            $sql .= ", price_autogen = " . (!$this->price_autogen ? 0 : 1);
931
            $sql .= ", fk_price_expression = " . ($this->fk_price_expression != 0 ? $this->fk_price_expression : 'NULL');
932
            $sql .= ", fk_user_modif = " . ($user->id > 0 ? $user->id : 'NULL');
933
            // stock field is not here because it is a denormalized value from product_stock.
934
            $sql .= " WHERE rowid = " . $id;
935
936
            dol_syslog(get_class($this) . "::update", LOG_DEBUG);
937
938
            $resql = $this->db->query($sql);
939
            if ($resql) {
940
                $this->id = $id;
941
942
                // Multilangs
943
                if (!empty($conf->global->MAIN_MULTILANGS)) {
944
                    if ($this->setMultiLangs($user) < 0) {
945
                        $this->error = $langs->trans("Error") . " : " . $this->db->error() . " - " . $sql;
946
                        return -2;
947
                    }
948
                }
949
950
                $action = 'update';
951
952
                // Actions on extra fields
953
                if (!$error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
954
                    $result = $this->insertExtraFields();
955
                    if ($result < 0) {
956
                        $error++;
957
                    }
958
                }
959
960
                if (!$error && !$notrigger) {
961
                    // Call trigger
962
                    $result = $this->call_trigger('PRODUCT_MODIFY', $user);
963
                    if ($result < 0) {
964
                        $error++;
965
                    }
966
                    // End call triggers
967
                }
968
969
                if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
970
                    // We remove directory
971
                    if ($conf->product->dir_output) {
972
                        $olddir = $conf->product->dir_output . "/" . dol_sanitizeFileName($this->oldcopy->ref);
973
                        $newdir = $conf->product->dir_output . "/" . dol_sanitizeFileName($this->ref);
974
                        if (file_exists($olddir)) {
975
                            //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
976
                            //$res = dol_move($olddir, $newdir);
977
                            // do not use dol_move with directory
978
                            $res = @rename($olddir, $newdir);
979
                            if (!$res) {
980
                                $langs->load("errors");
981
                                $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
982
                                $error++;
983
                            }
984
                        }
985
                    }
986
                }
987
988
                if (!$error) {
989
                    if ($conf->variants->enabled) {
990
991
                        include_once DOL_DOCUMENT_ROOT . '/variants/class/ProductCombination.class.php';
992
993
                        $comb = new ProductCombination($this->db);
994
995
                        foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
996
                            $currcomb->updateProperties($this);
997
                        }
998
                    }
999
1000
                    $this->db->commit();
1001
                    return 1;
1002
                } else {
1003
                    $this->db->rollback();
1004
                    return -$error;
1005
                }
1006
            } else {
1007
                if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1008
                    $langs->load("errors");
1009
                    if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1010
                        $this->error = $langs->trans("Error") . " : " . $langs->trans("ErrorProductAlreadyExists", $this->ref);
1011
                    } else {
1012
                        $this->error = $langs->trans("Error") . " : " . $langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1013
                    }
1014
                    $this->errors[] = $this->error;
1015
                    $this->db->rollback();
1016
                    return -1;
1017
                } else {
1018
                    $this->error = $langs->trans("Error") . " : " . $this->db->error() . " - " . $sql;
1019
                    $this->errors[] = $this->error;
1020
                    $this->db->rollback();
1021
                    return -2;
1022
                }
1023
            }
1024
        } else {
1025
            $this->db->rollback();
1026
            dol_syslog(get_class($this) . "::Update fails verify " . join(',', $this->errors), LOG_WARNING);
1027
            return -3;
1028
        }
1029
    }
1030
1031
    /**
1032
     *  Delete a product from database (if not used)
1033
     *
1034
     * @param  User $user      Product id (usage of this is deprecated, delete should be called without parameters on a fetched object)
1035
     * @param  int  $notrigger Do not execute trigger
1036
     * @return int                    < 0 if KO, 0 = Not possible, > 0 if OK
1037
     */
1038
    function delete(User $user, $notrigger = 0)
1039
    {
1040
        // Deprecation warning
1041
        if ($id > 0) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id seems to be never defined.
Loading history...
1042
            dol_syslog(__METHOD__ . " with parameter is deprecated", LOG_WARNING);
1043
        }
1044
1045
        global $conf, $langs;
1046
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1047
1048
        $error = 0;
1049
1050
        // Clean parameters
1051
        if (empty($id)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id seems to never exist and therefore empty should always be true.
Loading history...
1052
            $id = $this->id;
1053
        } else {
1054
            $this->fetch($id);
1055
        }
1056
1057
        // Check parameters
1058
        if (empty($id)) {
1059
            $this->error = "Object must be fetched before calling delete";
1060
            return -1;
1061
        }
1062
        if (($this->type == Product::TYPE_PRODUCT && empty($user->rights->produit->supprimer)) || ($this->type == Product::TYPE_SERVICE && empty($user->rights->service->supprimer))) {
1063
            $this->error = "ErrorForbidden";
1064
            return 0;
1065
        }
1066
1067
        $objectisused = $this->isObjectUsed($id);
1068
        if (empty($objectisused)) {
1069
            $this->db->begin();
1070
1071
            if (!$error && empty($notrigger)) {
1072
                // Call trigger
1073
                $result = $this->call_trigger('PRODUCT_DELETE', $user);
1074
                if ($result < 0) {
1075
                    $error++;
1076
                }
1077
                // End call triggers
1078
            }
1079
1080
            // Delete from product_batch on product delete
1081
            if (!$error) {
1082
                $sql = "DELETE FROM " . MAIN_DB_PREFIX . 'product_batch';
1083
                $sql .= " WHERE fk_product_stock IN (";
1084
                $sql .= "SELECT rowid FROM " . MAIN_DB_PREFIX . 'product_stock';
1085
                $sql .= " WHERE fk_product = " . $id . ")";
1086
1087
                $result = $this->db->query($sql);
1088
                if (!$result) {
1089
                    $error++;
1090
                    $this->errors[] = $this->db->lasterror();
1091
                }
1092
            }
1093
1094
            // Delete all child tables
1095
            if (!$error) {
1096
                $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot');  // product_batch is done before
1097
                foreach ($elements as $table) {
1098
                    if (!$error) {
1099
                        $sql = "DELETE FROM " . MAIN_DB_PREFIX . $table;
1100
                        $sql .= " WHERE fk_product = " . $id;
1101
1102
                        $result = $this->db->query($sql);
1103
                        if (!$result) {
1104
                            $error++;
1105
                            $this->errors[] = $this->db->lasterror();
1106
                        }
1107
                    }
1108
                }
1109
            }
1110
1111
            if (!$error) {
1112
1113
                include_once DOL_DOCUMENT_ROOT . '/variants/class/ProductCombination.class.php';
1114
                include_once DOL_DOCUMENT_ROOT . '/variants/class/ProductCombination2ValuePair.class.php';
1115
1116
                //If it is a parent product, then we remove the association with child products
1117
                $prodcomb = new ProductCombination($this->db);
1118
1119
                if ($prodcomb->deleteByFkProductParent($user, $id) < 0) {
1120
                    $error++;
1121
                    $this->errors[] = 'Error deleting combinations';
1122
                }
1123
1124
                //We also check if it is a child product
1125
                if (!$error && ($prodcomb->fetchByFkProductChild($id) > 0) && ($prodcomb->delete($user) < 0)) {
1126
                    $error++;
1127
                    $this->errors[] = 'Error deleting child combination';
1128
                }
1129
            }
1130
1131
            // Delete from product_association
1132
            if (!$error) {
1133
                $sql = "DELETE FROM " . MAIN_DB_PREFIX . "product_association";
1134
                $sql .= " WHERE fk_product_pere = " . $id . " OR fk_product_fils = " . $id;
1135
1136
                $result = $this->db->query($sql);
1137
                if (!$result) {
1138
                    $error++;
1139
                    $this->errors[] = $this->db->lasterror();
1140
                }
1141
            }
1142
1143
            // Delete product
1144
            if (!$error) {
1145
                $sqlz = "DELETE FROM " . MAIN_DB_PREFIX . "product";
1146
                $sqlz .= " WHERE rowid = " . $id;
1147
1148
                $resultz = $this->db->query($sqlz);
1149
                if (!$resultz) {
1150
                    $error++;
1151
                    $this->errors[] = $this->db->lasterror();
1152
                }
1153
            }
1154
1155
            if (!$error) {
1156
                // We remove directory
1157
                $ref = dol_sanitizeFileName($this->ref);
1158
                if ($conf->product->dir_output) {
1159
                    $dir = $conf->product->dir_output . "/" . $ref;
1160
                    if (file_exists($dir)) {
1161
                        $res = @dol_delete_dir_recursive($dir);
1162
                        if (!$res) {
1163
                            $this->errors[] = 'ErrorFailToDeleteDir';
1164
                            $error++;
1165
                        }
1166
                    }
1167
                }
1168
            }
1169
1170
            // Remove extrafields
1171
            if ((!$error) && (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED))) { // For avoid conflicts if trigger used
1172
                $result = $this->deleteExtraFields();
1173
                if ($result < 0) {
1174
                    $error++;
1175
                    dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
1176
                }
1177
            }
1178
1179
            if (!$error) {
1180
                $this->db->commit();
1181
                return 1;
1182
            } else {
1183
                foreach ($this->errors as $errmsg) {
1184
                    dol_syslog(get_class($this) . "::delete " . $errmsg, LOG_ERR);
1185
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1186
                }
1187
                $this->db->rollback();
1188
                return -$error;
1189
            }
1190
        } else {
1191
            $this->error = "ErrorRecordIsUsedCantDelete";
1192
            return 0;
1193
        }
1194
    }
1195
1196
    /**
1197
     *    Update or add a translation for a product
1198
     *
1199
     * @param  User $user Object user making update
1200
     * @return int        <0 if KO, >0 if OK
1201
     */
1202
    function setMultiLangs($user)
1203
    {
1204
        global $conf, $langs;
1205
1206
        $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1207
        $current_lang = $langs->getDefaultLang();
1208
1209
        foreach ($langs_available as $key => $value) {
1210
            if ($key == $current_lang) {
1211
                $sql = "SELECT rowid";
1212
                $sql .= " FROM " . MAIN_DB_PREFIX . "product_lang";
1213
                $sql .= " WHERE fk_product=" . $this->id;
1214
                $sql .= " AND lang='" . $key . "'";
1215
1216
                $result = $this->db->query($sql);
1217
1218
                if ($this->db->num_rows($result)) { // if there is already a description line for this language
1219
                    $sql2 = "UPDATE " . MAIN_DB_PREFIX . "product_lang";
1220
                    $sql2 .= " SET ";
1221
                    $sql2 .= " label='" . $this->db->escape($this->label) . "',";
1222
                    $sql2 .= " description='" . $this->db->escape($this->description) . "'";
1223
                    if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1224
                        $sql2 .= ", note='" . $this->db->escape($this->other) . "'";
1225
                    }
1226
                    $sql2 .= " WHERE fk_product=" . $this->id . " AND lang='" . $this->db->escape($key) . "'";
1227
                } else {
1228
                    $sql2 = "INSERT INTO " . MAIN_DB_PREFIX . "product_lang (fk_product, lang, label, description";
1229
                    if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1230
                        $sql2 .= ", note";
1231
                    }
1232
                    $sql2 .= ")";
1233
                    $sql2 .= " VALUES(" . $this->id . ",'" . $this->db->escape($key) . "','" . $this->db->escape($this->label) . "',";
1234
                    $sql2 .= " '" . $this->db->escape($this->description) . "'";
1235
                    if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1236
                        $sql2 .= ", '" . $this->db->escape($this->other) . "'";
1237
                    }
1238
                    $sql2 .= ")";
1239
                }
1240
                dol_syslog(get_class($this) . '::setMultiLangs key = current_lang = ' . $key);
1241
                if (!$this->db->query($sql2)) {
1242
                    $this->error = $this->db->lasterror();
1243
                    return -1;
1244
                }
1245
            } else if (isset($this->multilangs[$key])) {
1246
                $sql = "SELECT rowid";
1247
                $sql .= " FROM " . MAIN_DB_PREFIX . "product_lang";
1248
                $sql .= " WHERE fk_product=" . $this->id;
1249
                $sql .= " AND lang='" . $key . "'";
1250
1251
                $result = $this->db->query($sql);
1252
1253
                if ($this->db->num_rows($result)) { // if there is already a description line for this language
1254
                    $sql2 = "UPDATE " . MAIN_DB_PREFIX . "product_lang";
1255
                    $sql2 .= " SET ";
1256
                    $sql2 .= " label='" . $this->db->escape($this->multilangs["$key"]["label"]) . "',";
1257
                    $sql2 .= " description='" . $this->db->escape($this->multilangs["$key"]["description"]) . "'";
1258
                    if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1259
                        $sql2 .= ", note='" . $this->db->escape($this->multilangs["$key"]["other"]) . "'";
1260
                    }
1261
                    $sql2 .= " WHERE fk_product=" . $this->id . " AND lang='" . $this->db->escape($key) . "'";
1262
                } else {
1263
                    $sql2 = "INSERT INTO " . MAIN_DB_PREFIX . "product_lang (fk_product, lang, label, description";
1264
                    if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1265
                        $sql2 .= ", note";
1266
                    }
1267
                    $sql2 .= ")";
1268
                    $sql2 .= " VALUES(" . $this->id . ",'" . $this->db->escape($key) . "','" . $this->db->escape($this->multilangs["$key"]["label"]) . "',";
1269
                    $sql2 .= " '" . $this->db->escape($this->multilangs["$key"]["description"]) . "'";
1270
                    if (!empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) {
1271
                        $sql2 .= ", '" . $this->db->escape($this->multilangs["$key"]["other"]) . "'";
1272
                    }
1273
                    $sql2 .= ")";
1274
                }
1275
1276
                // We do not save if main fields are empty
1277
                if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
1278
                    if (!$this->db->query($sql2)) {
1279
                        $this->error = $this->db->lasterror();
1280
                        return -1;
1281
                    }
1282
                }
1283
            } else {
1284
                // language is not current language and we didn't provide a multilang description for this language
1285
            }
1286
        }
1287
1288
        // Call trigger
1289
        $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
1290
        if ($result < 0) {
1291
            $this->error = $this->db->lasterror();
1292
            return -1;
1293
        }
1294
        // End call triggers
1295
1296
        return 1;
1297
    }
1298
1299
    /**
1300
     *    Delete a language for this product
1301
     *
1302
     * @param string $langtodelete Language code to delete
1303
     * @param User   $user         Object user making delete
1304
     *
1305
     * @return int                            <0 if KO, >0 if OK
1306
     */
1307
    function delMultiLangs($langtodelete, $user)
1308
    {
1309
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "product_lang";
1310
        $sql .= " WHERE fk_product=" . $this->id . " AND lang='" . $this->db->escape($langtodelete) . "'";
1311
1312
        dol_syslog(get_class($this) . '::delMultiLangs', LOG_DEBUG);
1313
        $result = $this->db->query($sql);
1314
        if ($result) {
1315
            // Call trigger
1316
            $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
1317
            if ($result < 0) {
1318
                $this->error = $this->db->lasterror();
1319
                dol_syslog(get_class($this) . '::delMultiLangs error=' . $this->error, LOG_ERR);
1320
                return -1;
1321
            }
1322
            // End call triggers
1323
            return 1;
1324
        } else {
1325
            $this->error = $this->db->lasterror();
1326
            dol_syslog(get_class($this) . '::delMultiLangs error=' . $this->error, LOG_ERR);
1327
            return -1;
1328
        }
1329
    }
1330
    /*
1331
     * Sets an accountancy code for a product.
1332
     * Also calls PRODUCT_MODIFY trigger when modified
1333
     *
1334
     * @param string $type It can be 'buy', 'sell', 'sell_intra' or 'sell_export'
1335
     * @param string $value Accountancy code
1336
     * @return int <0 KO >0 OK
1337
     */
1338
1339
    public function setAccountancyCode($type, $value)
1340
    {
1341
        global $user, $langs, $conf;
1342
1343
        $this->db->begin();
1344
1345
        if ($type == 'buy') {
1346
            $field = 'accountancy_code_buy';
1347
        } elseif ($type == 'sell') {
1348
            $field = 'accountancy_code_sell';
1349
        } elseif ($type == 'sell_intra') {
1350
            $field = 'accountancy_code_sell_intra';
1351
        } elseif ($type == 'sell_export') {
1352
            $field = 'accountancy_code_sell_export';
1353
        } else {
1354
            return -1;
1355
        }
1356
1357
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element . " SET ";
1358
        $sql .= "$field = '" . $this->db->escape($value) . "'";
1359
        $sql .= " WHERE rowid = " . $this->id;
1360
1361
        dol_syslog(get_class($this) . "::" . __FUNCTION__ . " sql=" . $sql, LOG_DEBUG);
1362
        $resql = $this->db->query($sql);
1363
1364
        if ($resql) {
1365
            // Call triggers
1366
            include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
1367
            $interface = new Interfaces($this->db);
1368
            $result = $interface->run_triggers('PRODUCT_MODIFY', $this, $user, $langs, $conf);
1369
            if ($result < 0) {
1370
                $this->errors = $interface->errors;
1371
                $this->db->rollback();
1372
                return -1;
1373
            }
1374
            // End call triggers
1375
1376
            $this->$field = $value;
1377
1378
            $this->db->commit();
1379
            return 1;
1380
        } else {
1381
            $this->error = $this->db->lasterror();
1382
            $this->db->rollback();
1383
            return -1;
1384
        }
1385
    }
1386
1387
    /**
1388
     *    Load array this->multilangs
1389
     *
1390
     * @return int        <0 if KO, >0 if OK
1391
     */
1392
    function getMultiLangs()
1393
    {
1394
        global $langs;
1395
1396
        $current_lang = $langs->getDefaultLang();
1397
1398
        $sql = "SELECT lang, label, description, note as other";
1399
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_lang";
1400
        $sql .= " WHERE fk_product=" . $this->id;
1401
1402
        $result = $this->db->query($sql);
1403
        if ($result) {
1404
            while ($obj = $this->db->fetch_object($result)) {
1405
                //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1406
                if ($obj->lang == $current_lang) {  // si on a les traduct. dans la langue courante on les charge en infos principales.
1407
                    $this->label = $obj->label;
1408
                    $this->description = $obj->description;
1409
                    $this->other = $obj->other;
1410
                }
1411
                $this->multilangs["$obj->lang"]["label"] = $obj->label;
1412
                $this->multilangs["$obj->lang"]["description"] = $obj->description;
1413
                $this->multilangs["$obj->lang"]["other"] = $obj->other;
1414
            }
1415
            return 1;
1416
        } else {
1417
            $this->error = "Error: " . $this->db->lasterror() . " - " . $sql;
1418
            return -1;
1419
        }
1420
    }
1421
1422
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1423
    /**
1424
     *  Insert a track that we changed a customer price
1425
     *
1426
     * @param  User $user  User making change
1427
     * @param  int  $level price level to change
1428
     * @return int                    <0 if KO, >0 if OK
1429
     */
1430
    function _log_price($user, $level = 0)
1431
    {
1432
        // phpcs:enable
1433
        global $conf;
1434
1435
        $now = dol_now();
1436
1437
        // Clean parameters
1438
        if (empty($this->price_by_qty)) {
1439
            $this->price_by_qty = 0;
1440
        }
1441
1442
        // Add new price
1443
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "product_price(price_level,date_price, fk_product, fk_user_author, price, price_ttc, price_base_type,tosell, tva_tx, default_vat_code, recuperableonly,";
1444
        $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1445
        $sql .= " VALUES(" . ($level ? $level : 1) . ", '" . $this->db->idate($now) . "'," . $this->id . "," . $user->id . "," . $this->price . "," . $this->price_ttc . ",'" . $this->db->escape($this->price_base_type) . "'," . $this->status . "," . $this->tva_tx . ", " . ($this->default_vat_code ? ("'" . $this->db->escape($this->default_vat_code) . "'") : "null") . "," . $this->tva_npr . ",";
1446
        $sql .= " " . $this->localtax1_tx . ", " . $this->localtax2_tx . ", '" . $this->db->escape($this->localtax1_type) . "', '" . $this->db->escape($this->localtax2_type) . "', " . $this->price_min . "," . $this->price_min_ttc . "," . $this->price_by_qty . "," . $conf->entity . "," . ($this->fk_price_expression > 0 ? $this->fk_price_expression : 'null');
1447
        $sql .= ")";
1448
1449
        dol_syslog(get_class($this) . "::_log_price", LOG_DEBUG);
1450
        $resql = $this->db->query($sql);
1451
        if (!$resql) {
1452
            $this->error = $this->db->lasterror();
1453
            dol_print_error($this->db);
1454
            return -1;
1455
        } else {
1456
            return 1;
1457
        }
1458
    }
1459
1460
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1461
    /**
1462
     *  Delete a price line
1463
     *
1464
     * @param  User $user  Object user
1465
     * @param  int  $rowid Line id to delete
1466
     * @return int                <0 if KO, >0 if OK
1467
     */
1468
    function log_price_delete($user, $rowid)
1469
    {
1470
        // phpcs:enable
1471
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "product_price_by_qty";
1472
        $sql .= " WHERE fk_product_price=" . $rowid;
1473
        $resql = $this->db->query($sql);
1474
1475
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "product_price";
1476
        $sql .= " WHERE rowid=" . $rowid;
1477
        $resql = $this->db->query($sql);
1478
        if ($resql) {
1479
            return 1;
1480
        } else {
1481
            $this->error = $this->db->lasterror();
1482
            return -1;
1483
        }
1484
    }
1485
1486
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
1487
    /**
1488
     *    Read price used by a provider.
1489
     *    We enter as input couple prodfournprice/qty or triplet qty/product_id/fourn_ref.
1490
     *  This also set some properties on product like ->buyprice, ->fourn_pu, ...
1491
     *
1492
     * @param  int    $prodfournprice Id du tarif = rowid table product_fournisseur_price
1493
     * @param  double $qty            Quantity asked or -1 to get first entry found
1494
     * @param  int    $product_id     Filter on a particular product id
1495
     * @param  string $fourn_ref      Filter on a supplier price ref. 'none' to exclude ref in search.
1496
     * @param  int    $fk_soc         If of supplier
1497
     * @return int                         <-1 if KO, -1 if qty not enough, 0 if OK but nothing found, id_product if OK and found. May also initialize some properties like (->ref_supplier, buyprice, fourn_pu, vatrate_supplier...)
1498
     */
1499
    function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
1500
    {
1501
        // phpcs:enable
1502
        global $conf;
1503
        $result = 0;
1504
1505
        // We do a first seach with a select by searching with couple prodfournprice and qty only (later we will search on triplet qty/product_id/fourn_ref)
1506
        $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent,";
1507
        $sql .= " pfp.fk_product, pfp.ref_fourn, pfp.desc_fourn, pfp.fk_soc, pfp.tva_tx, pfp.fk_supplier_price_expression";
1508
        $sql .= " ,pfp.default_vat_code";
1509
        $sql .= " ,pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code";
1510
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_fournisseur_price as pfp";
1511
        $sql .= " WHERE pfp.rowid = " . $prodfournprice;
1512
        if ($qty > 0) {
1513
            $sql .= " AND pfp.quantity <= " . $qty;
1514
        }
1515
        $sql .= " ORDER BY pfp.quantity DESC";
1516
1517
        dol_syslog(get_class($this) . "::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
1518
        $resql = $this->db->query($sql);
1519
        if ($resql) {
1520
            $obj = $this->db->fetch_object($resql);
1521
            if ($obj && $obj->quantity > 0) {        // If we found a supplier prices from the id of supplier price
1522
                if (!empty($conf->dynamicprices->enabled) && !empty($obj->fk_supplier_price_expression)) {
1523
                    include_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
1524
                    $prod_supplier = new ProductFournisseur($this->db);
1525
                    $prod_supplier->product_fourn_price_id = $obj->rowid;
1526
                    $prod_supplier->id = $obj->fk_product;
1527
                    $prod_supplier->fourn_qty = $obj->quantity;
1528
                    $prod_supplier->fourn_tva_tx = $obj->tva_tx;
1529
                    $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
1530
                    $priceparser = new PriceParser($this->db);
1531
                    $price_result = $priceparser->parseProductSupplier($prod_supplier);
1532
                    if ($price_result >= 0) {
1533
                        $obj->price = $price_result;
1534
                    }
1535
                }
1536
                $this->product_fourn_price_id = $obj->rowid;
1537
                $this->buyprice = $obj->price;                      // deprecated
1538
                $this->fourn_pu = $obj->price / $obj->quantity;     // Unit price of product of supplier
1539
                $this->fourn_price_base_type = 'HT';                // Price base type
1540
                $this->fourn_socid = $obj->fk_soc;                  // Company that offer this price
1541
                $this->ref_fourn = $obj->ref_fourn;                 // deprecated
1542
                $this->ref_supplier = $obj->ref_fourn;              // Ref supplier
1543
                $this->desc_supplier = $obj->desc_fourn;            // desc supplier
1544
                $this->remise_percent = $obj->remise_percent;       // remise percent if present and not typed
1545
                $this->vatrate_supplier = $obj->tva_tx;             // Vat ref supplier
1546
                $this->default_vat_code = $obj->default_vat_code;   // Vat code supplier
1547
                $this->fourn_multicurrency_price = $obj->multicurrency_price;
1548
                $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
1549
                $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
1550
                $this->fourn_multicurrency_id = $obj->fk_multicurrency;
1551
                $this->fourn_multicurrency_code = $obj->multicurrency_code;
1552
                $result = $obj->fk_product;
1553
                return $result;
1554
            } else { // If not found
1555
                // We do a second search by doing a select again but searching with less reliable criteria: couple qty/id product, and if set fourn_ref or fk_soc.
1556
                $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.fk_soc,";
1557
                $sql .= " pfp.fk_product, pfp.ref_fourn as ref_supplier, pfp.desc_fourn as desc_supplier, pfp.tva_tx, pfp.fk_supplier_price_expression";
1558
                $sql .= " ,pfp.default_vat_code";
1559
                $sql .= " ,pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code";
1560
                $sql .= " FROM " . MAIN_DB_PREFIX . "product_fournisseur_price as pfp";
1561
                $sql .= " WHERE pfp.fk_product = " . $product_id;
1562
                if ($fourn_ref != 'none') {
1563
                    $sql .= " AND pfp.ref_fourn = '" . $fourn_ref . "'";
1564
                }
1565
                if ($fk_soc > 0) {
1566
                    $sql .= " AND pfp.fk_soc = " . $fk_soc;
1567
                }
1568
                if ($qty > 0) {
1569
                    $sql .= " AND pfp.quantity <= " . $qty;
1570
                }
1571
                $sql .= " ORDER BY pfp.quantity DESC";
1572
                $sql .= " LIMIT 1";
1573
1574
                dol_syslog(get_class($this) . "::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
1575
                $resql = $this->db->query($sql);
1576
                if ($resql) {
1577
                    $obj = $this->db->fetch_object($resql);
1578
                    if ($obj && $obj->quantity > 0) {        // If found
1579
                        if (!empty($conf->dynamicprices->enabled) && !empty($obj->fk_supplier_price_expression)) {
1580
                            include_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
1581
                            $prod_supplier = new ProductFournisseur($this->db);
1582
                            $prod_supplier->product_fourn_price_id = $obj->rowid;
1583
                            $prod_supplier->id = $obj->fk_product;
1584
                            $prod_supplier->fourn_qty = $obj->quantity;
1585
                            $prod_supplier->fourn_tva_tx = $obj->tva_tx;
1586
                            $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
1587
                            $priceparser = new PriceParser($this->db);
1588
                            $price_result = $priceparser->parseProductSupplier($prod_supplier);
1589
                            if ($result >= 0) {
1590
                                $obj->price = $price_result;
1591
                            }
1592
                        }
1593
                        $this->product_fourn_price_id = $obj->rowid;
1594
                        $this->buyprice = $obj->price;                      // deprecated
1595
                        $this->fourn_qty = $obj->quantity;                    // min quantity for price for a virtual supplier
1596
                        $this->fourn_pu = $obj->price / $obj->quantity;     // Unit price of product for a virtual supplier
1597
                        $this->fourn_price_base_type = 'HT';                // Price base type for a virtual supplier
1598
                        $this->fourn_socid = $obj->fk_soc;                  // Company that offer this price
1599
                        $this->ref_fourn = $obj->ref_supplier;              // deprecated
1600
                        $this->ref_supplier = $obj->ref_supplier;           // Ref supplier
1601
                        $this->desc_supplier = $obj->desc_supplier;         // desc supplier
1602
                        $this->remise_percent = $obj->remise_percent;       // remise percent if present and not typed
1603
                        $this->vatrate_supplier = $obj->tva_tx;             // Vat ref supplier
1604
                        $this->default_vat_code = $obj->default_vat_code;   // Vat code supplier
1605
                        $this->fourn_multicurrency_price = $obj->multicurrency_price;
1606
                        $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
1607
                        $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
1608
                        $this->fourn_multicurrency_id = $obj->fk_multicurrency;
1609
                        $this->fourn_multicurrency_code = $obj->multicurrency_code;
1610
                        $result = $obj->fk_product;
1611
                        return $result;
1612
                    } else {
1613
                        return -1;    // Ce produit n'existe pas avec cet id tarif fournisseur ou existe mais qte insuffisante, ni pour le couple produit/ref fournisseur dans la quantité.
1614
                    }
1615
                } else {
1616
                    $this->error = $this->db->lasterror();
1617
                    return -3;
1618
                }
1619
            }
1620
        } else {
1621
            $this->error = $this->db->lasterror();
1622
            return -2;
1623
        }
1624
    }
1625
1626
    /**
1627
     *    Modify customer price of a product/Service
1628
     *
1629
     * @param  double $newprice          New price
1630
     * @param  string $newpricebase      HT or TTC
1631
     * @param  User   $user              Object user that make change
1632
     * @param  double $newvat            New VAT Rate (For example 8.5. Should not be a string)
1633
     * @param  double $newminprice       New price min
1634
     * @param  int    $level             0=standard, >0 = level if multilevel prices
1635
     * @param  int    $newnpr            0=Standard vat rate, 1=Special vat rate for French NPR VAT
1636
     * @param  int    $newpbq            1 if it has price by quantity
1637
     * @param  int    $ignore_autogen    Used to avoid infinite loops
1638
     * @param  array  $localtaxes_array  Array with localtaxes info array('0'=>type1,'1'=>rate1,'2'=>type2,'3'=>rate2) (loaded by getLocalTaxesFromRate(vatrate, 0, ...) function).
1639
     * @param  string $newdefaultvatcode Default vat code
1640
     * @return int                            <0 if KO, >0 if OK
1641
     */
1642
    function updatePrice($newprice, $newpricebase, $user, $newvat = '', $newminprice = 0, $level = 0, $newnpr = 0, $newpbq = 0, $ignore_autogen = 0, $localtaxes_array = array(), $newdefaultvatcode = '')
1643
    {
1644
        global $conf, $langs;
1645
1646
        $id = $this->id;
1647
1648
        dol_syslog(get_class($this) . "::update_price id=" . $id . " newprice=" . $newprice . " newpricebase=" . $newpricebase . " newminprice=" . $newminprice . " level=" . $level . " npr=" . $newnpr . " newdefaultvatcode=" . $newdefaultvatcode);
1649
1650
        // Clean parameters
1651
        if (empty($this->tva_tx)) {
1652
            $this->tva_tx = 0;
1653
        }
1654
        if (empty($newnpr)) {
1655
            $newnpr = 0;
1656
        }
1657
1658
        // Check parameters
1659
        if ($newvat == '') {
1660
            $newvat = $this->tva_tx;
1661
        }
1662
1663
        // If multiprices are enabled, then we check if the current product is subject to price autogeneration
1664
        // Price will be modified ONLY when the first one is the one that is being modified
1665
        if ((!empty($conf->global->PRODUIT_MULTIPRICES) || !empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
1666
            return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
1667
        }
1668
1669
        if (!empty($newminprice) && ($newminprice > $newprice)) {
1670
            $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
1671
            return -1;
1672
        }
1673
1674
        if ($newprice !== '' || $newprice === 0) {
1675
            if ($newpricebase == 'TTC') {
1676
                $price_ttc = price2num($newprice, 'MU');
1677
                $price = price2num($newprice) / (1 + ($newvat / 100));
1678
                $price = price2num($price, 'MU');
1679
1680
                if ($newminprice != '' || $newminprice == 0) {
1681
                    $price_min_ttc = price2num($newminprice, 'MU');
1682
                    $price_min = price2num($newminprice) / (1 + ($newvat / 100));
1683
                    $price_min = price2num($price_min, 'MU');
1684
                } else {
1685
                    $price_min = 0;
1686
                    $price_min_ttc = 0;
1687
                }
1688
            } else {
1689
                $price = price2num($newprice, 'MU');
1690
                $price_ttc = ( $newnpr != 1 ) ? price2num($newprice) * (1 + ($newvat / 100)) : $price;
1691
                $price_ttc = price2num($price_ttc, 'MU');
1692
1693
                if ($newminprice !== '' || $newminprice === 0) {
1694
                    $price_min = price2num($newminprice, 'MU');
1695
                    $price_min_ttc = price2num($newminprice) * (1 + ($newvat / 100));
1696
                    $price_min_ttc = price2num($price_min_ttc, 'MU');
1697
                    //print 'X'.$newminprice.'-'.$price_min;
1698
                } else {
1699
                    $price_min = 0;
1700
                    $price_min_ttc = 0;
1701
                }
1702
            }
1703
            //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
1704
1705
            if (count($localtaxes_array) > 0) {
1706
                $localtaxtype1 = $localtaxes_array['0'];
1707
                $localtax1 = $localtaxes_array['1'];
1708
                $localtaxtype2 = $localtaxes_array['2'];
1709
                $localtax2 = $localtaxes_array['3'];
1710
            } else {     // old method. deprecated because ot can't retreive type
1711
                $localtaxtype1 = '0';
1712
                $localtax1 = get_localtax($newvat, 1);
1713
                $localtaxtype2 = '0';
1714
                $localtax2 = get_localtax($newvat, 2);
1715
            }
1716
            if (empty($localtax1)) {
1717
                $localtax1 = 0;    // If = '' then = 0
1718
            }
1719
            if (empty($localtax2)) {
1720
                $localtax2 = 0;    // If = '' then = 0
1721
            }
1722
1723
            $this->db->begin();
1724
1725
            // Ne pas mettre de quote sur les numeriques decimaux.
1726
            // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
1727
            $sql = "UPDATE " . MAIN_DB_PREFIX . "product SET";
1728
            $sql .= " price_base_type='" . $newpricebase . "',";
1729
            $sql .= " price=" . $price . ",";
1730
            $sql .= " price_ttc=" . $price_ttc . ",";
1731
            $sql .= " price_min=" . $price_min . ",";
1732
            $sql .= " price_min_ttc=" . $price_min_ttc . ",";
1733
            $sql .= " localtax1_tx=" . ($localtax1 >= 0 ? $localtax1 : 'NULL') . ",";
1734
            $sql .= " localtax2_tx=" . ($localtax2 >= 0 ? $localtax2 : 'NULL') . ",";
1735
            $sql .= " localtax1_type=" . ($localtaxtype1 != '' ? "'" . $localtaxtype1 . "'" : "'0'") . ",";
1736
            $sql .= " localtax2_type=" . ($localtaxtype2 != '' ? "'" . $localtaxtype2 . "'" : "'0'") . ",";
1737
            $sql .= " default_vat_code=" . ($newdefaultvatcode ? "'" . $this->db->escape($newdefaultvatcode) . "'" : "null") . ",";
1738
            $sql .= " tva_tx='" . price2num($newvat) . "',";
1739
            $sql .= " recuperableonly='" . $newnpr . "'";
1740
            $sql .= " WHERE rowid = " . $id;
1741
1742
            dol_syslog(get_class($this) . "::update_price", LOG_DEBUG);
1743
            $resql = $this->db->query($sql);
1744
            if ($resql) {
1745
                $this->multiprices[$level] = $price;
1746
                $this->multiprices_ttc[$level] = $price_ttc;
1747
                $this->multiprices_min[$level] = $price_min;
1748
                $this->multiprices_min_ttc[$level] = $price_min_ttc;
1749
                $this->multiprices_base_type[$level] = $newpricebase;
1750
                $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
1751
                $this->multiprices_tva_tx[$level] = $newvat;
1752
                $this->multiprices_recuperableonly[$level] = $newnpr;
1753
1754
                $this->price = $price;
1755
                $this->price_ttc = $price_ttc;
1756
                $this->price_min = $price_min;
1757
                $this->price_min_ttc = $price_min_ttc;
1758
                $this->price_base_type = $newpricebase;
1759
                $this->default_vat_code = $newdefaultvatcode;
1760
                $this->tva_tx = $newvat;
1761
                $this->tva_npr = $newnpr;
1762
                //Local taxes
1763
                $this->localtax1_tx = $localtax1;
1764
                $this->localtax2_tx = $localtax2;
1765
                $this->localtax1_type = $localtaxtype1;
1766
                $this->localtax2_type = $localtaxtype2;
1767
1768
                // Price by quantity
1769
                $this->price_by_qty = $newpbq;
1770
1771
                $this->_log_price($user, $level);    // Save price for level into table product_price
1772
1773
                $this->level = $level;                // Store level of price edited for trigger
1774
                // Call trigger
1775
                $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
1776
                if ($result < 0) {
1777
                    $this->db->rollback();
1778
                    return -1;
1779
                }
1780
                // End call triggers
1781
1782
                $this->db->commit();
1783
            } else {
1784
                $this->db->rollback();
1785
                dol_print_error($this->db);
1786
            }
1787
        }
1788
1789
        return 1;
1790
    }
1791
1792
    /**
1793
     *  Sets the supplier price expression
1794
     *
1795
     * @param      int $expression_id Expression
1796
     * @return     int                     <0 if KO, >0 if OK
1797
     * @deprecated Use Product::update instead
1798
     */
1799
    function setPriceExpression($expression_id)
1800
    {
1801
        global $user;
1802
1803
        $this->fk_price_expression = $expression_id;
1804
1805
        return $this->update($this->id, $user);
1806
    }
1807
1808
    /**
1809
     *  Load a product in memory from database
1810
     *
1811
     * @param  int    $id                Id of product/service to load
1812
     * @param  string $ref               Ref of product/service to load
1813
     * @param  string $ref_ext           Ref ext of product/service to load
1814
     * @param  string $barcode           Barcode of product/service to load
1815
     * @param  int    $ignore_expression Ignores the math expression for calculating price and uses the db value instead
1816
     * @return int                         <0 if KO, 0 if not found, >0 if OK
1817
     */
1818
    function fetch($id = '', $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0)
1819
    {
1820
        include_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
1821
1822
        global $langs, $conf;
1823
1824
        dol_syslog(get_class($this) . "::fetch id=" . $id . " ref=" . $ref . " ref_ext=" . $ref_ext);
1825
1826
        // Check parameters
1827
        if (!$id && !$ref && !$ref_ext && !$barcode) {
1828
            $this->error = 'ErrorWrongParameters';
1829
            dol_syslog(get_class($this) . "::fetch " . $this->error);
1830
            return -1;
1831
        }
1832
1833
        $sql = "SELECT rowid, ref, ref_ext, label, description, url, note as note_private, customcode, fk_country, price, price_ttc,";
1834
        $sql .= " price_min, price_min_ttc, price_base_type, cost_price, default_vat_code, tva_tx, recuperableonly as tva_npr, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, tosell,";
1835
        $sql .= " tobuy, fk_product_type, duration, fk_default_warehouse, seuil_stock_alerte, canvas, weight, weight_units,";
1836
        $sql .= " length, length_units, width, width_units, height, height_units,";
1837
        $sql .= " surface, surface_units, volume, volume_units, barcode, fk_barcode_type, finished,";
1838
        $sql .= " accountancy_code_buy, accountancy_code_sell, accountancy_code_sell_intra, accountancy_code_sell_export, stock, pmp,";
1839
        $sql .= " datec, tms, import_key, entity, desiredstock, tobatch, fk_unit,";
1840
        $sql .= " fk_price_expression, price_autogen";
1841
        $sql .= " FROM " . MAIN_DB_PREFIX . "product";
1842
        if ($id) {
1843
            $sql .= " WHERE rowid = " . $this->db->escape($id);
1844
        } else {
1845
            $sql .= " WHERE entity IN (" . getEntity($this->element) . ")";
1846
            if ($ref) {
1847
                $sql .= " AND ref = '" . $this->db->escape($ref) . "'";
1848
            } else if ($ref_ext) {
1849
                $sql .= " AND ref_ext = '" . $this->db->escape($ref_ext) . "'";
1850
            } else if ($barcode) {
1851
                $sql .= " AND barcode = '" . $this->db->escape($barcode) . "'";
1852
            }
1853
        }
1854
1855
        $resql = $this->db->query($sql);
1856
        if ($resql) {
1857
            if ($this->db->num_rows($resql) > 0) {
1858
                $obj = $this->db->fetch_object($resql);
1859
1860
                $this->id = $obj->rowid;
1861
                $this->ref = $obj->ref;
1862
                $this->ref_ext = $obj->ref_ext;
1863
                $this->label = $obj->label;
1864
                $this->description = $obj->description;
1865
                $this->url = $obj->url;
1866
                $this->note_private = $obj->note_private;
1867
                $this->note = $obj->note_private;  // deprecated
1868
1869
                $this->type = $obj->fk_product_type;
1870
                $this->status = $obj->tosell;
1871
                $this->status_buy = $obj->tobuy;
1872
                $this->status_batch = $obj->tobatch;
1873
1874
                $this->customcode = $obj->customcode;
1875
                $this->country_id = $obj->fk_country;
1876
                $this->country_code = getCountry($this->country_id, 2, $this->db);
1877
                $this->price = $obj->price;
1878
                $this->price_ttc = $obj->price_ttc;
1879
                $this->price_min = $obj->price_min;
1880
                $this->price_min_ttc = $obj->price_min_ttc;
1881
                $this->price_base_type = $obj->price_base_type;
1882
                $this->cost_price = $obj->cost_price;
1883
                $this->default_vat_code = $obj->default_vat_code;
1884
                $this->tva_tx = $obj->tva_tx;
1885
                //! French VAT NPR
1886
                $this->tva_npr = $obj->tva_npr;
1887
                $this->recuperableonly = $obj->tva_npr;       // For backward compatibility
1888
                //! Local taxes
1889
                $this->localtax1_tx = $obj->localtax1_tx;
1890
                $this->localtax2_tx = $obj->localtax2_tx;
1891
                $this->localtax1_type = $obj->localtax1_type;
1892
                $this->localtax2_type = $obj->localtax2_type;
1893
1894
                $this->finished = $obj->finished;
1895
                $this->duration = $obj->duration;
1896
                $this->duration_value = substr($obj->duration, 0, dol_strlen($obj->duration) - 1);
1897
                $this->duration_unit = substr($obj->duration, -1);
1898
                $this->canvas = $obj->canvas;
1899
                $this->weight = $obj->weight;
1900
                $this->weight_units = $obj->weight_units;
1901
                $this->length = $obj->length;
1902
                $this->length_units = $obj->length_units;
1903
                $this->width = $obj->width;
1904
                $this->width_units = $obj->width_units;
1905
                $this->height = $obj->height;
1906
                $this->height_units = $obj->height_units;
1907
1908
                $this->surface = $obj->surface;
1909
                $this->surface_units = $obj->surface_units;
1910
                $this->volume = $obj->volume;
1911
                $this->volume_units = $obj->volume_units;
1912
                $this->barcode = $obj->barcode;
1913
                $this->barcode_type = $obj->fk_barcode_type;
1914
1915
                $this->accountancy_code_buy = $obj->accountancy_code_buy;
1916
                $this->accountancy_code_sell = $obj->accountancy_code_sell;
1917
                $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
1918
                $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
1919
1920
                $this->fk_default_warehouse = $obj->fk_default_warehouse;
1921
                $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
1922
                $this->desiredstock = $obj->desiredstock;
1923
                $this->stock_reel = $obj->stock;
1924
                $this->pmp = $obj->pmp;
1925
1926
                $this->date_creation = $obj->datec;
1927
                $this->date_modification = $obj->tms;
1928
                $this->import_key = $obj->import_key;
1929
                $this->entity = $obj->entity;
1930
1931
                $this->ref_ext = $obj->ref_ext;
1932
                $this->fk_price_expression = $obj->fk_price_expression;
1933
                $this->fk_unit = $obj->fk_unit;
1934
                $this->price_autogen = $obj->price_autogen;
1935
1936
                $this->db->free($resql);
1937
1938
                // Retreive all extrafield
1939
                // fetch optionals attributes and labels
1940
                $this->fetch_optionals();
1941
1942
                // multilangs
1943
                if (!empty($conf->global->MAIN_MULTILANGS)) {
1944
                    $this->getMultiLangs();
1945
                }
1946
1947
                // Load multiprices array
1948
                if (!empty($conf->global->PRODUIT_MULTIPRICES)) {                // prices per segment
1949
                    for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
1950
                        $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
1951
                        $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
1952
                        $sql .= " FROM " . MAIN_DB_PREFIX . "product_price";
1953
                        $sql .= " WHERE entity IN (" . getEntity('productprice') . ")";
1954
                        $sql .= " AND price_level=" . $i;
1955
                        $sql .= " AND fk_product = " . $this->id;
1956
                        $sql .= " ORDER BY date_price DESC, rowid DESC";
1957
                        $sql .= " LIMIT 1";
1958
                        $resql = $this->db->query($sql);
1959
                        if ($resql) {
1960
                            $result = $this->db->fetch_array($resql);
1961
1962
                            $this->multiprices[$i] = $result["price"];
1963
                            $this->multiprices_ttc[$i] = $result["price_ttc"];
1964
                            $this->multiprices_min[$i] = $result["price_min"];
1965
                            $this->multiprices_min_ttc[$i] = $result["price_min_ttc"];
1966
                            $this->multiprices_base_type[$i] = $result["price_base_type"];
1967
                            // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
1968
                            $this->multiprices_tva_tx[$i] = $result["tva_tx"];     // TODO Add ' ('.$result['default_vat_code'].')'
1969
                            $this->multiprices_recuperableonly[$i] = $result["recuperableonly"];
1970
1971
                            // Price by quantity
1972
                            /*
1973
                              $this->prices_by_qty[$i]=$result["price_by_qty"];
1974
                              $this->prices_by_qty_id[$i]=$result["rowid"];
1975
                              // Récuperation de la liste des prix selon qty si flag positionné
1976
                              if ($this->prices_by_qty[$i] == 1)
1977
                              {
1978
                              $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
1979
                              $sql.= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
1980
                              $sql.= " WHERE fk_product_price = ".$this->prices_by_qty_id[$i];
1981
                              $sql.= " ORDER BY quantity ASC";
1982
                              $resultat=array();
1983
                              $resql = $this->db->query($sql);
1984
                              if ($resql)
1985
                              {
1986
                              $ii=0;
1987
                              while ($result= $this->db->fetch_array($resql)) {
1988
                              $resultat[$ii]=array();
1989
                              $resultat[$ii]["rowid"]=$result["rowid"];
1990
                              $resultat[$ii]["price"]= $result["price"];
1991
                              $resultat[$ii]["unitprice"]= $result["unitprice"];
1992
                              $resultat[$ii]["quantity"]= $result["quantity"];
1993
                              $resultat[$ii]["remise_percent"]= $result["remise_percent"];
1994
                              $resultat[$ii]["remise"]= $result["remise"];                    // deprecated
1995
                              $resultat[$ii]["price_base_type"]= $result["price_base_type"];
1996
                              $ii++;
1997
                              }
1998
                              $this->prices_by_qty_list[$i]=$resultat;
1999
                              }
2000
                              else
2001
                              {
2002
                              dol_print_error($this->db);
2003
                              return -1;
2004
                              }
2005
                              } */
2006
                        } else {
2007
                            dol_print_error($this->db);
2008
                            return -1;
2009
                        }
2010
                    }
2011
                } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {            // prices per customers
2012
                    // Nothing loaded by default. List may be very long.
2013
                } else if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY)) {    // prices per quantity
2014
                    $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2015
                    $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2016
                    $sql .= " FROM " . MAIN_DB_PREFIX . "product_price";
2017
                    $sql .= " WHERE fk_product = " . $this->id;
2018
                    $sql .= " ORDER BY date_price DESC, rowid DESC";
2019
                    $sql .= " LIMIT 1";
2020
                    $resql = $this->db->query($sql);
2021
                    if ($resql) {
2022
                        $result = $this->db->fetch_array($resql);
2023
2024
                        // Price by quantity
2025
                        $this->prices_by_qty[0] = $result["price_by_qty"];
2026
                        $this->prices_by_qty_id[0] = $result["rowid"];
2027
                        // Récuperation de la liste des prix selon qty si flag positionné
2028
                        if ($this->prices_by_qty[0] == 1) {
2029
                            $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2030
                            $sql .= " FROM " . MAIN_DB_PREFIX . "product_price_by_qty";
2031
                            $sql .= " WHERE fk_product_price = " . $this->prices_by_qty_id[0];
2032
                            $sql .= " ORDER BY quantity ASC";
2033
                            $resultat = array();
2034
                            $resql = $this->db->query($sql);
2035
                            if ($resql) {
2036
                                $ii = 0;
2037
                                while ($result = $this->db->fetch_array($resql)) {
2038
                                    $resultat[$ii] = array();
2039
                                    $resultat[$ii]["rowid"] = $result["rowid"];
2040
                                    $resultat[$ii]["price"] = $result["price"];
2041
                                    $resultat[$ii]["unitprice"] = $result["unitprice"];
2042
                                    $resultat[$ii]["quantity"] = $result["quantity"];
2043
                                    $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2044
                                    //$resultat[$ii]["remise"]= $result["remise"];                    // deprecated
2045
                                    $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2046
                                    $ii++;
2047
                                }
2048
                                $this->prices_by_qty_list[0] = $resultat;
2049
                            } else {
2050
                                dol_print_error($this->db);
2051
                                return -1;
2052
                            }
2053
                        }
2054
                    } else {
2055
                        dol_print_error($this->db);
2056
                        return -1;
2057
                    }
2058
                } else if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) {    // prices per customer and quantity
2059
                    for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
2060
                        $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2061
                        $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2062
                        $sql .= " FROM " . MAIN_DB_PREFIX . "product_price";
2063
                        $sql .= " WHERE entity IN (" . getEntity('productprice') . ")";
2064
                        $sql .= " AND price_level=" . $i;
2065
                        $sql .= " AND fk_product = " . $this->id;
2066
                        $sql .= " ORDER BY date_price DESC, rowid DESC";
2067
                        $sql .= " LIMIT 1";
2068
                        $resql = $this->db->query($sql);
2069
                        if ($resql) {
2070
                            $result = $this->db->fetch_array($resql);
2071
2072
                            $this->multiprices[$i] = $result["price"];
2073
                            $this->multiprices_ttc[$i] = $result["price_ttc"];
2074
                            $this->multiprices_min[$i] = $result["price_min"];
2075
                            $this->multiprices_min_ttc[$i] = $result["price_min_ttc"];
2076
                            $this->multiprices_base_type[$i] = $result["price_base_type"];
2077
                            // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2078
                            $this->multiprices_tva_tx[$i] = $result["tva_tx"];     // TODO Add ' ('.$result['default_vat_code'].')'
2079
                            $this->multiprices_recuperableonly[$i] = $result["recuperableonly"];
2080
2081
                            // Price by quantity
2082
                            $this->prices_by_qty[$i] = $result["price_by_qty"];
2083
                            $this->prices_by_qty_id[$i] = $result["rowid"];
2084
                            // Récuperation de la liste des prix selon qty si flag positionné
2085
                            if ($this->prices_by_qty[$i] == 1) {
2086
                                $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2087
                                $sql .= " FROM " . MAIN_DB_PREFIX . "product_price_by_qty";
2088
                                $sql .= " WHERE fk_product_price = " . $this->prices_by_qty_id[$i];
2089
                                $sql .= " ORDER BY quantity ASC";
2090
                                $resultat = array();
2091
                                $resql = $this->db->query($sql);
2092
                                if ($resql) {
2093
                                    $ii = 0;
2094
                                    while ($result = $this->db->fetch_array($resql)) {
2095
                                        $resultat[$ii] = array();
2096
                                        $resultat[$ii]["rowid"] = $result["rowid"];
2097
                                        $resultat[$ii]["price"] = $result["price"];
2098
                                        $resultat[$ii]["unitprice"] = $result["unitprice"];
2099
                                        $resultat[$ii]["quantity"] = $result["quantity"];
2100
                                        $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2101
                                        $resultat[$ii]["remise"] = $result["remise"];                    // deprecated
2102
                                        $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2103
                                        $ii++;
2104
                                    }
2105
                                    $this->prices_by_qty_list[$i] = $resultat;
2106
                                } else {
2107
                                    dol_print_error($this->db);
2108
                                    return -1;
2109
                                }
2110
                            }
2111
                        } else {
2112
                            dol_print_error($this->db);
2113
                            return -1;
2114
                        }
2115
                    }
2116
                }
2117
2118
                if (!empty($conf->dynamicprices->enabled) && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2119
                    include_once DOL_DOCUMENT_ROOT . '/product/dynamic_price/class/price_parser.class.php';
2120
                    $priceparser = new PriceParser($this->db);
2121
                    $price_result = $priceparser->parseProduct($this);
2122
                    if ($price_result >= 0) {
2123
                        $this->price = $price_result;
2124
                        // Calculate the VAT
2125
                        $this->price_ttc = price2num($this->price) * (1 + ($this->tva_tx / 100));
2126
                        $this->price_ttc = price2num($this->price_ttc, 'MU');
2127
                    }
2128
                }
2129
2130
                // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2131
                // Instead we just init the stock_warehouse array
2132
                $this->stock_warehouse = array();
2133
2134
                return 1;
2135
            } else {
2136
                return 0;
2137
            }
2138
        } else {
2139
            dol_print_error($this->db);
2140
            return -1;
2141
        }
2142
    }
2143
2144
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2145
    /**
2146
     *  Charge tableau des stats propale pour le produit/service
2147
     *
2148
     * @param  int $socid Id societe
2149
     * @return array               Tableau des stats
2150
     */
2151
    function load_stats_propale($socid = 0)
2152
    {
2153
        // phpcs:enable
2154
        global $conf;
2155
        global $user;
2156
2157
        $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
2158
        $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2159
        $sql .= " FROM " . MAIN_DB_PREFIX . "propaldet as pd";
2160
        $sql .= ", " . MAIN_DB_PREFIX . "propal as p";
2161
        $sql .= ", " . MAIN_DB_PREFIX . "societe as s";
2162
        if (!$user->rights->societe->client->voir && !$socid) {
2163
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2164
        }
2165
        $sql .= " WHERE p.rowid = pd.fk_propal";
2166
        $sql .= " AND p.fk_soc = s.rowid";
2167
        $sql .= " AND p.entity IN (" . getEntity('propal') . ")";
2168
        $sql .= " AND pd.fk_product = " . $this->id;
2169
        if (!$user->rights->societe->client->voir && !$socid) {
2170
            $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2171
        }
2172
        //$sql.= " AND pr.fk_statut != 0";
2173
        if ($socid > 0) {
2174
            $sql .= " AND p.fk_soc = " . $socid;
2175
        }
2176
2177
        $result = $this->db->query($sql);
2178
        if ($result) {
2179
            $obj = $this->db->fetch_object($result);
2180
            $this->stats_propale['customers'] = $obj->nb_customers;
2181
            $this->stats_propale['nb'] = $obj->nb;
2182
            $this->stats_propale['rows'] = $obj->nb_rows;
2183
            $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
2184
            return 1;
2185
        } else {
2186
            $this->error = $this->db->error();
2187
            return -1;
2188
        }
2189
    }
2190
2191
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2192
    /**
2193
     *  Charge tableau des stats propale pour le produit/service
2194
     *
2195
     * @param  int $socid Id thirdparty
2196
     * @return array               Tableau des stats
2197
     */
2198
    function load_stats_proposal_supplier($socid = 0)
2199
    {
2200
        // phpcs:enable
2201
        global $conf;
2202
        global $user;
2203
2204
        $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
2205
        $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2206
        $sql .= " FROM " . MAIN_DB_PREFIX . "supplier_proposaldet as pd";
2207
        $sql .= ", " . MAIN_DB_PREFIX . "supplier_proposal as p";
2208
        $sql .= ", " . MAIN_DB_PREFIX . "societe as s";
2209
        if (!$user->rights->societe->client->voir && !$socid) {
2210
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2211
        }
2212
        $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
2213
        $sql .= " AND p.fk_soc = s.rowid";
2214
        $sql .= " AND p.entity IN (" . getEntity('supplier_proposal') . ")";
2215
        $sql .= " AND pd.fk_product = " . $this->id;
2216
        if (!$user->rights->societe->client->voir && !$socid) {
2217
            $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2218
        }
2219
        //$sql.= " AND pr.fk_statut != 0";
2220
        if ($socid > 0) {
2221
            $sql .= " AND p.fk_soc = " . $socid;
2222
        }
2223
2224
        $result = $this->db->query($sql);
2225
        if ($result) {
2226
            $obj = $this->db->fetch_object($result);
2227
            $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
2228
            $this->stats_proposal_supplier['nb'] = $obj->nb;
2229
            $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
2230
            $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
2231
            return 1;
2232
        } else {
2233
            $this->error = $this->db->error();
2234
            return -1;
2235
        }
2236
    }
2237
2238
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2239
    /**
2240
     *  Charge tableau des stats commande client pour le produit/service
2241
     *
2242
     * @param  int    $socid           Id societe pour filtrer sur une societe
2243
     * @param  string $filtrestatut    Id statut pour filtrer sur un statut
2244
     * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
2245
     * @return array                  Array of stats (nb=nb of order, qty=qty ordered)
2246
     */
2247
    function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2248
    {
2249
        // phpcs:enable
2250
        global $conf, $user;
2251
2252
        $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2253
        $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2254
        $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as cd";
2255
        $sql .= ", " . MAIN_DB_PREFIX . "commande as c";
2256
        $sql .= ", " . MAIN_DB_PREFIX . "societe as s";
2257
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2258
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2259
        }
2260
        $sql .= " WHERE c.rowid = cd.fk_commande";
2261
        $sql .= " AND c.fk_soc = s.rowid";
2262
        $sql .= " AND c.entity IN (" . getEntity('commande') . ")";
2263
        $sql .= " AND cd.fk_product = " . $this->id;
2264
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2265
            $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2266
        }
2267
        if ($socid > 0) {
2268
            $sql .= " AND c.fk_soc = " . $socid;
2269
        }
2270
        if ($filtrestatut <> '') {
2271
            $sql .= " AND c.fk_statut in (" . $filtrestatut . ")";
2272
        }
2273
2274
        $result = $this->db->query($sql);
2275
        if ($result) {
2276
            $obj = $this->db->fetch_object($result);
2277
            $this->stats_commande['customers'] = $obj->nb_customers;
2278
            $this->stats_commande['nb'] = $obj->nb;
2279
            $this->stats_commande['rows'] = $obj->nb_rows;
2280
            $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
2281
2282
            // if it's a virtual product, maybe it is in order by extension
2283
            if (!empty($conf->global->ORDER_ADD_ORDERS_WITH_PARENT_PROD_IF_INCDEC)) {
2284
                $TFather = $this->getFather();
2285
                if (is_array($TFather) && !empty($TFather)) {
2286
                    foreach ($TFather as &$fatherData) {
2287
                        $pFather = new Product($this->db);
2288
                        $pFather->id = $fatherData['id'];
2289
                        $qtyCoef = $fatherData['qty'];
2290
2291
                        if ($fatherData['incdec']) {
2292
                            $pFather->load_stats_commande($socid, $filtrestatut);
2293
2294
                            $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
2295
                            $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
2296
                            $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
2297
                            $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
2298
                        }
2299
                    }
2300
                }
2301
            }
2302
2303
            // If stock decrease is on invoice validation, the theorical stock continue to
2304
            // count the orders to ship in theorical stock when some are already removed b invoice validation.
2305
            // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation.
2306
            if (!empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
2307
                if (!empty($conf->global->DECREASE_ONLY_UNINVOICEDPRODUCTS)) {
2308
                    $adeduire = 0;
2309
                    $sql = "SELECT sum(fd.qty) as count FROM " . MAIN_DB_PREFIX . "facturedet fd ";
2310
                    $sql .= " JOIN " . MAIN_DB_PREFIX . "facture f ON fd.fk_facture = f.rowid ";
2311
                    $sql .= " JOIN " . MAIN_DB_PREFIX . "element_element el ON el.fk_target = f.rowid and el.targettype = 'facture' and sourcetype = 'commande'";
2312
                    $sql .= " JOIN " . MAIN_DB_PREFIX . "commande c ON el.fk_source = c.rowid ";
2313
                    $sql .= " WHERE c.fk_statut IN (" . $filtrestatut . ") AND c.facture = 0 AND fd.fk_product = " . $this->id;
2314
                    dol_syslog(__METHOD__ . ":: sql $sql", LOG_NOTICE);
2315
2316
                    $resql = $this->db->query($sql);
2317
                    if ($resql) {
2318
                        if ($this->db->num_rows($resql) > 0) {
2319
                            $obj = $this->db->fetch_object($resql);
2320
                            $adeduire += $obj->count;
2321
                        }
2322
                    }
2323
2324
                    $this->stats_commande['qty'] -= $adeduire;
2325
                }
2326
            }
2327
2328
            return 1;
2329
        } else {
2330
            $this->error = $this->db->error();
2331
            return -1;
2332
        }
2333
    }
2334
2335
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2336
    /**
2337
     *  Charge tableau des stats commande fournisseur pour le produit/service
2338
     *
2339
     * @param  int    $socid           Id societe pour filtrer sur une societe
2340
     * @param  string $filtrestatut    Id des statuts pour filtrer sur des statuts
2341
     * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
2342
     * @return array                     Tableau des stats
2343
     */
2344
    function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2345
    {
2346
        // phpcs:enable
2347
        global $conf, $user;
2348
2349
        $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
2350
        $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2351
        $sql .= " FROM " . MAIN_DB_PREFIX . "commande_fournisseurdet as cd";
2352
        $sql .= ", " . MAIN_DB_PREFIX . "commande_fournisseur as c";
2353
        $sql .= ", " . MAIN_DB_PREFIX . "societe as s";
2354
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2355
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2356
        }
2357
        $sql .= " WHERE c.rowid = cd.fk_commande";
2358
        $sql .= " AND c.fk_soc = s.rowid";
2359
        $sql .= " AND c.entity IN (" . getEntity('supplier_order') . ")";
2360
        $sql .= " AND cd.fk_product = " . $this->id;
2361
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2362
            $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2363
        }
2364
        if ($socid > 0) {
2365
            $sql .= " AND c.fk_soc = " . $socid;
2366
        }
2367
        if ($filtrestatut != '') {
2368
            $sql .= " AND c.fk_statut in (" . $filtrestatut . ")"; // Peut valoir 0
2369
        }
2370
2371
        $result = $this->db->query($sql);
2372
        if ($result) {
2373
            $obj = $this->db->fetch_object($result);
2374
            $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
2375
            $this->stats_commande_fournisseur['nb'] = $obj->nb;
2376
            $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
2377
            $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
2378
            return 1;
2379
        } else {
2380
            $this->error = $this->db->error() . ' sql=' . $sql;
2381
            return -1;
2382
        }
2383
    }
2384
2385
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2386
    /**
2387
     *  Charge tableau des stats expedition client pour le produit/service
2388
     *
2389
     * @param  int    $socid           Id societe pour filtrer sur une societe
2390
     * @param  string $filtrestatut    Id statut pour filtrer sur un statut
2391
     * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
2392
     * @return array                   Tableau des stats
2393
     */
2394
    function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2395
    {
2396
        // phpcs:enable
2397
        global $conf, $user;
2398
2399
        $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
2400
        $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
2401
        $sql .= " FROM " . MAIN_DB_PREFIX . "expeditiondet as ed";
2402
        $sql .= ", " . MAIN_DB_PREFIX . "commandedet as cd";
2403
        $sql .= ", " . MAIN_DB_PREFIX . "commande as c";
2404
        $sql .= ", " . MAIN_DB_PREFIX . "expedition as e";
2405
        $sql .= ", " . MAIN_DB_PREFIX . "societe as s";
2406
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2407
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2408
        }
2409
        $sql .= " WHERE e.rowid = ed.fk_expedition";
2410
        $sql .= " AND c.rowid = cd.fk_commande";
2411
        $sql .= " AND e.fk_soc = s.rowid";
2412
        $sql .= " AND e.entity IN (" . getEntity('expedition') . ")";
2413
        $sql .= " AND ed.fk_origin_line = cd.rowid";
2414
        $sql .= " AND cd.fk_product = " . $this->id;
2415
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2416
            $sql .= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2417
        }
2418
        if ($socid > 0) {
2419
            $sql .= " AND e.fk_soc = " . $socid;
2420
        }
2421
        if ($filtrestatut <> '') {
2422
            $sql .= " AND c.fk_statut in (" . $filtrestatut . ")";
2423
        }
2424
2425
        $result = $this->db->query($sql);
2426
        if ($result) {
2427
            $obj = $this->db->fetch_object($result);
2428
            $this->stats_expedition['customers'] = $obj->nb_customers;
2429
            $this->stats_expedition['nb'] = $obj->nb;
2430
            $this->stats_expedition['rows'] = $obj->nb_rows;
2431
            $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
2432
            return 1;
2433
        } else {
2434
            $this->error = $this->db->error();
2435
            return -1;
2436
        }
2437
    }
2438
2439
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2440
    /**
2441
     *  Charge tableau des stats réception fournisseur pour le produit/service
2442
     *
2443
     * @param  int    $socid           Id societe pour filtrer sur une societe
2444
     * @param  string $filtrestatut    Id statut pour filtrer sur un statut
2445
     * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
2446
     * @return array                   Tableau des stats
2447
     */
2448
    function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2449
    {
2450
        // phpcs:enable
2451
        global $conf, $user;
2452
2453
        $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_customers, COUNT(DISTINCT cf.rowid) as nb,";
2454
        $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2455
        $sql .= " FROM " . MAIN_DB_PREFIX . "commande_fournisseur_dispatch as fd";
2456
        $sql .= ", " . MAIN_DB_PREFIX . "commande_fournisseur as cf";
2457
        $sql .= ", " . MAIN_DB_PREFIX . "societe as s";
2458
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2459
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2460
        }
2461
        $sql .= " WHERE cf.rowid = fd.fk_commande";
2462
        $sql .= " AND cf.fk_soc = s.rowid";
2463
        $sql .= " AND cf.entity IN (" . getEntity('supplier_order') . ")";
2464
        $sql .= " AND fd.fk_product = " . $this->id;
2465
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2466
            $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2467
        }
2468
        if ($socid > 0) {
2469
            $sql .= " AND cf.fk_soc = " . $socid;
2470
        }
2471
        if ($filtrestatut <> '') {
2472
            $sql .= " AND cf.fk_statut in (" . $filtrestatut . ")";
2473
        }
2474
2475
        $result = $this->db->query($sql);
2476
        if ($result) {
2477
            $obj = $this->db->fetch_object($result);
2478
            $this->stats_reception['suppliers'] = $obj->nb_customers;
2479
            $this->stats_reception['nb'] = $obj->nb;
2480
            $this->stats_reception['rows'] = $obj->nb_rows;
2481
            $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
2482
            return 1;
2483
        } else {
2484
            $this->error = $this->db->error();
2485
            return -1;
2486
        }
2487
    }
2488
2489
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2490
    /**
2491
     *  Charge tableau des stats contrat pour le produit/service
2492
     *
2493
     * @param  int $socid Id societe
2494
     * @return array               Tableau des stats
2495
     */
2496
    function load_stats_contrat($socid = 0)
2497
    {
2498
        // phpcs:enable
2499
        global $conf;
2500
        global $user;
2501
2502
        $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2503
        $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2504
        $sql .= " FROM " . MAIN_DB_PREFIX . "contratdet as cd";
2505
        $sql .= ", " . MAIN_DB_PREFIX . "contrat as c";
2506
        $sql .= ", " . MAIN_DB_PREFIX . "societe as s";
2507
        if (!$user->rights->societe->client->voir && !$socid) {
2508
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2509
        }
2510
        $sql .= " WHERE c.rowid = cd.fk_contrat";
2511
        $sql .= " AND c.fk_soc = s.rowid";
2512
        $sql .= " AND c.entity IN (" . getEntity('contract') . ")";
2513
        $sql .= " AND cd.fk_product = " . $this->id;
2514
        if (!$user->rights->societe->client->voir && !$socid) {
2515
            $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2516
        }
2517
        //$sql.= " AND c.statut != 0";
2518
        if ($socid > 0) {
2519
            $sql .= " AND c.fk_soc = " . $socid;
2520
        }
2521
2522
        $result = $this->db->query($sql);
2523
        if ($result) {
2524
            $obj = $this->db->fetch_object($result);
2525
            $this->stats_contrat['customers'] = $obj->nb_customers;
2526
            $this->stats_contrat['nb'] = $obj->nb;
2527
            $this->stats_contrat['rows'] = $obj->nb_rows;
2528
            $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
2529
            return 1;
2530
        } else {
2531
            $this->error = $this->db->error() . ' sql=' . $sql;
2532
            return -1;
2533
        }
2534
    }
2535
2536
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2537
    /**
2538
     *  Charge tableau des stats facture pour le produit/service
2539
     *
2540
     * @param  int $socid Id societe
2541
     * @return array                   Tableau des stats
2542
     */
2543
    function load_stats_facture($socid = 0)
2544
    {
2545
        // phpcs:enable
2546
        global $conf;
2547
        global $user;
2548
2549
        $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
2550
        $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2551
        $sql .= " FROM " . MAIN_DB_PREFIX . "facturedet as fd";
2552
        $sql .= ", " . MAIN_DB_PREFIX . "facture as f";
2553
        $sql .= ", " . MAIN_DB_PREFIX . "societe as s";
2554
        if (!$user->rights->societe->client->voir && !$socid) {
2555
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2556
        }
2557
        $sql .= " WHERE f.rowid = fd.fk_facture";
2558
        $sql .= " AND f.fk_soc = s.rowid";
2559
        $sql .= " AND f.entity IN (" . getEntity('invoice') . ")";
2560
        $sql .= " AND fd.fk_product = " . $this->id;
2561
        if (!$user->rights->societe->client->voir && !$socid) {
2562
            $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2563
        }
2564
        //$sql.= " AND f.fk_statut != 0";
2565
        if ($socid > 0) {
2566
            $sql .= " AND f.fk_soc = " . $socid;
2567
        }
2568
2569
        $result = $this->db->query($sql);
2570
        if ($result) {
2571
            $obj = $this->db->fetch_object($result);
2572
            $this->stats_facture['customers'] = $obj->nb_customers;
2573
            $this->stats_facture['nb'] = $obj->nb;
2574
            $this->stats_facture['rows'] = $obj->nb_rows;
2575
            $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
2576
            return 1;
2577
        } else {
2578
            $this->error = $this->db->error();
2579
            return -1;
2580
        }
2581
    }
2582
2583
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2584
    /**
2585
     *  Charge tableau des stats facture pour le produit/service
2586
     *
2587
     * @param  int $socid Id societe
2588
     * @return array                   Tableau des stats
2589
     */
2590
    function load_stats_facture_fournisseur($socid = 0)
2591
    {
2592
        // phpcs:enable
2593
        global $conf;
2594
        global $user;
2595
2596
        $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
2597
        $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2598
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture_fourn_det as fd";
2599
        $sql .= ", " . MAIN_DB_PREFIX . "facture_fourn as f";
2600
        $sql .= ", " . MAIN_DB_PREFIX . "societe as s";
2601
        if (!$user->rights->societe->client->voir && !$socid) {
2602
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2603
        }
2604
        $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
2605
        $sql .= " AND f.fk_soc = s.rowid";
2606
        $sql .= " AND f.entity IN (" . getEntity('facture_fourn') . ")";
2607
        $sql .= " AND fd.fk_product = " . $this->id;
2608
        if (!$user->rights->societe->client->voir && !$socid) {
2609
            $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2610
        }
2611
        //$sql.= " AND f.fk_statut != 0";
2612
        if ($socid > 0) {
2613
            $sql .= " AND f.fk_soc = " . $socid;
2614
        }
2615
2616
        $result = $this->db->query($sql);
2617
        if ($result) {
2618
            $obj = $this->db->fetch_object($result);
2619
            $this->stats_facture_fournisseur['suppliers'] = $obj->nb_suppliers;
2620
            $this->stats_facture_fournisseur['nb'] = $obj->nb;
2621
            $this->stats_facture_fournisseur['rows'] = $obj->nb_rows;
2622
            $this->stats_facture_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
2623
            return 1;
2624
        } else {
2625
            $this->error = $this->db->error();
2626
            return -1;
2627
        }
2628
    }
2629
2630
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2631
    /**
2632
     *  Return an array formated for showing graphs
2633
     *
2634
     * @param  string $sql  Request to execute
2635
     * @param  string $mode 'byunit'=number of unit, 'bynumber'=nb of entities
2636
     * @param  int    $year Year (0=current year)
2637
     * @return array               <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
2638
     */
2639
    function _get_stats($sql, $mode, $year = 0)
2640
    {
2641
        // phpcs:enable
2642
        $resql = $this->db->query($sql);
2643
        if ($resql) {
2644
            $num = $this->db->num_rows($resql);
2645
            $i = 0;
2646
            while ($i < $num) {
2647
                $arr = $this->db->fetch_array($resql);
2648
                if ($mode == 'byunit') {
2649
                    $tab[$arr[1]] = $arr[0];    // 1st field
2650
                }
2651
                if ($mode == 'bynumber') {
2652
                    $tab[$arr[1]] = $arr[2];    // 3rd field
2653
                }
2654
                $i++;
2655
            }
2656
        } else {
2657
            $this->error = $this->db->error() . ' sql=' . $sql;
2658
            return -1;
2659
        }
2660
2661
        if (empty($year)) {
2662
            $year = strftime('%Y', time());
2663
            $month = strftime('%m', time());
2664
        } else {
2665
            $month = 12;    // We imagine we are at end of year, so we get last 12 month before, so all correct year.
2666
        }
2667
        $result = array();
2668
2669
        for ($j = 0; $j < 12; $j++) {
2670
            $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, $year), "%b"), 3, 'right', 'UTF-8', 1));
2671
            $monthnum = sprintf("%02s", $month);
2672
2673
            $result[$j] = array($idx, isset($tab[$year . $month]) ? $tab[$year . $month] : 0);
2674
            //            $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
2675
2676
            $month = "0" . ($month - 1);
2677
            if (dol_strlen($month) == 3) {
2678
                $month = substr($month, 1);
2679
            }
2680
            if ($month == 0) {
2681
                $month = 12;
2682
                $year = $year - 1;
2683
            }
2684
        }
2685
2686
        return array_reverse($result);
2687
    }
2688
2689
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2690
    /**
2691
     *  Return nb of units or customers invoices in which product is included
2692
     *
2693
     * @param  int    $socid               Limit count on a particular third party id
2694
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
2695
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
2696
     * @param  int    $year                Year (0=last 12 month)
2697
     * @param  string $morefilter          More sql filters
2698
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
2699
     */
2700
    function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
2701
    {
2702
        // phpcs:enable
2703
        global $conf;
2704
        global $user;
2705
2706
        $sql = "SELECT sum(d.qty), date_format(f.datef, '%Y%m')";
2707
        if ($mode == 'bynumber') {
2708
            $sql .= ", count(DISTINCT f.rowid)";
2709
        }
2710
        $sql .= " FROM " . MAIN_DB_PREFIX . "facturedet as d, " . MAIN_DB_PREFIX . "facture as f, " . MAIN_DB_PREFIX . "societe as s";
2711
        if ($filteronproducttype >= 0) {
2712
            $sql .= ", " . MAIN_DB_PREFIX . "product as p";
2713
        }
2714
        if (!$user->rights->societe->client->voir && !$socid) {
2715
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2716
        }
2717
        $sql .= " WHERE f.rowid = d.fk_facture";
2718
        if ($this->id > 0) {
2719
            $sql .= " AND d.fk_product =" . $this->id;
2720
        } else {
2721
            $sql .= " AND d.fk_product > 0";
2722
        }
2723
        if ($filteronproducttype >= 0) {
2724
            $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type =" . $filteronproducttype;
2725
        }
2726
        $sql .= " AND f.fk_soc = s.rowid";
2727
        $sql .= " AND f.entity IN (" . getEntity('invoice') . ")";
2728
        if (!$user->rights->societe->client->voir && !$socid) {
2729
            $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2730
        }
2731
        if ($socid > 0) {
2732
            $sql .= " AND f.fk_soc = $socid";
2733
        }
2734
        $sql .= $morefilter;
2735
        $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
2736
        $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
2737
2738
        return $this->_get_stats($sql, $mode, $year);
2739
    }
2740
2741
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2742
    /**
2743
     *  Return nb of units or supplier invoices in which product is included
2744
     *
2745
     * @param  int    $socid               Limit count on a particular third party id
2746
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
2747
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
2748
     * @param  int    $year                Year (0=last 12 month)
2749
     * @param  string $morefilter          More sql filters
2750
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
2751
     */
2752
    function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
2753
    {
2754
        // phpcs:enable
2755
        global $conf;
2756
        global $user;
2757
2758
        $sql = "SELECT sum(d.qty), date_format(f.datef, '%Y%m')";
2759
        if ($mode == 'bynumber') {
2760
            $sql .= ", count(DISTINCT f.rowid)";
2761
        }
2762
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture_fourn_det as d, " . MAIN_DB_PREFIX . "facture_fourn as f, " . MAIN_DB_PREFIX . "societe as s";
2763
        if ($filteronproducttype >= 0) {
2764
            $sql .= ", " . MAIN_DB_PREFIX . "product as p";
2765
        }
2766
        if (!$user->rights->societe->client->voir && !$socid) {
2767
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2768
        }
2769
        $sql .= " WHERE f.rowid = d.fk_facture_fourn";
2770
        if ($this->id > 0) {
2771
            $sql .= " AND d.fk_product =" . $this->id;
2772
        } else {
2773
            $sql .= " AND d.fk_product > 0";
2774
        }
2775
        if ($filteronproducttype >= 0) {
2776
            $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type =" . $filteronproducttype;
2777
        }
2778
        $sql .= " AND f.fk_soc = s.rowid";
2779
        $sql .= " AND f.entity IN (" . getEntity('facture_fourn') . ")";
2780
        if (!$user->rights->societe->client->voir && !$socid) {
2781
            $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2782
        }
2783
        if ($socid > 0) {
2784
            $sql .= " AND f.fk_soc = $socid";
2785
        }
2786
        $sql .= $morefilter;
2787
        $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
2788
        $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
2789
2790
        return $this->_get_stats($sql, $mode, $year);
2791
    }
2792
2793
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2794
    /**
2795
     *  Return nb of units or proposals in which product is included
2796
     *
2797
     * @param  int    $socid               Limit count on a particular third party id
2798
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
2799
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
2800
     * @param  int    $year                Year (0=last 12 month)
2801
     * @param  string $morefilter          More sql filters
2802
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
2803
     */
2804
    function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
2805
    {
2806
        // phpcs:enable
2807
        global $conf;
2808
        global $user;
2809
2810
        $sql = "SELECT sum(d.qty), date_format(p.datep, '%Y%m')";
2811
        if ($mode == 'bynumber') {
2812
            $sql .= ", count(DISTINCT p.rowid)";
2813
        }
2814
        $sql .= " FROM " . MAIN_DB_PREFIX . "propaldet as d, " . MAIN_DB_PREFIX . "propal as p, " . MAIN_DB_PREFIX . "societe as s";
2815
        if ($filteronproducttype >= 0) {
2816
            $sql .= ", " . MAIN_DB_PREFIX . "product as prod";
2817
        }
2818
        if (!$user->rights->societe->client->voir && !$socid) {
2819
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2820
        }
2821
        $sql .= " WHERE p.rowid = d.fk_propal";
2822
        if ($this->id > 0) {
2823
            $sql .= " AND d.fk_product =" . $this->id;
2824
        } else {
2825
            $sql .= " AND d.fk_product > 0";
2826
        }
2827
        if ($filteronproducttype >= 0) {
2828
            $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type =" . $filteronproducttype;
2829
        }
2830
        $sql .= " AND p.fk_soc = s.rowid";
2831
        $sql .= " AND p.entity IN (" . getEntity('propal') . ")";
2832
        if (!$user->rights->societe->client->voir && !$socid) {
2833
            $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2834
        }
2835
        if ($socid > 0) {
2836
            $sql .= " AND p.fk_soc = " . $socid;
2837
        }
2838
        $sql .= $morefilter;
2839
        $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
2840
        $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
2841
2842
        return $this->_get_stats($sql, $mode, $year);
2843
    }
2844
2845
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2846
    /**
2847
     *  Return nb of units or proposals in which product is included
2848
     *
2849
     * @param  int    $socid               Limit count on a particular third party id
2850
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
2851
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
2852
     * @param  int    $year                Year (0=last 12 month)
2853
     * @param  string $morefilter          More sql filters
2854
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
2855
     */
2856
    function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
2857
    {
2858
        // phpcs:enable
2859
        global $conf;
2860
        global $user;
2861
2862
        $sql = "SELECT sum(d.qty), date_format(p.date_valid, '%Y%m')";
2863
        if ($mode == 'bynumber') {
2864
            $sql .= ", count(DISTINCT p.rowid)";
2865
        }
2866
        $sql .= " FROM " . MAIN_DB_PREFIX . "supplier_proposaldet as d, " . MAIN_DB_PREFIX . "supplier_proposal as p, " . MAIN_DB_PREFIX . "societe as s";
2867
        if ($filteronproducttype >= 0) {
2868
            $sql .= ", " . MAIN_DB_PREFIX . "product as prod";
2869
        }
2870
        if (!$user->rights->societe->client->voir && !$socid) {
2871
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2872
        }
2873
        $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
2874
        if ($this->id > 0) {
2875
            $sql .= " AND d.fk_product =" . $this->id;
2876
        } else {
2877
            $sql .= " AND d.fk_product > 0";
2878
        }
2879
        if ($filteronproducttype >= 0) {
2880
            $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type =" . $filteronproducttype;
2881
        }
2882
        $sql .= " AND p.fk_soc = s.rowid";
2883
        $sql .= " AND p.entity IN (" . getEntity('supplier_proposal') . ")";
2884
        if (!$user->rights->societe->client->voir && !$socid) {
2885
            $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2886
        }
2887
        if ($socid > 0) {
2888
            $sql .= " AND p.fk_soc = " . $socid;
2889
        }
2890
        $sql .= $morefilter;
2891
        $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
2892
        $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
2893
2894
        return $this->_get_stats($sql, $mode, $year);
2895
    }
2896
2897
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2898
    /**
2899
     *  Return nb of units or orders in which product is included
2900
     *
2901
     * @param  int    $socid               Limit count on a particular third party id
2902
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
2903
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
2904
     * @param  int    $year                Year (0=last 12 month)
2905
     * @param  string $morefilter          More sql filters
2906
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
2907
     */
2908
    function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
2909
    {
2910
        // phpcs:enable
2911
        global $conf, $user;
2912
2913
        $sql = "SELECT sum(d.qty), date_format(c.date_commande, '%Y%m')";
2914
        if ($mode == 'bynumber') {
2915
            $sql .= ", count(DISTINCT c.rowid)";
2916
        }
2917
        $sql .= " FROM " . MAIN_DB_PREFIX . "commandedet as d, " . MAIN_DB_PREFIX . "commande as c, " . MAIN_DB_PREFIX . "societe as s";
2918
        if ($filteronproducttype >= 0) {
2919
            $sql .= ", " . MAIN_DB_PREFIX . "product as p";
2920
        }
2921
        if (!$user->rights->societe->client->voir && !$socid) {
2922
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2923
        }
2924
        $sql .= " WHERE c.rowid = d.fk_commande";
2925
        if ($this->id > 0) {
2926
            $sql .= " AND d.fk_product =" . $this->id;
2927
        } else {
2928
            $sql .= " AND d.fk_product > 0";
2929
        }
2930
        if ($filteronproducttype >= 0) {
2931
            $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type =" . $filteronproducttype;
2932
        }
2933
        $sql .= " AND c.fk_soc = s.rowid";
2934
        $sql .= " AND c.entity IN (" . getEntity('commande') . ")";
2935
        if (!$user->rights->societe->client->voir && !$socid) {
2936
            $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2937
        }
2938
        if ($socid > 0) {
2939
            $sql .= " AND c.fk_soc = " . $socid;
2940
        }
2941
        $sql .= $morefilter;
2942
        $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
2943
        $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
2944
2945
        return $this->_get_stats($sql, $mode, $year);
2946
    }
2947
2948
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
2949
    /**
2950
     *  Return nb of units or orders in which product is included
2951
     *
2952
     * @param  int    $socid               Limit count on a particular third party id
2953
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
2954
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
2955
     * @param  int    $year                Year (0=last 12 month)
2956
     * @param  string $morefilter          More sql filters
2957
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
2958
     */
2959
    function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
2960
    {
2961
        // phpcs:enable
2962
        global $conf, $user;
2963
2964
        $sql = "SELECT sum(d.qty), date_format(c.date_commande, '%Y%m')";
2965
        if ($mode == 'bynumber') {
2966
            $sql .= ", count(DISTINCT c.rowid)";
2967
        }
2968
        $sql .= " FROM " . MAIN_DB_PREFIX . "commande_fournisseurdet as d, " . MAIN_DB_PREFIX . "commande_fournisseur as c, " . MAIN_DB_PREFIX . "societe as s";
2969
        if ($filteronproducttype >= 0) {
2970
            $sql .= ", " . MAIN_DB_PREFIX . "product as p";
2971
        }
2972
        if (!$user->rights->societe->client->voir && !$socid) {
2973
            $sql .= ", " . MAIN_DB_PREFIX . "societe_commerciaux as sc";
2974
        }
2975
        $sql .= " WHERE c.rowid = d.fk_commande";
2976
        if ($this->id > 0) {
2977
            $sql .= " AND d.fk_product =" . $this->id;
2978
        } else {
2979
            $sql .= " AND d.fk_product > 0";
2980
        }
2981
        if ($filteronproducttype >= 0) {
2982
            $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type =" . $filteronproducttype;
2983
        }
2984
        $sql .= " AND c.fk_soc = s.rowid";
2985
        $sql .= " AND c.entity IN (" . getEntity('supplier_order') . ")";
2986
        if (!$user->rights->societe->client->voir && !$socid) {
2987
            $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " . $user->id;
2988
        }
2989
        if ($socid > 0) {
2990
            $sql .= " AND c.fk_soc = " . $socid;
2991
        }
2992
        $sql .= $morefilter;
2993
        $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
2994
        $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
2995
2996
        return $this->_get_stats($sql, $mode, $year);
2997
    }
2998
2999
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3000
    /**
3001
     *  Link a product/service to a parent product/service
3002
     *
3003
     * @param  int $id_pere Id of parent product/service
3004
     * @param  int $id_fils Id of child product/service
3005
     * @param  int $qty     Quantity
3006
     * @param  int $incdec  1=Increase/decrease stock of child when parent stock increase/decrease
3007
     * @return int                < 0 if KO, > 0 if OK
3008
     */
3009
    function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1)
3010
    {
3011
        // phpcs:enable
3012
        // Clean parameters
3013
        if (!is_numeric($id_pere)) {
3014
            $id_pere = 0;
3015
        }
3016
        if (!is_numeric($id_fils)) {
3017
            $id_fils = 0;
3018
        }
3019
        if (!is_numeric($incdec)) {
3020
            $incdec = 0;
3021
        }
3022
3023
        $result = $this->del_sousproduit($id_pere, $id_fils);
3024
        if ($result < 0) {
3025
            return $result;
3026
        }
3027
3028
        // Check not already father of id_pere (to avoid father -> child -> father links)
3029
        $sql = 'SELECT fk_product_pere from ' . MAIN_DB_PREFIX . 'product_association';
3030
        $sql .= ' WHERE fk_product_pere  = ' . $id_fils . ' AND fk_product_fils = ' . $id_pere;
3031
        if (!$this->db->query($sql)) {
3032
            dol_print_error($this->db);
3033
            return -1;
3034
        } else {
3035
            $result = $this->db->query($sql);
3036
            if ($result) {
3037
                $num = $this->db->num_rows($result);
3038
                if ($num > 0) {
3039
                    $this->error = "isFatherOfThis";
3040
                    return -1;
3041
                } else {
3042
                    $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'product_association(fk_product_pere,fk_product_fils,qty,incdec)';
3043
                    $sql .= ' VALUES (' . $id_pere . ', ' . $id_fils . ', ' . $qty . ', ' . $incdec . ')';
3044
                    if (!$this->db->query($sql)) {
3045
                        dol_print_error($this->db);
3046
                        return -1;
3047
                    } else {
3048
                        return 1;
3049
                    }
3050
                }
3051
            }
3052
        }
3053
    }
3054
3055
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3056
    /**
3057
     *  Modify composed product
3058
     *
3059
     * @param  int $id_pere Id of parent product/service
3060
     * @param  int $id_fils Id of child product/service
3061
     * @param  int $qty     Quantity
3062
     * @param  int $incdec  1=Increase/decrease stock of child when parent stock increase/decrease
3063
     * @return int                < 0 if KO, > 0 if OK
3064
     */
3065
    function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1)
3066
    {
3067
        // phpcs:enable
3068
        // Clean parameters
3069
        if (!is_numeric($id_pere)) {
3070
            $id_pere = 0;
3071
        }
3072
        if (!is_numeric($id_fils)) {
3073
            $id_fils = 0;
3074
        }
3075
        if (!is_numeric($incdec)) {
3076
            $incdec = 1;
3077
        }
3078
        if (!is_numeric($qty)) {
3079
            $qty = 1;
3080
        }
3081
3082
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'product_association SET ';
3083
        $sql .= 'qty=' . $qty;
3084
        $sql .= ',incdec=' . $incdec;
3085
        $sql .= ' WHERE fk_product_pere=' . $id_pere . ' AND fk_product_fils=' . $id_fils;
3086
3087
        if (!$this->db->query($sql)) {
3088
            dol_print_error($this->db);
3089
            return -1;
3090
        } else {
3091
            return 1;
3092
        }
3093
    }
3094
3095
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3096
    /**
3097
     *  Retire le lien entre un sousproduit et un produit/service
3098
     *
3099
     * @param  int $fk_parent Id du produit auquel ne sera plus lie le produit lie
3100
     * @param  int $fk_child  Id du produit a ne plus lie
3101
     * @return int                    < 0 if KO, > 0 if OK
3102
     */
3103
    function del_sousproduit($fk_parent, $fk_child)
3104
    {
3105
        // phpcs:enable
3106
        if (!is_numeric($fk_parent)) {
3107
            $fk_parent = 0;
3108
        }
3109
        if (!is_numeric($fk_child)) {
3110
            $fk_child = 0;
3111
        }
3112
3113
        $sql = "DELETE FROM " . MAIN_DB_PREFIX . "product_association";
3114
        $sql .= " WHERE fk_product_pere  = " . $fk_parent;
3115
        $sql .= " AND fk_product_fils = " . $fk_child;
3116
3117
        dol_syslog(get_class($this) . '::del_sousproduit', LOG_DEBUG);
3118
        if (!$this->db->query($sql)) {
3119
            dol_print_error($this->db);
3120
            return -1;
3121
        }
3122
3123
        return 1;
3124
    }
3125
3126
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3127
    /**
3128
     *  Verifie si c'est un sous-produit
3129
     *
3130
     * @param  int $fk_parent Id du produit auquel le produit est lie
3131
     * @param  int $fk_child  Id du produit lie
3132
     * @return int                    < 0 si erreur, > 0 si ok
3133
     */
3134
    function is_sousproduit($fk_parent, $fk_child)
3135
    {
3136
        // phpcs:enable
3137
        $sql = "SELECT fk_product_pere, qty, incdec";
3138
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_association";
3139
        $sql .= " WHERE fk_product_pere  = '" . $fk_parent . "'";
3140
        $sql .= " AND fk_product_fils = '" . $fk_child . "'";
3141
3142
        $result = $this->db->query($sql);
3143
        if ($result) {
3144
            $num = $this->db->num_rows($result);
3145
3146
            if ($num > 0) {
3147
                $obj = $this->db->fetch_object($result);
3148
                $this->is_sousproduit_qty = $obj->qty;
3149
                $this->is_sousproduit_incdec = $obj->incdec;
3150
3151
                return true;
3152
            } else {
3153
                return false;
3154
            }
3155
        } else {
3156
            dol_print_error($this->db);
3157
            return -1;
3158
        }
3159
    }
3160
3161
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3162
    /**
3163
     *  Add a supplier price for the product.
3164
     *  Note: Duplicate ref is accepted for different quantity only, or for different companies.
3165
     *
3166
     * @param  User   $user      User that make link
3167
     * @param  int    $id_fourn  Supplier id
3168
     * @param  string $ref_fourn Supplier ref
3169
     * @param  float  $quantity  Quantity minimum for price
3170
     * @return int                 < 0 if KO, 0 if link already exists for this product, > 0 if OK
3171
     */
3172
    function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
3173
    {
3174
        // phpcs:enable
3175
        global $conf;
3176
3177
        $now = dol_now();
3178
3179
        dol_syslog(get_class($this) . "::add_fournisseur id_fourn = " . $id_fourn . " ref_fourn=" . $ref_fourn . " quantity=" . $quantity, LOG_DEBUG);
3180
3181
        if ($ref_fourn) {
3182
            $sql = "SELECT rowid, fk_product";
3183
            $sql .= " FROM " . MAIN_DB_PREFIX . "product_fournisseur_price";
3184
            $sql .= " WHERE fk_soc = " . $id_fourn;
3185
            $sql .= " AND ref_fourn = '" . $this->db->escape($ref_fourn) . "'";
3186
            $sql .= " AND fk_product != " . $this->id;
3187
            $sql .= " AND entity IN (" . getEntity('productsupplierprice') . ")";
3188
3189
            $resql = $this->db->query($sql);
3190
            if ($resql) {
3191
                $obj = $this->db->fetch_object($resql);
3192
                if ($obj) {
3193
                    // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
3194
                    $this->product_id_already_linked = $obj->fk_product;
3195
                    return -3;
3196
                }
3197
                $this->db->free($resql);
3198
            }
3199
        }
3200
3201
        $sql = "SELECT rowid";
3202
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_fournisseur_price";
3203
        $sql .= " WHERE fk_soc = " . $id_fourn;
3204
        if ($ref_fourn) {
3205
            $sql .= " AND ref_fourn = '" . $this->db->escape($ref_fourn) . "'";
3206
        } else {
3207
            $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
3208
        }
3209
        $sql .= " AND quantity = '" . $quantity . "'";
3210
        $sql .= " AND fk_product = " . $this->id;
3211
        $sql .= " AND entity IN (" . getEntity('productsupplierprice') . ")";
3212
3213
        $resql = $this->db->query($sql);
3214
        if ($resql) {
3215
            $obj = $this->db->fetch_object($resql);
3216
3217
            // The reference supplier does not exist, we create it for this product.
3218
            if (!$obj) {
3219
                $sql = "INSERT INTO " . MAIN_DB_PREFIX . "product_fournisseur_price(";
3220
                $sql .= "datec";
3221
                $sql .= ", entity";
3222
                $sql .= ", fk_product";
3223
                $sql .= ", fk_soc";
3224
                $sql .= ", ref_fourn";
3225
                $sql .= ", quantity";
3226
                $sql .= ", fk_user";
3227
                $sql .= ", tva_tx";
3228
                $sql .= ") VALUES (";
3229
                $sql .= "'" . $this->db->idate($now) . "'";
3230
                $sql .= ", " . $conf->entity;
3231
                $sql .= ", " . $this->id;
3232
                $sql .= ", " . $id_fourn;
3233
                $sql .= ", '" . $this->db->escape($ref_fourn) . "'";
3234
                $sql .= ", " . $quantity;
3235
                $sql .= ", " . $user->id;
3236
                $sql .= ", 0";
3237
                $sql .= ")";
3238
3239
                if ($this->db->query($sql)) {
3240
                    $this->product_fourn_price_id = $this->db->last_insert_id(MAIN_DB_PREFIX . "product_fournisseur_price");
3241
                    return 1;
3242
                } else {
3243
                    $this->error = $this->db->lasterror();
3244
                    return -1;
3245
                }
3246
            }
3247
            // If the supplier price already exists for this product and quantity
3248
            else {
3249
                $this->product_fourn_price_id = $obj->rowid;
3250
                return 0;
3251
            }
3252
        } else {
3253
            $this->error = $this->db->lasterror();
3254
            return -2;
3255
        }
3256
    }
3257
3258
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3259
    /**
3260
     *  Renvoie la liste des fournisseurs du produit/service
3261
     *
3262
     * @return array        Tableau des id de fournisseur
3263
     */
3264
    function list_suppliers()
3265
    {
3266
        // phpcs:enable
3267
        global $conf;
3268
3269
        $list = array();
3270
3271
        $sql = "SELECT DISTINCT p.fk_soc";
3272
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_fournisseur_price as p";
3273
        $sql .= " WHERE p.fk_product = " . $this->id;
3274
        $sql .= " AND p.entity = " . $conf->entity;
3275
3276
        $result = $this->db->query($sql);
3277
        if ($result) {
3278
            $num = $this->db->num_rows($result);
3279
            $i = 0;
3280
            while ($i < $num) {
3281
                $obj = $this->db->fetch_object($result);
3282
                $list[$i] = $obj->fk_soc;
3283
                $i++;
3284
            }
3285
        }
3286
3287
        return $list;
3288
    }
3289
3290
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3291
    /**
3292
     *  Recopie les prix d'un produit/service sur un autre
3293
     *
3294
     * @param  int $fromId Id product source
3295
     * @param  int $toId   Id product target
3296
     * @return int                     < 0 if KO, > 0 if OK
3297
     */
3298
    function clone_price($fromId, $toId)
3299
    {
3300
        // phpcs:enable
3301
        $this->db->begin();
3302
3303
        // les prix
3304
        $sql = "INSERT " . MAIN_DB_PREFIX . "product_price (";
3305
        $sql .= " fk_product, date_price, price, tva_tx, localtax1_tx, localtax2_tx, fk_user_author, tosell)";
3306
        $sql .= " SELECT " . $toId . ", date_price, price, tva_tx, localtax1_tx, localtax2_tx, fk_user_author, tosell";
3307
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_price ";
3308
        $sql .= " WHERE fk_product = " . $fromId;
3309
3310
        dol_syslog(get_class($this) . '::clone_price', LOG_DEBUG);
3311
        if (!$this->db->query($sql)) {
3312
            $this->db->rollback();
3313
            return -1;
3314
        }
3315
        $this->db->commit();
3316
        return 1;
3317
    }
3318
3319
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3320
    /**
3321
     * Clone links between products
3322
     *
3323
     * @param  int $fromId Product id
3324
     * @param  int $toId   Product id
3325
     * @return int                  <0 if KO, >0 if OK
3326
     */
3327
    function clone_associations($fromId, $toId)
3328
    {
3329
        // phpcs:enable
3330
        $this->db->begin();
3331
3332
        $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'product_association (fk_product_pere, fk_product_fils, qty)';
3333
        $sql .= " SELECT " . $toId . ", fk_product_fils, qty FROM " . MAIN_DB_PREFIX . "product_association";
3334
        $sql .= " WHERE fk_product_pere = " . $fromId;
3335
3336
        dol_syslog(get_class($this) . '::clone_association', LOG_DEBUG);
3337
        if (!$this->db->query($sql)) {
3338
            $this->db->rollback();
3339
            return -1;
3340
        }
3341
3342
        $this->db->commit();
3343
        return 1;
3344
    }
3345
3346
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3347
    /**
3348
     *  Recopie les fournisseurs et prix fournisseurs d'un produit/service sur un autre
3349
     *
3350
     * @param  int $fromId Id produit source
3351
     * @param  int $toId   Id produit cible
3352
     * @return int                 < 0 si erreur, > 0 si ok
3353
     */
3354
    function clone_fournisseurs($fromId, $toId)
3355
    {
3356
        // phpcs:enable
3357
        $this->db->begin();
3358
3359
        $now = dol_now();
3360
3361
        // les fournisseurs
3362
        /* $sql = "INSERT ".MAIN_DB_PREFIX."product_fournisseur ("
3363
          . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
3364
          . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
3365
          . " FROM ".MAIN_DB_PREFIX."product_fournisseur"
3366
          . " WHERE fk_product = ".$fromId;
3367
3368
          if ( ! $this->db->query($sql ) )
3369
          {
3370
          $this->db->rollback();
3371
          return -1;
3372
          } */
3373
3374
        // les prix de fournisseurs.
3375
        $sql = "INSERT " . MAIN_DB_PREFIX . "product_fournisseur_price (";
3376
        $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user)";
3377
        $sql .= " SELECT '" . $this->db->idate($now) . "', " . $toId . ", fk_soc, price, quantity, fk_user";
3378
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_fournisseur_price";
3379
        $sql .= " WHERE fk_product = " . $fromId;
3380
3381
        dol_syslog(get_class($this) . '::clone_fournisseurs', LOG_DEBUG);
3382
        $resql = $this->db->query($sql);
3383
        if (!$resql) {
3384
            $this->db->rollback();
3385
            return -1;
3386
        } else {
3387
            $this->db->commit();
3388
            return 1;
3389
        }
3390
    }
3391
3392
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3393
    /**
3394
     *  Fonction recursive uniquement utilisee par get_arbo_each_prod, recompose l'arborescence des sousproduits
3395
     *     Define value of this->res
3396
     *
3397
     * @param  array  $prod       Products array
3398
     * @param  string $compl_path Directory path of parents to add before
3399
     * @param  int    $multiply   Because each sublevel must be multiplicated by parent nb
3400
     * @param  int    $level      Init level
3401
     * @param  int    $id_parent  Id parent
3402
     * @return void
3403
     */
3404
    function fetch_prod_arbo($prod, $compl_path = "", $multiply = 1, $level = 1, $id_parent = 0)
3405
    {
3406
        // phpcs:enable
3407
        global $conf, $langs;
3408
3409
        $product = new Product($this->db);
3410
        //var_dump($prod);
3411
        foreach ($prod as $id_product => $desc_pere) {    // $id_product is 0 (first call starting with root top) or an id of a sub_product
3412
            if (is_array($desc_pere)) {    // If desc_pere is an array, this means it's a child
3413
                $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
3414
                $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
3415
                $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
3416
                $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
3417
                $incdec = !empty($desc_pere[4]) ? $desc_pere[4] : 0;
3418
3419
                if ($multiply < 1) {
3420
                    $multiply = 1;
3421
                }
3422
3423
                //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
3424
                $this->fetch($id);        // Load product
3425
                $this->load_stock('nobatch,novirtual');    // Load stock to get true this->stock_reel
3426
                $this->res[] = array(
3427
                    'id' => $id, // Id product
3428
                    'id_parent' => $id_parent,
3429
                    'ref' => $this->ref, // Ref product
3430
                    'nb' => $nb, // Nb of units that compose parent product
3431
                    'nb_total' => $nb * $multiply, // Nb of units for all nb of product
3432
                    'stock' => $this->stock_reel, // Stock
3433
                    'stock_alert' => $this->seuil_stock_alerte, // Stock alert
3434
                    'label' => $label,
3435
                    'fullpath' => $compl_path . $label, // Label
3436
                    'type' => $type, // Nb of units that compose parent product
3437
                    'desiredstock' => $this->desiredstock,
3438
                    'level' => $level,
3439
                    'incdec' => $incdec,
3440
                    'entity' => $this->entity
3441
                );
3442
3443
                // Recursive call if there is childs to child
3444
                if (is_array($desc_pere['childs'])) {
3445
                    //print 'YYY We go down for '.$desc_pere[3]." -> \n";
3446
                    $this->fetch_prod_arbo($desc_pere['childs'], $compl_path . $desc_pere[3] . " -> ", $desc_pere[1] * $multiply, $level + 1, $id);
3447
                }
3448
            }
3449
        }
3450
    }
3451
3452
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3453
    /**
3454
     *  Build the tree of subproducts into an array
3455
     *  this->sousprods is loaded by this->get_sousproduits_arbo()
3456
     *
3457
     * @param  int $multiply Because each sublevel must be multiplicated by parent nb
3458
     * @return array                     $this->res
3459
     */
3460
    function get_arbo_each_prod($multiply = 1)
3461
    {
3462
        // phpcs:enable
3463
        $this->res = array();
3464
        if (isset($this->sousprods) && is_array($this->sousprods)) {
3465
            foreach ($this->sousprods as $prod_name => $desc_product) {
3466
                if (is_array($desc_product)) {
3467
                    $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id);
3468
                }
3469
            }
3470
        }
3471
        //var_dump($this->res);
3472
        return $this->res;
3473
    }
3474
3475
    /**
3476
     *  Return all parent products for current product (first level only)
3477
     *
3478
     * @return int            Nb of father + child
3479
     */
3480
    public function hasFatherOrChild()
3481
    {
3482
        $nb = 0;
3483
3484
        $sql = "SELECT COUNT(pa.rowid) as nb";
3485
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_association as pa";
3486
        $sql .= " WHERE pa.fk_product_fils = " . $this->id . " OR pa.fk_product_pere = " . $this->id;
3487
        $resql = $this->db->query($sql);
3488
        if ($resql) {
3489
            $obj = $this->db->fetch_object($resql);
3490
            if ($obj) {
3491
                $nb = $obj->nb;
3492
            }
3493
        } else {
3494
            return -1;
3495
        }
3496
3497
        return $nb;
3498
    }
3499
3500
    /**
3501
     * Return if a product has variants or not
3502
     *
3503
     * @return int        Number of variants
3504
     */
3505
    public function hasVariants()
3506
    {
3507
        $nb = 0;
3508
        $sql = "SELECT count(rowid) as nb FROM " . MAIN_DB_PREFIX . "product_attribute_combination WHERE fk_product_parent = " . $this->id;
3509
        $sql .= " AND entity IN (" . getEntity('product') . ")";
3510
3511
        $resql = $this->db->query($sql);
3512
        if ($resql) {
3513
            $obj = $this->db->fetch_object($resql);
3514
            if ($obj) {
3515
                $nb = $obj->nb;
3516
            }
3517
        }
3518
3519
        return $nb;
3520
    }
3521
3522
    /**
3523
     * Return if loaded product is a variant
3524
     *
3525
     * @return int
3526
     */
3527
    public function isVariant()
3528
    {
3529
        global $conf;
3530
        if (!empty($conf->variants->enabled)) {
3531
            $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "product_attribute_combination WHERE fk_product_child = " . $this->id . " AND entity IN (" . getEntity('product') . ")";
3532
3533
            $query = $this->db->query($sql);
3534
3535
            if ($query) {
3536
                if (!$this->db->num_rows($query)) {
3537
                    return false;
3538
                }
3539
                return true;
3540
            } else {
3541
                dol_print_error($this->db);
3542
                return -1;
3543
            }
3544
        } else {
3545
            return false;
3546
        }
3547
    }
3548
3549
    /**
3550
     *  Return all parent products for current product (first level only)
3551
     *
3552
     * @return array         Array of product
3553
     */
3554
    public function getFather()
3555
    {
3556
        $sql = "SELECT p.rowid, p.label as label, p.ref as ref, pa.fk_product_pere as id, p.fk_product_type, pa.qty, pa.incdec, p.entity";
3557
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_association as pa,";
3558
        $sql .= " " . MAIN_DB_PREFIX . "product as p";
3559
        $sql .= " WHERE p.rowid = pa.fk_product_pere";
3560
        $sql .= " AND pa.fk_product_fils = " . $this->id;
3561
3562
        $res = $this->db->query($sql);
3563
        if ($res) {
3564
            $prods = array();
3565
            while ($record = $this->db->fetch_array($res)) {
3566
                // $record['id'] = $record['rowid'] = id of father
3567
                $prods[$record['id']]['id'] = $record['rowid'];
3568
                $prods[$record['id']]['ref'] = $record['ref'];
3569
                $prods[$record['id']]['label'] = $record['label'];
3570
                $prods[$record['id']]['qty'] = $record['qty'];
3571
                $prods[$record['id']]['incdec'] = $record['incdec'];
3572
                $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
3573
                $prods[$record['id']]['entity'] = $record['entity'];
3574
            }
3575
            return $prods;
3576
        } else {
3577
            dol_print_error($this->db);
3578
            return -1;
3579
        }
3580
    }
3581
3582
    /**
3583
     *  Return childs of product $id
3584
     *
3585
     * @param  int $id             Id of product to search childs of
3586
     * @param  int $firstlevelonly Return only direct child
3587
     * @param  int $level          Level of recursing call (start to 1)
3588
     * @return array                       Return array(prodid=>array(0=prodid, 1=>qty, 2=> ...)
3589
     */
3590
    public function getChildsArbo($id, $firstlevelonly = 0, $level = 1)
3591
    {
3592
        global $alreadyfound;
3593
3594
        $sql = "SELECT p.rowid, p.label as label, pa.qty as qty, pa.fk_product_fils as id, p.fk_product_type, pa.incdec";
3595
        $sql .= " FROM " . MAIN_DB_PREFIX . "product as p";
3596
        $sql .= ", " . MAIN_DB_PREFIX . "product_association as pa";
3597
        $sql .= " WHERE p.rowid = pa.fk_product_fils";
3598
        $sql .= " AND pa.fk_product_pere = " . $id;
3599
        $sql .= " AND pa.fk_product_fils != " . $id;    // This should not happens, it is to avoid infinite loop if it happens
3600
3601
        dol_syslog(get_class($this) . '::getChildsArbo id=' . $id . ' level=' . $level, LOG_DEBUG);
3602
3603
        if ($level == 1) {
3604
            $alreadyfound = array($id => 1);    // We init array of found object to start of tree, so if we found it later (should not happened), we stop immediatly
3605
        }
3606
        // Protection against infinite loop
3607
        if ($level > 30) {
3608
            return array();
3609
        }
3610
3611
        $res = $this->db->query($sql);
3612
        if ($res) {
3613
            $prods = array();
3614
            while ($rec = $this->db->fetch_array($res)) {
3615
                if (!empty($alreadyfound[$rec['rowid']])) {
3616
                    dol_syslog(get_class($this) . '::getChildsArbo the product id=' . $rec['rowid'] . ' was already found at a higher level in tree. We discard to avoid infinite loop', LOG_WARNING);
3617
                    continue;
3618
                }
3619
                $alreadyfound[$rec['rowid']] = 1;
3620
                $prods[$rec['rowid']] = array(
3621
                    0 => $rec['rowid'],
3622
                    1 => $rec['qty'],
3623
                    2 => $rec['fk_product_type'],
3624
                    3 => $this->db->escape($rec['label']),
3625
                    4 => $rec['incdec']
3626
                );
3627
                //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
3628
                //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
3629
                if (empty($firstlevelonly)) {
3630
                    $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1);
3631
                    foreach ($listofchilds as $keyChild => $valueChild) {
3632
                        $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
3633
                    }
3634
                }
3635
            }
3636
3637
            return $prods;
3638
        } else {
3639
            dol_print_error($this->db);
3640
            return -1;
3641
        }
3642
    }
3643
3644
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3645
    /**
3646
     *     Return tree of all subproducts for product. Tree contains id, name and quantity.
3647
     *     Set this->sousprods
3648
     *
3649
     * @return void
3650
     */
3651
    function get_sousproduits_arbo()
3652
    {
3653
        // phpcs:enable
3654
        $parent = array();
3655
3656
        foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) {    // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
3657
            $parent[$this->label][$keyChild] = $valueChild;
3658
        }
3659
        foreach ($parent as $key => $value) {        // key=label, value is array of childs
3660
            $this->sousprods[$key] = $value;
3661
        }
3662
    }
3663
3664
    /**
3665
     *    Return clicable link of object (with eventually picto)
3666
     *
3667
     * @param  int    $withpicto             Add picto into link
3668
     * @param  string $option                Where point the link ('stock', 'composition', 'category', 'supplier', '')
3669
     * @param  int    $maxlength             Maxlength of ref
3670
     * @param  int    $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
3671
     * @param  int    $notooltip			 No tooltip
3672
     * @return string                                String with URL
3673
     */
3674
    public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0)
3675
    {
3676
        global $conf, $langs, $hookmanager;
3677
3678
        // include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
3679
        include_once DOL_BASE_PATH . '/core/lib/product.lib.php';
3680
3681
        $result = '';
3682
        $newref = $this->ref;
3683
        if ($maxlength) {
3684
            $newref = dol_trunc($newref, $maxlength, 'middle');
3685
        }
3686
3687
        if ($this->type == Product::TYPE_PRODUCT) {
3688
            $label = '<u>' . $langs->trans("ShowProduct") . '</u>';
3689
        }
3690
        if ($this->type == Product::TYPE_SERVICE) {
3691
            $label = '<u>' . $langs->trans("ShowService") . '</u>';
3692
        }
3693
        if (!empty($this->ref)) {
3694
            $label .= '<br><b>' . $langs->trans('ProductRef') . ':</b> ' . $this->ref;
3695
        }
3696
        if (!empty($this->label)) {
3697
            $label .= '<br><b>' . $langs->trans('ProductLabel') . ':</b> ' . $this->label;
3698
        }
3699
3700
        if ($this->type == Product::TYPE_PRODUCT) {
3701
            if ($this->weight) {
3702
                $label .= "<br><b>" . $langs->trans("Weight") . '</b>: ' . $this->weight . ' ' . measuring_units_string($this->weight_units, "weight");
3703
            }
3704
            if ($this->length) {
3705
                $label .= "<br><b>" . $langs->trans("Length") . '</b>: ' . $this->length . ' ' . measuring_units_string($this->length_units, 'length');
3706
            }
3707
            if ($this->surface) {
3708
                $label .= "<br><b>" . $langs->trans("Surface") . '</b>: ' . $this->surface . ' ' . measuring_units_string($this->surface_units, 'surface');
3709
            }
3710
            if ($this->volume) {
3711
                $label .= "<br><b>" . $langs->trans("Volume") . '</b>: ' . $this->volume . ' ' . measuring_units_string($this->volume_units, 'volume');
3712
            }
3713
        }
3714
3715
        if ($this->type == Product::TYPE_PRODUCT || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
3716
            if (!empty($conf->productbatch->enabled)) {
3717
                $langs->load("productbatch");
3718
                $label .= "<br><b>" . $langs->trans("ManageLotSerial") . '</b>: ' . $this->getLibStatut(0, 2);
3719
            }
3720
        }
3721
        //if ($this->type == Product::TYPE_SERVICE)
3722
        //{
3723
        //
3724
        //}
3725
        if (!empty($conf->accounting->enabled) && $this->status) {
3726
            include_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
3727
            $label .= '<br><b>' . $langs->trans('ProductAccountancySellCode') . ':</b> ' . length_accountg($this->accountancy_code_sell);
3728
            $label .= '<br><b>' . $langs->trans('ProductAccountancySellIntraCode') . ':</b> ' . length_accountg($this->accountancy_code_sell_export);
3729
            $label .= '<br><b>' . $langs->trans('ProductAccountancySellExportCode') . ':</b> ' . length_accountg($this->accountancy_code_sell_intra);
3730
        }
3731
        if (!empty($conf->accounting->enabled) && $this->status_buy) {
3732
            include_once DOL_DOCUMENT_ROOT . '/core/lib/accounting.lib.php';
3733
            $label .= '<br><b>' . $langs->trans('ProductAccountancyBuyCode') . ':</b> ' . length_accountg($this->accountancy_code_buy);
3734
        }
3735
        if (!empty($this->entity)) {
3736
            $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80);
3737
            if ($this->nbphoto > 0) {
3738
                $label .= '<br>' . $tmpphoto;
3739
            }
3740
        }
3741
3742
        $linkclose = '';
3743
        if (empty($notooltip)) {
3744
            if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3745
                $label = $langs->trans("ShowProduct");
3746
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
3747
            }
3748
3749
            $linkclose .= ' title="' . dol_escape_htmltag($label, 1, 1) . '"';
3750
            $linkclose .= ' class="classfortooltip"';
3751
3752
            /*
3753
              $hookmanager->initHooks(array('productdao'));
3754
              $parameters=array('id'=>$this->id);
3755
              $reshook=$hookmanager->executeHooks('getnomurltooltip',$parameters,$this,$action);    // Note that $action and $object may have been modified by some hooks
3756
              if ($reshook > 0) $linkclose = $hookmanager->resPrint;
3757
             */
3758
        }
3759
3760
        if ($option == 'supplier' || $option == 'category') {
3761
            // $url = DOL_URL_ROOT . '/product/fournisseurs.php?id=' . $this->id;
3762
            $url = BASE_URI . '?controller=product&method=fournisseurs&id=' . $this->id;
0 ignored issues
show
Bug introduced by
The constant BASE_URI was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3763
        } else if ($option == 'stock') {
3764
            // $url = DOL_URL_ROOT . '/product/stock/product.php?id=' . $this->id;
3765
            $url = BASE_URI . '?controller/stock=product&method=product&id=' . $this->id;
3766
        } else if ($option == 'composition') {
3767
            // $url = DOL_URL_ROOT . '/product/composition/card.php?id=' . $this->id;
3768
            $url = BASE_URI . '?controller=product/composition&method=card&id=' . $this->id;
3769
        } else {
3770
            // $url = DOL_URL_ROOT . '/product/card.php?id=' . $this->id;
3771
            $url = BASE_URI . '?controller=product&method=card&id=' . $this->id;
3772
        }
3773
3774
        if ($option !== 'nolink') {
3775
            // Add param to save lastsearch_values or not
3776
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3777
            if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3778
                $add_save_lastsearch_values = 1;
3779
            }
3780
            if ($add_save_lastsearch_values) {
3781
                $url .= '&save_lastsearch_values=1';
3782
            }
3783
        }
3784
3785
        $linkstart = '<a href="' . $url . '"';
3786
        $linkstart .= $linkclose . '>';
3787
        $linkend = '</a>';
3788
3789
        $result .= $linkstart;
3790
        if ($withpicto) {
3791
            if ($this->type == Product::TYPE_PRODUCT) {
3792
                $result .= (img_object(($notooltip ? '' : $label), 'product', ($notooltip ? 'class="paddingright"' : 'class="paddingright classfortooltip"'), 0, 0, $notooltip ? 0 : 1));
3793
            }
3794
            if ($this->type == Product::TYPE_SERVICE) {
3795
                $result .= (img_object(($notooltip ? '' : $label), 'service', ($notooltip ? 'class="paddinright"' : 'class="paddingright classfortooltip"'), 0, 0, $notooltip ? 0 : 1));
3796
            }
3797
        }
3798
        $result .= $newref;
3799
        $result .= $linkend;
3800
3801
        global $action;
3802
        $hookmanager->initHooks(array('productdao'));
3803
        $parameters = array('id' => $this->id, 'getnomurl' => $result);
3804
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
3805
        if ($reshook > 0) {
3806
            $result = $hookmanager->resPrint;
3807
        } else {
3808
            $result .= $hookmanager->resPrint;
3809
        }
3810
3811
        return $result;
3812
    }
3813
3814
    /**
3815
     *  Create a document onto disk according to template module.
3816
     *
3817
     * @param  string    $modele      Force model to use ('' to not force)
3818
     * @param  Translate $outputlangs Object langs to use for output
3819
     * @param  int       $hidedetails Hide details of lines
3820
     * @param  int       $hidedesc    Hide description
3821
     * @param  int       $hideref     Hide ref
3822
     * @return int                         0 if KO, 1 if OK
3823
     */
3824
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
3825
    {
3826
        global $conf, $user, $langs;
3827
3828
        $langs->load("products");
3829
3830
        // Positionne le modele sur le nom du modele a utiliser
3831
        if (!dol_strlen($modele)) {
3832
            if (!empty($conf->global->PRODUCT_ADDON_PDF)) {
3833
                $modele = $conf->global->PRODUCT_ADDON_PDF;
3834
            } else {
3835
                $modele = 'strato';
3836
            }
3837
        }
3838
3839
        $modelpath = "core/modules/product/doc/";
3840
3841
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
3842
    }
3843
3844
    /**
3845
     *    Return label of status of object
3846
     *
3847
     * @param  int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
3848
     * @param  int $type 0=Sell, 1=Buy, 2=Batch Number management
3849
     * @return string          Label of status
3850
     */
3851
    public function getLibStatut($mode = 0, $type = 0)
3852
    {
3853
        switch ($type) {
3854
            case 0:
3855
                return $this->LibStatut($this->status, $mode, $type);
3856
            case 1:
3857
                return $this->LibStatut($this->status_buy, $mode, $type);
3858
            case 2:
3859
                return $this->LibStatut($this->status_batch, $mode, $type);
3860
            default:
3861
                //Simulate previous behavior but should return an error string
3862
                return $this->LibStatut($this->status_buy, $mode, $type);
3863
        }
3864
    }
3865
3866
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3867
    /**
3868
     *    Return label of a given status
3869
     *
3870
     * @param  int $status Statut
3871
     * @param  int $mode   0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
3872
     * @param  int $type   0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
3873
     * @return string              Label of status
3874
     */
3875
    function LibStatut($status, $mode = 0, $type = 0)
3876
    {
3877
        // phpcs:enable
3878
        global $conf, $langs;
3879
3880
        $langs->load('products');
3881
        if (!empty($conf->productbatch->enabled)) {
3882
            $langs->load("productbatch");
3883
        }
3884
3885
        if ($type == 2) {
3886
            switch ($mode) {
3887
                case 0:
3888
                    return ($status == 0 ? $langs->trans('ProductStatusNotOnBatch') : $langs->trans('ProductStatusOnBatch'));
3889
                case 1:
3890
                    return ($status == 0 ? $langs->trans('ProductStatusNotOnBatchShort') : $langs->trans('ProductStatusOnBatchShort'));
3891
                case 2:
3892
                    return $this->LibStatut($status, 3, 2) . ' ' . $this->LibStatut($status, 1, 2);
3893
                case 3:
3894
                    if ($status == 0) {
3895
                        return img_picto($langs->trans('ProductStatusNotOnBatch'), 'statut5');
3896
                    }
3897
                    return img_picto($langs->trans('ProductStatusOnBatch'), 'statut4');
3898
                case 4:
3899
                    return $this->LibStatut($status, 3, 2) . ' ' . $this->LibStatut($status, 0, 2);
3900
                case 5:
3901
                    return $this->LibStatut($status, 1, 2) . ' ' . $this->LibStatut($status, 3, 2);
3902
                default:
3903
                    return $langs->trans('Unknown');
3904
            }
3905
        }
3906
        if ($mode == 0) {
3907
            if ($status == 0) {
3908
                return ($type == 0 ? $langs->trans('ProductStatusNotOnSellShort') : $langs->trans('ProductStatusNotOnBuyShort'));
3909
            } elseif ($status == 1) {
3910
                return ($type == 0 ? $langs->trans('ProductStatusOnSellShort') : $langs->trans('ProductStatusOnBuyShort'));
3911
            }
3912
        } elseif ($mode == 1) {
3913
            if ($status == 0) {
3914
                return ($type == 0 ? $langs->trans('ProductStatusNotOnSell') : $langs->trans('ProductStatusNotOnBuy'));
3915
            } elseif ($status == 1) {
3916
                return ($type == 0 ? $langs->trans('ProductStatusOnSell') : $langs->trans('ProductStatusOnBuy'));
3917
            }
3918
        } elseif ($mode == 2) {
3919
            if ($status == 0) {
3920
                return img_picto($langs->trans('ProductStatusNotOnSell'), 'statut5', 'class="pictostatus"') . ' ' . ($type == 0 ? $langs->trans('ProductStatusNotOnSellShort') : $langs->trans('ProductStatusNotOnBuyShort'));
3921
            } elseif ($status == 1) {
3922
                return img_picto($langs->trans('ProductStatusOnSell'), 'statut4', 'class="pictostatus"') . ' ' . ($type == 0 ? $langs->trans('ProductStatusOnSellShort') : $langs->trans('ProductStatusOnBuyShort'));
3923
            }
3924
        } elseif ($mode == 3) {
3925
            if ($status == 0) {
3926
                return img_picto(($type == 0 ? $langs->trans('ProductStatusNotOnSell') : $langs->trans('ProductStatusNotOnBuy')), 'statut5', 'class="pictostatus"');
3927
            } elseif ($status == 1) {
3928
                return img_picto(($type == 0 ? $langs->trans('ProductStatusOnSell') : $langs->trans('ProductStatusOnBuy')), 'statut4', 'class="pictostatus"');
3929
            }
3930
        } elseif ($mode == 4) {
3931
            if ($status == 0) {
3932
                return img_picto($langs->trans('ProductStatusNotOnSell'), 'statut5', 'class="pictostatus"') . ' ' . ($type == 0 ? $langs->trans('ProductStatusNotOnSell') : $langs->trans('ProductStatusNotOnBuy'));
3933
            } elseif ($status == 1) {
3934
                return img_picto($langs->trans('ProductStatusOnSell'), 'statut4', 'class="pictostatus"') . ' ' . ($type == 0 ? $langs->trans('ProductStatusOnSell') : $langs->trans('ProductStatusOnBuy'));
3935
            }
3936
        } elseif ($mode == 5) {
3937
            if ($status == 0) {
3938
                return ($type == 0 ? $langs->trans('ProductStatusNotOnSellShort') : $langs->trans('ProductStatusNotOnBuyShort')) . ' ' . img_picto(($type == 0 ? $langs->trans('ProductStatusNotOnSell') : $langs->trans('ProductStatusNotOnBuy')), 'statut5', 'class="pictostatus"');
3939
            } elseif ($status == 1) {
3940
                return ($type == 0 ? $langs->trans('ProductStatusOnSellShort') : $langs->trans('ProductStatusOnBuyShort')) . ' ' . img_picto(($type == 0 ? $langs->trans('ProductStatusOnSell') : $langs->trans('ProductStatusOnBuy')), 'statut4', 'class="pictostatus"');
3941
            }
3942
        } elseif ($mode == 6) {
3943
            if ($status == 0) {
3944
                return ($type == 0 ? $langs->trans('ProductStatusNotOnSellShort') : $langs->trans('ProductStatusNotOnBuyShort')) . ' ' . img_picto(($type == 0 ? $langs->trans('ProductStatusNotOnSell') : $langs->trans('ProductStatusNotOnBuy')), 'statut5', 'class="pictostatus"');
3945
            } elseif ($status == 1) {
3946
                return ($type == 0 ? $langs->trans('ProductStatusOnSellShort') : $langs->trans('ProductStatusOnBuyShort')) . ' ' . img_picto(($type == 0 ? $langs->trans('ProductStatusOnSell') : $langs->trans('ProductStatusOnBuy')), 'statut4', 'class="pictostatus"');
3947
            }
3948
        }
3949
        return $langs->trans('Unknown');
3950
    }
3951
3952
    /**
3953
     *  Retourne le libelle du finished du produit
3954
     *
3955
     * @return string        Libelle
3956
     */
3957
    function getLibFinished()
3958
    {
3959
        global $langs;
3960
        $langs->load('products');
3961
3962
        if ($this->finished == '0') {
3963
            return $langs->trans("RowMaterial");
3964
        }
3965
        if ($this->finished == '1') {
3966
            return $langs->trans("Finished");
3967
        }
3968
        return '';
3969
    }
3970
3971
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
3972
    /**
3973
     *  Adjust stock in a warehouse for product
3974
     *
3975
     * @param  User   $user           user asking change
3976
     * @param  int    $id_entrepot    id of warehouse
3977
     * @param  double $nbpiece        nb of units
3978
     * @param  int    $movement       0 = add, 1 = remove
3979
     * @param  string $label          Label of stock movement
3980
     * @param  double $price          Unit price HT of product, used to calculate average weighted price (PMP in french). If 0, average weighted price is not changed.
3981
     * @param  string $inventorycode  Inventory code
3982
     * @param  string $origin_element Origin element type
3983
     * @param  int    $origin_id      Origin id of element
3984
     * @return int                     <0 if KO, >0 if OK
3985
     */
3986
    function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null)
3987
    {
3988
        // phpcs:enable
3989
        if ($id_entrepot) {
3990
            $this->db->begin();
3991
3992
            include_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
3993
3994
            $op[0] = "+" . trim($nbpiece);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$op was never initialized. Although not strictly required by PHP, it is generally a good practice to add $op = array(); before regardless.
Loading history...
3995
            $op[1] = "-" . trim($nbpiece);
3996
3997
            $movementstock = new MouvementStock($this->db);
3998
            $movementstock->setOrigin($origin_element, $origin_id);
3999
            $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode);
4000
4001
            if ($result >= 0) {
4002
                $this->db->commit();
4003
                return 1;
4004
            } else {
4005
                $this->error = $movementstock->error;
4006
                $this->errors = $movementstock->errors;
4007
4008
                $this->db->rollback();
4009
                return -1;
4010
            }
4011
        }
4012
    }
4013
4014
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4015
    /**
4016
     *  Adjust stock in a warehouse for product with batch number
4017
     *
4018
     * @param  User   $user           user asking change
4019
     * @param  int    $id_entrepot    id of warehouse
4020
     * @param  double $nbpiece        nb of units
4021
     * @param  int    $movement       0 = add, 1 = remove
4022
     * @param  string $label          Label of stock movement
4023
     * @param  double $price          Price to use for stock eval
4024
     * @param  date   $dlc            eat-by date
4025
     * @param  date   $dluo           sell-by date
4026
     * @param  string $lot            Lot number
4027
     * @param  string $inventorycode  Inventory code
4028
     * @param  string $origin_element Origin element type
4029
     * @param  int    $origin_id      Origin id of element
4030
     * @return int                     <0 if KO, >0 if OK
4031
     */
4032
    function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $dlc = '', $dluo = '', $lot = '', $inventorycode = '', $origin_element = '', $origin_id = null)
4033
    {
4034
        // phpcs:enable
4035
        if ($id_entrepot) {
4036
            $this->db->begin();
4037
4038
            include_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
4039
4040
            $op[0] = "+" . trim($nbpiece);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$op was never initialized. Although not strictly required by PHP, it is generally a good practice to add $op = array(); before regardless.
Loading history...
4041
            $op[1] = "-" . trim($nbpiece);
4042
4043
            $movementstock = new MouvementStock($this->db);
4044
            $movementstock->setOrigin($origin_element, $origin_id);
4045
            $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot);
4046
4047
            if ($result >= 0) {
4048
                $this->db->commit();
4049
                return 1;
4050
            } else {
4051
                $this->error = $movementstock->error;
4052
                $this->errors = $movementstock->errors;
4053
4054
                $this->db->rollback();
4055
                return -1;
4056
            }
4057
        }
4058
    }
4059
4060
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4061
    /**
4062
     *    Load information about stock of a product into ->stock_reel, ->stock_warehouse[] (including stock_warehouse[idwarehouse]->detail_batch for batch products)
4063
     *    This function need a lot of load. If you use it on list, use a cache to execute it once for each product id.
4064
     *    If ENTREPOT_EXTRA_STATUS set, filtering on warehouse status possible.
4065
     *
4066
     * @param  string $option '' = Load all stock info, also from closed and internal warehouses,
4067
     * @return int                   < 0 if KO, > 0 if OK
4068
     * @see    load_virtual_stock(), loadBatchInfo()
4069
     */
4070
    function load_stock($option = '')
4071
    {
4072
        // phpcs:enable
4073
        global $conf;
4074
4075
        $this->stock_reel = 0;
4076
        $this->stock_warehouse = array();
4077
        $this->stock_theorique = 0;
4078
4079
        $warehouseStatus = array();
4080
4081
        if (preg_match('/warehouseclosed/', $option)) {
4082
            $warehouseStatus[] = Entrepot::STATUS_CLOSED;
4083
        }
4084
        if (preg_match('/warehouseopen/', $option)) {
4085
            $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL;
4086
        }
4087
        if (preg_match('/warehouseinternal/', $option)) {
4088
            $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL;
4089
        }
4090
4091
        $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
4092
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_stock as ps";
4093
        $sql .= ", " . MAIN_DB_PREFIX . "entrepot as w";
4094
        $sql .= " WHERE w.entity IN (" . getEntity('stock') . ")";
4095
        $sql .= " AND w.rowid = ps.fk_entrepot";
4096
        $sql .= " AND ps.fk_product = " . $this->id;
4097
        if ($conf->global->ENTREPOT_EXTRA_STATUS && count($warehouseStatus)) {
4098
            $sql .= " AND w.statut IN (" . $this->db->escape(implode(',', $warehouseStatus)) . ")";
4099
        }
4100
4101
        dol_syslog(get_class($this) . "::load_stock", LOG_DEBUG);
4102
        $result = $this->db->query($sql);
4103
        if ($result) {
4104
            $num = $this->db->num_rows($result);
4105
            $i = 0;
4106
            if ($num > 0) {
4107
                while ($i < $num) {
4108
                    $row = $this->db->fetch_object($result);
4109
                    $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
4110
                    $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
4111
                    $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
4112
                    if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
4113
                        $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
4114
                    }
4115
                    $this->stock_reel += $row->reel;
4116
                    $i++;
4117
                }
4118
            }
4119
            $this->db->free($result);
4120
4121
            if (!preg_match('/novirtual/', $option)) {
4122
                $this->load_virtual_stock();        // This also load stats_commande_fournisseur, ...
4123
            }
4124
4125
            return 1;
4126
        } else {
4127
            $this->error = $this->db->lasterror();
4128
            return -1;
4129
        }
4130
    }
4131
4132
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4133
    /**
4134
     *    Load value ->stock_theorique of a product. Property this->id must be defined.
4135
     *    This function need a lot of load. If you use it on list, use a cache to execute it one for each product id.
4136
     *
4137
     *    @return   int             < 0 if KO, > 0 if OK
4138
     *    @see		load_stock(), loadBatchInfo()
4139
     */
4140
    function load_virtual_stock()
4141
    {
4142
        // phpcs:enable
4143
        global $conf, $hookmanager, $action;
4144
4145
        $stock_commande_client = 0;
4146
        $stock_commande_fournisseur = 0;
4147
        $stock_sending_client = 0;
4148
        $stock_reception_fournisseur = 0;
4149
4150
        if (!empty($conf->commande->enabled)) {
4151
            $result = $this->load_stats_commande(0, '1,2', 1);
4152
            if ($result < 0)
4153
                dol_print_error($this->db, $this->error);
4154
            $stock_commande_client = $this->stats_commande['qty'];
4155
        }
4156
        if (!empty($conf->expedition->enabled)) {
4157
            $result = $this->load_stats_sending(0, '1,2', 1);
4158
            if ($result < 0)
4159
                dol_print_error($this->db, $this->error);
4160
            $stock_sending_client = $this->stats_expedition['qty'];
4161
        }
4162
        if (!empty($conf->fournisseur->enabled)) {
4163
            $result = $this->load_stats_commande_fournisseur(0, '1,2,3,4', 1);
4164
            if ($result < 0)
4165
                dol_print_error($this->db, $this->error);
4166
            $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
4167
4168
            $result = $this->load_stats_reception(0, '4', 1);
4169
            if ($result < 0)
4170
                dol_print_error($this->db, $this->error);
4171
            $stock_reception_fournisseur = $this->stats_reception['qty'];
4172
        }
4173
4174
        // Stock decrease mode
4175
        if (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
4176
            $this->stock_theorique = $this->stock_reel - $stock_commande_client + $stock_sending_client;
4177
        }
4178
        if (!empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER)) {
4179
            $this->stock_theorique = $this->stock_reel;
4180
        }
4181
        if (!empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
4182
            $this->stock_theorique = $this->stock_reel - $stock_commande_client;
4183
        }
4184
        // Stock Increase mode
4185
        if (!empty($conf->global->STOCK_CALCULATE_ON_RECEPTION) || !empty($conf->global->STOCK_CALCULATE_ON_RECEPTION_CLOSE)) {
4186
            $this->stock_theorique += $stock_commande_fournisseur - $stock_reception_fournisseur;
4187
        }
4188
        if (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER)) {
4189
            $this->stock_theorique += $stock_commande_fournisseur - $stock_reception_fournisseur;
4190
        }
4191
        if (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER)) {
4192
            $this->stock_theorique -= $stock_reception_fournisseur;
4193
        }
4194
        if (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL)) {
4195
            $this->stock_theorique += $stock_commande_fournisseur - $stock_reception_fournisseur;
4196
        }
4197
4198
        if (!is_object($hookmanager)) {
4199
            include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
4200
            $hookmanager = new HookManager($this->db);
4201
        }
4202
        $hookmanager->initHooks(array('productdao'));
4203
        $parameters = array('id' => $this->id);
4204
        // Note that $action and $object may have been modified by some hooks
4205
        $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
4206
        if ($reshook > 0)
4207
            $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
4208
4209
        return 1;
4210
    }
4211
4212
    /**
4213
     *  Load existing information about a serial
4214
     *
4215
     * @param  string $batch Lot/serial number
4216
     * @return array                    Array with record into product_batch
4217
     * @see    load_stock(), load_virtual_stock()
4218
     */
4219
    function loadBatchInfo($batch)
4220
    {
4221
        $result = array();
4222
4223
        $sql = "SELECT pb.batch, pb.eatby, pb.sellby, SUM(pb.qty) FROM " . MAIN_DB_PREFIX . "product_batch as pb, " . MAIN_DB_PREFIX . "product_stock as ps";
4224
        $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = " . $this->id . " AND pb.batch = '" . $this->db->escape($batch) . "'";
4225
        $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
4226
        dol_syslog(get_class($this) . "::loadBatchInfo load first entry found for lot/serial = " . $batch, LOG_DEBUG);
4227
        $resql = $this->db->query($sql);
4228
        if ($resql) {
4229
            $num = $this->db->num_rows($resql);
4230
            $i = 0;
4231
            while ($i < $num) {
4232
                $obj = $this->db->fetch_object($resql);
4233
                $result[] = array('batch' => $batch, 'eatby' => $this->db->jdate($obj->eatby), 'sellby' => $this->db->jdate($obj->sellby), 'qty' => $obj->qty);
4234
                $i++;
4235
            }
4236
            return $result;
4237
        } else {
4238
            dol_print_error($this->db);
4239
            $this->db->rollback();
4240
            return array();
4241
        }
4242
    }
4243
4244
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4245
    /**
4246
     *  Move an uploaded file described into $file array into target directory $sdir.
4247
     *
4248
     * @param  string $sdir Target directory
4249
     * @param  string $file Array of file info of file to upload: array('name'=>..., 'tmp_name'=>...)
4250
     * @return int                    <0 if KO, >0 if OK
4251
     */
4252
    function add_photo($sdir, $file)
4253
    {
4254
        // phpcs:enable
4255
        global $conf;
4256
4257
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
4258
4259
        $result = 0;
4260
4261
        $dir = $sdir;
4262
        if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
4263
            $dir .= '/' . get_exdir($this->id, 2, 0, 0, $this, 'product') . $this->id . "/photos";
4264
        } else {
4265
            $dir .= '/' . get_exdir(0, 0, 0, 0, $this, 'product') . dol_sanitizeFileName($this->ref);
4266
        }
4267
4268
        dol_mkdir($dir);
4269
4270
        $dir_osencoded = $dir;
4271
4272
        if (is_dir($dir_osencoded)) {
4273
            $originImage = $dir . '/' . $file['name'];
4274
4275
            // Cree fichier en taille origine
4276
            $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
4277
4278
            if (file_exists(dol_osencode($originImage))) {
4279
                // Create thumbs
4280
                $this->addThumbs($originImage);
4281
            }
4282
        }
4283
4284
        if (is_numeric($result) && $result > 0) {
4285
            return 1;
4286
        } else {
4287
            return -1;
4288
        }
4289
    }
4290
4291
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4292
    /**
4293
     *  Return if at least one photo is available
4294
     *
4295
     * @param  string $sdir Directory to scan
4296
     * @return boolean                 True if at least one photo is available, False if not
4297
     */
4298
    function is_photo_available($sdir)
4299
    {
4300
        // phpcs:enable
4301
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
4302
        include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
4303
4304
        global $conf;
4305
4306
        $dir = $sdir;
4307
        if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
4308
            $dir .= '/' . get_exdir($this->id, 2, 0, 0, $this, 'product') . $this->id . "/photos/";
4309
        } else {
4310
            $dir .= '/' . get_exdir(0, 0, 0, 0, $this, 'product') . dol_sanitizeFileName($this->ref) . '/';
4311
        }
4312
4313
        $nbphoto = 0;
4314
4315
        $dir_osencoded = dol_osencode($dir);
4316
        if (file_exists($dir_osencoded)) {
4317
            $handle = opendir($dir_osencoded);
4318
            if (is_resource($handle)) {
4319
                while (($file = readdir($handle)) !== false) {
4320
                    if (!utf8_check($file)) {
4321
                        $file = utf8_encode($file);    // To be sure data is stored in UTF8 in memory
4322
                    }
4323
                    if (dol_is_file($dir . $file) && image_format_supported($file) > 0) {
4324
                        return true;
4325
                    }
4326
                }
4327
            }
4328
        }
4329
        return false;
4330
    }
4331
4332
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4333
    /**
4334
     *  Retourne tableau de toutes les photos du produit
4335
     *
4336
     * @param  string $dir   Repertoire a scanner
4337
     * @param  int    $nbmax Nombre maximum de photos (0=pas de max)
4338
     * @return array                   Tableau de photos
4339
     */
4340
    function liste_photos($dir, $nbmax = 0)
4341
    {
4342
        // phpcs:enable
4343
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
4344
        include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
4345
4346
        $nbphoto = 0;
4347
        $tabobj = array();
4348
4349
        $dir_osencoded = dol_osencode($dir);
4350
        $handle = @opendir($dir_osencoded);
4351
        if (is_resource($handle)) {
4352
            while (($file = readdir($handle)) !== false) {
4353
                if (!utf8_check($file)) {
4354
                    $file = utf8_encode($file);    // readdir returns ISO
4355
                }
4356
                if (dol_is_file($dir . $file) && image_format_supported($file) >= 0) {
4357
                    $nbphoto++;
4358
4359
                    // On determine nom du fichier vignette
4360
                    $photo = $file;
4361
                    $photo_vignette = '';
4362
                    if (preg_match('/(' . $this->regeximgext . ')$/i', $photo, $regs)) {
4363
                        $photo_vignette = preg_replace('/' . $regs[0] . '/i', '', $photo) . '_small' . $regs[0];
4364
                    }
4365
4366
                    $dirthumb = $dir . 'thumbs/';
4367
4368
                    // Objet
4369
                    $obj = array();
4370
                    $obj['photo'] = $photo;
4371
                    if ($photo_vignette && dol_is_file($dirthumb . $photo_vignette)) {
4372
                        $obj['photo_vignette'] = 'thumbs/' . $photo_vignette;
4373
                    } else {
4374
                        $obj['photo_vignette'] = "";
4375
                    }
4376
4377
                    $tabobj[$nbphoto - 1] = $obj;
4378
4379
                    // On continue ou on arrete de boucler ?
4380
                    if ($nbmax && $nbphoto >= $nbmax) {
4381
                        break;
4382
                    }
4383
                }
4384
            }
4385
4386
            closedir($handle);
4387
        }
4388
4389
        return $tabobj;
4390
    }
4391
4392
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4393
    /**
4394
     *  Efface la photo du produit et sa vignette
4395
     *
4396
     * @param  string $file Chemin de l'image
4397
     * @return void
4398
     */
4399
    function delete_photo($file)
4400
    {
4401
        // phpcs:enable
4402
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
4403
        include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
4404
4405
        $dir = dirname($file) . '/'; // Chemin du dossier contenant l'image d'origine
4406
        $dirthumb = $dir . '/thumbs/'; // Chemin du dossier contenant la vignette
4407
        $filename = preg_replace('/' . preg_quote($dir, '/') . '/i', '', $file); // Nom du fichier
4408
        // On efface l'image d'origine
4409
        dol_delete_file($file, 0, 0, 0, $this); // For triggers
4410
        // Si elle existe, on efface la vignette
4411
        if (preg_match('/(' . $this->regeximgext . ')$/i', $filename, $regs)) {
4412
            $photo_vignette = preg_replace('/' . $regs[0] . '/i', '', $filename) . '_small' . $regs[0];
4413
            if (file_exists(dol_osencode($dirthumb . $photo_vignette))) {
4414
                dol_delete_file($dirthumb . $photo_vignette);
4415
            }
4416
4417
            $photo_vignette = preg_replace('/' . $regs[0] . '/i', '', $filename) . '_mini' . $regs[0];
4418
            if (file_exists(dol_osencode($dirthumb . $photo_vignette))) {
4419
                dol_delete_file($dirthumb . $photo_vignette);
4420
            }
4421
        }
4422
    }
4423
4424
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4425
    /**
4426
     *  Load size of image file
4427
     *
4428
     * @param  string $file Path to file
4429
     * @return void
4430
     */
4431
    function get_image_size($file)
4432
    {
4433
        // phpcs:enable
4434
        $file_osencoded = dol_osencode($file);
4435
        $infoImg = getimagesize($file_osencoded); // Get information on image
4436
        $this->imgWidth = $infoImg[0]; // Largeur de l'image
4437
        $this->imgHeight = $infoImg[1]; // Hauteur de l'image
4438
    }
4439
4440
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4441
    /**
4442
     *  Load indicators this->nb for the dashboard
4443
     *
4444
     * @return int                 <0 if KO, >0 if OK
4445
     */
4446
    function load_state_board()
4447
    {
4448
        // phpcs:enable
4449
        global $conf, $user, $hookmanager;
4450
4451
        $this->nb = array();
4452
4453
        $sql = "SELECT count(p.rowid) as nb, fk_product_type";
4454
        $sql .= " FROM " . MAIN_DB_PREFIX . "product as p";
4455
        $sql .= ' WHERE p.entity IN (' . getEntity($this->element, 1) . ')';
4456
        // Add where from hooks
4457
        if (is_object($hookmanager)) {
4458
            $parameters = array();
4459
            $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters);    // Note that $action and $object may have been modified by hook
4460
            $sql .= $hookmanager->resPrint;
4461
        }
4462
        $sql .= ' GROUP BY fk_product_type';
4463
4464
        $resql = $this->db->query($sql);
4465
        if ($resql) {
4466
            while ($obj = $this->db->fetch_object($resql)) {
4467
                if ($obj->fk_product_type == 1) {
4468
                    $this->nb["services"] = $obj->nb;
4469
                } else {
4470
                    $this->nb["products"] = $obj->nb;
4471
                }
4472
            }
4473
            $this->db->free($resql);
4474
            return 1;
4475
        } else {
4476
            dol_print_error($this->db);
4477
            $this->error = $this->db->error();
4478
            return -1;
4479
        }
4480
    }
4481
4482
    /**
4483
     * Return if object is a product
4484
     *
4485
     * @return boolean     True if it's a product
4486
     */
4487
    function isProduct()
4488
    {
4489
        return ($this->type == Product::TYPE_PRODUCT ? true : false);
4490
    }
4491
4492
    /**
4493
     * Return if object is a product
4494
     *
4495
     * @return boolean     True if it's a service
4496
     */
4497
    function isService()
4498
    {
4499
        return ($this->type == Product::TYPE_SERVICE ? true : false);
4500
    }
4501
4502
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4503
    /**
4504
     *  Get a barcode from the module to generate barcode values.
4505
     *  Return value is stored into this->barcode
4506
     *
4507
     * @param  Product $object Object product or service
4508
     * @param  string  $type   Barcode type (ean, isbn, ...)
4509
     * @return void
4510
     */
4511
    function get_barcode($object, $type = '')
4512
    {
4513
        // phpcs:enable
4514
        global $conf;
4515
4516
        $result = '';
4517
        if (!empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
4518
            $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
4519
            foreach ($dirsociete as $dirroot) {
4520
                $res = dol_include_once($dirroot . $conf->global->BARCODE_PRODUCT_ADDON_NUM . '.php');
4521
                if ($res) {
4522
                    break;
4523
                }
4524
            }
4525
            $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
4526
            $mod = new $var;
4527
4528
            $result = $mod->getNextValue($object, $type);
4529
4530
            dol_syslog(get_class($this) . "::get_barcode barcode=" . $result . " module=" . $var);
4531
        }
4532
        return $result;
4533
    }
4534
4535
    /**
4536
     *  Initialise an instance with random values.
4537
     *  Used to build previews or test instances.
4538
     *    id must be 0 if object instance is a specimen.
4539
     *
4540
     * @return void
4541
     */
4542
    function initAsSpecimen()
4543
    {
4544
        global $user, $langs, $conf, $mysoc;
4545
4546
        $now = dol_now();
4547
4548
        // Initialize parameters
4549
        $this->specimen = 1;
4550
        $this->id = 0;
4551
        $this->ref = 'PRODUCT_SPEC';
4552
        $this->label = 'PRODUCT SPECIMEN';
4553
        $this->description = 'This is description of this product specimen that was created the ' . dol_print_date($now, 'dayhourlog') . '.';
4554
        $this->specimen = 1;
4555
        $this->country_id = 1;
4556
        $this->tosell = 1;
4557
        $this->tobuy = 1;
4558
        $this->tobatch = 0;
4559
        $this->note = 'This is a comment (private)';
4560
        $this->date_creation = $now;
4561
        $this->date_modification = $now;
4562
4563
        $this->weight = 4;
4564
        $this->weight_unit = 1;
4565
4566
        $this->length = 5;
4567
        $this->length_unit = 1;
4568
        $this->width = 6;
4569
        $this->width_unit = 0;
4570
        $this->height = null;
4571
        $this->height_unit = null;
4572
4573
        $this->surface = 30;
4574
        $this->surface_unit = 0;
4575
        $this->volume = 300;
4576
        $this->volume_unit = 0;
4577
4578
        $this->barcode = -1;    // Create barcode automatically
4579
    }
4580
4581
    /**
4582
     *    Returns the text label from units dictionary
4583
     *
4584
     * @param  string $type Label type (long or short)
4585
     * @return string|int <0 if ko, label if ok
4586
     */
4587
    function getLabelOfUnit($type = 'long')
4588
    {
4589
        global $langs;
4590
4591
        if (!$this->fk_unit) {
4592
            return '';
4593
        }
4594
4595
        $langs->load('products');
4596
4597
        $label_type = 'label';
4598
4599
        if ($type == 'short') {
4600
            $label_type = 'short_label';
4601
        }
4602
4603
        $sql = 'select ' . $label_type . ' from ' . MAIN_DB_PREFIX . 'c_units where rowid=' . $this->fk_unit;
4604
        $resql = $this->db->query($sql);
4605
        if ($resql && $this->db->num_rows($resql) > 0) {
4606
            $res = $this->db->fetch_array($resql);
4607
            $label = $res[$label_type];
4608
            $this->db->free($resql);
4609
            return $label;
4610
        } else {
4611
            $this->error = $this->db->error() . ' sql=' . $sql;
4612
            dol_syslog(get_class($this) . "::getLabelOfUnit Error " . $this->error, LOG_ERR);
4613
            return -1;
4614
        }
4615
    }
4616
4617
    /**
4618
     * Return if object has a sell-by date or eat-by date
4619
     *
4620
     * @return boolean     True if it's has
4621
     */
4622
    function hasbatch()
4623
    {
4624
        return ($this->status_batch == 1 ? true : false);
4625
    }
4626
4627
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
4628
    /**
4629
     * Return minimum product recommended price
4630
     *
4631
     * @return int            Minimum recommanded price that is higher price among all suppliers * PRODUCT_MINIMUM_RECOMMENDED_PRICE
4632
     */
4633
    function min_recommended_price()
4634
    {
4635
        // phpcs:enable
4636
        global $conf;
4637
4638
        $maxpricesupplier = 0;
4639
4640
        if (!empty($conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE)) {
4641
            include_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
4642
            $product_fourn = new ProductFournisseur($this->db);
4643
            $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
4644
4645
            if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
4646
                foreach ($product_fourn_list as $productfourn) {
4647
                    if ($productfourn->fourn_unitprice > $maxpricesupplier) {
4648
                        $maxpricesupplier = $productfourn->fourn_unitprice;
4649
                    }
4650
                }
4651
4652
                $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
4653
            }
4654
        }
4655
4656
        return $maxpricesupplier;
4657
    }
4658
4659
    /**
4660
     * Sets object to supplied categories.
4661
     *
4662
     * Deletes object from existing categories not supplied.
4663
     * Adds it to non existing supplied categories.
4664
     * Existing categories are left untouch.
4665
     *
4666
     * @param  int[]|int $categories Category or categories IDs
4667
     * @return void
4668
     */
4669
    public function setCategories($categories)
4670
    {
4671
        // Handle single category
4672
        if (!is_array($categories)) {
4673
            $categories = array($categories);
4674
        }
4675
4676
        // Get current categories
4677
        include_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
4678
        $c = new Categorie($this->db);
4679
        $existing = $c->containing($this->id, Categorie::TYPE_PRODUCT, 'id');
4680
4681
        // Diff
4682
        if (is_array($existing)) {
4683
            $to_del = array_diff($existing, $categories);
4684
            $to_add = array_diff($categories, $existing);
4685
        } else {
4686
            $to_del = array(); // Nothing to delete
4687
            $to_add = $categories;
4688
        }
4689
4690
        // Process
4691
        foreach ($to_del as $del) {
4692
            if ($c->fetch($del) > 0) {
4693
                $c->del_type($this, 'product');
4694
            }
4695
        }
4696
        foreach ($to_add as $add) {
4697
            if ($c->fetch($add) > 0) {
4698
                $c->add_type($this, 'product');
4699
            }
4700
        }
4701
4702
        return;
4703
    }
4704
4705
    /**
4706
     * Function used to replace a thirdparty id with another one.
4707
     *
4708
     * @param  DoliDB $db        Database handler
4709
     * @param  int    $origin_id Old thirdparty id
4710
     * @param  int    $dest_id   New thirdparty id
4711
     * @return bool
4712
     */
4713
    public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
4714
    {
4715
        $tables = array(
4716
            'product_customer_price',
4717
            'product_customer_price_log'
4718
        );
4719
4720
        return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
4721
    }
4722
4723
    /**
4724
     * Generates prices for a product based on product multiprice generation rules
4725
     *
4726
     * @param  User   $user       User that updates the prices
4727
     * @param  float  $baseprice  Base price
4728
     * @param  string $price_type Base price type
4729
     * @param  float  $price_vat  VAT % tax
4730
     * @param  int    $npr        NPR
4731
     * @param  string $psq        ¿?
4732
     * @return int -1 KO, 1 OK
4733
     */
4734
    public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
4735
    {
4736
        global $conf, $db;
4737
4738
        $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM " . MAIN_DB_PREFIX . "product_pricerules";
4739
        $query = $db->query($sql);
4740
4741
        $rules = array();
4742
4743
        while ($result = $db->fetch_object($query)) {
4744
            $rules[$result->level] = $result;
4745
        }
4746
4747
        //Because prices can be based on other level's prices, we temporarily store them
4748
        $prices = array(
4749
            1 => $baseprice
4750
        );
4751
4752
        for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
4753
4754
            $price = $baseprice;
4755
            $price_min = $baseprice;
4756
4757
            //We have to make sure it does exist and it is > 0
4758
            //First price level only allows changing min_price
4759
            if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
4760
                $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
4761
            }
4762
4763
            $prices[$i] = $price;
4764
4765
            //We have to make sure it does exist and it is > 0
4766
            if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
4767
                $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
4768
            }
4769
4770
            //Little check to make sure the price is modified before triggering generation
4771
            $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
4772
            $check_type = ($baseprice == $this->multiprices_base_type[$i]);
4773
4774
            if ($check_amount && $check_type) {
4775
                continue;
4776
            }
4777
4778
            if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
4779
                return -1;
4780
            }
4781
        }
4782
4783
        return 1;
4784
    }
4785
4786
    /**
4787
     * Returns the rights used for this class
4788
     *
4789
     * @return stdClass
4790
     */
4791
    public function getRights()
4792
    {
4793
        global $user;
4794
4795
        if ($this->isProduct()) {
4796
            return $user->rights->produit;
4797
        } else {
4798
            return $user->rights->service;
4799
        }
4800
    }
4801
4802
    /**
4803
     *  Load information for tab info
4804
     *
4805
     * @param  int $id Id of thirdparty to load
4806
     * @return void
4807
     */
4808
    function info($id)
4809
    {
4810
        $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
4811
        $sql .= " p.fk_user_author, p.fk_user_modif";
4812
        $sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element . " as p";
4813
        $sql .= " WHERE p.rowid = " . $id;
4814
4815
        $result = $this->db->query($sql);
4816
        if ($result) {
4817
            if ($this->db->num_rows($result)) {
4818
                $obj = $this->db->fetch_object($result);
4819
4820
                $this->id = $obj->rowid;
4821
4822
                if ($obj->fk_user_author) {
4823
                    $cuser = new User($this->db);
4824
                    $cuser->fetch($obj->fk_user_author);
4825
                    $this->user_creation = $cuser;
4826
                }
4827
4828
                if ($obj->fk_user_modif) {
4829
                    $muser = new User($this->db);
4830
                    $muser->fetch($obj->fk_user_modif);
4831
                    $this->user_modification = $muser;
4832
                }
4833
4834
                $this->ref = $obj->ref;
4835
                $this->date_creation = $this->db->jdate($obj->date_creation);
4836
                $this->date_modification = $this->db->jdate($obj->date_modification);
4837
            }
4838
4839
            $this->db->free($result);
4840
        } else {
4841
            dol_print_error($this->db);
4842
        }
4843
    }
4844
}
4845