Passed
Branch develop (05352a)
by
unknown
27:04
created

Product::updatePrice()   F

Complexity

Conditions 29
Paths > 20000

Size

Total Lines 159
Code Lines 101

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 29
eloc 101
c 0
b 0
f 0
nc 24984
nop 11
dl 0
loc 159
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
 * Copyright (C) 2019       Frédéric France         <[email protected]>
18
 *
19
 * This program is free software; you can redistribute it and/or modify
20
 * it under the terms of the GNU General Public License as published by
21
 * the Free Software Foundation; either version 3 of the License, or
22
 * (at your option) any later version.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
 * GNU General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU General Public License
30
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31
 */
32
33
/**
34
 *    \file       htdocs/product/class/product.class.php
35
 *    \ingroup    produit
36
 *    \brief      File of class to manage predefined products or services
37
 */
38
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
39
require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
40
require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
41
42
/**
43
 * Class to manage products or services
44
 */
45
class Product extends CommonObject
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
62
    /**
63
     * @var array	List of child tables. To test if we can delete object.
64
     */
65
    protected $childtables=array('supplier_proposaldet', 'propaldet', 'commandedet', 'facturedet', 'contratdet', 'facture_fourn_det', 'commande_fournisseurdet');
66
67
    /**
68
     * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
69
     *
70
     * @var int
71
     */
72
    public $ismultientitymanaged = 1;
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    protected $table_ref_field = 'ref';
78
79
    public $regeximgext='\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.xpm|\.xbm'; // See also into images.lib.php
80
81
    /*
82
    * @deprecated
83
    * @see label
84
    */
85
    public $libelle;
86
    /**
87
     * Product label
88
     *
89
     * @var string
90
     */
91
    public $label;
92
93
    /**
94
     * Product descripion
95
     *
96
     * @var string
97
     */
98
    public $description;
99
100
    /**
101
     * Check TYPE constants
102
     *
103
     * @var int
104
     */
105
    public $type = self::TYPE_PRODUCT;
106
107
    /**
108
     * Selling price
109
     *
110
     * @var float
111
     */
112
    public $price;            // Price net
113
114
    /**
115
     * Price with tax
116
     *
117
     * @var float
118
     */
119
    public $price_ttc;
120
121
    /**
122
     * Minimum price net
123
     *
124
     * @var float
125
     */
126
    public $price_min;
127
128
    /**
129
     * Minimum price with tax
130
     *
131
     * @var float
132
     */
133
    public $price_min_ttc;
134
135
    /*
136
    * Base price ('TTC' for price including tax or 'HT' for net price)
137
    * @var float
138
    */
139
    public $price_base_type;
140
141
    //! Arrays for multiprices
142
    public $multiprices=array();
143
    public $multiprices_ttc=array();
144
    public $multiprices_base_type=array();
145
    public $multiprices_min=array();
146
    public $multiprices_min_ttc=array();
147
    public $multiprices_tva_tx=array();
148
    public $multiprices_recuperableonly=array();
149
150
    //! Price by quantity arrays
151
    public $price_by_qty;
152
    public $prices_by_qty=array();
153
    public $prices_by_qty_id=array();
154
    public $prices_by_qty_list=array();
155
156
    //! Default VAT code for product (link to code into llx_c_tva but without foreign keys)
157
    public $default_vat_code;
158
159
    //! Default VAT rate of product
160
    public $tva_tx;
161
162
    //! French VAT NPR (0 or 1)
163
    public $tva_npr=0;
164
165
    //! Other local taxes
166
    public $localtax1_tx;
167
    public $localtax2_tx;
168
    public $localtax1_type;
169
    public $localtax2_type;
170
171
    /**
172
     * Stock real
173
     *
174
     * @var int
175
     */
176
    public $stock_reel = 0;
177
178
    /**
179
     * Stock virtual
180
     *
181
     * @var int
182
     */
183
    public $stock_theorique;
184
185
    /**
186
     * Cost price
187
     *
188
     * @var float
189
     */
190
    public $cost_price;
191
192
    //! Average price value for product entry into stock (PMP)
193
    public $pmp;
194
195
    /**
196
     * Stock alert
197
     *
198
     * @var int
199
     */
200
    public $seuil_stock_alerte=0;
201
202
    /**
203
     * Ask for replenishment when $desiredstock < $stock_reel
204
     */
205
    public $desiredstock=0;
206
207
    /*
208
    * Service expiration
209
    */
210
    public $duration_value;
211
212
    /**
213
     * Exoiration unit
214
     */
215
    public $duration_unit;
216
217
    /**
218
     * Status indicates whether the product is on sale '1' or not '0'
219
     *
220
     * @var int
221
     */
222
    public $status=0;
223
224
    /**
225
     * Status indicate whether the product is available for purchase '1' or not '0'
226
     *
227
     * @var int
228
     */
229
    public $status_buy=0;
230
231
    /**
232
     * Status indicates whether the product is a finished product '1' or a raw material '0'
233
     *
234
     * @var int
235
     */
236
    public $finished;
237
238
    /**
239
     * We must manage lot/batch number, sell-by date and so on : '1':yes '0':no
240
     *
241
     * @var int
242
     */
243
    public $status_batch=0;
244
245
    /**
246
     * Customs code
247
     *
248
     * @var
249
     */
250
    public $customcode;
251
252
    /**
253
     * Product URL
254
     *
255
     * @var string
256
     */
257
    public $url;
258
259
    //! Unites de mesure
260
    public $net_measure;
261
    public $net_measure_units;
262
    public $weight;
263
    public $weight_units;
264
    public $length;
265
    public $length_units;
266
    public $surface;
267
    public $surface_units;
268
    public $volume;
269
    public $volume_units;
270
271
    public $accountancy_code_sell;
272
    public $accountancy_code_sell_intra;
273
    public $accountancy_code_sell_export;
274
    public $accountancy_code_buy;
275
276
    /**
277
     * Main Barcode value
278
     *
279
     * @var string
280
     */
281
    public $barcode;
282
283
    /**
284
     * Main Barcode type ID
285
     *
286
     * @var int
287
     */
288
    public $barcode_type;
289
290
    /**
291
     * Main Barcode type code
292
     *
293
     * @var string
294
     */
295
    public $barcode_type_code;
296
297
    /**
298
     * Additional barcodes (Some products have different barcodes according to the country of origin of manufacture)
299
     *
300
     * @var array
301
     */
302
    public $barcodes_extra=array();
303
304
    public $stats_propale=array();
305
    public $stats_commande=array();
306
    public $stats_contrat=array();
307
    public $stats_facture=array();
308
    public $stats_commande_fournisseur=array();
309
310
    public $multilangs=array();
311
312
    //! Size of image
313
    public $imgWidth;
314
    public $imgHeight;
315
316
    public $date_creation;
317
    public $date_modification;
318
319
    //! Id du fournisseur
320
    public $product_fourn_id;
321
322
    //! Product ID already linked to a reference supplier
323
    public $product_id_already_linked;
324
325
    public $nbphoto=0;
326
327
    //! Contains detail of stock of product into each warehouse
328
    public $stock_warehouse=array();
329
330
    public $oldcopy;
331
332
    public $fk_default_warehouse;
333
    /**
334
     * @var int ID
335
     */
336
    public $fk_price_expression;
337
338
    /* To store supplier price found */
339
    public $fourn_pu;
340
    public $fourn_price_base_type;
341
    public $fourn_socid;
342
343
    /**
344
     * @deprecated
345
     * @see        $ref_supplier
346
     */
347
    public $ref_fourn;
348
    public $ref_supplier;
349
350
    /**
351
     * Unit code ('km', 'm', 'l', 'p', ...)
352
     *
353
     * @var string
354
     */
355
    public $fk_unit;
356
357
    /**
358
     * Price is generated using multiprice rules
359
     *
360
     * @var int
361
     */
362
    public $price_autogen = 0;
363
364
365
    public $fields = array(
366
        'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'index'=>1, 'position'=>1, 'comment'=>'Id'),
367
        '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'),
368
        'entity'        =>array('type'=>'integer',      'label'=>'Entity',           'enabled'=>1, 'visible'=>0,  'default'=>1, 'notnull'=>1,  'index'=>1, 'position'=>20),
369
        'note_public'   =>array('type'=>'html',            'label'=>'NotePublic',         'enabled'=>1, 'visible'=>0,  'position'=>61),
370
        'note'          =>array('type'=>'html',            'label'=>'NotePrivate',         'enabled'=>1, 'visible'=>0,  'position'=>62),
371
        'datec'         =>array('type'=>'datetime',     'label'=>'DateCreation',     'enabled'=>1, 'visible'=>-2, 'notnull'=>1,  'position'=>500),
372
        'tms'           =>array('type'=>'timestamp',    'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1,  'position'=>501),
373
        //'date_valid'    =>array('type'=>'datetime',     'label'=>'DateCreation',     'enabled'=>1, 'visible'=>-2, 'position'=>502),
374
        'fk_user_author'=>array('type'=>'integer',      'label'=>'UserAuthor',       'enabled'=>1, 'visible'=>-2, 'notnull'=>1,  'position'=>510, 'foreignkey'=>'llx_user.rowid'),
375
        'fk_user_modif' =>array('type'=>'integer',      'label'=>'UserModif',        'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>511),
376
        //'fk_user_valid' =>array('type'=>'integer',      'label'=>'UserValidation',        'enabled'=>1, 'visible'=>-1, 'position'=>512),
377
        'import_key'    =>array('type'=>'varchar(14)',  'label'=>'ImportId',         'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'index'=>0,  'position'=>1000),
378
        //'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')),
379
        //'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')),
380
    );
381
382
    /**
383
     * Regular product
384
     */
385
    const TYPE_PRODUCT = 0;
386
    /**
387
     * Service
388
     */
389
    const TYPE_SERVICE = 1;
390
    /**
391
     * Advanced feature: assembly kit
392
     */
393
    const TYPE_ASSEMBLYKIT = 2;
394
    /**
395
     * Advanced feature: stock kit
396
     */
397
    const TYPE_STOCKKIT = 3;
398
399
400
    /**
401
     *  Constructor
402
     *
403
     * @param DoliDB $db Database handler
404
     */
405
    public function __construct($db)
406
    {
407
        $this->db = $db;
408
        $this->canvas = '';
409
    }
410
411
    /**
412
     *    Check that ref and label are ok
413
     *
414
     * @return int         >1 if OK, <=0 if KO
415
     */
416
    public function check()
417
    {
418
        $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
419
420
        $err = 0;
421
        if (dol_strlen(trim($this->ref)) == 0) {
422
            $err++;
423
        }
424
425
        if (dol_strlen(trim($this->label)) == 0) {
426
            $err++;
427
        }
428
429
        if ($err > 0) {
430
            return 0;
431
        }
432
        else
433
        {
434
            return 1;
435
        }
436
    }
437
438
    /**
439
     *    Insert product into database
440
     *
441
     * @param  User $user      User making insert
442
     * @param  int  $notrigger Disable triggers
443
     * @return int                         Id of product/service if OK, < 0 if KO
444
     */
445
    public function create($user, $notrigger = 0)
446
    {
447
        global $conf, $langs;
448
449
            $error=0;
450
451
        // Clean parameters
452
        $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
453
        $this->label = trim($this->label);
454
        $this->price_ttc=price2num($this->price_ttc);
1 ignored issue
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...
455
        $this->price=price2num($this->price);
1 ignored issue
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...
456
        $this->price_min_ttc=price2num($this->price_min_ttc);
1 ignored issue
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...
457
        $this->price_min=price2num($this->price_min);
1 ignored issue
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...
458
        if (empty($this->tva_tx)) {
459
            $this->tva_tx = 0;
460
        }
461
        if (empty($this->tva_npr)) {
462
            $this->tva_npr = 0;
463
        }
464
        //Local taxes
465
        if (empty($this->localtax1_tx)) {
466
            $this->localtax1_tx = 0;
467
        }
468
        if (empty($this->localtax2_tx)) {
469
            $this->localtax2_tx = 0;
470
        }
471
        if (empty($this->localtax1_type)) {
472
            $this->localtax1_type = '0';
473
        }
474
        if (empty($this->localtax2_type)) {
475
            $this->localtax2_type = '0';
476
        }
477
        if (empty($this->price)) {
478
            $this->price = 0;
479
        }
480
        if (empty($this->price_min)) {
481
            $this->price_min = 0;
482
        }
483
        // Price by quantity
484
        if (empty($this->price_by_qty)) {
485
            $this->price_by_qty = 0;
486
        }
487
488
        if (empty($this->status)) {
489
            $this->status = 0;
490
        }
491
        if (empty($this->status_buy)) {
492
            $this->status_buy = 0;
493
        }
494
495
        $price_ht=0;
496
        $price_ttc=0;
497
        $price_min_ht=0;
498
        $price_min_ttc=0;
499
500
        //
501
        if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
502
            $price_ttc = price2num($this->price_ttc, 'MU');
503
            $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
504
        }
505
506
        //
507
        if ($this->price_base_type != 'TTC' && $this->price > 0) {
508
            $price_ht = price2num($this->price, 'MU');
509
            $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
510
        }
511
512
        //
513
        if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
514
            $price_min_ttc = price2num($this->price_min_ttc, 'MU');
515
            $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
516
        }
517
518
        //
519
        if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
520
            $price_min_ht = price2num($this->price_min, 'MU');
521
            $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
522
        }
523
524
        $this->accountancy_code_buy = trim($this->accountancy_code_buy);
525
        $this->accountancy_code_sell= trim($this->accountancy_code_sell);
526
        $this->accountancy_code_sell_intra= trim($this->accountancy_code_sell_intra);
527
        $this->accountancy_code_sell_export= trim($this->accountancy_code_sell_export);
528
529
        // Barcode value
530
        $this->barcode=trim($this->barcode);
531
532
        // Check parameters
533
        if (empty($this->label)) {
534
            $this->error='ErrorMandatoryParametersNotProvided';
535
            return -1;
536
        }
537
538
        if (empty($this->ref)) {
539
            // Load object modCodeProduct
540
            $module=(! empty($conf->global->PRODUCT_CODEPRODUCT_ADDON)?$conf->global->PRODUCT_CODEPRODUCT_ADDON:'mod_codeproduct_leopard');
541
            if ($module != 'mod_codeproduct_leopard')    // Do not load module file for leopard
542
            {
543
                if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
544
                    $module = substr($module, 0, dol_strlen($module)-4);
545
                }
546
                dol_include_once('/core/modules/product/'.$module.'.php');
547
                $modCodeProduct = new $module;
548
                if (! empty($modCodeProduct->code_auto)) {
549
                    $this->ref = $modCodeProduct->getNextValue($this, $this->type);
550
                }
551
                unset($modCodeProduct);
552
            }
553
554
            if (empty($this->ref)) {
555
                $this->error='ProductModuleNotSetupForAutoRef';
556
                return -2;
557
            }
558
        }
559
560
        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);
561
562
        $now=dol_now();
563
564
        $this->db->begin();
565
566
        // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
567
        if ($this->barcode == -1) {
568
            $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
569
        }
570
571
        // Check more parameters
572
        // If error, this->errors[] is filled
573
        $result = $this->verify();
574
575
        if ($result >= 0) {
576
            $sql = "SELECT count(*) as nb";
577
            $sql.= " FROM ".MAIN_DB_PREFIX."product";
578
            $sql.= " WHERE entity IN (".getEntity('product').")";
579
            $sql.= " AND ref = '" .$this->db->escape($this->ref)."'";
580
581
            $result = $this->db->query($sql);
582
            if ($result) {
583
                $obj = $this->db->fetch_object($result);
584
                if ($obj->nb == 0) {
585
                    // Produit non deja existant
586
                    $sql = "INSERT INTO ".MAIN_DB_PREFIX."product (";
587
                    $sql.= "datec";
588
                    $sql.= ", entity";
589
                    $sql.= ", ref";
590
                    $sql.= ", ref_ext";
591
                    $sql.= ", price_min";
592
                    $sql.= ", price_min_ttc";
593
                    $sql.= ", label";
594
                    $sql.= ", fk_user_author";
595
                    $sql.= ", fk_product_type";
596
                    $sql.= ", price";
597
                    $sql.= ", price_ttc";
598
                    $sql.= ", price_base_type";
599
                    $sql.= ", tobuy";
600
                    $sql.= ", tosell";
601
                    $sql.= ", accountancy_code_buy";
602
                    $sql.= ", accountancy_code_sell";
603
                    $sql.= ", accountancy_code_sell_intra";
604
                    $sql.= ", accountancy_code_sell_export";
605
                    $sql.= ", canvas";
606
                    $sql.= ", finished";
607
                    $sql.= ", tobatch";
608
                    $sql.= ", fk_unit";
609
                    $sql.= ") VALUES (";
610
                    $sql.= "'".$this->db->idate($now)."'";
611
                    $sql.= ", ".$conf->entity;
612
                    $sql.= ", '".$this->db->escape($this->ref)."'";
613
                    $sql.= ", ".(! empty($this->ref_ext)?"'".$this->db->escape($this->ref_ext)."'":"null");
614
                    $sql.= ", ".price2num($price_min_ht);
615
                    $sql.= ", ".price2num($price_min_ttc);
616
                    $sql.= ", ".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null");
617
                    $sql.= ", ".$user->id;
618
                    $sql.= ", ".$this->type;
619
                    $sql.= ", ".price2num($price_ht);
620
                    $sql.= ", ".price2num($price_ttc);
621
                    $sql.= ", '".$this->db->escape($this->price_base_type)."'";
622
                    $sql.= ", ".$this->status;
623
                    $sql.= ", ".$this->status_buy;
624
                    $sql.= ", '".$this->db->escape($this->accountancy_code_buy)."'";
625
                    $sql.= ", '".$this->db->escape($this->accountancy_code_sell)."'";
626
                    $sql.= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
627
                    $sql.= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
628
                    $sql.= ", '".$this->db->escape($this->canvas)."'";
629
                    $sql.= ", ".((! isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'null' : (int) $this->finished);
630
                    $sql.= ", ".((empty($this->status_batch) || $this->status_batch < 0)? '0':$this->status_batch);
631
                    $sql.= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
632
                    $sql.= ")";
633
634
                    dol_syslog(get_class($this)."::Create", LOG_DEBUG);
635
                    $result = $this->db->query($sql);
636
                    if ($result ) {
637
                        $id = $this->db->last_insert_id(MAIN_DB_PREFIX."product");
638
639
                        if ($id > 0) {
640
                            $this->id = $id;
641
                            $this->price            = $price_ht;
642
                            $this->price_ttc        = $price_ttc;
643
                            $this->price_min        = $price_min_ht;
644
                            $this->price_min_ttc    = $price_min_ttc;
645
646
                            $result = $this->_log_price($user);
647
                            if ($result > 0) {
648
                                if ($this->update($id, $user, true, 'add') <= 0) {
649
                                    $error++;
650
                                }
651
                            }
652
                            else
653
                            {
654
                                $error++;
655
                                $this->error=$this->db->lasterror();
656
                            }
657
                        }
658
                        else
659
                        {
660
                            $error++;
661
                            $this->error='ErrorFailedToGetInsertedId';
662
                        }
663
                    }
664
                    else
665
                    {
666
                        $error++;
667
                        $this->error=$this->db->lasterror();
668
                    }
669
                }
670
                else
671
                {
672
                    // Product already exists with this ref
673
                    $langs->load("products");
674
                    $error++;
675
                    $this->error = "ErrorProductAlreadyExists";
676
                }
677
            }
678
            else
679
            {
680
                $error++;
681
                $this->error=$this->db->lasterror();
682
            }
683
684
            if (! $error && ! $notrigger) {
685
                // Call trigger
686
                $result=$this->call_trigger('PRODUCT_CREATE', $user);
687
                if ($result < 0) { $error++;
688
                }
689
                // End call triggers
690
            }
691
692
            if (! $error) {
693
                $this->db->commit();
694
                return $this->id;
695
            }
696
            else
697
            {
698
                $this->db->rollback();
699
                return -$error;
700
            }
701
        }
702
        else
703
          {
704
            $this->db->rollback();
705
            dol_syslog(get_class($this)."::Create fails verify ".join(',', $this->errors), LOG_WARNING);
706
            return -3;
707
        }
708
    }
709
710
711
    /**
712
     *    Check properties of product are ok (like name, barcode, ...).
713
     *    All properties must be already loaded on object (this->barcode, this->barcode_type_code, ...).
714
     *
715
     * @return int        0 if OK, <0 if KO
716
     */
717
    public function verify()
718
    {
719
        $this->errors=array();
720
721
        $result = 0;
722
        $this->ref = trim($this->ref);
723
724
        if (! $this->ref) {
725
            $this->errors[] = 'ErrorBadRef';
726
            $result = -2;
727
        }
728
729
        $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
730
        if ($rescode) {
731
            if ($rescode == -1) {
732
                $this->errors[] = 'ErrorBadBarCodeSyntax';
733
            }
734
            elseif ($rescode == -2) {
735
                $this->errors[] = 'ErrorBarCodeRequired';
736
            }
737
            elseif ($rescode == -3) {
738
                // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
739
                $this->errors[] = 'ErrorBarCodeAlreadyUsed';
740
            }
741
742
            $result = -3;
743
        }
744
745
        return $result;
746
    }
747
748
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
749
    /**
750
     *  Check barcode
751
     *
752
     * @param  string $valuetotest Value to test
753
     * @param  string $typefortest Type of barcode (ISBN, EAN, ...)
754
     * @return int                        0 if OK
755
     *                                     -1 ErrorBadBarCodeSyntax
756
     *                                     -2 ErrorBarCodeRequired
757
     *                                     -3 ErrorBarCodeAlreadyUsed
758
     */
759
    public function check_barcode($valuetotest, $typefortest)
760
    {
761
        // phpcs:enable
762
        global $conf;
763
        if (! empty($conf->barcode->enabled) && ! empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
764
            $module=strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
765
766
            $dirsociete=array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
767
            foreach ($dirsociete as $dirroot)
768
            {
769
                $res=dol_include_once($dirroot.$module.'.php');
770
                if ($res) { break;
771
                }
772
            }
773
774
            $mod = new $module();
775
776
            dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
777
            $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
778
            return $result;
779
        }
780
        else
781
        {
782
            return 0;
783
        }
784
    }
785
786
    /**
787
     *    Update a record into database.
788
     *  If batch flag is set to on, we create records into llx_product_batch
789
     *
790
     * @param  int     $id          Id of product
791
     * @param  User    $user        Object user making update
792
     * @param  int     $notrigger   Disable triggers
793
     * @param  string  $action      Current action for hookmanager ('add' or 'update')
794
	 * @param  boolean $updatetype  Update product type
795
     * @return int                  1 if OK, -1 if ref already exists, -2 if other error
796
     */
797
    public function update($id, $user, $notrigger = false, $action = 'update', $updatetype = false)
798
    {
799
        global $langs, $conf, $hookmanager;
800
801
        $error=0;
802
803
        // Check parameters
804
        if (! $this->label) { $this->label = 'MISSING LABEL';
805
        }
806
807
        // Clean parameters
808
        $this->ref = dol_string_nospecial(trim($this->ref));
809
        $this->label = trim($this->label);
810
        $this->description = trim($this->description);
811
        $this->note = (isset($this->note) ? trim($this->note) : null);
812
        $this->net_measure = price2num($this->net_measure);
813
        $this->net_measure_units = trim($this->net_measure_units);
814
        $this->weight = price2num($this->weight);
815
        $this->weight_units = trim($this->weight_units);
816
        $this->length = price2num($this->length);
817
        $this->length_units = trim($this->length_units);
818
        $this->width = price2num($this->width);
819
        $this->width_units = trim($this->width_units);
820
        $this->height = price2num($this->height);
821
        $this->height_units = trim($this->height_units);
822
        // set unit not defined
823
        if ($this->length_units) { $this->width_units = $this->length_units;    // Not used yet
824
        }
825
        if ($this->length_units) { $this->height_units = $this->length_units;    // Not used yet
826
        }
827
        // Automated compute surface and volume if not filled
828
        if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
829
            $this->surface = $this->length * $this->width;
830
            $this->surface_units = measuring_units_squared($this->length_units);
831
        }
832
        if (empty($this->volume) && !empty($this->surface_units) && !empty($this->height) && $this->length_units == $this->height_units) {
833
            $this->volume =  $this->surface * $this->height;
834
            $this->volume_units = measuring_units_cubed($this->height_units);
835
        }
836
837
        $this->surface = price2num($this->surface);
838
        $this->surface_units = trim($this->surface_units);
839
        $this->volume = price2num($this->volume);
840
        $this->volume_units = trim($this->volume_units);
841
        if (empty($this->tva_tx)) {
842
            $this->tva_tx = 0;
843
        }
844
        if (empty($this->tva_npr)) {
845
            $this->tva_npr = 0;
846
        }
847
        if (empty($this->localtax1_tx)) {
848
            $this->localtax1_tx = 0;
849
        }
850
        if (empty($this->localtax2_tx)) {
851
            $this->localtax2_tx = 0;
852
        }
