Passed
Branch develop (502626)
by
unknown
23:33
created

Product::load_stats_sending()   D

Complexity

Conditions 17
Paths 224

Size

Total Lines 66
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 47
c 0
b 0
f 0
nc 224
nop 4
dl 0
loc 66
rs 4.0833

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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