Passed
Branch develop (f6c1a1)
by
unknown
28:54
created

Product::updatePrice()   F

Complexity

Conditions 31
Paths > 20000

Size

Total Lines 156
Code Lines 105

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 31
eloc 105
nc 99936
nop 11
dl 0
loc 156
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

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