853
        if (empty($this->localtax1_type)) {
854
            $this->localtax1_type = '0';
855
        }
856
        if (empty($this->localtax2_type)) {
857
            $this->localtax2_type = '0';
858
        }
859
        if (empty($this->status)) {
860
            $this->status = 0;
861
        }
862
        if (empty($this->status_buy)) {
863
            $this->status_buy = 0;
864
        }
865
866
        if (empty($this->country_id)) {
867
            $this->country_id = 0;
868
        }
869
870
        // Barcode value
871
        $this->barcode=trim($this->barcode);
872
873
        $this->accountancy_code_buy = trim($this->accountancy_code_buy);
874
        $this->accountancy_code_sell= trim($this->accountancy_code_sell);
875
        $this->accountancy_code_sell_intra= trim($this->accountancy_code_sell_intra);
876
        $this->accountancy_code_sell_export= trim($this->accountancy_code_sell_export);
877
878
879
        $this->db->begin();
880
881
        // Check name is required and codes are ok or unique.
882
        // If error, this->errors[] is filled
883
        if ($action != 'add') {
884
            $result = $this->verify();    // We don't check when update called during a create because verify was already done
885
        }
886
887
        if ($result >= 0) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
888
            if (empty($this->oldcopy)) {
889
                $org=new self($this->db);
890
                $org->fetch($this->id);
891
                $this->oldcopy=$org;
892
            }
893
894
            // Test if batch management is activated on existing product
895
            // If yes, we create missing entries into product_batch
896
            if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
897
                //$valueforundefinedlot = 'Undefined';  // In previous version, 39 and lower
898
                $valueforundefinedlot = '000000';
899
900
                dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
901
902
                $this->load_stock();
903
                foreach ($this->stock_warehouse as $idW => $ObjW)   // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
904
                {
905
                    $qty_batch = 0;
906
                    foreach ($ObjW->detail_batch as $detail)    // Each lines of detail in product_batch of the current $ObjW = product_stock
907
                    {
908
                        if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
909
                            // We discard this line, we will create it later
910
                            $sqlclean="DELETE FROM ".MAIN_DB_PREFIX."product_batch WHERE batch in('Undefined', '".$valueforundefinedlot."') AND fk_product_stock = ".$ObjW->id;
911
                            $result = $this->db->query($sqlclean);
912
                            if (! $result) {
913
                                dol_print_error($this->db);
914
                                exit;
915
                            }
916
                            continue;
917
                        }
918
919
                        $qty_batch += $detail->qty;
920
                    }
921
                    // Quantities in batch details are not same as stock quantity,
922
                    // so we add a default batch record to complete and get same qty in parent and child table
923
                    if ($ObjW->real <> $qty_batch) {
924
                        $ObjBatch = new Productbatch($this->db);
925
                        $ObjBatch->batch = $valueforundefinedlot;
926
                        $ObjBatch->qty = ($ObjW->real - $qty_batch);
927
                        $ObjBatch->fk_product_stock = $ObjW->id;
928
929
                        if ($ObjBatch->create($user, 1) < 0) {
930
                            $error++;
931
                            $this->errors=$ObjBatch->errors;
932
                        }
933
                    }
934
                }
935
            }
936
937
            // For automatic creation
938
            if ($this->barcode == -1) { $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
939
            }
940
941
            $sql = "UPDATE ".MAIN_DB_PREFIX."product";
942
            $sql.= " SET label = '" . $this->db->escape($this->label) ."'";
943
            
944
            if ($updatetype && ($this->isProduct() || $this->isService())) {
945
                $sql.= ", fk_product_type = " . $this->type;
946
            }
947
            
948
            $sql.= ", ref = '" . $this->db->escape($this->ref) ."'";
949
            $sql.= ", ref_ext = ".(! empty($this->ref_ext)?"'".$this->db->escape($this->ref_ext)."'":"null");
950
            $sql.= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
951
            $sql.= ", tva_tx = " . $this->tva_tx;
952
            $sql.= ", recuperableonly = " . $this->tva_npr;
953
            $sql.= ", localtax1_tx = " . $this->localtax1_tx;
954
            $sql.= ", localtax2_tx = " . $this->localtax2_tx;
955
            $sql.= ", localtax1_type = " . ($this->localtax1_type!=''?"'".$this->db->escape($this->localtax1_type)."'":"'0'");
956
            $sql.= ", localtax2_type = " . ($this->localtax2_type!=''?"'".$this->db->escape($this->localtax2_type)."'":"'0'");
957
958
            $sql.= ", barcode = ". (empty($this->barcode)?"null":"'".$this->db->escape($this->barcode)."'");
959
            $sql.= ", fk_barcode_type = ". (empty($this->barcode_type)?"null":$this->db->escape($this->barcode_type));
960
961
            $sql.= ", tosell = " . (int) $this->status;
962
            $sql.= ", tobuy = " . (int) $this->status_buy;
963
            $sql.= ", tobatch = " . ((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
964
            $sql.= ", finished = " . ((! isset($this->finished) || $this->finished < 0) ? "null" : (int) $this->finished);
965
            $sql.= ", net_measure = " . ($this->net_measure!='' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
966
            $sql.= ", net_measure_units = " . ($this->net_measure_units!='' ? "'".$this->db->escape($this->net_measure_units)."'": 'null');
967
            $sql.= ", weight = " . ($this->weight!='' ? "'".$this->db->escape($this->weight)."'" : 'null');
968
            $sql.= ", weight_units = " . ($this->weight_units!='' ? "'".$this->db->escape($this->weight_units)."'": 'null');
969
            $sql.= ", length = " . ($this->length!='' ? "'".$this->db->escape($this->length)."'" : 'null');
970
            $sql.= ", length_units = " . ($this->length_units!='' ? "'".$this->db->escape($this->length_units)."'" : 'null');
971
            $sql.= ", width= " . ($this->width!='' ? "'".$this->db->escape($this->width)."'" : 'null');
972
            $sql.= ", width_units = " . ($this->width_units!='' ? "'".$this->db->escape($this->width_units)."'" : 'null');
973
            $sql.= ", height = " . ($this->height!='' ? "'".$this->db->escape($this->height)."'" : 'null');
974
            $sql.= ", height_units = " . ($this->height_units!='' ? "'".$this->db->escape($this->height_units)."'" : 'null');
975
            $sql.= ", surface = " . ($this->surface!='' ? "'".$this->db->escape($this->surface)."'" : 'null');
976
            $sql.= ", surface_units = " . ($this->surface_units!='' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
977
            $sql.= ", volume = " . ($this->volume!='' ? "'".$this->db->escape($this->volume)."'" : 'null');
978
            $sql.= ", volume_units = " . ($this->volume_units!='' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
979
            $sql.= ", fk_default_warehouse = " . ($this->fk_default_warehouse > 0 ? $this->db->escape($this->fk_default_warehouse) : 'null');
980
            $sql.= ", seuil_stock_alerte = " . ((isset($this->seuil_stock_alerte) && $this->seuil_stock_alerte != '') ? "'".$this->db->escape($this->seuil_stock_alerte)."'" : "null");
981
            $sql.= ", description = '" . $this->db->escape($this->description) ."'";
982
            $sql.= ", url = " . ($this->url?"'".$this->db->escape($this->url)."'":'null');
983
            $sql.= ", customcode = '" .        $this->db->escape($this->customcode) ."'";
984
            $sql.= ", fk_country = " . ($this->country_id > 0 ? (int) $this->country_id : 'null');
985
            $sql.= ", note = ".(isset($this->note) ? "'" .$this->db->escape($this->note)."'" : 'null');
986
            $sql.= ", duration = '" . $this->db->escape($this->duration_value . $this->duration_unit) ."'";
987
            $sql.= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy)."'";
988
            $sql.= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell)."'";
989
            $sql.= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra)."'";
990
            $sql.= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export)."'";
991
            $sql.= ", desiredstock = " . ((isset($this->desiredstock) && $this->desiredstock != '') ? (int) $this->desiredstock : "null");
992
            $sql.= ", cost_price = " . ($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
993
            $sql.= ", fk_unit= " . (!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
994
            $sql.= ", price_autogen = " . (!$this->price_autogen ? 0 : 1);
995
            $sql.= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
996
            $sql.= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
997
            // stock field is not here because it is a denormalized value from product_stock.
998
            $sql.= " WHERE rowid = " . $id;
999
1000
            dol_syslog(get_class($this)."::update", LOG_DEBUG);
1001
1002
            $resql=$this->db->query($sql);
1003
            if ($resql) {
1004
                $this->id = $id;
1005
1006
                // Multilangs
1007
                if (! empty($conf->global->MAIN_MULTILANGS)) {
1008
                    if ($this->setMultiLangs($user) < 0) {
1009
                           $this->error=$langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1010
                           return -2;
1011
                    }
1012
                }
1013
1014
                $action='update';
1015
1016
                // Actions on extra fields
1017
                if (! $error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) {
1018
                    $result=$this->insertExtraFields();
1019
                    if ($result < 0) {
1020
                        $error++;
1021
                    }
1022
                }
1023
1024
                if (! $error && ! $notrigger) {
1025
                    // Call trigger
1026
                    $result=$this->call_trigger('PRODUCT_MODIFY', $user);
1027
                    if ($result < 0) {
1028
                        $error++;
1029
                    }
1030
                    // End call triggers
1031
                }
1032
1033
                if (! $error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1034
                    // We remove directory
1035
                    if ($conf->product->dir_output) {
1036
                        $olddir = $conf->product->dir_output . "/" . dol_sanitizeFileName($this->oldcopy->ref);
1037
                        $newdir = $conf->product->dir_output . "/" . dol_sanitizeFileName($this->ref);
1038
                        if (file_exists($olddir)) {
1039
                            //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1040
                            //$res = dol_move($olddir, $newdir);
1041
                            // do not use dol_move with directory
1042
                            $res = @rename($olddir, $newdir);
1043
                            if (! $res) {
1044
                                $langs->load("errors");
1045
                                $this->error=$langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1046
                                $error++;
1047
                            }
1048
                        }
1049
                    }
1050
                }
1051
1052
                if (! $error) {
1053
                    if ($conf->variants->enabled) {
1054
                        include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1055
1056
                        $comb = new ProductCombination($this->db);
1057
1058
                        foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1059
                                 $currcomb->updateProperties($this);
1060
                        }
1061
                    }
1062
1063
                    $this->db->commit();
1064
                    return 1;
1065
                }
1066
                else
1067
                {
1068
                    $this->db->rollback();
1069
                    return -$error;
1070
                }
1071
            }
1072
            else
1073
            {
1074
                if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1075
                    $langs->load("errors");
1076
                    if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1077
                        $this->error=$langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1078
                    } else {
1079
                        $this->error=$langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1080
                    }
1081
                    $this->errors[]=$this->error;
1082
                    $this->db->rollback();
1083
                    return -1;
1084
                }
1085
                else
1086
                {
1087
                    $this->error=$langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1088
                    $this->errors[]=$this->error;
1089
                    $this->db->rollback();
1090
                    return -2;
1091
                }
1092
            }
1093
        }
1094
        else
1095
          {
1096
            $this->db->rollback();
1097
            dol_syslog(get_class($this)."::Update fails verify ".join(',', $this->errors), LOG_WARNING);
1098
            return -3;
1099
        }
1100
    }
1101
1102
    /**
1103
     *  Delete a product from database (if not used)
1104
     *
1105
     * @param  User $user      Product id (usage of this is deprecated, delete should be called without parameters on a fetched object)
1106
     * @param  int  $notrigger Do not execute trigger
1107
     * @return int                    < 0 if KO, 0 = Not possible, > 0 if OK
1108
     */
1109
    public function delete(User $user, $notrigger = 0)
1110
    {
1111
        // Deprecation warning
1112
        if ($id > 0) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id seems to be never defined.
Loading history...
1113
            dol_syslog(__METHOD__ . " with parameter is deprecated", LOG_WARNING);
1114
        }
1115
1116
        global $conf, $langs;
1117
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1118
1119
        $error=0;
1120
1121
        // Clean parameters
1122
        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...
1123
            $id=$this->id;
1124
        } else {
1125
            $this->fetch($id);
1126
        }
1127
1128
        // Check parameters
1129
        if (empty($id)) {
1130
            $this->error = "Object must be fetched before calling delete";
1131
            return -1;
1132
        }
1133
        if (($this->type == Product::TYPE_PRODUCT && empty($user->rights->produit->supprimer)) || ($this->type == Product::TYPE_SERVICE && empty($user->rights->service->supprimer))) {
1134
            $this->error = "ErrorForbidden";
1135
            return 0;
1136
        }
1137
1138
        $objectisused = $this->isObjectUsed($id);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id does not seem to be defined for all execution paths leading up to this point.
Loading history...
1139
        if (empty($objectisused)) {
1140
            $this->db->begin();
1141
1142
            if (! $error && empty($notrigger)) {
1143
                // Call trigger
1144
                $result=$this->call_trigger('PRODUCT_DELETE', $user);
1145
                if ($result < 0) { $error++;
1146
                }
1147
                // End call triggers
1148
            }
1149
1150
            // Delete from product_batch on product delete
1151
            if (! $error) {
1152
                $sql = "DELETE FROM ".MAIN_DB_PREFIX.'product_batch';
1153
                $sql.= " WHERE fk_product_stock IN (";
1154
                $sql.= "SELECT rowid FROM ".MAIN_DB_PREFIX.'product_stock';
1155
                $sql.= " WHERE fk_product = ".$id.")";
1156
1157
                $result = $this->db->query($sql);
1158
                if (! $result) {
1159
                    $error++;
1160
                    $this->errors[] = $this->db->lasterror();
1161
                }
1162
            }
1163
1164
            // Delete all child tables
1165
            if (! $error) {
1166
                $elements = array('product_fournisseur_price','product_price','product_lang','categorie_product','product_stock','product_customer_price','product_lot');  // product_batch is done before
1167
                foreach($elements as $table)
1168
                {
1169
                    if (! $error) {
1170
                        $sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
1171
                        $sql.= " WHERE fk_product = ".$id;
1172
1173
                        $result = $this->db->query($sql);
1174
                        if (! $result) {
1175
                            $error++;
1176
                            $this->errors[] = $this->db->lasterror();
1177
                        }
1178
                    }
1179
                }
1180
            }
1181
1182
            if (!$error) {
1183
                include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1184
                include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1185
1186
                //If it is a parent product, then we remove the association with child products
1187
                $prodcomb = new ProductCombination($this->db);
1188
1189
                if ($prodcomb->deleteByFkProductParent($user, $id) < 0) {
1190
                    $error++;
1191
                    $this->errors[] = 'Error deleting combinations';
1192
                }
1193
1194
                //We also check if it is a child product
1195
                if (!$error && ($prodcomb->fetchByFkProductChild($id) > 0) && ($prodcomb->delete($user) < 0)) {
1196
                    $error++;
1197
                    $this->errors[] = 'Error deleting child combination';
1198
                }
1199
            }
1200
1201
            // Delete from product_association
1202
            if (!$error) {
1203
                $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_association";
1204
                $sql.= " WHERE fk_product_pere = ".$id." OR fk_product_fils = ".$id;
1205
1206
                $result = $this->db->query($sql);
1207
                if (! $result) {
1208
                    $error++;
1209
                    $this->errors[] = $this->db->lasterror();
1210
                }
1211
            }
1212
1213
            // Delete product
1214
            if (! $error) {
1215
                $sqlz = "DELETE FROM ".MAIN_DB_PREFIX."product";
1216
                $sqlz.= " WHERE rowid = ".$id;
1217
1218
                $resultz = $this->db->query($sqlz);
1219
                if (! $resultz ) {
1220
                    $error++;
1221
                    $this->errors[] = $this->db->lasterror();
1222
                }
1223
            }
1224
1225
            if (! $error) {
1226
                // We remove directory
1227
                $ref = dol_sanitizeFileName($this->ref);
1228
                if ($conf->product->dir_output) {
1229
                    $dir = $conf->product->dir_output . "/" . $ref;
1230
                    if (file_exists($dir)) {
1231
                        $res=@dol_delete_dir_recursive($dir);
1232
                        if (! $res) {
1233
                            $this->errors[] = 'ErrorFailToDeleteDir';
1234
                            $error++;
1235
                        }
1236
                    }
1237
                }
1238
            }
1239
1240
            // Remove extrafields
1241
            if ((! $error) && (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED))) // For avoid conflicts if trigger used
1242
            {
1243
                $result=$this->deleteExtraFields();
1244
                if ($result < 0) {
1245
                    $error++;
1246
                    dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1247
                }
1248
            }
1249
1250
            if (! $error) {
1251
                $this->db->commit();
1252
                return 1;
1253
            }
1254
            else
1255
            {
1256
                foreach($this->errors as $errmsg)
1257
                {
1258
                    dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1259
                    $this->error.=($this->error?', '.$errmsg:$errmsg);
1260
                }
1261
                $this->db->rollback();
1262
                return -$error;
1263
            }
1264
        }
1265
        else
1266
        {
1267
            $this->error = "ErrorRecordIsUsedCantDelete";
1268
            return 0;
1269
        }
1270
    }
1271
1272
    /**
1273
     *    Update or add a translation for a product
1274
     *
1275
     * @param  User $user Object user making update
1276
     * @return int        <0 if KO, >0 if OK
1277
     */
1278
    public function setMultiLangs($user)
1279
    {
1280
        global $conf, $langs;
1281
1282
        $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1283
        $current_lang = $langs->getDefaultLang();
1284
1285
        foreach ($langs_available as $key => $value)
1286
        {
1287
            if ($key == $current_lang) {
1288
                $sql = "SELECT rowid";
1289
                $sql.= " FROM ".MAIN_DB_PREFIX."product_lang";
1290
                $sql.= " WHERE fk_product=".$this->id;
1291
                $sql.= " AND lang='".$key."'";
1292
1293
                $result = $this->db->query($sql);
1294
1295
                if ($this->db->num_rows($result)) // if there is already a description line for this language
1296
                {
1297
                    $sql2 = "UPDATE ".MAIN_DB_PREFIX."product_lang";
1298
                    $sql2.= " SET ";
1299
                    $sql2.= " label='".$this->db->escape($this->label)."',";
1300
                    $sql2.= " description='".$this->db->escape($this->description)."'";
1301
                    if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.= ", note='".$this->db->escape($this->other)."'";
1302
                    }
1303
                    $sql2.= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($key)."'";
1304
                }
1305
                else
1306
                {
1307
                    $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."product_lang (fk_product, lang, label, description";
1308
                    if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.=", note";
1309
                    }
1310
                    $sql2.= ")";
1311
                    $sql2.= " VALUES(".$this->id.",'".$this->db->escape($key)."','". $this->db->escape($this->label)."',";
1312
                    $sql2.= " '".$this->db->escape($this->description)."'";
1313
                    if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.= ", '".$this->db->escape($this->other)."'";
1314
                    }
1315
                    $sql2.= ")";
1316
                }
1317
                dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1318
                if (! $this->db->query($sql2)) {
1319
                    $this->error=$this->db->lasterror();
1320
                    return -1;
1321
                }
1322
            }
1323
            elseif (isset($this->multilangs[$key])) {
1324
                $sql = "SELECT rowid";
1325
                $sql.= " FROM ".MAIN_DB_PREFIX."product_lang";
1326
                $sql.= " WHERE fk_product=".$this->id;
1327
                $sql.= " AND lang='".$key."'";
1328
1329
                $result = $this->db->query($sql);
1330
1331
                if ($this->db->num_rows($result)) // if there is already a description line for this language
1332
                {
1333
                    $sql2 = "UPDATE ".MAIN_DB_PREFIX."product_lang";
1334
                    $sql2.= " SET ";
1335
                    $sql2.= " label='".$this->db->escape($this->multilangs["$key"]["label"])."',";
1336
                    $sql2.= " description='".$this->db->escape($this->multilangs["$key"]["description"])."'";
1337
                    if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.= ", note='".$this->db->escape($this->multilangs["$key"]["other"])."'";
1338
                    }
1339
                    $sql2.= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($key)."'";
1340
                }
1341
                else
1342
                {
1343
                    $sql2 = "INSERT INTO ".MAIN_DB_PREFIX."product_lang (fk_product, lang, label, description";
1344
                    if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.=", note";
1345
                    }
1346
                    $sql2.= ")";
1347
                    $sql2.= " VALUES(".$this->id.",'".$this->db->escape($key)."','". $this->db->escape($this->multilangs["$key"]["label"])."',";
1348
                    $sql2.= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1349
                    if (! empty($conf->global->PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION)) { $sql2.= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1350
                    }
1351
                    $sql2.= ")";
1352
                }
1353
1354
                // We do not save if main fields are empty
1355
                if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
1356
                    if (! $this->db->query($sql2)) {
1357
                        $this->error=$this->db->lasterror();
1358
                        return -1;
1359
                    }
1360
                }
1361
            }
1362
            else
1363
            {
1364
                // language is not current language and we didn't provide a multilang description for this language
1365
            }
1366
        }
1367
1368
        // Call trigger
1369
        $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
1370
        if ($result < 0) {
1371
            $this->error = $this->db->lasterror();
1372
            return -1;
1373
        }
1374
        // End call triggers
1375
1376
        return 1;
1377
    }
1378
1379
    /**
1380
     *    Delete a language for this product
1381
     *
1382
     * @param string $langtodelete Language code to delete
1383
     * @param User   $user         Object user making delete
1384
     *
1385
     * @return int                            <0 if KO, >0 if OK
1386
     */
1387
    public function delMultiLangs($langtodelete, $user)
1388
    {
1389
        $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_lang";
1390
        $sql.= " WHERE fk_product=".$this->id." AND lang='".$this->db->escape($langtodelete)."'";
1391
1392
        dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
1393
        $result = $this->db->query($sql);
1394
        if ($result) {
1395
            // Call trigger
1396
            $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
1397
            if ($result < 0) {
1398
                $this->error = $this->db->lasterror();
1399
                dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1400
                return -1;
1401
            }
1402
            // End call triggers
1403
            return 1;
1404
        }
1405
        else
1406
        {
1407
            $this->error=$this->db->lasterror();
1408
            dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1409
            return -1;
1410
        }
1411
    }
1412
1413
    /*
1414
    * Sets an accountancy code for a product.
1415
    * Also calls PRODUCT_MODIFY trigger when modified
1416
    *
1417
    * @param string $type It can be 'buy', 'sell', 'sell_intra' or 'sell_export'
1418
    * @param string $value Accountancy code
1419
    * @return int <0 KO >0 OK
1420
    */
1421
    public function setAccountancyCode($type, $value)
1422
    {
1423
        global $user, $langs, $conf;
1424
1425
        $this->db->begin();
1426
1427
        if ($type == 'buy') {
1428
            $field = 'accountancy_code_buy';
1429
        } elseif ($type == 'sell') {
1430
            $field = 'accountancy_code_sell';
1431
        } elseif ($type == 'sell_intra') {
1432
            $field = 'accountancy_code_sell_intra';
1433
        } elseif ($type == 'sell_export') {
1434
            $field = 'accountancy_code_sell_export';
1435
        } else {
1436
            return -1;
1437
        }
1438
1439
        $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET ";
1440
        $sql.= "$field = '".$this->db->escape($value)."'";
1441
        $sql.= " WHERE rowid = ".$this->id;
1442
1443
        dol_syslog(get_class($this)."::".__FUNCTION__." sql=".$sql, LOG_DEBUG);
1444
        $resql = $this->db->query($sql);
1445
1446
        if ($resql) {
1447
            // Call triggers
1448
            include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
1449
            $interface=new Interfaces($this->db);
1450
            $result=$interface->run_triggers('PRODUCT_MODIFY', $this, $user, $langs, $conf);
1451
            if ($result < 0) {
1452
                $this->errors=$interface->errors;
1453
                $this->db->rollback();
1454
                return -1;
1455
            }
1456
            // End call triggers
1457
1458
            $this->$field = $value;
1459
1460
            $this->db->commit();
1461
            return 1;
1462
        }
1463
        else
1464
        {
1465
            $this->error=$this->db->lasterror();
1466
            $this->db->rollback();
1467
            return -1;
1468
        }
1469
    }
1470
1471
    /**
1472
     *    Load array this->multilangs
1473
     *
1474
     * @return int        <0 if KO, >0 if OK
1475
     */
1476
    public function getMultiLangs()
1477
    {
1478
        global $langs;
1479
1480
        $current_lang = $langs->getDefaultLang();
1481
1482
        $sql = "SELECT lang, label, description, note as other";
1483
        $sql.= " FROM ".MAIN_DB_PREFIX."product_lang";
1484
        $sql.= " WHERE fk_product=".$this->id;
1485
1486
        $result = $this->db->query($sql);
1487
        if ($result) {
1488
            while ($obj = $this->db->fetch_object($result))
1489
            {
1490
                //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1491
                if ($obj->lang == $current_lang)  // si on a les traduct. dans la langue courante on les charge en infos principales.
1492
                {
1493
                    $this->label        = $obj->label;
1494
                    $this->description    = $obj->description;
1495
                    $this->other        = $obj->other;
1496
                }
1497
                $this->multilangs["$obj->lang"]["label"]        = $obj->label;
1498
                $this->multilangs["$obj->lang"]["description"]    = $obj->description;
1499
                $this->multilangs["$obj->lang"]["other"]        = $obj->other;
1500
            }
1501
            return 1;
1502
        }
1503
        else
1504
        {
1505
            $this->error="Error: ".$this->db->lasterror()." - ".$sql;
1506
            return -1;
1507
        }
1508
    }
