dolibarr 21.0.0-beta
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-2024 Frédéric France <frederic.france@free.fr>
18 * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
19 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
20 *
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 3 of the License, or
24 * (at your option) any later version.
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
30 *
31 * You should have received a copy of the GNU General Public License
32 * along with this program. If not, see <https://www.gnu.org/licenses/>.
33 */
34
40require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
41require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
42require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
43require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
44require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
45
49class Product extends CommonObject
50{
55 const SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY = 1;
56 const SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY = 2;
57 const SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT = 3;
58
62 public $element = 'product';
63
67 public $table_element = 'product';
68
72 public $fk_element = 'fk_product';
73
77 protected $childtables = array(
78 'supplier_proposaldet' => array('name' => 'SupplierProposal', 'parent' => 'supplier_proposal', 'parentkey' => 'fk_supplier_proposal'),
79 'propaldet' => array('name' => 'Proposal', 'parent' => 'propal', 'parentkey' => 'fk_propal'),
80 'commandedet' => array('name' => 'Order', 'parent' => 'commande', 'parentkey' => 'fk_commande'),
81 'facturedet' => array('name' => 'Invoice', 'parent' => 'facture', 'parentkey' => 'fk_facture'),
82 'contratdet' => array('name' => 'Contract', 'parent' => 'contrat', 'parentkey' => 'fk_contrat'),
83 'facture_fourn_det' => array('name' => 'SupplierInvoice', 'parent' => 'facture_fourn', 'parentkey' => 'fk_facture_fourn'),
84 'commande_fournisseurdet' => array('name' => 'SupplierOrder', 'parent' => 'commande_fournisseur', 'parentkey' => 'fk_commande'),
85 'mrp_production' => array('name' => 'Mo', 'parent' => 'mrp_mo', 'parentkey' => 'fk_mo' ),
86 'bom_bom' => array('name' => 'BOM'),
87 'bom_bomline' => array('name' => 'BOMLine', 'parent' => 'bom_bom', 'parentkey' => 'fk_bom'),
88 );
89
95 public $picto = 'product';
96
100 protected $table_ref_field = 'ref';
101
106 public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm';
107
113 public $libelle;
114
120 public $label;
121
127 public $description;
128
134 public $other;
135
141 public $type = self::TYPE_PRODUCT;
142
148 public $price;
149
153 public $price_formated; // used by takepos/ajax/ajax.php
154
160 public $price_ttc;
161
165 public $price_ttc_formated; // used by takepos/ajax/ajax.php
166
172 public $price_min;
173
179 public $price_min_ttc;
180
185 public $price_base_type;
189 public $price_label;
190
192
195 public $multiprices = array();
199 public $multiprices_ttc = array();
203 public $multiprices_base_type = array();
207 public $multiprices_default_vat_code = array();
211 public $multiprices_min = array();
215 public $multiprices_min_ttc = array();
219 public $multiprices_tva_tx = array();
223 public $multiprices_recuperableonly = array();
224
226
229 public $price_by_qty;
233 public $prices_by_qty = array();
237 public $prices_by_qty_id = array();
241 public $prices_by_qty_list = array();
242
246 public $level;
247
251 public $multilangs = array();
252
256 public $default_vat_code;
257
261 public $tva_tx;
262
266 public $tva_npr = 0;
267
271 public $remise_percent;
272
276 public $localtax1_tx;
280 public $localtax2_tx;
284 public $localtax1_type;
288 public $localtax2_type;
289
290 // Properties set by get_buyprice() for return
291
295 public $desc_supplier;
299 public $vatrate_supplier;
303 public $default_vat_code_supplier;
304
308 public $fourn_multicurrency_price;
312 public $fourn_multicurrency_unitprice;
316 public $fourn_multicurrency_tx;
320 public $fourn_multicurrency_id;
324 public $fourn_multicurrency_code;
325
329 public $packaging;
330
331
338 public $lifetime;
339
346 public $qc_frequency;
347
353 public $stock_reel = 0;
354
360 public $stock_theorique;
361
367 public $cost_price;
368
372 public $pmp;
373
379 public $seuil_stock_alerte = 0;
380
384 public $desiredstock = 0;
385
389 public $duration_value;
393 public $duration_unit;
397 public $duration;
398
402 public $fk_default_workstation;
403
409 public $status = 0;
410
417 public $tosell;
418
424 public $status_buy = 0;
425
432 public $tobuy;
433
439 public $finished;
440
446 public $fk_default_bom;
447
453 public $product_fourn_price_id;
454
460 public $buyprice;
461
467 public $tobatch;
468
469
475 public $status_batch = 0;
476
482 public $sell_or_eat_by_mandatory = 0;
483
489 public $batch_mask = '';
490
496 public $customcode;
497
503 public $url;
504
506
509 public $weight;
510
514 public $weight_units; // scale -3, 0, 3, 6
518 public $length;
522 public $length_units; // scale -3, 0, 3, 6
526 public $width;
530 public $width_units; // scale -3, 0, 3, 6
534 public $height;
538 public $height_units; // scale -3, 0, 3, 6
542 public $surface;
546 public $surface_units; // scale -3, 0, 3, 6
550 public $volume;
554 public $volume_units; // scale -3, 0, 3, 6
555
559 public $net_measure;
563 public $net_measure_units; // scale -3, 0, 3, 6
564
568 public $accountancy_code_sell;
572 public $accountancy_code_sell_intra;
576 public $accountancy_code_sell_export;
580 public $accountancy_code_buy;
584 public $accountancy_code_buy_intra;
588 public $accountancy_code_buy_export;
589
593 public $barcode;
594
598 public $barcode_type;
599
603 public $barcode_type_code;
604
608 public $stats_propale = array();
609
613 public $stats_commande = array();
614
618 public $stats_contrat = array();
619
623 public $stats_facture = array();
624
628 public $stats_proposal_supplier = array();
629
633 public $stats_commande_fournisseur = array();
634
638 public $stats_expedition = array();
639
643 public $stats_reception = array();
644
648 public $stats_mo = array();
649 public $stats_bom = array();
650 public $stats_mrptoconsume = array();
651 public $stats_mrptoproduce = array();
652 public $stats_facturerec = array();
653 public $stats_facture_fournisseur = array();
654
658 public $imgWidth;
662 public $imgHeight;
663
668 public $product_fourn_id;
669
674 public $product_id_already_linked;
675
680 public $nbphoto = 0;
681
685 public $stock_warehouse = array();
686
690 public $fk_default_warehouse;
691
695 public $fk_price_expression;
696
701 public $fourn_qty;
702
707 public $fourn_pu;
708
713 public $fourn_price_base_type;
714
718 public $fourn_socid;
719
725 public $ref_fourn;
726
730 public $ref_supplier;
731
737 public $fk_unit;
738
744 public $price_autogen = 0;
745
751 public $supplierprices;
752
758 public $sousprods;
759
763 public $res;
764
765
771 public $is_object_used;
772
782 public $is_sousproduit_qty;
783
794 public $is_sousproduit_incdec;
795
799 public $mandatory_period;
800
801
830 public $fields = array(
831 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'index' => 1, 'position' => 1, 'comment' => 'Id'),
832 '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'),
833 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'notnull' => 1, 'index' => 1, 'position' => 5),
834 'label' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'showoncombobox' => 2, 'position' => 15, 'csslist' => 'tdoverflowmax250'),
835 'barcode' => array('type' => 'varchar(255)', 'label' => 'Barcode', 'enabled' => 'isModEnabled("barcode")', 'position' => 20, 'visible' => -1, 'showoncombobox' => 3, 'cssview' => 'tdwordbreak', 'csslist' => 'tdoverflowmax125'),
836 'fk_barcode_type' => array('type' => 'integer', 'label' => 'BarcodeType', 'enabled' => 1, 'position' => 21, 'notnull' => 0, 'visible' => -1,),
837 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 61),
838 'note' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 62),
839 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 500),
840 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 501),
841 //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
842 'fk_user_author' => array('type' => 'integer', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 510, 'foreignkey' => 'llx_user.rowid'),
843 'fk_user_modif' => array('type' => 'integer', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 511),
844 //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
845 'localtax1_tx' => array('type' => 'double(6,3)', 'label' => 'Localtax1tx', 'enabled' => 1, 'position' => 150, 'notnull' => 0, 'visible' => -1,),
846 'localtax1_type' => array('type' => 'varchar(10)', 'label' => 'Localtax1type', 'enabled' => 1, 'position' => 155, 'notnull' => 1, 'visible' => -1,),
847 'localtax2_tx' => array('type' => 'double(6,3)', 'label' => 'Localtax2tx', 'enabled' => 1, 'position' => 160, 'notnull' => 0, 'visible' => -1,),
848 'localtax2_type' => array('type' => 'varchar(10)', 'label' => 'Localtax2type', 'enabled' => 1, 'position' => 165, 'notnull' => 1, 'visible' => -1,),
849 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 170),
850 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'index' => 0, 'position' => 1000),
851 //'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')),
852 //'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')),
853 'mandatory_period' => array('type' => 'integer', 'label' => 'mandatoryperiod', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'default' => '0', 'index' => 1, 'position' => 1000),
854 );
855
859 const TYPE_PRODUCT = 0;
863 const TYPE_SERVICE = 1;
864
870 public function __construct($db)
871 {
872 $this->db = $db;
873
874 $this->ismultientitymanaged = 1;
875 $this->isextrafieldmanaged = 1;
876
877 $this->canvas = '';
878 }
879
885 public function check()
886 {
887 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
888 $this->ref = trim($this->ref);
889 } else {
890 $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
891 }
892
893 $err = 0;
894 if (dol_strlen(trim($this->ref)) == 0) {
895 $err++;
896 }
897
898 if (dol_strlen(trim($this->label)) == 0) {
899 $err++;
900 }
901
902 if ($err > 0) {
903 return 0;
904 } else {
905 return 1;
906 }
907 }
908
916 public function create($user, $notrigger = 0)
917 {
918 global $conf, $langs;
919
920 $error = 0;
921
922 // Clean parameters
923 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
924 $this->ref = trim($this->ref);
925 } else {
926 $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
927 }
928 $this->label = trim($this->label);
929 $this->price_ttc = (float) price2num($this->price_ttc);
930 $this->price = (float) price2num($this->price);
931 $this->price_min_ttc = (float) price2num($this->price_min_ttc);
932 $this->price_min = (float) price2num($this->price_min);
933 $this->price_label = trim($this->price_label);
934 if (empty($this->tva_tx)) {
935 $this->tva_tx = 0;
936 }
937 if (empty($this->tva_npr)) {
938 $this->tva_npr = 0;
939 }
940 //Local taxes
941 if (empty($this->localtax1_tx)) {
942 $this->localtax1_tx = 0;
943 }
944 if (empty($this->localtax2_tx)) {
945 $this->localtax2_tx = 0;
946 }
947 if (empty($this->localtax1_type)) {
948 $this->localtax1_type = '0';
949 }
950 if (empty($this->localtax2_type)) {
951 $this->localtax2_type = '0';
952 }
953 if (empty($this->price)) {
954 $this->price = 0;
955 }
956 if (empty($this->price_min)) {
957 $this->price_min = 0;
958 }
959 // Price by quantity
960 if (empty($this->price_by_qty)) {
961 $this->price_by_qty = 0;
962 }
963
964 if (empty($this->status)) {
965 $this->status = 0;
966 }
967 if (empty($this->status_buy)) {
968 $this->status_buy = 0;
969 }
970
971 $price_ht = 0;
972 $price_ttc = 0;
973 $price_min_ht = 0;
974 $price_min_ttc = 0;
975
976 //
977 if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
978 $price_ttc = price2num($this->price_ttc, 'MU');
979 $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
980 }
981
982 //
983 if ($this->price_base_type != 'TTC' && $this->price > 0) {
984 $price_ht = price2num($this->price, 'MU');
985 $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
986 }
987
988 //
989 if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
990 $price_min_ttc = price2num($this->price_min_ttc, 'MU');
991 $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
992 }
993
994 //
995 if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
996 $price_min_ht = price2num($this->price_min, 'MU');
997 $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
998 }
999
1000 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1001 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
1002 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1003 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1004 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1005 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1006
1007 // Barcode value
1008 $this->barcode = trim($this->barcode);
1009 $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
1010 // Check parameters
1011 if (empty($this->label)) {
1012 $this->error = 'ErrorMandatoryParametersNotProvided';
1013 return -1;
1014 }
1015
1016 if (empty($this->ref) || $this->ref == 'auto') {
1017 // Load object modCodeProduct
1018 $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
1019 if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
1020 if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
1021 $module = substr($module, 0, dol_strlen($module) - 4);
1022 }
1023 dol_include_once('/core/modules/product/'.$module.'.php');
1024 $modCodeProduct = new $module();
1025 '@phan-var-force ModeleProductCode $modCodeProduct';
1026 if (!empty($modCodeProduct->code_auto)) {
1027 $this->ref = $modCodeProduct->getNextValue($this, $this->type);
1028 }
1029 unset($modCodeProduct);
1030 }
1031
1032 if (empty($this->ref)) {
1033 $this->error = 'ProductModuleNotSetupForAutoRef';
1034 return -2;
1035 }
1036 }
1037
1038 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);
1039
1040 $now = dol_now();
1041
1042 if (empty($this->date_creation)) {
1043 $this->date_creation = $now;
1044 }
1045
1046 $this->db->begin();
1047
1048 // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
1049 if ($this->barcode == '-1' || $this->barcode == 'auto') {
1050 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1051 }
1052
1053 // Check more parameters
1054 // If error, this->errors[] is filled
1055 $result = $this->verify();
1056
1057 if ($result >= 0) {
1058 $sql = "SELECT count(*) as nb";
1059 $sql .= " FROM ".$this->db->prefix()."product";
1060 $sql .= " WHERE entity IN (".getEntity('product').")";
1061 $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
1062
1063 $result = $this->db->query($sql);
1064 if ($result) {
1065 $obj = $this->db->fetch_object($result);
1066 if ($obj->nb == 0) {
1067 // Insert new product, no previous one found
1068 $sql = "INSERT INTO ".$this->db->prefix()."product (";
1069 $sql .= "datec";
1070 $sql .= ", entity";
1071 $sql .= ", ref";
1072 $sql .= ", ref_ext";
1073 $sql .= ", price_min";
1074 $sql .= ", price_min_ttc";
1075 $sql .= ", label";
1076 $sql .= ", fk_user_author";
1077 $sql .= ", fk_product_type";
1078 $sql .= ", price";
1079 $sql .= ", price_ttc";
1080 $sql .= ", price_base_type";
1081 $sql .= ", price_label";
1082 $sql .= ", tobuy";
1083 $sql .= ", tosell";
1084 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1085 $sql .= ", accountancy_code_buy";
1086 $sql .= ", accountancy_code_buy_intra";
1087 $sql .= ", accountancy_code_buy_export";
1088 $sql .= ", accountancy_code_sell";
1089 $sql .= ", accountancy_code_sell_intra";
1090 $sql .= ", accountancy_code_sell_export";
1091 }
1092 $sql .= ", canvas";
1093 $sql .= ", finished";
1094 $sql .= ", tobatch";
1095 $sql .= ", sell_or_eat_by_mandatory";
1096 $sql .= ", batch_mask";
1097 $sql .= ", fk_unit";
1098 $sql .= ", mandatory_period";
1099 $sql .= ") VALUES (";
1100 $sql .= "'".$this->db->idate($this->date_creation)."'";
1101 $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
1102 $sql .= ", '".$this->db->escape($this->ref)."'";
1103 $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1104 $sql .= ", ".price2num($price_min_ht);
1105 $sql .= ", ".price2num($price_min_ttc);
1106 $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
1107 $sql .= ", ".((int) $user->id);
1108 $sql .= ", ".((int) $this->type);
1109 $sql .= ", ".price2num($price_ht, 'MT');
1110 $sql .= ", ".price2num($price_ttc, 'MT');
1111 $sql .= ", '".$this->db->escape($this->price_base_type)."'";
1112 $sql .= ", ".(!empty($this->price_label) ? "'".$this->db->escape($this->price_label)."'" : "null");
1113 $sql .= ", ".((int) $this->status);
1114 $sql .= ", ".((int) $this->status_buy);
1115 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1116 $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
1117 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
1118 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
1119 $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
1120 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
1121 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
1122 }
1123 $sql .= ", '".$this->db->escape($this->canvas)."'";
1124 $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
1125 $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
1126 $sql .= ", ".((empty($this->sell_or_eat_by_mandatory) || $this->sell_or_eat_by_mandatory < 0) ? 0 : ((int) $this->sell_or_eat_by_mandatory));
1127 $sql .= ", '".$this->db->escape($this->batch_mask)."'";
1128 $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
1129 $sql .= ", '".$this->db->escape($this->mandatory_period)."'";
1130 $sql .= ")";
1131
1132 dol_syslog(get_class($this)."::Create", LOG_DEBUG);
1133
1134 $result = $this->db->query($sql);
1135 if ($result) {
1136 $id = $this->db->last_insert_id($this->db->prefix()."product");
1137
1138 if ($id > 0) {
1139 $this->id = $id;
1140 $this->price = $price_ht;
1141 $this->price_ttc = $price_ttc;
1142 $this->price_min = $price_min_ht;
1143 $this->price_min_ttc = $price_min_ttc;
1144
1145 $result = $this->_log_price($user);
1146 if ($result > 0) {
1147 if ($this->update($id, $user, 1, 'add') <= 0) {
1148 $error++;
1149 }
1150 } else {
1151 $error++;
1152 $this->error = $this->db->lasterror();
1153 }
1154
1155 // update accountancy for this entity
1156 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1157 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1158
1159 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1160 $sql .= " fk_product";
1161 $sql .= ", entity";
1162 $sql .= ", accountancy_code_buy";
1163 $sql .= ", accountancy_code_buy_intra";
1164 $sql .= ", accountancy_code_buy_export";
1165 $sql .= ", accountancy_code_sell";
1166 $sql .= ", accountancy_code_sell_intra";
1167 $sql .= ", accountancy_code_sell_export";
1168 $sql .= ") VALUES (";
1169 $sql .= $this->id;
1170 $sql .= ", " . ((int) $conf->entity);
1171 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1172 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1173 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1174 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1175 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1176 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1177 $sql .= ")";
1178 $result = $this->db->query($sql);
1179 if (!$result) {
1180 $error++;
1181 $this->error = 'ErrorFailedToInsertAccountancyForEntity';
1182 }
1183 }
1184 } else {
1185 $error++;
1186 $this->error = 'ErrorFailedToGetInsertedId';
1187 }
1188 } else {
1189 $error++;
1190 $this->error = $this->db->lasterror();
1191 }
1192 } else {
1193 // Product already exists with this ref
1194 $langs->load("products");
1195 $error++;
1196 $this->error = "ErrorProductAlreadyExists";
1197 dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
1198 }
1199 } else {
1200 $error++;
1201 $this->error = $this->db->lasterror();
1202 }
1203
1204 if (!$error && !$notrigger) {
1205 // Call trigger
1206 $result = $this->call_trigger('PRODUCT_CREATE', $user);
1207 if ($result < 0) {
1208 $error++;
1209 }
1210 // End call triggers
1211 }
1212
1213 if (!$error) {
1214 $this->db->commit();
1215 return $this->id;
1216 } else {
1217 $this->db->rollback();
1218 return -$error;
1219 }
1220 } else {
1221 $this->db->rollback();
1222 dol_syslog(get_class($this)."::Create fails verify ".implode(',', $this->errors), LOG_WARNING);
1223 return -3;
1224 }
1225 }
1226
1227
1234 public function verify()
1235 {
1236 global $langs;
1237
1238 $this->errors = array();
1239
1240 $result = 0;
1241 $this->ref = trim($this->ref);
1242
1243 if (!$this->ref) {
1244 $this->errors[] = 'ErrorBadRef';
1245 $result = -2;
1246 }
1247
1248 $arrayofnonnegativevalue = array('weight' => 'Weight', 'width' => 'Width', 'height' => 'Height', 'length' => 'Length', 'surface' => 'Surface', 'volume' => 'Volume');
1249 foreach ($arrayofnonnegativevalue as $key => $value) {
1250 if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
1251 $langs->loadLangs(array("main", "other"));
1252 $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
1253 $this->errors[] = $this->error;
1254 $result = -4;
1255 }
1256 }
1257
1258 $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
1259 if ($rescode) {
1260 if ($rescode == -1) {
1261 $this->errors[] = 'ErrorBadBarCodeSyntax';
1262 } elseif ($rescode == -2) {
1263 $this->errors[] = 'ErrorBarCodeRequired';
1264 } elseif ($rescode == -3) {
1265 // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
1266 $this->errors[] = 'ErrorBarCodeAlreadyUsed';
1267 }
1268
1269 $result = -3;
1270 }
1271
1272 return $result;
1273 }
1274
1275 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1286 public function check_barcode($valuetotest, $typefortest)
1287 {
1288 // phpcs:enable
1289 global $conf;
1290
1291 if (isModEnabled('barcode') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
1292 $module = strtolower(getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM'));
1293
1294 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1295 foreach ($dirsociete as $dirroot) {
1296 $res = dol_include_once($dirroot.$module.'.php');
1297 if ($res) {
1298 break;
1299 }
1300 }
1301
1302 $mod = new $module();
1303 '@phan-var-force ModeleNumRefBarCode $mod';
1304
1305 dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1306 $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1307 return $result;
1308 } else {
1309 return 0;
1310 }
1311 }
1312
1324 public function update($id, $user, $notrigger = 0, $action = 'update', $updatetype = false)
1325 {
1326 global $langs, $conf, $hookmanager;
1327
1328 $error = 0;
1329
1330 // Check parameters
1331 if (!$this->label) {
1332 $this->label = 'MISSING LABEL';
1333 }
1334
1335 // Clean parameters
1336 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1337 $this->ref = trim($this->ref);
1338 } else {
1339 $this->ref = dol_string_nospecial(trim($this->ref));
1340 }
1341 $this->label = trim($this->label);
1342 $this->description = trim($this->description);
1343 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1344 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1345 $this->net_measure = price2num($this->net_measure);
1346 $this->net_measure_units = (empty($this->net_measure_units) ? '' : trim((string) $this->net_measure_units));
1347 $this->weight = price2num($this->weight);
1348 $this->weight_units = (empty($this->weight_units) ? '' : trim((string) $this->weight_units));
1349 $this->length = price2num($this->length);
1350 $this->length_units = (empty($this->length_units) ? '' : trim((string) $this->length_units));
1351 $this->width = price2num($this->width);
1352 $this->width_units = (empty($this->width_units) ? '' : trim((string) $this->width_units));
1353 $this->height = price2num($this->height);
1354 $this->height_units = (empty($this->height_units) ? '' : trim((string) $this->height_units));
1355 $this->surface = price2num($this->surface);
1356 $this->surface_units = (empty($this->surface_units) ? '' : trim((string) $this->surface_units));
1357 $this->volume = price2num($this->volume);
1358 $this->volume_units = (empty($this->volume_units) ? '' : trim((string) $this->volume_units));
1359
1360 // set unit not defined
1361 if (is_numeric($this->length_units)) {
1362 $this->width_units = $this->length_units; // Not used yet
1363 }
1364 if (is_numeric($this->length_units)) {
1365 $this->height_units = $this->length_units; // Not used yet
1366 }
1367
1368 // Automated compute surface and volume if not filled
1369 if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1370 $this->surface = (float) $this->length * (float) $this->width;
1371 $this->surface_units = measuring_units_squared((int) $this->length_units);
1372 }
1373 if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1374 $this->volume = $this->surface * (float) $this->height;
1375 $this->volume_units = measuring_units_cubed((int) $this->height_units);
1376 }
1377
1378 if (empty($this->tva_tx)) {
1379 $this->tva_tx = 0;
1380 }
1381 if (empty($this->tva_npr)) {
1382 $this->tva_npr = 0;
1383 }
1384 if (empty($this->localtax1_tx)) {
1385 $this->localtax1_tx = 0;
1386 }
1387 if (empty($this->localtax2_tx)) {
1388 $this->localtax2_tx = 0;
1389 }
1390 if (empty($this->localtax1_type)) {
1391 $this->localtax1_type = '0';
1392 }
1393 if (empty($this->localtax2_type)) {
1394 $this->localtax2_type = '0';
1395 }
1396 if (empty($this->status)) {
1397 $this->status = 0;
1398 }
1399 if (empty($this->status_buy)) {
1400 $this->status_buy = 0;
1401 }
1402
1403 if (empty($this->country_id)) {
1404 $this->country_id = 0;
1405 }
1406
1407 if (empty($this->state_id)) {
1408 $this->state_id = 0;
1409 }
1410
1411 // Barcode value
1412 $this->barcode = (empty($this->barcode) ? '' : trim($this->barcode));
1413
1414 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1415 $this->accountancy_code_buy_intra = (!empty($this->accountancy_code_buy_intra) ? trim($this->accountancy_code_buy_intra) : '');
1416 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1417 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1418 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1419 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1420
1421
1422 $this->db->begin();
1423
1424 $result = 0;
1425 // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1426 if ($action != 'add') {
1427 $result = $this->verify(); // We don't check when update called during a create because verify was already done
1428 } else {
1429 // we can continue
1430 $result = 0;
1431 }
1432
1433 if ($result >= 0) {
1434 // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1435 if (is_null($this->oldcopy) || (is_object($this->oldcopy) && $this->oldcopy->isEmpty())) {
1436 $this->oldcopy = dol_clone($this, 1);
1437 }
1438 // Test if batch management is activated on existing product
1439 // If yes, we create missing entries into product_batch
1440 if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1441 //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1442 $valueforundefinedlot = '000000';
1443 if (getDolGlobalString('STOCK_DEFAULT_BATCH')) {
1444 $valueforundefinedlot = getDolGlobalString('STOCK_DEFAULT_BATCH');
1445 }
1446
1447 dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1448
1449 $this->load_stock();
1450 foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1451 $qty_batch = 0;
1452 foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1453 if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1454 // We discard this line, we will create it later
1455 $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1456 $result = $this->db->query($sqlclean);
1457 if (!$result) {
1458 dol_print_error($this->db);
1459 exit;
1460 }
1461 continue;
1462 }
1463
1464 $qty_batch += $detail->qty;
1465 }
1466 // Quantities in batch details are not same as stock quantity,
1467 // so we add a default batch record to complete and get same qty in parent and child table
1468 if ($ObjW->real != $qty_batch) {
1469 $ObjBatch = new Productbatch($this->db);
1470 $ObjBatch->batch = $valueforundefinedlot;
1471 $ObjBatch->qty = ($ObjW->real - $qty_batch);
1472 $ObjBatch->fk_product_stock = (int) $ObjW->id;
1473
1474 if ($ObjBatch->create($user, 1) < 0) {
1475 $error++;
1476 $this->errors = $ObjBatch->errors;
1477 } else {
1478 // we also add lot record if not exist
1479 $ObjLot = new Productlot($this->db);
1480 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
1481 if ($ObjLot->fetch(0, $this->id, $valueforundefinedlot) == 0) {
1482 $ObjLot->fk_product = $this->id;
1483 $ObjLot->entity = $this->entity;
1484 $ObjLot->fk_user_creat = $user->id;
1485 $ObjLot->batch = $valueforundefinedlot;
1486 if ($ObjLot->create($user, true) < 0) {
1487 $error++;
1488 $this->errors = $ObjLot->errors;
1489 }
1490 }
1491 }
1492 }
1493 }
1494 }
1495
1496 // For automatic creation
1497 if ($this->barcode == -1) {
1498 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1499 }
1500
1501 $sql = "UPDATE ".$this->db->prefix()."product";
1502 $sql .= " SET label = '".$this->db->escape($this->label)."'";
1503
1504 if ($updatetype && ($this->isProduct() || $this->isService())) {
1505 $sql .= ", fk_product_type = ".((int) $this->type);
1506 }
1507
1508 $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1509 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1510 $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1511 $sql .= ", tva_tx = ".((float) $this->tva_tx);
1512 $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1513 $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1514 $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1515 $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1516 $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1517
1518 $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1519 $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
1520
1521 $sql .= ", tosell = ".(int) $this->status;
1522 $sql .= ", tobuy = ".(int) $this->status_buy;
1523 $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1524 $sql .= ", sell_or_eat_by_mandatory = ".((empty($this->sell_or_eat_by_mandatory) || $this->sell_or_eat_by_mandatory < 0) ? 0 : (int) $this->sell_or_eat_by_mandatory);
1525 $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1526
1527 $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? "null" : (int) $this->finished);
1528 $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1529 $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1530 $sql .= ", net_measure_units = ".($this->net_measure_units != '' ? "'".$this->db->escape($this->net_measure_units)."'" : 'null');
1531 $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1532 $sql .= ", weight_units = ".($this->weight_units != '' ? "'".$this->db->escape($this->weight_units)."'" : 'null');
1533 $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1534 $sql .= ", length_units = ".($this->length_units != '' ? "'".$this->db->escape($this->length_units)."'" : 'null');
1535 $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1536 $sql .= ", width_units = ".($this->width_units != '' ? "'".$this->db->escape($this->width_units)."'" : 'null');
1537 $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1538 $sql .= ", height_units = ".($this->height_units != '' ? "'".$this->db->escape($this->height_units)."'" : 'null');
1539 $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1540 $sql .= ", surface_units = ".($this->surface_units != '' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
1541 $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1542 $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
1543 $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1544 $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1545 $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1546 $sql .= ", description = '".$this->db->escape($this->description)."'";
1547 $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1548 $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1549 $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1550 $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1551 $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1552 $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1553 $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1554 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1555 $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1556 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1557 $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1558 $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1559 $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1560 $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1561 $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1562 $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1563 }
1564 $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1565 $sql .= ", cost_price = ".($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null');
1566 $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1567 $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1568 $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1569 $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL');
1570 $sql .= ", mandatory_period = ".($this->mandatory_period);
1571 // stock field is not here because it is a denormalized value from product_stock.
1572 $sql .= " WHERE rowid = ".((int) $id);
1573
1574 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1575
1576 $resql = $this->db->query($sql);
1577 if ($resql) {
1578 $this->id = $id;
1579
1580 // Multilangs
1581 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1582 if ($this->setMultiLangs($user) < 0) {
1583 $this->db->rollback();
1584 return -2;
1585 }
1586 }
1587
1588 $action = 'update';
1589
1590 // update accountancy for this entity
1591 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1592 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1593
1594 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1595 $sql .= " fk_product";
1596 $sql .= ", entity";
1597 $sql .= ", accountancy_code_buy";
1598 $sql .= ", accountancy_code_buy_intra";
1599 $sql .= ", accountancy_code_buy_export";
1600 $sql .= ", accountancy_code_sell";
1601 $sql .= ", accountancy_code_sell_intra";
1602 $sql .= ", accountancy_code_sell_export";
1603 $sql .= ") VALUES (";
1604 $sql .= ((int) $this->id);
1605 $sql .= ", " . ((int) $conf->entity);
1606 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1607 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1608 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1609 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1610 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1611 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1612 $sql .= ")";
1613 $result = $this->db->query($sql);
1614 if (!$result) {
1615 $error++;
1616 $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1617 }
1618 }
1619
1620 if (!$this->hasbatch() && $this->oldcopy->hasbatch()) {
1621 // Selection of all product stock movements that contains batchs
1622 $sql = 'SELECT pb.qty, ps.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb';
1623 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = pb.fk_product_stock)';
1624 $sql .= ' WHERE ps.fk_product = '.(int) $this->id;
1625
1626 $resql = $this->db->query($sql);
1627 if ($resql) {
1628 $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S');
1629
1630 while ($obj = $this->db->fetch_object($resql)) {
1631 $value = $obj->qty;
1632 $fk_entrepot = $obj->fk_entrepot;
1633 $price = 0;
1634 $dlc = '';
1635 $dluo = '';
1636 $batch = $obj->batch;
1637
1638 // To know how to revert stockMouvement (add or remove)
1639 $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add
1640 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1641 $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true);
1642
1643 if ($res > 0) {
1644 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1645 $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0);
1646 if ($res < 0) {
1647 $error++;
1648 }
1649 } else {
1650 $error++;
1651 }
1652 }
1653 }
1654 }
1655
1656 // Actions on extra fields
1657 if (!$error) {
1658 $result = $this->insertExtraFields();
1659 if ($result < 0) {
1660 $error++;
1661 }
1662 }
1663
1664 if (!$error && !$notrigger) {
1665 // Call trigger
1666 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1667 if ($result < 0) {
1668 $error++;
1669 }
1670 // End call triggers
1671 }
1672
1673 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1674 // We remove directory
1675 if ($conf->product->dir_output) {
1676 $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1677 $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1678 if (file_exists($olddir)) {
1679 // include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1680 // $res = dol_move($olddir, $newdir);
1681 // do not use dol_move with directory
1682 $res = @rename($olddir, $newdir);
1683 if (!$res) {
1684 $langs->load("errors");
1685 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1686 $error++;
1687 } else {
1688 // to keep old entries with the new dir
1689 require_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
1690 $ecmfiles = new EcmFiles($this->db);
1691 $ecmfiles->updateAfterRename("produit/".dol_sanitizeFileName($this->oldcopy->ref), "produit/".dol_sanitizeFileName($this->ref));
1692 }
1693 }
1694 }
1695 }
1696
1697 if (!$error) {
1698 if (isModEnabled('variants')) {
1699 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1700
1701 $comb = new ProductCombination($this->db);
1702
1703 foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1704 $currcomb->updateProperties($this, $user);
1705 }
1706 }
1707
1708 $this->db->commit();
1709 return 1;
1710 } else {
1711 $this->db->rollback();
1712 return -$error;
1713 }
1714 } else {
1715 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1716 $langs->load("errors");
1717 if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1718 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1719 } else {
1720 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1721 }
1722 $this->errors[] = $this->error;
1723 $this->db->rollback();
1724 return -1;
1725 } else {
1726 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1727 $this->errors[] = $this->error;
1728 $this->db->rollback();
1729 return -2;
1730 }
1731 }
1732 } else {
1733 $this->db->rollback();
1734 dol_syslog(get_class($this)."::Update fails verify ".implode(',', $this->errors), LOG_WARNING);
1735 return -3;
1736 }
1737 }
1738
1746 public function delete(User $user, $notrigger = 0)
1747 {
1748 global $conf;
1749 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1750
1751 $error = 0;
1752
1753 // Check parameters
1754 if (empty($this->id)) {
1755 $this->error = "Object must be fetched before calling delete";
1756 return -1;
1757 }
1758 if (($this->isProduct() && !$user->hasRight('produit', 'supprimer')) || ($this->isService() && !$user->hasRight('service', 'supprimer'))) {
1759 $this->error = "ErrorForbidden";
1760 return 0;
1761 }
1762
1763 $objectisused = $this->isObjectUsed($this->id);
1764 if (empty($objectisused)) {
1765 $this->db->begin();
1766
1767 if (!$error && empty($notrigger)) {
1768 // Call trigger
1769 $result = $this->call_trigger('PRODUCT_DELETE', $user);
1770 if ($result < 0) {
1771 $error++;
1772 }
1773 // End call triggers
1774 }
1775
1776 // Delete from product_batch on product delete
1777 if (!$error) {
1778 $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1779 $sql .= " WHERE fk_product_stock IN (";
1780 $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1781 $sql .= " WHERE fk_product = ".((int) $this->id).")";
1782
1783 $result = $this->db->query($sql);
1784 if (!$result) {
1785 $error++;
1786 $this->errors[] = $this->db->lasterror();
1787 }
1788 }
1789
1790 // Delete all child tables
1791 if (!$error) {
1792 $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1793 foreach ($elements as $table) {
1794 if (!$error) {
1795 $sql = "DELETE FROM ".$this->db->prefix().$table;
1796 $sql .= " WHERE fk_product = ".(int) $this->id;
1797
1798 $result = $this->db->query($sql);
1799 if (!$result) {
1800 $error++;
1801 $this->errors[] = $this->db->lasterror();
1802 }
1803 }
1804 }
1805 }
1806
1807 if (!$error) {
1808 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1809 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1810
1811 //If it is a parent product, then we remove the association with child products
1812 $prodcomb = new ProductCombination($this->db);
1813
1814 if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1815 $error++;
1816 $this->errors[] = 'Error deleting combinations';
1817 }
1818
1819 //We also check if it is a child product
1820 if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1821 $error++;
1822 $this->errors[] = 'Error deleting child combination';
1823 }
1824 }
1825
1826 // Delete from product_association
1827 if (!$error) {
1828 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1829 $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1830
1831 $result = $this->db->query($sql);
1832 if (!$result) {
1833 $error++;
1834 $this->errors[] = $this->db->lasterror();
1835 }
1836 }
1837
1838 // Remove extrafields
1839 if (!$error) {
1840 $result = $this->deleteExtraFields();
1841 if ($result < 0) {
1842 $error++;
1843 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1844 }
1845 }
1846
1847 // Delete product
1848 if (!$error) {
1849 $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1850 $sqlz .= " WHERE rowid = ".(int) $this->id;
1851
1852 $resultz = $this->db->query($sqlz);
1853 if (!$resultz) {
1854 $error++;
1855 $this->errors[] = $this->db->lasterror();
1856 }
1857 }
1858
1859 // Delete record into ECM index and physically
1860 if (!$error) {
1861 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1862 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1863 if (!$res) {
1864 $error++;
1865 }
1866 }
1867
1868 if (!$error) {
1869 // We remove directory
1870 $ref = dol_sanitizeFileName($this->ref);
1871 if ($conf->product->dir_output) {
1872 $dir = $conf->product->dir_output."/".$ref;
1873 if (file_exists($dir)) {
1874 $res = @dol_delete_dir_recursive($dir);
1875 if (!$res) {
1876 $this->errors[] = 'ErrorFailToDeleteDir';
1877 $error++;
1878 }
1879 }
1880 }
1881 }
1882
1883 if (!$error) {
1884 $this->db->commit();
1885 return 1;
1886 } else {
1887 foreach ($this->errors as $errmsg) {
1888 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1889 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1890 }
1891 $this->db->rollback();
1892 return -$error;
1893 }
1894 } else {
1895 $this->error = "ErrorRecordIsUsedCantDelete";
1896 return 0;
1897 }
1898 }
1899
1905 public static function getSellOrEatByMandatoryList()
1906 {
1907 global $langs;
1908
1909 $sellByLabel = $langs->trans('SellByDate');
1910 $eatByLabel = $langs->trans('EatByDate');
1911 return array(
1912 self::SELL_OR_EAT_BY_MANDATORY_ID_NONE => $langs->trans('BatchSellOrEatByMandatoryNone'),
1913 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY => $sellByLabel,
1914 self::SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY => $eatByLabel,
1915 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT => $langs->trans('BatchSellOrEatByMandatoryAll', $sellByLabel, $eatByLabel),
1916 );
1917 }
1918
1925 {
1926 $sellOrEatByMandatoryLabel = '';
1927
1928 $sellOrEatByMandatoryList = self::getSellOrEatByMandatoryList();
1929 if (isset($sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory])) {
1930 $sellOrEatByMandatoryLabel = $sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory];
1931 }
1932
1933 return $sellOrEatByMandatoryLabel;
1934 }
1935
1942 public function setMultiLangs($user)
1943 {
1944 global $langs;
1945
1946 $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1947 $current_lang = $langs->getDefaultLang();
1948
1949 foreach ($langs_available as $key => $value) {
1950 if ($key == $current_lang) {
1951 $sql = "SELECT rowid";
1952 $sql .= " FROM ".$this->db->prefix()."product_lang";
1953 $sql .= " WHERE fk_product = ".((int) $this->id);
1954 $sql .= " AND lang = '".$this->db->escape($key)."'";
1955
1956 $result = $this->db->query($sql);
1957
1958 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1959 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1960 $sql2 .= " SET ";
1961 $sql2 .= " label='".$this->db->escape($this->label)."',";
1962 $sql2 .= " description='".$this->db->escape($this->description)."'";
1963 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1964 $sql2 .= ", note='".$this->db->escape($this->other)."'";
1965 }
1966 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1967 } else {
1968 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1969 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1970 $sql2 .= ", note";
1971 }
1972 $sql2 .= ")";
1973 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
1974 $sql2 .= " '".$this->db->escape($this->description)."'";
1975 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1976 $sql2 .= ", '".$this->db->escape($this->other)."'";
1977 }
1978 $sql2 .= ")";
1979 }
1980 dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
1981 if (!$this->db->query($sql2)) {
1982 $this->error = $this->db->lasterror();
1983 return -1;
1984 }
1985 } elseif (isset($this->multilangs[$key])) {
1986 if (empty($this->multilangs[$key]["label"])) {
1987 $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
1988 return -1;
1989 }
1990
1991 $sql = "SELECT rowid";
1992 $sql .= " FROM ".$this->db->prefix()."product_lang";
1993 $sql .= " WHERE fk_product = ".((int) $this->id);
1994 $sql .= " AND lang = '".$this->db->escape($key)."'";
1995
1996 $result = $this->db->query($sql);
1997
1998 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1999 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
2000 $sql2 .= " SET ";
2001 $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
2002 $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
2003 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2004 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2005 $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
2006 }
2007 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
2008 } else {
2009 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
2010 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2011 $sql2 .= ", note";
2012 }
2013 $sql2 .= ")";
2014 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
2015 $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
2016 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2017 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2018 $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
2019 }
2020 $sql2 .= ")";
2021 }
2022
2023 // We do not save if main fields are empty
2024 if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
2025 if (!$this->db->query($sql2)) {
2026 $this->error = $this->db->lasterror();
2027 return -1;
2028 }
2029 }
2030 } else {
2031 // language is not current language and we didn't provide a multilang description for this language
2032 }
2033 }
2034
2035 // Call trigger
2036 $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
2037 if ($result < 0) {
2038 $this->error = $this->db->lasterror();
2039 return -1;
2040 }
2041 // End call triggers
2042
2043 return 1;
2044 }
2045
2054 public function delMultiLangs($langtodelete, $user)
2055 {
2056 $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
2057 $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
2058
2059 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
2060 $result = $this->db->query($sql);
2061 if ($result) {
2062 // Call trigger
2063 $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
2064 if ($result < 0) {
2065 $this->error = $this->db->lasterror();
2066 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2067 return -1;
2068 }
2069 // End call triggers
2070 return 1;
2071 } else {
2072 $this->error = $this->db->lasterror();
2073 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2074 return -1;
2075 }
2076 }
2077
2086 public function setAccountancyCode($type, $value)
2087 {
2088 global $user;
2089
2090 $error = 0;
2091
2092 $this->db->begin();
2093
2094 if ($type == 'buy') {
2095 $field = 'accountancy_code_buy';
2096 } elseif ($type == 'buy_intra') {
2097 $field = 'accountancy_code_buy_intra';
2098 } elseif ($type == 'buy_export') {
2099 $field = 'accountancy_code_buy_export';
2100 } elseif ($type == 'sell') {
2101 $field = 'accountancy_code_sell';
2102 } elseif ($type == 'sell_intra') {
2103 $field = 'accountancy_code_sell_intra';
2104 } elseif ($type == 'sell_export') {
2105 $field = 'accountancy_code_sell_export';
2106 } else {
2107 return -1;
2108 }
2109
2110 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
2111 $sql .= "$field = '".$this->db->escape($value)."'";
2112 $sql .= " WHERE rowid = ".((int) $this->id);
2113
2114 dol_syslog(__METHOD__, LOG_DEBUG);
2115 $resql = $this->db->query($sql);
2116
2117 if ($resql) {
2118 // Call trigger
2119 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
2120 if ($result < 0) {
2121 $error++;
2122 }
2123 // End call triggers
2124
2125 if ($error) {
2126 $this->db->rollback();
2127 return -1;
2128 }
2129
2130 $this->$field = $value;
2131
2132 $this->db->commit();
2133 return 1;
2134 } else {
2135 $this->error = $this->db->lasterror();
2136 $this->db->rollback();
2137 return -1;
2138 }
2139 }
2140
2146 public function getMultiLangs()
2147 {
2148 global $langs;
2149
2150 $current_lang = $langs->getDefaultLang();
2151
2152 $sql = "SELECT lang, label, description, note as other";
2153 $sql .= " FROM ".$this->db->prefix()."product_lang";
2154 $sql .= " WHERE fk_product = ".((int) $this->id);
2155
2156 $result = $this->db->query($sql);
2157 if ($result) {
2158 while ($obj = $this->db->fetch_object($result)) {
2159 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
2160 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
2161 $this->label = $obj->label;
2162 $this->description = $obj->description;
2163 $this->other = $obj->other;
2164 }
2165 $this->multilangs[(string) $obj->lang]["label"] = $obj->label;
2166 $this->multilangs[(string) $obj->lang]["description"] = $obj->description;
2167 $this->multilangs[(string) $obj->lang]["other"] = $obj->other;
2168 }
2169 return 1;
2170 } else {
2171 $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
2172 return -1;
2173 }
2174 }
2175
2182 private function getArrayForPriceCompare($level = 0)
2183 {
2184 $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
2185
2186 foreach ($testExit as $field) {
2187 if (!isset($this->$field)) {
2188 return array();
2189 }
2190 $tmparray = $this->$field;
2191 if (!isset($tmparray[$level])) {
2192 return array();
2193 }
2194 }
2195
2196 $lastPrice = array(
2197 'level' => $level ? $level : 1,
2198 'multiprices' => (float) $this->multiprices[$level],
2199 'multiprices_ttc' => (float) $this->multiprices_ttc[$level],
2200 'multiprices_base_type' => $this->multiprices_base_type[$level],
2201 'multiprices_min' => (float) $this->multiprices_min[$level],
2202 'multiprices_min_ttc' => (float) $this->multiprices_min_ttc[$level],
2203 'multiprices_tva_tx' => (float) $this->multiprices_tva_tx[$level],
2204 'multiprices_recuperableonly' => (float) $this->multiprices_recuperableonly[$level],
2205 );
2206
2207 return $lastPrice;
2208 }
2209
2210
2211 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2219 private function _log_price($user, $level = 0)
2220 {
2221 // phpcs:enable
2222 global $conf;
2223
2224 $now = dol_now();
2225
2226 // Clean parameters
2227 if (empty($this->price_by_qty)) {
2228 $this->price_by_qty = 0;
2229 }
2230
2231 // Add new price
2232 $sql = "INSERT INTO ".$this->db->prefix()."product_price(price_level,date_price, fk_product, fk_user_author, price_label, price, price_ttc, price_base_type,tosell, tva_tx, default_vat_code, recuperableonly,";
2233 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
2234 $sql .= " VALUES(".($level ? ((int) $level) : 1).", '".$this->db->idate($now)."', ".((int) $this->id).", ".((int) $user->id).", ".(empty($this->price_label) ? "null" : "'".$this->db->escape($this->price_label)."'").", ".((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).",";
2235 $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');
2236 $sql .= ")";
2237
2238 dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
2239 $resql = $this->db->query($sql);
2240 if (!$resql) {
2241 $this->error = $this->db->lasterror();
2242 dol_print_error($this->db);
2243 return -1;
2244 } else {
2245 return 1;
2246 }
2247 }
2248
2249
2250 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2258 public function log_price_delete($user, $rowid)
2259 {
2260 // phpcs:enable
2261 $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
2262 $sql .= " WHERE fk_product_price = ".((int) $rowid);
2263 $resql = $this->db->query($sql);
2264
2265 $sql = "DELETE FROM ".$this->db->prefix()."product_price";
2266 $sql .= " WHERE rowid=".((int) $rowid);
2267 $resql = $this->db->query($sql);
2268 if ($resql) {
2269 return 1;
2270 } else {
2271 $this->error = $this->db->lasterror();
2272 return -1;
2273 }
2274 }
2275
2276
2286 public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
2287 {
2288 global $hookmanager, $action;
2289
2290 // Call hook if any
2291 if (is_object($hookmanager)) {
2292 $parameters = array('thirdparty_seller' => $thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
2293 // Note that $action and $object may have been modified by some hooks
2294 $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
2295 if ($reshook > 0) {
2296 return $hookmanager->resArray;
2297 }
2298 }
2299
2300 // Update if prices fields are defined
2301 $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
2302 $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
2303 if (empty($tva_tx)) {
2304 $tva_npr = 0;
2305 }
2306
2307 $pu_ht = $this->price;
2308 $pu_ttc = $this->price_ttc;
2309 $price_min = $this->price_min;
2310 $price_base_type = $this->price_base_type;
2311
2312 // if price by customer / level
2313 if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
2314 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2315
2316 $prodcustprice = new ProductCustomerPrice($this->db);
2317
2318 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2319
2320 // If a price per customer exist
2321 $pricebycustomerexist = false;
2322 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2323 if ($result) {
2324 if (count($prodcustprice->lines) > 0) {
2325 $pricebycustomerexist = true;
2326 $pu_ht = price($prodcustprice->lines[0]->price);
2327 $price_min = price($prodcustprice->lines[0]->price_min);
2328 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2329 $price_base_type = $prodcustprice->lines[0]->price_base_type;
2330 $tva_tx = $prodcustprice->lines[0]->tva_tx;
2331 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2332 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2333 }
2334 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2335 if (empty($tva_tx)) {
2336 $tva_npr = 0;
2337 }
2338 }
2339 }
2340
2341 if (!$pricebycustomerexist && !empty($thirdparty_buyer->price_level)) {
2342 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2343 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2344 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2345 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2346 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) {
2347 // using this option is a bug. kept for backward compatibility
2348 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2349 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2350 }
2351 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2352 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2353 }
2354 if (empty($tva_tx)) {
2355 $tva_npr = 0;
2356 }
2357 }
2358 }
2359 } elseif (getDolGlobalString('PRODUIT_MULTIPRICES') && !empty($thirdparty_buyer->price_level)) { // // If price per segment
2360 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2361 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2362 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2363 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2364 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
2365 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2366 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2367 }
2368 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2369 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2370 }
2371 if (empty($tva_tx)) {
2372 $tva_npr = 0;
2373 }
2374 }
2375 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
2376 // If price per customer
2377 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2378
2379 $prodcustprice = new ProductCustomerPrice($this->db);
2380
2381 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2382
2383 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2384 if ($result) {
2385 if (count($prodcustprice->lines) > 0) {
2386 $pu_ht = price($prodcustprice->lines[0]->price);
2387 $price_min = price($prodcustprice->lines[0]->price_min);
2388 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2389 $price_base_type = $prodcustprice->lines[0]->price_base_type;
2390 $tva_tx = $prodcustprice->lines[0]->tva_tx;
2391 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2392 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2393 }
2394 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2395 if (empty($tva_tx)) {
2396 $tva_npr = 0;
2397 }
2398 }
2399 }
2400 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
2401 // If price per quantity
2402 if ($this->prices_by_qty[0]) {
2403 // yes, this product has some prices per quantity
2404 // Search price into product_price_by_qty from $this->id
2405 foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
2406 if ($priceforthequantityarray['rowid'] != $pqp) {
2407 continue;
2408 }
2409 // We found the price
2410 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2411 $pu_ht = $priceforthequantityarray['unitprice'];
2412 } else {
2413 $pu_ttc = $priceforthequantityarray['unitprice'];
2414 }
2415 break;
2416 }
2417 }
2418 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2419 // If price per quantity and customer
2420 if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
2421 // yes, this product has some prices per quantity
2422 // Search price into product_price_by_qty from $this->id
2423 foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
2424 if ($priceforthequantityarray['rowid'] != $pqp) {
2425 continue;
2426 }
2427 // We found the price
2428 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2429 $pu_ht = $priceforthequantityarray['unitprice'];
2430 } else {
2431 $pu_ttc = $priceforthequantityarray['unitprice'];
2432 }
2433 break;
2434 }
2435 }
2436 }
2437
2438 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);
2439 }
2440
2441 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2455 public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2456 {
2457 // phpcs:enable
2458 global $action, $hookmanager;
2459
2460 // Call hook if any
2461 if (is_object($hookmanager)) {
2462 $parameters = array(
2463 'prodfournprice' => $prodfournprice,
2464 'qty' => $qty,
2465 'product_id' => $product_id,
2466 'fourn_ref' => $fourn_ref,
2467 'fk_soc' => $fk_soc,
2468 );
2469 // Note that $action and $object may have been modified by some hooks
2470 $reshook = $hookmanager->executeHooks('getBuyPrice', $parameters, $this, $action);
2471 if ($reshook > 0) {
2472 return $hookmanager->resArray;
2473 }
2474 }
2475
2476 $result = 0;
2477
2478 // 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)
2479 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2480 $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,";
2481 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2482 $sql .= " pfp.packaging";
2483 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2484 $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2485 if ($qty > 0) {
2486 $sql .= " AND pfp.quantity <= ".((float) $qty);
2487 }
2488 $sql .= " ORDER BY pfp.quantity DESC";
2489
2490 dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
2491 $resql = $this->db->query($sql);
2492 if ($resql) {
2493 $obj = $this->db->fetch_object($resql);
2494 if ($obj && $obj->quantity > 0) { // If we found a supplier prices from the id of supplier price
2495 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2496 $prod_supplier = new ProductFournisseur($this->db);
2497 $prod_supplier->product_fourn_price_id = $obj->rowid;
2498 $prod_supplier->id = $obj->fk_product;
2499 $prod_supplier->fourn_qty = $obj->quantity;
2500 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2501 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2502
2503 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2504 $priceparser = new PriceParser($this->db);
2505 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2506 if ($price_result >= 0) {
2507 $obj->price = $price_result;
2508 }
2509 }
2510 $this->product_fourn_price_id = $obj->rowid;
2511 $this->buyprice = $obj->price; // deprecated
2512 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2513 $this->fourn_price_base_type = 'HT'; // Price base type
2514 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2515 $this->ref_fourn = $obj->ref_supplier; // deprecated
2516 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2517 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2518 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2519 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2520 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2521 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2522 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2523 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2524 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2525 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2526 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2527 $this->packaging = $obj->packaging;
2528 }
2529 $result = $obj->fk_product;
2530 return $result;
2531 } else { // If not found
2532 // 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.
2533 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2534 $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,";
2535 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2536 $sql .= " pfp.packaging";
2537 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2538 $sql .= " WHERE 1 = 1";
2539 if ($product_id > 0) {
2540 $sql .= " AND pfp.fk_product = ".((int) $product_id);
2541 }
2542 if ($fourn_ref != 'none') {
2543 $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2544 }
2545 if ($fk_soc > 0) {
2546 $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2547 }
2548 if ($qty > 0) {
2549 $sql .= " AND pfp.quantity <= ".((float) $qty);
2550 }
2551 $sql .= " ORDER BY pfp.quantity DESC";
2552 $sql .= " LIMIT 1";
2553
2554 dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2555 $resql = $this->db->query($sql);
2556 if ($resql) {
2557 $obj = $this->db->fetch_object($resql);
2558 if ($obj && $obj->quantity > 0) { // If found
2559 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2560 $prod_supplier = new ProductFournisseur($this->db);
2561 $prod_supplier->product_fourn_price_id = $obj->rowid;
2562 $prod_supplier->id = $obj->fk_product;
2563 $prod_supplier->fourn_qty = $obj->quantity;
2564 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2565 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2566
2567 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2568 $priceparser = new PriceParser($this->db);
2569 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2570 if ($result >= 0) {
2571 $obj->price = $price_result;
2572 }
2573 }
2574 $this->product_fourn_price_id = $obj->rowid;
2575 $this->buyprice = $obj->price; // deprecated
2576 $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2577 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2578 $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2579 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2580 $this->ref_fourn = $obj->ref_supplier; // deprecated
2581 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2582 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2583 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2584 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2585 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2586 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2587 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2588 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2589 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2590 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2591 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2592 $this->packaging = $obj->packaging;
2593 }
2594 $result = $obj->fk_product;
2595 return $result;
2596 } else {
2597 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é.
2598 }
2599 } else {
2600 $this->error = $this->db->lasterror();
2601 return -3;
2602 }
2603 }
2604 } else {
2605 $this->error = $this->db->lasterror();
2606 return -2;
2607 }
2608 }
2609
2610
2629 public function updatePrice($newprice, $newpricebase, $user, $newvat = null, $newminprice = 0, $level = 0, $newnpr = 0, $newpbq = 0, $ignore_autogen = 0, $localtaxes_array = array(), $newdefaultvatcode = '', $price_label = '', $notrigger = 0)
2630 {
2631 $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2632
2633 $id = $this->id;
2634
2635 dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2636
2637 // Clean parameters
2638 if (empty($this->tva_tx)) {
2639 $this->tva_tx = 0.0;
2640 }
2641 if (empty($newnpr)) {
2642 $newnpr = 0.0;
2643 }
2644 if (empty($newminprice)) {
2645 $newminprice = 0.0;
2646 }
2647
2648 // Check parameters
2649 if ($newvat === null || $newvat == '') { // Maintain '' for backwards compatibility
2650 $newvat = (float) $this->tva_tx;
2651 }
2652
2653 $localtaxtype1 = '';
2654 $localtaxtype2 = '';
2655
2656 // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2657 // Price will be modified ONLY when the first one is the one that is being modified
2658 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2659 return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2660 }
2661
2662 if (!empty($newminprice) && ($newminprice > $newprice)) {
2663 $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2664 return -1;
2665 }
2666
2667 if ($newprice === 0 || $newprice !== '') {
2668 if ($newpricebase == 'TTC') {
2669 $price_ttc = (float) price2num($newprice, 'MU');
2670 $price = (float) price2num($newprice) / (1 + ((float) $newvat / 100));
2671 $price = (float) price2num($price, 'MU');
2672
2673 if ((string) $newminprice != '0') {
2674 $price_min_ttc = (float) price2num($newminprice, 'MU');
2675 $price_min = (float) price2num($newminprice) / (1 + ($newvat / 100));
2676 $price_min = (float) price2num($price_min, 'MU');
2677 } else {
2678 $price_min = 0.0;
2679 $price_min_ttc = 0.0;
2680 }
2681 } else {
2682 $price = (float) price2num($newprice, 'MU');
2683 $price_ttc = ($newnpr != 1) ? (float) price2num($newprice) * (1 + ($newvat / 100)) : $price;
2684 $price_ttc = (float) price2num($price_ttc, 'MU');
2685
2686 if ((string) $newminprice != '0') {
2687 $price_min = (float) price2num($newminprice, 'MU');
2688 $price_min_ttc = (float) price2num($newminprice) * (1 + ($newvat / 100));
2689 $price_min_ttc = (float) price2num($price_min_ttc, 'MU');
2690 //print 'X'.$newminprice.'-'.$price_min;
2691 } else {
2692 $price_min = 0.0;
2693 $price_min_ttc = 0.0;
2694 }
2695 }
2696 //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2697 if (count($localtaxes_array) > 0) {
2698 $localtaxtype1 = $localtaxes_array['0'];
2699 $localtax1 = $localtaxes_array['1'];
2700 $localtaxtype2 = $localtaxes_array['2'];
2701 $localtax2 = $localtaxes_array['3'];
2702 } else {
2703 // if array empty, we try to use the vat code
2704 if (!empty($newdefaultvatcode)) {
2705 global $mysoc;
2706 // Get record from code
2707 $sql = "SELECT t.rowid, t.code, t.recuperableonly as tva_npr, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2708 $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2709 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2710 $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2711 $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2712 $resql = $this->db->query($sql);
2713 if ($resql) {
2714 $obj = $this->db->fetch_object($resql);
2715 if ($obj) {
2716 $npr = $obj->tva_npr;
2717 $localtax1 = $obj->localtax1;
2718 $localtax2 = $obj->localtax2;
2719 $localtaxtype1 = $obj->localtax1_type;
2720 $localtaxtype2 = $obj->localtax2_type;
2721 }
2722 }
2723 } else {
2724 // old method. deprecated because we can't retrieve type
2725 $localtaxtype1 = '0';
2726 $localtax1 = get_localtax($newvat, 1);
2727 $localtaxtype2 = '0';
2728 $localtax2 = get_localtax($newvat, 2);
2729 }
2730 }
2731 if (empty($localtax1)) {
2732 $localtax1 = 0; // If = '' then = 0
2733 }
2734 if (empty($localtax2)) {
2735 $localtax2 = 0; // If = '' then = 0
2736 }
2737
2738 $this->db->begin();
2739
2740 // Ne pas mettre de quote sur les numeriques decimaux.
2741 // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2742 $sql = "UPDATE ".$this->db->prefix()."product SET";
2743 $sql .= " price_base_type = '".$this->db->escape($newpricebase)."',";
2744 $sql .= " price = ".(float) $price.",";
2745 $sql .= " price_ttc = ".(float) $price_ttc.",";
2746 $sql .= " price_min = ".(float) $price_min.",";
2747 $sql .= " price_min_ttc = ".(float) $price_min_ttc.",";
2748 $sql .= " localtax1_tx = ".($localtax1 >= 0 ? (float) $localtax1 : 'NULL').",";
2749 $sql .= " localtax2_tx = ".($localtax2 >= 0 ? (float) $localtax2 : 'NULL').",";
2750 $sql .= " localtax1_type = ".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2751 $sql .= " localtax2_type = ".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2752 $sql .= " default_vat_code = ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2753 $sql .= " price_label = ".(!empty($price_label) ? "'".$this->db->escape($price_label)."'" : "null").",";
2754 $sql .= " tva_tx = ".(float) price2num($newvat).",";
2755 $sql .= " recuperableonly = '".$this->db->escape($newnpr)."'";
2756 $sql .= " WHERE rowid = ".((int) $id);
2757
2758 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2759 $resql = $this->db->query($sql);
2760 if ($resql) {
2761 $this->multiprices[$level] = $price;
2762 $this->multiprices_ttc[$level] = $price_ttc;
2763 $this->multiprices_min[$level] = $price_min;
2764 $this->multiprices_min_ttc[$level] = $price_min_ttc;
2765 $this->multiprices_base_type[$level] = $newpricebase;
2766 $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2767 $this->multiprices_tva_tx[$level] = $newvat;
2768 $this->multiprices_recuperableonly[$level] = $newnpr;
2769
2770 $this->price = $price;
2771 $this->price_label = $price_label;
2772 $this->price_ttc = $price_ttc;
2773 $this->price_min = $price_min;
2774 $this->price_min_ttc = $price_min_ttc;
2775 $this->price_base_type = $newpricebase;
2776 $this->default_vat_code = $newdefaultvatcode;
2777 $this->tva_tx = $newvat;
2778 $this->tva_npr = $newnpr;
2779
2780 //Local taxes
2781 $this->localtax1_tx = $localtax1;
2782 $this->localtax2_tx = $localtax2;
2783 $this->localtax1_type = $localtaxtype1;
2784 $this->localtax2_type = $localtaxtype2;
2785
2786 // Price by quantity
2787 $this->price_by_qty = $newpbq;
2788
2789 // check if price have really change before log
2790 $newPriceData = $this->getArrayForPriceCompare($level);
2791 if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || !getDolGlobalString('PRODUIT_MULTIPRICES')) {
2792 $this->_log_price($user, $level); // Save price for level into table product_price
2793 }
2794
2795 $this->level = $level; // Store level of price edited for trigger
2796
2797 // Call trigger
2798 if (!$notrigger) {
2799 $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2800 if ($result < 0) {
2801 $this->db->rollback();
2802 return -1;
2803 }
2804 }
2805 // End call triggers
2806
2807 $this->db->commit();
2808 } else {
2809 $this->db->rollback();
2810 $this->error = $this->db->lasterror();
2811 return -1;
2812 }
2813 }
2814
2815 return 1;
2816 }
2817
2825 public function setPriceExpression($expression_id)
2826 {
2827 global $user;
2828
2829 $this->fk_price_expression = $expression_id;
2830
2831 return $this->update($this->id, $user);
2832 }
2833
2846 public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2847 {
2848 include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2849
2850 global $conf;
2851
2852 dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2853
2854 // Check parameters
2855 if (!$id && !$ref && !$ref_ext && !$barcode) {
2856 $this->error = 'ErrorWrongParameters';
2857 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2858 return -1;
2859 }
2860
2861 $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,";
2862 $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,";
2863 $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,";
2864 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.last_main_doc,";
2865 $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,";
2866 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2867 $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,";
2868 } else {
2869 $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,";
2870 }
2871
2872 // For MultiCompany
2873 // PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
2874 $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
2875 $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.
2876 $visibleWarehousesEntities = $conf->entity;
2877 if (getDolGlobalString('MULTICOMPANY_PRODUCT_SHARING_ENABLED')) {
2878 if (getDolGlobalString('MULTICOMPANY_PMP_PER_ENTITY_ENABLED')) {
2879 $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
2880 if ($this->db->num_rows($checkPMPPerEntity) > 0) {
2881 $separatedEntityPMP = true;
2882 }
2883 }
2884 global $mc;
2885 $separatedStock = true;
2886 if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
2887 $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
2888 }
2889 }
2890 if ($separatedEntityPMP) {
2891 $sql .= " ppe.pmp,";
2892 } else {
2893 $sql .= " p.pmp,";
2894 }
2895 $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.sell_or_eat_by_mandatory, p.batch_mask, p.fk_unit,";
2896 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf,";
2897 $sql .= " p.price_label,";
2898 if ($separatedStock) {
2899 $sql .= " SUM(sp.reel) as stock";
2900 } else {
2901 $sql .= " p.stock";
2902 }
2903 $sql .= " FROM ".$this->db->prefix()."product as p";
2904 if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') || $separatedEntityPMP) {
2905 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
2906 }
2907 if ($separatedStock) {
2908 $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)."))";
2909 }
2910
2911 if ($id) {
2912 $sql .= " WHERE p.rowid = ".((int) $id);
2913 } else {
2914 $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
2915 if ($ref) {
2916 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
2917 } elseif ($ref_ext) {
2918 $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
2919 } elseif ($barcode) {
2920 $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
2921 }
2922 }
2923 if ($separatedStock) {
2924 $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,";
2925 $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,";
2926 $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,";
2927 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2928 $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,";
2929 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2930 $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,";
2931 } else {
2932 $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,";
2933 }
2934 if ($separatedEntityPMP) {
2935 $sql .= " ppe.pmp,";
2936 } else {
2937 $sql .= " p.pmp,";
2938 }
2939 $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.sell_or_eat_by_mandatory, p.batch_mask, p.fk_unit,";
2940 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf";
2941 $sql .= " ,p.price_label";
2942 if (!$separatedStock) {
2943 $sql .= ", p.stock";
2944 }
2945 }
2946
2947 $resql = $this->db->query($sql);
2948 if ($resql) {
2949 unset($this->oldcopy);
2950
2951 if ($this->db->num_rows($resql) > 0) {
2952 $obj = $this->db->fetch_object($resql);
2953
2954 $this->id = $obj->rowid;
2955 $this->ref = $obj->ref;
2956 $this->ref_ext = $obj->ref_ext;
2957 $this->label = $obj->label;
2958 $this->description = $obj->description;
2959 $this->url = $obj->url;
2960 $this->note_public = $obj->note_public;
2961 $this->note_private = $obj->note_private;
2962 $this->note = $obj->note_private; // deprecated
2963
2964 $this->type = $obj->fk_product_type;
2965 $this->price_label = $obj->price_label;
2966 $this->status = $obj->tosell;
2967 $this->status_buy = $obj->tobuy;
2968 $this->status_batch = $obj->tobatch;
2969 $this->sell_or_eat_by_mandatory = $obj->sell_or_eat_by_mandatory;
2970 $this->batch_mask = $obj->batch_mask;
2971
2972 $this->customcode = $obj->customcode;
2973 $this->country_id = $obj->fk_country;
2974 $this->country_code = getCountry($this->country_id, '2', $this->db);
2975 $this->state_id = $obj->fk_state;
2976 $this->lifetime = $obj->lifetime;
2977 $this->qc_frequency = $obj->qc_frequency;
2978 $this->price = $obj->price;
2979 $this->price_ttc = $obj->price_ttc;
2980 $this->price_min = $obj->price_min;
2981 $this->price_min_ttc = $obj->price_min_ttc;
2982 $this->price_base_type = $obj->price_base_type;
2983 $this->cost_price = $obj->cost_price;
2984 $this->default_vat_code = $obj->default_vat_code;
2985 $this->tva_tx = $obj->tva_tx;
2987 $this->tva_npr = $obj->tva_npr;
2989 $this->localtax1_tx = $obj->localtax1_tx;
2990 $this->localtax2_tx = $obj->localtax2_tx;
2991 $this->localtax1_type = $obj->localtax1_type;
2992 $this->localtax2_type = $obj->localtax2_type;
2993
2994 $this->finished = $obj->finished;
2995 $this->fk_default_bom = $obj->fk_default_bom;
2996
2997 $this->duration = $obj->duration;
2998 $matches = [];
2999 preg_match('/(\d+)(\w+)/', $obj->duration, $matches);
3000 $this->duration_value = !empty($matches[1]) ? (int) $matches[1] : 0;
3001 $this->duration_unit = !empty($matches[2]) ? (string) $matches[2] : null;
3002 $this->canvas = $obj->canvas;
3003 $this->net_measure = $obj->net_measure;
3004 $this->net_measure_units = $obj->net_measure_units;
3005 $this->weight = $obj->weight;
3006 $this->weight_units = $obj->weight_units;
3007 $this->length = $obj->length;
3008 $this->length_units = $obj->length_units;
3009 $this->width = $obj->width;
3010 $this->width_units = $obj->width_units;
3011 $this->height = $obj->height;
3012 $this->height_units = $obj->height_units;
3013
3014 $this->surface = $obj->surface;
3015 $this->surface_units = $obj->surface_units;
3016 $this->volume = $obj->volume;
3017 $this->volume_units = $obj->volume_units;
3018 $this->barcode = $obj->barcode;
3019 $this->barcode_type = $obj->fk_barcode_type;
3020
3021 $this->accountancy_code_buy = $obj->accountancy_code_buy;
3022 $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
3023 $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
3024 $this->accountancy_code_sell = $obj->accountancy_code_sell;
3025 $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
3026 $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
3027
3028 $this->fk_default_warehouse = $obj->fk_default_warehouse;
3029 $this->fk_default_workstation = $obj->fk_default_workstation;
3030 $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
3031 $this->desiredstock = $obj->desiredstock;
3032 $this->stock_reel = $obj->stock;
3033 $this->pmp = $obj->pmp;
3034
3035 $this->date_creation = $obj->datec;
3036 $this->date_modification = $obj->tms;
3037 $this->import_key = $obj->import_key;
3038 $this->entity = $obj->entity;
3039
3040 $this->ref_ext = $obj->ref_ext;
3041 $this->fk_price_expression = $obj->fk_price_expression;
3042 $this->fk_unit = $obj->fk_unit;
3043 $this->price_autogen = $obj->price_autogen;
3044 $this->model_pdf = $obj->model_pdf;
3045 $this->last_main_doc = $obj->last_main_doc;
3046
3047 $this->mandatory_period = $obj->mandatory_period;
3048
3049 $this->db->free($resql);
3050
3051 // fetch optionals attributes and labels
3052 $this->fetch_optionals();
3053
3054 // Multilangs
3055 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
3056 $this->getMultiLangs();
3057 }
3058
3059 // Load multiprices array
3060 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($ignore_price_load)) { // prices per segment
3061 $produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
3062 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
3063 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3064 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
3065 $sql .= " ,price_label";
3066 $sql .= " FROM ".$this->db->prefix()."product_price";
3067 $sql .= " WHERE entity IN (".getEntity('productprice').")";
3068 $sql .= " AND price_level=".((int) $i);
3069 $sql .= " AND fk_product = ".((int) $this->id);
3070 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
3071 $sql .= " LIMIT 1"; // Only the first one
3072 $resql = $this->db->query($sql);
3073 if ($resql) {
3074 $result = $this->db->fetch_array($resql);
3075
3076 $this->multiprices[$i] = $result ? $result["price"] : null;
3077 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
3078 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
3079 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
3080 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
3081 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
3082 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].($result ? ' ('.$result['default_vat_code'].')' : '') : null;
3083 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
3084
3085 // Price by quantity
3086 /*
3087 $this->prices_by_qty[$i]=$result["price_by_qty"];
3088 $this->prices_by_qty_id[$i]=$result["rowid"];
3089 // Récuperation de la liste des prix selon qty si flag positionné
3090 if ($this->prices_by_qty[$i] == 1)
3091 {
3092 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
3093 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
3094 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
3095 $sql.= " ORDER BY quantity ASC";
3096
3097 $resql = $this->db->query($sql);
3098 if ($resql)
3099 {
3100 $resultat=array();
3101 $ii=0;
3102 while ($result= $this->db->fetch_array($resql)) {
3103 $resultat[$ii]=array();
3104 $resultat[$ii]["rowid"]=$result["rowid"];
3105 $resultat[$ii]["price"]= $result["price"];
3106 $resultat[$ii]["unitprice"]= $result["unitprice"];
3107 $resultat[$ii]["quantity"]= $result["quantity"];
3108 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
3109 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
3110 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
3111 $ii++;
3112 }
3113 $this->prices_by_qty_list[$i]=$resultat;
3114 }
3115 else
3116 {
3117 dol_print_error($this->db);
3118 return -1;
3119 }
3120 }*/
3121 } else {
3122 $this->error = $this->db->lasterror;
3123 return -1;
3124 }
3125 }
3126 } elseif ((getDolGlobalString('PRODUIT_CUSTOMER_PRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($ignore_price_load)) { // prices per customers
3127 // Nothing loaded by default. List may be very long.
3128 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
3129 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3130 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
3131 $sql .= " FROM ".$this->db->prefix()."product_price";
3132 $sql .= " WHERE fk_product = ".((int) $this->id);
3133 $sql .= " ORDER BY date_price DESC, rowid DESC";
3134 $sql .= " LIMIT 1";
3135
3136 $resql = $this->db->query($sql);
3137 if ($resql) {
3138 $result = $this->db->fetch_array($resql);
3139
3140 if ($result) {
3141 // Price by quantity
3142 $this->prices_by_qty[0] = $result["price_by_qty"];
3143 $this->prices_by_qty_id[0] = $result["rowid"];
3144 // Récuperation de la liste des prix selon qty si flag positionné
3145 if ($this->prices_by_qty[0] == 1) {
3146 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
3147 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
3148 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
3149 $sql .= " ORDER BY quantity ASC";
3150
3151 $resql = $this->db->query($sql);
3152 if ($resql) {
3153 $resultat = array();
3154 $ii = 0;
3155 while ($result = $this->db->fetch_array($resql)) {
3156 $resultat[$ii] = array();
3157 $resultat[$ii]["rowid"] = $result["rowid"];
3158 $resultat[$ii]["price"] = $result["price"];
3159 $resultat[$ii]["unitprice"] = $result["unitprice"];
3160 $resultat[$ii]["quantity"] = $result["quantity"];
3161 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
3162 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
3163 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
3164 $ii++;
3165 }
3166 $this->prices_by_qty_list[0] = $resultat;
3167 } else {
3168 $this->error = $this->db->lasterror;
3169 return -1;
3170 }
3171 }
3172 }
3173 } else {
3174 $this->error = $this->db->lasterror;
3175 return -1;
3176 }
3177 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
3178 $produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
3179 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
3180 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3181 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
3182 $sql .= " FROM ".$this->db->prefix()."product_price";
3183 $sql .= " WHERE entity IN (".getEntity('productprice').")";
3184 $sql .= " AND price_level=".((int) $i);
3185 $sql .= " AND fk_product = ".((int) $this->id);
3186 $sql .= " ORDER BY date_price DESC, rowid DESC";
3187 $sql .= " LIMIT 1";
3188 $resql = $this->db->query($sql);
3189 if (!$resql) {
3190 $this->error = $this->db->lasterror;
3191 return -1;
3192 } elseif ($result = $this->db->fetch_array($resql)) {
3193 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
3194 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
3195 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
3196 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
3197 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
3198 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
3199 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
3200 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
3201
3202 // Price by quantity
3203 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
3204 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
3205 // Récuperation de la liste des prix selon qty si flag positionné
3206 if ($this->prices_by_qty[$i] == 1) {
3207 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
3208 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
3209 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
3210 $sql .= " ORDER BY quantity ASC";
3211
3212 $resql = $this->db->query($sql);
3213 if ($resql) {
3214 $resultat = array();
3215 $ii = 0;
3216 while ($result = $this->db->fetch_array($resql)) {
3217 $resultat[$ii] = array();
3218 $resultat[$ii]["rowid"] = $result["rowid"];
3219 $resultat[$ii]["price"] = $result["price"];
3220 $resultat[$ii]["unitprice"] = $result["unitprice"];
3221 $resultat[$ii]["quantity"] = $result["quantity"];
3222 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
3223 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
3224 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
3225 $ii++;
3226 }
3227 $this->prices_by_qty_list[$i] = $resultat;
3228 } else {
3229 $this->error = $this->db->lasterror;
3230 return -1;
3231 }
3232 }
3233 }
3234 }
3235 }
3236
3237 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
3238 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
3239 $priceparser = new PriceParser($this->db);
3240 $price_result = $priceparser->parseProduct($this);
3241 if ($price_result >= 0) {
3242 $this->price = $price_result;
3243 // Calculate the VAT
3244 $this->price_ttc = (float) price2num($this->price) * (1 + ($this->tva_tx / 100));
3245 $this->price_ttc = (float) price2num($this->price_ttc, 'MU');
3246 }
3247 }
3248
3249 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
3250 // Instead we just init the stock_warehouse array
3251 $this->stock_warehouse = array();
3252
3253 return 1;
3254 } else {
3255 return 0;
3256 }
3257 } else {
3258 $this->error = $this->db->lasterror();
3259 return -1;
3260 }
3261 }
3262
3263 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3270 public function load_stats_mo($socid = 0)
3271 {
3272 // phpcs:enable
3273 global $user, $hookmanager, $action;
3274
3275 $error = 0;
3276
3277 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
3278 $this->stats_mo['customers_'.$role] = 0;
3279 $this->stats_mo['nb_'.$role] = 0;
3280 $this->stats_mo['qty_'.$role] = 0;
3281
3282 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3283 $sql .= " SUM(mp.qty) as qty";
3284 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
3285 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
3286 if (!$user->hasRight('societe', 'client', 'voir')) {
3287 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc=c.fk_soc AND sc.fk_user = ".((int) $user->id);
3288 }
3289 $sql .= " WHERE ";
3290 $sql .= " c.entity IN (".getEntity('mo').")";
3291
3292 $sql .= " AND mp.fk_product = ".((int) $this->id);
3293 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
3294 if ($socid > 0) {
3295 $sql .= " AND c.fk_soc = ".((int) $socid);
3296 }
3297
3298 $result = $this->db->query($sql);
3299 if ($result) {
3300 $obj = $this->db->fetch_object($result);
3301 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
3302 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
3303 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
3304 } else {
3305 $this->error = $this->db->error();
3306 $error++;
3307 }
3308 }
3309
3310 if (!empty($error)) {
3311 return -1;
3312 }
3313
3314 $parameters = array('socid' => $socid);
3315 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3316 if ($reshook > 0) {
3317 $this->stats_mo = $hookmanager->resArray['stats_mo'];
3318 }
3319
3320 return 1;
3321 }
3322
3323 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3330 public function load_stats_bom($socid = 0)
3331 {
3332 // phpcs:enable
3333 global $hookmanager, $action;
3334
3335 $error = 0;
3336
3337 $this->stats_bom['nb_toproduce'] = 0;
3338 $this->stats_bom['nb_toconsume'] = 0;
3339 $this->stats_bom['qty_toproduce'] = 0;
3340 $this->stats_bom['qty_toconsume'] = 0;
3341
3342 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
3343 $sql .= " SUM(b.qty) as qty_toproduce";
3344 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3345 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
3346 $sql .= " WHERE ";
3347 $sql .= " b.entity IN (".getEntity('bom').")";
3348 $sql .= " AND b.fk_product =".((int) $this->id);
3349 $sql .= " GROUP BY b.rowid";
3350
3351 $result = $this->db->query($sql);
3352 if ($result) {
3353 $obj = $this->db->fetch_object($result);
3354 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
3355 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
3356 } else {
3357 $this->error = $this->db->error();
3358 $error++;
3359 }
3360
3361 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
3362 $sql .= " SUM(bl.qty) as qty_toconsume";
3363 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3364 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
3365 $sql .= " WHERE ";
3366 $sql .= " b.entity IN (".getEntity('bom').")";
3367 $sql .= " AND bl.fk_product =".((int) $this->id);
3368
3369 $result = $this->db->query($sql);
3370 if ($result) {
3371 $obj = $this->db->fetch_object($result);
3372 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
3373 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
3374 } else {
3375 $this->error = $this->db->error();
3376 $error++;
3377 }
3378
3379 if (!empty($error)) {
3380 return -1;
3381 }
3382
3383 $parameters = array('socid' => $socid);
3384 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3385 if ($reshook > 0) {
3386 $this->stats_bom = $hookmanager->resArray['stats_bom'];
3387 }
3388
3389 return 1;
3390 }
3391
3392 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3399 public function load_stats_propale($socid = 0)
3400 {
3401 // phpcs:enable
3402 global $user, $hookmanager, $action;
3403
3404 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3405 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3406 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3407 $sql .= ", ".$this->db->prefix()."propal as p";
3408 $sql .= ", ".$this->db->prefix()."societe as s";
3409 if (!$user->hasRight('societe', 'client', 'voir')) {
3410 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3411 }
3412 $sql .= " WHERE p.rowid = pd.fk_propal";
3413 $sql .= " AND p.fk_soc = s.rowid";
3414 $sql .= " AND p.entity IN (".getEntity('propal').")";
3415 $sql .= " AND pd.fk_product = ".((int) $this->id);
3416 if (!$user->hasRight('societe', 'client', 'voir')) {
3417 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3418 }
3419 //$sql.= " AND pr.fk_statut != 0";
3420 if ($socid > 0) {
3421 $sql .= " AND p.fk_soc = ".((int) $socid);
3422 }
3423
3424 $result = $this->db->query($sql);
3425 if ($result) {
3426 $obj = $this->db->fetch_object($result);
3427 $this->stats_propale['customers'] = $obj->nb_customers;
3428 $this->stats_propale['nb'] = $obj->nb;
3429 $this->stats_propale['rows'] = $obj->nb_rows;
3430 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3431
3432 // if it's a virtual product, maybe it is in proposal by extension
3433 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3434 $TFather = $this->getFather();
3435 if (is_array($TFather) && !empty($TFather)) {
3436 foreach ($TFather as &$fatherData) {
3437 $pFather = new Product($this->db);
3438 $pFather->id = $fatherData['id'];
3439 $qtyCoef = $fatherData['qty'];
3440
3441 if ($fatherData['incdec']) {
3442 $pFather->load_stats_propale($socid);
3443
3444 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3445 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3446 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3447 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3448 }
3449 }
3450 }
3451 }
3452
3453 $parameters = array('socid' => $socid);
3454 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3455 if ($reshook > 0) {
3456 $this->stats_propale = $hookmanager->resArray['stats_propale'];
3457 }
3458
3459 return 1;
3460 } else {
3461 $this->error = $this->db->error();
3462 return -1;
3463 }
3464 }
3465
3466
3467 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3474 public function load_stats_proposal_supplier($socid = 0)
3475 {
3476 // phpcs:enable
3477 global $user, $hookmanager, $action;
3478
3479 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3480 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3481 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3482 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3483 $sql .= ", ".$this->db->prefix()."societe as s";
3484 if (!$user->hasRight('societe', 'client', 'voir')) {
3485 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3486 }
3487 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3488 $sql .= " AND p.fk_soc = s.rowid";
3489 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3490 $sql .= " AND pd.fk_product = ".((int) $this->id);
3491 if (!$user->hasRight('societe', 'client', 'voir')) {
3492 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3493 }
3494 //$sql.= " AND pr.fk_statut != 0";
3495 if ($socid > 0) {
3496 $sql .= " AND p.fk_soc = ".((int) $socid);
3497 }
3498
3499 $result = $this->db->query($sql);
3500 if ($result) {
3501 $obj = $this->db->fetch_object($result);
3502 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3503 $this->stats_proposal_supplier['nb'] = $obj->nb;
3504 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3505 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3506
3507 $parameters = array('socid' => $socid);
3508 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3509 if ($reshook > 0) {
3510 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3511 }
3512
3513 return 1;
3514 } else {
3515 $this->error = $this->db->error();
3516 return -1;
3517 }
3518 }
3519
3520
3521 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3530 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3531 {
3532 // phpcs:enable
3533 global $user, $hookmanager, $action;
3534
3535 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3536 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3537 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3538 $sql .= ", ".$this->db->prefix()."commande as c";
3539 $sql .= ", ".$this->db->prefix()."societe as s";
3540 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3541 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3542 }
3543 $sql .= " WHERE c.rowid = cd.fk_commande";
3544 $sql .= " AND c.fk_soc = s.rowid";
3545 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3546 $sql .= " AND cd.fk_product = ".((int) $this->id);
3547 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3548 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3549 }
3550 if ($socid > 0) {
3551 $sql .= " AND c.fk_soc = ".((int) $socid);
3552 }
3553 if ($filtrestatut != '') {
3554 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")";
3555 }
3556
3557 $result = $this->db->query($sql);
3558 if ($result) {
3559 $obj = $this->db->fetch_object($result);
3560 $this->stats_commande['customers'] = $obj->nb_customers;
3561 $this->stats_commande['nb'] = $obj->nb;
3562 $this->stats_commande['rows'] = $obj->nb_rows;
3563 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3564
3565 // if it's a virtual product, maybe it is in order by extension
3566 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3567 $TFather = $this->getFather();
3568 if (is_array($TFather) && !empty($TFather)) {
3569 foreach ($TFather as &$fatherData) {
3570 $pFather = new Product($this->db);
3571 $pFather->id = $fatherData['id'];
3572 $qtyCoef = $fatherData['qty'];
3573
3574 if ($fatherData['incdec']) {
3575 $pFather->load_stats_commande($socid, $filtrestatut);
3576
3577 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3578 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3579 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3580 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3581 }
3582 }
3583 }
3584 }
3585
3586 // If stock decrease is on invoice validation, the theoretical stock continue to
3587 // count the orders to ship in theoretical stock when some are already removed by invoice validation.
3588 if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3589 if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3590 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3591 $adeduire = 0;
3592 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3593 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3594 $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'))";
3595 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3596 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3597
3598 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3599 $resql = $this->db->query($sql);
3600 if ($resql) {
3601 if ($this->db->num_rows($resql) > 0) {
3602 $obj = $this->db->fetch_object($resql);
3603 $adeduire += $obj->count;
3604 }
3605 }
3606
3607 $this->stats_commande['qty'] -= $adeduire;
3608 } else {
3609 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3610 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3611
3612 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3613 $adeduire = 0;
3614 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
3615 $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
3616 $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'))";
3617 $sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
3618 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3619
3620 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3621 $resql = $this->db->query($sql);
3622 if ($resql) {
3623 if ($this->db->num_rows($resql) > 0) {
3624 $obj = $this->db->fetch_object($resql);
3625 $adeduire += $obj->count;
3626 }
3627 } else {
3628 $this->error = $this->db->error();
3629 return -1;
3630 }
3631
3632 $this->stats_commande['qty'] -= $adeduire;
3633 }
3634 }
3635
3636 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3637 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3638 if ($reshook > 0) {
3639 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3640 }
3641 return 1;
3642 } else {
3643 $this->error = $this->db->error();
3644 return -1;
3645 }
3646 }
3647
3648 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3658 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3659 {
3660 // phpcs:enable
3661 global $user, $hookmanager, $action;
3662
3663 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3664 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3665 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3666 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3667 $sql .= ", ".$this->db->prefix()."societe as s";
3668 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3669 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3670 }
3671 $sql .= " WHERE c.rowid = cd.fk_commande";
3672 $sql .= " AND c.fk_soc = s.rowid";
3673 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3674 $sql .= " AND cd.fk_product = ".((int) $this->id);
3675 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3676 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3677 }
3678 if ($socid > 0) {
3679 $sql .= " AND c.fk_soc = ".((int) $socid);
3680 }
3681 if ($filtrestatut != '') {
3682 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3683 }
3684 if (!empty($dateofvirtualstock)) {
3685 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3686 }
3687
3688 $result = $this->db->query($sql);
3689 if ($result) {
3690 $obj = $this->db->fetch_object($result);
3691 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3692 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3693 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3694 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3695
3696 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3697 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3698 if ($reshook > 0) {
3699 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3700 }
3701
3702 return 1;
3703 } else {
3704 $this->error = $this->db->error().' sql='.$sql;
3705 return -1;
3706 }
3707 }
3708
3709 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3719 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3720 {
3721 // phpcs:enable
3722 global $user, $hookmanager, $action;
3723
3724 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3725 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3726 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3727 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3728 $sql .= ", ".$this->db->prefix()."commande as c";
3729 $sql .= ", ".$this->db->prefix()."expedition as e";
3730 $sql .= ", ".$this->db->prefix()."societe as s";
3731 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3732 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3733 }
3734 $sql .= " WHERE e.rowid = ed.fk_expedition";
3735 $sql .= " AND c.rowid = cd.fk_commande";
3736 $sql .= " AND e.fk_soc = s.rowid";
3737 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3738 $sql .= " AND ed.fk_elementdet = cd.rowid";
3739 $sql .= " AND cd.fk_product = ".((int) $this->id);
3740 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3741 $sql .= " AND e.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3742 }
3743 if ($socid > 0) {
3744 $sql .= " AND e.fk_soc = ".((int) $socid);
3745 }
3746 if ($filtrestatut != '') {
3747 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3748 }
3749 if (!empty($filterShipmentStatus)) {
3750 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3751 }
3752
3753 $result = $this->db->query($sql);
3754 if ($result) {
3755 $obj = $this->db->fetch_object($result);
3756 $this->stats_expedition['customers'] = $obj->nb_customers;
3757 $this->stats_expedition['nb'] = $obj->nb;
3758 $this->stats_expedition['rows'] = $obj->nb_rows;
3759 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3760
3761 // if it's a virtual product, maybe it is in sending by extension
3762 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3763 $TFather = $this->getFather();
3764 if (is_array($TFather) && !empty($TFather)) {
3765 foreach ($TFather as &$fatherData) {
3766 $pFather = new Product($this->db);
3767 $pFather->id = $fatherData['id'];
3768 $qtyCoef = $fatherData['qty'];
3769
3770 if ($fatherData['incdec']) {
3771 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3772
3773 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3774 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3775 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3776 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3777 }
3778 }
3779 }
3780 }
3781
3782 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3783 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3784 if ($reshook > 0) {
3785 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3786 }
3787
3788 return 1;
3789 } else {
3790 $this->error = $this->db->error();
3791 return -1;
3792 }
3793 }
3794
3795 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3805 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3806 {
3807 // phpcs:enable
3808 global $user, $hookmanager, $action;
3809
3810 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3811 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3812 $sql .= " FROM ".$this->db->prefix()."receptiondet_batch as fd";
3813 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3814 $sql .= ", ".$this->db->prefix()."societe as s";
3815 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3816 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3817 }
3818 $sql .= " WHERE cf.rowid = fd.fk_element";
3819 $sql .= " AND cf.fk_soc = s.rowid";
3820 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3821 $sql .= " AND fd.fk_product = ".((int) $this->id);
3822 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3823 $sql .= " AND cf.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3824 }
3825 if ($socid > 0) {
3826 $sql .= " AND cf.fk_soc = ".((int) $socid);
3827 }
3828 if ($filtrestatut != '') {
3829 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3830 }
3831 if (!empty($dateofvirtualstock)) {
3832 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3833 }
3834
3835 $result = $this->db->query($sql);
3836 if ($result) {
3837 $obj = $this->db->fetch_object($result);
3838 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3839 $this->stats_reception['nb'] = $obj->nb;
3840 $this->stats_reception['rows'] = $obj->nb_rows;
3841 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3842
3843 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3844 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3845 if ($reshook > 0) {
3846 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3847 }
3848
3849 return 1;
3850 } else {
3851 $this->error = $this->db->error();
3852 return -1;
3853 }
3854 }
3855
3856 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3867 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null, $warehouseid = 0)
3868 {
3869 // phpcs:enable
3870 global $user, $hookmanager, $action;
3871
3872 $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
3873
3874 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3875 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3876 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3877 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3878 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3879 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3880 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
3881 }
3882 $sql .= " WHERE m.rowid = mp.fk_mo";
3883 $sql .= " AND m.entity IN (".getEntity(($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE')) ? 'stock' : 'mrp').")";
3884 $sql .= " AND mp.fk_product = ".((int) $this->id);
3885 $sql .= " AND (mp.disable_stock_change IN (0) OR mp.disable_stock_change IS NULL)";
3886 if (!$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) {
3887 $sql .= " AND m.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3888 }
3889 if ($socid > 0) {
3890 $sql .= " AND m.fk_soc = ".((int) $socid);
3891 }
3892 if ($filtrestatut != '') {
3893 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3894 }
3895 if (!empty($dateofvirtualstock)) {
3896 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3897 }
3898 if (!$serviceStockIsEnabled) {
3899 $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))";
3900 }
3901 if (!empty($warehouseid)) {
3902 $sql .= " AND m.fk_warehouse = ".((int) $warehouseid);
3903 }
3904 $sql .= " GROUP BY role";
3905
3906 if ($warehouseid) {
3907 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3908 } else {
3909 $this->stats_mrptoconsume['customers'] = 0;
3910 $this->stats_mrptoconsume['nb'] = 0;
3911 $this->stats_mrptoconsume['rows'] = 0;
3912 $this->stats_mrptoconsume['qty'] = 0;
3913 $this->stats_mrptoproduce['customers'] = 0;
3914 $this->stats_mrptoproduce['nb'] = 0;
3915 $this->stats_mrptoproduce['rows'] = 0;
3916 $this->stats_mrptoproduce['qty'] = 0;
3917 }
3918
3919 $result = $this->db->query($sql);
3920 if ($result) {
3921 while ($obj = $this->db->fetch_object($result)) {
3922 if ($obj->role == 'toconsume' && empty($warehouseid)) {
3923 $this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3924 $this->stats_mrptoconsume['nb'] += $obj->nb;
3925 $this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3926 $this->stats_mrptoconsume['qty'] += ($obj->qty ? $obj->qty : 0);
3927 }
3928 if ($obj->role == 'consumed' && empty($warehouseid)) {
3929 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3930 //$this->stats_mrptoconsume['nb'] += $obj->nb;
3931 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3932 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? $obj->qty : 0);
3933 }
3934 if ($obj->role == 'toproduce') {
3935 if ($warehouseid) {
3936 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3937 } else {
3938 $this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3939 $this->stats_mrptoproduce['nb'] += $obj->nb;
3940 $this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3941 $this->stats_mrptoproduce['qty'] += ($obj->qty ? $obj->qty : 0);
3942 }
3943 }
3944 if ($obj->role == 'produced') {
3945 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3946 //$this->stats_mrptoproduce['nb'] += $obj->nb;
3947 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3948 if ($warehouseid) {
3949 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3950 } else {
3951 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3952 }
3953 }
3954 }
3955
3956 // Clean data
3957 if ($warehouseid) {
3958 if ($this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] < 0) {
3959 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3960 }
3961 } else {
3962 if ($this->stats_mrptoconsume['qty'] < 0) {
3963 $this->stats_mrptoconsume['qty'] = 0;
3964 }
3965 if ($this->stats_mrptoproduce['qty'] < 0) {
3966 $this->stats_mrptoproduce['qty'] = 0;
3967 }
3968 }
3969
3970 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3971 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
3972 if ($reshook > 0) {
3973 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
3974 }
3975
3976 return 1;
3977 } else {
3978 $this->error = $this->db->error();
3979 return -1;
3980 }
3981 }
3982
3983 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3990 public function load_stats_contrat($socid = 0)
3991 {
3992 // phpcs:enable
3993 global $user, $hookmanager, $action;
3994
3995 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3996 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3997 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
3998 $sql .= ", ".$this->db->prefix()."contrat as c";
3999 $sql .= ", ".$this->db->prefix()."societe as s";
4000 if (!$user->hasRight('societe', 'client', 'voir')) {
4001 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4002 }
4003 $sql .= " WHERE c.rowid = cd.fk_contrat";
4004 $sql .= " AND c.fk_soc = s.rowid";
4005 $sql .= " AND c.entity IN (".getEntity('contract').")";
4006 $sql .= " AND cd.fk_product = ".((int) $this->id);
4007 if (!$user->hasRight('societe', 'client', 'voir')) {
4008 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4009 }
4010 //$sql.= " AND c.statut != 0";
4011 if ($socid > 0) {
4012 $sql .= " AND c.fk_soc = ".((int) $socid);
4013 }
4014
4015 $result = $this->db->query($sql);
4016 if ($result) {
4017 $obj = $this->db->fetch_object($result);
4018 $this->stats_contrat['customers'] = $obj->nb_customers;
4019 $this->stats_contrat['nb'] = $obj->nb;
4020 $this->stats_contrat['rows'] = $obj->nb_rows;
4021 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
4022
4023 // if it's a virtual product, maybe it is in contract by extension
4024 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4025 $TFather = $this->getFather();
4026 if (is_array($TFather) && !empty($TFather)) {
4027 foreach ($TFather as &$fatherData) {
4028 $pFather = new Product($this->db);
4029 $pFather->id = $fatherData['id'];
4030 $qtyCoef = $fatherData['qty'];
4031
4032 if ($fatherData['incdec']) {
4033 $pFather->load_stats_contrat($socid);
4034
4035 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
4036 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
4037 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
4038 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
4039 }
4040 }
4041 }
4042 }
4043
4044 $parameters = array('socid' => $socid);
4045 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
4046 if ($reshook > 0) {
4047 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
4048 }
4049
4050 return 1;
4051 } else {
4052 $this->error = $this->db->error().' sql='.$sql;
4053 return -1;
4054 }
4055 }
4056
4057 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4064 public function load_stats_facture($socid = 0)
4065 {
4066 // phpcs:enable
4067 global $user, $hookmanager, $action;
4068
4069 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
4070 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
4071 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
4072 $sql .= ", ".$this->db->prefix()."facture as f";
4073 $sql .= ", ".$this->db->prefix()."societe as s";
4074 if (!$user->hasRight('societe', 'client', 'voir')) {
4075 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4076 }
4077 $sql .= " WHERE f.rowid = fd.fk_facture";
4078 $sql .= " AND f.fk_soc = s.rowid";
4079 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4080 $sql .= " AND fd.fk_product = ".((int) $this->id);
4081 if (!$user->hasRight('societe', 'client', 'voir')) {
4082 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4083 }
4084 //$sql.= " AND f.fk_statut != 0";
4085 if ($socid > 0) {
4086 $sql .= " AND f.fk_soc = ".((int) $socid);
4087 }
4088
4089 $result = $this->db->query($sql);
4090 if ($result) {
4091 $obj = $this->db->fetch_object($result);
4092 $this->stats_facture['customers'] = $obj->nb_customers;
4093 $this->stats_facture['nb'] = $obj->nb;
4094 $this->stats_facture['rows'] = $obj->nb_rows;
4095 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
4096
4097 // if it's a virtual product, maybe it is in invoice by extension
4098 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4099 $TFather = $this->getFather();
4100 if (is_array($TFather) && !empty($TFather)) {
4101 foreach ($TFather as &$fatherData) {
4102 $pFather = new Product($this->db);
4103 $pFather->id = $fatherData['id'];
4104 $qtyCoef = $fatherData['qty'];
4105
4106 if ($fatherData['incdec']) {
4107 $pFather->load_stats_facture($socid);
4108
4109 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
4110 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
4111 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
4112 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
4113 }
4114 }
4115 }
4116 }
4117
4118 $parameters = array('socid' => $socid);
4119 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
4120 if ($reshook > 0) {
4121 $this->stats_facture = $hookmanager->resArray['stats_facture'];
4122 }
4123
4124 return 1;
4125 } else {
4126 $this->error = $this->db->error();
4127 return -1;
4128 }
4129 }
4130
4131
4132 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4139 public function load_stats_facturerec($socid = 0)
4140 {
4141 // phpcs:enable
4142 global $user, $hookmanager, $action;
4143
4144 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
4145 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4146 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
4147 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
4148 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
4149 if (!$user->hasRight('societe', 'client', 'voir')) {
4150 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
4151 }
4152 $sql .= " WHERE f.rowid = fd.fk_facture";
4153 $sql .= " AND f.fk_soc = s.rowid";
4154 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4155 $sql .= " AND fd.fk_product = ".((int) $this->id);
4156 if (!$user->hasRight('societe', 'client', 'voir')) {
4157 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4158 }
4159 //$sql.= " AND f.fk_statut != 0";
4160 if ($socid > 0) {
4161 $sql .= " AND f.fk_soc = ".((int) $socid);
4162 }
4163
4164 $result = $this->db->query($sql);
4165 if ($result) {
4166 $obj = $this->db->fetch_object($result);
4167 $this->stats_facturerec['customers'] = $obj->nb_customers;
4168 $this->stats_facturerec['nb'] = $obj->nb;
4169 $this->stats_facturerec['rows'] = $obj->nb_rows;
4170 $this->stats_facturerec['qty'] = $obj->qty ? $obj->qty : 0;
4171
4172 // if it's a virtual product, maybe it is in invoice by extension
4173 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4174 $TFather = $this->getFather();
4175 if (is_array($TFather) && !empty($TFather)) {
4176 foreach ($TFather as &$fatherData) {
4177 $pFather = new Product($this->db);
4178 $pFather->id = $fatherData['id'];
4179 $qtyCoef = $fatherData['qty'];
4180
4181 if ($fatherData['incdec']) {
4182 $pFather->load_stats_facture($socid);
4183
4184 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
4185 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
4186 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
4187 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
4188 }
4189 }
4190 }
4191 }
4192
4193 $parameters = array('socid' => $socid);
4194 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
4195 if ($reshook > 0) {
4196 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
4197 }
4198
4199 return 1;
4200 } else {
4201 $this->error = $this->db->error();
4202 return -1;
4203 }
4204 }
4205
4206 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4213 public function load_stats_facture_fournisseur($socid = 0)
4214 {
4215 // phpcs:enable
4216 global $user, $hookmanager, $action;
4217
4218 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
4219 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4220 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
4221 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
4222 $sql .= ", ".$this->db->prefix()."societe as s";
4223 if (!$user->hasRight('societe', 'client', 'voir')) {
4224 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4225 }
4226 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
4227 $sql .= " AND f.fk_soc = s.rowid";
4228 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4229 $sql .= " AND fd.fk_product = ".((int) $this->id);
4230 if (!$user->hasRight('societe', 'client', 'voir')) {
4231 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4232 }
4233 //$sql.= " AND f.fk_statut != 0";
4234 if ($socid > 0) {
4235 $sql .= " AND f.fk_soc = ".((int) $socid);
4236 }
4237
4238 $result = $this->db->query($sql);
4239 if ($result) {
4240 $obj = $this->db->fetch_object($result);
4241 $this->stats_facture_fournisseur['suppliers'] = $obj->nb_suppliers;
4242 $this->stats_facture_fournisseur['nb'] = $obj->nb;
4243 $this->stats_facture_fournisseur['rows'] = $obj->nb_rows;
4244 $this->stats_facture_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
4245
4246 $parameters = array('socid' => $socid);
4247 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
4248 if ($reshook > 0) {
4249 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
4250 }
4251
4252 return 1;
4253 } else {
4254 $this->error = $this->db->error();
4255 return -1;
4256 }
4257 }
4258
4259 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4268 private function _get_stats($sql, $mode, $year = 0)
4269 {
4270 // phpcs:enable
4271 $tab = array();
4272
4273 $resql = $this->db->query($sql);
4274 if ($resql) {
4275 $num = $this->db->num_rows($resql);
4276 $i = 0;
4277 while ($i < $num) {
4278 $arr = $this->db->fetch_array($resql);
4279 if (is_array($arr)) {
4280 $keyfortab = (string) $arr[1];
4281 if ($year == -1) {
4282 $keyfortab = substr($keyfortab, -2);
4283 }
4284
4285 if ($mode == 'byunit') {
4286 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
4287 } elseif ($mode == 'bynumber') {
4288 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
4289 } elseif ($mode == 'byamount') {
4290 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
4291 } else {
4292 // Bad value for $mode
4293 return -1;
4294 }
4295 }
4296 $i++;
4297 }
4298 } else {
4299 $this->error = $this->db->error().' sql='.$sql;
4300 return -1;
4301 }
4302
4303 if (empty($year)) {
4304 $year = dol_print_date(time(), '%Y');
4305 $month = dol_print_date(time(), '%m');
4306 } elseif ($year == -1) {
4307 $year = '';
4308 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4309 } else {
4310 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4311 }
4312
4313 $result = array();
4314
4315 for ($j = 0; $j < 12; $j++) {
4316 // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
4317 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
4318
4319 //print $idx.'-'.$year.'-'.$month.'<br>';
4320 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
4321 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
4322
4323 $month = "0".($month - 1);
4324 if (dol_strlen($month) == 3) {
4325 $month = substr($month, 1);
4326 }
4327 if ($month == 0) {
4328 $month = 12;
4329 $year -= 1;
4330 }
4331 }
4332
4333 return array_reverse($result);
4334 }
4335
4336
4337 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4348 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4349 {
4350 // phpcs:enable
4351 global $user;
4352
4353 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4354 if ($mode == 'bynumber') {
4355 $sql .= ", count(DISTINCT f.rowid)";
4356 }
4357 $sql .= ", sum(d.total_ht) as total_ht";
4358 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
4359 if ($filteronproducttype >= 0) {
4360 $sql .= ", ".$this->db->prefix()."product as p";
4361 }
4362 if (!$user->hasRight('societe', 'client', 'voir')) {
4363 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4364 }
4365 $sql .= " WHERE f.rowid = d.fk_facture";
4366 if ($this->id > 0) {
4367 $sql .= " AND d.fk_product = ".((int) $this->id);
4368 } else {
4369 $sql .= " AND d.fk_product > 0";
4370 }
4371 if ($filteronproducttype >= 0) {
4372 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4373 }
4374 $sql .= " AND f.fk_soc = s.rowid";
4375 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4376 if (!$user->hasRight('societe', 'client', 'voir')) {
4377 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4378 }
4379 if ($socid > 0) {
4380 $sql .= " AND f.fk_soc = $socid";
4381 }
4382 $sql .= $morefilter;
4383 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4384 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4385
4386 return $this->_get_stats($sql, $mode, $year);
4387 }
4388
4389
4390 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4401 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4402 {
4403 // phpcs:enable
4404 global $user;
4405
4406 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4407 if ($mode == 'bynumber') {
4408 $sql .= ", count(DISTINCT f.rowid)";
4409 }
4410 $sql .= ", sum(d.total_ht) as total_ht";
4411 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
4412 if ($filteronproducttype >= 0) {
4413 $sql .= ", ".$this->db->prefix()."product as p";
4414 }
4415 if (!$user->hasRight('societe', 'client', 'voir')) {
4416 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4417 }
4418 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4419 if ($this->id > 0) {
4420 $sql .= " AND d.fk_product = ".((int) $this->id);
4421 } else {
4422 $sql .= " AND d.fk_product > 0";
4423 }
4424 if ($filteronproducttype >= 0) {
4425 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4426 }
4427 $sql .= " AND f.fk_soc = s.rowid";
4428 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4429 if (!$user->hasRight('societe', 'client', 'voir')) {
4430 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4431 }
4432 if ($socid > 0) {
4433 $sql .= " AND f.fk_soc = $socid";
4434 }
4435 $sql .= $morefilter;
4436 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4437 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4438
4439 return $this->_get_stats($sql, $mode, $year);
4440 }
4441
4442 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4453 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4454 {
4455 // phpcs:enable
4456 global $user;
4457
4458 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4459 if ($mode == 'bynumber') {
4460 $sql .= ", count(DISTINCT p.rowid)";
4461 }
4462 $sql .= ", sum(d.total_ht) as total_ht";
4463 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4464 if ($filteronproducttype >= 0) {
4465 $sql .= ", ".$this->db->prefix()."product as prod";
4466 }
4467 if (!$user->hasRight('societe', 'client', 'voir')) {
4468 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4469 }
4470 $sql .= " WHERE p.rowid = d.fk_propal";
4471 if ($this->id > 0) {
4472 $sql .= " AND d.fk_product = ".((int) $this->id);
4473 } else {
4474 $sql .= " AND d.fk_product > 0";
4475 }
4476 if ($filteronproducttype >= 0) {
4477 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4478 }
4479 $sql .= " AND p.fk_soc = s.rowid";
4480 $sql .= " AND p.entity IN (".getEntity('propal').")";
4481 if (!$user->hasRight('societe', 'client', 'voir')) {
4482 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4483 }
4484 if ($socid > 0) {
4485 $sql .= " AND p.fk_soc = ".((int) $socid);
4486 }
4487 $sql .= $morefilter;
4488 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4489 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4490
4491 return $this->_get_stats($sql, $mode, $year);
4492 }
4493
4494 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4505 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4506 {
4507 // phpcs:enable
4508 global $user;
4509
4510 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4511 if ($mode == 'bynumber') {
4512 $sql .= ", count(DISTINCT p.rowid)";
4513 }
4514 $sql .= ", sum(d.total_ht) as total_ht";
4515 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4516 if ($filteronproducttype >= 0) {
4517 $sql .= ", ".$this->db->prefix()."product as prod";
4518 }
4519 if (!$user->hasRight('societe', 'client', 'voir')) {
4520 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4521 }
4522 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4523 if ($this->id > 0) {
4524 $sql .= " AND d.fk_product = ".((int) $this->id);
4525 } else {
4526 $sql .= " AND d.fk_product > 0";
4527 }
4528 if ($filteronproducttype >= 0) {
4529 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4530 }
4531 $sql .= " AND p.fk_soc = s.rowid";
4532 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4533 if (!$user->hasRight('societe', 'client', 'voir')) {
4534 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4535 }
4536 if ($socid > 0) {
4537 $sql .= " AND p.fk_soc = ".((int) $socid);
4538 }
4539 $sql .= $morefilter;
4540 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4541 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4542
4543 return $this->_get_stats($sql, $mode, $year);
4544 }
4545
4546 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4557 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4558 {
4559 // phpcs:enable
4560 global $user;
4561
4562 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4563 if ($mode == 'bynumber') {
4564 $sql .= ", count(DISTINCT c.rowid)";
4565 }
4566 $sql .= ", sum(d.total_ht) as total_ht";
4567 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4568 if ($filteronproducttype >= 0) {
4569 $sql .= ", ".$this->db->prefix()."product as p";
4570 }
4571 if (!$user->hasRight('societe', 'client', 'voir')) {
4572 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4573 }
4574 $sql .= " WHERE c.rowid = d.fk_commande";
4575 if ($this->id > 0) {
4576 $sql .= " AND d.fk_product = ".((int) $this->id);
4577 } else {
4578 $sql .= " AND d.fk_product > 0";
4579 }
4580 if ($filteronproducttype >= 0) {
4581 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4582 }
4583 $sql .= " AND c.fk_soc = s.rowid";
4584 $sql .= " AND c.entity IN (".getEntity('commande').")";
4585 if (!$user->hasRight('societe', 'client', 'voir')) {
4586 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4587 }
4588 if ($socid > 0) {
4589 $sql .= " AND c.fk_soc = ".((int) $socid);
4590 }
4591 $sql .= $morefilter;
4592 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4593 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4594
4595 return $this->_get_stats($sql, $mode, $year);
4596 }
4597
4598 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4609 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4610 {
4611 // phpcs:enable
4612 global $user;
4613
4614 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4615 if ($mode == 'bynumber') {
4616 $sql .= ", count(DISTINCT c.rowid)";
4617 }
4618 $sql .= ", sum(d.total_ht) as total_ht";
4619 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4620 if ($filteronproducttype >= 0) {
4621 $sql .= ", ".$this->db->prefix()."product as p";
4622 }
4623 if (!$user->hasRight('societe', 'client', 'voir')) {
4624 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4625 }
4626 $sql .= " WHERE c.rowid = d.fk_commande";
4627 if ($this->id > 0) {
4628 $sql .= " AND d.fk_product = ".((int) $this->id);
4629 } else {
4630 $sql .= " AND d.fk_product > 0";
4631 }
4632 if ($filteronproducttype >= 0) {
4633 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4634 }
4635 $sql .= " AND c.fk_soc = s.rowid";
4636 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4637 if (!$user->hasRight('societe', 'client', 'voir')) {
4638 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4639 }
4640 if ($socid > 0) {
4641 $sql .= " AND c.fk_soc = ".((int) $socid);
4642 }
4643 $sql .= $morefilter;
4644 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4645 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4646
4647 return $this->_get_stats($sql, $mode, $year);
4648 }
4649
4650 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4661 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4662 {
4663 // phpcs:enable
4664 global $user;
4665
4666 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4667 if ($mode == 'bynumber') {
4668 $sql .= ", count(DISTINCT c.rowid)";
4669 }
4670 $sql .= ", sum(d.total_ht) as total_ht";
4671 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4672 if ($filteronproducttype >= 0) {
4673 $sql .= ", ".$this->db->prefix()."product as p";
4674 }
4675 if (!$user->hasRight('societe', 'client', 'voir')) {
4676 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4677 }
4678 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4679 $sql .= " AND c.rowid = d.fk_contrat";
4680
4681 if ($this->id > 0) {
4682 $sql .= " AND d.fk_product = ".((int) $this->id);
4683 } else {
4684 $sql .= " AND d.fk_product > 0";
4685 }
4686 if ($filteronproducttype >= 0) {
4687 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4688 }
4689 $sql .= " AND c.fk_soc = s.rowid";
4690
4691 if (!$user->hasRight('societe', 'client', 'voir')) {
4692 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4693 }
4694 if ($socid > 0) {
4695 $sql .= " AND c.fk_soc = ".((int) $socid);
4696 }
4697 $sql .= $morefilter;
4698 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4699 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4700
4701 return $this->_get_stats($sql, $mode, $year);
4702 }
4703
4704 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4715 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4716 {
4717 // phpcs:enable
4718 global $user;
4719
4720 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4721 if ($mode == 'bynumber') {
4722 $sql .= ", count(DISTINCT d.rowid)";
4723 }
4724 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4725 if ($filteronproducttype >= 0) {
4726 $sql .= ", ".$this->db->prefix()."product as p";
4727 }
4728 if (!$user->hasRight('societe', 'client', 'voir')) {
4729 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4730 }
4731
4732 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4733 $sql .= " AND d.status > 0";
4734
4735 if ($this->id > 0) {
4736 $sql .= " AND d.fk_product = ".((int) $this->id);
4737 } else {
4738 $sql .= " AND d.fk_product > 0";
4739 }
4740 if ($filteronproducttype >= 0) {
4741 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4742 }
4743
4744 if (!$user->hasRight('societe', 'client', 'voir')) {
4745 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4746 }
4747 if ($socid > 0) {
4748 $sql .= " AND d.fk_soc = ".((int) $socid);
4749 }
4750 $sql .= $morefilter;
4751 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4752 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4753
4754 return $this->_get_stats($sql, $mode, $year);
4755 }
4756
4757 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4768 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4769 {
4770 global $user;
4771
4772 // phpcs:enable
4773 // Clean parameters
4774 if (!is_numeric($id_pere)) {
4775 $id_pere = 0;
4776 }
4777 if (!is_numeric($id_fils)) {
4778 $id_fils = 0;
4779 }
4780 if (!is_numeric($incdec)) {
4781 $incdec = 0;
4782 }
4783
4784 $result = $this->del_sousproduit($id_pere, $id_fils);
4785 if ($result < 0) {
4786 return $result;
4787 }
4788
4789 // Check not already father of id_pere (to avoid father -> child -> father links)
4790 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4791 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4792 if (!$this->db->query($sql)) {
4793 dol_print_error($this->db);
4794 return -1;
4795 } else {
4796 //Selection of the highest row
4797 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4798 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4799 $resql = $this->db->query($sql);
4800 if ($resql) {
4801 $obj = $this->db->fetch_object($resql);
4802 $rank = $obj->max_rank + 1;
4803 //Addition of a product with the highest rank +1
4804 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4805 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".((int) $incdec).", ".((int) $rank).")";
4806 if (! $this->db->query($sql)) {
4807 dol_print_error($this->db);
4808 return -1;
4809 } else {
4810 if (!$notrigger) {
4811 // Call trigger
4812 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
4813 if ($result < 0) {
4814 $this->error = $this->db->lasterror();
4815 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
4816 return -1;
4817 }
4818 }
4819 // End call triggers
4820
4821 return 1;
4822 }
4823 } else {
4824 dol_print_error($this->db);
4825 return -1;
4826 }
4827 }
4828 }
4829
4830 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4841 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4842 {
4843 global $user;
4844
4845 // phpcs:enable
4846 // Clean parameters
4847 if (!is_numeric($id_pere)) {
4848 $id_pere = 0;
4849 }
4850 if (!is_numeric($id_fils)) {
4851 $id_fils = 0;
4852 }
4853 if (!is_numeric($incdec)) {
4854 $incdec = 1;
4855 }
4856 if (!is_numeric($qty)) {
4857 $qty = 1;
4858 }
4859
4860 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4861 $sql .= 'qty = '.price2num($qty, 'MS');
4862 $sql .= ',incdec = '.((int) $incdec);
4863 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4864
4865 if (!$this->db->query($sql)) {
4866 dol_print_error($this->db);
4867 return -1;
4868 } else {
4869 if (!$notrigger) {
4870 // Call trigger
4871 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
4872 if ($result < 0) {
4873 $this->error = $this->db->lasterror();
4874 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
4875 return -1;
4876 }
4877 // End call triggers
4878 }
4879
4880 return 1;
4881 }
4882 }
4883
4884 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4893 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
4894 {
4895 global $user;
4896
4897 // phpcs:enable
4898 if (!is_numeric($fk_parent)) {
4899 $fk_parent = 0;
4900 }
4901 if (!is_numeric($fk_child)) {
4902 $fk_child = 0;
4903 }
4904
4905 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
4906 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4907 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4908
4909 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
4910 if (!$this->db->query($sql)) {
4911 dol_print_error($this->db);
4912 return -1;
4913 }
4914
4915 // Updated ranks so that none are missing
4916 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
4917 $sqlrank .= " WHERE fk_product_pere = ".((int) $fk_parent);
4918 $sqlrank .= " ORDER BY rang";
4919 $resqlrank = $this->db->query($sqlrank);
4920 if ($resqlrank) {
4921 $cpt = 0;
4922 while ($objrank = $this->db->fetch_object($resqlrank)) {
4923 $cpt++;
4924 $sql = "UPDATE ".$this->db->prefix()."product_association";
4925 $sql .= " SET rang = ".((int) $cpt);
4926 $sql .= " WHERE rowid = ".((int) $objrank->rowid);
4927 if (! $this->db->query($sql)) {
4928 dol_print_error($this->db);
4929 return -1;
4930 }
4931 }
4932 }
4933
4934 if (!$notrigger) {
4935 // Call trigger
4936 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
4937 if ($result < 0) {
4938 $this->error = $this->db->lasterror();
4939 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
4940 return -1;
4941 }
4942 // End call triggers
4943 }
4944
4945 return 1;
4946 }
4947
4948 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4956 public function is_sousproduit($fk_parent, $fk_child)
4957 {
4958 // phpcs:enable
4959 $sql = "SELECT fk_product_pere, qty, incdec";
4960 $sql .= " FROM ".$this->db->prefix()."product_association";
4961 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4962 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4963
4964 $result = $this->db->query($sql);
4965 if ($result) {
4966 $num = $this->db->num_rows($result);
4967
4968 if ($num > 0) {
4969 $obj = $this->db->fetch_object($result);
4970
4971 $this->is_sousproduit_qty = $obj->qty;
4972 $this->is_sousproduit_incdec = $obj->incdec;
4973
4974 return 1;
4975 } else {
4976 return 0;
4977 }
4978 } else {
4979 dol_print_error($this->db);
4980 return -1;
4981 }
4982 }
4983
4984
4985 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4996 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
4997 {
4998 // phpcs:enable
4999 global $conf;
5000
5001 $now = dol_now();
5002
5003 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
5004
5005 // Clean parameters
5006 $quantity = price2num($quantity, 'MS');
5007
5008 if ($ref_fourn) {
5009 // Check if ref is not already used
5010 $sql = "SELECT rowid, fk_product";
5011 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5012 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5013 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5014 $sql .= " AND fk_product <> ".((int) $this->id);
5015 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5016
5017 $resql = $this->db->query($sql);
5018 if ($resql) {
5019 $obj = $this->db->fetch_object($resql);
5020 if ($obj) {
5021 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
5022 $this->product_id_already_linked = $obj->fk_product;
5023 return -3;
5024 }
5025 $this->db->free($resql);
5026 }
5027 }
5028
5029 $sql = "SELECT rowid";
5030 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5031 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5032 if ($ref_fourn) {
5033 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5034 } else {
5035 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
5036 }
5037 $sql .= " AND quantity = ".((float) $quantity);
5038 $sql .= " AND fk_product = ".((int) $this->id);
5039 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5040
5041 $resql = $this->db->query($sql);
5042 if ($resql) {
5043 $obj = $this->db->fetch_object($resql);
5044
5045 // The reference supplier does not exist, we create it for this product.
5046 if (empty($obj)) {
5047 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
5048 $sql .= "datec";
5049 $sql .= ", entity";
5050 $sql .= ", fk_product";
5051 $sql .= ", fk_soc";
5052 $sql .= ", ref_fourn";
5053 $sql .= ", quantity";
5054 $sql .= ", fk_user";
5055 $sql .= ", tva_tx";
5056 $sql .= ") VALUES (";
5057 $sql .= "'".$this->db->idate($now)."'";
5058 $sql .= ", ".((int) $conf->entity);
5059 $sql .= ", ".((int) $this->id);
5060 $sql .= ", ".((int) $id_fourn);
5061 $sql .= ", '".$this->db->escape($ref_fourn)."'";
5062 $sql .= ", ".((float) $quantity);
5063 $sql .= ", ".((int) $user->id);
5064 $sql .= ", 0";
5065 $sql .= ")";
5066
5067 if ($this->db->query($sql)) {
5068 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
5069 return 1;
5070 } else {
5071 $this->error = $this->db->lasterror();
5072 return -1;
5073 }
5074 } else {
5075 // If the supplier price already exists for this product and quantity
5076 $this->product_fourn_price_id = $obj->rowid;
5077 return 0;
5078 }
5079 } else {
5080 $this->error = $this->db->lasterror();
5081 return -2;
5082 }
5083 }
5084
5085
5086 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5092 public function list_suppliers()
5093 {
5094 // phpcs:enable
5095 global $conf;
5096
5097 $list = array();
5098
5099 $sql = "SELECT DISTINCT p.fk_soc";
5100 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
5101 $sql .= " WHERE p.fk_product = ".((int) $this->id);
5102 $sql .= " AND p.entity = ".((int) $conf->entity);
5103
5104 $result = $this->db->query($sql);
5105 if ($result) {
5106 $num = $this->db->num_rows($result);
5107 $i = 0;
5108 while ($i < $num) {
5109 $obj = $this->db->fetch_object($result);
5110 $list[$i] = $obj->fk_soc;
5111 $i++;
5112 }
5113 }
5114
5115 return $list;
5116 }
5117
5118 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5126 public function clone_price($fromId, $toId)
5127 {
5128 global $user;
5129
5130 $now = dol_now();
5131
5132 $this->db->begin();
5133
5134 // prices
5135 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
5136 $sql .= " entity";
5137 $sql .= ", fk_product";
5138 $sql .= ", date_price";
5139 $sql .= ", price_level";
5140 $sql .= ", price";
5141 $sql .= ", price_ttc";
5142 $sql .= ", price_min";
5143 $sql .= ", price_min_ttc";
5144 $sql .= ", price_base_type";
5145 $sql .= ", price_label";
5146 $sql .= ", default_vat_code";
5147 $sql .= ", tva_tx";
5148 $sql .= ", recuperableonly";
5149 $sql .= ", localtax1_tx";
5150 $sql .= ", localtax1_type";
5151 $sql .= ", localtax2_tx";
5152 $sql .= ", localtax2_type";
5153 $sql .= ", fk_user_author";
5154 $sql .= ", tosell";
5155 $sql .= ", price_by_qty";
5156 $sql .= ", fk_price_expression";
5157 $sql .= ", fk_multicurrency";
5158 $sql .= ", multicurrency_code";
5159 $sql .= ", multicurrency_tx";
5160 $sql .= ", multicurrency_price";
5161 $sql .= ", multicurrency_price_ttc";
5162 $sql .= ")";
5163 $sql .= " SELECT";
5164 $sql .= " entity";
5165 $sql .= ", ".$toId;
5166 $sql .= ", '".$this->db->idate($now)."'";
5167 $sql .= ", price_level";
5168 $sql .= ", price";
5169 $sql .= ", price_ttc";
5170 $sql .= ", price_min";
5171 $sql .= ", price_min_ttc";
5172 $sql .= ", price_base_type";
5173 $sql .= ", price_label";
5174 $sql .= ", default_vat_code";
5175 $sql .= ", tva_tx";
5176 $sql .= ", recuperableonly";
5177 $sql .= ", localtax1_tx";
5178 $sql .= ", localtax1_type";
5179 $sql .= ", localtax2_tx";
5180 $sql .= ", localtax2_type";
5181 $sql .= ", ".$user->id;
5182 $sql .= ", tosell";
5183 $sql .= ", price_by_qty";
5184 $sql .= ", fk_price_expression";
5185 $sql .= ", fk_multicurrency";
5186 $sql .= ", multicurrency_code";
5187 $sql .= ", multicurrency_tx";
5188 $sql .= ", multicurrency_price";
5189 $sql .= ", multicurrency_price_ttc";
5190 $sql .= " FROM ".$this->db->prefix()."product_price ps";
5191 $sql .= " WHERE fk_product = ".((int) $fromId);
5192 $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)";
5193 $sql .= " ORDER BY date_price DESC";
5194
5195 dol_syslog(__METHOD__, LOG_DEBUG);
5196 $resql = $this->db->query($sql);
5197 if (!$resql) {
5198 $this->db->rollback();
5199 return -1;
5200 }
5201
5202 $this->db->commit();
5203 return 1;
5204 }
5205
5206 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5214 public function clone_associations($fromId, $toId)
5215 {
5216 // phpcs:enable
5217 $this->db->begin();
5218
5219 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
5220 $sql .= " SELECT ".$toId.", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
5221 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
5222
5223 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
5224 if (!$this->db->query($sql)) {
5225 $this->db->rollback();
5226 return -1;
5227 }
5228
5229 $this->db->commit();
5230 return 1;
5231 }
5232
5233 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5241 public function clone_fournisseurs($fromId, $toId)
5242 {
5243 // phpcs:enable
5244 $this->db->begin();
5245
5246 $now = dol_now();
5247
5248 // les fournisseurs
5249 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
5250 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
5251 . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
5252 . " FROM ".$this->db->prefix()."product_fournisseur"
5253 . " WHERE fk_product = ".((int) $fromId);
5254
5255 if ( ! $this->db->query($sql ) )
5256 {
5257 $this->db->rollback();
5258 return -1;
5259 }*/
5260
5261 // les prix de fournisseurs.
5262 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
5263 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
5264 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
5265 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5266 $sql .= " WHERE fk_product = ".((int) $fromId);
5267
5268 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
5269 $resql = $this->db->query($sql);
5270 if (!$resql) {
5271 $this->db->rollback();
5272 return -1;
5273 } else {
5274 $this->db->commit();
5275 return 1;
5276 }
5277 }
5278
5279 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5292 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
5293 {
5294 // phpcs:enable
5295 $tmpproduct = null;
5296
5297 //var_dump($prod);
5298 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
5299 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
5300 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
5301 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
5302 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
5303 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
5304 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
5305
5306 if ($multiply < 1) {
5307 $multiply = 1;
5308 }
5309
5310 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
5311 if (is_null($tmpproduct)) {
5312 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
5313 }
5314 $tmpproduct->fetch($id); // Load product to get ->ref
5315
5316 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
5317 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
5318 }
5319
5320 $this->res[] = array(
5321 'id' => $id, // Id product
5322 'id_parent' => $id_parent,
5323 'ref' => $tmpproduct->ref, // Ref product
5324 'nb' => $nb, // Nb of units that compose parent product
5325 'nb_total' => $nb * $multiply, // Nb of units for all nb of product
5326 'stock' => $tmpproduct->stock_reel, // Stock
5327 'stock_alert' => $tmpproduct->seuil_stock_alerte, // Stock alert
5328 'label' => $label,
5329 'fullpath' => $compl_path.$label, // Label
5330 'type' => $type, // Nb of units that compose parent product
5331 'desiredstock' => $tmpproduct->desiredstock,
5332 'level' => $level,
5333 'incdec' => $incdec,
5334 'entity' => $tmpproduct->entity
5335 );
5336
5337 // Recursive call if there child has children of its own
5338 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
5339 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
5340 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id, $ignore_stock_load);
5341 }
5342 }
5343 }
5344 }
5345
5346 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5355 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
5356 {
5357 // phpcs:enable
5358 $this->res = array();
5359 if (isset($this->sousprods) && is_array($this->sousprods)) {
5360 foreach ($this->sousprods as $prod_name => $desc_product) {
5361 if (is_array($desc_product)) {
5362 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
5363 }
5364 }
5365 }
5366 //var_dump($res);
5367 return $this->res;
5368 }
5369
5377 public function hasFatherOrChild($mode = 0)
5378 {
5379 $nb = 0;
5380
5381 $sql = "SELECT COUNT(pa.rowid) as nb";
5382 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
5383 if ($mode == 0) {
5384 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
5385 } elseif ($mode == -1) {
5386 $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)
5387 } elseif ($mode == 1) {
5388 $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)
5389 }
5390
5391 $resql = $this->db->query($sql);
5392 if ($resql) {
5393 $obj = $this->db->fetch_object($resql);
5394 if ($obj) {
5395 $nb = $obj->nb;
5396 }
5397 } else {
5398 return -1;
5399 }
5400
5401 return $nb;
5402 }
5403
5409 public function hasVariants()
5410 {
5411 $nb = 0;
5412 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
5413 $sql .= " AND entity IN (".getEntity('product').")";
5414
5415 $resql = $this->db->query($sql);
5416 if ($resql) {
5417 $obj = $this->db->fetch_object($resql);
5418 if ($obj) {
5419 $nb = $obj->nb;
5420 }
5421 }
5422
5423 return $nb;
5424 }
5425
5426
5432 public function isVariant()
5433 {
5434 if (isModEnabled('variants')) {
5435 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5436
5437 $query = $this->db->query($sql);
5438
5439 if ($query) {
5440 if (!$this->db->num_rows($query)) {
5441 return false;
5442 }
5443 return true;
5444 } else {
5445 dol_print_error($this->db);
5446 return -1;
5447 }
5448 } else {
5449 return false;
5450 }
5451 }
5452
5459 public function getFather()
5460 {
5461 $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";
5462 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5463 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5464 $sql .= " ".$this->db->prefix()."product as p";
5465 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5466 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5467
5468 $res = $this->db->query($sql);
5469 if ($res) {
5470 $prods = array();
5471 while ($record = $this->db->fetch_array($res)) {
5472 // $record['id'] = $record['rowid'] = id of father
5473 $prods[$record['id']] = array();
5474 $prods[$record['id']]['id'] = $record['rowid'];
5475 $prods[$record['id']]['ref'] = $record['ref'];
5476 $prods[$record['id']]['label'] = $record['label'];
5477 $prods[$record['id']]['qty'] = $record['qty'];
5478 $prods[$record['id']]['incdec'] = $record['incdec'];
5479 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5480 $prods[$record['id']]['entity'] = $record['entity'];
5481 $prods[$record['id']]['status'] = $record['status'];
5482 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5483 }
5484 return $prods;
5485 } else {
5486 dol_print_error($this->db);
5487 return -1;
5488 }
5489 }
5490
5491
5501 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5502 {
5503 global $alreadyfound;
5504
5505 if (empty($id)) {
5506 return array();
5507 }
5508
5509 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5510 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5511 $sql .= " pa.rowid as fk_association, pa.rang";
5512 $sql .= " FROM ".$this->db->prefix()."product as p,";
5513 $sql .= " ".$this->db->prefix()."product_association as pa";
5514 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5515 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5516 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5517 $sql .= " ORDER BY pa.rang";
5518
5519 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5520
5521 if ($level == 1) {
5522 $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 immediately
5523 }
5524 // Protection against infinite loop
5525 if ($level > 30) {
5526 return array();
5527 }
5528
5529 $res = $this->db->query($sql);
5530 if ($res) {
5531 $prods = array();
5532 while ($rec = $this->db->fetch_array($res)) {
5533 if (!empty($alreadyfound[$rec['rowid']])) {
5534 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);
5535 if (in_array($rec['id'], $parents)) {
5536 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5537 }
5538 }
5539 $alreadyfound[$rec['rowid']] = 1;
5540 $prods[$rec['rowid']] = array(
5541 0 => $rec['rowid'],
5542 1 => $rec['qty'],
5543 2 => $rec['fk_product_type'],
5544 3 => $this->db->escape($rec['label']),
5545 4 => $rec['incdec'],
5546 5 => $rec['ref'],
5547 6 => $rec['fk_association'],
5548 7 => $rec['rang']
5549 );
5550 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5551 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5552 if (empty($firstlevelonly)) {
5553 $parents[] = $rec['rowid'];
5554 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5555 foreach ($listofchilds as $keyChild => $valueChild) {
5556 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5557 }
5558 }
5559 }
5560
5561 return $prods;
5562 } else {
5563 dol_print_error($this->db);
5564 return -1;
5565 }
5566 }
5567
5568 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5575 public function get_sousproduits_arbo()
5576 {
5577 // phpcs:enable
5578 $parent = array();
5579
5580 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5581 $parent[$this->label][$keyChild] = $valueChild;
5582 }
5583 foreach ($parent as $key => $value) { // key=label, value is array of children
5584 $this->sousprods[$key] = $value; // @phan-suppress-current-line PhanTypeMismatchProperty
5585 }
5586 }
5587
5595 public function getTooltipContentArray($params)
5596 {
5597 global $conf, $langs, $user;
5598
5599 $langs->loadLangs(array('products', 'other'));
5600
5601 $datas = array();
5602 $nofetch = !empty($params['nofetch']);
5603
5604 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5605 return ['optimize' => $langs->trans("ShowProduct")];
5606 }
5607
5608 // Does user has permission to read product/service
5609 $permissiontoreadproduct = 0;
5610 if ($this->type == self::TYPE_PRODUCT && $user->hasRight('product', 'read')) {
5611 $permissiontoreadproduct = 1;
5612 }
5613 if ($this->type == self::TYPE_SERVICE && $user->hasRight('service', 'read')) {
5614 $permissiontoreadproduct = 1;
5615 }
5616
5617 if (!empty($this->entity) && $permissiontoreadproduct) {
5618 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, '1');
5619 if ($this->nbphoto > 0) {
5620 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5621 }
5622 }
5623
5624 if ($this->isProduct()) {
5625 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5626 } elseif ($this->isService()) {
5627 $datas['picto'] = img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5628 }
5629 if (isset($this->status) && isset($this->status_buy)) {
5630 $datas['status'] = ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5631 }
5632
5633 if (!empty($this->ref)) {
5634 $datas['ref'] = '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5635 }
5636 if (!empty($this->label)) {
5637 $datas['label'] = '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5638 }
5639
5640 if ($permissiontoreadproduct) {
5641 if (!empty($this->description)) {
5642 $datas['description'] = '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineOfText($this->description, 5);
5643 }
5644 if ($this->isStockManaged()) {
5645 if (isModEnabled('productbatch')) {
5646 $langs->load("productbatch");
5647 $datas['batchstatus'] = "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5648 }
5649 }
5650 if (isModEnabled('barcode')) {
5651 $datas['barcode'] = '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5652 }
5653
5654 if ($this->isProduct()) {
5655 if ($this->weight) {
5656 $datas['weight'] = "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5657 }
5658 $labelsize = "";
5659 if ($this->length) {
5660 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5661 }
5662 if ($this->width) {
5663 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5664 }
5665 if ($this->height) {
5666 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5667 }
5668 if ($labelsize) {
5669 $datas['size'] = "<br>".$labelsize;
5670 }
5671
5672 $labelsurfacevolume = "";
5673 if ($this->surface) {
5674 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5675 }
5676 if ($this->volume) {
5677 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5678 }
5679 if ($labelsurfacevolume) {
5680 $datas['surface'] = "<br>" . $labelsurfacevolume;
5681 }
5682 }
5683 if ($this->isService() && !empty($this->duration_value)) {
5684 // Duration
5685 $datas['duration'] = '<br><b>'.$langs->trans("Duration").':</b> '.$this->duration_value;
5686 if ($this->duration_value > 1) {
5687 $dur = array("i" => $langs->trans("Minutes"), "h" => $langs->trans("Hours"), "d" => $langs->trans("Days"), "w" => $langs->trans("Weeks"), "m" => $langs->trans("Months"), "y" => $langs->trans("Years"));
5688 } elseif ($this->duration_value > 0) {
5689 $dur = array("i" => $langs->trans("Minute"), "h" => $langs->trans("Hour"), "d" => $langs->trans("Day"), "w" => $langs->trans("Week"), "m" => $langs->trans("Month"), "y" => $langs->trans("Year"));
5690 }
5691 $datas['duration'] .= (!empty($this->duration_unit) && isset($dur[$this->duration_unit]) ? "&nbsp;".$langs->trans($dur[$this->duration_unit]) : '');
5692 }
5693 if (empty($user->socid)) {
5694 if (!empty($this->pmp) && $this->pmp) {
5695 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5696 }
5697
5698 if (isModEnabled('accounting')) {
5699 if ($this->status && isset($this->accountancy_code_sell)) {
5700 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5701 $selllabel = '<br>';
5702 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5703 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5704 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5705 $datas['accountancysell'] = $selllabel;
5706 }
5707 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5708 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5709 $buylabel = '';
5710 if (empty($this->status)) {
5711 $buylabel .= '<br>';
5712 }
5713 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5714 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5715 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5716 $datas['accountancybuy'] = $buylabel;
5717 }
5718 }
5719 }
5720 // show categories for this record only in ajax to not overload lists
5721 if (isModEnabled('category') && !$nofetch) {
5722 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5723 $form = new Form($this->db);
5724 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5725 }
5726 }
5727
5728 return $datas;
5729 }
5730
5744 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5745 {
5746 global $langs, $hookmanager;
5747
5748 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5749
5750 $result = '';
5751
5752 $newref = $this->ref;
5753 if ($maxlength) {
5754 $newref = dol_trunc($newref, $maxlength, 'middle');
5755 }
5756 $params = [
5757 'id' => $this->id,
5758 'objecttype' => ($this->type == 1 ? 'service' : 'product'),
5759 'option' => $option,
5760 'nofetch' => 1,
5761 ];
5762 $classfortooltip = 'classfortooltip';
5763 $dataparams = '';
5764 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5765 $classfortooltip = 'classforajaxtooltip';
5766 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5767 $label = '';
5768 } else {
5769 $label = implode($this->getTooltipContentArray($params));
5770 }
5771
5772 $linkclose = '';
5773 if (empty($notooltip)) {
5774 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5775 $label = $langs->trans("ShowProduct");
5776 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5777 }
5778 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5779 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5780 } else {
5781 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5782 }
5783
5784 if ($option == 'supplier' || $option == 'category') {
5785 $url = DOL_URL_ROOT.'/product/price_suppliers.php?id='.$this->id;
5786 } elseif ($option == 'stock') {
5787 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5788 } elseif ($option == 'composition') {
5789 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5790 } else {
5791 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5792 }
5793
5794 if ($option !== 'nolink') {
5795 // Add param to save lastsearch_values or not
5796 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5797 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5798 $add_save_lastsearch_values = 1;
5799 }
5800 if ($add_save_lastsearch_values) {
5801 $url .= '&save_lastsearch_values=1';
5802 }
5803 }
5804
5805 $linkstart = '<a href="'.$url.'"';
5806 $linkstart .= $linkclose.'>';
5807 $linkend = '</a>';
5808
5809 $result .= $linkstart;
5810 if ($withpicto) {
5811 if ($this->isProduct()) {
5812 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5813 }
5814 if ($this->isService()) {
5815 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5816 }
5817 }
5818 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
5819 $result .= $linkend;
5820 if ($withpicto != 2) {
5821 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5822 }
5823
5824 global $action;
5825 $hookmanager->initHooks(array('productdao'));
5826 $parameters = array('id' => $this->id, 'getnomurl' => &$result, 'label' => &$label);
5827 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5828 if ($reshook > 0) {
5829 $result = $hookmanager->resPrint;
5830 } else {
5831 $result .= $hookmanager->resPrint;
5832 }
5833
5834 return $result;
5835 }
5836
5837
5848 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5849 {
5850 global $langs;
5851
5852 $langs->load("products");
5853 $outputlangs->load("products");
5854
5855 // Positionne le modele sur le nom du modele a utiliser
5856 if (!dol_strlen($modele)) {
5857 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5858 }
5859
5860 $modelpath = "core/modules/product/doc/";
5861
5862 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5863 }
5864
5872 public function getLibStatut($mode = 0, $type = 0)
5873 {
5874 switch ($type) {
5875 case 0:
5876 return $this->LibStatut($this->status, $mode, $type);
5877 case 1:
5878 return $this->LibStatut($this->status_buy, $mode, $type);
5879 case 2:
5880 return $this->LibStatut($this->status_batch, $mode, $type);
5881 default:
5882 //Simulate previous behavior but should return an error string
5883 return $this->LibStatut($this->status_buy, $mode, $type);
5884 }
5885 }
5886
5887 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5896 public function LibStatut($status, $mode = 0, $type = 0)
5897 {
5898 // phpcs:enable
5899 global $langs;
5900
5901 $labelStatus = $labelStatusShort = '';
5902
5903 $langs->load('products');
5904 if (isModEnabled('productbatch')) {
5905 $langs->load("productbatch");
5906 }
5907
5908 if ($type == 2) {
5909 switch ($mode) {
5910 case 0:
5911 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5912 return dolGetStatus($label);
5913 case 1:
5914 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5915 return dolGetStatus($label);
5916 case 2:
5917 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5918 case 3:
5919 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5920 case 4:
5921 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5922 case 5:
5923 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5924 default:
5925 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5926 }
5927 }
5928
5929 $statuttrans = empty($status) ? 'status5' : 'status4';
5930
5931 if ($status == 0) {
5932 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5933 if ($type == 0) {
5934 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5935 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5936 } elseif ($type == 1) {
5937 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5938 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5939 } elseif ($type == 2) {
5940 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5941 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5942 }
5943 } elseif ($status == 1) {
5944 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5945 if ($type == 0) {
5946 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5947 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5948 } elseif ($type == 1) {
5949 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5950 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5951 } elseif ($type == 2) {
5952 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5953 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5954 }
5955 } elseif ($type == 2 && $status == 2) {
5956 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5957 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5958 }
5959
5960 if ($mode > 6) {
5961 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5962 } else {
5963 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5964 }
5965 }
5966
5967
5973 public function getLibFinished()
5974 {
5975 global $langs;
5976
5977 $langs->load('products');
5978 $label = '';
5979
5980 if (isset($this->finished) && $this->finished >= 0) {
5981 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5982 $resql = $this->db->query($sql);
5983 if (!$resql) {
5984 $this->error = $this->db->error().' sql='.$sql;
5985 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
5986 return -1;
5987 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
5988 $label = $langs->trans($res['label']);
5989 }
5990 $this->db->free($resql);
5991 }
5992
5993 return $label;
5994 }
5995
5996
5997 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6014 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
6015 {
6016 // phpcs:enable
6017 if ($id_entrepot) {
6018 $this->db->begin();
6019
6020 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6021
6022 if ($nbpiece < 0) {
6023 if (!$movement) {
6024 $movement = 1;
6025 }
6026 $nbpiece = abs($nbpiece);
6027 }
6028 $op = array();
6029 $op[0] = "+".trim((string) $nbpiece);
6030 $op[1] = "-".trim((string) $nbpiece);
6031
6032 $movementstock = new MouvementStock($this->db);
6033 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
6034 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
6035
6036 if ($result >= 0) {
6037 if ($extrafields) {
6038 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6039 $movementstock->array_options = $array_options;
6040 $movementstock->insertExtraFields();
6041 }
6042 $this->db->commit();
6043 return 1;
6044 } else {
6045 $this->error = $movementstock->error;
6046 $this->errors = $movementstock->errors;
6047
6048 $this->db->rollback();
6049 return -1;
6050 }
6051 }
6052
6053 return -1;
6054 }
6055
6056 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6077 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)
6078 {
6079 // phpcs:enable
6080 if ($id_entrepot) {
6081 $this->db->begin();
6082
6083 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6084
6085 if ($nbpiece < 0) {
6086 if (!$movement) {
6087 $movement = 1;
6088 }
6089 $nbpiece = abs($nbpiece);
6090 }
6091
6092 $op = array();
6093 $op[0] = "+".trim((string) $nbpiece);
6094 $op[1] = "-".trim((string) $nbpiece);
6095
6096 $movementstock = new MouvementStock($this->db);
6097 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
6098 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
6099
6100 if ($result >= 0) {
6101 if ($extrafields) {
6102 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6103 $movementstock->array_options = $array_options;
6104 $movementstock->insertExtraFields();
6105 }
6106 $this->db->commit();
6107 return 1;
6108 } else {
6109 $this->error = $movementstock->error;
6110 $this->errors = $movementstock->errors;
6111
6112 $this->db->rollback();
6113 return -1;
6114 }
6115 }
6116 return -1;
6117 }
6118
6119 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6132 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
6133 {
6134 // phpcs:enable
6135 $this->stock_reel = 0;
6136 $this->stock_warehouse = array();
6137 $this->stock_theorique = 0;
6138
6139 // Set filter on warehouse status
6140 $warehouseStatus = array();
6141 if (preg_match('/warehouseclosed/', $option)) {
6143 }
6144 if (preg_match('/warehouseopen/', $option)) {
6146 }
6147 if (preg_match('/warehouseinternal/', $option)) {
6148 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
6150 } else {
6152 }
6153 }
6154
6155 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
6156 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
6157 $sql .= ", ".$this->db->prefix()."entrepot as w";
6158 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
6159 $sql .= " AND w.rowid = ps.fk_entrepot";
6160 $sql .= " AND ps.fk_product = ".((int) $this->id);
6161 if (count($warehouseStatus)) {
6162 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
6163 }
6164
6165 $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;
6166
6167 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
6168 $result = $this->db->query($sql);
6169 if ($result) {
6170 $num = $this->db->num_rows($result);
6171 $i = 0;
6172 if ($num > 0) {
6173 while ($i < $num) {
6174 $row = $this->db->fetch_object($result);
6175 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
6176 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
6177 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
6178 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
6179 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
6180 }
6181 $this->stock_reel += $row->reel;
6182 $i++;
6183 }
6184 $this->stock_reel = (float) price2num($this->stock_reel, 'MS');
6185 }
6186 $this->db->free($result);
6187
6188 if (!preg_match('/novirtual/', $option)) {
6189 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
6190 }
6191
6192 return 1;
6193 } else {
6194 $this->error = $this->db->lasterror();
6195 return -1;
6196 }
6197 }
6198
6199
6200 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6210 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
6211 {
6212 // phpcs:enable
6213 global $hookmanager, $action;
6214
6215 $stock_commande_client = 0;
6216 $stock_commande_fournisseur = 0;
6217 $stock_sending_client = 0;
6218 $stock_reception_fournisseur = 0;
6219 $stock_inproduction = 0;
6220
6221 //dol_syslog("load_virtual_stock");
6222
6223 if (isModEnabled('order')) {
6224 $result = $this->load_stats_commande(0, '1,2', 1);
6225 if ($result < 0) {
6226 dol_print_error($this->db, $this->error);
6227 }
6228 $stock_commande_client = $this->stats_commande['qty'];
6229 }
6230 if (isModEnabled("shipping")) {
6231 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
6232 $filterShipmentStatus = '';
6233 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
6234 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
6235 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6236 $filterShipmentStatus = Expedition::STATUS_CLOSED;
6237 }
6238 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
6239 if ($result < 0) {
6240 dol_print_error($this->db, $this->error);
6241 }
6242 $stock_sending_client = $this->stats_expedition['qty'];
6243 }
6244 // Include supplier order lines
6245 if (isModEnabled("supplier_order")) {
6246 $filterStatus = getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK', '3,4');
6247 if (isset($includedraftpoforvirtual)) {
6248 $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
6249 }
6250 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
6251 if ($result < 0) {
6252 dol_print_error($this->db, $this->error);
6253 }
6254 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
6255 }
6256 // Include reception lines
6257 if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) {
6258 $filterStatus = '4';
6259 if (isset($includedraftpoforvirtual)) {
6260 $filterStatus = '0,'.$filterStatus;
6261 }
6262 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
6263 if ($result < 0) {
6264 dol_print_error($this->db, $this->error);
6265 }
6266 $stock_reception_fournisseur = $this->stats_reception['qty'];
6267 }
6268 // Include manufacturing
6269 if (isModEnabled('mrp')) {
6270 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
6271 if ($result < 0) {
6272 dol_print_error($this->db, $this->error);
6273 }
6274 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
6275 }
6276
6277 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
6278
6279 // Stock decrease mode
6280 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6281 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6282 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
6283 $this->stock_theorique += 0;
6284 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
6285 $this->stock_theorique -= $stock_commande_client;
6286 }
6287 // Stock Increase mode
6288 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
6289 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6290 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
6291 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6292 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
6293 $this->stock_theorique -= $stock_reception_fournisseur;
6294 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
6295 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6296 }
6297
6298 $parameters = array('id' => $this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
6299 // Note that $action and $object may have been modified by some hooks
6300 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
6301 if ($reshook > 0) {
6302 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
6303 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
6304 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
6305 }
6306
6307 //Virtual Stock by Warehouse
6308 if (!empty($this->stock_warehouse) && getDolGlobalString('STOCK_ALLOW_VIRTUAL_STOCK_PER_WAREHOUSE')) {
6309 foreach ($this->stock_warehouse as $warehouseid => $stockwarehouse) {
6310 if (isModEnabled('mrp')) {
6311 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock, $warehouseid);
6312 if ($result < 0) {
6313 dol_print_error($this->db, $this->error);
6314 }
6315 }
6316
6317 if ($this->fk_default_warehouse == $warehouseid) {
6318 $this->stock_warehouse[$warehouseid]->virtual = $this->stock_warehouse[$warehouseid]->real + $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] + $this->stats_commande_fournisseur['qty'] - ($this->stats_commande['qty'] + $this->stats_mrptoconsume['qty']);
6319 } else {
6320 $this->stock_warehouse[$warehouseid]->virtual = $this->stock_warehouse[$warehouseid]->real + $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'];
6321 }
6322 }
6323 }
6324
6325 return 1;
6326 }
6327
6328
6336 public function loadBatchInfo($batch)
6337 {
6338 $result = array();
6339
6340 $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";
6341 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
6342 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
6343 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
6344 $resql = $this->db->query($sql);
6345 if ($resql) {
6346 $num = $this->db->num_rows($resql);
6347 $i = 0;
6348 while ($i < $num) {
6349 $obj = $this->db->fetch_object($resql);
6350 $result[] = array('batch' => $batch, 'eatby' => $this->db->jdate($obj->eatby), 'sellby' => $this->db->jdate($obj->sellby), 'qty' => $obj->qty);
6351 $i++;
6352 }
6353 return $result;
6354 } else {
6355 dol_print_error($this->db);
6356 $this->db->rollback();
6357 return array();
6358 }
6359 }
6360
6361 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6370 public function add_photo($sdir, $file)
6371 {
6372 // phpcs:enable
6373 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6374
6375 $result = 0;
6376
6377 $dir = $sdir;
6378 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6379 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
6380 } else {
6381 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
6382 }
6383
6384 dol_mkdir($dir);
6385
6386 $dir_osencoded = $dir;
6387
6388 if (is_dir($dir_osencoded)) {
6389 $originImage = $dir.'/'.$file['name'];
6390
6391 // Cree fichier en taille origine
6392 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
6393
6394 if (file_exists(dol_osencode($originImage))) {
6395 // Create thumbs
6396 $this->addThumbs($originImage);
6397 }
6398 }
6399
6400 if (is_numeric($result) && $result > 0) {
6401 return 1;
6402 } else {
6403 return -1;
6404 }
6405 }
6406
6407 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6414 public function is_photo_available($sdir)
6415 {
6416 // phpcs:enable
6417 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6418 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6419
6420 $dir = $sdir;
6421 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6422 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
6423 } else {
6424 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
6425 }
6426
6427 $dir_osencoded = dol_osencode($dir);
6428 if (file_exists($dir_osencoded)) {
6429 $handle = opendir($dir_osencoded);
6430 if (is_resource($handle)) {
6431 while (($file = readdir($handle)) !== false) {
6432 if (!utf8_check($file)) {
6433 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6434 }
6435 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6436 return true;
6437 }
6438 }
6439 }
6440 }
6441
6442 return false;
6443 }
6444
6445 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6453 public function liste_photos($dir, $nbmax = 0)
6454 {
6455 // phpcs:enable
6456 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6457 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6458
6459 $nbphoto = 0;
6460 $tabobj = array();
6461
6462 $dir_osencoded = dol_osencode($dir);
6463 $handle = @opendir($dir_osencoded);
6464 if (is_resource($handle)) {
6465 while (($file = readdir($handle)) !== false) {
6466 if (!utf8_check($file)) {
6467 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6468 }
6469 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6470 $nbphoto++;
6471
6472 // We forge name of thumb.
6473 $photo = $file;
6474 $photo_vignette = '';
6475 $regs = array();
6476 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6477 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6478 }
6479
6480 $dirthumb = $dir.'thumbs/';
6481
6482 // Object
6483 $obj = array();
6484 $obj['photo'] = $photo;
6485 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6486 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6487 } else {
6488 $obj['photo_vignette'] = "";
6489 }
6490
6491 $tabobj[$nbphoto - 1] = $obj;
6492
6493 // Do we have to continue with next photo ?
6494 if ($nbmax && $nbphoto >= $nbmax) {
6495 break;
6496 }
6497 }
6498 }
6499
6500 closedir($handle);
6501 }
6502
6503 return $tabobj;
6504 }
6505
6506 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6513 public function delete_photo($file)
6514 {
6515 // phpcs:enable
6516 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6517 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6518
6519 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6520 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6521 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6522
6523 // On efface l'image d'origine
6524 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6525
6526 // Si elle existe, on efface la vignette
6527 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6528 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6529 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6530 dol_delete_file($dirthumb.$photo_vignette);
6531 }
6532
6533 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6534 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6535 dol_delete_file($dirthumb.$photo_vignette);
6536 }
6537 }
6538 }
6539
6540 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6547 public function get_image_size($file)
6548 {
6549 // phpcs:enable
6550 $file_osencoded = dol_osencode($file);
6551 $infoImg = getimagesize($file_osencoded); // Get information on image
6552 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6553 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6554 }
6555
6561 public function loadStateBoard()
6562 {
6563 global $hookmanager;
6564
6565 $this->nb = array();
6566
6567 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6568 $sql .= " FROM ".$this->db->prefix()."product as p";
6569 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6570 // Add where from hooks
6571 if (is_object($hookmanager)) {
6572 $parameters = array();
6573 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6574 $sql .= $hookmanager->resPrint;
6575 }
6576 $sql .= ' GROUP BY fk_product_type';
6577
6578 $resql = $this->db->query($sql);
6579 if ($resql) {
6580 while ($obj = $this->db->fetch_object($resql)) {
6581 if ($obj->fk_product_type == 1) {
6582 $this->nb["services"] = $obj->nb;
6583 } else {
6584 $this->nb["products"] = $obj->nb;
6585 }
6586 }
6587 $this->db->free($resql);
6588 return 1;
6589 } else {
6590 dol_print_error($this->db);
6591 $this->error = $this->db->error();
6592 return -1;
6593 }
6594 }
6595
6601 public function isProduct()
6602 {
6603 return $this->type == Product::TYPE_PRODUCT;
6604 }
6605
6611 public function isService()
6612 {
6613 return $this->type == Product::TYPE_SERVICE;
6614 }
6615
6621 public function isStockManaged()
6622 {
6623 return ($this->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'));
6624 }
6625
6631 public function isMandatoryPeriod()
6632 {
6633 return $this->mandatory_period == 1;
6634 }
6635
6641 public function hasbatch()
6642 {
6643 return $this->status_batch > 0;
6644 }
6645
6646
6647 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6656 public function get_barcode($object, $type = '')
6657 {
6658 // phpcs:enable
6659 global $conf;
6660
6661 $result = '';
6662 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6663 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6664 foreach ($dirsociete as $dirroot) {
6665 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6666 if ($res) {
6667 break;
6668 }
6669 }
6670 $var = getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM');
6671 $mod = new $var();
6672 '@phan-var-force ModeleNumRefBarCode $mod';
6673
6674 $result = $mod->getNextValue($object, $type);
6675
6676 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6677 }
6678 return $result;
6679 }
6680
6688 public function initAsSpecimen()
6689 {
6690 $now = dol_now();
6691
6692 // Initialize parameters
6693 $this->specimen = 1;
6694 $this->id = 0;
6695 $this->ref = 'PRODUCT_SPEC';
6696 $this->label = 'PRODUCT SPECIMEN';
6697 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6698 $this->specimen = 1;
6699 $this->country_id = 1;
6700 $this->status = 1;
6701 $this->status_buy = 1;
6702 $this->tobatch = 0;
6703 $this->sell_or_eat_by_mandatory = 0;
6704 $this->note_private = 'This is a comment (private)';
6705 $this->note_public = 'This is a comment (public)';
6706 $this->date_creation = $now;
6707 $this->date_modification = $now;
6708
6709 $this->weight = 4;
6710 $this->weight_units = 3;
6711
6712 $this->length = 5;
6713 $this->length_units = 1;
6714 $this->width = 6;
6715 $this->width_units = 0;
6716 $this->height = null;
6717 $this->height_units = null;
6718
6719 $this->surface = 30;
6720 $this->surface_units = 0;
6721 $this->volume = 300;
6722 $this->volume_units = 0;
6723
6724 $this->barcode = -1; // Create barcode automatically
6725
6726 return 1;
6727 }
6728
6735 public function getLabelOfUnit($type = 'long')
6736 {
6737 global $langs;
6738
6739 if (!$this->fk_unit) {
6740 return '';
6741 }
6742
6743 $langs->load('products');
6744 $label = '';
6745 $label_type = 'label';
6746 if ($type == 'short') {
6747 $label_type = 'short_label';
6748 }
6749
6750 $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6751
6752 $resql = $this->db->query($sql);
6753 if (!$resql) {
6754 $this->error = $this->db->error();
6755 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6756 return -1;
6757 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
6758 $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6759 }
6760 $this->db->free($resql);
6761
6762 return $label;
6763 }
6764
6765 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6771 public function min_recommended_price()
6772 {
6773 // phpcs:enable
6774 $maxpricesupplier = 0;
6775
6776 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
6777 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6778 $product_fourn = new ProductFournisseur($this->db);
6779 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6780
6781 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6782 foreach ($product_fourn_list as $productfourn) {
6783 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6784 $maxpricesupplier = $productfourn->fourn_unitprice;
6785 }
6786 }
6787
6788 $maxpricesupplier *= getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE');
6789 }
6790 }
6791
6792 return $maxpricesupplier;
6793 }
6794
6795
6806 public function setCategories($categories)
6807 {
6808 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6809 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6810 }
6811
6820 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6821 {
6822 $tables = array(
6823 'product_customer_price',
6824 'product_customer_price_log'
6825 );
6826
6827 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6828 }
6829
6841 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6842 {
6843 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6844 $query = $this->db->query($sql);
6845
6846 $rules = array();
6847
6848 while ($result = $this->db->fetch_object($query)) {
6849 $rules[$result->level] = $result;
6850 }
6851
6852 //Because prices can be based on other level's prices, we temporarily store them
6853 $prices = array(
6854 1 => $baseprice
6855 );
6856
6857 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
6858 for ($i = 1; $i <= $nbofproducts; $i++) {
6859 $price = $baseprice;
6860 $price_min = $baseprice;
6861
6862 //We have to make sure it does exist and it is > 0
6863 //First price level only allows changing min_price
6864 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6865 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6866 }
6867
6868 $prices[$i] = $price;
6869
6870 //We have to make sure it does exist and it is > 0
6871 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6872 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6873 }
6874
6875 //Little check to make sure the price is modified before triggering generation
6876 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6877 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6878
6879 if ($check_amount && $check_type) {
6880 continue;
6881 }
6882
6883 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, 1) < 0) {
6884 return -1;
6885 }
6886 }
6887
6888 return 1;
6889 }
6890
6896 public function getRights()
6897 {
6898 global $user;
6899
6900 if ($this->isProduct()) {
6901 return $user->rights->produit;
6902 } else {
6903 return $user->rights->service;
6904 }
6905 }
6906
6913 public function info($id)
6914 {
6915 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6916 $sql .= " p.fk_user_author, p.fk_user_modif";
6917 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6918 $sql .= " WHERE p.rowid = ".((int) $id);
6919
6920 $result = $this->db->query($sql);
6921 if ($result) {
6922 if ($this->db->num_rows($result)) {
6923 $obj = $this->db->fetch_object($result);
6924
6925 $this->id = $obj->rowid;
6926 $this->ref = $obj->ref;
6927
6928 $this->user_creation_id = $obj->fk_user_author;
6929 $this->user_modification_id = $obj->fk_user_modif;
6930
6931 $this->date_creation = $this->db->jdate($obj->date_creation);
6932 $this->date_modification = $this->db->jdate($obj->date_modification);
6933 }
6934
6935 $this->db->free($result);
6936 } else {
6937 dol_print_error($this->db);
6938 }
6939 }
6940
6941
6947 public function getProductDurationHours()
6948 {
6949 if (empty($this->duration_value)) {
6950 $this->errors[] = 'ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6951 return -1;
6952 }
6953 if ($this->duration_unit == 's') {
6954 $prodDurationHours = 1. / 3600;
6955 } elseif ($this->duration_unit == 'i' || $this->duration_unit == 'mn' || $this->duration_unit == 'min') {
6956 $prodDurationHours = 1. / 60;
6957 } elseif ($this->duration_unit == 'h') {
6958 $prodDurationHours = 1.;
6959 } elseif ($this->duration_unit == 'd') {
6960 $prodDurationHours = 24.;
6961 } elseif ($this->duration_unit == 'w') {
6962 $prodDurationHours = 24. * 7;
6963 } elseif ($this->duration_unit == 'm') {
6964 $prodDurationHours = 24. * 30;
6965 } elseif ($this->duration_unit == 'y') {
6966 $prodDurationHours = 24. * 365;
6967 } else {
6968 $prodDurationHours = 0.0;
6969 }
6970 $prodDurationHours *= $this->duration_value;
6971
6972 return $prodDurationHours;
6973 }
6974
6975
6983 public function getKanbanView($option = '', $arraydata = null)
6984 {
6985 global $langs, $conf;
6986
6987 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6988
6989 $return = '<div class="box-flex-item box-flex-grow-zero">';
6990 $return .= '<div class="info-box info-box-sm">';
6991 $return .= '<div class="info-box-img">';
6992 $label = '';
6993 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
6994 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
6995 $return .= $label;
6996 } else {
6997 if ($this->isProduct()) {
6998 $label .= img_p