dolibarr 19.0.3
product.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2001-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2014 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2005-2015 Regis Houssin <regis.houssin@inodbox.com>
5 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
6 * Copyright (C) 2007-2011 Jean Heimburger <jean@tiaris.info>
7 * Copyright (C) 2010-2018 Juanjo Menent <jmenent@2byte.es>
8 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
9 * Copyright (C) 2013-2014 Cedric GROSS <c.gross@kreiz-it.fr>
10 * Copyright (C) 2013-2016 Marcos García <marcosgdf@gmail.com>
11 * Copyright (C) 2011-2021 Open-DSI <support@open-dsi.fr>
12 * Copyright (C) 2014 Henry Florian <florian.henry@open-concept.pro>
13 * Copyright (C) 2014-2016 Philippe Grand <philippe.grand@atoo-net.com>
14 * Copyright (C) 2014 Ion agorria <ion@agorria.com>
15 * Copyright (C) 2016-2024 Ferran Marcet <fmarcet@2byte.es>
16 * Copyright (C) 2017 Gustavo Novaro
17 * Copyright (C) 2019-2023 Frédéric France <frederic.france@netlogic.fr>
18 * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 3 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program. If not, see <https://www.gnu.org/licenses/>.
32 */
33
39require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
40require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
41require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
42require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
43require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
44
48class Product extends CommonObject
49{
53 public $element = 'product';
54
58 public $table_element = 'product';
59
63 public $fk_element = 'fk_product';
64
68 protected $childtables = array(
69 'supplier_proposaldet' => array('name' => 'SupplierProposal', 'parent' => 'supplier_proposal', 'parentkey' => 'fk_supplier_proposal'),
70 'propaldet' => array('name' => 'Proposal', 'parent' => 'propal', 'parentkey' => 'fk_propal'),
71 'commandedet' => array('name' => 'Order', 'parent' => 'commande', 'parentkey' => 'fk_commande'),
72 'facturedet' => array('name' => 'Invoice', 'parent' => 'facture', 'parentkey' => 'fk_facture'),
73 'contratdet' => array('name' => 'Contract', 'parent' => 'contrat', 'parentkey' => 'fk_contrat'),
74 'facture_fourn_det' => array('name' => 'SupplierInvoice', 'parent' => 'facture_fourn', 'parentkey' => 'fk_facture_fourn'),
75 'commande_fournisseurdet' => array('name' => 'SupplierOrder', 'parent' => 'commande_fournisseur', 'parentkey' => 'fk_commande'),
76 'mrp_production' => array('name' => 'Mo', 'parent' => 'mrp_mo', 'parentkey' => 'fk_mo' ),
77 'bom_bom' => array('name' => 'BOM'),
78 'bom_bomline' => array('name' => 'BOMLine', 'parent' => 'bom_bom', 'parentkey' => 'fk_bom'),
79 );
80
86 public $ismultientitymanaged = 1;
87
91 public $isextrafieldmanaged = 1;
92
96 public $picto = 'product';
97
101 protected $table_ref_field = 'ref';
102
103 public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm'; // See also into images.lib.php
104
109 public $libelle;
110
116 public $label;
117
123 public $description;
124
130 public $other;
131
137 public $type = self::TYPE_PRODUCT;
138
144 public $price;
145
146 public $price_formated; // used by takepos/ajax/ajax.php
147
153 public $price_ttc;
154
155 public $price_ttc_formated; // used by takepos/ajax/ajax.php
156
162 public $price_min;
163
169 public $price_min_ttc;
170
175 public $price_base_type;
176
178 public $multiprices = array();
179 public $multiprices_ttc = array();
180 public $multiprices_base_type = array();
181 public $multiprices_default_vat_code = array();
182 public $multiprices_min = array();
183 public $multiprices_min_ttc = array();
184 public $multiprices_tva_tx = array();
185 public $multiprices_recuperableonly = array();
186
189 public $prices_by_qty = array();
190 public $prices_by_qty_id = array();
191 public $prices_by_qty_list = array();
192
196 public $level;
197
199 public $multilangs = array();
200
203
205 public $tva_tx;
206
210 public $tva_npr = 0;
211
214
217 public $localtax2_tx;
218 public $localtax1_type;
219 public $localtax2_type;
220
221 // Properties set by get_buyprice() for return
222
223 public $desc_supplier;
224 public $vatrate_supplier;
225 public $default_vat_code_supplier;
226 public $fourn_multicurrency_price;
227 public $fourn_multicurrency_unitprice;
228 public $fourn_multicurrency_tx;
229 public $fourn_multicurrency_id;
230 public $fourn_multicurrency_code;
231 public $packaging;
232
233
234 public $lifetime; // In seconds
235
236 public $qc_frequency;
237
243 public $stock_reel = 0;
244
250 public $stock_theorique;
251
257 public $cost_price;
258
260 public $pmp;
261
267 public $seuil_stock_alerte = 0;
268
272 public $desiredstock = 0;
273
285 public $duration;
286
291
297 public $status = 0;
298
305 public $tosell;
306
312 public $status_buy = 0;
313
320 public $tobuy;
321
327 public $finished;
328
334 public $fk_default_bom;
335
341 public $product_fourn_price_id;
342
348 public $buyprice;
349
355 public $tobatch;
356
357
363 public $status_batch = 0;
364
370 public $batch_mask = '';
371
377 public $customcode;
378
384 public $url;
385
387 public $weight;
388 public $weight_units; // scale -3, 0, 3, 6
389 public $length;
390 public $length_units; // scale -3, 0, 3, 6
391 public $width;
392 public $width_units; // scale -3, 0, 3, 6
393 public $height;
394 public $height_units; // scale -3, 0, 3, 6
395 public $surface;
396 public $surface_units; // scale -3, 0, 3, 6
397 public $volume;
398 public $volume_units; // scale -3, 0, 3, 6
399
400 public $net_measure;
401 public $net_measure_units; // scale -3, 0, 3, 6
402
403 public $accountancy_code_sell;
404 public $accountancy_code_sell_intra;
405 public $accountancy_code_sell_export;
406 public $accountancy_code_buy;
407 public $accountancy_code_buy_intra;
408 public $accountancy_code_buy_export;
409
413 public $barcode;
414
418 public $barcode_type;
419
423 public $barcode_type_code;
424
425 public $stats_propale = array();
426 public $stats_commande = array();
427 public $stats_contrat = array();
428 public $stats_facture = array();
429 public $stats_proposal_supplier = array();
430 public $stats_commande_fournisseur = array();
431 public $stats_expedition = array();
432 public $stats_reception = array();
433 public $stats_mo = array();
434 public $stats_bom = array();
435 public $stats_mrptoconsume = array();
436 public $stats_mrptoproduce = array();
437 public $stats_facturerec = array();
438 public $stats_facture_fournisseur = array();
439
441 public $imgWidth;
442 public $imgHeight;
443
447 public $date_creation;
448
452 public $date_modification;
453
456
459
460 public $nbphoto = 0;
461
463 public $stock_warehouse = array();
464
468 public $fk_default_warehouse;
472 public $fk_price_expression;
473
474 /* To store supplier price found */
475 public $fourn_qty;
476 public $fourn_pu;
477 public $fourn_price_base_type;
478 public $fourn_socid;
479
485
489 public $ref_supplier;
490
496 public $fk_unit;
497
503 public $price_autogen = 0;
504
510 public $supplierprices;
511
517 public $sousprods;
518
522 public $res;
523
524
530 public $is_object_used;
531
532 public $is_sousproduit_qty;
533 public $is_sousproduit_incdec;
534
535 public $mandatory_period;
536
537
566 public $fields = array(
567 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'index'=>1, 'position'=>1, 'comment'=>'Id'),
568 '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'),
569 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'notnull'=>1, 'index'=>1, 'position'=>5),
570 'label' =>array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>2, 'position'=>15, 'csslist'=>'tdoverflowmax250'),
571 'barcode' =>array('type'=>'varchar(255)', 'label'=>'Barcode', 'enabled'=>'isModEnabled("barcode")', 'position'=>20, 'visible'=>-1, 'showoncombobox'=>3),
572 'fk_barcode_type' => array('type'=>'integer', 'label'=>'BarcodeType', 'enabled'=>'1', 'position'=>21, 'notnull'=>0, 'visible'=>-1,),
573 'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>61),
574 'note' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>62),
575 'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>500),
576 'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>501),
577 //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
578 'fk_user_author'=>array('type'=>'integer', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>510, 'foreignkey'=>'llx_user.rowid'),
579 'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>511),
580 //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
581 'localtax1_tx' => array('type'=>'double(6,3)', 'label'=>'Localtax1tx', 'enabled'=>'1', 'position'=>150, 'notnull'=>0, 'visible'=>-1,),
582 'localtax1_type' => array('type'=>'varchar(10)', 'label'=>'Localtax1type', 'enabled'=>'1', 'position'=>155, 'notnull'=>1, 'visible'=>-1,),
583 'localtax2_tx' => array('type'=>'double(6,3)', 'label'=>'Localtax2tx', 'enabled'=>'1', 'position'=>160, 'notnull'=>0, 'visible'=>-1,),
584 'localtax2_type' => array('type'=>'varchar(10)', 'label'=>'Localtax2type', 'enabled'=>'1', 'position'=>165, 'notnull'=>1, 'visible'=>-1,),
585 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'index'=>0, 'position'=>1000),
586 //'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')),
587 //'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')),
588 'mandatory_period' => array('type'=>'integer', 'label'=>'mandatoryperiod', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000),
589 );
590
594 const TYPE_PRODUCT = 0;
598 const TYPE_SERVICE = 1;
599
604 const SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY = 1;
605 const SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY = 2;
606 const SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT = 3;
607
608
614 public function __construct($db)
615 {
616 $this->db = $db;
617 $this->canvas = '';
618 }
619
625 public function check()
626 {
627 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
628 $this->ref = trim($this->ref);
629 } else {
630 $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
631 }
632
633 $err = 0;
634 if (dol_strlen(trim($this->ref)) == 0) {
635 $err++;
636 }
637
638 if (dol_strlen(trim($this->label)) == 0) {
639 $err++;
640 }
641
642 if ($err > 0) {
643 return 0;
644 } else {
645 return 1;
646 }
647 }
648
656 public function create($user, $notrigger = 0)
657 {
658 global $conf, $langs;
659
660 $error = 0;
661
662 // Clean parameters
663 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
664 $this->ref = trim($this->ref);
665 } else {
666 $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
667 }
668 $this->label = trim($this->label);
669 $this->price_ttc = price2num($this->price_ttc);
670 $this->price = price2num($this->price);
671 $this->price_min_ttc = price2num($this->price_min_ttc);
672 $this->price_min = price2num($this->price_min);
673 if (empty($this->tva_tx)) {
674 $this->tva_tx = 0;
675 }
676 if (empty($this->tva_npr)) {
677 $this->tva_npr = 0;
678 }
679 //Local taxes
680 if (empty($this->localtax1_tx)) {
681 $this->localtax1_tx = 0;
682 }
683 if (empty($this->localtax2_tx)) {
684 $this->localtax2_tx = 0;
685 }
686 if (empty($this->localtax1_type)) {
687 $this->localtax1_type = '0';
688 }
689 if (empty($this->localtax2_type)) {
690 $this->localtax2_type = '0';
691 }
692 if (empty($this->price)) {
693 $this->price = 0;
694 }
695 if (empty($this->price_min)) {
696 $this->price_min = 0;
697 }
698 // Price by quantity
699 if (empty($this->price_by_qty)) {
700 $this->price_by_qty = 0;
701 }
702
703 if (empty($this->status)) {
704 $this->status = 0;
705 }
706 if (empty($this->status_buy)) {
707 $this->status_buy = 0;
708 }
709
710 $price_ht = 0;
711 $price_ttc = 0;
712 $price_min_ht = 0;
713 $price_min_ttc = 0;
714
715 //
716 if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
717 $price_ttc = price2num($this->price_ttc, 'MU');
718 $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
719 }
720
721 //
722 if ($this->price_base_type != 'TTC' && $this->price > 0) {
723 $price_ht = price2num($this->price, 'MU');
724 $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
725 }
726
727 //
728 if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
729 $price_min_ttc = price2num($this->price_min_ttc, 'MU');
730 $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
731 }
732
733 //
734 if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
735 $price_min_ht = price2num($this->price_min, 'MU');
736 $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
737 }
738
739 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
740 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
741 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
742 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
743 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
744 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
745
746 // Barcode value
747 $this->barcode = trim($this->barcode);
748 $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
749 // Check parameters
750 if (empty($this->label)) {
751 $this->error = 'ErrorMandatoryParametersNotProvided';
752 return -1;
753 }
754
755 if (empty($this->ref) || $this->ref == 'auto') {
756 // Load object modCodeProduct
757 $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
758 if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
759 if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
760 $module = substr($module, 0, dol_strlen($module) - 4);
761 }
762 dol_include_once('/core/modules/product/'.$module.'.php');
763 $modCodeProduct = new $module();
764 if (!empty($modCodeProduct->code_auto)) {
765 $this->ref = $modCodeProduct->getNextValue($this, $this->type);
766 }
767 unset($modCodeProduct);
768 }
769
770 if (empty($this->ref)) {
771 $this->error = 'ProductModuleNotSetupForAutoRef';
772 return -2;
773 }
774 }
775
776 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);
777
778 $now = dol_now();
779
780 $this->db->begin();
781
782 // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
783 if ($this->barcode == -1) {
784 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
785 }
786
787 // Check more parameters
788 // If error, this->errors[] is filled
789 $result = $this->verify();
790
791 if ($result >= 0) {
792 $sql = "SELECT count(*) as nb";
793 $sql .= " FROM ".$this->db->prefix()."product";
794 $sql .= " WHERE entity IN (".getEntity('product').")";
795 $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
796
797 $result = $this->db->query($sql);
798 if ($result) {
799 $obj = $this->db->fetch_object($result);
800 if ($obj->nb == 0) {
801 // Produit non deja existant
802 $sql = "INSERT INTO ".$this->db->prefix()."product (";
803 $sql .= "datec";
804 $sql .= ", entity";
805 $sql .= ", ref";
806 $sql .= ", ref_ext";
807 $sql .= ", price_min";
808 $sql .= ", price_min_ttc";
809 $sql .= ", label";
810 $sql .= ", fk_user_author";
811 $sql .= ", fk_product_type";
812 $sql .= ", price";
813 $sql .= ", price_ttc";
814 $sql .= ", price_base_type";
815 $sql .= ", tobuy";
816 $sql .= ", tosell";
817 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
818 $sql .= ", accountancy_code_buy";
819 $sql .= ", accountancy_code_buy_intra";
820 $sql .= ", accountancy_code_buy_export";
821 $sql .= ", accountancy_code_sell";
822 $sql .= ", accountancy_code_sell_intra";
823 $sql .= ", accountancy_code_sell_export";
824 }
825 $sql .= ", canvas";
826 $sql .= ", finished";
827 $sql .= ", tobatch";
828 $sql .= ", batch_mask";
829 $sql .= ", fk_unit";
830 $sql .= ", mandatory_period";
831 $sql .= ") VALUES (";
832 $sql .= "'".$this->db->idate($now)."'";
833 $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
834 $sql .= ", '".$this->db->escape($this->ref)."'";
835 $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
836 $sql .= ", ".price2num($price_min_ht);
837 $sql .= ", ".price2num($price_min_ttc);
838 $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
839 $sql .= ", ".((int) $user->id);
840 $sql .= ", ".((int) $this->type);
841 $sql .= ", ".price2num($price_ht, 'MT');
842 $sql .= ", ".price2num($price_ttc, 'MT');
843 $sql .= ", '".$this->db->escape($this->price_base_type)."'";
844 $sql .= ", ".((int) $this->status);
845 $sql .= ", ".((int) $this->status_buy);
846 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
847 $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
848 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
849 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
850 $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
851 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
852 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
853 }
854 $sql .= ", '".$this->db->escape($this->canvas)."'";
855 $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
856 $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
857 $sql .= ", '".$this->db->escape($this->batch_mask)."'";
858 $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
859 $sql .= ", '".$this->db->escape($this->mandatory_period)."'";
860 $sql .= ")";
861
862 dol_syslog(get_class($this)."::Create", LOG_DEBUG);
863 $result = $this->db->query($sql);
864 if ($result) {
865 $id = $this->db->last_insert_id($this->db->prefix()."product");
866
867 if ($id > 0) {
868 $this->id = $id;
869 $this->price = $price_ht;
870 $this->price_ttc = $price_ttc;
871 $this->price_min = $price_min_ht;
872 $this->price_min_ttc = $price_min_ttc;
873
874 $result = $this->_log_price($user);
875 if ($result > 0) {
876 if ($this->update($id, $user, true, 'add') <= 0) {
877 $error++;
878 }
879 } else {
880 $error++;
881 $this->error = $this->db->lasterror();
882 }
883
884 // update accountancy for this entity
885 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
886 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
887
888 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
889 $sql .= " fk_product";
890 $sql .= ", entity";
891 $sql .= ", accountancy_code_buy";
892 $sql .= ", accountancy_code_buy_intra";
893 $sql .= ", accountancy_code_buy_export";
894 $sql .= ", accountancy_code_sell";
895 $sql .= ", accountancy_code_sell_intra";
896 $sql .= ", accountancy_code_sell_export";
897 $sql .= ") VALUES (";
898 $sql .= $this->id;
899 $sql .= ", " . $conf->entity;
900 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
901 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
902 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
903 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
904 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
905 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
906 $sql .= ")";
907 $result = $this->db->query($sql);
908 if (!$result) {
909 $error++;
910 $this->error = 'ErrorFailedToInsertAccountancyForEntity';
911 }
912 }
913 } else {
914 $error++;
915 $this->error = 'ErrorFailedToGetInsertedId';
916 }
917 } else {
918 $error++;
919 $this->error = $this->db->lasterror();
920 }
921 } else {
922 // Product already exists with this ref
923 $langs->load("products");
924 $error++;
925 $this->error = "ErrorProductAlreadyExists";
926 dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
927 }
928 } else {
929 $error++;
930 $this->error = $this->db->lasterror();
931 }
932
933 if (!$error && !$notrigger) {
934 // Call trigger
935 $result = $this->call_trigger('PRODUCT_CREATE', $user);
936 if ($result < 0) {
937 $error++;
938 }
939 // End call triggers
940 }
941
942 if (!$error) {
943 $this->db->commit();
944 return $this->id;
945 } else {
946 $this->db->rollback();
947 return -$error;
948 }
949 } else {
950 $this->db->rollback();
951 dol_syslog(get_class($this)."::Create fails verify ".join(',', $this->errors), LOG_WARNING);
952 return -3;
953 }
954 }
955
956
963 public function verify()
964 {
965 global $langs;
966
967 $this->errors = array();
968
969 $result = 0;
970 $this->ref = trim($this->ref);
971
972 if (!$this->ref) {
973 $this->errors[] = 'ErrorBadRef';
974 $result = -2;
975 }
976
977 $arrayofnonnegativevalue = array('weight'=>'Weight', 'width'=>'Width', 'height'=>'Height', 'length'=>'Length', 'surface'=>'Surface', 'volume'=>'Volume');
978 foreach ($arrayofnonnegativevalue as $key => $value) {
979 if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
980 $langs->loadLangs(array("main", "other"));
981 $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
982 $this->errors[] = $this->error;
983 $result = -4;
984 }
985 }
986
987 $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
988 if ($rescode) {
989 if ($rescode == -1) {
990 $this->errors[] = 'ErrorBadBarCodeSyntax';
991 } elseif ($rescode == -2) {
992 $this->errors[] = 'ErrorBarCodeRequired';
993 } elseif ($rescode == -3) {
994 // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
995 $this->errors[] = 'ErrorBarCodeAlreadyUsed';
996 }
997
998 $result = -3;
999 }
1000
1001 return $result;
1002 }
1003
1004 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1015 public function check_barcode($valuetotest, $typefortest)
1016 {
1017 // phpcs:enable
1018 global $conf;
1019 if (isModEnabled('barcode') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
1020 $module = strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
1021
1022 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1023 foreach ($dirsociete as $dirroot) {
1024 $res = dol_include_once($dirroot.$module.'.php');
1025 if ($res) {
1026 break;
1027 }
1028 }
1029
1030 $mod = new $module();
1031
1032 dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1033 $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1034 return $result;
1035 } else {
1036 return 0;
1037 }
1038 }
1039
1051 public function update($id, $user, $notrigger = false, $action = 'update', $updatetype = false)
1052 {
1053 global $langs, $conf, $hookmanager;
1054
1055 $error = 0;
1056
1057 // Check parameters
1058 if (!$this->label) {
1059 $this->label = 'MISSING LABEL';
1060 }
1061
1062 // Clean parameters
1063 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1064 $this->ref = trim($this->ref);
1065 } else {
1066 $this->ref = dol_string_nospecial(trim($this->ref));
1067 }
1068 $this->label = trim($this->label);
1069 $this->description = trim($this->description);
1070 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1071 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1072 $this->net_measure = price2num($this->net_measure);
1073 $this->net_measure_units = trim($this->net_measure_units);
1074 $this->weight = price2num($this->weight);
1075 $this->weight_units = trim($this->weight_units);
1076 $this->length = price2num($this->length);
1077 $this->length_units = trim($this->length_units);
1078 $this->width = price2num($this->width);
1079 $this->width_units = trim($this->width_units);
1080 $this->height = price2num($this->height);
1081 $this->height_units = trim($this->height_units);
1082 $this->surface = price2num($this->surface);
1083 $this->surface_units = trim($this->surface_units);
1084 $this->volume = price2num($this->volume);
1085 $this->volume_units = trim($this->volume_units);
1086
1087 // set unit not defined
1088 if (is_numeric($this->length_units)) {
1089 $this->width_units = $this->length_units; // Not used yet
1090 }
1091 if (is_numeric($this->length_units)) {
1092 $this->height_units = $this->length_units; // Not used yet
1093 }
1094
1095 // Automated compute surface and volume if not filled
1096 if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1097 $this->surface = $this->length * $this->width;
1098 $this->surface_units = measuring_units_squared($this->length_units);
1099 }
1100 if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1101 $this->volume = $this->surface * $this->height;
1102 $this->volume_units = measuring_units_cubed($this->height_units);
1103 }
1104
1105 if (empty($this->tva_tx)) {
1106 $this->tva_tx = 0;
1107 }
1108 if (empty($this->tva_npr)) {
1109 $this->tva_npr = 0;
1110 }
1111 if (empty($this->localtax1_tx)) {
1112 $this->localtax1_tx = 0;
1113 }
1114 if (empty($this->localtax2_tx)) {
1115 $this->localtax2_tx = 0;
1116 }
1117 if (empty($this->localtax1_type)) {
1118 $this->localtax1_type = '0';
1119 }
1120 if (empty($this->localtax2_type)) {
1121 $this->localtax2_type = '0';
1122 }
1123 if (empty($this->status)) {
1124 $this->status = 0;
1125 }
1126 if (empty($this->status_buy)) {
1127 $this->status_buy = 0;
1128 }
1129
1130 if (empty($this->country_id)) {
1131 $this->country_id = 0;
1132 }
1133
1134 if (empty($this->state_id)) {
1135 $this->state_id = 0;
1136 }
1137
1138 // Barcode value
1139 $this->barcode = trim($this->barcode);
1140
1141 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1142 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
1143 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1144 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1145 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1146 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1147
1148
1149 $this->db->begin();
1150
1151 $result = 0;
1152 // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1153 if ($action != 'add') {
1154 $result = $this->verify(); // We don't check when update called during a create because verify was already done
1155 } else {
1156 // we can continue
1157 $result = 0;
1158 }
1159
1160 if ($result >= 0) {
1161 // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1162 if (empty($this->oldcopy)) {
1163 $this->oldcopy = dol_clone($this, 1);
1164 }
1165 // Test if batch management is activated on existing product
1166 // If yes, we create missing entries into product_batch
1167 if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1168 //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1169 $valueforundefinedlot = '000000';
1170 if (getDolGlobalString('STOCK_DEFAULT_BATCH')) {
1171 $valueforundefinedlot = $conf->global->STOCK_DEFAULT_BATCH;
1172 }
1173
1174 dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1175
1176 $this->load_stock();
1177 foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1178 $qty_batch = 0;
1179 foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1180 if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1181 // We discard this line, we will create it later
1182 $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1183 $result = $this->db->query($sqlclean);
1184 if (!$result) {
1185 dol_print_error($this->db);
1186 exit;
1187 }
1188 continue;
1189 }
1190
1191 $qty_batch += $detail->qty;
1192 }
1193 // Quantities in batch details are not same as stock quantity,
1194 // so we add a default batch record to complete and get same qty in parent and child table
1195 if ($ObjW->real != $qty_batch) {
1196 $ObjBatch = new Productbatch($this->db);
1197 $ObjBatch->batch = $valueforundefinedlot;
1198 $ObjBatch->qty = ($ObjW->real - $qty_batch);
1199 $ObjBatch->fk_product_stock = $ObjW->id;
1200
1201 if ($ObjBatch->create($user, 1) < 0) {
1202 $error++;
1203 $this->errors = $ObjBatch->errors;
1204 } else {
1205 // we also add lot record if not exist
1206 $ObjLot = new Productlot($this->db);
1207 if ($ObjLot->fetch(0, $this->id, $valueforundefinedlot) == 0) {
1208 $ObjLot->fk_product = $this->id;
1209 $ObjLot->entity = $this->entity;
1210 $ObjLot->fk_user_creat = $user->id;
1211 $ObjLot->batch = $valueforundefinedlot;
1212 if ($ObjLot->create($user, true) < 0) {
1213 $error++;
1214 $this->errors = $ObjLot->errors;
1215 }
1216 }
1217 }
1218 }
1219 }
1220 }
1221
1222 // For automatic creation
1223 if ($this->barcode == -1) {
1224 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1225 }
1226
1227 $sql = "UPDATE ".$this->db->prefix()."product";
1228 $sql .= " SET label = '".$this->db->escape($this->label)."'";
1229
1230 if ($updatetype && ($this->isProduct() || $this->isService())) {
1231 $sql .= ", fk_product_type = ".((int) $this->type);
1232 }
1233
1234 $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1235 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1236 $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1237 $sql .= ", tva_tx = ".((float) $this->tva_tx);
1238 $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1239 $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1240 $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1241 $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1242 $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1243
1244 $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1245 $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
1246
1247 $sql .= ", tosell = ".(int) $this->status;
1248 $sql .= ", tobuy = ".(int) $this->status_buy;
1249 $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1250 $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1251
1252 $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? "null" : (int) $this->finished);
1253 $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1254 $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1255 $sql .= ", net_measure_units = ".($this->net_measure_units != '' ? "'".$this->db->escape($this->net_measure_units)."'" : 'null');
1256 $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1257 $sql .= ", weight_units = ".($this->weight_units != '' ? "'".$this->db->escape($this->weight_units)."'" : 'null');
1258 $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1259 $sql .= ", length_units = ".($this->length_units != '' ? "'".$this->db->escape($this->length_units)."'" : 'null');
1260 $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1261 $sql .= ", width_units = ".($this->width_units != '' ? "'".$this->db->escape($this->width_units)."'" : 'null');
1262 $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1263 $sql .= ", height_units = ".($this->height_units != '' ? "'".$this->db->escape($this->height_units)."'" : 'null');
1264 $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1265 $sql .= ", surface_units = ".($this->surface_units != '' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
1266 $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1267 $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
1268 $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1269 $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1270 $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1271 $sql .= ", description = '".$this->db->escape($this->description)."'";
1272 $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1273 $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1274 $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1275 $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1276 $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1277 $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1278 $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1279 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1280 $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1281 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1282 $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1283 $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1284 $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1285 $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1286 $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1287 $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1288 }
1289 $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1290 $sql .= ", cost_price = ".($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
1291 $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1292 $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1293 $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1294 $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
1295 $sql .= ", mandatory_period = ".($this->mandatory_period);
1296 // stock field is not here because it is a denormalized value from product_stock.
1297 $sql .= " WHERE rowid = ".((int) $id);
1298
1299 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1300
1301 $resql = $this->db->query($sql);
1302 if ($resql) {
1303 $this->id = $id;
1304
1305 // Multilangs
1306 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1307 if ($this->setMultiLangs($user) < 0) {
1308 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1309 return -2;
1310 }
1311 }
1312
1313 $action = 'update';
1314
1315 // update accountancy for this entity
1316 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1317 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1318
1319 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1320 $sql .= " fk_product";
1321 $sql .= ", entity";
1322 $sql .= ", accountancy_code_buy";
1323 $sql .= ", accountancy_code_buy_intra";
1324 $sql .= ", accountancy_code_buy_export";
1325 $sql .= ", accountancy_code_sell";
1326 $sql .= ", accountancy_code_sell_intra";
1327 $sql .= ", accountancy_code_sell_export";
1328 $sql .= ") VALUES (";
1329 $sql .= $this->id;
1330 $sql .= ", " . $conf->entity;
1331 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1332 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1333 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1334 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1335 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1336 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1337 $sql .= ")";
1338 $result = $this->db->query($sql);
1339 if (!$result) {
1340 $error++;
1341 $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1342 }
1343 }
1344
1345 if (!$this->hasbatch() && $this->oldcopy->hasbatch()) {
1346 // Selection of all product stock mouvements that contains batchs
1347 $sql = 'SELECT pb.qty, ps.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb';
1348 $sql.= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = pb.fk_product_stock)';
1349 $sql.= ' WHERE ps.fk_product = '.(int) $this->id;
1350
1351 $resql = $this->db->query($sql);
1352 if ($resql) {
1353 $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S');
1354
1355 while ($obj = $this->db->fetch_object($resql)) {
1356 $value = $obj->qty;
1357 $fk_entrepot = $obj->fk_entrepot;
1358 $price = 0;
1359 $dlc = '';
1360 $dluo = '';
1361 $batch = $obj->batch;
1362
1363 // To know how to revert stockMouvement (add or remove)
1364 $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add
1365 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1366 $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true);
1367
1368 if ($res > 0) {
1369 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1370 $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0);
1371 if ($res < 0) {
1372 $error++;
1373 }
1374 } else {
1375 $error++;
1376 }
1377 }
1378 }
1379 }
1380
1381 // Actions on extra fields
1382 if (!$error) {
1383 $result = $this->insertExtraFields();
1384 if ($result < 0) {
1385 $error++;
1386 }
1387 }
1388
1389 if (!$error && !$notrigger) {
1390 // Call trigger
1391 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1392 if ($result < 0) {
1393 $error++;
1394 }
1395 // End call triggers
1396 }
1397
1398 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1399 // We remove directory
1400 if ($conf->product->dir_output) {
1401 $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1402 $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1403 if (file_exists($olddir)) {
1404 //include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1405 //$res = dol_move($olddir, $newdir);
1406 // do not use dol_move with directory
1407 $res = @rename($olddir, $newdir);
1408 if (!$res) {
1409 $langs->load("errors");
1410 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1411 $error++;
1412 }
1413 }
1414 }
1415 }
1416
1417 if (!$error) {
1418 if (isModEnabled('variants')) {
1419 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1420
1421 $comb = new ProductCombination($this->db);
1422
1423 foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1424 $currcomb->updateProperties($this, $user);
1425 }
1426 }
1427
1428 $this->db->commit();
1429 return 1;
1430 } else {
1431 $this->db->rollback();
1432 return -$error;
1433 }
1434 } else {
1435 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1436 $langs->load("errors");
1437 if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1438 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1439 } else {
1440 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1441 }
1442 $this->errors[] = $this->error;
1443 $this->db->rollback();
1444 return -1;
1445 } else {
1446 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1447 $this->errors[] = $this->error;
1448 $this->db->rollback();
1449 return -2;
1450 }
1451 }
1452 } else {
1453 $this->db->rollback();
1454 dol_syslog(get_class($this)."::Update fails verify ".join(',', $this->errors), LOG_WARNING);
1455 return -3;
1456 }
1457 }
1458
1466 public function delete(User $user, $notrigger = 0)
1467 {
1468 global $conf, $langs;
1469 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1470
1471 $error = 0;
1472
1473 // Check parameters
1474 if (empty($this->id)) {
1475 $this->error = "Object must be fetched before calling delete";
1476 return -1;
1477 }
1478 if (($this->type == Product::TYPE_PRODUCT && !$user->hasRight('produit', 'supprimer')) || ($this->type == Product::TYPE_SERVICE && !$user->hasRight('service', 'supprimer'))) {
1479 $this->error = "ErrorForbidden";
1480 return 0;
1481 }
1482
1483 $objectisused = $this->isObjectUsed($this->id);
1484 if (empty($objectisused)) {
1485 $this->db->begin();
1486
1487 if (!$error && empty($notrigger)) {
1488 // Call trigger
1489 $result = $this->call_trigger('PRODUCT_DELETE', $user);
1490 if ($result < 0) {
1491 $error++;
1492 }
1493 // End call triggers
1494 }
1495
1496 // Delete from product_batch on product delete
1497 if (!$error) {
1498 $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1499 $sql .= " WHERE fk_product_stock IN (";
1500 $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1501 $sql .= " WHERE fk_product = ".((int) $this->id).")";
1502
1503 $result = $this->db->query($sql);
1504 if (!$result) {
1505 $error++;
1506 $this->errors[] = $this->db->lasterror();
1507 }
1508 }
1509
1510 // Delete all child tables
1511 if (!$error) {
1512 $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1513 foreach ($elements as $table) {
1514 if (!$error) {
1515 $sql = "DELETE FROM ".$this->db->prefix().$table;
1516 $sql .= " WHERE fk_product = ".(int) $this->id;
1517
1518 $result = $this->db->query($sql);
1519 if (!$result) {
1520 $error++;
1521 $this->errors[] = $this->db->lasterror();
1522 }
1523 }
1524 }
1525 }
1526
1527 if (!$error) {
1528 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1529 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1530
1531 //If it is a parent product, then we remove the association with child products
1532 $prodcomb = new ProductCombination($this->db);
1533
1534 if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1535 $error++;
1536 $this->errors[] = 'Error deleting combinations';
1537 }
1538
1539 //We also check if it is a child product
1540 if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1541 $error++;
1542 $this->errors[] = 'Error deleting child combination';
1543 }
1544 }
1545
1546 // Delete from product_association
1547 if (!$error) {
1548 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1549 $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1550
1551 $result = $this->db->query($sql);
1552 if (!$result) {
1553 $error++;
1554 $this->errors[] = $this->db->lasterror();
1555 }
1556 }
1557
1558 // Remove extrafields
1559 if (!$error) {
1560 $result = $this->deleteExtraFields();
1561 if ($result < 0) {
1562 $error++;
1563 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1564 }
1565 }
1566
1567 // Delete product
1568 if (!$error) {
1569 $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1570 $sqlz .= " WHERE rowid = ".(int) $this->id;
1571
1572 $resultz = $this->db->query($sqlz);
1573 if (!$resultz) {
1574 $error++;
1575 $this->errors[] = $this->db->lasterror();
1576 }
1577 }
1578
1579 // Delete record into ECM index and physically
1580 if (!$error) {
1581 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1582 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1583 if (!$res) {
1584 $error++;
1585 }
1586 }
1587
1588 if (!$error) {
1589 // We remove directory
1590 $ref = dol_sanitizeFileName($this->ref);
1591 if ($conf->product->dir_output) {
1592 $dir = $conf->product->dir_output."/".$ref;
1593 if (file_exists($dir)) {
1595 if (!$res) {
1596 $this->errors[] = 'ErrorFailToDeleteDir';
1597 $error++;
1598 }
1599 }
1600 }
1601 }
1602
1603 if (!$error) {
1604 $this->db->commit();
1605 return 1;
1606 } else {
1607 foreach ($this->errors as $errmsg) {
1608 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1609 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1610 }
1611 $this->db->rollback();
1612 return -$error;
1613 }
1614 } else {
1615 $this->error = "ErrorRecordIsUsedCantDelete";
1616 return 0;
1617 }
1618 }
1619
1626 public function setMultiLangs($user)
1627 {
1628 global $conf, $langs;
1629
1630 $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1631 $current_lang = $langs->getDefaultLang();
1632
1633 foreach ($langs_available as $key => $value) {
1634 if ($key == $current_lang) {
1635 $sql = "SELECT rowid";
1636 $sql .= " FROM ".$this->db->prefix()."product_lang";
1637 $sql .= " WHERE fk_product = ".((int) $this->id);
1638 $sql .= " AND lang = '".$this->db->escape($key)."'";
1639
1640 $result = $this->db->query($sql);
1641
1642 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1643 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1644 $sql2 .= " SET ";
1645 $sql2 .= " label='".$this->db->escape($this->label)."',";
1646 $sql2 .= " description='".$this->db->escape($this->description)."'";
1647 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1648 $sql2 .= ", note='".$this->db->escape($this->other)."'";
1649 }
1650 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1651 } else {
1652 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1653 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1654 $sql2 .= ", note";
1655 }
1656 $sql2 .= ")";
1657 $sql2 .= " VALUES(".$this->id.",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
1658 $sql2 .= " '".$this->db->escape($this->description)."'";
1659 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1660 $sql2 .= ", '".$this->db->escape($this->other)."'";
1661 }
1662 $sql2 .= ")";
1663 }
1664 dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1665 if (!$this->db->query($sql2)) {
1666 $this->error = $this->db->lasterror();
1667 return -1;
1668 }
1669 } elseif (isset($this->multilangs[$key])) {
1670 if (empty($this->multilangs["$key"]["label"])) {
1671 $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
1672 return -1;
1673 }
1674
1675 $sql = "SELECT rowid";
1676 $sql .= " FROM ".$this->db->prefix()."product_lang";
1677 $sql .= " WHERE fk_product = ".((int) $this->id);
1678 $sql .= " AND lang = '".$this->db->escape($key)."'";
1679
1680 $result = $this->db->query($sql);
1681
1682 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1683 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1684 $sql2 .= " SET ";
1685 $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
1686 $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1687 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1688 $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1689 }
1690 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1691 } else {
1692 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1693 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1694 $sql2 .= ", note";
1695 }
1696 $sql2 .= ")";
1697 $sql2 .= " VALUES(".$this->id.",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
1698 $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
1699 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1700 $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
1701 }
1702 $sql2 .= ")";
1703 }
1704
1705 // We do not save if main fields are empty
1706 if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
1707 if (!$this->db->query($sql2)) {
1708 $this->error = $this->db->lasterror();
1709 return -1;
1710 }
1711 }
1712 } else {
1713 // language is not current language and we didn't provide a multilang description for this language
1714 }
1715 }
1716
1717 // Call trigger
1718 $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
1719 if ($result < 0) {
1720 $this->error = $this->db->lasterror();
1721 return -1;
1722 }
1723 // End call triggers
1724
1725 return 1;
1726 }
1727
1736 public function delMultiLangs($langtodelete, $user)
1737 {
1738 $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
1739 $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
1740
1741 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
1742 $result = $this->db->query($sql);
1743 if ($result) {
1744 // Call trigger
1745 $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
1746 if ($result < 0) {
1747 $this->error = $this->db->lasterror();
1748 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1749 return -1;
1750 }
1751 // End call triggers
1752 return 1;
1753 } else {
1754 $this->error = $this->db->lasterror();
1755 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
1756 return -1;
1757 }
1758 }
1759
1768 public function setAccountancyCode($type, $value)
1769 {
1770 global $user, $langs, $conf;
1771
1772 $error = 0;
1773
1774 $this->db->begin();
1775
1776 if ($type == 'buy') {
1777 $field = 'accountancy_code_buy';
1778 } elseif ($type == 'buy_intra') {
1779 $field = 'accountancy_code_buy_intra';
1780 } elseif ($type == 'buy_export') {
1781 $field = 'accountancy_code_buy_export';
1782 } elseif ($type == 'sell') {
1783 $field = 'accountancy_code_sell';
1784 } elseif ($type == 'sell_intra') {
1785 $field = 'accountancy_code_sell_intra';
1786 } elseif ($type == 'sell_export') {
1787 $field = 'accountancy_code_sell_export';
1788 } else {
1789 return -1;
1790 }
1791
1792 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
1793 $sql .= "$field = '".$this->db->escape($value)."'";
1794 $sql .= " WHERE rowid = ".((int) $this->id);
1795
1796 dol_syslog(__METHOD__, LOG_DEBUG);
1797 $resql = $this->db->query($sql);
1798
1799 if ($resql) {
1800 // Call trigger
1801 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1802 if ($result < 0) {
1803 $error++;
1804 }
1805 // End call triggers
1806
1807 if ($error) {
1808 $this->db->rollback();
1809 return -1;
1810 }
1811
1812 $this->$field = $value;
1813
1814 $this->db->commit();
1815 return 1;
1816 } else {
1817 $this->error = $this->db->lasterror();
1818 $this->db->rollback();
1819 return -1;
1820 }
1821 }
1822
1828 public function getMultiLangs()
1829 {
1830 global $langs;
1831
1832 $current_lang = $langs->getDefaultLang();
1833
1834 $sql = "SELECT lang, label, description, note as other";
1835 $sql .= " FROM ".$this->db->prefix()."product_lang";
1836 $sql .= " WHERE fk_product = ".((int) $this->id);
1837
1838 $result = $this->db->query($sql);
1839 if ($result) {
1840 while ($obj = $this->db->fetch_object($result)) {
1841 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
1842 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
1843 $this->label = $obj->label;
1844 $this->description = $obj->description;
1845 $this->other = $obj->other;
1846 }
1847 $this->multilangs["$obj->lang"]["label"] = $obj->label;
1848 $this->multilangs["$obj->lang"]["description"] = $obj->description;
1849 $this->multilangs["$obj->lang"]["other"] = $obj->other;
1850 }
1851 return 1;
1852 } else {
1853 $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
1854 return -1;
1855 }
1856 }
1857
1864 private function getArrayForPriceCompare($level = 0)
1865 {
1866 $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
1867
1868 foreach ($testExit as $field) {
1869 if (!isset($this->$field)) {
1870 return array();
1871 }
1872 $tmparray = $this->$field;
1873 if (!isset($tmparray[$level])) {
1874 return array();
1875 }
1876 }
1877
1878 $lastPrice = array(
1879 'level' => $level ? $level : 1,
1880 'multiprices' => (float) $this->multiprices[$level],
1881 'multiprices_ttc' => (float) $this->multiprices_ttc[$level],
1882 'multiprices_base_type' => $this->multiprices_base_type[$level],
1883 'multiprices_min' => (float) $this->multiprices_min[$level],
1884 'multiprices_min_ttc' => (float) $this->multiprices_min_ttc[$level],
1885 'multiprices_tva_tx' => (float) $this->multiprices_tva_tx[$level],
1886 'multiprices_recuperableonly' => (float) $this->multiprices_recuperableonly[$level],
1887 );
1888
1889 return $lastPrice;
1890 }
1891
1892
1893 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1901 private function _log_price($user, $level = 0)
1902 {
1903 // phpcs:enable
1904 global $conf;
1905
1906 $now = dol_now();
1907
1908 // Clean parameters
1909 if (empty($this->price_by_qty)) {
1910 $this->price_by_qty = 0;
1911 }
1912
1913 // Add new price
1914 $sql = "INSERT INTO ".$this->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,";
1915 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
1916 $sql .= " VALUES(".($level ? ((int) $level) : 1).", '".$this->db->idate($now)."', ".((int) $this->id).", ".((int) $user->id).", ".((float) price2num($this->price)).", ".((float) price2num($this->price_ttc)).",'".$this->db->escape($this->price_base_type)."',".((int) $this->status).", ".((float) price2num($this->tva_tx)).", ".($this->default_vat_code ? ("'".$this->db->escape($this->default_vat_code)."'") : "null").", ".((int) $this->tva_npr).",";
1917 $sql .= " ".price2num($this->localtax1_tx).", ".price2num($this->localtax2_tx).", '".$this->db->escape($this->localtax1_type)."', '".$this->db->escape($this->localtax2_type)."', ".price2num($this->price_min).", ".price2num($this->price_min_ttc).", ".price2num($this->price_by_qty).", ".((int) $conf->entity).",".($this->fk_price_expression > 0 ? ((int) $this->fk_price_expression) : 'null');
1918 $sql .= ")";
1919
1920 dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
1921 $resql = $this->db->query($sql);
1922 if (!$resql) {
1923 $this->error = $this->db->lasterror();
1924 dol_print_error($this->db);
1925 return -1;
1926 } else {
1927 return 1;
1928 }
1929 }
1930
1931
1932 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1940 public function log_price_delete($user, $rowid)
1941 {
1942 // phpcs:enable
1943 $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
1944 $sql .= " WHERE fk_product_price = ".((int) $rowid);
1945 $resql = $this->db->query($sql);
1946
1947 $sql = "DELETE FROM ".$this->db->prefix()."product_price";
1948 $sql .= " WHERE rowid=".((int) $rowid);
1949 $resql = $this->db->query($sql);
1950 if ($resql) {
1951 return 1;
1952 } else {
1953 $this->error = $this->db->lasterror();
1954 return -1;
1955 }
1956 }
1957
1958
1968 public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
1969 {
1970 global $conf, $hookmanager, $action;
1971
1972 // Call hook if any
1973 if (is_object($hookmanager)) {
1974 $parameters = array('thirdparty_seller'=>$thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
1975 // Note that $action and $object may have been modified by some hooks
1976 $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
1977 if ($reshook > 0) {
1978 return $hookmanager->resArray;
1979 }
1980 }
1981
1982 // Update if prices fields are defined
1983 $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
1984 $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
1985 if (empty($tva_tx)) {
1986 $tva_npr = 0;
1987 }
1988
1989 $pu_ht = $this->price;
1990 $pu_ttc = $this->price_ttc;
1991 $price_min = $this->price_min;
1992 $price_base_type = $this->price_base_type;
1993
1994 // If price per segment
1995 if (getDolGlobalString('PRODUIT_MULTIPRICES') && !empty($thirdparty_buyer->price_level)) {
1996 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
1997 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
1998 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
1999 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2000 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
2001 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2002 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2003 }
2004 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2005 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2006 }
2007 if (empty($tva_tx)) {
2008 $tva_npr = 0;
2009 }
2010 }
2011 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
2012 // If price per customer
2013 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2014
2015 $prodcustprice = new ProductCustomerPrice($this->db);
2016
2017 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2018
2019 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2020 if ($result) {
2021 if (count($prodcustprice->lines) > 0) {
2022 $pu_ht = price($prodcustprice->lines[0]->price);
2023 $price_min = price($prodcustprice->lines[0]->price_min);
2024 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2025 $price_base_type = $prodcustprice->lines[0]->price_base_type;
2026 $tva_tx = $prodcustprice->lines[0]->tva_tx;
2027 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2028 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2029 }
2030 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2031 if (empty($tva_tx)) {
2032 $tva_npr = 0;
2033 }
2034 }
2035 }
2036 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
2037 // If price per quantity
2038 if ($this->prices_by_qty[0]) {
2039 // yes, this product has some prices per quantity
2040 // Search price into product_price_by_qty from $this->id
2041 foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
2042 if ($priceforthequantityarray['rowid'] != $pqp) {
2043 continue;
2044 }
2045 // We found the price
2046 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2047 $pu_ht = $priceforthequantityarray['unitprice'];
2048 } else {
2049 $pu_ttc = $priceforthequantityarray['unitprice'];
2050 }
2051 break;
2052 }
2053 }
2054 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2055 // If price per quantity and customer
2056 if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
2057 // yes, this product has some prices per quantity
2058 // Search price into product_price_by_qty from $this->id
2059 foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
2060 if ($priceforthequantityarray['rowid'] != $pqp) {
2061 continue;
2062 }
2063 // We found the price
2064 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2065 $pu_ht = $priceforthequantityarray['unitprice'];
2066 } else {
2067 $pu_ttc = $priceforthequantityarray['unitprice'];
2068 }
2069 break;
2070 }
2071 }
2072 }
2073
2074 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);
2075 }
2076
2077 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2091 public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2092 {
2093 // phpcs:enable
2094 global $conf;
2095 $result = 0;
2096
2097 // We do a first search with a select by searching with couple prodfournprice and qty only (later we will search on triplet qty/product_id/fourn_ref)
2098 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2099 $sql .= " pfp.fk_product, pfp.ref_fourn as ref_supplier, pfp.desc_fourn as desc_supplier, pfp.tva_tx, pfp.default_vat_code, pfp.fk_supplier_price_expression,";
2100 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2101 $sql .= " pfp.packaging";
2102 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2103 $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2104 if ($qty > 0) {
2105 $sql .= " AND pfp.quantity <= ".((float) $qty);
2106 }
2107 $sql .= " ORDER BY pfp.quantity DESC";
2108
2109 dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
2110 $resql = $this->db->query($sql);
2111 if ($resql) {
2112 $obj = $this->db->fetch_object($resql);
2113 if ($obj && $obj->quantity > 0) { // If we found a supplier prices from the id of supplier price
2114 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2115 $prod_supplier = new ProductFournisseur($this->db);
2116 $prod_supplier->product_fourn_price_id = $obj->rowid;
2117 $prod_supplier->id = $obj->fk_product;
2118 $prod_supplier->fourn_qty = $obj->quantity;
2119 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2120 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2121
2122 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2123 $priceparser = new PriceParser($this->db);
2124 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2125 if ($price_result >= 0) {
2126 $obj->price = $price_result;
2127 }
2128 }
2129 $this->product_fourn_price_id = $obj->rowid;
2130 $this->buyprice = $obj->price; // deprecated
2131 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2132 $this->fourn_price_base_type = 'HT'; // Price base type
2133 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2134 $this->ref_fourn = $obj->ref_supplier; // deprecated
2135 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2136 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2137 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2138 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2139 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2140 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2141 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2142 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2143 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2144 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2145 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2146 $this->packaging = $obj->packaging;
2147 }
2148 $result = $obj->fk_product;
2149 return $result;
2150 } else { // If not found
2151 // 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.
2152 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2153 $sql .= " pfp.fk_product, pfp.ref_fourn as ref_supplier, pfp.desc_fourn as desc_supplier, pfp.tva_tx, pfp.default_vat_code, pfp.fk_supplier_price_expression,";
2154 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2155 $sql .= " pfp.packaging";
2156 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2157 $sql .= " WHERE 1 = 1";
2158 if ($product_id > 0) {
2159 $sql .= " AND pfp.fk_product = ".((int) $product_id);
2160 }
2161 if ($fourn_ref != 'none') {
2162 $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2163 }
2164 if ($fk_soc > 0) {
2165 $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2166 }
2167 if ($qty > 0) {
2168 $sql .= " AND pfp.quantity <= ".((float) $qty);
2169 }
2170 $sql .= " ORDER BY pfp.quantity DESC";
2171 $sql .= " LIMIT 1";
2172
2173 dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2174 $resql = $this->db->query($sql);
2175 if ($resql) {
2176 $obj = $this->db->fetch_object($resql);
2177 if ($obj && $obj->quantity > 0) { // If found
2178 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2179 $prod_supplier = new ProductFournisseur($this->db);
2180 $prod_supplier->product_fourn_price_id = $obj->rowid;
2181 $prod_supplier->id = $obj->fk_product;
2182 $prod_supplier->fourn_qty = $obj->quantity;
2183 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2184 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2185
2186 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2187 $priceparser = new PriceParser($this->db);
2188 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2189 if ($result >= 0) {
2190 $obj->price = $price_result;
2191 }
2192 }
2193 $this->product_fourn_price_id = $obj->rowid;
2194 $this->buyprice = $obj->price; // deprecated
2195 $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2196 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2197 $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2198 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2199 $this->ref_fourn = $obj->ref_supplier; // deprecated
2200 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2201 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2202 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2203 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2204 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2205 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2206 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2207 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2208 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2209 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2210 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2211 $this->packaging = $obj->packaging;
2212 }
2213 $result = $obj->fk_product;
2214 return $result;
2215 } else {
2216 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é.
2217 }
2218 } else {
2219 $this->error = $this->db->lasterror();
2220 return -3;
2221 }
2222 }
2223 } else {
2224 $this->error = $this->db->lasterror();
2225 return -2;
2226 }
2227 }
2228
2229
2247 public function updatePrice($newprice, $newpricebase, $user, $newvat = '', $newminprice = 0, $level = 0, $newnpr = 0, $newpbq = 0, $ignore_autogen = 0, $localtaxes_array = array(), $newdefaultvatcode = '', $notrigger = 0)
2248 {
2249 global $conf, $langs;
2250
2251 $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2252
2253 $id = $this->id;
2254
2255 dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2256
2257 // Clean parameters
2258 if (empty($this->tva_tx)) {
2259 $this->tva_tx = 0;
2260 }
2261 if (empty($newnpr)) {
2262 $newnpr = 0;
2263 }
2264 if (empty($newminprice)) {
2265 $newminprice = 0;
2266 }
2267 if (empty($newminprice)) {
2268 $newminprice = 0;
2269 }
2270
2271 // Check parameters
2272 if ($newvat == '') {
2273 $newvat = $this->tva_tx;
2274 }
2275
2276 // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2277 // Price will be modified ONLY when the first one is the one that is being modified
2278 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2279 return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2280 }
2281
2282 if (!empty($newminprice) && ($newminprice > $newprice)) {
2283 $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2284 return -1;
2285 }
2286
2287 if ($newprice !== '' || $newprice === 0) {
2288 if ($newpricebase == 'TTC') {
2289 $price_ttc = price2num($newprice, 'MU');
2290 $price = price2num($newprice) / (1 + ($newvat / 100));
2291 $price = price2num($price, 'MU');
2292
2293 if ($newminprice != '' || $newminprice == 0) {
2294 $price_min_ttc = price2num($newminprice, 'MU');
2295 $price_min = price2num($newminprice) / (1 + ($newvat / 100));
2296 $price_min = price2num($price_min, 'MU');
2297 } else {
2298 $price_min = 0;
2299 $price_min_ttc = 0;
2300 }
2301 } else {
2302 $price = price2num($newprice, 'MU');
2303 $price_ttc = ($newnpr != 1) ? (float) price2num($newprice) * (1 + ($newvat / 100)) : $price;
2304 $price_ttc = price2num($price_ttc, 'MU');
2305
2306 if ($newminprice !== '' || $newminprice === 0) {
2307 $price_min = price2num($newminprice, 'MU');
2308 $price_min_ttc = price2num($newminprice) * (1 + ($newvat / 100));
2309 $price_min_ttc = price2num($price_min_ttc, 'MU');
2310 //print 'X'.$newminprice.'-'.$price_min;
2311 } else {
2312 $price_min = 0;
2313 $price_min_ttc = 0;
2314 }
2315 }
2316 //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2317
2318 if (count($localtaxes_array) > 0) {
2319 $localtaxtype1 = $localtaxes_array['0'];
2320 $localtax1 = $localtaxes_array['1'];
2321 $localtaxtype2 = $localtaxes_array['2'];
2322 $localtax2 = $localtaxes_array['3'];
2323 } else {
2324 // if array empty, we try to use the vat code
2325 if (!empty($newdefaultvatcode)) {
2326 global $mysoc;
2327 // Get record from code
2328 $sql = "SELECT t.rowid, t.code, t.recuperableonly as tva_npr, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2329 $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2330 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2331 $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2332 $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2333 $resql = $this->db->query($sql);
2334 if ($resql) {
2335 $obj = $this->db->fetch_object($resql);
2336 if ($obj) {
2337 $npr = $obj->tva_npr;
2338 $localtax1 = $obj->localtax1;
2339 $localtax2 = $obj->localtax2;
2340 $localtaxtype1 = $obj->localtax1_type;
2341 $localtaxtype2 = $obj->localtax2_type;
2342 }
2343 }
2344 } else {
2345 // old method. deprecated because we can't retrieve type
2346 $localtaxtype1 = '0';
2347 $localtax1 = get_localtax($newvat, 1);
2348 $localtaxtype2 = '0';
2349 $localtax2 = get_localtax($newvat, 2);
2350 }
2351 }
2352 if (empty($localtax1)) {
2353 $localtax1 = 0; // If = '' then = 0
2354 }
2355 if (empty($localtax2)) {
2356 $localtax2 = 0; // If = '' then = 0
2357 }
2358
2359 $this->db->begin();
2360
2361 // Ne pas mettre de quote sur les numeriques decimaux.
2362 // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2363 $sql = "UPDATE ".$this->db->prefix()."product SET";
2364 $sql .= " price_base_type = '".$this->db->escape($newpricebase)."',";
2365 $sql .= " price = ".(float) $price.",";
2366 $sql .= " price_ttc = ".(float) $price_ttc.",";
2367 $sql .= " price_min = ".(float) $price_min.",";
2368 $sql .= " price_min_ttc = ".(float) $price_min_ttc.",";
2369 $sql .= " localtax1_tx = ".($localtax1 >= 0 ? (float) $localtax1 : 'NULL').",";
2370 $sql .= " localtax2_tx = ".($localtax2 >= 0 ? (float) $localtax2 : 'NULL').",";
2371 $sql .= " localtax1_type = ".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2372 $sql .= " localtax2_type = ".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2373 $sql .= " default_vat_code = ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2374 $sql .= " tva_tx = ".(float) price2num($newvat).",";
2375 $sql .= " recuperableonly = '".$this->db->escape($newnpr)."'";
2376 $sql .= " WHERE rowid = ".((int) $id);
2377
2378 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2379 $resql = $this->db->query($sql);
2380 if ($resql) {
2381 $this->multiprices[$level] = $price;
2382 $this->multiprices_ttc[$level] = $price_ttc;
2383 $this->multiprices_min[$level] = $price_min;
2384 $this->multiprices_min_ttc[$level] = $price_min_ttc;
2385 $this->multiprices_base_type[$level] = $newpricebase;
2386 $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2387 $this->multiprices_tva_tx[$level] = $newvat;
2388 $this->multiprices_recuperableonly[$level] = $newnpr;
2389
2390 $this->price = $price;
2391 $this->price_ttc = $price_ttc;
2392 $this->price_min = $price_min;
2393 $this->price_min_ttc = $price_min_ttc;
2394 $this->price_base_type = $newpricebase;
2395 $this->default_vat_code = $newdefaultvatcode;
2396 $this->tva_tx = $newvat;
2397 $this->tva_npr = $newnpr;
2398 //Local taxes
2399 $this->localtax1_tx = $localtax1;
2400 $this->localtax2_tx = $localtax2;
2401 $this->localtax1_type = $localtaxtype1;
2402 $this->localtax2_type = $localtaxtype2;
2403
2404 // Price by quantity
2405 $this->price_by_qty = $newpbq;
2406
2407 // check if price have really change before log
2408 $newPriceData = $this->getArrayForPriceCompare($level);
2409 if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || !getDolGlobalString('PRODUIT_MULTIPRICES')) {
2410 $this->_log_price($user, $level); // Save price for level into table product_price
2411 }
2412
2413 $this->level = $level; // Store level of price edited for trigger
2414
2415 // Call trigger
2416 if (!$notrigger) {
2417 $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2418 if ($result < 0) {
2419 $this->db->rollback();
2420 return -1;
2421 }
2422 }
2423 // End call triggers
2424
2425 $this->db->commit();
2426 } else {
2427 $this->db->rollback();
2428 $this->error = $this->db->lasterror();
2429 return -1;
2430 }
2431 }
2432
2433 return 1;
2434 }
2435
2443 public function setPriceExpression($expression_id)
2444 {
2445 global $user;
2446
2447 $this->fk_price_expression = $expression_id;
2448
2449 return $this->update($this->id, $user);
2450 }
2451
2464 public function fetch($id = '', $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2465 {
2466 include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2467
2468 global $langs, $conf;
2469
2470 dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2471
2472 // Check parameters
2473 if (!$id && !$ref && !$ref_ext && !$barcode) {
2474 $this->error = 'ErrorWrongParameters';
2475 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2476 return -1;
2477 }
2478
2479 $sql = "SELECT p.rowid, p.ref, p.ref_ext, p.label, p.description, p.url, p.note_public, p.note as note_private, p.customcode, p.fk_country, p.fk_state, p.lifetime, p.qc_frequency, p.price, p.price_ttc,";
2480 $sql .= " p.price_min, p.price_min_ttc, p.price_base_type, p.cost_price, p.default_vat_code, p.tva_tx, p.recuperableonly as tva_npr, p.localtax1_tx, p.localtax2_tx, p.localtax1_type, p.localtax2_type, p.tosell,";
2481 $sql .= " p.tobuy, p.fk_product_type, p.duration, p.fk_default_warehouse, p.fk_default_workstation, p.seuil_stock_alerte, p.canvas, p.net_measure, p.net_measure_units, p.weight, p.weight_units,";
2482 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2483 $sql .= " p.surface, p.surface_units, p.volume, p.volume_units, p.barcode, p.fk_barcode_type, p.finished, p.fk_default_bom, p.mandatory_period,";
2484 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2485 $sql .= " p.accountancy_code_buy, p.accountancy_code_buy_intra, p.accountancy_code_buy_export, p.accountancy_code_sell, p.accountancy_code_sell_intra, p.accountancy_code_sell_export,";
2486 } else {
2487 $sql .= " ppe.accountancy_code_buy, ppe.accountancy_code_buy_intra, ppe.accountancy_code_buy_export, ppe.accountancy_code_sell, ppe.accountancy_code_sell_intra, ppe.accountancy_code_sell_export,";
2488 }
2489
2490 //For MultiCompany
2491 //PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
2492 $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
2493 $separatedStock = false; // Set to true will count stock from subtable llx_product_stock. It is slower than using denormalized field 'stock', but it is required when using multientity and shared warehouses.
2494 $visibleWarehousesEntities = $conf->entity;
2495 if (getDolGlobalString('MULTICOMPANY_PRODUCT_SHARING_ENABLED')) {
2496 if (getDolGlobalString('MULTICOMPANY_PMP_PER_ENTITY_ENABLED')) {
2497 $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
2498 if ($this->db->num_rows($checkPMPPerEntity)>0) {
2499 $separatedEntityPMP = true;
2500 }
2501 }
2502 global $mc;
2503 $separatedStock = true;
2504 if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
2505 $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
2506 }
2507 }
2508 if ($separatedEntityPMP) {
2509 $sql .= " ppe.pmp,";
2510 } else {
2511 $sql .= " p.pmp,";
2512 }
2513 $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit,";
2514 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf,";
2515 if ($separatedStock) {
2516 $sql .= " SUM(sp.reel) as stock";
2517 } else {
2518 $sql .= " p.stock";
2519 }
2520 $sql .= " FROM ".$this->db->prefix()."product as p";
2521 if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') || $separatedEntityPMP) {
2522 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
2523 }
2524 if ($separatedStock) {
2525 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_stock as sp ON sp.fk_product = p.rowid AND sp.fk_entrepot IN (SELECT rowid FROM ".$this->db->prefix()."entrepot WHERE entity IN (".$this->db->sanitize($visibleWarehousesEntities)."))";
2526 }
2527
2528 if ($id) {
2529 $sql .= " WHERE p.rowid = ".((int) $id);
2530 } else {
2531 $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
2532 if ($ref) {
2533 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
2534 } elseif ($ref_ext) {
2535 $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
2536 } elseif ($barcode) {
2537 $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
2538 }
2539 }
2540 if ($separatedStock) {
2541 $sql .= " GROUP BY p.rowid, p.ref, p.ref_ext, p.label, p.description, p.url, p.note_public, p.note, p.customcode, p.fk_country, p.fk_state, p.lifetime, p.qc_frequency, p.price, p.price_ttc,";
2542 $sql .= " p.price_min, p.price_min_ttc, p.price_base_type, p.cost_price, p.default_vat_code, p.tva_tx, p.recuperableonly, p.localtax1_tx, p.localtax2_tx, p.localtax1_type, p.localtax2_type, p.tosell,";
2543 $sql .= " p.tobuy, p.fk_product_type, p.duration, p.fk_default_warehouse, p.fk_default_workstation, p.seuil_stock_alerte, p.canvas, p.net_measure, p.net_measure_units, p.weight, p.weight_units,";
2544 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2545 $sql .= " p.surface, p.surface_units, p.volume, p.volume_units, p.barcode, p.fk_barcode_type, p.finished, p.fk_default_bom, p.mandatory_period,";
2546 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2547 $sql .= " p.accountancy_code_buy, p.accountancy_code_buy_intra, p.accountancy_code_buy_export, p.accountancy_code_sell, p.accountancy_code_sell_intra, p.accountancy_code_sell_export,";
2548 } else {
2549 $sql .= " ppe.accountancy_code_buy, ppe.accountancy_code_buy_intra, ppe.accountancy_code_buy_export, ppe.accountancy_code_sell, ppe.accountancy_code_sell_intra, ppe.accountancy_code_sell_export,";
2550 }
2551 if ($separatedEntityPMP) {
2552 $sql .= " ppe.pmp,";
2553 } else {
2554 $sql .= " p.pmp,";
2555 }
2556 $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit,";
2557 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf";
2558 if (!$separatedStock) {
2559 $sql .= ", p.stock";
2560 }
2561 }
2562
2563 $resql = $this->db->query($sql);
2564 if ($resql) {
2565 unset($this->oldcopy);
2566
2567 if ($this->db->num_rows($resql) > 0) {
2568 $obj = $this->db->fetch_object($resql);
2569
2570 $this->id = $obj->rowid;
2571 $this->ref = $obj->ref;
2572 $this->ref_ext = $obj->ref_ext;
2573 $this->label = $obj->label;
2574 $this->description = $obj->description;
2575 $this->url = $obj->url;
2576 $this->note_public = $obj->note_public;
2577 $this->note_private = $obj->note_private;
2578 $this->note = $obj->note_private; // deprecated
2579
2580 $this->type = $obj->fk_product_type;
2581 $this->status = $obj->tosell;
2582 $this->status_buy = $obj->tobuy;
2583 $this->status_batch = $obj->tobatch;
2584 $this->batch_mask = $obj->batch_mask;
2585
2586 $this->customcode = $obj->customcode;
2587 $this->country_id = $obj->fk_country;
2588 $this->country_code = getCountry($this->country_id, 2, $this->db);
2589 $this->state_id = $obj->fk_state;
2590 $this->lifetime = $obj->lifetime;
2591 $this->qc_frequency = $obj->qc_frequency;
2592 $this->price = $obj->price;
2593 $this->price_ttc = $obj->price_ttc;
2594 $this->price_min = $obj->price_min;
2595 $this->price_min_ttc = $obj->price_min_ttc;
2596 $this->price_base_type = $obj->price_base_type;
2597 $this->cost_price = $obj->cost_price;
2598 $this->default_vat_code = $obj->default_vat_code;
2599 $this->tva_tx = $obj->tva_tx;
2601 $this->tva_npr = $obj->tva_npr;
2603 $this->localtax1_tx = $obj->localtax1_tx;
2604 $this->localtax2_tx = $obj->localtax2_tx;
2605 $this->localtax1_type = $obj->localtax1_type;
2606 $this->localtax2_type = $obj->localtax2_type;
2607
2608 $this->finished = $obj->finished;
2609 $this->fk_default_bom = $obj->fk_default_bom;
2610
2611 $this->duration = $obj->duration;
2612 $this->duration_value = $obj->duration ? (int) (substr($obj->duration, 0, dol_strlen($obj->duration) - 1)) : 0;
2613 $this->duration_unit = $obj->duration ? substr($obj->duration, -1) : null;
2614 $this->canvas = $obj->canvas;
2615 $this->net_measure = $obj->net_measure;
2616 $this->net_measure_units = $obj->net_measure_units;
2617 $this->weight = $obj->weight;
2618 $this->weight_units = $obj->weight_units;
2619 $this->length = $obj->length;
2620 $this->length_units = $obj->length_units;
2621 $this->width = $obj->width;
2622 $this->width_units = $obj->width_units;
2623 $this->height = $obj->height;
2624 $this->height_units = $obj->height_units;
2625
2626 $this->surface = $obj->surface;
2627 $this->surface_units = $obj->surface_units;
2628 $this->volume = $obj->volume;
2629 $this->volume_units = $obj->volume_units;
2630 $this->barcode = $obj->barcode;
2631 $this->barcode_type = $obj->fk_barcode_type;
2632
2633 $this->accountancy_code_buy = $obj->accountancy_code_buy;
2634 $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
2635 $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
2636 $this->accountancy_code_sell = $obj->accountancy_code_sell;
2637 $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
2638 $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
2639
2640 $this->fk_default_warehouse = $obj->fk_default_warehouse;
2641 $this->fk_default_workstation = $obj->fk_default_workstation;
2642 $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
2643 $this->desiredstock = $obj->desiredstock;
2644 $this->stock_reel = $obj->stock;
2645 $this->pmp = $obj->pmp;
2646
2647 $this->date_creation = $obj->datec;
2648 $this->date_modification = $obj->tms;
2649 $this->import_key = $obj->import_key;
2650 $this->entity = $obj->entity;
2651
2652 $this->ref_ext = $obj->ref_ext;
2653 $this->fk_price_expression = $obj->fk_price_expression;
2654 $this->fk_unit = $obj->fk_unit;
2655 $this->price_autogen = $obj->price_autogen;
2656 $this->model_pdf = $obj->model_pdf;
2657
2658 $this->mandatory_period = $obj->mandatory_period;
2659
2660 $this->db->free($resql);
2661
2662 // fetch optionals attributes and labels
2663 $this->fetch_optionals();
2664
2665 // Multilangs
2666 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
2667 $this->getMultiLangs();
2668 }
2669
2670 // Load multiprices array
2671 if (getDolGlobalString('PRODUIT_MULTIPRICES') && empty($ignore_price_load)) { // prices per segment
2672 for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
2673 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2674 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2675 $sql .= " FROM ".$this->db->prefix()."product_price";
2676 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2677 $sql .= " AND price_level=".((int) $i);
2678 $sql .= " AND fk_product = ".((int) $this->id);
2679 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
2680 $sql .= " LIMIT 1"; // Only the first one
2681 $resql = $this->db->query($sql);
2682 if ($resql) {
2683 $result = $this->db->fetch_array($resql);
2684
2685 $this->multiprices[$i] = $result ? $result["price"] : null;
2686 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
2687 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
2688 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
2689 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
2690 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2691 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].($result ? ' ('.$result['default_vat_code'].')' : '') : null;
2692 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
2693
2694 // Price by quantity
2695 /*
2696 $this->prices_by_qty[$i]=$result["price_by_qty"];
2697 $this->prices_by_qty_id[$i]=$result["rowid"];
2698 // Récuperation de la liste des prix selon qty si flag positionné
2699 if ($this->prices_by_qty[$i] == 1)
2700 {
2701 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2702 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
2703 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2704 $sql.= " ORDER BY quantity ASC";
2705 $resultat=array();
2706 $resql = $this->db->query($sql);
2707 if ($resql)
2708 {
2709 $ii=0;
2710 while ($result= $this->db->fetch_array($resql)) {
2711 $resultat[$ii]=array();
2712 $resultat[$ii]["rowid"]=$result["rowid"];
2713 $resultat[$ii]["price"]= $result["price"];
2714 $resultat[$ii]["unitprice"]= $result["unitprice"];
2715 $resultat[$ii]["quantity"]= $result["quantity"];
2716 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
2717 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
2718 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
2719 $ii++;
2720 }
2721 $this->prices_by_qty_list[$i]=$resultat;
2722 }
2723 else
2724 {
2725 dol_print_error($this->db);
2726 return -1;
2727 }
2728 }*/
2729 } else {
2730 $this->error = $this->db->lasterror;
2731 return -1;
2732 }
2733 }
2734 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES') && empty($ignore_price_load)) { // prices per customers
2735 // Nothing loaded by default. List may be very long.
2736 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
2737 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2738 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
2739 $sql .= " FROM ".$this->db->prefix()."product_price";
2740 $sql .= " WHERE fk_product = ".((int) $this->id);
2741 $sql .= " ORDER BY date_price DESC, rowid DESC";
2742 $sql .= " LIMIT 1";
2743 $resql = $this->db->query($sql);
2744 if ($resql) {
2745 $result = $this->db->fetch_array($resql);
2746
2747 // Price by quantity
2748 $this->prices_by_qty[0] = $result["price_by_qty"];
2749 $this->prices_by_qty_id[0] = $result["rowid"];
2750 // Récuperation de la liste des prix selon qty si flag positionné
2751 if ($this->prices_by_qty[0] == 1) {
2752 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
2753 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2754 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
2755 $sql .= " ORDER BY quantity ASC";
2756 $resultat = array();
2757 $resql = $this->db->query($sql);
2758 if ($resql) {
2759 $ii = 0;
2760 while ($result = $this->db->fetch_array($resql)) {
2761 $resultat[$ii] = array();
2762 $resultat[$ii]["rowid"] = $result["rowid"];
2763 $resultat[$ii]["price"] = $result["price"];
2764 $resultat[$ii]["unitprice"] = $result["unitprice"];
2765 $resultat[$ii]["quantity"] = $result["quantity"];
2766 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2767 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
2768 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2769 $ii++;
2770 }
2771 $this->prices_by_qty_list[0] = $resultat;
2772 } else {
2773 $this->error = $this->db->lasterror;
2774 return -1;
2775 }
2776 }
2777 } else {
2778 $this->error = $this->db->lasterror;
2779 return -1;
2780 }
2781 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
2782 for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
2783 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
2784 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
2785 $sql .= " FROM ".$this->db->prefix()."product_price";
2786 $sql .= " WHERE entity IN (".getEntity('productprice').")";
2787 $sql .= " AND price_level=".((int) $i);
2788 $sql .= " AND fk_product = ".((int) $this->id);
2789 $sql .= " ORDER BY date_price DESC, rowid DESC";
2790 $sql .= " LIMIT 1";
2791 $resql = $this->db->query($sql);
2792 if ($resql) {
2793 $result = $this->db->fetch_array($resql);
2794
2795 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
2796 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
2797 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
2798 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
2799 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
2800 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
2801 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
2802 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
2803
2804 // Price by quantity
2805 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
2806 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
2807 // Récuperation de la liste des prix selon qty si flag positionné
2808 if ($this->prices_by_qty[$i] == 1) {
2809 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
2810 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
2811 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
2812 $sql .= " ORDER BY quantity ASC";
2813 $resultat = array();
2814 $resql = $this->db->query($sql);
2815 if ($resql) {
2816 $ii = 0;
2817 while ($result = $this->db->fetch_array($resql)) {
2818 $resultat[$ii] = array();
2819 $resultat[$ii]["rowid"] = $result["rowid"];
2820 $resultat[$ii]["price"] = $result["price"];
2821 $resultat[$ii]["unitprice"] = $result["unitprice"];
2822 $resultat[$ii]["quantity"] = $result["quantity"];
2823 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
2824 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
2825 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
2826 $ii++;
2827 }
2828 $this->prices_by_qty_list[$i] = $resultat;
2829 } else {
2830 $this->error = $this->db->lasterror;
2831 return -1;
2832 }
2833 }
2834 } else {
2835 $this->error = $this->db->lasterror;
2836 return -1;
2837 }
2838 }
2839 }
2840
2841 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
2842 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2843 $priceparser = new PriceParser($this->db);
2844 $price_result = $priceparser->parseProduct($this);
2845 if ($price_result >= 0) {
2846 $this->price = $price_result;
2847 // Calculate the VAT
2848 $this->price_ttc = price2num($this->price) * (1 + ($this->tva_tx / 100));
2849 $this->price_ttc = price2num($this->price_ttc, 'MU');
2850 }
2851 }
2852
2853 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
2854 // Instead we just init the stock_warehouse array
2855 $this->stock_warehouse = array();
2856
2857 return 1;
2858 } else {
2859 return 0;
2860 }
2861 } else {
2862 $this->error = $this->db->lasterror();
2863 return -1;
2864 }
2865 }
2866
2867 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2874 public function load_stats_mo($socid = 0)
2875 {
2876 // phpcs:enable
2877 global $user, $hookmanager, $action;
2878
2879 $error = 0;
2880
2881 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
2882 $this->stats_mo['customers_'.$role] = 0;
2883 $this->stats_mo['nb_'.$role] = 0;
2884 $this->stats_mo['qty_'.$role] = 0;
2885
2886 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
2887 $sql .= " SUM(mp.qty) as qty";
2888 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
2889 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
2890 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
2891 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc=c.fk_soc AND sc.fk_user = ".((int) $user->id);
2892 }
2893 $sql .= " WHERE ";
2894 $sql .= " c.entity IN (".getEntity('mo').")";
2895
2896 $sql .= " AND mp.fk_product = ".((int) $this->id);
2897 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
2898 if ($socid > 0) {
2899 $sql .= " AND c.fk_soc = ".((int) $socid);
2900 }
2901
2902 $result = $this->db->query($sql);
2903 if ($result) {
2904 $obj = $this->db->fetch_object($result);
2905 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
2906 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
2907 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
2908 } else {
2909 $this->error = $this->db->error();
2910 $error++;
2911 }
2912 }
2913
2914 if (!empty($error)) {
2915 return -1;
2916 }
2917
2918 $parameters = array('socid' => $socid);
2919 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2920 if ($reshook > 0) {
2921 $this->stats_mo = $hookmanager->resArray['stats_mo'];
2922 }
2923
2924 return 1;
2925 }
2926
2927 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2934 public function load_stats_bom($socid = 0)
2935 {
2936 // phpcs:enable
2937 global $user, $hookmanager, $action;
2938
2939 $error = 0;
2940
2941 $this->stats_bom['nb_toproduce'] = 0;
2942 $this->stats_bom['nb_toconsume'] = 0;
2943 $this->stats_bom['qty_toproduce'] = 0;
2944 $this->stats_bom['qty_toconsume'] = 0;
2945
2946 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
2947 $sql .= " SUM(b.qty) as qty_toproduce";
2948 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
2949 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
2950 $sql .= " WHERE ";
2951 $sql .= " b.entity IN (".getEntity('bom').")";
2952 $sql .= " AND b.fk_product =".((int) $this->id);
2953 $sql .= " GROUP BY b.rowid";
2954
2955 $result = $this->db->query($sql);
2956 if ($result) {
2957 $obj = $this->db->fetch_object($result);
2958 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
2959 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
2960 } else {
2961 $this->error = $this->db->error();
2962 $error++;
2963 }
2964
2965 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
2966 $sql .= " SUM(bl.qty) as qty_toconsume";
2967 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
2968 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
2969 $sql .= " WHERE ";
2970 $sql .= " b.entity IN (".getEntity('bom').")";
2971 $sql .= " AND bl.fk_product =".((int) $this->id);
2972
2973 $result = $this->db->query($sql);
2974 if ($result) {
2975 $obj = $this->db->fetch_object($result);
2976 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
2977 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
2978 } else {
2979 $this->error = $this->db->error();
2980 $error++;
2981 }
2982
2983 if (!empty($error)) {
2984 return -1;
2985 }
2986
2987 $parameters = array('socid' => $socid);
2988 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
2989 if ($reshook > 0) {
2990 $this->stats_bom = $hookmanager->resArray['stats_bom'];
2991 }
2992
2993 return 1;
2994 }
2995
2996 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3003 public function load_stats_propale($socid = 0)
3004 {
3005 // phpcs:enable
3006 global $conf, $user, $hookmanager, $action;
3007
3008 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3009 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3010 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3011 $sql .= ", ".$this->db->prefix()."propal as p";
3012 $sql .= ", ".$this->db->prefix()."societe as s";
3013 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3014 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3015 }
3016 $sql .= " WHERE p.rowid = pd.fk_propal";
3017 $sql .= " AND p.fk_soc = s.rowid";
3018 $sql .= " AND p.entity IN (".getEntity('propal').")";
3019 $sql .= " AND pd.fk_product = ".((int) $this->id);
3020 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3021 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3022 }
3023 //$sql.= " AND pr.fk_statut != 0";
3024 if ($socid > 0) {
3025 $sql .= " AND p.fk_soc = ".((int) $socid);
3026 }
3027
3028 $result = $this->db->query($sql);
3029 if ($result) {
3030 $obj = $this->db->fetch_object($result);
3031 $this->stats_propale['customers'] = $obj->nb_customers;
3032 $this->stats_propale['nb'] = $obj->nb;
3033 $this->stats_propale['rows'] = $obj->nb_rows;
3034 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3035
3036 // if it's a virtual product, maybe it is in proposal by extension
3037 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3038 $TFather = $this->getFather();
3039 if (is_array($TFather) && !empty($TFather)) {
3040 foreach ($TFather as &$fatherData) {
3041 $pFather = new Product($this->db);
3042 $pFather->id = $fatherData['id'];
3043 $qtyCoef = $fatherData['qty'];
3044
3045 if ($fatherData['incdec']) {
3046 $pFather->load_stats_propale($socid);
3047
3048 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3049 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3050 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3051 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3052 }
3053 }
3054 }
3055 }
3056
3057 $parameters = array('socid' => $socid);
3058 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3059 if ($reshook > 0) {
3060 $this->stats_propale = $hookmanager->resArray['stats_propale'];
3061 }
3062
3063 return 1;
3064 } else {
3065 $this->error = $this->db->error();
3066 return -1;
3067 }
3068 }
3069
3070
3071 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3078 public function load_stats_proposal_supplier($socid = 0)
3079 {
3080 // phpcs:enable
3081 global $conf, $user, $hookmanager, $action;
3082
3083 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3084 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3085 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3086 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3087 $sql .= ", ".$this->db->prefix()."societe as s";
3088 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3089 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3090 }
3091 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3092 $sql .= " AND p.fk_soc = s.rowid";
3093 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3094 $sql .= " AND pd.fk_product = ".((int) $this->id);
3095 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3096 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3097 }
3098 //$sql.= " AND pr.fk_statut != 0";
3099 if ($socid > 0) {
3100 $sql .= " AND p.fk_soc = ".((int) $socid);
3101 }
3102
3103 $result = $this->db->query($sql);
3104 if ($result) {
3105 $obj = $this->db->fetch_object($result);
3106 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3107 $this->stats_proposal_supplier['nb'] = $obj->nb;
3108 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3109 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3110
3111 $parameters = array('socid' => $socid);
3112 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3113 if ($reshook > 0) {
3114 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3115 }
3116
3117 return 1;
3118 } else {
3119 $this->error = $this->db->error();
3120 return -1;
3121 }
3122 }
3123
3124
3125 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3134 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3135 {
3136 // phpcs:enable
3137 global $conf, $user, $hookmanager, $action;
3138
3139 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3140 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3141 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3142 $sql .= ", ".$this->db->prefix()."commande as c";
3143 $sql .= ", ".$this->db->prefix()."societe as s";
3144 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3145 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3146 }
3147 $sql .= " WHERE c.rowid = cd.fk_commande";
3148 $sql .= " AND c.fk_soc = s.rowid";
3149 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3150 $sql .= " AND cd.fk_product = ".((int) $this->id);
3151 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3152 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3153 }
3154 if ($socid > 0) {
3155 $sql .= " AND c.fk_soc = ".((int) $socid);
3156 }
3157 if ($filtrestatut != '') {
3158 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")";
3159 }
3160
3161 $result = $this->db->query($sql);
3162 if ($result) {
3163 $obj = $this->db->fetch_object($result);
3164 $this->stats_commande['customers'] = $obj->nb_customers;
3165 $this->stats_commande['nb'] = $obj->nb;
3166 $this->stats_commande['rows'] = $obj->nb_rows;
3167 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3168
3169 // if it's a virtual product, maybe it is in order by extension
3170 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3171 $TFather = $this->getFather();
3172 if (is_array($TFather) && !empty($TFather)) {
3173 foreach ($TFather as &$fatherData) {
3174 $pFather = new Product($this->db);
3175 $pFather->id = $fatherData['id'];
3176 $qtyCoef = $fatherData['qty'];
3177
3178 if ($fatherData['incdec']) {
3179 $pFather->load_stats_commande($socid, $filtrestatut);
3180
3181 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3182 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3183 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3184 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3185 }
3186 }
3187 }
3188 }
3189
3190 // If stock decrease is on invoice validation, the theorical stock continue to
3191 // count the orders to ship in theorical stock when some are already removed by invoice validation.
3192 if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3193 if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3194 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3195 $adeduire = 0;
3196 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3197 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3198 $sql .= " JOIN ".$this->db->prefix()."element_element as el ON ((el.fk_target = f.rowid AND el.targettype = 'facture' AND sourcetype = 'commande') OR (el.fk_source = f.rowid AND el.targettype = 'commande' AND sourcetype = 'facture'))";
3199 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3200 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3201
3202 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3203 $resql = $this->db->query($sql);
3204 if ($resql) {
3205 if ($this->db->num_rows($resql) > 0) {
3206 $obj = $this->db->fetch_object($resql);
3207 $adeduire += $obj->count;
3208 }
3209 }
3210
3211 $this->stats_commande['qty'] -= $adeduire;
3212 } else {
3213 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3214 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3215
3216 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3217 $adeduire = 0;
3218 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
3219 $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
3220 $sql .= " JOIN ".MAIN_DB_PREFIX."element_element as el ON ((el.fk_target = f.rowid AND el.targettype = 'facture' AND sourcetype = 'commande') OR (el.fk_source = f.rowid AND el.targettype = 'commande' AND sourcetype = 'facture'))";
3221 $sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
3222 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3223
3224 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3225 $resql = $this->db->query($sql);
3226 if ($resql) {
3227 if ($this->db->num_rows($resql) > 0) {
3228 $obj = $this->db->fetch_object($resql);
3229 $adeduire += $obj->count;
3230 }
3231 } else {
3232 $this->error = $this->db->error();
3233 return -1;
3234 }
3235
3236 $this->stats_commande['qty'] -= $adeduire;
3237 }
3238 }
3239
3240 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3241 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3242 if ($reshook > 0) {
3243 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3244 }
3245 return 1;
3246 } else {
3247 $this->error = $this->db->error();
3248 return -1;
3249 }
3250 }
3251
3252 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3262 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3263 {
3264 // phpcs:enable
3265 global $conf, $user, $hookmanager, $action;
3266
3267 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3268 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3269 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3270 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3271 $sql .= ", ".$this->db->prefix()."societe as s";
3272 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3273 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3274 }
3275 $sql .= " WHERE c.rowid = cd.fk_commande";
3276 $sql .= " AND c.fk_soc = s.rowid";
3277 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3278 $sql .= " AND cd.fk_product = ".((int) $this->id);
3279 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3280 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3281 }
3282 if ($socid > 0) {
3283 $sql .= " AND c.fk_soc = ".((int) $socid);
3284 }
3285 if ($filtrestatut != '') {
3286 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3287 }
3288 if (!empty($dateofvirtualstock)) {
3289 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3290 }
3291
3292 $result = $this->db->query($sql);
3293 if ($result) {
3294 $obj = $this->db->fetch_object($result);
3295 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3296 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3297 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3298 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3299
3300 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3301 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3302 if ($reshook > 0) {
3303 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3304 }
3305
3306 return 1;
3307 } else {
3308 $this->error = $this->db->error().' sql='.$sql;
3309 return -1;
3310 }
3311 }
3312
3313 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3323 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3324 {
3325 // phpcs:enable
3326 global $conf, $user, $hookmanager, $action;
3327
3328 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3329 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3330 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3331 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3332 $sql .= ", ".$this->db->prefix()."commande as c";
3333 $sql .= ", ".$this->db->prefix()."expedition as e";
3334 $sql .= ", ".$this->db->prefix()."societe as s";
3335 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3336 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3337 }
3338 $sql .= " WHERE e.rowid = ed.fk_expedition";
3339 $sql .= " AND c.rowid = cd.fk_commande";
3340 $sql .= " AND e.fk_soc = s.rowid";
3341 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3342 $sql .= " AND ed.fk_origin_line = cd.rowid";
3343 $sql .= " AND cd.fk_product = ".((int) $this->id);
3344 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3345 $sql .= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3346 }
3347 if ($socid > 0) {
3348 $sql .= " AND e.fk_soc = ".((int) $socid);
3349 }
3350 if ($filtrestatut != '') {
3351 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3352 }
3353 if (!empty($filterShipmentStatus)) {
3354 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3355 }
3356
3357 $result = $this->db->query($sql);
3358 if ($result) {
3359 $obj = $this->db->fetch_object($result);
3360 $this->stats_expedition['customers'] = $obj->nb_customers;
3361 $this->stats_expedition['nb'] = $obj->nb;
3362 $this->stats_expedition['rows'] = $obj->nb_rows;
3363 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3364
3365 // if it's a virtual product, maybe it is in sending by extension
3366 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3367 $TFather = $this->getFather();
3368 if (is_array($TFather) && !empty($TFather)) {
3369 foreach ($TFather as &$fatherData) {
3370 $pFather = new Product($this->db);
3371 $pFather->id = $fatherData['id'];
3372 $qtyCoef = $fatherData['qty'];
3373
3374 if ($fatherData['incdec']) {
3375 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3376
3377 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3378 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3379 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3380 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3381 }
3382 }
3383 }
3384 }
3385
3386 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3387 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3388 if ($reshook > 0) {
3389 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3390 }
3391
3392 return 1;
3393 } else {
3394 $this->error = $this->db->error();
3395 return -1;
3396 }
3397 }
3398
3399 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3409 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3410 {
3411 // phpcs:enable
3412 global $conf, $user, $hookmanager, $action;
3413
3414 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3415 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3416 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur_dispatch as fd";
3417 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3418 $sql .= ", ".$this->db->prefix()."societe as s";
3419 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3420 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3421 }
3422 $sql .= " WHERE cf.rowid = fd.fk_commande";
3423 $sql .= " AND cf.fk_soc = s.rowid";
3424 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3425 $sql .= " AND fd.fk_product = ".((int) $this->id);
3426 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3427 $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3428 }
3429 if ($socid > 0) {
3430 $sql .= " AND cf.fk_soc = ".((int) $socid);
3431 }
3432 if ($filtrestatut != '') {
3433 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3434 }
3435 if (!empty($dateofvirtualstock)) {
3436 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3437 }
3438
3439 $result = $this->db->query($sql);
3440 if ($result) {
3441 $obj = $this->db->fetch_object($result);
3442 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3443 $this->stats_reception['nb'] = $obj->nb;
3444 $this->stats_reception['rows'] = $obj->nb_rows;
3445 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3446
3447 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3448 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3449 if ($reshook > 0) {
3450 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3451 }
3452
3453 return 1;
3454 } else {
3455 $this->error = $this->db->error();
3456 return -1;
3457 }
3458 }
3459
3460 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3470 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3471 {
3472 // phpcs:enable
3473 global $conf, $user, $hookmanager, $action;
3474
3475 $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
3476
3477 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3478 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3479 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3480 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3481 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3482 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3483 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3484 }
3485 $sql .= " WHERE m.rowid = mp.fk_mo";
3486 $sql .= " AND m.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'mrp').")";
3487 $sql .= " AND mp.fk_product = ".((int) $this->id);
3488 $sql .= " AND (mp.disable_stock_change IN (0) OR mp.disable_stock_change IS NULL)";
3489 if (!$user->hasRight('societe', 'client', 'voir') && !$socid && !$forVirtualStock) {
3490 $sql .= " AND m.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3491 }
3492 if ($socid > 0) {
3493 $sql .= " AND m.fk_soc = ".((int) $socid);
3494 }
3495 if ($filtrestatut != '') {
3496 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3497 }
3498 if (!empty($dateofvirtualstock)) {
3499 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3500 }
3501 if (!$serviceStockIsEnabled) {
3502 $sql .= "AND EXISTS (SELECT p.rowid FROM ".$this->db->prefix()."product AS p WHERE p.rowid = ".((int) $this->id)." AND p.fk_product_type IN (0))";
3503 }
3504 $sql .= " GROUP BY role";
3505
3506 $this->stats_mrptoconsume['customers'] = 0;
3507 $this->stats_mrptoconsume['nb'] = 0;
3508 $this->stats_mrptoconsume['rows'] = 0;
3509 $this->stats_mrptoconsume['qty'] = 0;
3510 $this->stats_mrptoproduce['customers'] = 0;
3511 $this->stats_mrptoproduce['nb'] = 0;
3512 $this->stats_mrptoproduce['rows'] = 0;
3513 $this->stats_mrptoproduce['qty'] = 0;
3514
3515 $result = $this->db->query($sql);
3516 if ($result) {
3517 while ($obj = $this->db->fetch_object($result)) {
3518 if ($obj->role == 'toconsume') {
3519 $this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3520 $this->stats_mrptoconsume['nb'] += $obj->nb;
3521 $this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3522 $this->stats_mrptoconsume['qty'] += ($obj->qty ? $obj->qty : 0);
3523 }
3524 if ($obj->role == 'consumed') {
3525 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3526 //$this->stats_mrptoconsume['nb'] += $obj->nb;
3527 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3528 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? $obj->qty : 0);
3529 }
3530 if ($obj->role == 'toproduce') {
3531 $this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3532 $this->stats_mrptoproduce['nb'] += $obj->nb;
3533 $this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3534 $this->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3535 }
3536 if ($obj->role == 'produced') {
3537 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3538 //$this->stats_mrptoproduce['nb'] += $obj->nb;
3539 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3540 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3541 }
3542 }
3543
3544 // Clean data
3545 if ($this->stats_mrptoconsume['qty'] < 0) {
3546 $this->stats_mrptoconsume['qty'] = 0;
3547 }
3548 if ($this->stats_mrptoproduce['qty'] < 0) {
3549 $this->stats_mrptoproduce['qty'] = 0;
3550 }
3551
3552 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3553 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
3554 if ($reshook > 0) {
3555 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
3556 }
3557
3558 return 1;
3559 } else {
3560 $this->error = $this->db->error();
3561 return -1;
3562 }
3563 }
3564
3565 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3572 public function load_stats_contrat($socid = 0)
3573 {
3574 // phpcs:enable
3575 global $conf, $user, $hookmanager, $action;
3576
3577 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3578 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3579 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
3580 $sql .= ", ".$this->db->prefix()."contrat as c";
3581 $sql .= ", ".$this->db->prefix()."societe as s";
3582 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3583 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3584 }
3585 $sql .= " WHERE c.rowid = cd.fk_contrat";
3586 $sql .= " AND c.fk_soc = s.rowid";
3587 $sql .= " AND c.entity IN (".getEntity('contract').")";
3588 $sql .= " AND cd.fk_product = ".((int) $this->id);
3589 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3590 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3591 }
3592 //$sql.= " AND c.statut != 0";
3593 if ($socid > 0) {
3594 $sql .= " AND c.fk_soc = ".((int) $socid);
3595 }
3596
3597 $result = $this->db->query($sql);
3598 if ($result) {
3599 $obj = $this->db->fetch_object($result);
3600 $this->stats_contrat['customers'] = $obj->nb_customers;
3601 $this->stats_contrat['nb'] = $obj->nb;
3602 $this->stats_contrat['rows'] = $obj->nb_rows;
3603 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
3604
3605 // if it's a virtual product, maybe it is in contract by extension
3606 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3607 $TFather = $this->getFather();
3608 if (is_array($TFather) && !empty($TFather)) {
3609 foreach ($TFather as &$fatherData) {
3610 $pFather = new Product($this->db);
3611 $pFather->id = $fatherData['id'];
3612 $qtyCoef = $fatherData['qty'];
3613
3614 if ($fatherData['incdec']) {
3615 $pFather->load_stats_contrat($socid);
3616
3617 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
3618 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
3619 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
3620 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
3621 }
3622 }
3623 }
3624 }
3625
3626 $parameters = array('socid' => $socid);
3627 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
3628 if ($reshook > 0) {
3629 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
3630 }
3631
3632 return 1;
3633 } else {
3634 $this->error = $this->db->error().' sql='.$sql;
3635 return -1;
3636 }
3637 }
3638
3639 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3646 public function load_stats_facture($socid = 0)
3647 {
3648 // phpcs:enable
3649 global $conf, $user, $hookmanager, $action;
3650
3651 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3652 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
3653 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
3654 $sql .= ", ".$this->db->prefix()."facture as f";
3655 $sql .= ", ".$this->db->prefix()."societe as s";
3656 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3657 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3658 }
3659 $sql .= " WHERE f.rowid = fd.fk_facture";
3660 $sql .= " AND f.fk_soc = s.rowid";
3661 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3662 $sql .= " AND fd.fk_product = ".((int) $this->id);
3663 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3664 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3665 }
3666 //$sql.= " AND f.fk_statut != 0";
3667 if ($socid > 0) {
3668 $sql .= " AND f.fk_soc = ".((int) $socid);
3669 }
3670
3671 $result = $this->db->query($sql);
3672 if ($result) {
3673 $obj = $this->db->fetch_object($result);
3674 $this->stats_facture['customers'] = $obj->nb_customers;
3675 $this->stats_facture['nb'] = $obj->nb;
3676 $this->stats_facture['rows'] = $obj->nb_rows;
3677 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
3678
3679 // if it's a virtual product, maybe it is in invoice by extension
3680 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3681 $TFather = $this->getFather();
3682 if (is_array($TFather) && !empty($TFather)) {
3683 foreach ($TFather as &$fatherData) {
3684 $pFather = new Product($this->db);
3685 $pFather->id = $fatherData['id'];
3686 $qtyCoef = $fatherData['qty'];
3687
3688 if ($fatherData['incdec']) {
3689 $pFather->load_stats_facture($socid);
3690
3691 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
3692 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
3693 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
3694 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
3695 }
3696 }
3697 }
3698 }
3699
3700 $parameters = array('socid' => $socid);
3701 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
3702 if ($reshook > 0) {
3703 $this->stats_facture = $hookmanager->resArray['stats_facture'];
3704 }
3705
3706 return 1;
3707 } else {
3708 $this->error = $this->db->error();
3709 return -1;
3710 }
3711 }
3712
3713
3714 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3721 public function load_stats_facturerec($socid = 0)
3722 {
3723 // phpcs:enable
3724 global $conf, $user, $hookmanager, $action;
3725
3726 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
3727 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3728 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
3729 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
3730 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
3731 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3732 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3733 }
3734 $sql .= " WHERE f.rowid = fd.fk_facture";
3735 $sql .= " AND f.fk_soc = s.rowid";
3736 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3737 $sql .= " AND fd.fk_product = ".((int) $this->id);
3738 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3739 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3740 }
3741 //$sql.= " AND f.fk_statut != 0";
3742 if ($socid > 0) {
3743 $sql .= " AND f.fk_soc = ".((int) $socid);
3744 }
3745
3746 $result = $this->db->query($sql);
3747 if ($result) {
3748 $obj = $this->db->fetch_object($result);
3749 $this->stats_facturerec['customers'] = $obj->nb_customers;
3750 $this->stats_facturerec['nb'] = $obj->nb;
3751 $this->stats_facturerec['rows'] = $obj->nb_rows;
3752 $this->stats_facturerec['qty'] = $obj->qty ? $obj->qty : 0;
3753
3754 // if it's a virtual product, maybe it is in invoice by extension
3755 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3756 $TFather = $this->getFather();
3757 if (is_array($TFather) && !empty($TFather)) {
3758 foreach ($TFather as &$fatherData) {
3759 $pFather = new Product($this->db);
3760 $pFather->id = $fatherData['id'];
3761 $qtyCoef = $fatherData['qty'];
3762
3763 if ($fatherData['incdec']) {
3764 $pFather->load_stats_facture($socid);
3765
3766 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
3767 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
3768 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
3769 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
3770 }
3771 }
3772 }
3773 }
3774
3775 $parameters = array('socid' => $socid);
3776 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
3777 if ($reshook > 0) {
3778 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
3779 }
3780
3781 return 1;
3782 } else {
3783 $this->error = $this->db->error();
3784 return -1;
3785 }
3786 }
3787
3788 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3795 public function load_stats_facture_fournisseur($socid = 0)
3796 {
3797 // phpcs:enable
3798 global $conf, $user, $hookmanager, $action;
3799
3800 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
3801 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3802 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
3803 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
3804 $sql .= ", ".$this->db->prefix()."societe as s";
3805 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3806 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3807 }
3808 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
3809 $sql .= " AND f.fk_soc = s.rowid";
3810 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
3811 $sql .= " AND fd.fk_product = ".((int) $this->id);
3812 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3813 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3814 }
3815 //$sql.= " AND f.fk_statut != 0";
3816 if ($socid > 0) {
3817 $sql .= " AND f.fk_soc = ".((int) $socid);
3818 }
3819
3820 $result = $this->db->query($sql);
3821 if ($result) {
3822 $obj = $this->db->fetch_object($result);
3823 $this->stats_facture_fournisseur['suppliers'] = $obj->nb_suppliers;
3824 $this->stats_facture_fournisseur['nb'] = $obj->nb;
3825 $this->stats_facture_fournisseur['rows'] = $obj->nb_rows;
3826 $this->stats_facture_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3827
3828 $parameters = array('socid' => $socid);
3829 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
3830 if ($reshook > 0) {
3831 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
3832 }
3833
3834 return 1;
3835 } else {
3836 $this->error = $this->db->error();
3837 return -1;
3838 }
3839 }
3840
3841 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3850 private function _get_stats($sql, $mode, $year = 0)
3851 {
3852 // phpcs:enable
3853 $tab = array();
3854
3855 $resql = $this->db->query($sql);
3856 if ($resql) {
3857 $num = $this->db->num_rows($resql);
3858 $i = 0;
3859 while ($i < $num) {
3860 $arr = $this->db->fetch_array($resql);
3861 $keyfortab = (string) $arr[1];
3862 if ($year == -1) {
3863 $keyfortab = substr($keyfortab, -2);
3864 }
3865
3866 if ($mode == 'byunit') {
3867 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
3868 } elseif ($mode == 'bynumber') {
3869 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3870 } elseif ($mode == 'byamount') {
3871 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
3872 } else {
3873 // Bad value for $mode
3874 return -1;
3875 }
3876 $i++;
3877 }
3878 } else {
3879 $this->error = $this->db->error().' sql='.$sql;
3880 return -1;
3881 }
3882
3883 if (empty($year)) {
3884 $year = strftime('%Y', time());
3885 $month = strftime('%m', time());
3886 } elseif ($year == -1) {
3887 $year = '';
3888 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3889 } else {
3890 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
3891 }
3892
3893 $result = array();
3894
3895 for ($j = 0; $j < 12; $j++) {
3896 // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
3897 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
3898
3899 //print $idx.'-'.$year.'-'.$month.'<br>';
3900 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
3901 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
3902
3903 $month = "0".($month - 1);
3904 if (dol_strlen($month) == 3) {
3905 $month = substr($month, 1);
3906 }
3907 if ($month == 0) {
3908 $month = 12;
3909 $year = $year - 1;
3910 }
3911 }
3912
3913 return array_reverse($result);
3914 }
3915
3916
3917 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3928 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3929 {
3930 // phpcs:enable
3931 global $conf;
3932 global $user;
3933
3934 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
3935 if ($mode == 'bynumber') {
3936 $sql .= ", count(DISTINCT f.rowid)";
3937 }
3938 $sql .= ", sum(d.total_ht) as total_ht";
3939 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
3940 if ($filteronproducttype >= 0) {
3941 $sql .= ", ".$this->db->prefix()."product as p";
3942 }
3943 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3944 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3945 }
3946 $sql .= " WHERE f.rowid = d.fk_facture";
3947 if ($this->id > 0) {
3948 $sql .= " AND d.fk_product = ".((int) $this->id);
3949 } else {
3950 $sql .= " AND d.fk_product > 0";
3951 }
3952 if ($filteronproducttype >= 0) {
3953 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
3954 }
3955 $sql .= " AND f.fk_soc = s.rowid";
3956 $sql .= " AND f.entity IN (".getEntity('invoice').")";
3957 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3958 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3959 }
3960 if ($socid > 0) {
3961 $sql .= " AND f.fk_soc = $socid";
3962 }
3963 $sql .= $morefilter;
3964 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
3965 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
3966
3967 return $this->_get_stats($sql, $mode, $year);
3968 }
3969
3970
3971 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3982 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
3983 {
3984 // phpcs:enable
3985 global $conf;
3986 global $user;
3987
3988 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
3989 if ($mode == 'bynumber') {
3990 $sql .= ", count(DISTINCT f.rowid)";
3991 }
3992 $sql .= ", sum(d.total_ht) as total_ht";
3993 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
3994 if ($filteronproducttype >= 0) {
3995 $sql .= ", ".$this->db->prefix()."product as p";
3996 }
3997 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
3998 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3999 }
4000 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4001 if ($this->id > 0) {
4002 $sql .= " AND d.fk_product = ".((int) $this->id);
4003 } else {
4004 $sql .= " AND d.fk_product > 0";
4005 }
4006 if ($filteronproducttype >= 0) {
4007 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4008 }
4009 $sql .= " AND f.fk_soc = s.rowid";
4010 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4011 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4012 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4013 }
4014 if ($socid > 0) {
4015 $sql .= " AND f.fk_soc = $socid";
4016 }
4017 $sql .= $morefilter;
4018 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4019 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4020
4021 return $this->_get_stats($sql, $mode, $year);
4022 }
4023
4024 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4035 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4036 {
4037 // phpcs:enable
4038 global $conf, $user;
4039
4040 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4041 if ($mode == 'bynumber') {
4042 $sql .= ", count(DISTINCT p.rowid)";
4043 }
4044 $sql .= ", sum(d.total_ht) as total_ht";
4045 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4046 if ($filteronproducttype >= 0) {
4047 $sql .= ", ".$this->db->prefix()."product as prod";
4048 }
4049 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4050 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4051 }
4052 $sql .= " WHERE p.rowid = d.fk_propal";
4053 if ($this->id > 0) {
4054 $sql .= " AND d.fk_product = ".((int) $this->id);
4055 } else {
4056 $sql .= " AND d.fk_product > 0";
4057 }
4058 if ($filteronproducttype >= 0) {
4059 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4060 }
4061 $sql .= " AND p.fk_soc = s.rowid";
4062 $sql .= " AND p.entity IN (".getEntity('propal').")";
4063 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4064 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4065 }
4066 if ($socid > 0) {
4067 $sql .= " AND p.fk_soc = ".((int) $socid);
4068 }
4069 $sql .= $morefilter;
4070 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4071 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4072
4073 return $this->_get_stats($sql, $mode, $year);
4074 }
4075
4076 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4087 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4088 {
4089 // phpcs:enable
4090 global $conf;
4091 global $user;
4092
4093 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4094 if ($mode == 'bynumber') {
4095 $sql .= ", count(DISTINCT p.rowid)";
4096 }
4097 $sql .= ", sum(d.total_ht) as total_ht";
4098 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4099 if ($filteronproducttype >= 0) {
4100 $sql .= ", ".$this->db->prefix()."product as prod";
4101 }
4102 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4103 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4104 }
4105 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4106 if ($this->id > 0) {
4107 $sql .= " AND d.fk_product = ".((int) $this->id);
4108 } else {
4109 $sql .= " AND d.fk_product > 0";
4110 }
4111 if ($filteronproducttype >= 0) {
4112 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4113 }
4114 $sql .= " AND p.fk_soc = s.rowid";
4115 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4116 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4117 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4118 }
4119 if ($socid > 0) {
4120 $sql .= " AND p.fk_soc = ".((int) $socid);
4121 }
4122 $sql .= $morefilter;
4123 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4124 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4125
4126 return $this->_get_stats($sql, $mode, $year);
4127 }
4128
4129 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4140 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4141 {
4142 // phpcs:enable
4143 global $conf, $user;
4144
4145 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4146 if ($mode == 'bynumber') {
4147 $sql .= ", count(DISTINCT c.rowid)";
4148 }
4149 $sql .= ", sum(d.total_ht) as total_ht";
4150 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4151 if ($filteronproducttype >= 0) {
4152 $sql .= ", ".$this->db->prefix()."product as p";
4153 }
4154 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4155 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4156 }
4157 $sql .= " WHERE c.rowid = d.fk_commande";
4158 if ($this->id > 0) {
4159 $sql .= " AND d.fk_product = ".((int) $this->id);
4160 } else {
4161 $sql .= " AND d.fk_product > 0";
4162 }
4163 if ($filteronproducttype >= 0) {
4164 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4165 }
4166 $sql .= " AND c.fk_soc = s.rowid";
4167 $sql .= " AND c.entity IN (".getEntity('commande').")";
4168 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4169 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4170 }
4171 if ($socid > 0) {
4172 $sql .= " AND c.fk_soc = ".((int) $socid);
4173 }
4174 $sql .= $morefilter;
4175 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4176 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4177
4178 return $this->_get_stats($sql, $mode, $year);
4179 }
4180
4181 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4192 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4193 {
4194 // phpcs:enable
4195 global $conf, $user;
4196
4197 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4198 if ($mode == 'bynumber') {
4199 $sql .= ", count(DISTINCT c.rowid)";
4200 }
4201 $sql .= ", sum(d.total_ht) as total_ht";
4202 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4203 if ($filteronproducttype >= 0) {
4204 $sql .= ", ".$this->db->prefix()."product as p";
4205 }
4206 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4207 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4208 }
4209 $sql .= " WHERE c.rowid = d.fk_commande";
4210 if ($this->id > 0) {
4211 $sql .= " AND d.fk_product = ".((int) $this->id);
4212 } else {
4213 $sql .= " AND d.fk_product > 0";
4214 }
4215 if ($filteronproducttype >= 0) {
4216 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4217 }
4218 $sql .= " AND c.fk_soc = s.rowid";
4219 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4220 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4221 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4222 }
4223 if ($socid > 0) {
4224 $sql .= " AND c.fk_soc = ".((int) $socid);
4225 }
4226 $sql .= $morefilter;
4227 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4228 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4229
4230 return $this->_get_stats($sql, $mode, $year);
4231 }
4232
4233 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4244 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4245 {
4246 // phpcs:enable
4247 global $conf, $user;
4248
4249 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4250 if ($mode == 'bynumber') {
4251 $sql .= ", count(DISTINCT c.rowid)";
4252 }
4253 $sql .= ", sum(d.total_ht) as total_ht";
4254 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4255 if ($filteronproducttype >= 0) {
4256 $sql .= ", ".$this->db->prefix()."product as p";
4257 }
4258 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4259 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4260 }
4261
4262 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4263 $sql .= " AND c.rowid = d.fk_contrat";
4264
4265 if ($this->id > 0) {
4266 $sql .= " AND d.fk_product = ".((int) $this->id);
4267 } else {
4268 $sql .= " AND d.fk_product > 0";
4269 }
4270 if ($filteronproducttype >= 0) {
4271 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4272 }
4273 $sql .= " AND c.fk_soc = s.rowid";
4274
4275 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4276 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4277 }
4278 if ($socid > 0) {
4279 $sql .= " AND c.fk_soc = ".((int) $socid);
4280 }
4281 $sql .= $morefilter;
4282 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4283 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4284
4285 return $this->_get_stats($sql, $mode, $year);
4286 }
4287
4288 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4299 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4300 {
4301 // phpcs:enable
4302 global $conf, $user;
4303
4304 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4305 if ($mode == 'bynumber') {
4306 $sql .= ", count(DISTINCT d.rowid)";
4307 }
4308 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4309 if ($filteronproducttype >= 0) {
4310 $sql .= ", ".$this->db->prefix()."product as p";
4311 }
4312 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4313 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4314 }
4315
4316 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4317 $sql .= " AND d.status > 0";
4318
4319 if ($this->id > 0) {
4320 $sql .= " AND d.fk_product = ".((int) $this->id);
4321 } else {
4322 $sql .= " AND d.fk_product > 0";
4323 }
4324 if ($filteronproducttype >= 0) {
4325 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4326 }
4327
4328 if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
4329 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4330 }
4331 if ($socid > 0) {
4332 $sql .= " AND d.fk_soc = ".((int) $socid);
4333 }
4334 $sql .= $morefilter;
4335 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4336 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4337
4338 return $this->_get_stats($sql, $mode, $year);
4339 }
4340
4341 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4352 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4353 {
4354 global $user;
4355
4356 // phpcs:enable
4357 // Clean parameters
4358 if (!is_numeric($id_pere)) {
4359 $id_pere = 0;
4360 }
4361 if (!is_numeric($id_fils)) {
4362 $id_fils = 0;
4363 }
4364 if (!is_numeric($incdec)) {
4365 $incdec = 0;
4366 }
4367
4368 $result = $this->del_sousproduit($id_pere, $id_fils);
4369 if ($result < 0) {
4370 return $result;
4371 }
4372
4373 // Check not already father of id_pere (to avoid father -> child -> father links)
4374 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4375 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4376 if (!$this->db->query($sql)) {
4377 dol_print_error($this->db);
4378 return -1;
4379 } else {
4380 //Selection of the highest row
4381 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4382 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4383 $resql = $this->db->query($sql);
4384 if ($resql) {
4385 $obj = $this->db->fetch_object($resql);
4386 $rank = $obj->max_rank + 1;
4387 //Addition of a product with the highest rank +1
4388 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4389 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".price2num($incdec, 'MS').", ".((int) $rank).")";
4390 if (! $this->db->query($sql)) {
4391 dol_print_error($this->db);
4392 return -1;
4393 } else {
4394 if (!$notrigger) {
4395 // Call trigger
4396 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
4397 if ($result < 0) {
4398 $this->error = $this->db->lasterror();
4399 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
4400 return -1;
4401 }
4402 }
4403 // End call triggers
4404
4405 return 1;
4406 }
4407 } else {
4408 dol_print_error($this->db);
4409 return -1;
4410 }
4411 }
4412 }
4413
4414 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4425 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4426 {
4427 global $user;
4428
4429 // phpcs:enable
4430 // Clean parameters
4431 if (!is_numeric($id_pere)) {
4432 $id_pere = 0;
4433 }
4434 if (!is_numeric($id_fils)) {
4435 $id_fils = 0;
4436 }
4437 if (!is_numeric($incdec)) {
4438 $incdec = 1;
4439 }
4440 if (!is_numeric($qty)) {
4441 $qty = 1;
4442 }
4443
4444 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4445 $sql .= 'qty = '.price2num($qty, 'MS');
4446 $sql .= ',incdec = '.price2num($incdec, 'MS');
4447 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4448
4449 if (!$this->db->query($sql)) {
4450 dol_print_error($this->db);
4451 return -1;
4452 } else {
4453 if (!$notrigger) {
4454 // Call trigger
4455 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
4456 if ($result < 0) {
4457 $this->error = $this->db->lasterror();
4458 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
4459 return -1;
4460 }
4461 // End call triggers
4462 }
4463
4464 return 1;
4465 }
4466 }
4467
4468 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4477 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
4478 {
4479 global $user;
4480
4481 // phpcs:enable
4482 if (!is_numeric($fk_parent)) {
4483 $fk_parent = 0;
4484 }
4485 if (!is_numeric($fk_child)) {
4486 $fk_child = 0;
4487 }
4488
4489 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
4490 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4491 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4492
4493 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
4494 if (!$this->db->query($sql)) {
4495 dol_print_error($this->db);
4496 return -1;
4497 }
4498
4499 // Updated ranks so that none are missing
4500 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
4501 $sqlrank.= " WHERE fk_product_pere = ".((int) $fk_parent);
4502 $sqlrank.= " ORDER BY rang";
4503 $resqlrank = $this->db->query($sqlrank);
4504 if ($resqlrank) {
4505 $cpt = 0;
4506 while ($objrank = $this->db->fetch_object($resqlrank)) {
4507 $cpt++;
4508 $sql = "UPDATE ".$this->db->prefix()."product_association";
4509 $sql.= " SET rang = ".((int) $cpt);
4510 $sql.= " WHERE rowid = ".((int) $objrank->rowid);
4511 if (! $this->db->query($sql)) {
4512 dol_print_error($this->db);
4513 return -1;
4514 }
4515 }
4516 }
4517
4518 if (!$notrigger) {
4519 // Call trigger
4520 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
4521 if ($result < 0) {
4522 $this->error = $this->db->lasterror();
4523 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
4524 return -1;
4525 }
4526 // End call triggers
4527 }
4528
4529 return 1;
4530 }
4531
4532 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4540 public function is_sousproduit($fk_parent, $fk_child)
4541 {
4542 // phpcs:enable
4543 $sql = "SELECT fk_product_pere, qty, incdec";
4544 $sql .= " FROM ".$this->db->prefix()."product_association";
4545 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4546 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4547
4548 $result = $this->db->query($sql);
4549 if ($result) {
4550 $num = $this->db->num_rows($result);
4551
4552 if ($num > 0) {
4553 $obj = $this->db->fetch_object($result);
4554
4555 $this->is_sousproduit_qty = $obj->qty;
4556 $this->is_sousproduit_incdec = $obj->incdec;
4557
4558 return true;
4559 } else {
4560 return false;
4561 }
4562 } else {
4563 dol_print_error($this->db);
4564 return -1;
4565 }
4566 }
4567
4568
4569 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4580 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
4581 {
4582 // phpcs:enable
4583 global $conf;
4584
4585 $now = dol_now();
4586
4587 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
4588
4589 // Clean parameters
4590 $quantity = price2num($quantity, 'MS');
4591
4592 if ($ref_fourn) {
4593 $sql = "SELECT rowid, fk_product";
4594 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4595 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4596 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4597 $sql .= " AND fk_product <> ".((int) $this->id);
4598 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4599
4600 $resql = $this->db->query($sql);
4601 if ($resql) {
4602 $obj = $this->db->fetch_object($resql);
4603 if ($obj) {
4604 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
4605 $this->product_id_already_linked = $obj->fk_product;
4606 return -3;
4607 }
4608 $this->db->free($resql);
4609 }
4610 }
4611
4612 $sql = "SELECT rowid";
4613 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4614 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
4615 if ($ref_fourn) {
4616 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
4617 } else {
4618 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
4619 }
4620 $sql .= " AND quantity = ".((float) $quantity);
4621 $sql .= " AND fk_product = ".((int) $this->id);
4622 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
4623
4624 $resql = $this->db->query($sql);
4625 if ($resql) {
4626 $obj = $this->db->fetch_object($resql);
4627
4628 // The reference supplier does not exist, we create it for this product.
4629 if (empty($obj)) {
4630 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
4631 $sql .= "datec";
4632 $sql .= ", entity";
4633 $sql .= ", fk_product";
4634 $sql .= ", fk_soc";
4635 $sql .= ", ref_fourn";
4636 $sql .= ", quantity";
4637 $sql .= ", fk_user";
4638 $sql .= ", tva_tx";
4639 $sql .= ") VALUES (";
4640 $sql .= "'".$this->db->idate($now)."'";
4641 $sql .= ", ".$conf->entity;
4642 $sql .= ", ".$this->id;
4643 $sql .= ", ".$id_fourn;
4644 $sql .= ", '".$this->db->escape($ref_fourn)."'";
4645 $sql .= ", ".$quantity;
4646 $sql .= ", ".$user->id;
4647 $sql .= ", 0";
4648 $sql .= ")";
4649
4650 if ($this->db->query($sql)) {
4651 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
4652 return 1;
4653 } else {
4654 $this->error = $this->db->lasterror();
4655 return -1;
4656 }
4657 } else {
4658 // If the supplier price already exists for this product and quantity
4659 $this->product_fourn_price_id = $obj->rowid;
4660 return 0;
4661 }
4662 } else {
4663 $this->error = $this->db->lasterror();
4664 return -2;
4665 }
4666 }
4667
4668
4669 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4675 public function list_suppliers()
4676 {
4677 // phpcs:enable
4678 global $conf;
4679
4680 $list = array();
4681
4682 $sql = "SELECT DISTINCT p.fk_soc";
4683 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
4684 $sql .= " WHERE p.fk_product = ".((int) $this->id);
4685 $sql .= " AND p.entity = ".((int) $conf->entity);
4686
4687 $result = $this->db->query($sql);
4688 if ($result) {
4689 $num = $this->db->num_rows($result);
4690 $i = 0;
4691 while ($i < $num) {
4692 $obj = $this->db->fetch_object($result);
4693 $list[$i] = $obj->fk_soc;
4694 $i++;
4695 }
4696 }
4697
4698 return $list;
4699 }
4700
4701 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4709 public function clone_price($fromId, $toId)
4710 {
4711 global $conf, $user;
4712
4713 $now = dol_now();
4714
4715 $this->db->begin();
4716
4717 // prices
4718 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
4719 $sql .= " entity";
4720 $sql .= ", fk_product";
4721 $sql .= ", date_price";
4722 $sql .= ", price_level";
4723 $sql .= ", price";
4724 $sql .= ", price_ttc";
4725 $sql .= ", price_min";
4726 $sql .= ", price_min_ttc";
4727 $sql .= ", price_base_type";
4728 $sql .= ", default_vat_code";
4729 $sql .= ", tva_tx";
4730 $sql .= ", recuperableonly";
4731 $sql .= ", localtax1_tx";
4732 $sql .= ", localtax1_type";
4733 $sql .= ", localtax2_tx";
4734 $sql .= ", localtax2_type";
4735 $sql .= ", fk_user_author";
4736 $sql .= ", tosell";
4737 $sql .= ", price_by_qty";
4738 $sql .= ", fk_price_expression";
4739 $sql .= ", fk_multicurrency";
4740 $sql .= ", multicurrency_code";
4741 $sql .= ", multicurrency_tx";
4742 $sql .= ", multicurrency_price";
4743 $sql .= ", multicurrency_price_ttc";
4744 $sql .= ")";
4745 $sql .= " SELECT";
4746 $sql .= " entity";
4747 $sql .= ", ".$toId;
4748 $sql .= ", '".$this->db->idate($now)."'";
4749 $sql .= ", price_level";
4750 $sql .= ", price";
4751 $sql .= ", price_ttc";
4752 $sql .= ", price_min";
4753 $sql .= ", price_min_ttc";
4754 $sql .= ", price_base_type";
4755 $sql .= ", default_vat_code";
4756 $sql .= ", tva_tx";
4757 $sql .= ", recuperableonly";
4758 $sql .= ", localtax1_tx";
4759 $sql .= ", localtax1_type";
4760 $sql .= ", localtax2_tx";
4761 $sql .= ", localtax2_type";
4762 $sql .= ", ".$user->id;
4763 $sql .= ", tosell";
4764 $sql .= ", price_by_qty";
4765 $sql .= ", fk_price_expression";
4766 $sql .= ", fk_multicurrency";
4767 $sql .= ", multicurrency_code";
4768 $sql .= ", multicurrency_tx";
4769 $sql .= ", multicurrency_price";
4770 $sql .= ", multicurrency_price_ttc";
4771 $sql .= " FROM ".$this->db->prefix()."product_price ps";
4772 $sql .= " WHERE fk_product = ".((int) $fromId);
4773 $sql .= " AND date_price IN (SELECT MAX(pd.date_price) FROM ".$this->db->prefix()."product_price pd WHERE pd.fk_product = ".((int) $fromId)." AND pd.price_level = ps.price_level)";
4774 $sql .= " ORDER BY date_price DESC";
4775
4776 dol_syslog(__METHOD__, LOG_DEBUG);
4777 $resql = $this->db->query($sql);
4778 if (!$resql) {
4779 $this->db->rollback();
4780 return -1;
4781 }
4782
4783 $this->db->commit();
4784 return 1;
4785 }
4786
4787 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4795 public function clone_associations($fromId, $toId)
4796 {
4797 // phpcs:enable
4798 $this->db->begin();
4799
4800 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
4801 $sql .= " SELECT ".$toId.", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
4802 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
4803
4804 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
4805 if (!$this->db->query($sql)) {
4806 $this->db->rollback();
4807 return -1;
4808 }
4809
4810 $this->db->commit();
4811 return 1;
4812 }
4813
4814 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4822 public function clone_fournisseurs($fromId, $toId)
4823 {
4824 // phpcs:enable
4825 $this->db->begin();
4826
4827 $now = dol_now();
4828
4829 // les fournisseurs
4830 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
4831 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
4832 . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
4833 . " FROM ".$this->db->prefix()."product_fournisseur"
4834 . " WHERE fk_product = ".((int) $fromId);
4835
4836 if ( ! $this->db->query($sql ) )
4837 {
4838 $this->db->rollback();
4839 return -1;
4840 }*/
4841
4842 // les prix de fournisseurs.
4843 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
4844 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
4845 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
4846 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
4847 $sql .= " WHERE fk_product = ".((int) $fromId);
4848
4849 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
4850 $resql = $this->db->query($sql);
4851 if (!$resql) {
4852 $this->db->rollback();
4853 return -1;
4854 } else {
4855 $this->db->commit();
4856 return 1;
4857 }
4858 }
4859
4860 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4873 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
4874 {
4875 // phpcs:enable
4876 global $conf, $langs;
4877
4878 $tmpproduct = null;
4879 //var_dump($prod);
4880 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
4881 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
4882 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
4883 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
4884 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
4885 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
4886 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
4887
4888 if ($multiply < 1) {
4889 $multiply = 1;
4890 }
4891
4892 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
4893 if (is_null($tmpproduct)) {
4894 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
4895 }
4896 $tmpproduct->fetch($id); // Load product to get ->ref
4897
4898 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
4899 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
4900 }
4901
4902 $this->res[] = array(
4903 'id'=>$id, // Id product
4904 'id_parent'=>$id_parent,
4905 'ref'=>$tmpproduct->ref, // Ref product
4906 'nb'=>$nb, // Nb of units that compose parent product
4907 'nb_total'=>$nb * $multiply, // Nb of units for all nb of product
4908 'stock'=>$tmpproduct->stock_reel, // Stock
4909 'stock_alert'=>$tmpproduct->seuil_stock_alerte, // Stock alert
4910 'label'=>$label,
4911 'fullpath'=>$compl_path.$label, // Label
4912 'type'=>$type, // Nb of units that compose parent product
4913 'desiredstock'=>$tmpproduct->desiredstock,
4914 'level'=>$level,
4915 'incdec'=>$incdec,
4916 'entity'=>$tmpproduct->entity
4917 );
4918
4919 // Recursive call if there is childs to child
4920 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
4921 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
4922 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id, $ignore_stock_load);
4923 }
4924 }
4925 }
4926 }
4927
4928 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4937 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
4938 {
4939 // phpcs:enable
4940 $this->res = array();
4941 if (isset($this->sousprods) && is_array($this->sousprods)) {
4942 foreach ($this->sousprods as $prod_name => $desc_product) {
4943 if (is_array($desc_product)) {
4944 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
4945 }
4946 }
4947 }
4948 //var_dump($res);
4949 return $this->res;
4950 }
4951
4959 public function hasFatherOrChild($mode = 0)
4960 {
4961 $nb = 0;
4962
4963 $sql = "SELECT COUNT(pa.rowid) as nb";
4964 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
4965 if ($mode == 0) {
4966 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
4967 } elseif ($mode == -1) {
4968 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id); // We are a child, so we found lines that link to parents (can have several parents)
4969 } elseif ($mode == 1) {
4970 $sql .= " WHERE pa.fk_product_pere = ".((int) $this->id); // We are a parent, so we found lines that link to children (can have several children)
4971 }
4972
4973 $resql = $this->db->query($sql);
4974 if ($resql) {
4975 $obj = $this->db->fetch_object($resql);
4976 if ($obj) {
4977 $nb = $obj->nb;
4978 }
4979 } else {
4980 return -1;
4981 }
4982
4983 return $nb;
4984 }
4985
4991 public function hasVariants()
4992 {
4993 $nb = 0;
4994 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
4995 $sql .= " AND entity IN (".getEntity('product').")";
4996
4997 $resql = $this->db->query($sql);
4998 if ($resql) {
4999 $obj = $this->db->fetch_object($resql);
5000 if ($obj) {
5001 $nb = $obj->nb;
5002 }
5003 }
5004
5005 return $nb;
5006 }
5007
5008
5014 public function isVariant()
5015 {
5016 global $conf;
5017 if (isModEnabled('variants')) {
5018 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5019
5020 $query = $this->db->query($sql);
5021
5022 if ($query) {
5023 if (!$this->db->num_rows($query)) {
5024 return false;
5025 }
5026 return true;
5027 } else {
5028 dol_print_error($this->db);
5029 return -1;
5030 }
5031 } else {
5032 return false;
5033 }
5034 }
5035
5042 public function getFather()
5043 {
5044 $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";
5045 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5046 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5047 $sql .= " ".$this->db->prefix()."product as p";
5048 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5049 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5050
5051 $res = $this->db->query($sql);
5052 if ($res) {
5053 $prods = array();
5054 while ($record = $this->db->fetch_array($res)) {
5055 // $record['id'] = $record['rowid'] = id of father
5056 $prods[$record['id']]['id'] = $record['rowid'];
5057 $prods[$record['id']]['ref'] = $record['ref'];
5058 $prods[$record['id']]['label'] = $record['label'];
5059 $prods[$record['id']]['qty'] = $record['qty'];
5060 $prods[$record['id']]['incdec'] = $record['incdec'];
5061 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5062 $prods[$record['id']]['entity'] = $record['entity'];
5063 $prods[$record['id']]['status'] = $record['status'];
5064 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5065 }
5066 return $prods;
5067 } else {
5068 dol_print_error($this->db);
5069 return -1;
5070 }
5071 }
5072
5073
5083 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5084 {
5085 global $alreadyfound;
5086
5087 if (empty($id)) {
5088 return array();
5089 }
5090
5091 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5092 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5093 $sql .= " pa.rowid as fk_association, pa.rang";
5094 $sql .= " FROM ".$this->db->prefix()."product as p,";
5095 $sql .= " ".$this->db->prefix()."product_association as pa";
5096 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5097 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5098 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5099 $sql.= " ORDER BY pa.rang";
5100
5101 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5102
5103 if ($level == 1) {
5104 $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
5105 }
5106 // Protection against infinite loop
5107 if ($level > 30) {
5108 return array();
5109 }
5110
5111 $res = $this->db->query($sql);
5112 if ($res) {
5113 $prods = array();
5114 while ($rec = $this->db->fetch_array($res)) {
5115 if (!empty($alreadyfound[$rec['rowid']])) {
5116 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);
5117 if (in_array($rec['id'], $parents)) {
5118 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5119 }
5120 }
5121 $alreadyfound[$rec['rowid']] = 1;
5122 $prods[$rec['rowid']] = array(
5123 0=>$rec['rowid'],
5124 1=>$rec['qty'],
5125 2=>$rec['fk_product_type'],
5126 3=>$this->db->escape($rec['label']),
5127 4=>$rec['incdec'],
5128 5=>$rec['ref'],
5129 6=>$rec['fk_association'],
5130 7=>$rec['rang']
5131 );
5132 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5133 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5134 if (empty($firstlevelonly)) {
5135 $parents[] = $rec['rowid'];
5136 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5137 foreach ($listofchilds as $keyChild => $valueChild) {
5138 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5139 }
5140 }
5141 }
5142
5143 return $prods;
5144 } else {
5145 dol_print_error($this->db);
5146 return -1;
5147 }
5148 }
5149
5150 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5157 public function get_sousproduits_arbo()
5158 {
5159 // phpcs:enable
5160 $parent = array();
5161
5162 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5163 $parent[$this->label][$keyChild] = $valueChild;
5164 }
5165 foreach ($parent as $key => $value) { // key=label, value is array of childs
5166 $this->sousprods[$key] = $value;
5167 }
5168 }
5169
5176 public function getTooltipContentArray($params)
5177 {
5178 global $conf, $langs;
5179
5180 $langs->loadLangs(array('products', 'other'));
5181
5182 $datas = array();
5183 $nofetch = !empty($params['nofetch']);
5184
5185 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5186 return ['optimize' => $langs->trans("ShowProduct")];
5187 }
5188
5189 if (!empty($this->entity)) {
5190 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, 1);
5191 if ($this->nbphoto > 0) {
5192 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5193 }
5194 }
5195
5196 if ($this->type == Product::TYPE_PRODUCT) {
5197 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5198 } elseif ($this->type == Product::TYPE_SERVICE) {
5199 $datas['picto']= img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5200 }
5201 if (isset($this->status) && isset($this->status_buy)) {
5202 $datas['status']= ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5203 }
5204
5205 if (!empty($this->ref)) {
5206 $datas['ref']= '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5207 }
5208 if (!empty($this->label)) {
5209 $datas['label']= '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5210 }
5211 if (!empty($this->description)) {
5212 $datas['description']= '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineofText($this->description, 5);
5213 }
5214 if ($this->type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
5215 if (isModEnabled('productbatch')) {
5216 $langs->load("productbatch");
5217 $datas['batchstatus']= "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5218 }
5219 }
5220 if (isModEnabled('barcode')) {
5221 $datas['barcode']= '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5222 }
5223
5224 if ($this->type == Product::TYPE_PRODUCT) {
5225 if ($this->weight) {
5226 $datas['weight']= "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5227 }
5228 $labelsize = "";
5229 if ($this->length) {
5230 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5231 }
5232 if ($this->width) {
5233 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5234 }
5235 if ($this->height) {
5236 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5237 }
5238 if ($labelsize) {
5239 $datas['size']= "<br>".$labelsize;
5240 }
5241
5242 $labelsurfacevolume = "";
5243 if ($this->surface) {
5244 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5245 }
5246 if ($this->volume) {
5247 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5248 }
5249 if ($labelsurfacevolume) {
5250 $datas['surface']= "<br>" . $labelsurfacevolume;
5251 }
5252 }
5253 if (!empty($this->pmp) && $this->pmp) {
5254 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5255 }
5256
5257 if (isModEnabled('accounting')) {
5258 if ($this->status && isset($this->accountancy_code_sell)) {
5259 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5260 $selllabel = '<br>';
5261 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5262 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5263 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5264 $datas['accountancysell'] = $selllabel;
5265 }
5266 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5267 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5268 $buylabel = '';
5269 if (empty($this->status)) {
5270 $buylabel .= '<br>';
5271 }
5272 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5273 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5274 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5275 $datas['accountancybuy'] = $buylabel;
5276 }
5277 }
5278 // show categories for this record only in ajax to not overload lists
5279 if (isModEnabled('categorie') && !$nofetch) {
5280 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5281 $form = new Form($this->db);
5282 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5283 }
5284
5285 return $datas;
5286 }
5287
5301 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5302 {
5303 global $conf, $langs, $hookmanager;
5304 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5305
5306 $result = '';
5307
5308 $newref = $this->ref;
5309 if ($maxlength) {
5310 $newref = dol_trunc($newref, $maxlength, 'middle');
5311 }
5312 $params = [
5313 'id' => $this->id,
5314 'objecttype' => (isset($this->type) ? ($this->type == 1 ? 'service' : 'product') : $this->element),
5315 'option' => $option,
5316 'nofetch' => 1,
5317 ];
5318 $classfortooltip = 'classfortooltip';
5319 $dataparams = '';
5320 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5321 $classfortooltip = 'classforajaxtooltip';
5322 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5323 $label = '';
5324 } else {
5325 $label = implode($this->getTooltipContentArray($params));
5326 }
5327
5328 $linkclose = '';
5329 if (empty($notooltip)) {
5330 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5331 $label = $langs->trans("ShowProduct");
5332 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5333 }
5334 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5335 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5336 } else {
5337 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5338 }
5339
5340 if ($option == 'supplier' || $option == 'category') {
5341 $url = DOL_URL_ROOT.'/product/fournisseurs.php?id='.$this->id;
5342 } elseif ($option == 'stock') {
5343 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5344 } elseif ($option == 'composition') {
5345 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5346 } else {
5347 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5348 }
5349
5350 if ($option !== 'nolink') {
5351 // Add param to save lastsearch_values or not
5352 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5353 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5354 $add_save_lastsearch_values = 1;
5355 }
5356 if ($add_save_lastsearch_values) {
5357 $url .= '&save_lastsearch_values=1';
5358 }
5359 }
5360
5361 $linkstart = '<a href="'.$url.'"';
5362 $linkstart .= $linkclose.'>';
5363 $linkend = '</a>';
5364
5365 $result .= $linkstart;
5366 if ($withpicto) {
5367 if ($this->type == Product::TYPE_PRODUCT) {
5368 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5369 }
5370 if ($this->type == Product::TYPE_SERVICE) {
5371 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5372 }
5373 }
5374 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
5375 $result .= $linkend;
5376 if ($withpicto != 2) {
5377 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5378 }
5379
5380 global $action;
5381 $hookmanager->initHooks(array('productdao'));
5382 $parameters = array('id'=>$this->id, 'getnomurl' => &$result, 'label' => &$label);
5383 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5384 if ($reshook > 0) {
5385 $result = $hookmanager->resPrint;
5386 } else {
5387 $result .= $hookmanager->resPrint;
5388 }
5389
5390 return $result;
5391 }
5392
5393
5404 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5405 {
5406 global $conf, $user, $langs;
5407
5408 $langs->load("products");
5409 $outputlangs->load("products");
5410
5411 // Positionne le modele sur le nom du modele a utiliser
5412 if (!dol_strlen($modele)) {
5413 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5414 }
5415
5416 $modelpath = "core/modules/product/doc/";
5417
5418 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5419 }
5420
5428 public function getLibStatut($mode = 0, $type = 0)
5429 {
5430 switch ($type) {
5431 case 0:
5432 return $this->LibStatut($this->status, $mode, $type);
5433 case 1:
5434 return $this->LibStatut($this->status_buy, $mode, $type);
5435 case 2:
5436 return $this->LibStatut($this->status_batch, $mode, $type);
5437 default:
5438 //Simulate previous behavior but should return an error string
5439 return $this->LibStatut($this->status_buy, $mode, $type);
5440 }
5441 }
5442
5443 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5452 public function LibStatut($status, $mode = 0, $type = 0)
5453 {
5454 // phpcs:enable
5455 global $conf, $langs;
5456
5457 $labelStatus = $labelStatusShort = '';
5458
5459 $langs->load('products');
5460 if (isModEnabled('productbatch')) {
5461 $langs->load("productbatch");
5462 }
5463
5464 if ($type == 2) {
5465 switch ($mode) {
5466 case 0:
5467 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5468 return dolGetStatus($label);
5469 case 1:
5470 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5471 return dolGetStatus($label);
5472 case 2:
5473 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5474 case 3:
5475 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5476 case 4:
5477 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5478 case 5:
5479 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5480 default:
5481 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5482 }
5483 }
5484
5485 $statuttrans = empty($status) ? 'status5' : 'status4';
5486
5487 if ($status == 0) {
5488 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5489 if ($type == 0) {
5490 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5491 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5492 } elseif ($type == 1) {
5493 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5494 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5495 } elseif ($type == 2) {
5496 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5497 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5498 }
5499 } elseif ($status == 1) {
5500 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5501 if ($type == 0) {
5502 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5503 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5504 } elseif ($type == 1) {
5505 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5506 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5507 } elseif ($type == 2) {
5508 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5509 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5510 }
5511 } elseif ($type == 2 && $status == 2) {
5512 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5513 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5514 }
5515
5516 if ($mode > 6) {
5517 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5518 } else {
5519 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5520 }
5521 }
5522
5523
5529 public function getLibFinished()
5530 {
5531 global $langs;
5532 $langs->load('products');
5533
5534 if (isset($this->finished) && $this->finished >= 0) {
5535 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5536 $resql = $this->db->query($sql);
5537 if ($resql && $this->db->num_rows($resql) > 0) {
5538 $res = $this->db->fetch_array($resql);
5539 $label = $langs->trans($res['label']);
5540 $this->db->free($resql);
5541 return $label;
5542 } else {
5543 $this->error = $this->db->error().' sql='.$sql;
5544 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
5545 return -1;
5546 }
5547 }
5548
5549 return '';
5550 }
5551
5552
5553 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5570 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
5571 {
5572 // phpcs:enable
5573 if ($id_entrepot) {
5574 $this->db->begin();
5575
5576 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5577
5578 if ($nbpiece < 0) {
5579 if (!$movement) {
5580 $movement = 1;
5581 }
5582 $nbpiece = abs($nbpiece);
5583 }
5584
5585 $op[0] = "+".trim($nbpiece);
5586 $op[1] = "-".trim($nbpiece);
5587
5588 $movementstock = new MouvementStock($this->db);
5589 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
5590 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
5591
5592 if ($result >= 0) {
5593 if ($extrafields) {
5594 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5595 $movementstock->array_options = $array_options;
5596 $movementstock->insertExtraFields();
5597 }
5598 $this->db->commit();
5599 return 1;
5600 } else {
5601 $this->error = $movementstock->error;
5602 $this->errors = $movementstock->errors;
5603
5604 $this->db->rollback();
5605 return -1;
5606 }
5607 }
5608
5609 return -1;
5610 }
5611
5612 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5633 public function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $dlc = '', $dluo = '', $lot = '', $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null, $force_update_batch = false)
5634 {
5635 // phpcs:enable
5636 if ($id_entrepot) {
5637 $this->db->begin();
5638
5639 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
5640
5641 if ($nbpiece < 0) {
5642 if (!$movement) {
5643 $movement = 1;
5644 }
5645 $nbpiece = abs($nbpiece);
5646 }
5647
5648 $op[0] = "+".trim($nbpiece);
5649 $op[1] = "-".trim($nbpiece);
5650
5651 $movementstock = new MouvementStock($this->db);
5652 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
5653 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
5654
5655 if ($result >= 0) {
5656 if ($extrafields) {
5657 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
5658 $movementstock->array_options = $array_options;
5659 $movementstock->insertExtraFields();
5660 }
5661 $this->db->commit();
5662 return 1;
5663 } else {
5664 $this->error = $movementstock->error;
5665 $this->errors = $movementstock->errors;
5666
5667 $this->db->rollback();
5668 return -1;
5669 }
5670 }
5671 return -1;
5672 }
5673
5674 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5687 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
5688 {
5689 // phpcs:enable
5690 global $conf;
5691
5692 $this->stock_reel = 0;
5693 $this->stock_warehouse = array();
5694 $this->stock_theorique = 0;
5695
5696 // Set filter on warehouse status
5697 $warehouseStatus = array();
5698 if (preg_match('/warehouseclosed/', $option)) {
5700 }
5701 if (preg_match('/warehouseopen/', $option)) {
5703 }
5704 if (preg_match('/warehouseinternal/', $option)) {
5705 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
5707 } else {
5709 }
5710 }
5711
5712 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
5713 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
5714 $sql .= ", ".$this->db->prefix()."entrepot as w";
5715 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
5716 $sql .= " AND w.rowid = ps.fk_entrepot";
5717 $sql .= " AND ps.fk_product = ".((int) $this->id);
5718 if (count($warehouseStatus)) {
5719 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
5720 }
5721
5722 $sql .= " ORDER BY ps.reel ".(getDolGlobalString('DO_NOT_TRY_TO_DEFRAGMENT_STOCKS_WAREHOUSE') ? 'DESC' : 'ASC'); // Note : qty ASC is important for expedition card, to avoid stock fragmentation;
5723
5724 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
5725 $result = $this->db->query($sql);
5726 if ($result) {
5727 $num = $this->db->num_rows($result);
5728 $i = 0;
5729 if ($num > 0) {
5730 while ($i < $num) {
5731 $row = $this->db->fetch_object($result);
5732 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
5733 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
5734 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
5735 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
5736 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
5737 }
5738 $this->stock_reel += $row->reel;
5739 $i++;
5740 }
5741 }
5742 $this->db->free($result);
5743
5744 if (!preg_match('/novirtual/', $option)) {
5745 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
5746 }
5747
5748 return 1;
5749 } else {
5750 $this->error = $this->db->lasterror();
5751 return -1;
5752 }
5753 }
5754
5755
5756 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5766 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
5767 {
5768 // phpcs:enable
5769 global $conf, $hookmanager, $action;
5770
5771 $stock_commande_client = 0;
5772 $stock_commande_fournisseur = 0;
5773 $stock_sending_client = 0;
5774 $stock_reception_fournisseur = 0;
5775 $stock_inproduction = 0;
5776
5777 //dol_syslog("load_virtual_stock");
5778
5779 if (isModEnabled('commande')) {
5780 $result = $this->load_stats_commande(0, '1,2', 1);
5781 if ($result < 0) {
5782 dol_print_error($this->db, $this->error);
5783 }
5784 $stock_commande_client = $this->stats_commande['qty'];
5785 }
5786 if (isModEnabled("expedition")) {
5787 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
5788 $filterShipmentStatus = '';
5789 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
5790 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
5791 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5792 $filterShipmentStatus = Expedition::STATUS_CLOSED;
5793 }
5794 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
5795 if ($result < 0) {
5796 dol_print_error($this->db, $this->error);
5797 }
5798 $stock_sending_client = $this->stats_expedition['qty'];
5799 }
5800 if (isModEnabled("supplier_order")) {
5801 $filterStatus = !getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK') ? '3,4' : $conf->global->SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK;
5802 if (isset($includedraftpoforvirtual)) {
5803 $filterStatus = '0,1,2,'.$filterStatus; // 1,2 may have already been inside $filterStatus but it is better to have twice than missing $filterStatus does not include them
5804 }
5805 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
5806 if ($result < 0) {
5807 dol_print_error($this->db, $this->error);
5808 }
5809 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
5810 }
5811 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && empty($conf->reception->enabled)) {
5812 // Case module reception is not used
5813 $filterStatus = '4';
5814 if (isset($includedraftpoforvirtual)) {
5815 $filterStatus = '0,'.$filterStatus;
5816 }
5817 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
5818 if ($result < 0) {
5819 dol_print_error($this->db, $this->error);
5820 }
5821 $stock_reception_fournisseur = $this->stats_reception['qty'];
5822 }
5823 if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && isModEnabled("reception")) {
5824 // Case module reception is used
5825 $filterStatus = '4';
5826 if (isset($includedraftpoforvirtual)) {
5827 $filterStatus = '0,'.$filterStatus;
5828 }
5829 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock); // Use same tables than when module reception is not used.
5830 if ($result < 0) {
5831 dol_print_error($this->db, $this->error);
5832 }
5833 $stock_reception_fournisseur = $this->stats_reception['qty'];
5834 }
5835 if (isModEnabled('mrp')) {
5836 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
5837 if ($result < 0) {
5838 dol_print_error($this->db, $this->error);
5839 }
5840 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
5841 }
5842
5843 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
5844
5845 // Stock decrease mode
5846 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
5847 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
5848 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
5849 $this->stock_theorique += 0;
5850 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
5851 $this->stock_theorique -= $stock_commande_client;
5852 }
5853 // Stock Increase mode
5854 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
5855 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5856 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
5857 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5858 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
5859 $this->stock_theorique -= $stock_reception_fournisseur;
5860 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
5861 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
5862 }
5863
5864 $parameters = array('id'=>$this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
5865 // Note that $action and $object may have been modified by some hooks
5866 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
5867 if ($reshook > 0) {
5868 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
5869 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
5870 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
5871 }
5872
5873 return 1;
5874 }
5875
5876
5884 public function loadBatchInfo($batch)
5885 {
5886 $result = array();
5887
5888 $sql = "SELECT pb.batch, pb.eatby, pb.sellby, SUM(pb.qty) AS qty FROM ".$this->db->prefix()."product_batch as pb, ".$this->db->prefix()."product_stock as ps";
5889 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
5890 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
5891 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
5892 $resql = $this->db->query($sql);
5893 if ($resql) {
5894 $num = $this->db->num_rows($resql);
5895 $i = 0;
5896 while ($i < $num) {
5897 $obj = $this->db->fetch_object($resql);
5898 $result[] = array('batch'=>$batch, 'eatby'=>$this->db->jdate($obj->eatby), 'sellby'=>$this->db->jdate($obj->sellby), 'qty'=>$obj->qty);
5899 $i++;
5900 }
5901 return $result;
5902 } else {
5903 dol_print_error($this->db);
5904 $this->db->rollback();
5905 return array();
5906 }
5907 }
5908
5909 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5917 public function add_photo($sdir, $file)
5918 {
5919 // phpcs:enable
5920 global $conf;
5921
5922 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5923
5924 $result = 0;
5925
5926 $dir = $sdir;
5927 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5928 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
5929 } else {
5930 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
5931 }
5932
5933 dol_mkdir($dir);
5934
5935 $dir_osencoded = $dir;
5936
5937 if (is_dir($dir_osencoded)) {
5938 $originImage = $dir.'/'.$file['name'];
5939
5940 // Cree fichier en taille origine
5941 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
5942
5943 if (file_exists(dol_osencode($originImage))) {
5944 // Create thumbs
5945 $this->addThumbs($originImage);
5946 }
5947 }
5948
5949 if (is_numeric($result) && $result > 0) {
5950 return 1;
5951 } else {
5952 return -1;
5953 }
5954 }
5955
5956 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5963 public function is_photo_available($sdir)
5964 {
5965 // phpcs:enable
5966 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
5967 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
5968
5969 global $conf;
5970
5971 $dir = $sdir;
5972 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5973 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
5974 } else {
5975 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
5976 }
5977
5978 $nbphoto = 0;
5979
5980 $dir_osencoded = dol_osencode($dir);
5981 if (file_exists($dir_osencoded)) {
5982 $handle = opendir($dir_osencoded);
5983 if (is_resource($handle)) {
5984 while (($file = readdir($handle)) !== false) {
5985 if (!utf8_check($file)) {
5986 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
5987 }
5988 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
5989 return true;
5990 }
5991 }
5992 }
5993 }
5994 return false;
5995 }
5996
5997 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6005 public function liste_photos($dir, $nbmax = 0)
6006 {
6007 // phpcs:enable
6008 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6009 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6010
6011 $nbphoto = 0;
6012 $tabobj = array();
6013
6014 $dir_osencoded = dol_osencode($dir);
6015 $handle = @opendir($dir_osencoded);
6016 if (is_resource($handle)) {
6017 while (($file = readdir($handle)) !== false) {
6018 if (!utf8_check($file)) {
6019 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6020 }
6021 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6022 $nbphoto++;
6023
6024 // We forge name of thumb.
6025 $photo = $file;
6026 $photo_vignette = '';
6027 $regs = array();
6028 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6029 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6030 }
6031
6032 $dirthumb = $dir.'thumbs/';
6033
6034 // Objet
6035 $obj = array();
6036 $obj['photo'] = $photo;
6037 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6038 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6039 } else {
6040 $obj['photo_vignette'] = "";
6041 }
6042
6043 $tabobj[$nbphoto - 1] = $obj;
6044
6045 // Do we have to continue with next photo ?
6046 if ($nbmax && $nbphoto >= $nbmax) {
6047 break;
6048 }
6049 }
6050 }
6051
6052 closedir($handle);
6053 }
6054
6055 return $tabobj;
6056 }
6057
6058 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6065 public function delete_photo($file)
6066 {
6067 // phpcs:enable
6068 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6069 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6070
6071 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6072 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6073 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6074
6075 // On efface l'image d'origine
6076 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6077
6078 // Si elle existe, on efface la vignette
6079 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6080 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6081 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6082 dol_delete_file($dirthumb.$photo_vignette);
6083 }
6084
6085 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6086 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6087 dol_delete_file($dirthumb.$photo_vignette);
6088 }
6089 }
6090 }
6091
6092 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6099 public function get_image_size($file)
6100 {
6101 // phpcs:enable
6102 $file_osencoded = dol_osencode($file);
6103 $infoImg = getimagesize($file_osencoded); // Get information on image
6104 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6105 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6106 }
6107
6108 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6114 public function load_state_board()
6115 {
6116 // phpcs:enable
6117 global $hookmanager;
6118
6119 $this->nb = array();
6120
6121 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6122 $sql .= " FROM ".$this->db->prefix()."product as p";
6123 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6124 // Add where from hooks
6125 if (is_object($hookmanager)) {
6126 $parameters = array();
6127 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6128 $sql .= $hookmanager->resPrint;
6129 }
6130 $sql .= ' GROUP BY fk_product_type';
6131
6132 $resql = $this->db->query($sql);
6133 if ($resql) {
6134 while ($obj = $this->db->fetch_object($resql)) {
6135 if ($obj->fk_product_type == 1) {
6136 $this->nb["services"] = $obj->nb;
6137 } else {
6138 $this->nb["products"] = $obj->nb;
6139 }
6140 }
6141 $this->db->free($resql);
6142 return 1;
6143 } else {
6144 dol_print_error($this->db);
6145 $this->error = $this->db->error();
6146 return -1;
6147 }
6148 }
6149
6155 public function isProduct()
6156 {
6157 return ($this->type == Product::TYPE_PRODUCT ? true : false);
6158 }
6159
6165 public function isService()
6166 {
6167 return ($this->type == Product::TYPE_SERVICE ? true : false);
6168 }
6169
6175 public function isStockManaged()
6176 {
6177 return ($this->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'));
6178 }
6179
6185 public function isMandatoryPeriod()
6186 {
6187 return ($this->mandatory_period == 1 ? true : false);
6188 }
6189
6195 public function hasbatch()
6196 {
6197 return ($this->status_batch > 0 ? true : false);
6198 }
6199
6200
6201 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6210 public function get_barcode($object, $type = '')
6211 {
6212 // phpcs:enable
6213 global $conf;
6214
6215 $result = '';
6216 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6217 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6218 foreach ($dirsociete as $dirroot) {
6219 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6220 if ($res) {
6221 break;
6222 }
6223 }
6224 $var = $conf->global->BARCODE_PRODUCT_ADDON_NUM;
6225 $mod = new $var();
6226
6227 $result = $mod->getNextValue($object, $type);
6228
6229 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6230 }
6231 return $result;
6232 }
6233
6241 public function initAsSpecimen()
6242 {
6243 $now = dol_now();
6244
6245 // Initialize parameters
6246 $this->specimen = 1;
6247 $this->id = 0;
6248 $this->ref = 'PRODUCT_SPEC';
6249 $this->label = 'PRODUCT SPECIMEN';
6250 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6251 $this->specimen = 1;
6252 $this->country_id = 1;
6253 $this->status = 1;
6254 $this->status_buy = 1;
6255 $this->tobatch = 0;
6256 $this->note_private = 'This is a comment (private)';
6257 $this->note_public = 'This is a comment (public)';
6258 $this->date_creation = $now;
6259 $this->date_modification = $now;
6260
6261 $this->weight = 4;
6262 $this->weight_units = 3;
6263
6264 $this->length = 5;
6265 $this->length_units = 1;
6266 $this->width = 6;
6267 $this->width_units = 0;
6268 $this->height = null;
6269 $this->height_units = null;
6270
6271 $this->surface = 30;
6272 $this->surface_units = 0;
6273 $this->volume = 300;
6274 $this->volume_units = 0;
6275
6276 $this->barcode = -1; // Create barcode automatically
6277 }
6278
6285 public function getLabelOfUnit($type = 'long')
6286 {
6287 global $langs;
6288
6289 if (!$this->fk_unit) {
6290 return '';
6291 }
6292
6293 $langs->load('products');
6294
6295 $label_type = 'label';
6296 if ($type == 'short') {
6297 $label_type = 'short_label';
6298 }
6299
6300 $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6301
6302 $resql = $this->db->query($sql);
6303 if ($resql && $this->db->num_rows($resql) > 0) {
6304 $res = $this->db->fetch_array($resql);
6305 $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6306 $this->db->free($resql);
6307 return $label;
6308 } else {
6309 $this->error = $this->db->error();
6310 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6311 return -1;
6312 }
6313 }
6314
6315 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6321 public function min_recommended_price()
6322 {
6323 // phpcs:enable
6324 global $conf;
6325
6326 $maxpricesupplier = 0;
6327
6328 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
6329 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6330 $product_fourn = new ProductFournisseur($this->db);
6331 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6332
6333 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6334 foreach ($product_fourn_list as $productfourn) {
6335 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6336 $maxpricesupplier = $productfourn->fourn_unitprice;
6337 }
6338 }
6339
6340 $maxpricesupplier *= $conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE;
6341 }
6342 }
6343
6344 return $maxpricesupplier;
6345 }
6346
6347
6358 public function setCategories($categories)
6359 {
6360 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6361 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6362 }
6363
6372 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6373 {
6374 $tables = array(
6375 'product_customer_price',
6376 'product_customer_price_log'
6377 );
6378
6379 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6380 }
6381
6393 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6394 {
6395 global $conf;
6396
6397 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6398 $query = $this->db->query($sql);
6399
6400 $rules = array();
6401
6402 while ($result = $this->db->fetch_object($query)) {
6403 $rules[$result->level] = $result;
6404 }
6405
6406 //Because prices can be based on other level's prices, we temporarily store them
6407 $prices = array(
6408 1 => $baseprice
6409 );
6410
6411 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
6412 for ($i = 1; $i <= $nbofproducts; $i++) {
6413 $price = $baseprice;
6414 $price_min = $baseprice;
6415
6416 //We have to make sure it does exist and it is > 0
6417 //First price level only allows changing min_price
6418 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6419 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6420 }
6421
6422 $prices[$i] = $price;
6423
6424 //We have to make sure it does exist and it is > 0
6425 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6426 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6427 }
6428
6429 //Little check to make sure the price is modified before triggering generation
6430 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6431 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6432
6433 if ($check_amount && $check_type) {
6434 continue;
6435 }
6436
6437 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
6438 return -1;
6439 }
6440 }
6441
6442 return 1;
6443 }
6444
6450 public function getRights()
6451 {
6452 global $user;
6453
6454 if ($this->isProduct()) {
6455 return $user->rights->produit;
6456 } else {
6457 return $user->rights->service;
6458 }
6459 }
6460
6467 public function info($id)
6468 {
6469 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6470 $sql .= " p.fk_user_author, p.fk_user_modif";
6471 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6472 $sql .= " WHERE p.rowid = ".((int) $id);
6473
6474 $result = $this->db->query($sql);
6475 if ($result) {
6476 if ($this->db->num_rows($result)) {
6477 $obj = $this->db->fetch_object($result);
6478
6479 $this->id = $obj->rowid;
6480 $this->ref = $obj->ref;
6481
6482 $this->user_creation_id = $obj->fk_user_author;
6483 $this->user_modification_id = $obj->fk_user_modif;
6484
6485 $this->date_creation = $this->db->jdate($obj->date_creation);
6486 $this->date_modification = $this->db->jdate($obj->date_modification);
6487 }
6488
6489 $this->db->free($result);
6490 } else {
6491 dol_print_error($this->db);
6492 }
6493 }
6494
6495
6500 public function getProductDurationHours()
6501 {
6502 global $langs;
6503
6504 if (empty($this->duration_value)) {
6505 $this->errors[]='ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6506 return -1;
6507 }
6508
6509 if ($this->duration_unit == 'i') {
6510 $prodDurationHours = 1. / 60;
6511 }
6512 if ($this->duration_unit == 'h') {
6513 $prodDurationHours = 1.;
6514 }
6515 if ($this->duration_unit == 'd') {
6516 $prodDurationHours = 24.;
6517 }
6518 if ($this->duration_unit == 'w') {
6519 $prodDurationHours = 24. * 7;
6520 }
6521 if ($this->duration_unit == 'm') {
6522 $prodDurationHours = 24. * 30;
6523 }
6524 if ($this->duration_unit == 'y') {
6525 $prodDurationHours = 24. * 365;
6526 }
6527 $prodDurationHours *= $this->duration_value;
6528
6529 return $prodDurationHours;
6530 }
6531
6532
6540 public function getKanbanView($option = '', $arraydata = null)
6541 {
6542 global $langs,$conf;
6543
6544 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6545
6546 $return = '<div class="box-flex-item box-flex-grow-zero">';
6547 $return .= '<div class="info-box info-box-sm">';
6548 $return .= '<div class="info-box-img">';
6549 $label = '';
6550 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
6551 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
6552 $return .= $label;
6553 } else {
6554 if ($this->type == Product::TYPE_PRODUCT) {
6555 $label .= img_picto('', 'product');
6556 } elseif ($this->type == Product::TYPE_SERVICE) {
6557 $label .= img_picto('', 'service');
6558 }
6559 $return .= $label;
6560 }
6561 $return .= '</div>';
6562 $return .= '<div class="info-box-content">';
6563 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
6564 if ($selected >= 0) {
6565 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
6566 }
6567 if (property_exists($this, 'label')) {
6568 $return .= '<br><span class="info-box-label opacitymedium">'.$this->label.'</span>';
6569 }
6570 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
6571 if ($this->price_base_type == 'TTC') {
6572 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
6573 } else {
6574 if ($this->status) {
6575 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
6576 }
6577 }
6578 }
6579 $br = 1;
6580 if (property_exists($this, 'stock_reel') && $this->isProduct()) {
6581 $return .= '<br><div class="info-box-status opacitymedium inline-block valignmiddle">'.img_picto($langs->trans('PhysicalStock'), 'stock').'</div><div class="inline-block valignmiddle paddingleft" title="'.$langs->trans('PhysicalStock').'">'.$this->stock_reel.'</div>';
6582 $br = 0;
6583 }
6584 if (method_exists($this, 'getLibStatut')) {
6585 if ($br) {
6586 $return .= '<br><div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6587 } else {
6588 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
6589 }
6590 }
6591 $return .= '</div>';
6592 $return .= '</div>';
6593 $return .= '</div>';
6594 return $return;
6595 }
6596}
6597
6603{
6604 public $picto = 'service';
6605}
length_accountg($account)
Return General accounting account with defined length (used for product and miscellaneous)
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Definition security.php:604
$object ref
Definition info.php:79
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
deleteEcmFiles($mode=0)
Delete related files of object in database.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
isObjectUsed($id=0, $entity=0)
Function to check if an object is used by others (by children).
deleteExtraFields()
Delete all extra fields values for the current object.
addThumbs($file)
Build thumb.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
show_photos($modulepart, $sdir, $size=0, $nbmax=0, $nbbyrow=5, $showfilename=0, $showaction=0, $maxHeight=120, $maxWidth=160, $nolink=0, $overwritetitle=0, $usesharelink=0, $cache='', $addphotorefcss='photoref')
Show photos of an object (nbmax maximum), into several columns.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
call_trigger($triggerName, $user)
Call trigger based on this instance.
Class to manage Dolibarr database access.
const STATUS_OPEN_INTERNAL
Warehouse open and only operations for stock transfers/corrections allowed (not for customer shipping...
const STATUS_OPEN_ALL
Warehouse open and any operations are allowed (customer shipping, supplier dispatch,...
const STATUS_CLOSED
Warehouse closed, inactive.
const STATUS_CLOSED
Closed status.
const STATUS_VALIDATED
Validated status.
const STATUS_DRAFT
Draft status.
Class to manage generation of HTML components Only common components must be here.
Class to manage stock movements.
Class to parse product price expressions.
Class ProductCombination Used to represent a product combination.
File of class to manage predefined price products or services by customer.
Class to manage predefined suppliers products.
Class to manage products or services.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
get_nb_achat($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or supplier invoices in which product is included.
getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp=0)
Return price of sell of a product for a seller/buyer/product.
__construct($db)
Constructor.
$price_by_qty
Price by quantity arrays.
is_sousproduit($fk_parent, $fk_child)
Check if it is a sub-product into a kit.
const SELL_OR_EAT_BY_MANDATORY_ID_NONE
Const sell or eat by mandatory id.
isStockManaged()
Return if object need to have its stock managed.
$duration
Service expiration label (value + unit)
setPriceExpression($expression_id)
Sets the supplier price expression.
getArrayForPriceCompare($level=0)
used to check if price have really change to avoid log pollution
get_arbo_each_prod($multiply=1, $ignore_stock_load=0)
Build the tree of subproducts and return it.
check_barcode($valuetotest, $typefortest)
Check barcode.
list_suppliers()
Return list of suppliers providing the product or service.
load_stats_mo($socid=0)
Charge tableau des stats OF pour le produit/service.
isVariant()
Return if loaded product is a variant.
hasVariants()
Return if a product has variants or not.
delMultiLangs($langtodelete, $user)
Delete a language for this product.
getLabelOfUnit($type='long')
Returns the text label from units dictionary.
load_stats_proposal_supplier($socid=0)
Charge tableau des stats propale pour le produit/service.
getLibFinished()
Retour label of nature of product.
add_sousproduit($id_pere, $id_fils, $qty, $incdec=1, $notrigger=0)
Link a product/service to a parent product/service.
add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
Add a supplier price for the product.
hasFatherOrChild($mode=0)
Count all parent and children products for current product (first level only)
load_stats_facturerec($socid=0)
Charge tableau des stats facture recurrentes pour le produit/service.
$product_id_already_linked
Product ID already linked to a reference supplier.
get_nb_propalsupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in proposals in which product is included.
get_nb_contract($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
load_stats_facture_fournisseur($socid=0)
Charge tableau des stats facture pour le produit/service.
get_nb_ordersupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
getMultiLangs()
Load array this->multilangs.
get_nb_mos($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
clone_associations($fromId, $toId)
Clone links between products.
create($user, $notrigger=0)
Insert product into database.
load_stats_contrat($socid=0)
Charge tableau des stats contrat pour le produit/service.
updatePrice($newprice, $newpricebase, $user, $newvat='', $newminprice=0, $level=0, $newnpr=0, $newpbq=0, $ignore_autogen=0, $localtaxes_array=array(), $newdefaultvatcode='', $notrigger=0)
Modify customer price of a product/Service for a given level.
isService()
Return if object is a product.
getRights()
Returns the rights used for this class.
loadBatchInfo($batch)
Load existing information about a serial.
$pmp
Average price value for product entry into stock (PMP)
load_stock($option='', $includedraftpoforvirtual=null, $dateofvirtualstock=null)
Load information about stock of a product into ->stock_reel, ->stock_warehouse[] (including stock_war...
getProductDurationHours()
Return the duration in Hours of a service base on duration fields.
fetch($id='', $ref='', $ref_ext='', $barcode='', $ignore_expression=0, $ignore_price_load=0, $ignore_lang_load=0)
Load a product in memory from database.
$default_vat_code
Default VAT code for product (link to code into llx_c_tva but without foreign keys)
$duration_unit
Serivce expiration unit.
get_buyprice($prodfournprice, $qty, $product_id=0, $fourn_ref='', $fk_soc=0)
Read price used by a provider.
clone_fournisseurs($fromId, $toId)
Recopie les fournisseurs et prix fournisseurs d'un produit/service sur un autre.
const TYPE_PRODUCT
Regular product.
$stock_warehouse
Contains detail of stock of product into each warehouse.
$fk_default_workstation
Service Workstation.
add_photo($sdir, $file)
Move an uploaded file described into $file array into target directory $sdir.
log_price_delete($user, $rowid)
Delete a price line.
update($id, $user, $notrigger=false, $action='update', $updatetype=false)
Update a record into database.
info($id)
Load information for tab info.
load_stats_inproduction($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null)
Charge tableau des stats production pour le produit/service.
correct_stock($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $inventorycode='', $origin_element='', $origin_id=null, $disablestockchangeforsubproduct=0, $extrafields=null)
Adjust stock in a warehouse for product.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
Create a document onto disk according to template module.
$multiprices
Arrays for multiprices.
$localtax1_tx
Other local taxes.
getChildsArbo($id, $firstlevelonly=0, $level=1, $parents=array())
Return childs of product $id.
load_virtual_stock($includedraftpoforvirtual=null, $dateofvirtualstock=null)
Load value ->stock_theorique of a product.
load_stats_propale($socid=0)
Charge tableau des stats propale pour le produit/service.
get_barcode($object, $type='')
Get a barcode from the module to generate barcode values.
setAccountancyCode($type, $value)
Sets an accountancy code for a product.
load_stats_facture($socid=0)
Charge tableau des stats facture pour le produit/service.
$remise_percent
Default discount percent.
$imgWidth
Size of image.
setCategories($categories)
Sets object to supplied categories.
load_stats_reception($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null)
Charge tableau des stats réception fournisseur pour le produit/service.
get_nb_propal($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in proposals in which product is included.
setMultiLangs($user)
Update or add a translation for a product.
correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $dlc='', $dluo='', $lot='', $inventorycode='', $origin_element='', $origin_id=null, $disablestockchangeforsubproduct=0, $extrafields=null, $force_update_batch=false)
Adjust stock in a warehouse for product with batch number.
$tva_npr
int French VAT NPR is used (0 or 1)
$tva_tx
Default VAT rate of product.
load_stats_bom($socid=0)
Charge tableau des stats OF pour le produit/service.
hasbatch()
Return if object has a sell-by date or eat-by date.
$weight
Metric of products.
del_sousproduit($fk_parent, $fk_child, $notrigger=0)
Remove a link between a subproduct and a parent product/service.
update_sousproduit($id_pere, $id_fils, $qty, $incdec=1, $notrigger=0)
Modify composed product.
load_stats_commande($socid=0, $filtrestatut='', $forVirtualStock=0)
Charge tableau des stats commande client pour le produit/service.
delete_photo($file)
Delete a photo and its thumbs.
fetch_prod_arbo($prod, $compl_path='', $multiply=1, $level=1, $id_parent=0, $ignore_stock_load=0)
Function recursive, used only by get_arbo_each_prod(), to build tree of subproducts into ->res Define...
getKanbanView($option='', $arraydata=null)
Return clicable link of object (with eventually picto)
getLibStatut($mode=0, $type=0)
Return label of status of object.
load_stats_sending($socid=0, $filtrestatut='', $forVirtualStock=0, $filterShipmentStatus='')
Charge tableau des stats expedition client pour le produit/service.
clone_price($fromId, $toId)
Recopie les prix d'un produit/service sur un autre.
$duration_value
Service expiration.
check()
Check that ref and label are ok.
initAsSpecimen()
Initialise an instance with random values.
liste_photos($dir, $nbmax=0)
Return an array with all photos of product found on disk.
getFather()
Return all parent products for current product (first level only)
getNomUrl($withpicto=0, $option='', $maxlength=0, $save_lastsearch_value=-1, $notooltip=0, $morecss='', $add_label=0, $sep=' - ')
Return clickable link of object (with eventually picto)
$product_fourn_id
Id du fournisseur.
$desiredstock
Ask for replenishment when $desiredstock < $stock_reel.
verify()
Check properties of product are ok (like name, barcode, ...).
get_sousproduits_arbo()
get_nb_order($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
load_state_board()
Load indicators this->nb for the dashboard.
min_recommended_price()
Return minimum product recommended price.
_log_price($user, $level=0)
Insert a track that we changed a customer price.
$res
Path of subproducts.
_get_stats($sql, $mode, $year=0)
Return an array formated for showing graphs.
$multilangs
Array for multilangs.
load_stats_commande_fournisseur($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null)
Charge tableau des stats commande fournisseur pour le produit/service.
isMandatoryPeriod()
Return if object have a constraint on mandatory_period.
isProduct()
Return if object is a product.
generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
Generates prices for a product based on product multiprice generation rules.
LibStatut($status, $mode=0, $type=0)
Return label of a given status.
const TYPE_SERVICE
Service.
is_photo_available($sdir)
Return if at least one photo is available.
get_image_size($file)
Load size of image file.
get_nb_vente($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or customers invoices in which product is included.
getTooltipContentArray($params)
getTooltipContentArray
Class to manage products or services.
Manage record for batch number management.
static findAll($dbs, $fk_product_stock, $with_qty=0, $fk_product=0)
Return all batch detail records for a given product and warehouse.
Class with list of lots and properties.
Class to manage Dolibarr users.
hasRight($module, $permlevel1, $permlevel2='')
Return if a user has a permission.
getCountry($searchkey, $withcode='', $dbtouse=0, $outputlangs='', $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $nohook=0, $varfiles='addedfile', $upload_dir='')
Make control on an uploaded file from an GUI page and move it to final destination.
dol_is_file($pathoffile)
Return if path is a file.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed informations (by default a local PHP server timestamp) Re...
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
dol_clone($object, $native=0)
Create a clone of instance of object (new instance with same value for each properties) With native =...
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
utf8_check($str)
Check if a string is in UTF8.
get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that return vat rate of a product line (according to seller, buyer and product vat rate) VAT...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
measuringUnitString($unit, $measuring_style='', $scale='', $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.
measuring_units_squared($unit)
Transform a given unit scale into the square of that unit, if known.
measuring_units_cubed($unit)
Transform a given unit scale into the cube of that unit, if known.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:121