1509
1510
1511
1512
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1513
    /**
1514
     *  Insert a track that we changed a customer price
1515
     *
1516
     * @param  User $user  User making change
1517
     * @param  int  $level price level to change
1518
     * @return int                    <0 if KO, >0 if OK
1519
     */
1520
    private function _log_price($user, $level = 0)
1521
    {
1522
        // phpcs:enable
1523
        global $conf;
1524
1525
        $now=dol_now();
1526
1527
        // Clean parameters
1528
        if (empty($this->price_by_qty)) {
1529
            $this->price_by_qty=0;
1530
        }
1531
1532
        // Add new price
1533
        $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,";
1534
        $sql.= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1535
        $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.",";
1536
        $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');
1537
        $sql.= ")";
1538
1539
        dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
1540
        $resql=$this->db->query($sql);
1541
        if(! $resql) {
1542
            $this->error=$this->db->lasterror();
1543
            dol_print_error($this->db);
1544
            return -1;
1545
        }
1546
        else
1547
        {
1548
            return 1;
1549
        }
1550
    }
1551
1552
1553
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1554
    /**
1555
     *  Delete a price line
1556
     *
1557
     * @param  User $user  Object user
1558
     * @param  int  $rowid Line id to delete
1559
     * @return int                <0 if KO, >0 if OK
1560
     */
1561
    public function log_price_delete($user, $rowid)
1562
    {
1563
        // phpcs:enable
1564
        $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_price_by_qty";
1565
        $sql.= " WHERE fk_product_price=".$rowid;
1566
        $resql=$this->db->query($sql);
1567
1568
        $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_price";
1569
        $sql.= " WHERE rowid=".$rowid;
1570
        $resql=$this->db->query($sql);
1571
        if ($resql) {
1572
            return 1;
1573
        }
1574
        else
1575
        {
1576
            $this->error=$this->db->lasterror();
1577
            return -1;
1578
        }
1579
    }
1580
1581
1582
    /**
1583
     * Return price of sell of a product for a seller/buyer/product.
1584
     *
1585
     * @param	Societe		$thirdparty_seller		Seller
1586
     * @param	Societe		$thirdparty_buyer		Buyer
1587
     * @param	int			$pqp					Id of product per price if a selection was done of such a price
1588
     * @return	array								Array of price information
1589
     * @see get_buyprice(), find_min_price_product_fournisseur()
1590
     */
1591
    public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
1592
    {
1593
    	global $conf, $db;
1594
1595
    			// Update if prices fields are defined
1596
				$tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
1597
				$tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
1598
				if (empty($tva_tx)) $tva_npr=0;
1599
1600
				$pu_ht = $this->price;
1601
				$pu_ttc = $this->price_ttc;
1602
				$price_min = $this->price_min;
1603
				$price_base_type = $this->price_base_type;
1604
1605
				// If price per segment
1606
		if (! empty($conf->global->PRODUIT_MULTIPRICES) && ! empty($thirdparty_buyer->price_level))
1607
				{
1608
			$pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
1609
			$pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
1610
			$price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
1611
			$price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
1612
			if (! empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL))  // using this option is a bug. kept for backward compatibility
1613
			{
1614
				if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) $tva_tx=$this->multiprices_tva_tx[$thirdparty_buyer->price_level];
1615
				if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) $tva_npr=$this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
1616
				if (empty($tva_tx)) $tva_npr=0;
1617
			}
1618
		}
1619
				// If price per customer
1620
		elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES))
1621
				{
1622
			require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1623
1624
			$prodcustprice = new Productcustomerprice($db);
1625
1626
			$filter = array('t.fk_product' => $this->id,'t.fk_soc' => $thirdparty_buyer->id);
1627
1628
			$result = $prodcustprice->fetch_all('', '', 0, 0, $filter);
1629
			if ($result) {
1630
				if (count($prodcustprice->lines) > 0) {
1631
					$pu_ht = price($prodcustprice->lines[0]->price);
1632
					$pu_ttc = price($prodcustprice->lines[0]->price_ttc);
1633
					$price_base_type = $prodcustprice->lines[0]->price_base_type;
1634
					$tva_tx = $prodcustprice->lines[0]->tva_tx;
1635
					if ($prodcustprice->lines[0]->default_vat_code && ! preg_match('/\(.*\)/', $tva_tx)) $tva_tx.= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
1636
					$tva_npr = $prodcustprice->lines[0]->recuperableonly;
1637
					if (empty($tva_tx)) $tva_npr=0;
1638
				}
1639
			}
1640
		}
1641
				// If price per quantity
1642
		elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY))
1643
				{
1644
			if ($this->prices_by_qty[0])	// yes, this product has some prices per quantity
1645
			{
1646
				// Search price into product_price_by_qty from $this->id
1647
				foreach($this->prices_by_qty_list[0] as $priceforthequantityarray)
1648
				{
1649
					if ($priceforthequantityarray['rowid'] != $pqp) continue;
1650
					// We found the price
1651
					if ($priceforthequantityarray['price_base_type'] == 'HT')
1652
					{
1653
						$pu_ht = $priceforthequantityarray['unitprice'];
1654
					}
1655
					else
1656
					{
1657
						$pu_ttc = $priceforthequantityarray['unitprice'];
1658
					}
1659
					break;
1660
				}
1661
			}
1662
		}
1663
				// If price per quantity and customer
1664
		elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES))
1665
				{
1666
			if ($this->prices_by_qty[$thirdparty_buyer->price_level]) // yes, this product has some prices per quantity
1667
			{
1668
				// Search price into product_price_by_qty from $this->id
1669
				foreach($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray)
1670
				{
1671
					if ($priceforthequantityarray['rowid'] != $pqp) continue;
1672
					// We found the price
1673
					if ($priceforthequantityarray['price_base_type'] == 'HT')
1674
					{
1675
						$pu_ht = $priceforthequantityarray['unitprice'];
1676
					}
1677
					else
1678
					{
1679
						$pu_ttc = $priceforthequantityarray['unitprice'];
1680
					}
1681
					break;
1682
				}
1683
			}
1684
		}
1685
1686
    	return array('pu_ht'=>$pu_ht, 'pu_ttc'=>$pu_ttc, 'price_min'=>$price_min, 'price_base_type'=>$price_base_type, 'tva_tx'=>$tva_tx, 'tva_npr'=>$tva_npr);
1687
    }
1688
1689
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1690
    /**
1691
     * Read price used by a provider.
1692
     * We enter as input couple prodfournprice/qty or triplet qty/product_id/fourn_ref.
1693
     * This also set some properties on product like ->buyprice, ->fourn_pu, ...
1694
     *
1695
     * @param  int    $prodfournprice Id du tarif = rowid table product_fournisseur_price
1696
     * @param  double $qty            Quantity asked or -1 to get first entry found
1697
     * @param  int    $product_id     Filter on a particular product id
1698
     * @param  string $fourn_ref      Filter on a supplier price ref. 'none' to exclude ref in search.
1699
     * @param  int    $fk_soc         If of supplier
1700
     * @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...)
1701
     * @see getSellPrice(), find_min_price_product_fournisseur()
1702
     */
1703
    public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
1704
    {
1705
        // phpcs:enable
1706
        global $conf;
1707
        $result = 0;
1708
1709
        // 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)
1710
        $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent,";
1711
        $sql.= " pfp.fk_product, pfp.ref_fourn, pfp.desc_fourn, pfp.fk_soc, pfp.tva_tx, pfp.fk_supplier_price_expression";
1712
        $sql.= " ,pfp.default_vat_code";
1713
        $sql.= " ,pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code";
1714
        $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
1715
        $sql.= " WHERE pfp.rowid = ".$prodfournprice;
1716
        if ($qty > 0) { $sql.= " AND pfp.quantity <= ".$qty;
1717
        }
1718
        $sql.= " ORDER BY pfp.quantity DESC";
1719
1720
        dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
1721
        $resql = $this->db->query($sql);
1722
        if ($resql) {
1723
            $obj = $this->db->fetch_object($resql);
1724
            if ($obj && $obj->quantity > 0)        // If we found a supplier prices from the id of supplier price
1725
            {
1726
                if (!empty($conf->dynamicprices->enabled) && !empty($obj->fk_supplier_price_expression)) {
1727
                    include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
1728
                    $prod_supplier = new ProductFournisseur($this->db);
1729
                    $prod_supplier->product_fourn_price_id = $obj->rowid;
1730
                    $prod_supplier->id = $obj->fk_product;
1731
                    $prod_supplier->fourn_qty = $obj->quantity;
1732
                    $prod_supplier->fourn_tva_tx = $obj->tva_tx;
1733
                    $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
1734
                    $priceparser = new PriceParser($this->db);
1735
                    $price_result = $priceparser->parseProductSupplier($prod_supplier);
1736
                    if ($price_result >= 0) {
1737
                        $obj->price = $price_result;
1738
                    }
1739
                }
1740
                $this->product_fourn_price_id = $obj->rowid;
1741
                $this->buyprice = $obj->price;                      // deprecated
1742
                $this->fourn_pu = $obj->price / $obj->quantity;     // Unit price of product of supplier
1743
                $this->fourn_price_base_type = 'HT';                // Price base type
1744
                $this->fourn_socid = $obj->fk_soc;                  // Company that offer this price
1745
                $this->ref_fourn = $obj->ref_fourn;                 // deprecated
1746
                $this->ref_supplier = $obj->ref_fourn;              // Ref supplier
1747
                $this->desc_supplier = $obj->desc_fourn;            // desc supplier
1748
                $this->remise_percent = $obj->remise_percent;       // remise percent if present and not typed
1749
                $this->vatrate_supplier = $obj->tva_tx;             // Vat ref supplier
1750
                $this->default_vat_code = $obj->default_vat_code;   // Vat code supplier
1751
                $this->fourn_multicurrency_price       = $obj->multicurrency_price;
1752
                $this->fourn_multicurrency_unitprice   = $obj->multicurrency_unitprice;
1753
                $this->fourn_multicurrency_tx          = $obj->multicurrency_tx;
1754
                $this->fourn_multicurrency_id          = $obj->fk_multicurrency;
1755
                $this->fourn_multicurrency_code        = $obj->multicurrency_code;
1756
                $result=$obj->fk_product;
1757
                return $result;
1758
            }
1759
            else // If not found
1760
            {
1761
                // 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.
1762
                $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.fk_soc,";
1763
                $sql.= " pfp.fk_product, pfp.ref_fourn as ref_supplier, pfp.desc_fourn as desc_supplier, pfp.tva_tx, pfp.fk_supplier_price_expression";
1764
                $sql.= " ,pfp.default_vat_code";
1765
                $sql.= " ,pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code";
1766
                $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
1767
                $sql.= " WHERE pfp.fk_product = ".$product_id;
1768
                if ($fourn_ref != 'none') { $sql.= " AND pfp.ref_fourn = '".$fourn_ref."'";
1769
                }
1770
                if ($fk_soc > 0) { $sql.= " AND pfp.fk_soc = ".$fk_soc;
1771
                }
1772
                if ($qty > 0) { $sql.= " AND pfp.quantity <= ".$qty;
1773
                }
1774
                $sql.= " ORDER BY pfp.quantity DESC";
1775
                $sql.= " LIMIT 1";
1776
1777
                dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
1778
                $resql = $this->db->query($sql);
1779
                if ($resql) {
1780
                    $obj = $this->db->fetch_object($resql);
1781
                    if ($obj && $obj->quantity > 0)        // If found
1782
                    {
1783
                        if (!empty($conf->dynamicprices->enabled) && !empty($obj->fk_supplier_price_expression)) {
1784
                            include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
1785
                            $prod_supplier = new ProductFournisseur($this->db);
1786
                            $prod_supplier->product_fourn_price_id = $obj->rowid;
1787
                            $prod_supplier->id = $obj->fk_product;
1788
                            $prod_supplier->fourn_qty = $obj->quantity;
1789
                            $prod_supplier->fourn_tva_tx = $obj->tva_tx;
1790
                            $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
1791
                            $priceparser = new PriceParser($this->db);
1792
                            $price_result = $priceparser->parseProductSupplier($prod_supplier);
1793
                            if ($result >= 0) {
1794
                                $obj->price = $price_result;
1795
                            }
1796
                        }
1797
                        $this->product_fourn_price_id = $obj->rowid;
1798
                        $this->buyprice = $obj->price;                      // deprecated
1799
                        $this->fourn_qty = $obj->quantity;                    // min quantity for price for a virtual supplier
1800
                        $this->fourn_pu = $obj->price / $obj->quantity;     // Unit price of product for a virtual supplier
1801
                        $this->fourn_price_base_type = 'HT';                // Price base type for a virtual supplier
1802
                        $this->fourn_socid = $obj->fk_soc;                  // Company that offer this price
1803
                        $this->ref_fourn = $obj->ref_supplier;              // deprecated
1804
                        $this->ref_supplier = $obj->ref_supplier;           // Ref supplier
1805
                        $this->desc_supplier = $obj->desc_supplier;         // desc supplier
1806
                        $this->remise_percent = $obj->remise_percent;       // remise percent if present and not typed
1807
                        $this->vatrate_supplier = $obj->tva_tx;             // Vat ref supplier
1808
                        $this->default_vat_code = $obj->default_vat_code;   // Vat code supplier
1809
                        $this->fourn_multicurrency_price       = $obj->multicurrency_price;
1810
                        $this->fourn_multicurrency_unitprice   = $obj->multicurrency_unitprice;
1811
                        $this->fourn_multicurrency_tx          = $obj->multicurrency_tx;
1812
                        $this->fourn_multicurrency_id          = $obj->fk_multicurrency;
1813
                        $this->fourn_multicurrency_code        = $obj->multicurrency_code;
1814
                        $result=$obj->fk_product;
1815
                        return $result;
1816
                    }
1817
                    else
1818
                    {
1819
                        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é.
1820
                    }
1821
                }
1822
                else
1823
                {
1824
                    $this->error=$this->db->lasterror();
1825
                    return -3;
1826
                }
1827
            }
1828
        }
1829
        else
1830
        {
1831
            $this->error=$this->db->lasterror();
1832
            return -2;
1833
        }
1834
    }
1835
1836
1837
    /**
1838
     *    Modify customer price of a product/Service
1839
     *
1840
     * @param  double $newprice          New price
1841
     * @param  string $newpricebase      HT or TTC
1842
     * @param  User   $user              Object user that make change
1843
     * @param  double $newvat            New VAT Rate (For example 8.5. Should not be a string)
1844
     * @param  double $newminprice       New price min
1845
     * @param  int    $level             0=standard, >0 = level if multilevel prices
1846
     * @param  int    $newnpr            0=Standard vat rate, 1=Special vat rate for French NPR VAT
1847
     * @param  int    $newpbq            1 if it has price by quantity
1848
     * @param  int    $ignore_autogen    Used to avoid infinite loops
1849
     * @param  array  $localtaxes_array  Array with localtaxes info array('0'=>type1,'1'=>rate1,'2'=>type2,'3'=>rate2) (loaded by getLocalTaxesFromRate(vatrate, 0, ...) function).
1850
     * @param  string $newdefaultvatcode Default vat code
1851
     * @return int                            <0 if KO, >0 if OK
1852
     */
1853
    public function updatePrice($newprice, $newpricebase, $user, $newvat = '', $newminprice = 0, $level = 0, $newnpr = 0, $newpbq = 0, $ignore_autogen = 0, $localtaxes_array = array(), $newdefaultvatcode = '')
1854
    {
1855
        global $conf,$langs;
1856
1857
        $id=$this->id;
1858
1859
        dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
1860
1861
        // Clean parameters
1862
        if (empty($this->tva_tx)) {
1863
            $this->tva_tx=0;
1864
        }
1865
        if (empty($newnpr)) {
1866
            $newnpr=0;
1867
        }
1868
1869
        // Check parameters
1870
        if ($newvat == '') {
1871
            $newvat=$this->tva_tx;
1872
        }
1873
1874
        // If multiprices are enabled, then we check if the current product is subject to price autogeneration
1875
        // Price will be modified ONLY when the first one is the one that is being modified
1876
        if ((!empty($conf->global->PRODUIT_MULTIPRICES) || ! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES)) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
1877
            return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
1878
        }
1879
1880
        if (! empty($newminprice) && ($newminprice > $newprice)) {
1881
            $this->error='ErrorPriceCantBeLowerThanMinPrice';
1882
            return -1;
1883
        }
1884
1885
        if ($newprice !== '' || $newprice === 0) {
1 ignored issue
show
introduced by
The condition $newprice !== '' is always true.
Loading history...
1886
            if ($newpricebase == 'TTC') {
1887
                $price_ttc = price2num($newprice, 'MU');
1888
                $price = price2num($newprice) / (1 + ($newvat / 100));
1889
                $price = price2num($price, 'MU');
1890
1891
                if ($newminprice != '' || $newminprice == 0) {
1892
                    $price_min_ttc = price2num($newminprice, 'MU');
1893
                    $price_min = price2num($newminprice) / (1 + ($newvat / 100));
1894
                    $price_min = price2num($price_min, 'MU');
1895
                }
1896
                else
1897
                {
1898
                    $price_min=0;
1899
                    $price_min_ttc=0;
1900
                }
1901
            }
1902
            else
1903
            {
1904
                $price = price2num($newprice, 'MU');
1905
                $price_ttc = ( $newnpr != 1 ) ? price2num($newprice) * (1 + ($newvat / 100)) : $price;
1906
                $price_ttc = price2num($price_ttc, 'MU');
1907
1908
                if ($newminprice !== '' || $newminprice === 0) {
1909
                    $price_min = price2num($newminprice, 'MU');
1910
                    $price_min_ttc = price2num($newminprice) * (1 + ($newvat / 100));
1911
                    $price_min_ttc = price2num($price_min_ttc, 'MU');
1912
                    //print 'X'.$newminprice.'-'.$price_min;
1913
                }
1914
                else
1915
                {
1916
                    $price_min=0;
1917
                    $price_min_ttc=0;
1918
                }
1919
            }
1920
            //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
1921
1922
            if (count($localtaxes_array) > 0) {
1923
                $localtaxtype1=$localtaxes_array['0'];
1924
                $localtax1=$localtaxes_array['1'];
1925
                $localtaxtype2=$localtaxes_array['2'];
1926
                $localtax2=$localtaxes_array['3'];
1927
            }
1928
            else     // old method. deprecated because ot can't retreive type
1929
            {
1930
                $localtaxtype1='0';
1931
                $localtax1=get_localtax($newvat, 1);
1932
                $localtaxtype2='0';
1933
                $localtax2=get_localtax($newvat, 2);
1934
            }
1935
            if (empty($localtax1)) {
1936
                $localtax1=0;    // If = '' then = 0
1937
            }
1938
            if (empty($localtax2)) {
1939
                $localtax2=0;    // If = '' then = 0
1940
            }
1941
1942
            $this->db->begin();
1943
1944
            // Ne pas mettre de quote sur les numeriques decimaux.
1945
            // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
1946
            $sql = "UPDATE ".MAIN_DB_PREFIX."product SET";
1947
            $sql.= " price_base_type='".$newpricebase."',";
1948
            $sql.= " price=".$price.",";
1949
            $sql.= " price_ttc=".$price_ttc.",";
1950
            $sql.= " price_min=".$price_min.",";
1951
            $sql.= " price_min_ttc=".$price_min_ttc.",";
1952
            $sql.= " localtax1_tx=".($localtax1>=0?$localtax1:'NULL').",";
1953
            $sql.= " localtax2_tx=".($localtax2>=0?$localtax2:'NULL').",";
1954
            $sql.= " localtax1_type=".($localtaxtype1!=''?"'".$localtaxtype1."'":"'0'").",";
1955
            $sql.= " localtax2_type=".($localtaxtype2!=''?"'".$localtaxtype2."'":"'0'").",";
1956
            $sql.= " default_vat_code=".($newdefaultvatcode?"'".$this->db->escape($newdefaultvatcode)."'":"null").",";
1957
            $sql.= " tva_tx='".price2num($newvat)."',";
1958
            $sql.= " recuperableonly='".$newnpr."'";
1959
            $sql.= " WHERE rowid = ".$id;
1960
1961
            dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
1962
            $resql=$this->db->query($sql);
1963
            if ($resql) {
1964
                $this->multiprices[$level] = $price;
1965
                $this->multiprices_ttc[$level] = $price_ttc;
1966
                $this->multiprices_min[$level]= $price_min;
1967
                $this->multiprices_min_ttc[$level]= $price_min_ttc;
1968
                $this->multiprices_base_type[$level]= $newpricebase;
1969
                $this->multiprices_default_vat_code[$level]= $newdefaultvatcode;
1970
                $this->multiprices_tva_tx[$level]= $newvat;
1971
                $this->multiprices_recuperableonly[$level]= $newnpr;
1972
1973
                $this->price = $price;
1974
                $this->price_ttc = $price_ttc;
1975
                $this->price_min = $price_min;
1976
                $this->price_min_ttc = $price_min_ttc;
1977
                $this->price_base_type = $newpricebase;
1978
                $this->default_vat_code = $newdefaultvatcode;
1979
                $this->tva_tx = $newvat;
1980
                $this->tva_npr = $newnpr;
1981
                //Local taxes
1982
                $this->localtax1_tx = $localtax1;
1983
                $this->localtax2_tx = $localtax2;
1984
                $this->localtax1_type = $localtaxtype1;
1985
                $this->localtax2_type = $localtaxtype2;
1986
1987
                // Price by quantity
1988
                $this->price_by_qty = $newpbq;
1989
1990
                $this->_log_price($user, $level);    // Save price for level into table product_price
1991
1992
                $this->level = $level;                // Store level of price edited for trigger
1993
1994
                // Call trigger
1995
                $result=$this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
1996
                if ($result < 0) {
1997
                    $this->db->rollback();
1998
                    return -1;
1999
                }
2000
                // End call triggers
2001
2002
                $this->db->commit();
2003
            }
2004
            else
2005
            {
2006
                $this->db->rollback();
2007
                dol_print_error($this->db);
2008
            }
2009
        }
2010
2011
        return 1;
2012
    }
2013
2014
    /**
2015
     *  Sets the supplier price expression
2016
     *
2017
     * @param      int $expression_id Expression
2018
     * @return     int                     <0 if KO, >0 if OK
2019
     * @deprecated Use Product::update instead
2020
     */
2021
    public function setPriceExpression($expression_id)
2022
    {
2023
        global $user;
2024
2025
        $this->fk_price_expression = $expression_id;
2026
2027
        return $this->update($this->id, $user);
2028
    }
2029
2030
    /**
2031
     *  Load a product in memory from database
2032
     *
2033
     * @param  int    $id                Id of product/service to load
2034
     * @param  string $ref               Ref of product/service to load
2035
     * @param  string $ref_ext           Ref ext of product/service to load
2036
     * @param  string $barcode           Barcode of product/service to load
2037
     * @param  int    $ignore_expression Ignores the math expression for calculating price and uses the db value instead
2038
     * @return int                         <0 if KO, 0 if not found, >0 if OK
2039
     */
2040
    public function fetch($id = '', $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0)
