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

Product::correct_stock()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 16
c 0
b 0
f 0
nc 3
nop 9
dl 0
loc 26
rs 9.7333

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/* Copyright (C) 2001-2007  Rodolphe Quiedeville    <[email protected]>
3
 * Copyright (C) 2004-2014	Laurent Destailleur		<[email protected]>
4
 * Copyright (C) 2005-2015	Regis Houssin			<[email protected]>
5
 * Copyright (C) 2006		Andre Cianfarani		<[email protected]>
6
 * Copyright (C) 2007-2011	Jean Heimburger			<[email protected]>
7
 * Copyright (C) 2010-2018	Juanjo Menent			<[email protected]>
8
 * Copyright (C) 2012       Cedric Salvador         <[email protected]>
9
 * Copyright (C) 2013-2014	Cedric GROSS			<[email protected]>
10
 * Copyright (C) 2013-2016	Marcos García			<[email protected]>
11
 * Copyright (C) 2011-2017	Alexandre Spangaro		<[email protected]>
12
 * Copyright (C) 2014		Henry Florian			<[email protected]>
13
 * Copyright (C) 2014-2016	Philippe Grand			<[email protected]>
14
 * Copyright (C) 2014		Ion agorria			    <[email protected]>
15
 * Copyright (C) 2016-2018	Ferran Marcet			<[email protected]>
16
 * Copyright (C) 2017		Gustavo Novaro
17
 * Copyright (C) 2019       Frédéric France         <[email protected]>
18
 *
19
 * This program is free software; you can redistribute it and/or modify
20
 * it under the terms of the GNU General Public License as published by
21
 * the Free Software Foundation; either version 3 of the License, or
22
 * (at your option) any later version.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
 * GNU General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU General Public License
30
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31
 */
32
33
/**
34
 *    \file       htdocs/product/class/product.class.php
35
 *    \ingroup    produit
36
 *    \brief      File of class to manage predefined products or services
37
 */
38
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
39
require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
40
require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
41
42
/**
43
 * Class to manage products or services
44
 */
45
class Product extends CommonObject
46
{
47
    /**
48
     * @var string ID to identify managed object
49
     */
50
    public $element='product';
51
52
    /**
53
     * @var string Name of table without prefix where object is stored
54
     */
55
    public $table_element='product';
56
57
    /**
58
     * @var int Field with ID of parent key if this field has a parent
59
     */
60
    public $fk_element='fk_product';
61
62
    /**
63
     * @var array	List of child tables. To test if we can delete object.
64
     */
65
    protected $childtables=array('supplier_proposaldet', 'propaldet', 'commandedet', 'facturedet', 'contratdet', 'facture_fourn_det', 'commande_fournisseurdet');
66
67
    /**
68
     * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
69
     *
70
     * @var int
71
     */
72
    public $ismultientitymanaged = 1;
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    protected $table_ref_field = 'ref';
78
79
    public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.xpm|\.xbm'; // See also into images.lib.php
80
81
    /*
82
    * @deprecated
83
    * @see label
84
    */
85
    public $libelle;
86
    /**
87
     * Product label
88
     *
89
     * @var string
90
     */
91
    public $label;
92
93
    /**
94
     * Product 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