2041
    {
2042
        include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2043
2044
        global $langs, $conf;
2045
2046
        dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2047
2048
        // Check parameters
2049
        if (! $id && ! $ref && ! $ref_ext && ! $barcode) {
2050
            $this->error='ErrorWrongParameters';
2051
            dol_syslog(get_class($this)."::fetch ".$this->error);
2052
            return -1;
2053
        }
2054
2055
        $sql = "SELECT rowid, ref, ref_ext, label, description, url, note as note_private, customcode, fk_country, price, price_ttc,";
2056
        $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,";
2057
        $sql.= " tobuy, fk_product_type, duration, fk_default_warehouse, seuil_stock_alerte, canvas, net_measure, net_measure_units, weight, weight_units,";
2058
        $sql.= " length, length_units, width, width_units, height, height_units,";
2059
        $sql.= " surface, surface_units, volume, volume_units, barcode, fk_barcode_type, finished,";
2060
        $sql.= " accountancy_code_buy, accountancy_code_sell, accountancy_code_sell_intra, accountancy_code_sell_export, stock, pmp,";
2061
        $sql.= " datec, tms, import_key, entity, desiredstock, tobatch, fk_unit,";
2062
        $sql.= " fk_price_expression, price_autogen";
2063
        $sql.= " FROM ".MAIN_DB_PREFIX."product";
2064
        if ($id) {
2065
            $sql.= " WHERE rowid = ".(int) $id;
2066
        } else {
2067
            $sql.= " WHERE entity IN (".getEntity($this->element).")";
2068
            if ($ref) {
2069
                $sql.= " AND ref = '".$this->db->escape($ref)."'";
2070
            } elseif ($ref_ext) {
2071
                $sql.= " AND ref_ext = '".$this->db->escape($ref_ext)."'";
2072
            } elseif ($barcode) {
2073
                $sql.= " AND barcode = '".$this->db->escape($barcode)."'";
2074
            }
2075
        }
2076
2077
        $resql = $this->db->query($sql);
2078
        if ($resql ) {
2079
            if ($this->db->num_rows($resql) > 0) {
2080
                $obj = $this->db->fetch_object($resql);
2081
2082
                $this->id = $obj->rowid;
2083
                $this->ref                            = $obj->ref;
2084
                $this->ref_ext                        = $obj->ref_ext;
2085
                $this->label                        = $obj->label;
2086
                $this->description                    = $obj->description;
2087
                $this->url                            = $obj->url;
2088
                $this->note_private                    = $obj->note_private;
2089
                $this->note                            = $obj->note_private;  // deprecated
2090
2091
                $this->type                            = $obj->fk_product_type;
2092
                $this->status                        = $obj->tosell;
2093
                $this->status_buy                    = $obj->tobuy;
2094
                $this->status_batch                    = $obj->tobatch;
2095
2096
                $this->customcode                    = $obj->customcode;
2097
                $this->country_id                    = $obj->fk_country;
2098
                $this->country_code                    = getCountry($this->country_id, 2, $this->db);
2099
                $this->price                        = $obj->price;
2100
                $this->price_ttc                    = $obj->price_ttc;
2101
                $this->price_min                    = $obj->price_min;
2102
                $this->price_min_ttc                = $obj->price_min_ttc;
2103
                $this->price_base_type                = $obj->price_base_type;
2104
                $this->cost_price                    = $obj->cost_price;
2105
                $this->default_vat_code                = $obj->default_vat_code;
2106
                $this->tva_tx                        = $obj->tva_tx;
2107
                //! French VAT NPR
2108
                $this->tva_npr                        = $obj->tva_npr;
2109
                $this->recuperableonly                = $obj->tva_npr;       // For backward compatibility
2110
                //! Local taxes
2111
                $this->localtax1_tx                    = $obj->localtax1_tx;
2112
                $this->localtax2_tx                    = $obj->localtax2_tx;
2113
                $this->localtax1_type                = $obj->localtax1_type;
2114
                $this->localtax2_type                = $obj->localtax2_type;
2115
2116
                $this->finished                        = $obj->finished;
2117
                $this->duration                        = $obj->duration;
2118
                $this->duration_value                = substr($obj->duration, 0, dol_strlen($obj->duration)-1);
2119
                $this->duration_unit                = substr($obj->duration, -1);
2120
                $this->canvas                        = $obj->canvas;
2121
                $this->net_measure                        = $obj->net_measure;
2122
                $this->net_measure_units                    = $obj->net_measure_units;
2123
                $this->weight                        = $obj->weight;
2124
                $this->weight_units                    = $obj->weight_units;
2125
                $this->length                        = $obj->length;
2126
                $this->length_units                    = $obj->length_units;
2127
                $this->width                        = $obj->width;
2128
                $this->width_units                    = $obj->width_units;
2129
                $this->height                        = $obj->height;
2130
                $this->height_units                    = $obj->height_units;
2131
2132
                $this->surface                        = $obj->surface;
2133
                $this->surface_units                = $obj->surface_units;
2134
                $this->volume                        = $obj->volume;
2135
                $this->volume_units                    = $obj->volume_units;
2136
                $this->barcode                        = $obj->barcode;
2137
                $this->barcode_type                    = $obj->fk_barcode_type;
2138
2139
                $this->accountancy_code_buy            = $obj->accountancy_code_buy;
2140
                $this->accountancy_code_sell        = $obj->accountancy_code_sell;
2141
                $this->accountancy_code_sell_intra    = $obj->accountancy_code_sell_intra;
2142
                $this->accountancy_code_sell_export    = $obj->accountancy_code_sell_export;
2143
2144
                $this->fk_default_warehouse            = $obj->fk_default_warehouse;
2145
                $this->seuil_stock_alerte            = $obj->seuil_stock_alerte;
2146
                $this->desiredstock                    = $obj->desiredstock;
2147
                $this->stock_reel                    = $obj->stock;
2148
                $this->pmp                            = $obj->pmp;
2149
2150
                $this->date_creation                = $obj->datec;
2151
                $this->date_modification            = $obj->tms;
2152
                $this->import_key                    = $obj->import_key;
2153
                $this->entity                        = $obj->entity;
2154
2155
                $this->ref_ext                        = $obj->ref_ext;
2156
                $this->fk_price_expression            = $obj->fk_price_expression;
2157
                $this->fk_unit                        = $obj->fk_unit;
2158
                $this->price_autogen                = $obj->price_autogen;
2159
2160
                $this->db->free($resql);
2161
2162
                // Retreive all extrafield
2163
                // fetch optionals attributes and labels
2164
                $this->fetch_optionals();
2165
2166
                // multilangs
2167
                if (! empty($conf->global->MAIN_MULTILANGS)) {
2168
                    $this->getMultiLangs();
2169
                }
2170
2171
                // Load multiprices array
2172
                if (! empty($conf->global->PRODUIT_MULTIPRICES))                // prices per segment
2173
                {
2174
                    for ($i=1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++)
2175
                    {
2176
                        $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2177
                        $sql.= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2178
                        $sql.= " FROM ".MAIN_DB_PREFIX."product_price";
2179
                        $sql.= " WHERE entity IN (".getEntity('productprice').")";
2180
                        $sql.= " AND price_level=".$i;
2181
                        $sql.= " AND fk_product = ".$this->id;
2182
                        $sql.= " ORDER BY date_price DESC, rowid DESC";
2183
                        $sql.= " LIMIT 1";
2184
                        $resql = $this->db->query($sql);
2185
                        if ($resql) {
2186
                            $result = $this->db->fetch_array($resql);
2187
2188
                            $this->multiprices[$i]=$result["price"];
2189
                            $this->multiprices_ttc[$i]=$result["price_ttc"];
2190
                            $this->multiprices_min[$i]=$result["price_min"];
2191
                            $this->multiprices_min_ttc[$i]=$result["price_min_ttc"];
2192
                            $this->multiprices_base_type[$i]=$result["price_base_type"];
2193
                            // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2194
                            $this->multiprices_tva_tx[$i]=$result["tva_tx"];     // TODO Add ' ('.$result['default_vat_code'].')'
2195
                            $this->multiprices_recuperableonly[$i]=$result["recuperableonly"];
2196
2197
                            // Price by quantity
2198
                            /*
2199
                            $this->prices_by_qty[$i]=$result["price_by_qty"];
2200
                            $this->prices_by_qty_id[$i]=$result["rowid"];
2201
                            // Récuperation de la liste des prix selon qty si flag positionné
2202
                            if ($this->prices_by_qty[$i] == 1)
2203
                            {
2204
                            $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2205
                            $sql.= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2206
                            $sql.= " WHERE fk_product_price = ".$this->prices_by_qty_id[$i];
2207
                            $sql.= " ORDER BY quantity ASC";
2208
                            $resultat=array();
2209
                            $resql = $this->db->query($sql);
2210
                            if ($resql)
2211
                            {
2212
                            $ii=0;
2213
                            while ($result= $this->db->fetch_array($resql)) {
2214
                            $resultat[$ii]=array();
2215
                            $resultat[$ii]["rowid"]=$result["rowid"];
2216
                            $resultat[$ii]["price"]= $result["price"];
2217
                            $resultat[$ii]["unitprice"]= $result["unitprice"];
2218
                            $resultat[$ii]["quantity"]= $result["quantity"];
2219
                            $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2220
                            $resultat[$ii]["remise"]= $result["remise"];                    // deprecated
2221
                            $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2222
                            $ii++;
2223
                            }
2224
                            $this->prices_by_qty_list[$i]=$resultat;
2225
                            }
2226
                            else
2227
                            {
2228
                            dol_print_error($this->db);
2229
                            return -1;
2230
                            }
2231
                            }*/
2232
                        }
2233
                        else
2234
                        {
2235
                            dol_print_error($this->db);
2236
                            return -1;
2237
                        }
2238
                    }
2239
                }
2240
                elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES))            // prices per customers
2241
                {
2242
                    // Nothing loaded by default. List may be very long.
2243
                }
2244
                elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY))    // prices per quantity
2245
                {
2246
                    $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2247
                    $sql.= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2248
                    $sql.= " FROM ".MAIN_DB_PREFIX."product_price";
2249
                    $sql.= " WHERE fk_product = ".$this->id;
2250
                    $sql.= " ORDER BY date_price DESC, rowid DESC";
2251
                    $sql.= " LIMIT 1";
2252
                    $resql = $this->db->query($sql);
2253
                    if ($resql) {
2254
                        $result = $this->db->fetch_array($resql);
2255
2256
                        // Price by quantity
2257
                        $this->prices_by_qty[0]=$result["price_by_qty"];
2258
                        $this->prices_by_qty_id[0]=$result["rowid"];
2259
                        // Récuperation de la liste des prix selon qty si flag positionné
2260
                        if ($this->prices_by_qty[0] == 1) {
2261
                            $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2262
                            $sql.= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2263
                            $sql.= " WHERE fk_product_price = ".$this->prices_by_qty_id[0];
2264
                            $sql.= " ORDER BY quantity ASC";
2265
                            $resultat=array();
2266
                            $resql = $this->db->query($sql);
2267
                            if ($resql) {
2268
                                $ii=0;
2269
                                while ($result= $this->db->fetch_array($resql)) {
2270
                                    $resultat[$ii]=array();
2271
                                    $resultat[$ii]["rowid"]=$result["rowid"];
2272
                                    $resultat[$ii]["price"]= $result["price"];
2273
                                    $resultat[$ii]["unitprice"]= $result["unitprice"];
2274
                                    $resultat[$ii]["quantity"]= $result["quantity"];
2275
                                    $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2276
                                    //$resultat[$ii]["remise"]= $result["remise"];                    // deprecated
2277
                                    $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2278
                                    $ii++;
2279
                                }
2280
                                $this->prices_by_qty_list[0]=$resultat;
2281
                            }
2282
                            else
2283
                            {
2284
                                    dol_print_error($this->db);
2285
                                    return -1;
2286
                            }
2287
                        }
2288
                    }
2289
                    else
2290
                    {
2291
                        dol_print_error($this->db);
2292
                        return -1;
2293
                    }
2294
                }
2295
                elseif (! empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES))    // prices per customer and quantity
2296
                {
2297
                    for ($i=1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++)
2298
                    {
2299
                        $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2300
                        $sql.= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2301
                        $sql.= " FROM ".MAIN_DB_PREFIX."product_price";
2302
                        $sql.= " WHERE entity IN (".getEntity('productprice').")";
2303
                        $sql.= " AND price_level=".$i;
2304
                        $sql.= " AND fk_product = ".$this->id;
2305
                        $sql.= " ORDER BY date_price DESC, rowid DESC";
2306
                        $sql.= " LIMIT 1";
2307
                        $resql = $this->db->query($sql);
2308
                        if ($resql) {
2309
                            $result = $this->db->fetch_array($resql);
2310
2311
                            $this->multiprices[$i]=$result["price"];
2312
                            $this->multiprices_ttc[$i]=$result["price_ttc"];
2313
                            $this->multiprices_min[$i]=$result["price_min"];
2314
                            $this->multiprices_min_ttc[$i]=$result["price_min_ttc"];
2315
                            $this->multiprices_base_type[$i]=$result["price_base_type"];
2316
                            // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2317
                            $this->multiprices_tva_tx[$i]=$result["tva_tx"];     // TODO Add ' ('.$result['default_vat_code'].')'
2318
                            $this->multiprices_recuperableonly[$i]=$result["recuperableonly"];
2319
2320
                            // Price by quantity
2321
                            $this->prices_by_qty[$i]=$result["price_by_qty"];
2322
                            $this->prices_by_qty_id[$i]=$result["rowid"];
2323
                            // Récuperation de la liste des prix selon qty si flag positionné
2324
                            if ($this->prices_by_qty[$i] == 1) {
2325
                                $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2326
                                $sql.= " FROM ".MAIN_DB_PREFIX."product_price_by_qty";
2327
                                $sql.= " WHERE fk_product_price = ".$this->prices_by_qty_id[$i];
2328
                                $sql.= " ORDER BY quantity ASC";
2329
                                $resultat=array();
2330
                                $resql = $this->db->query($sql);
2331
                                if ($resql) {
2332
                                    $ii=0;
2333
                                    while ($result= $this->db->fetch_array($resql)) {
2334
                                        $resultat[$ii]=array();
2335
                                        $resultat[$ii]["rowid"]=$result["rowid"];
2336
                                        $resultat[$ii]["price"]= $result["price"];
2337
                                        $resultat[$ii]["unitprice"]= $result["unitprice"];
2338
                                        $resultat[$ii]["quantity"]= $result["quantity"];
2339
                                        $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2340
                                        $resultat[$ii]["remise"]= $result["remise"];                    // deprecated
2341
                                        $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2342
                                        $ii++;
2343
                                    }
2344
                                    $this->prices_by_qty_list[$i]=$resultat;
2345
                                }
2346
                                else
2347
                                         {
2348
                                    dol_print_error($this->db);
2349
                                    return -1;
2350
                                }
2351
                            }
2352
                        }
2353
                        else
2354
                        {
2355
                            dol_print_error($this->db);
2356
                            return -1;
2357
                        }
2358
                    }
2359
                }
2360
2361
                if (!empty($conf->dynamicprices->enabled) && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2362
                       include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2363
                    $priceparser = new PriceParser($this->db);
2364
                       $price_result = $priceparser->parseProduct($this);
2365
                    if ($price_result >= 0) {
2366
                        $this->price = $price_result;
2367
                        // Calculate the VAT
2368
                        $this->price_ttc = price2num($this->price) * (1 + ($this->tva_tx / 100));
2369
                        $this->price_ttc = price2num($this->price_ttc, 'MU');
2370
                    }
2371
                }
2372
2373
                // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2374
                // Instead we just init the stock_warehouse array
2375
                $this->stock_warehouse = array();
2376
2377
                return 1;
2378
            }
2379
            else
2380
            {
2381
                return 0;
2382
            }
2383
        }
2384
        else
2385
        {
2386
            dol_print_error($this->db);
2387
            return -1;
2388
        }
2389
    }
2390
2391
2392
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2393
    /**
2394
     *  Charge tableau des stats propale pour le produit/service
2395
     *
2396
     * @param  int $socid Id societe
2397
     * @return integer      Tableau des stats dans $this->stats_propale, <0 if ko >0 if ok
2398
     */
2399
    public function load_stats_propale($socid = 0)
2400
    {
2401
        // phpcs:enable
2402
        global $conf;
2403
        global $user;
2404
2405
        $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
2406
        $sql.= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2407
        $sql.= " FROM ".MAIN_DB_PREFIX."propaldet as pd";
2408
        $sql.= ", ".MAIN_DB_PREFIX."propal as p";
2409
        $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2410
        if (!$user->rights->societe->client->voir && !$socid) {
2411
            $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2412
        }
2413
        $sql.= " WHERE p.rowid = pd.fk_propal";
2414
        $sql.= " AND p.fk_soc = s.rowid";
2415
        $sql.= " AND p.entity IN (".getEntity('propal').")";
2416
        $sql.= " AND pd.fk_product = ".$this->id;
2417
        if (!$user->rights->societe->client->voir && !$socid) {
2418
            $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2419
        }
2420
        //$sql.= " AND pr.fk_statut != 0";
2421
        if ($socid > 0) {
2422
            $sql.= " AND p.fk_soc = ".$socid;
2423
        }
2424
2425
        $result = $this->db->query($sql);
2426
        if ($result ) {
2427
            $obj=$this->db->fetch_object($result);
2428
            $this->stats_propale['customers']=$obj->nb_customers;
2429
            $this->stats_propale['nb']=$obj->nb;
2430
            $this->stats_propale['rows']=$obj->nb_rows;
2431
            $this->stats_propale['qty']=$obj->qty?$obj->qty:0;
2432
2433
            // if it's a virtual product, maybe it is in proposal by extension
2434
            if (! empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
2435
                $TFather = $this->getFather();
2436
                if (is_array($TFather) && !empty($TFather)) {
2437
                    foreach($TFather as &$fatherData) {
2438
                        $pFather = new Product($this->db);
2439
                        $pFather->id = $fatherData['id'];
2440
                        $qtyCoef = $fatherData['qty'];
2441
2442
                        if ($fatherData['incdec']) {
2443
                            $pFather->load_stats_propale($socid);
2444
2445
                            $this->stats_propale['customers']+=$pFather->stats_propale['customers'];
2446
                            $this->stats_propale['nb']+=$pFather->stats_propale['nb'];
2447
                            $this->stats_propale['rows']+=$pFather->stats_propale['rows'];
2448
                            $this->stats_propale['qty']+=$pFather->stats_propale['qty'] * $qtyCoef;
2449
                        }
2450
                    }
2451
                }
2452
            }
2453
2454
            return 1;
2455
        }
2456
        else
2457
        {
2458
            $this->error=$this->db->error();
2459
            return -1;
2460
        }
2461
    }
2462
2463
2464
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2465
    /**
2466
     *  Charge tableau des stats propale pour le produit/service
2467
     *
2468
     * @param  int $socid Id thirdparty
2469
     * @return array               Tableau des stats
2470
     */
2471
    public function load_stats_proposal_supplier($socid = 0)
2472
    {
2473
        // phpcs:enable
2474
        global $conf;
2475
        global $user;
2476
2477
        $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
2478
        $sql.= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
2479
        $sql.= " FROM ".MAIN_DB_PREFIX."supplier_proposaldet as pd";
2480
        $sql.= ", ".MAIN_DB_PREFIX."supplier_proposal as p";
2481
        $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2482
        if (!$user->rights->societe->client->voir && !$socid) { $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2483
        }
2484
        $sql.= " WHERE p.rowid = pd.fk_supplier_proposal";
2485
        $sql.= " AND p.fk_soc = s.rowid";
2486
        $sql.= " AND p.entity IN (".getEntity('supplier_proposal').")";
2487
        $sql.= " AND pd.fk_product = ".$this->id;
2488
        if (!$user->rights->societe->client->voir && !$socid) { $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2489
        }
2490
        //$sql.= " AND pr.fk_statut != 0";
2491
        if ($socid > 0) {    $sql.= " AND p.fk_soc = ".$socid;
2492
        }
2493
2494
        $result = $this->db->query($sql);
2495
        if ($result ) {
2496
            $obj=$this->db->fetch_object($result);
2497
            $this->stats_proposal_supplier['suppliers']=$obj->nb_suppliers;
2498
            $this->stats_proposal_supplier['nb']=$obj->nb;
2499
            $this->stats_proposal_supplier['rows']=$obj->nb_rows;
2500
            $this->stats_proposal_supplier['qty']=$obj->qty?$obj->qty:0;
2501
            return 1;
2502
        }
2503
        else
2504
        {
2505
            $this->error=$this->db->error();
2506
            return -1;
2507
        }
2508
    }
2509
2510
2511
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2512
    /**
2513
     *  Charge tableau des stats commande client pour le produit/service
2514
     *
2515
     * @param  int    $socid           Id societe pour filtrer sur une societe
2516
     * @param  string $filtrestatut    Id statut pour filtrer sur un statut
2517
     * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
2518
     * @return integer                 Array of stats in $this->stats_commande (nb=nb of order, qty=qty ordered), <0 if ko or >0 if ok
2519
     */
2520
    public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2521
    {
2522
        // phpcs:enable
2523
        global $conf,$user;
2524
2525
        $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2526
        $sql.= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2527
        $sql.= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
2528
        $sql.= ", ".MAIN_DB_PREFIX."commande as c";
2529
        $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2530
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2531
            $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2532
        }
2533
        $sql.= " WHERE c.rowid = cd.fk_commande";
2534
        $sql.= " AND c.fk_soc = s.rowid";
2535
        $sql.= " AND c.entity IN (".getEntity('commande').")";
2536
        $sql.= " AND cd.fk_product = ".$this->id;
2537
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2538
            $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2539
        }
2540
        if ($socid > 0) {
2541
            $sql.= " AND c.fk_soc = ".$socid;
2542
        }
2543
        if ($filtrestatut <> '') {
2544
            $sql.= " AND c.fk_statut in (".$filtrestatut.")";
2545
        }
2546
2547
        $result = $this->db->query($sql);
2548
        if ($result ) {
2549
            $obj=$this->db->fetch_object($result);
2550
            $this->stats_commande['customers']=$obj->nb_customers;
2551
            $this->stats_commande['nb']=$obj->nb;
2552
            $this->stats_commande['rows']=$obj->nb_rows;
2553
            $this->stats_commande['qty']=$obj->qty?$obj->qty:0;
2554
2555
            // if it's a virtual product, maybe it is in order by extension
2556
            if (! empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
2557
                $TFather = $this->getFather();
2558
                if (is_array($TFather) && !empty($TFather)) {
2559
                    foreach($TFather as &$fatherData) {
2560
                        $pFather = new Product($this->db);
2561
                        $pFather->id = $fatherData['id'];
2562
                        $qtyCoef = $fatherData['qty'];
2563
2564
                        if ($fatherData['incdec']) {
2565
                            $pFather->load_stats_commande($socid, $filtrestatut);
2566
2567
                            $this->stats_commande['customers']+=$pFather->stats_commande['customers'];
2568
                            $this->stats_commande['nb']+=$pFather->stats_commande['nb'];
2569
                            $this->stats_commande['rows']+=$pFather->stats_commande['rows'];
2570
                            $this->stats_commande['qty']+=$pFather->stats_commande['qty'] * $qtyCoef;
2571
                        }
2572
                    }
2573
                }
2574
            }
2575
2576
            // If stock decrease is on invoice validation, the theorical stock continue to
2577
            // count the orders to ship in theorical stock when some are already removed b invoice validation.
2578
            // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation.
2579
            if (! empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
2580
                if (! empty($conf->global->DECREASE_ONLY_UNINVOICEDPRODUCTS)) {
2581
                    $adeduire = 0;
2582
                    $sql = "SELECT sum(fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet fd ";
2583
                    $sql .= " JOIN ".MAIN_DB_PREFIX."facture f ON fd.fk_facture = f.rowid ";
2584
                    $sql .= " JOIN ".MAIN_DB_PREFIX."element_element el ON el.fk_target = f.rowid and el.targettype = 'facture' and sourcetype = 'commande'";
2585
                    $sql .= " JOIN ".MAIN_DB_PREFIX."commande c ON el.fk_source = c.rowid ";
2586
                    $sql .= " WHERE c.fk_statut IN (".$filtrestatut.") AND c.facture = 0 AND fd.fk_product = ".$this->id;
2587
                    dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
2588
2589
                    $resql = $this->db->query($sql);
2590
                    if ($resql ) {
2591
                        if ($this->db->num_rows($resql) > 0) {
2592
                            $obj = $this->db->fetch_object($resql);
2593
                            $adeduire += $obj->count;
2594
                        }
2595
                    }
2596
2597
                    $this->stats_commande['qty'] -= $adeduire;
2598
                }
2599
            }
2600
2601
            return 1;
2602
        }
2603
        else
2604
        {
2605
            $this->error=$this->db->error();
2606
            return -1;
2607
        }
2608
    }
2609
2610
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2611
    /**
2612
     *  Charge tableau des stats commande fournisseur pour le produit/service
2613
     *
2614
     * @param  int    $socid           Id societe pour filtrer sur une societe
2615
     * @param  string $filtrestatut    Id des statuts pour filtrer sur des statuts
2616
     * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
2617
     * @return array                     Tableau des stats
2618
     */
2619
    public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2620
    {
2621
        // phpcs:enable
2622
        global $conf,$user;
2623
2624
        $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
2625
        $sql.= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2626
        $sql.= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as cd";
2627
        $sql.= ", ".MAIN_DB_PREFIX."commande_fournisseur as c";
2628
        $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2629
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2630
            $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2631
        }
2632
        $sql.= " WHERE c.rowid = cd.fk_commande";
2633
        $sql.= " AND c.fk_soc = s.rowid";
2634
        $sql.= " AND c.entity IN (".getEntity('supplier_order').")";
2635
        $sql.= " AND cd.fk_product = ".$this->id;
2636
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2637
            $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2638
        }
2639
        if ($socid > 0) {
2640
            $sql.= " AND c.fk_soc = ".$socid;
2641
        }
2642
        if ($filtrestatut != '') {
2643
            $sql.= " AND c.fk_statut in (".$filtrestatut.")"; // Peut valoir 0
2644
        }
2645
2646
        $result = $this->db->query($sql);
2647
        if ($result ) {
2648
            $obj=$this->db->fetch_object($result);
2649
            $this->stats_commande_fournisseur['suppliers']=$obj->nb_suppliers;
2650
            $this->stats_commande_fournisseur['nb']=$obj->nb;
2651
            $this->stats_commande_fournisseur['rows']=$obj->nb_rows;
2652
            $this->stats_commande_fournisseur['qty']=$obj->qty?$obj->qty:0;
2653
            return 1;
2654
        }
2655
        else
2656
        {
2657
            $this->error=$this->db->error().' sql='.$sql;
2658
            return -1;
2659
        }
2660
    }
2661
2662
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2663
    /**
2664
     *  Charge tableau des stats expedition client pour le produit/service
2665
     *
2666
     * @param  int    $socid           Id societe pour filtrer sur une societe
2667
     * @param  string $filtrestatut    Id statut pour filtrer sur un statut
2668
     * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
2669
     * @return array                   Tableau des stats
2670
     */
2671
    public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2672
    {
2673
        // phpcs:enable
2674
        global $conf,$user;
2675
2676
        $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
2677
        $sql.= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
2678
        $sql.= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed";
2679
        $sql.= ", ".MAIN_DB_PREFIX."commandedet as cd";
2680
        $sql.= ", ".MAIN_DB_PREFIX."commande as c";
2681
        $sql.= ", ".MAIN_DB_PREFIX."expedition as e";
2682
        $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2683
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2684
            $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2685
        }
2686
        $sql.= " WHERE e.rowid = ed.fk_expedition";
2687
        $sql.= " AND c.rowid = cd.fk_commande";
2688
        $sql.= " AND e.fk_soc = s.rowid";
2689
        $sql.= " AND e.entity IN (".getEntity('expedition').")";
2690
        $sql.= " AND ed.fk_origin_line = cd.rowid";
2691
        $sql.= " AND cd.fk_product = ".$this->id;
2692
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) {
2693
            $sql.= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2694
        }
2695
        if ($socid > 0) {
2696
            $sql.= " AND e.fk_soc = ".$socid;
2697
        }
2698
        if ($filtrestatut <> '') {
2699
            $sql.= " AND c.fk_statut in (".$filtrestatut.")";
2700
        }
2701
2702
        $result = $this->db->query($sql);
2703
        if ($result ) {
2704
            $obj=$this->db->fetch_object($result);
2705
            $this->stats_expedition['customers']=$obj->nb_customers;
2706
            $this->stats_expedition['nb']=$obj->nb;
2707
            $this->stats_expedition['rows']=$obj->nb_rows;
2708
            $this->stats_expedition['qty']=$obj->qty?$obj->qty:0;
2709
2710
            // if it's a virtual product, maybe it is in sending by extension
2711
            if (! empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
2712
                $TFather = $this->getFather();
2713
                if (is_array($TFather) && !empty($TFather)) {
2714
                    foreach($TFather as &$fatherData) {
2715
                        $pFather = new Product($this->db);
2716
                        $pFather->id = $fatherData['id'];
2717
                        $qtyCoef = $fatherData['qty'];
2718
2719
                        if ($fatherData['incdec']) {
2720
                            $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
2721
2722
                            $this->stats_expedition['customers']+=$pFather->stats_expedition['customers'];
2723
                            $this->stats_expedition['nb']+=$pFather->stats_expedition['nb'];
2724
                            $this->stats_expedition['rows']+=$pFather->stats_expedition['rows'];
2725
                            $this->stats_expedition['qty']+=$pFather->stats_expedition['qty'] * $qtyCoef;
2726
                        }
2727
                    }
2728
                }
2729
            }
2730
            return 1;
2731
        }
2732
        else
2733
        {
2734
            $this->error=$this->db->error();
2735
            return -1;
2736
        }
2737
    }
2738
2739
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2740
    /**
2741
     *  Charge tableau des stats réception fournisseur pour le produit/service
2742
     *
2743
     * @param  int    $socid           Id societe pour filtrer sur une societe
2744
     * @param  string $filtrestatut    Id statut pour filtrer sur un statut
2745
     * @param  int    $forVirtualStock Ignore rights filter for virtual stock calculation.
2746
     * @return array                   Tableau des stats
2747
     */
2748
    public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
2749
    {
2750
        // phpcs:enable
2751
        global $conf,$user;
2752
2753
        $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_customers, COUNT(DISTINCT cf.rowid) as nb,";
2754
        $sql.= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2755
        $sql.= " FROM ".MAIN_DB_PREFIX."commande_fournisseur_dispatch as fd";
2756
        $sql.= ", ".MAIN_DB_PREFIX."commande_fournisseur as cf";
2757
        $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2758
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) { $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2759
        }
2760
        $sql.= " WHERE cf.rowid = fd.fk_commande";
2761
        $sql.= " AND cf.fk_soc = s.rowid";
2762
        $sql.= " AND cf.entity IN (".getEntity('supplier_order').")";
2763
        $sql.= " AND fd.fk_product = ".$this->id;
2764
        if (!$user->rights->societe->client->voir && !$socid && !$forVirtualStock) { $sql.= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2765
        }
2766
        if ($socid > 0) {    $sql.= " AND cf.fk_soc = ".$socid;
2767
        }
2768
        if ($filtrestatut <> '') { $sql.= " AND cf.fk_statut in (".$filtrestatut.")";
2769
        }
2770
2771
        $result = $this->db->query($sql);
2772
        if ($result ) {
2773
            $obj=$this->db->fetch_object($result);
2774
            $this->stats_reception['suppliers']=$obj->nb_customers;
2775
            $this->stats_reception['nb']=$obj->nb;
2776
            $this->stats_reception['rows']=$obj->nb_rows;
2777
            $this->stats_reception['qty']=$obj->qty?$obj->qty:0;
2778
            return 1;
2779
        }
2780
        else
2781
        {
2782
            $this->error=$this->db->error();
2783
            return -1;
2784
        }
2785
    }
2786
2787
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2788
    /**
2789
     *  Charge tableau des stats contrat pour le produit/service
2790
     *
2791
     * @param  int $socid Id societe
2792
     * @return array               Tableau des stats
2793
     */
2794
    public function load_stats_contrat($socid = 0)
2795
    {
2796
        // phpcs:enable
2797
        global $conf;
2798
        global $user;
2799
2800
        $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2801
        $sql.= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
2802
        $sql.= " FROM ".MAIN_DB_PREFIX."contratdet as cd";
2803
        $sql.= ", ".MAIN_DB_PREFIX."contrat as c";
2804
        $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2805
        if (!$user->rights->societe->client->voir && !$socid) {
2806
            $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2807
        }
2808
        $sql.= " WHERE c.rowid = cd.fk_contrat";
2809
        $sql.= " AND c.fk_soc = s.rowid";
2810
        $sql.= " AND c.entity IN (".getEntity('contract').")";
2811
        $sql.= " AND cd.fk_product = ".$this->id;
2812
        if (!$user->rights->societe->client->voir && !$socid) {
2813
            $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2814
        }
2815
        //$sql.= " AND c.statut != 0";
2816
        if ($socid > 0) {
2817
            $sql.= " AND c.fk_soc = ".$socid;
2818
        }
2819
2820
        $result = $this->db->query($sql);
2821
        if ($result ) {
2822
            $obj=$this->db->fetch_object($result);
2823
            $this->stats_contrat['customers']=$obj->nb_customers;
2824
            $this->stats_contrat['nb']=$obj->nb;
2825
            $this->stats_contrat['rows']=$obj->nb_rows;
2826
            $this->stats_contrat['qty']=$obj->qty?$obj->qty:0;
2827
2828
            // if it's a virtual product, maybe it is in contract by extension
2829
            if (! empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
2830
                $TFather = $this->getFather();
2831
                if (is_array($TFather) && !empty($TFather)) {
2832
                    foreach($TFather as &$fatherData) {
2833
                        $pFather = new Product($this->db);
2834
                        $pFather->id = $fatherData['id'];
2835
                        $qtyCoef = $fatherData['qty'];
2836
2837
                        if ($fatherData['incdec']) {
2838
                            $pFather->load_stats_contrat($socid);
2839
2840
                            $this->stats_contrat['customers']+=$pFather->stats_contrat['customers'];
2841
                            $this->stats_contrat['nb']+=$pFather->stats_contrat['nb'];
2842
                            $this->stats_contrat['rows']+=$pFather->stats_contrat['rows'];
2843
                            $this->stats_contrat['qty']+=$pFather->stats_contrat['qty'] * $qtyCoef;
2844
                        }
2845
                    }
2846
                }
2847
            }
2848
            return 1;
2849
        }
2850
        else
2851
        {
2852
            $this->error=$this->db->error().' sql='.$sql;
2853
            return -1;
2854
        }
2855
    }
2856
2857
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2858
    /**
2859
     *  Charge tableau des stats facture pour le produit/service
2860
     *
2861
     * @param  int $socid Id societe
2862
     * @return array                   Tableau des stats
2863
     */
2864
    public function load_stats_facture($socid = 0)
2865
    {
2866
        // phpcs:enable
2867
        global $db, $conf, $user;
2868
2869
        $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
2870
        $sql.= " COUNT(fd.rowid) as nb_rows, SUM(".$db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
2871
        $sql.= " FROM ".MAIN_DB_PREFIX."facturedet as fd";
2872
        $sql.= ", ".MAIN_DB_PREFIX."facture as f";
2873
        $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2874
        if (!$user->rights->societe->client->voir && !$socid) {
2875
            $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2876
        }
2877
        $sql.= " WHERE f.rowid = fd.fk_facture";
2878
        $sql.= " AND f.fk_soc = s.rowid";
2879
        $sql.= " AND f.entity IN (".getEntity('invoice').")";
2880
        $sql.= " AND fd.fk_product = ".$this->id;
2881
        if (!$user->rights->societe->client->voir && !$socid) {
2882
            $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2883
        }
2884
        //$sql.= " AND f.fk_statut != 0";
2885
        if ($socid > 0) {
2886
            $sql .= " AND f.fk_soc = ".$socid;
2887
        }
2888
2889
        $result = $this->db->query($sql);
2890
        if ($result ) {
2891
            $obj=$this->db->fetch_object($result);
2892
            $this->stats_facture['customers']=$obj->nb_customers;
2893
            $this->stats_facture['nb']=$obj->nb;
2894
            $this->stats_facture['rows']=$obj->nb_rows;
2895
            $this->stats_facture['qty']=$obj->qty?$obj->qty:0;
2896
2897
            // if it's a virtual product, maybe it is in invoice by extension
2898
            if (! empty($conf->global->PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC)) {
2899
                $TFather = $this->getFather();
2900
                if (is_array($TFather) && !empty($TFather)) {
2901
                    foreach($TFather as &$fatherData) {
2902
                        $pFather = new Product($this->db);
2903
                        $pFather->id = $fatherData['id'];
2904
                        $qtyCoef = $fatherData['qty'];
2905
2906
                        if ($fatherData['incdec']) {
2907
                            $pFather->load_stats_facture($socid);
2908
2909
                            $this->stats_facture['customers']+=$pFather->stats_facture['customers'];
2910
                            $this->stats_facture['nb']+=$pFather->stats_facture['nb'];
2911
                            $this->stats_facture['rows']+=$pFather->stats_facture['rows'];
2912
                            $this->stats_facture['qty']+=$pFather->stats_facture['qty'] * $qtyCoef;
2913
                        }
2914
                    }
2915
                }
2916
            }
2917
            return 1;
2918
        }
2919
        else
2920
        {
2921
            $this->error=$this->db->error();
2922
            return -1;
2923
        }
2924
    }
2925
2926
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2927
    /**
2928
     *  Charge tableau des stats facture pour le produit/service
2929
     *
2930
     * @param  int $socid Id societe
2931
     * @return array                   Tableau des stats
2932
     */
2933
    public function load_stats_facture_fournisseur($socid = 0)
2934
    {
2935
        // phpcs:enable
2936
        global $conf;
2937
        global $user;
2938
2939
        $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
2940
        $sql.= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
2941
        $sql.= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as fd";
2942
        $sql.= ", ".MAIN_DB_PREFIX."facture_fourn as f";
2943
        $sql.= ", ".MAIN_DB_PREFIX."societe as s";
2944
        if (!$user->rights->societe->client->voir && !$socid) {
2945
            $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2946
        }
2947
        $sql.= " WHERE f.rowid = fd.fk_facture_fourn";
2948
        $sql.= " AND f.fk_soc = s.rowid";
2949
        $sql.= " AND f.entity IN (".getEntity('facture_fourn').")";
2950
        $sql.= " AND fd.fk_product = ".$this->id;
2951
        if (!$user->rights->societe->client->voir && !$socid) {
2952
            $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
2953
        }
2954
        //$sql.= " AND f.fk_statut != 0";
2955
        if ($socid > 0) {
2956
            $sql .= " AND f.fk_soc = ".$socid;
2957
        }
2958
2959
        $result = $this->db->query($sql);
2960
        if ($result ) {
2961
            $obj=$this->db->fetch_object($result);
2962
            $this->stats_facture_fournisseur['suppliers']=$obj->nb_suppliers;
2963
            $this->stats_facture_fournisseur['nb']=$obj->nb;
2964
            $this->stats_facture_fournisseur['rows']=$obj->nb_rows;
2965
            $this->stats_facture_fournisseur['qty']=$obj->qty?$obj->qty:0;
2966
            return 1;
2967
        }
2968
        else
2969
        {
2970
            $this->error=$this->db->error();
2971
            return -1;
2972
        }
2973
    }
2974
2975
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2976
    /**
2977
     *  Return an array formated for showing graphs
2978
     *
2979
     * @param  string $sql  Request to execute
2980
     * @param  string $mode 'byunit'=number of unit, 'bynumber'=nb of entities
2981
     * @param  int    $year Year (0=current year)
2982
     * @return array               <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
2983
     */
2984
    private function _get_stats($sql, $mode, $year = 0)
2985
    {
2986
        // phpcs:enable
2987
        $resql = $this->db->query($sql);
2988
        if ($resql) {
2989
            $num = $this->db->num_rows($resql);
2990
            $i = 0;
2991
            while ($i < $num)
2992
            {
2993
                $arr = $this->db->fetch_array($resql);
2994
                if ($mode == 'byunit') {
2995
                    $tab[$arr[1]] = $arr[0];    // 1st field
2996
                }
2997
                if ($mode == 'bynumber') {
2998
                    $tab[$arr[1]] = $arr[2];    // 3rd field
2999
                }
3000
                $i++;
3001
            }
3002
        }
3003
        else
3004
        {
3005
            $this->error=$this->db->error().' sql='.$sql;
3006
            return -1;
3007
        }
3008
3009
        if (empty($year)) {
3010
            $year = strftime('%Y', time());
3011
            $month = strftime('%m', time());
3012
        }
3013
        else
3014
        {
3015
            $month=12;    // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3016
        }
3017
        $result = array();
3018
3019
        for ($j = 0 ; $j < 12 ; $j++)
3020
        {
3021
            $idx=ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, $year), "%b"), 3, 'right', 'UTF-8', 1));
3022
            $monthnum=sprintf("%02s", $month);
3023
3024
            $result[$j] = array($idx,isset($tab[$year.$month])?$tab[$year.$month]:0);
3025
            //            $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
3026
3027
            $month = "0".($month - 1);
3028
            if (dol_strlen($month) == 3) {
3029
                $month = substr($month, 1);
3030
            }
3031
            if ($month == 0) {
3032
                $month = 12;
3033
                $year = $year - 1;
3034
            }
3035
        }
3036
3037
        return array_reverse($result);
3038
    }
3039
3040
3041
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3042
    /**
3043
     *  Return nb of units or customers invoices in which product is included
3044
     *
3045
     * @param  int    $socid               Limit count on a particular third party id
3046
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
3047
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
3048
     * @param  int    $year                Year (0=last 12 month)
3049
     * @param  string $morefilter          More sql filters
3050
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
3051
     */
3052
    public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3053
    {
3054
        // phpcs:enable
3055
        global $conf;
3056
        global $user;
3057
3058
        $sql = "SELECT sum(d.qty), date_format(f.datef, '%Y%m')";
3059
        if ($mode == 'bynumber') {
3060
            $sql.= ", count(DISTINCT f.rowid)";
3061
        }
3062
        $sql.= " FROM ".MAIN_DB_PREFIX."facturedet as d, ".MAIN_DB_PREFIX."facture as f, ".MAIN_DB_PREFIX."societe as s";
3063
        if ($filteronproducttype >= 0) {
3064
            $sql.=", ".MAIN_DB_PREFIX."product as p";
3065
        }
3066
        if (!$user->rights->societe->client->voir && !$socid) {
3067
            $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3068
        }
3069
        $sql.= " WHERE f.rowid = d.fk_facture";
3070
        if ($this->id > 0) {
3071
            $sql.= " AND d.fk_product =".$this->id;
3072
        } else {
3073
            $sql.=" AND d.fk_product > 0";
3074
        }
3075
        if ($filteronproducttype >= 0) {
3076
            $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
3077
        }
3078
        $sql.= " AND f.fk_soc = s.rowid";
3079
        $sql.= " AND f.entity IN (".getEntity('invoice').")";
3080
        if (!$user->rights->societe->client->voir && !$socid) {
3081
            $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
3082
        }
3083
        if ($socid > 0) {
3084
            $sql.= " AND f.fk_soc = $socid";
3085
        }
3086
        $sql.=$morefilter;
3087
        $sql.= " GROUP BY date_format(f.datef,'%Y%m')";
3088
        $sql.= " ORDER BY date_format(f.datef,'%Y%m') DESC";
3089
3090
        return $this->_get_stats($sql, $mode, $year);
3091
    }
3092
3093
3094
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3095
    /**
3096
     *  Return nb of units or supplier invoices in which product is included
3097
     *
3098
     * @param  int    $socid               Limit count on a particular third party id
3099
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
3100
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
3101
     * @param  int    $year                Year (0=last 12 month)
3102
     * @param  string $morefilter          More sql filters
3103
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
3104
     */
3105
    public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3106
    {
3107
        // phpcs:enable
3108
        global $conf;
3109
        global $user;
3110
3111
        $sql = "SELECT sum(d.qty), date_format(f.datef, '%Y%m')";
3112
        if ($mode == 'bynumber') {
3113
            $sql.= ", count(DISTINCT f.rowid)";
3114
        }
3115
        $sql.= " FROM ".MAIN_DB_PREFIX."facture_fourn_det as d, ".MAIN_DB_PREFIX."facture_fourn as f, ".MAIN_DB_PREFIX."societe as s";
3116
        if ($filteronproducttype >= 0) {
3117
            $sql.=", ".MAIN_DB_PREFIX."product as p";
3118
        }
3119
        if (!$user->rights->societe->client->voir && !$socid) {
3120
            $sql.= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3121
        }
3122
        $sql.= " WHERE f.rowid = d.fk_facture_fourn";
3123
        if ($this->id > 0) {
3124
            $sql.= " AND d.fk_product =".$this->id;
3125
        } else {
3126
            $sql.=" AND d.fk_product > 0";
3127
        }
3128
        if ($filteronproducttype >= 0) {
3129
            $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
3130
        }
3131
        $sql.= " AND f.fk_soc = s.rowid";
3132
        $sql.= " AND f.entity IN (".getEntity('facture_fourn').")";
3133
        if (!$user->rights->societe->client->voir && !$socid) {
3134
            $sql.= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
3135
        }
3136
        if ($socid > 0) {
3137
            $sql.= " AND f.fk_soc = $socid";
3138
        }
3139
        $sql.=$morefilter;
3140
        $sql.= " GROUP BY date_format(f.datef,'%Y%m')";
3141
        $sql.= " ORDER BY date_format(f.datef,'%Y%m') DESC";
3142
3143
        return $this->_get_stats($sql, $mode, $year);
3144
    }
3145
3146
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3147
    /**
3148
     *  Return nb of units or proposals in which product is included
3149
     *
3150
     * @param  int    $socid               Limit count on a particular third party id
3151
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
3152
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
3153
     * @param  int    $year                Year (0=last 12 month)
3154
     * @param  string $morefilter          More sql filters
3155
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
3156
     */
3157
    public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3158
    {
3159
        // phpcs:enable
3160
        global $conf;
3161
        global $user;
3162
3163
        $sql = "SELECT sum(d.qty), date_format(p.datep, '%Y%m')";
3164
        if ($mode == 'bynumber') {
3165
            $sql.= ", count(DISTINCT p.rowid)";
3166
        }
3167
        $sql.= " FROM ".MAIN_DB_PREFIX."propaldet as d, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."societe as s";
3168
        if ($filteronproducttype >= 0) {
3169
            $sql.=", ".MAIN_DB_PREFIX."product as prod";
3170
        }
3171
        if (!$user->rights->societe->client->voir && !$socid) {
3172
            $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3173
        }
3174
        $sql.= " WHERE p.rowid = d.fk_propal";
3175
        if ($this->id > 0) {
3176
            $sql.= " AND d.fk_product =".$this->id;
3177
        } else {
3178
            $sql.=" AND d.fk_product > 0";
3179
        }
3180
        if ($filteronproducttype >= 0) {
3181
            $sql.= " AND prod.rowid = d.fk_product AND prod.fk_product_type =".$filteronproducttype;
3182
        }
3183
        $sql.= " AND p.fk_soc = s.rowid";
3184
        $sql.= " AND p.entity IN (".getEntity('propal').")";
3185
        if (!$user->rights->societe->client->voir && !$socid) {
3186
            $sql.= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
3187
        }
3188
        if ($socid > 0) {
3189
            $sql.= " AND p.fk_soc = ".$socid;
3190
        }
3191
        $sql.=$morefilter;
3192
        $sql.= " GROUP BY date_format(p.datep,'%Y%m')";
3193
        $sql.= " ORDER BY date_format(p.datep,'%Y%m') DESC";
3194
3195
        return $this->_get_stats($sql, $mode, $year);
3196
    }
3197
3198
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3199
    /**
3200
     *  Return nb of units or proposals in which product is included
3201
     *
3202
     * @param  int    $socid               Limit count on a particular third party id
3203
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
3204
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
3205
     * @param  int    $year                Year (0=last 12 month)
3206
     * @param  string $morefilter          More sql filters
3207
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
3208
     */
3209
    public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3210
    {
3211
        // phpcs:enable
3212
        global $conf;
3213
        global $user;
3214
3215
        $sql = "SELECT sum(d.qty), date_format(p.date_valid, '%Y%m')";
3216
        if ($mode == 'bynumber') {
3217
            $sql.= ", count(DISTINCT p.rowid)";
3218
        }
3219
        $sql.= " FROM ".MAIN_DB_PREFIX."supplier_proposaldet as d, ".MAIN_DB_PREFIX."supplier_proposal as p, ".MAIN_DB_PREFIX."societe as s";
3220
        if ($filteronproducttype >= 0) {
3221
            $sql.=", ".MAIN_DB_PREFIX."product as prod";
3222
        }
3223
        if (!$user->rights->societe->client->voir && !$socid) {
3224
            $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3225
        }
3226
        $sql.= " WHERE p.rowid = d.fk_supplier_proposal";
3227
        if ($this->id > 0) {
3228
            $sql.= " AND d.fk_product =".$this->id;
3229
        } else {
3230
            $sql.=" AND d.fk_product > 0";
3231
        }
3232
        if ($filteronproducttype >= 0) {
3233
            $sql.= " AND prod.rowid = d.fk_product AND prod.fk_product_type =".$filteronproducttype;
3234
        }
3235
        $sql.= " AND p.fk_soc = s.rowid";
3236
        $sql.= " AND p.entity IN (".getEntity('supplier_proposal').")";
3237
        if (!$user->rights->societe->client->voir && !$socid) {
3238
            $sql.= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
3239
        }
3240
        if ($socid > 0) {
3241
            $sql.= " AND p.fk_soc = ".$socid;
3242
        }
3243
        $sql.=$morefilter;
3244
        $sql.= " GROUP BY date_format(p.date_valid,'%Y%m')";
3245
        $sql.= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
3246
3247
        return $this->_get_stats($sql, $mode, $year);
3248
    }
3249
3250
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3251
    /**
3252
     *  Return nb of units or orders in which product is included
3253
     *
3254
     * @param  int    $socid               Limit count on a particular third party id
3255
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
3256
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
3257
     * @param  int    $year                Year (0=last 12 month)
3258
     * @param  string $morefilter          More sql filters
3259
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
3260
     */
3261
    public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3262
    {
3263
        // phpcs:enable
3264
        global $conf, $user;
3265
3266
        $sql = "SELECT sum(d.qty), date_format(c.date_commande, '%Y%m')";
3267
        if ($mode == 'bynumber') {
3268
            $sql.= ", count(DISTINCT c.rowid)";
3269
        }
3270
        $sql.= " FROM ".MAIN_DB_PREFIX."commandedet as d, ".MAIN_DB_PREFIX."commande as c, ".MAIN_DB_PREFIX."societe as s";
3271
        if ($filteronproducttype >= 0) {
3272
            $sql.=", ".MAIN_DB_PREFIX."product as p";
3273
        }
3274
        if (!$user->rights->societe->client->voir && !$socid) {
3275
            $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3276
        }
3277
        $sql.= " WHERE c.rowid = d.fk_commande";
3278
        if ($this->id > 0) {
3279
            $sql.= " AND d.fk_product =".$this->id;
3280
        } else {
3281
            $sql.=" AND d.fk_product > 0";
3282
        }
3283
        if ($filteronproducttype >= 0) {
3284
            $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
3285
        }
3286
        $sql.= " AND c.fk_soc = s.rowid";
3287
        $sql.= " AND c.entity IN (".getEntity('commande').")";
3288
        if (!$user->rights->societe->client->voir && !$socid) {
3289
            $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
3290
        }
3291
        if ($socid > 0) {
3292
            $sql.= " AND c.fk_soc = ".$socid;
3293
        }
3294
        $sql.=$morefilter;
3295
        $sql.= " GROUP BY date_format(c.date_commande,'%Y%m')";
3296
        $sql.= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
3297
3298
        return $this->_get_stats($sql, $mode, $year);
3299
    }
3300
3301
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3302
    /**
3303
     *  Return nb of units or orders in which product is included
3304
     *
3305
     * @param  int    $socid               Limit count on a particular third party id
3306
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
3307
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
3308
     * @param  int    $year                Year (0=last 12 month)
3309
     * @param  string $morefilter          More sql filters
3310
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
3311
     */
3312
    public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3313
    {
3314
        // phpcs:enable
3315
        global $conf, $user;
3316
3317
        $sql = "SELECT sum(d.qty), date_format(c.date_commande, '%Y%m')";
3318
        if ($mode == 'bynumber') {
3319
            $sql.= ", count(DISTINCT c.rowid)";
3320
        }
3321
        $sql.= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as d, ".MAIN_DB_PREFIX."commande_fournisseur as c, ".MAIN_DB_PREFIX."societe as s";
3322
        if ($filteronproducttype >= 0) {
3323
            $sql.=", ".MAIN_DB_PREFIX."product as p";
3324
        }
3325
        if (!$user->rights->societe->client->voir && !$socid) {
3326
            $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3327
        }
3328
        $sql.= " WHERE c.rowid = d.fk_commande";
3329
        if ($this->id > 0) {
3330
            $sql.= " AND d.fk_product =".$this->id;
3331
        } else {
3332
            $sql.=" AND d.fk_product > 0";
3333
        }
3334
        if ($filteronproducttype >= 0) {
3335
            $sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
3336
        }
3337
        $sql.= " AND c.fk_soc = s.rowid";
3338
        $sql.= " AND c.entity IN (".getEntity('supplier_order').")";
3339
        if (!$user->rights->societe->client->voir && !$socid) {
3340
            $sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
3341
        }
3342
        if ($socid > 0) {
3343
            $sql.= " AND c.fk_soc = ".$socid;
3344
        }
3345
        $sql.=$morefilter;
3346
        $sql.= " GROUP BY date_format(c.date_commande,'%Y%m')";
3347
        $sql.= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
3348
3349
        return $this->_get_stats($sql, $mode, $year);
3350
    }
3351
3352
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3353
    /**
3354
     *  Return nb of units or orders in which product is included
3355
     *
3356
     * @param  int    $socid               Limit count on a particular third party id
3357
     * @param  string $mode                'byunit'=number of unit, 'bynumber'=nb of entities
3358
     * @param  int    $filteronproducttype 0=To filter on product only, 1=To filter on services only
3359
     * @param  int    $year                Year (0=last 12 month)
3360
     * @param  string $morefilter          More sql filters
3361
     * @return array                            <0 if KO, result[month]=array(valuex,valuey) where month is 0 to 11
3362
     */
3363
    public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3364
    {
3365
    	// phpcs:enable
3366
    	global $conf, $user;
3367
3368
    	$sql = "SELECT sum(d.qty), date_format(c.date_contrat, '%Y%m')";
3369
    	if ($mode == 'bynumber') {
3370
    		$sql.= ", count(DISTINCT c.rowid)";
3371
    	}
3372
    	$sql.= " FROM ".MAIN_DB_PREFIX."contratdet as d, ".MAIN_DB_PREFIX."contrat as c, ".MAIN_DB_PREFIX."societe as s";
3373
    	if ($filteronproducttype >= 0) {
3374
    		$sql.=", ".MAIN_DB_PREFIX."product as p";
3375
    	}
3376
    	if (!$user->rights->societe->client->voir && !$socid) {
3377
    		$sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3378
    	}
3379
3380
		$sql.= " WHERE c.entity IN (".getEntity('contract').")";
3381
		$sql.= " AND c.rowid = d.fk_contrat";
3382
3383
    	if ($this->id > 0) {
3384
    		$sql.= " AND d.fk_product =".$this->id;
3385
    	} else {
3386
    		$sql.=" AND d.fk_product > 0";
3387
    	}
3388
    	if ($filteronproducttype >= 0) {
3389
    		$sql.= " AND p.rowid = d.fk_product AND p.fk_product_type =".$filteronproducttype;
3390
    	}
3391
    	$sql.= " AND c.fk_soc = s.rowid";
3392
3393
    	if (!$user->rights->societe->client->voir && !$socid) {
3394
    		$sql.= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = " .$user->id;
3395
    	}
3396
    	if ($socid > 0) {
3397
    		$sql.= " AND c.fk_soc = ".$socid;
3398
    	}
3399
    	$sql.=$morefilter;
3400
    	$sql.= " GROUP BY date_format(c.date_contrat,'%Y%m')";
3401
    	$sql.= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
3402
3403
    	return $this->_get_stats($sql, $mode, $year);
3404
    }
3405
3406
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3407
    /**
3408
     *  Link a product/service to a parent product/service
3409
     *
3410
     * @param  int $id_pere Id of parent product/service
3411
     * @param  int $id_fils Id of child product/service
3412
     * @param  int $qty     Quantity
3413
     * @param  int $incdec  1=Increase/decrease stock of child when parent stock increase/decrease
3414
     * @return int                < 0 if KO, > 0 if OK
3415
     */
3416
    public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1)
3417
    {
3418
        // phpcs:enable
3419
        // Clean parameters
3420
        if (! is_numeric($id_pere)) {
1 ignored issue
show
introduced by
The condition is_numeric($id_pere) is always true.
Loading history...
3421
            $id_pere=0;
3422
        }
3423
        if (! is_numeric($id_fils)) {
1 ignored issue
show
introduced by
The condition is_numeric($id_fils) is always true.
Loading history...
3424
            $id_fils=0;
3425
        }
3426
        if (! is_numeric($incdec)) {
1 ignored issue
show
introduced by
The condition is_numeric($incdec) is always true.
Loading history...
3427
            $incdec=0;
3428
        }
3429
3430
        $result=$this->del_sousproduit($id_pere, $id_fils);
3431
        if ($result < 0) {
3432
            return $result;
3433
        }
3434
3435
        // Check not already father of id_pere (to avoid father -> child -> father links)
3436
        $sql = 'SELECT fk_product_pere from '.MAIN_DB_PREFIX.'product_association';
3437
        $sql .= ' WHERE fk_product_pere  = '.$id_fils.' AND fk_product_fils = '.$id_pere;
3438
        if (! $this->db->query($sql)) {
3439
            dol_print_error($this->db);
3440
            return -1;
3441
        }
3442
        else
3443
        {
3444
            $result = $this->db->query($sql);
3445
            if ($result) {
3446
                $num = $this->db->num_rows($result);
3447
                if($num > 0) {
3448
                    $this->error="isFatherOfThis";
3449
                    return -1;
3450
                }
3451
                else
3452
                {
3453
                    $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'product_association(fk_product_pere,fk_product_fils,qty,incdec)';
3454
                    $sql .= ' VALUES ('.$id_pere.', '.$id_fils.', '.$qty.', '.$incdec.')';
3455
                    if (! $this->db->query($sql)) {
3456
                         dol_print_error($this->db);
3457
                         return -1;
3458
                    }
3459
                    else
3460
                    {
3461
                         return 1;
3462
                    }
3463
                }
3464
            }
3465
        }
3466
    }
3467
3468
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3469
    /**
3470
     *  Modify composed product
3471
     *
3472
     * @param  int $id_pere Id of parent product/service
3473
     * @param  int $id_fils Id of child product/service
3474
     * @param  int $qty     Quantity
3475
     * @param  int $incdec  1=Increase/decrease stock of child when parent stock increase/decrease
3476
     * @return int                < 0 if KO, > 0 if OK
3477
     */
3478
    public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1)
3479
    {
3480
        // phpcs:enable
3481
        // Clean parameters
3482
        if (! is_numeric($id_pere)) {
1 ignored issue
show
introduced by
The condition is_numeric($id_pere) is always true.
Loading history...
3483
            $id_pere=0;
3484
        }
3485
        if (! is_numeric($id_fils)) {
1 ignored issue
show
introduced by
The condition is_numeric($id_fils) is always true.
Loading history...
3486
            $id_fils=0;
3487
        }
3488
        if (! is_numeric($incdec)) {
1 ignored issue
show
introduced by
The condition is_numeric($incdec) is always true.
Loading history...
3489
            $incdec=1;
3490
        }
3491
        if (! is_numeric($qty)) {
1 ignored issue
show
introduced by
The condition is_numeric($qty) is always true.
Loading history...
3492
            $qty=1;
3493
        }
3494
3495
        $sql = 'UPDATE '.MAIN_DB_PREFIX.'product_association SET ';
3496
        $sql.= 'qty='.$qty;
3497
        $sql.= ',incdec='.$incdec;
3498
        $sql .= ' WHERE fk_product_pere='.$id_pere.' AND fk_product_fils='.$id_fils;
3499
3500
        if (!$this->db->query($sql)) {
3501
            dol_print_error($this->db);
3502
            return -1;
3503
        }
3504
        else
3505
        {
3506
            return 1;
3507
        }
3508
    }
3509
3510
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3511
    /**
3512
     *  Retire le lien entre un sousproduit et un produit/service
3513
     *
3514
     * @param  int $fk_parent Id du produit auquel ne sera plus lie le produit lie
3515
     * @param  int $fk_child  Id du produit a ne plus lie
3516
     * @return int                    < 0 if KO, > 0 if OK
3517
     */
3518
    public function del_sousproduit($fk_parent, $fk_child)
3519
    {
3520
        // phpcs:enable
3521
        if (! is_numeric($fk_parent)) {
1 ignored issue
show
introduced by
The condition is_numeric($fk_parent) is always true.
Loading history...
3522
            $fk_parent=0;
3523
        }
3524
        if (! is_numeric($fk_child)) {
1 ignored issue
show
introduced by
The condition is_numeric($fk_child) is always true.
Loading history...
3525
            $fk_child=0;
3526
        }
3527
3528
        $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_association";
3529
        $sql.= " WHERE fk_product_pere  = ".$fk_parent;
3530
        $sql.= " AND fk_product_fils = ".$fk_child;
3531
3532
        dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
3533
        if (! $this->db->query($sql)) {
3534
            dol_print_error($this->db);
3535
            return -1;
3536
        }
3537
3538
        return 1;
3539
    }
3540
3541
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3542
    /**
3543
     *  Verifie si c'est un sous-produit
3544
     *
3545
     * @param  int $fk_parent Id du produit auquel le produit est lie
3546
     * @param  int $fk_child  Id du produit lie
3547
     * @return int                    < 0 si erreur, > 0 si ok
3548
     */
3549
    public function is_sousproduit($fk_parent, $fk_child)
3550
    {
3551
        // phpcs:enable
3552
        $sql = "SELECT fk_product_pere, qty, incdec";
3553
        $sql.= " FROM ".MAIN_DB_PREFIX."product_association";
3554
        $sql.= " WHERE fk_product_pere  = '".$fk_parent."'";
3555
        $sql.= " AND fk_product_fils = '".$fk_child."'";
3556
3557
        $result = $this->db->query($sql);
3558
        if ($result) {
3559
            $num = $this->db->num_rows($result);
3560
3561
            if($num > 0) {
3562
                $obj = $this->db->fetch_object($result);
3563
                $this->is_sousproduit_qty = $obj->qty;
3564
                $this->is_sousproduit_incdec = $obj->incdec;
3565
3566
                return true;
3567
            }
3568
            else
3569
            {
3570
                return false;
3571
            }
3572
        }
3573
        else
3574
        {
3575
            dol_print_error($this->db);
3576
            return -1;
3577
        }
3578
    }
3579
3580
3581
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3582
    /**
3583
     *  Add a supplier price for the product.
3584
     *  Note: Duplicate ref is accepted for different quantity only, or for different companies.
3585
     *
3586
     * @param  User   $user      User that make link
3587
     * @param  int    $id_fourn  Supplier id
3588
     * @param  string $ref_fourn Supplier ref
3589
     * @param  float  $quantity  Quantity minimum for price
3590
     * @return int               < 0 if KO, 0 if link already exists for this product, > 0 if OK
3591
     */
3592
    public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
3593
    {
3594
        // phpcs:enable
3595
        global $conf;
3596
3597
        $now=dol_now();
3598
3599
        dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
3600
3601
        // Clean parameters
3602
        $quantity = price2num($quantity, 'MS');
3603
3604
        if ($ref_fourn) {
3605
            $sql = "SELECT rowid, fk_product";
3606
            $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
3607
            $sql.= " WHERE fk_soc = ".$id_fourn;
3608
            $sql.= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
3609
            $sql.= " AND fk_product != ".$this->id;
3610
            $sql.= " AND entity IN (".getEntity('productsupplierprice').")";
3611
3612
            $resql=$this->db->query($sql);
3613
            if ($resql) {
3614
                $obj = $this->db->fetch_object($resql);
3615
                if ($obj) {
3616
                    // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
3617
                    $this->product_id_already_linked = $obj->fk_product;
3618
                    return -3;
3619
                }
3620
                $this->db->free($resql);
3621
            }
3622
        }
3623
3624
        $sql = "SELECT rowid";
3625
        $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
3626
        $sql.= " WHERE fk_soc = ".$id_fourn;
3627
        if ($ref_fourn) { $sql.= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
3628
        } else { $sql.= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
3629
        }
3630
        $sql.= " AND quantity = ".$quantity;
3631
        $sql.= " AND fk_product = ".$this->id;
3632
        $sql.= " AND entity IN (".getEntity('productsupplierprice').")";
3633
3634
        $resql=$this->db->query($sql);
3635
        if ($resql) {
3636
            $obj = $this->db->fetch_object($resql);
3637
3638
            // The reference supplier does not exist, we create it for this product.
3639
            if (! $obj) {
3640
                $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_fournisseur_price(";
3641
                $sql.= "datec";
3642
                $sql.= ", entity";
3643
                $sql.= ", fk_product";
3644
                $sql.= ", fk_soc";
3645
                $sql.= ", ref_fourn";
3646
                $sql.= ", quantity";
3647
                $sql.= ", fk_user";
3648
                $sql.= ", tva_tx";
3649
                $sql.= ") VALUES (";
3650
                $sql.= "'".$this->db->idate($now)."'";
3651
                $sql.= ", ".$conf->entity;
3652
                $sql.= ", ".$this->id;
3653
                $sql.= ", ".$id_fourn;
3654
                $sql.= ", '".$this->db->escape($ref_fourn)."'";
3655
                $sql.= ", ".$quantity;
3656
                $sql.= ", ".$user->id;
3657
                $sql.= ", 0";
3658
                $sql.= ")";
3659
3660
                if ($this->db->query($sql)) {
3661
                    $this->product_fourn_price_id = $this->db->last_insert_id(MAIN_DB_PREFIX."product_fournisseur_price");
3662
                    return 1;
3663
                }
3664
                else
3665
                {
3666
                    $this->error=$this->db->lasterror();
3667
                    return -1;
3668
                }
3669
            }
3670
            // If the supplier price already exists for this product and quantity
3671
            else
3672
            {
3673
                $this->product_fourn_price_id = $obj->rowid;
3674
                return 0;
3675
            }
3676
        }
3677
        else
3678
        {
3679
            $this->error=$this->db->lasterror();
3680
            return -2;
3681
        }
3682
    }
3683
3684
3685
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3686
    /**
3687
     *  Renvoie la liste des fournisseurs du produit/service
3688
     *
3689
     * @return array        Tableau des id de fournisseur
3690
     */
3691
    public function list_suppliers()
3692
    {
3693
        // phpcs:enable
3694
        global $conf;
3695
3696
        $list = array();
3697
3698
        $sql = "SELECT DISTINCT p.fk_soc";
3699
        $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as p";
3700
        $sql.= " WHERE p.fk_product = ".$this->id;
3701
        $sql.= " AND p.entity = ".$conf->entity;
3702
3703
        $result = $this->db->query($sql);
3704
        if ($result) {
3705
            $num = $this->db->num_rows($result);
3706
            $i=0;
3707
            while ($i < $num)
3708
            {
3709
                $obj = $this->db->fetch_object($result);
3710
                $list[$i] = $obj->fk_soc;
3711
                $i++;
3712
            }
3713
        }
3714
3715
        return $list;
3716
    }
3717
3718
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3719
    /**
3720
     *  Recopie les prix d'un produit/service sur un autre
3721
     *
3722
     * @param  int $fromId Id product source
3723
     * @param  int $toId   Id product target
3724
     * @return int                     < 0 if KO, > 0 if OK
3725
     */
3726
    public function clone_price($fromId, $toId)
3727
    {
3728
        global $conf, $user;
3729
3730
        $now = dol_now();
3731
3732
        $this->db->begin();
3733
3734
        // prices
3735
        $sql  = "INSERT INTO " . MAIN_DB_PREFIX . "product_price (";
3736
        $sql .= " entity";
3737
        $sql .= ", fk_product";
3738
        $sql .= ", date_price";
3739
        $sql .= ", price_level";
3740
        $sql .= ", price";
3741
        $sql .= ", price_ttc";
3742
        $sql .= ", price_min";
3743
        $sql .= ", price_min_ttc";
3744
        $sql .= ", price_base_type";
3745
        $sql .= ", default_vat_code";
3746
        $sql .= ", tva_tx";
3747
        $sql .= ", recuperableonly";
3748
        $sql .= ", localtax1_tx";
3749
        $sql .= ", localtax1_type";
3750
        $sql .= ", localtax2_tx";
3751
        $sql .= ", localtax2_type";
3752
        $sql .= ", fk_user_author";
3753
        $sql .= ", tosell";
3754
        $sql .= ", price_by_qty";
3755
        $sql .= ", fk_price_expression";
3756
        $sql .= ", fk_multicurrency";
3757
        $sql .= ", multicurrency_code";
3758
        $sql .= ", multicurrency_tx";
3759
        $sql .= ", multicurrency_price";
3760
        $sql .= ", multicurrency_price_ttc";
3761
        $sql .= ")";
3762
        $sql .= " SELECT";
3763
        $sql .= " entity";
3764
        $sql .= ", " . $toId;
3765
        $sql .= ", '" . $this->db->idate($now) . "'";
3766
        $sql .= ", price_level";
3767
        $sql .= ", price";
3768
        $sql .= ", price_ttc";
3769
        $sql .= ", price_min";
3770
        $sql .= ", price_min_ttc";
3771
        $sql .= ", price_base_type";
3772
        $sql .= ", default_vat_code";
3773
        $sql .= ", tva_tx";
3774
        $sql .= ", recuperableonly";
3775
        $sql .= ", localtax1_tx";
3776
        $sql .= ", localtax1_type";
3777
        $sql .= ", localtax2_tx";
3778
        $sql .= ", localtax2_type";
3779
        $sql .= ", " . $user->id;
3780
        $sql .= ", tosell";
3781
        $sql .= ", price_by_qty";
3782
        $sql .= ", fk_price_expression";
3783
        $sql .= ", fk_multicurrency";
3784
        $sql .= ", multicurrency_code";
3785
        $sql .= ", multicurrency_tx";
3786
        $sql .= ", multicurrency_price";
3787
        $sql .= ", multicurrency_price_ttc";
3788
        $sql .= " FROM " . MAIN_DB_PREFIX . "product_price";
3789
        $sql .= " WHERE fk_product = ". $fromId;
3790
        $sql .= " ORDER BY date_price DESC";
3791
        if ($conf->global->PRODUIT_MULTIPRICES_LIMIT>0) {
3792
            $sql .= " LIMIT " . $conf->global->PRODUIT_MULTIPRICES_LIMIT;
3793
        }
3794
3795
        dol_syslog(__METHOD__, LOG_DEBUG);
3796
        $resql = $this->db->query($sql);
3797
        if (!$resql) {
3798
            $this->db->rollback();
3799
            return -1;
3800
        }
3801
3802
        $this->db->commit();
3803
        return 1;
3804
    }
3805
3806
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3807
    /**
3808
     * Clone links between products
3809
     *
3810
     * @param  int $fromId Product id
3811
     * @param  int $toId   Product id
3812
     * @return int                  <0 if KO, >0 if OK
3813
     */
3814
    public function clone_associations($fromId, $toId)
3815
    {
3816
        // phpcs:enable
3817
        $this->db->begin();
3818
3819
        $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'product_association (fk_product_pere, fk_product_fils, qty)';
3820
        $sql.= " SELECT ".$toId.", fk_product_fils, qty FROM ".MAIN_DB_PREFIX."product_association";
3821
        $sql.= " WHERE fk_product_pere = ".$fromId;
3822
3823
        dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
3824
        if (! $this->db->query($sql)) {
3825
            $this->db->rollback();
3826
            return -1;
3827
        }
3828
3829
        $this->db->commit();
3830
        return 1;
3831
    }
3832
3833
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3834
    /**
3835
     *  Recopie les fournisseurs et prix fournisseurs d'un produit/service sur un autre
3836
     *
3837
     * @param  int $fromId Id produit source
3838
     * @param  int $toId   Id produit cible
3839
     * @return int                 < 0 si erreur, > 0 si ok
3840
     */
3841
    public function clone_fournisseurs($fromId, $toId)
3842
    {
3843
        // phpcs:enable
3844
        $this->db->begin();
3845
3846
        $now=dol_now();
3847
3848
        // les fournisseurs
3849
        /*$sql = "INSERT ".MAIN_DB_PREFIX."product_fournisseur ("
3850
        . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
3851
        . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
3852
        . " FROM ".MAIN_DB_PREFIX."product_fournisseur"
3853
        . " WHERE fk_product = ".$fromId;
3854
3855
        if ( ! $this->db->query($sql ) )
3856
        {
3857
        $this->db->rollback();
3858
        return -1;
3859
        }*/
3860
3861
        // les prix de fournisseurs.
3862
        $sql = "INSERT ".MAIN_DB_PREFIX."product_fournisseur_price (";
3863
        $sql.= " datec, fk_product, fk_soc, price, quantity, fk_user)";
3864
        $sql.= " SELECT '".$this->db->idate($now)."', ".$toId. ", fk_soc, price, quantity, fk_user";
3865
        $sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
3866
        $sql.= " WHERE fk_product = ".$fromId;
3867
3868
        dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
3869
        $resql=$this->db->query($sql);
3870
        if (! $resql) {
3871
            $this->db->rollback();
3872
            return -1;
3873
        }
3874
        else
3875
        {
3876
            $this->db->commit();
3877
            return 1;
3878
        }
3879
    }
3880
3881
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3882
    /**
3883
     *  Fonction recursive uniquement utilisee par get_arbo_each_prod, recompose l'arborescence des sousproduits
3884
     *  Define value of this->res
3885
     *
3886
     * @param  array  $prod       Products array
3887
     * @param  string $compl_path Directory path of parents to add before
3888
     * @param  int    $multiply   Because each sublevel must be multiplicated by parent nb
3889
     * @param  int    $level      Init level
3890
     * @param  int    $id_parent  Id parent
3891
     * @return void
3892
     */
3893
    public function fetch_prod_arbo($prod, $compl_path = "", $multiply = 1, $level = 1, $id_parent = 0)
3894
    {
3895
        // phpcs:enable
3896
        global $conf,$langs;
3897
3898
        $tmpproduct = null;
3899
        //var_dump($prod);
3900
        foreach($prod as $id_product => $desc_pere)    // $id_product is 0 (first call starting with root top) or an id of a sub_product
3901
        {
3902
            if (is_array($desc_pere))    // If desc_pere is an array, this means it's a child
3903
            {
3904
                $id=(! empty($desc_pere[0]) ? $desc_pere[0] :'');
3905
                $nb=(! empty($desc_pere[1]) ? $desc_pere[1] :'');
3906
                $type=(! empty($desc_pere[2]) ? $desc_pere[2] :'');
3907
                $label=(! empty($desc_pere[3]) ? $desc_pere[3] :'');
3908
                $incdec=!empty($desc_pere[4]) ? $desc_pere[4] : 0;
3909
3910
                if ($multiply < 1) { $multiply=1;
3911
                }
3912
3913
                //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
3914
                if (is_null($tmpproduct)) $tmpproduct = new Product($this->db);		// So we initialize tmpproduct only once for all loop.
3915
                $tmpproduct->fetch($id);        				 // Load product to get ->ref
3916
                $tmpproduct->load_stock('nobatch,novirtual');    // Load stock to get true ->stock_reel
3917
                //$this->fetch($id);        				   // Load product to get ->ref
3918
                //$this->load_stock('nobatch,novirtual');    // Load stock to get true ->stock_reel
3919
                $this->res[]= array(
3920
	                'id'=>$id,                    // Id product
3921
	                'id_parent'=>$id_parent,
3922
                	'ref'=>$tmpproduct->ref,            // Ref product
3923
	                'nb'=>$nb,                    // Nb of units that compose parent product
3924
	                'nb_total'=>$nb*$multiply,    // Nb of units for all nb of product
3925
                	'stock'=>$tmpproduct->stock_reel,    // Stock
3926
                	'stock_alert'=>$tmpproduct->seuil_stock_alerte,    // Stock alert
3927
	                'label'=>$label,
3928
	                'fullpath'=>$compl_path.$label,            // Label
3929
	                'type'=>$type,                // Nb of units that compose parent product
3930
                	'desiredstock'=>$tmpproduct->desiredstock,
3931
	                'level'=>$level,
3932
	                'incdec'=>$incdec,
3933
                	'entity'=>$tmpproduct->entity
3934
                );
3935
3936
                // Recursive call if there is childs to child
3937
                if (is_array($desc_pere['childs'])) {
3938
                       //print 'YYY We go down for '.$desc_pere[3]." -> \n";
3939
                       $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1]*$multiply, $level+1, $id);
3940
                }
3941
            }
3942
        }
3943
    }
3944
3945
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3946
    /**
3947
     *  Build the tree of subproducts into an array
3948
     *  this->sousprods is loaded by this->get_sousproduits_arbo()
3949
     *
3950
     * @param  int $multiply Because each sublevel must be multiplicated by parent nb
3951
     * @return array                     $this->res
3952
     */
3953
    public function get_arbo_each_prod($multiply = 1)
3954
    {
3955
        // phpcs:enable
3956
        $this->res = array();
3957
        if (isset($this->sousprods) && is_array($this->sousprods)) {
3958
            foreach($this->sousprods as $prod_name => $desc_product)
3959
            {
3960
                if (is_array($desc_product)) { $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id);
3961
                }
3962
            }
3963
        }
3964
        //var_dump($this->res);
3965
        return $this->res;
3966
    }
3967
3968
    /**
3969
     *  Return all parent products for current product (first level only)
3970
     *
3971
     * @return int            Nb of father + child
3972
     */
3973
    public function hasFatherOrChild()
3974
    {
3975
        $nb = 0;
3976
3977
        $sql = "SELECT COUNT(pa.rowid) as nb";
3978
        $sql.= " FROM ".MAIN_DB_PREFIX."product_association as pa";
3979
        $sql.= " WHERE pa.fk_product_fils = ".$this->id." OR pa.fk_product_pere = ".$this->id;
3980
        $resql = $this->db->query($sql);
3981
        if ($resql) {
3982
            $obj = $this->db->fetch_object($resql);
3983
            if ($obj) { $nb = $obj->nb;
3984
            }
3985
        }
3986
        else
3987
        {
3988
            return -1;
3989
        }
3990
3991
        return $nb;
3992
    }
3993
3994
    /**
3995
     * Return if a product has variants or not
3996
     *
3997
     * @return int        Number of variants
3998
     */
3999
    public function hasVariants()
4000
    {
4001
        $nb = 0;
4002
        $sql = "SELECT count(rowid) as nb FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_parent = ".$this->id;
4003
        $sql.= " AND entity IN (".getEntity('product').")";
4004
4005
        $resql = $this->db->query($sql);
4006
        if ($resql) {
4007
            $obj = $this->db->fetch_object($resql);
4008
            if ($obj) { $nb = $obj->nb;
4009
            }
4010
        }
4011
4012
        return $nb;
4013
    }
4014
4015
4016
    /**
4017
     * Return if loaded product is a variant
4018
     *
4019
     * @return int
4020
     */
4021
    public function isVariant()
4022
    {
4023
        global $conf;
4024
        if (!empty($conf->variants->enabled)) {
4025
            $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "product_attribute_combination WHERE fk_product_child = " . $this->id . " AND entity IN (" . getEntity('product') . ")";
4026
4027
            $query = $this->db->query($sql);
4028
4029
            if ($query) {
4030
                if (!$this->db->num_rows($query)) {
4031
                    return false;
4032
                }
4033
                return true;
4034
            } else {
4035
                dol_print_error($this->db);
4036
                return -1;
4037
            }
4038
        } else {
4039
            return false;
4040
        }
4041
    }
4042
4043
    /**
4044
     *  Return all parent products for current product (first level only)
4045
     *
4046
     * @return array         Array of product
4047
     */
4048
    public function getFather()
4049
    {
4050
        $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";
4051
        $sql.= " FROM ".MAIN_DB_PREFIX."product_association as pa,";
4052
        $sql.= " ".MAIN_DB_PREFIX."product as p";
4053
        $sql.= " WHERE p.rowid = pa.fk_product_pere";
4054
        $sql.= " AND pa.fk_product_fils = ".$this->id;
4055
4056
        $res = $this->db->query($sql);
4057
        if ($res) {
4058
            $prods = array ();
4059
            while ($record = $this->db->fetch_array($res))
4060
            {
4061
                // $record['id'] = $record['rowid'] = id of father
4062
                $prods[$record['id']]['id'] = $record['rowid'];
4063
                $prods[$record['id']]['ref'] = $record['ref'];
4064
                $prods[$record['id']]['label'] = $record['label'];
4065
                $prods[$record['id']]['qty'] = $record['qty'];
4066
                $prods[$record['id']]['incdec'] = $record['incdec'];
4067
                $prods[$record['id']]['fk_product_type'] =  $record['fk_product_type'];
4068
                $prods[$record['id']]['entity'] =  $record['entity'];
4069
            }
4070
            return $prods;
4071
        }
4072
        else
4073
        {
4074
            dol_print_error($this->db);
4075
            return -1;
4076
        }
4077
    }
4078
4079
4080
    /**
4081
     *  Return childs of product $id
4082
     *
4083
     * @param  int $id             Id of product to search childs of
4084
     * @param  int $firstlevelonly Return only direct child
4085
     * @param  int $level          Level of recursing call (start to 1)
4086
     * @return array                       Return array(prodid=>array(0=prodid, 1=>qty, 2=> ...)
4087
     */
4088
    public function getChildsArbo($id, $firstlevelonly = 0, $level = 1)
4089
    {
4090
        global $alreadyfound;
4091
4092
        $sql = "SELECT p.rowid, p.label as label, pa.qty as qty, pa.fk_product_fils as id, p.fk_product_type, pa.incdec";
4093
        $sql.= " FROM ".MAIN_DB_PREFIX."product as p";
4094
        $sql.= ", ".MAIN_DB_PREFIX."product_association as pa";
4095
        $sql.= " WHERE p.rowid = pa.fk_product_fils";
4096
        $sql.= " AND pa.fk_product_pere = ".$id;
4097
        $sql.= " AND pa.fk_product_fils != ".$id;    // This should not happens, it is to avoid infinite loop if it happens
4098
4099
        dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level, LOG_DEBUG);
4100
4101
        if ($level == 1) { $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
4102
        }
4103
        // Protection against infinite loop
4104
        if ($level > 30) { return array();
4105
        }
4106
4107
        $res  = $this->db->query($sql);
4108
        if ($res) {
4109
            $prods = array();
4110
            while ($rec = $this->db->fetch_array($res))
4111
            {
4112
                if (! empty($alreadyfound[$rec['rowid']])) {
4113
                    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);
4114
                    continue;
4115
                }
4116
                $alreadyfound[$rec['rowid']]=1;
4117
                $prods[$rec['rowid']]= array(
4118
                 0=>$rec['rowid'],
4119
                 1=>$rec['qty'],
4120
                 2=>$rec['fk_product_type'],
4121
                 3=>$this->db->escape($rec['label']),
4122
                 4=>$rec['incdec']
4123
                );
4124
                //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
4125
                //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
4126
                if (empty($firstlevelonly)) {
4127
                       $listofchilds=$this->getChildsArbo($rec['rowid'], 0, $level + 1);
4128
                    foreach($listofchilds as $keyChild => $valueChild)
4129
                       {
4130
                        $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
4131
                    }
4132
                }
4133
            }
4134
4135
            return $prods;
4136
        }
4137
        else
4138
        {
4139
            dol_print_error($this->db);
4140
            return -1;
4141
        }
4142
    }
4143
4144
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4145
    /**
4146
     *     Return tree of all subproducts for product. Tree contains id, name and quantity.
4147
     *     Set this->sousprods
4148
     *
4149
     * @return void
4150
     */
4151
    public function get_sousproduits_arbo()
4152
    {
4153
        // phpcs:enable
4154
        $parent=array();
4155
4156
        foreach($this->getChildsArbo($this->id) as $keyChild => $valueChild)    // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
4157
        {
4158
            $parent[$this->label][$keyChild] = $valueChild;
4159
        }
4160
        foreach($parent as $key => $value)        // key=label, value is array of childs
4161
        {
4162
            $this->sousprods[$key] = $value;
4163
        }
4164
    }
4165
4166
    /**
4167
     *    Return clicable link of object (with eventually picto)
4168
     *
4169
     * @param  int    $withpicto             Add picto into link
4170
     * @param  string $option                Where point the link ('stock', 'composition', 'category', 'supplier', '')
4171
     * @param  int    $maxlength             Maxlength of ref
4172
     * @param  int    $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
4173
     * @param  int    $notooltip			 No tooltip
4174
     * @return string                                String with URL
4175
     */
4176
    public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0)
4177
    {
4178
        global $conf, $langs, $hookmanager;
4179
        include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
4180
4181
        $result='';
4182
        $newref=$this->ref;
4183
        if ($maxlength) { $newref=dol_trunc($newref, $maxlength, 'middle');
4184
        }
4185
4186
        if ($this->type == Product::TYPE_PRODUCT) { $label = '<u>' . $langs->trans("ShowProduct") . '</u>';
4187
        }
4188
        if ($this->type == Product::TYPE_SERVICE) { $label = '<u>' . $langs->trans("ShowService") . '</u>';
4189
        }
4190
        if (! empty($this->ref)) {
4191
            $label .= '<br><b>' . $langs->trans('ProductRef') . ':</b> ' . $this->ref;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $label does not seem to be defined for all execution paths leading up to this point.
Loading history...
4192
        }
4193
        if (! empty($this->label)) {
4194
            $label .= '<br><b>' . $langs->trans('ProductLabel') . ':</b> ' . $this->label;
4195
        }
4196
4197
        if ($this->type == Product::TYPE_PRODUCT)
4198
        {
4199
            if ($this->weight) {
4200
                $label.="<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuring_units_string($this->weight_units, "weight");
4201
            }
4202
            if ($this->length) {
4203
                $label.="<br><b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuring_units_string($this->length_units, 'size');
4204
            }
4205
            if ($this->width) {
4206
                $label.="<br><b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuring_units_string($this->width_units, 'size');
4207
            }
4208
            if ($this->height) {
4209
                $label.="<br><b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuring_units_string($this->height_units, 'size');
4210
            }
4211
            if ($this->surface) {
4212
                $label.="<br><b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuring_units_string($this->surface_units, 'surface');
4213
            }
4214
            if ($this->volume) {
4215
                $label.="<br><b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuring_units_string($this->volume_units, 'volume');
4216
            }
4217
        }
4218
4219
        if ($this->type == Product::TYPE_PRODUCT || ! empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
4220
            if (! empty($conf->productbatch->enabled)) {
4221
                   $langs->load("productbatch");
4222
                $label.="<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
4223
            }
4224
        }
4225
        //if ($this->type == Product::TYPE_SERVICE)
4226
        //{
4227
            //
4228
        //}
4229
        if (! empty($conf->accounting->enabled) && $this->status) {
4230
            include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
4231
            $label.= '<br><b>' . $langs->trans('ProductAccountancySellCode') . ':</b> '. length_accountg($this->accountancy_code_sell);
4232
            if(!empty($conf->global->MAIN_FEATURES_LEVEL)) {
4233
                $label.= '<br><b>' . $langs->trans('ProductAccountancySellIntraCode') . ':</b> '. length_accountg($this->accountancy_code_sell_export);
4234
                $label.= '<br><b>' . $langs->trans('ProductAccountancySellExportCode') . ':</b> '. length_accountg($this->accountancy_code_sell_intra);
4235
            }
4236
        }
4237
        if (! empty($conf->accounting->enabled) && $this->status_buy) {
4238
            include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
4239
            $label.= '<br><b>' . $langs->trans('ProductAccountancyBuyCode') . ':</b> '. length_accountg($this->accountancy_code_buy);
4240
        }
4241
        if (! empty($this->entity)) {
4242
            $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80);
4243
            if ($this->nbphoto > 0) { $label .= '<br>' . $tmpphoto;
4244
            }
4245
        }
4246
4247
        $linkclose='';
4248
        if (empty($notooltip)) {
4249
            if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
4250
                $label=$langs->trans("ShowProduct");
4251
                $linkclose.=' alt="'.dol_escape_htmltag($label, 1).'"';
4252
            }
4253
4254
            $linkclose.= ' title="'.dol_escape_htmltag($label, 1, 1).'"';
4255
            $linkclose.= ' class="nowraponall classfortooltip"';
4256
        }
4257
        else
4258
        {
4259
        	$linkclose = ' class="nowraponall"';
4260
        }
4261
4262
        if ($option == 'supplier' || $option == 'category') {
4263
            $url = DOL_URL_ROOT.'/product/fournisseurs.php?id='.$this->id;
4264
        } elseif ($option == 'stock') {
4265
            $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
4266
        } elseif ($option == 'composition') {
4267
            $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
4268
        } else {
4269
            $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
4270
        }
4271
4272
        if ($option !== 'nolink') {
4273
            // Add param to save lastsearch_values or not
4274
            $add_save_lastsearch_values=($save_lastsearch_value == 1 ? 1 : 0);
4275
            if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) { $add_save_lastsearch_values=1;
4276
            }
4277
            if ($add_save_lastsearch_values) { $url.='&save_lastsearch_values=1';
4278
            }
4279
        }
4280
4281
        $linkstart = '<a href="'.$url.'"';
4282
        $linkstart.=$linkclose.'>';
4283
        $linkend='</a>';
4284
4285
        $result.=$linkstart;
4286
        if ($withpicto)
4287
        {
4288
            if ($this->type == Product::TYPE_PRODUCT) {
4289
            	$result.=(img_object(($notooltip?'':$label), 'product', ($notooltip?'class="paddingright"':'class="paddingright classfortooltip"'), 0, 0, $notooltip?0:1));
4290
            }
4291
            if ($this->type == Product::TYPE_SERVICE) {
4292
            	$result.=(img_object(($notooltip?'':$label), 'service', ($notooltip?'class="paddinright"':'class="paddingright classfortooltip"'), 0, 0, $notooltip?0:1));
4293
            }
4294
        }
4295
        $result.= $newref;
4296
        $result.= $linkend;
4297
4298
        global $action;
4299
        $hookmanager->initHooks(array('productdao'));
4300
        $parameters=array('id'=>$this->id, 'getnomurl'=>$result);
4301
        $reshook=$hookmanager->executeHooks('getNomUrl', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
4302
        if ($reshook > 0) {
4303
        	$result = $hookmanager->resPrint;
4304
        } else {
4305
        	$result .= $hookmanager->resPrint;
4306
        }
4307
4308
        return $result;
4309
    }
4310
4311
4312
    /**
4313
     *  Create a document onto disk according to template module.
4314
     *
4315
     * @param  string    $modele      Force model to use ('' to not force)
4316
     * @param  Translate $outputlangs Object langs to use for output
4317
     * @param  int       $hidedetails Hide details of lines
4318
     * @param  int       $hidedesc    Hide description
4319
     * @param  int       $hideref     Hide ref
4320
     * @return int                         0 if KO, 1 if OK
4321
     */
4322
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
4323
    {
4324
        global $conf,$user,$langs;
4325
4326
        $langs->load("products");
4327
4328
        // Positionne le modele sur le nom du modele a utiliser
4329
        if (! dol_strlen($modele)) {
4330
            if (! empty($conf->global->PRODUCT_ADDON_PDF)) {
4331
                $modele = $conf->global->PRODUCT_ADDON_PDF;
4332
            }
4333
            else
4334
            {
4335
                $modele = 'strato';
4336
            }
4337
        }
4338
4339
        $modelpath = "core/modules/product/doc/";
4340
4341
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
4342
    }
4343
4344
    /**
4345
     *    Return label of status of object
4346
     *
4347
     * @param  int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
4348
     * @param  int $type 0=Sell, 1=Buy, 2=Batch Number management
4349
     * @return string          Label of status
4350
     */
4351
    public function getLibStatut($mode = 0, $type = 0)
4352
    {
4353
        switch ($type)
4354
        {
4355
			case 0:
4356
            return $this->LibStatut($this->status, $mode, $type);
4357
			case 1:
4358
            return $this->LibStatut($this->status_buy, $mode, $type);
4359
			case 2:
4360
            return $this->LibStatut($this->status_batch, $mode, $type);
4361
			default:
4362
				//Simulate previous behavior but should return an error string
4363
            return $this->LibStatut($this->status_buy, $mode, $type);
4364
        }
4365
    }
4366
4367
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4368
    /**
4369
     *    Return label of a given status
4370
     *
4371
     * @param  int $status Statut
4372
     * @param  int $mode   0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
4373
     * @param  int $type   0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
4374
     * @return string              Label of status
4375
     */
4376
    public function LibStatut($status, $mode = 0, $type = 0)
4377
    {
4378
        // phpcs:enable
4379
        global $conf, $langs;
4380
4381
        $labelstatut = $labelstatutShort = '';
4382
4383
        $langs->load('products');
4384
        if (! empty($conf->productbatch->enabled)) { $langs->load("productbatch");
4385
        }
4386
4387
        if ($type == 2) {
4388
            switch ($mode)
4389
            {
4390
                case 0:
4391
                    $label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatch') : $langs->trans('ProductStatusOnBatch'));
4392
                    return dolGetStatus($label);
4393
                case 1:
4394
                    $label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatchShort') : $langs->trans('ProductStatusOnBatchShort'));
4395
                    return dolGetStatus($label);
4396
                case 2:
4397
                    return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
4398
                case 3:
4399
                    return dolGetStatus($langs->trans('ProductStatusNotOnBatch'), '', '', empty($status)?'status5':'status4', 3, 'dot');
4400
                case 4:
4401
                    return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
4402
                case 5:
4403
                    return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
4404
                default:
4405
                    return dolGetStatus($langs->trans('Unknown'));
4406
            }
4407
        }
4408
4409
        $statuttrans=empty($status)?'status5':'status4';
4410
4411
        if($status == 0){
4412
            // $type   0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
4413
            if($type==0){
4414
                $labelstatut = $langs->trans('ProductStatusNotOnSellShort');
4415
                $labelstatutShort = $langs->trans('ProductStatusNotOnSell');
4416
            }
4417
            elseif($type == 1){
4418
                $labelstatut = $langs->trans('ProductStatusNotOnBuyShort');
4419
                $labelstatutShort = $langs->trans('ProductStatusNotOnBuy');
4420
            }
4421
            elseif($type == 2){
4422
                $labelstatut = $langs->trans('ProductStatusNotOnBatch');
4423
                $labelstatutShort = $langs->trans('ProductStatusNotOnBatchShort');
4424
            }
4425
        }
4426
        elseif($status == 1){
4427
            // $type   0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
4428
            if($type==0){
4429
                $labelstatut = $langs->trans('ProductStatusOnSellShort');
4430
                $labelstatutShort = $langs->trans('ProductStatusOnSell');
4431
            }
4432
            elseif($type == 1){
4433
                $labelstatut = $langs->trans('ProductStatusOnBuyShort');
4434
                $labelstatutShort = $langs->trans('ProductStatusOnBuy');
4435
            }
4436
            elseif($type == 2){
4437
                $labelstatut = $langs->trans('ProductStatusOnBatch');
4438
                $labelstatutShort = $langs->trans('ProductStatusOnBatchShort');
4439
            }
4440
        }
4441
4442
4443
        if($mode>6){
4444
            return dolGetStatus($langs->trans('Unknown'), '', '', 'status0', 0);
4445
        }
4446
        else{
4447
            return dolGetStatus($labelstatut, $labelstatutShort, '', $statuttrans, $mode);
4448
        }
4449
    }
4450
4451
4452
    /**
4453
     *  Retourne le libelle du finished du produit
4454
     *
4455
     * @return string        Libelle
4456
     */
4457
    public function getLibFinished()
4458
    {
4459
        global $langs;
4460
        $langs->load('products');
4461
4462
        if ($this->finished == '0') { return $langs->trans("RowMaterial");
4463
        }
4464
        if ($this->finished == '1') { return $langs->trans("Finished");
4465
        }
4466
        return '';
4467
    }
4468
4469
4470
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4471
    /**
4472
     *  Adjust stock in a warehouse for product
4473
     *
4474
     * @param  User   $user           user asking change
4475
     * @param  int    $id_entrepot    id of warehouse
4476
     * @param  double $nbpiece        nb of units
4477
     * @param  int    $movement       0 = add, 1 = remove
4478
     * @param  string $label          Label of stock movement
4479
     * @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.
4480
     * @param  string $inventorycode  Inventory code
4481
     * @param  string $origin_element Origin element type
4482
     * @param  int    $origin_id      Origin id of element
4483
     * @return int                     <0 if KO, >0 if OK
4484
     */
4485
    public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null)
4486
    {
4487
        // phpcs:enable
4488
        if ($id_entrepot) {
4489
            $this->db->begin();
4490
4491
            include_once DOL_DOCUMENT_ROOT .'/product/stock/class/mouvementstock.class.php';
4492
4493
            $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...
4494
            $op[1] = "-".trim($nbpiece);
4495
4496
            $movementstock=new MouvementStock($this->db);
4497
            $movementstock->setOrigin($origin_element, $origin_id);
4498
            $result=$movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode);
4499
4500
            if ($result >= 0) {
4501
                $this->db->commit();
4502
                return 1;
4503
            }
4504
            else
4505
            {
4506
                $this->error=$movementstock->error;
4507
                $this->errors=$movementstock->errors;
4508
4509
                $this->db->rollback();
4510
                return -1;
4511
            }
4512
        }
4513
    }
4514
4515
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4516
    /**
4517
     *  Adjust stock in a warehouse for product with batch number
4518
     *
4519
     * @param  User     $user           user asking change
4520
     * @param  int      $id_entrepot    id of warehouse
4521
     * @param  double   $nbpiece        nb of units
4522
     * @param  int      $movement       0 = add, 1 = remove
4523
     * @param  string   $label          Label of stock movement
4524
     * @param  double   $price          Price to use for stock eval
4525
     * @param  integer  $dlc            eat-by date
4526
     * @param  integer  $dluo           sell-by date
4527
     * @param  string   $lot            Lot number
4528
     * @param  string   $inventorycode  Inventory code
4529
     * @param  string   $origin_element Origin element type
4530
     * @param  int      $origin_id      Origin id of element
4531
     * @return int                      <0 if KO, >0 if OK
4532
     */
4533
    public function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $dlc = '', $dluo = '', $lot = '', $inventorycode = '', $origin_element = '', $origin_id = null)
4534
    {
4535
        // phpcs:enable
4536
        if ($id_entrepot) {
4537
            $this->db->begin();
4538
4539
            include_once DOL_DOCUMENT_ROOT .'/product/stock/class/mouvementstock.class.php';
4540
4541
            $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...
4542
            $op[1] = "-".trim($nbpiece);
4543
4544
            $movementstock=new MouvementStock($this->db);
4545
            $movementstock->setOrigin($origin_element, $origin_id);
4546
            $result=$movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot);
4547
4548
            if ($result >= 0) {
4549
                $this->db->commit();
4550
                return 1;
4551
            }
4552
            else
4553
            {
4554
                $this->error=$movementstock->error;
4555
                $this->errors=$movementstock->errors;
4556
4557
                $this->db->rollback();
4558
                return -1;
4559
            }
4560
        }
4561
    }
4562
4563
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4564
    /**
4565
     *    Load information about stock of a product into ->stock_reel, ->stock_warehouse[] (including stock_warehouse[idwarehouse]->detail_batch for batch products)
4566
     *    This function need a lot of load. If you use it on list, use a cache to execute it once for each product id.
4567
     *    If ENTREPOT_EXTRA_STATUS set, filtering on warehouse status possible.
4568
     *
4569
     * @param  string $option '' = Load all stock info, also from closed and internal warehouses,
4570
     * @return int                   < 0 if KO, > 0 if OK
4571
     * @see    load_virtual_stock(), loadBatchInfo()
4572
     */
4573
    public function load_stock($option = '')
4574
    {
4575
        // phpcs:enable
4576
        global $conf;
4577
4578
        $this->stock_reel = 0;
4579
        $this->stock_warehouse = array();
4580
        $this->stock_theorique = 0;
4581
4582
        $warehouseStatus = array();
4583
4584
        if (preg_match('/warehouseclosed/', $option)) {
4585
            $warehouseStatus[] = Entrepot::STATUS_CLOSED;
4586
        }
4587
        if (preg_match('/warehouseopen/', $option)) {
4588
            $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL;
4589
        }
4590
        if (preg_match('/warehouseinternal/', $option)) {
4591
            $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL;
4592
        }
4593
4594
        $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
4595
        $sql.= " FROM ".MAIN_DB_PREFIX."product_stock as ps";
4596
        $sql.= ", ".MAIN_DB_PREFIX."entrepot as w";
4597
        $sql.= " WHERE w.entity IN (".getEntity('stock').")";
4598
        $sql.= " AND w.rowid = ps.fk_entrepot";
4599
        $sql.= " AND ps.fk_product = ".$this->id;
4600
        if ($conf->global->ENTREPOT_EXTRA_STATUS && count($warehouseStatus)) { $sql.= " AND w.statut IN (".$this->db->escape(implode(',', $warehouseStatus)).")";
4601
        }
4602
4603
        dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
4604
        $result = $this->db->query($sql);
4605
        if ($result) {
4606
            $num = $this->db->num_rows($result);
4607
            $i=0;
4608
            if ($num > 0) {
4609
                while ($i < $num)
4610
                {
4611
                    $row = $this->db->fetch_object($result);
4612
                    $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
4613
                    $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
4614
                    $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
4615
                    if ((! preg_match('/nobatch/', $option)) && $this->hasbatch()) { $this->stock_warehouse[$row->fk_entrepot]->detail_batch=Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
4616
                    }
4617
                    $this->stock_reel+=$row->reel;
4618
                    $i++;
4619
                }
4620
            }
4621
            $this->db->free($result);
4622
4623
            if (! preg_match('/novirtual/', $option)) {
4624
                $this->load_virtual_stock();        // This also load stats_commande_fournisseur, ...
4625
            }
4626
4627
            return 1;
4628
        }
4629
        else
4630
        {
4631
            $this->error=$this->db->lasterror();
4632
            return -1;
4633
        }
4634
    }
4635
4636
4637
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4638
	/**
4639
	 *    Load value ->stock_theorique of a product. Property this->id must be defined.
4640
	 *    This function need a lot of load. If you use it on list, use a cache to execute it one for each product id.
4641
	 *
4642
	 *    @return   int             < 0 if KO, > 0 if OK
4643
	 *    @see		load_stock(), loadBatchInfo()
4644
	 */
4645
	public function load_virtual_stock()
4646
	{
4647
		// phpcs:enable
4648
		global $conf, $hookmanager, $action;
4649
4650
		$stock_commande_client=0;
4651
		$stock_commande_fournisseur=0;
4652
		$stock_sending_client=0;
4653
		$stock_reception_fournisseur=0;
4654
4655
		if (! empty($conf->commande->enabled))
4656
		{
4657
			$result=$this->load_stats_commande(0, '1,2', 1);
4658
			if ($result < 0) dol_print_error($this->db, $this->error);
4659
			$stock_commande_client=$this->stats_commande['qty'];
4660
		}
4661
		if (! empty($conf->expedition->enabled))
4662
		{
4663
			$result=$this->load_stats_sending(0, '1,2', 1);
4664
			if ($result < 0) dol_print_error($this->db, $this->error);
4665
			$stock_sending_client=$this->stats_expedition['qty'];
4666
		}
4667
		if (! empty($conf->fournisseur->enabled))
4668
		{
4669
			$result=$this->load_stats_commande_fournisseur(0, '1,2,3,4', 1);
4670
			if ($result < 0) dol_print_error($this->db, $this->error);
4671
			$stock_commande_fournisseur=$this->stats_commande_fournisseur['qty'];
4672
4673
			$result=$this->load_stats_reception(0, '4', 1);
4674
			if ($result < 0) dol_print_error($this->db, $this->error);
4675
			$stock_reception_fournisseur=$this->stats_reception['qty'];
4676
		}
4677
4678
		// Stock decrease mode
4679
		if (! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || ! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
4680
			$this->stock_theorique=$this->stock_reel-$stock_commande_client+$stock_sending_client;
4681
		}
4682
		if (! empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER)) {
4683
			$this->stock_theorique=$this->stock_reel;
4684
		}
4685
		if (! empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
4686
			$this->stock_theorique=$this->stock_reel-$stock_commande_client;
4687
		}
4688
		// Stock Increase mode
4689
        if (! empty($conf->global->STOCK_CALCULATE_ON_RECEPTION) || ! empty($conf->global->STOCK_CALCULATE_ON_RECEPTION_CLOSE)) {
4690
            $this->stock_theorique+=$stock_commande_fournisseur-$stock_reception_fournisseur;
4691
        }
4692
		if (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER)) {
4693
			$this->stock_theorique+=$stock_commande_fournisseur-$stock_reception_fournisseur;
4694
		}
4695
		if (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER)) {
4696
			$this->stock_theorique-=$stock_reception_fournisseur;
4697
		}
4698
		if (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL)) {
4699
			$this->stock_theorique+=$stock_commande_fournisseur-$stock_reception_fournisseur;
4700
		}
4701
4702
		if (! is_object($hookmanager)) {
4703
			include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
4704
			$hookmanager=new HookManager($this->db);
4705
		}
4706
		$hookmanager->initHooks(array('productdao'));
4707
		$parameters=array('id'=>$this->id);
4708
		// Note that $action and $object may have been modified by some hooks
4709
		$reshook=$hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
4710
		if ($reshook > 0) $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
4711
4712
		return 1;
4713
	}
4714
4715
4716
    /**
4717
     *  Load existing information about a serial
4718
     *
4719
     * @param  string $batch Lot/serial number
4720
     * @return array                    Array with record into product_batch
4721
     * @see    load_stock(), load_virtual_stock()
4722
     */
4723
    public function loadBatchInfo($batch)
4724
    {
4725
        $result=array();
4726
4727
        $sql = "SELECT pb.batch, pb.eatby, pb.sellby, SUM(pb.qty) AS qty FROM ".MAIN_DB_PREFIX."product_batch as pb, ".MAIN_DB_PREFIX."product_stock as ps";
4728
        $sql.= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".$this->id." AND pb.batch = '".$this->db->escape($batch)."'";
4729
        $sql.= " GROUP BY pb.batch, pb.eatby, pb.sellby";
4730
        dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
4731
        $resql = $this->db->query($sql);
4732
        if ($resql) {
4733
            $num = $this->db->num_rows($resql);
4734
            $i=0;
4735
            while ($i < $num)
4736
            {
4737
                $obj = $this->db->fetch_object($resql);
4738
                $result[]=array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
4739
                $i++;
4740
            }
4741
            return $result;
4742
        }
4743
        else
4744
        {
4745
            dol_print_error($this->db);
4746
            $this->db->rollback();
4747
            return array();
4748
        }
4749
    }
4750
4751
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4752
    /**
4753
     *  Move an uploaded file described into $file array into target directory $sdir.
4754
     *
4755
     * @param  string $sdir Target directory
4756
     * @param  string $file Array of file info of file to upload: array('name'=>..., 'tmp_name'=>...)
4757
     * @return int                    <0 if KO, >0 if OK
4758
     */
4759
    public function add_photo($sdir, $file)
4760
    {
4761
        // phpcs:enable
4762
        global $conf;
4763
4764
        include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4765
4766
        $result = 0;
4767
4768
        $dir = $sdir;
4769
        if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
4770
            $dir .= '/'. get_exdir($this->id, 2, 0, 0, $this, 'product') . $this->id ."/photos";
4771
        } else {
4772
            $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
4773
        }
4774
4775
        dol_mkdir($dir);
4776
4777
        $dir_osencoded=$dir;
4778
4779
        if (is_dir($dir_osencoded)) {
4780
            $originImage = $dir . '/' . $file['name'];
4781
4782
            // Cree fichier en taille origine
4783
            $result=dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
4784
4785
            if (file_exists(dol_osencode($originImage))) {
4786
                // Create thumbs
4787
                $this->addThumbs($originImage);
4788
            }
4789
        }
4790
4791
        if (is_numeric($result) && $result > 0) {
4792
            return 1;
4793
        } else {
4794
            return -1;
4795
        }
4796
    }
4797
4798
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4799
    /**
4800
     *  Return if at least one photo is available
4801
     *
4802
     * @param  string $sdir Directory to scan
4803
     * @return boolean                 True if at least one photo is available, False if not
4804
     */
4805
    public function is_photo_available($sdir)
4806
    {
4807
        // phpcs:enable
4808
        include_once DOL_DOCUMENT_ROOT .'/core/lib/files.lib.php';
4809
        include_once DOL_DOCUMENT_ROOT .'/core/lib/images.lib.php';
4810
4811
        global $conf;
4812
4813
        $dir = $sdir;
4814
        if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) { $dir .= '/'. get_exdir($this->id, 2, 0, 0, $this, 'product') . $this->id ."/photos/";
4815
        } else { $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref).'/';
4816
        }
4817
4818
        $nbphoto=0;
4819
4820
        $dir_osencoded=dol_osencode($dir);
4821
        if (file_exists($dir_osencoded)) {
4822
            $handle=opendir($dir_osencoded);
4823
            if (is_resource($handle)) {
4824
                while (($file = readdir($handle)) !== false)
4825
                {
4826
                    if (! utf8_check($file)) { $file=utf8_encode($file);    // To be sure data is stored in UTF8 in memory
4827
                    }
4828
                    if (dol_is_file($dir.$file) && image_format_supported($file) > 0) { return true;
4829
                    }
4830
                }
4831
            }
4832
        }
4833
        return false;
4834
    }
4835
4836
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4837
    /**
4838
     *  Retourne tableau de toutes les photos du produit
4839
     *
4840
     * @param  string $dir   Repertoire a scanner
4841
     * @param  int    $nbmax Nombre maximum de photos (0=pas de max)
4842
     * @return array                   Tableau de photos
4843
     */
4844
    public function liste_photos($dir, $nbmax = 0)
4845
    {
4846
        // phpcs:enable
4847
        include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4848
        include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
4849
4850
        $nbphoto=0;
4851
        $tabobj=array();
4852
4853
        $dir_osencoded=dol_osencode($dir);
4854
        $handle=@opendir($dir_osencoded);
4855
        if (is_resource($handle)) {
4856
            while (($file = readdir($handle)) !== false)
4857
            {
4858
                if (! utf8_check($file)) { $file=utf8_encode($file);    // readdir returns ISO
4859
                }
4860
                if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
4861
                    $nbphoto++;
4862
4863
                    // On determine nom du fichier vignette
4864
                    $photo=$file;
4865
                    $photo_vignette='';
4866
                    if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
4867
                        $photo_vignette=preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
4868
                    }
4869
4870
                    $dirthumb = $dir.'thumbs/';
4871
4872
                    // Objet
4873
                    $obj=array();
4874
                    $obj['photo']=$photo;
4875
                    if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) { $obj['photo_vignette']='thumbs/' . $photo_vignette;
4876
                    } else { $obj['photo_vignette']="";
4877
                    }
4878
4879
                    $tabobj[$nbphoto-1]=$obj;
4880
4881
                    // On continue ou on arrete de boucler ?
4882
                    if ($nbmax && $nbphoto >= $nbmax) { break;
4883
                    }
4884
                }
4885
            }
4886
4887
            closedir($handle);
4888
        }
4889
4890
        return $tabobj;
4891
    }
4892
4893
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4894
    /**
4895
     *  Efface la photo du produit et sa vignette
4896
     *
4897
     * @param  string $file Chemin de l'image
4898
     * @return void
4899
     */
4900
    public function delete_photo($file)
4901
    {
4902
        // phpcs:enable
4903
        include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4904
        include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
4905
4906
        $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
4907
        $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
4908
        $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
4909
4910
        // On efface l'image d'origine
4911
        dol_delete_file($file, 0, 0, 0, $this); // For triggers
4912
4913
        // Si elle existe, on efface la vignette
4914
        if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
4915
            $photo_vignette=preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
4916
            if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
4917
                dol_delete_file($dirthumb.$photo_vignette);
4918
            }
4919
4920
            $photo_vignette=preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
4921
            if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
4922
                dol_delete_file($dirthumb.$photo_vignette);
4923
            }
4924
        }
4925
    }
4926
4927
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4928
    /**
4929
     *  Load size of image file
4930
     *
4931
     * @param  string $file Path to file
4932
     * @return void
4933
     */
4934
    public function get_image_size($file)
4935
    {
4936
        // phpcs:enable
4937
        $file_osencoded=dol_osencode($file);
4938
        $infoImg = getimagesize($file_osencoded); // Get information on image
4939
        $this->imgWidth = $infoImg[0]; // Largeur de l'image
4940
        $this->imgHeight = $infoImg[1]; // Hauteur de l'image
4941
    }
4942
4943
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4944
    /**
4945
     *  Load indicators this->nb for the dashboard
4946
     *
4947
     * @return int                 <0 if KO, >0 if OK
4948
     */
4949
    public function load_state_board()
4950
    {
4951
        // phpcs:enable
4952
        global $conf, $user, $hookmanager;
4953
4954
        $this->nb=array();
4955
4956
        $sql = "SELECT count(p.rowid) as nb, fk_product_type";
4957
        $sql.= " FROM ".MAIN_DB_PREFIX."product as p";
4958
        $sql.= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
4959
        // Add where from hooks
4960
        if (is_object($hookmanager)) {
4961
            $parameters=array();
4962
            $reshook=$hookmanager->executeHooks('printFieldListWhere', $parameters);    // Note that $action and $object may have been modified by hook
4963
            $sql.=$hookmanager->resPrint;
4964
        }
4965
        $sql.= ' GROUP BY fk_product_type';
4966
4967
        $resql=$this->db->query($sql);
4968
        if ($resql) {
4969
            while ($obj=$this->db->fetch_object($resql))
4970
            {
4971
                if ($obj->fk_product_type == 1) { $this->nb["services"]=$obj->nb;
4972
                } else { $this->nb["products"]=$obj->nb;
4973
                }
4974
            }
4975
            $this->db->free($resql);
4976
            return 1;
4977
        }
4978
        else
4979
        {
4980
            dol_print_error($this->db);
4981
            $this->error=$this->db->error();
4982
            return -1;
4983
        }
4984
    }
4985
4986
    /**
4987
     * Return if object is a product
4988
     *
4989
     * @return boolean     True if it's a product
4990
     */
4991
    public function isProduct()
4992
    {
4993
        return ($this->type == Product::TYPE_PRODUCT ? true : false);
4994
    }
4995
4996
    /**
4997
     * Return if object is a product
4998
     *
4999
     * @return boolean     True if it's a service
5000
     */
5001
    public function isService()
5002
    {
5003
        return ($this->type == Product::TYPE_SERVICE ? true : false);
5004
    }
5005
5006
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5007
    /**
5008
     *  Get a barcode from the module to generate barcode values.
5009
     *  Return value is stored into this->barcode
5010
     *
5011
     * @param  Product $object Object product or service
5012
     * @param  string  $type   Barcode type (ean, isbn, ...)
5013
     * @return string
5014
     */
5015
    public function get_barcode($object, $type = '')
5016
    {
5017
        // phpcs:enable
5018
        global $conf;
5019
5020
        $result='';
5021
        if (! empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
5022
            $dirsociete=array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
5023
            foreach ($dirsociete as $dirroot)
5024
            {
5025
                $res=dol_include_once($dirroot.$conf->global->BARCODE_PRODUCT_ADDON_NUM.'.php');
5026
                if ($res) { break;
5027
                }
5028
            }
5029
            $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
5030
            $mod = new $var;
5031
5032
            $result=$mod->getNextValue($object, $type);
5033
5034
            dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
5035
        }
5036
        return $result;
5037
    }
5038
5039
    /**
5040
     *  Initialise an instance with random values.
5041
     *  Used to build previews or test instances.
5042
     *    id must be 0 if object instance is a specimen.
5043
     *
5044
     * @return void
5045
     */
5046
    public function initAsSpecimen()
5047
    {
5048
        global $user,$langs,$conf,$mysoc;
5049
5050
        $now=dol_now();
5051
5052
        // Initialize parameters
5053
        $this->specimen=1;
5054
        $this->id=0;
5055
        $this->ref = 'PRODUCT_SPEC';
5056
        $this->label = 'PRODUCT SPECIMEN';
5057
        $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
5058
        $this->specimen=1;
5059
        $this->country_id=1;
5060
        $this->tosell=1;
5061
        $this->tobuy=1;
5062
        $this->tobatch=0;
5063
        $this->note='This is a comment (private)';
5064
        $this->date_creation = $now;
5065
        $this->date_modification = $now;
5066
5067
        $this->weight = 4;
5068
        $this->weight_unit = 1;
5069
5070
        $this->length = 5;
5071
        $this->length_unit = 1;
5072
        $this->width = 6;
5073
        $this->width_unit = 0;
5074
        $this->height = null;
5075
        $this->height_unit = null;
5076
5077
        $this->surface = 30;
5078
        $this->surface_unit = 0;
5079
        $this->volume = 300;
5080
        $this->volume_unit = 0;
5081
5082
        $this->barcode=-1;    // Create barcode automatically
5083
    }
5084
5085
    /**
5086
     *    Returns the text label from units dictionary
5087
     *
5088
     * @param  string $type Label type (long or short)
5089
     * @return string|int <0 if ko, label if ok
5090
     */
5091
    public function getLabelOfUnit($type = 'long')
5092
    {
5093
        global $langs;
5094
5095
        if (!$this->fk_unit) {
5096
            return '';
5097
        }
5098
5099
        $langs->load('products');
5100
5101
        $label_type = 'label';
5102
5103
        if ($type == 'short') {
5104
            $label_type = 'short_label';
5105
        }
5106
5107
        $sql = 'select '.$label_type.' from '.MAIN_DB_PREFIX.'c_units where rowid='.$this->fk_unit;
5108
        $resql = $this->db->query($sql);
5109
        if($resql && $this->db->num_rows($resql) > 0) {
5110
            $res = $this->db->fetch_array($resql);
5111
            $label = $res[$label_type];
5112
            $this->db->free($resql);
5113
            return $label;
5114
        }
5115
        else
5116
        {
5117
            $this->error=$this->db->error().' sql='.$sql;
5118
            dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
5119
            return -1;
5120
        }
5121
    }
5122
5123
    /**
5124
     * Return if object has a sell-by date or eat-by date
5125
     *
5126
     * @return boolean     True if it's has
5127
     */
5128
    public function hasbatch()
5129
    {
5130
        return ($this->status_batch == 1 ? true : false);
5131
    }
5132
5133
5134
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5135
    /**
5136
     * Return minimum product recommended price
5137
     *
5138
     * @return int            Minimum recommanded price that is higher price among all suppliers * PRODUCT_MINIMUM_RECOMMENDED_PRICE
5139
     */
5140
    public function min_recommended_price()
5141
    {
5142
        // phpcs:enable
5143
        global $conf;
5144
5145
        $maxpricesupplier=0;
5146
5147
        if (! empty($conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE)) {
5148
            include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
5149
            $product_fourn = new ProductFournisseur($this->db);
5150
            $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
5151
5152
            if (is_array($product_fourn_list) && count($product_fourn_list)>0) {
5153
                foreach($product_fourn_list as $productfourn)
5154
                {
5155
                    if ($productfourn->fourn_unitprice > $maxpricesupplier) {
5156
                        $maxpricesupplier = $productfourn->fourn_unitprice;
5157
                    }
5158
                }
5159
5160
                $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
5161
            }
5162
        }
5163
5164
        return $maxpricesupplier;
5165
    }
5166
5167
5168
    /**
5169
     * Sets object to supplied categories.
5170
     *
5171
     * Deletes object from existing categories not supplied.
5172
     * Adds it to non existing supplied categories.
5173
     * Existing categories are left untouch.
5174
     *
5175
     * @param  int[]|int $categories Category or categories IDs
5176
     * @return void
5177
     */
5178
    public function setCategories($categories)
5179
    {
5180
        // Handle single category
5181
        if (! is_array($categories)) {
5182
            $categories = array($categories);
5183
        }
5184
5185
        // Get current categories
5186
        include_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5187
        $c = new Categorie($this->db);
5188
        $existing = $c->containing($this->id, Categorie::TYPE_PRODUCT, 'id');
5189
5190
        // Diff
5191
        if (is_array($existing)) {
0 ignored issues
show
introduced by
The condition is_array($existing) is always false.
Loading history...
5192
            $to_del = array_diff($existing, $categories);
5193
            $to_add = array_diff($categories, $existing);
5194
        } else {
5195
            $to_del = array(); // Nothing to delete
5196
            $to_add = $categories;
5197
        }
5198
5199
        // Process
5200
        foreach($to_del as $del) {
5201
            if ($c->fetch($del) > 0) {
5202
            	$c->del_type($this, Categorie::TYPE_PRODUCT);
5203
            }
5204
        }
5205
        foreach ($to_add as $add) {
5206
            if ($c->fetch($add) > 0) {
5207
            	$c->add_type($this, Categorie::TYPE_PRODUCT);
5208
            }
5209
        }
5210
5211
        return;
5212
    }
5213
5214
    /**
5215
     * Function used to replace a thirdparty id with another one.
5216
     *
5217
     * @param  DoliDB $db        Database handler
5218
     * @param  int    $origin_id Old thirdparty id
5219
     * @param  int    $dest_id   New thirdparty id
5220
     * @return bool
5221
     */
5222
    public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
5223
    {
5224
        $tables = array(
5225
        'product_customer_price',
5226
        'product_customer_price_log'
5227
        );
5228
5229
        return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
5230
    }
5231
5232
    /**
5233
     * Generates prices for a product based on product multiprice generation rules
5234
     *
5235
     * @param  User   $user       User that updates the prices
5236
     * @param  float  $baseprice  Base price
5237
     * @param  string $price_type Base price type
5238
     * @param  float  $price_vat  VAT % tax
5239
     * @param  int    $npr        NPR
5240
     * @param  string $psq        ¿?
5241
     * @return int -1 KO, 1 OK
5242
     */
5243
    public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
5244
    {
5245
        global $conf, $db;
5246
5247
        $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".MAIN_DB_PREFIX."product_pricerules";
5248
        $query = $db->query($sql);
5249
5250
        $rules = array();
5251
5252
        while ($result = $db->fetch_object($query)) {
5253
            $rules[$result->level] = $result;
5254
        }
5255
5256
        //Because prices can be based on other level's prices, we temporarily store them
5257
        $prices = array(
5258
        1 => $baseprice
5259
        );
5260
5261
        for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
5262
            $price = $baseprice;
5263
            $price_min = $baseprice;
5264
5265
            //We have to make sure it does exist and it is > 0
5266
            //First price level only allows changing min_price
5267
            if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
5268
                $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent/100));
5269
            }
5270
5271
            $prices[$i] = $price;
5272
5273
            //We have to make sure it does exist and it is > 0
5274
            if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
5275
                $price_min = $price * (1 - ($rules[$i]->var_min_percent/100));
5276
            }
5277
5278
            //Little check to make sure the price is modified before triggering generation
5279
            $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
5280
            $check_type = ($baseprice == $this->multiprices_base_type[$i]);
5281
5282
            if ($check_amount && $check_type) {
5283
                continue;
5284
            }
5285
5286
            if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
5287
                return -1;
5288
            }
5289
        }
5290
5291
        return 1;
5292
    }
5293
5294
    /**
5295
     * Returns the rights used for this class
5296
     *
5297
     * @return Object
5298
     */
5299
    public function getRights()
5300
    {
5301
        global $user;
5302
5303
        if ($this->isProduct()) {
5304
            return $user->rights->produit;
5305
        } else {
5306
            return $user->rights->service;
5307
        }
5308
    }
5309
5310
    /**
5311
     *  Load information for tab info
5312
     *
5313
     * @param  int $id Id of thirdparty to load
5314
     * @return void
5315
     */
5316
    public function info($id)
5317
    {
5318
        $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
5319
        $sql.= " p.fk_user_author, p.fk_user_modif";
5320
        $sql.= " FROM ".MAIN_DB_PREFIX.$this->table_element." as p";
5321
        $sql.= " WHERE p.rowid = ".$id;
5322
5323
        $result=$this->db->query($sql);
5324
        if ($result) {
5325
            if ($this->db->num_rows($result)) {
5326
                $obj = $this->db->fetch_object($result);
5327
5328
                $this->id = $obj->rowid;
5329
5330
                if ($obj->fk_user_author) {
5331
                    $cuser = new User($this->db);
5332
                    $cuser->fetch($obj->fk_user_author);
5333
                    $this->user_creation     = $cuser;
5334
                }
5335
5336
                if ($obj->fk_user_modif) {
5337
                    $muser = new User($this->db);
5338
                    $muser->fetch($obj->fk_user_modif);
5339
                    $this->user_modification = $muser;
5340
                }
5341
5342
                $this->ref                 = $obj->ref;
5343
                $this->date_creation     = $this->db->jdate($obj->date_creation);
5344
                $this->date_modification = $this->db->jdate($obj->date_modification);
5345
            }
5346
5347
            $this->db->free($result);
5348
        }
5349
        else
5350
        {
5351
            dol_print_error($this->db);
5352
        }
5353
    }
5354
}
5355