dolibarr 21.0.3
product.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2001-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2014 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2005-2015 Regis Houssin <regis.houssin@inodbox.com>
5 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
6 * Copyright (C) 2007-2011 Jean Heimburger <jean@tiaris.info>
7 * Copyright (C) 2010-2018 Juanjo Menent <jmenent@2byte.es>
8 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
9 * Copyright (C) 2013-2014 Cedric GROSS <c.gross@kreiz-it.fr>
10 * Copyright (C) 2013-2016 Marcos García <marcosgdf@gmail.com>
11 * Copyright (C) 2011-2021 Open-DSI <support@open-dsi.fr>
12 * Copyright (C) 2014 Henry Florian <florian.henry@open-concept.pro>
13 * Copyright (C) 2014-2016 Philippe Grand <philippe.grand@atoo-net.com>
14 * Copyright (C) 2014 Ion agorria <ion@agorria.com>
15 * Copyright (C) 2016-2024 Ferran Marcet <fmarcet@2byte.es>
16 * Copyright (C) 2017 Gustavo Novaro
17 * Copyright (C) 2019-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', 'enabled' => 'isModEnabled("mrp")'),
86 'bom_bom' => array('name' => 'BOM', 'enabled' => 'isModEnabled("bom")'),
87 'bom_bomline' => array('name' => 'BOMLine', 'parent' => 'bom_bom', 'parentkey' => 'fk_bom', 'enabled' => 'isModEnabled("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
374 public $pmp;
375
381 public $seuil_stock_alerte = 0;
382
386 public $desiredstock = 0;
387
391 public $duration_value;
395 public $duration_unit;
399 public $duration;
400
404 public $fk_default_workstation;
405
411 public $status = 0;
412
419 public $tosell;
420
426 public $status_buy = 0;
427
434 public $tobuy;
435
441 public $finished;
442
448 public $fk_default_bom;
449
455 public $product_fourn_price_id;
456
462 public $buyprice;
463
469 public $tobatch;
470
471
477 public $status_batch = 0;
478
484 public $sell_or_eat_by_mandatory = 0;
485
491 public $batch_mask = '';
492
498 public $customcode;
499
505 public $url;
506
508
511 public $weight;
512
516 public $weight_units; // scale -3, 0, 3, 6
520 public $length;
524 public $length_units; // scale -3, 0, 3, 6
528 public $width;
532 public $width_units; // scale -3, 0, 3, 6
536 public $height;
540 public $height_units; // scale -3, 0, 3, 6
544 public $surface;
548 public $surface_units; // scale -3, 0, 3, 6
552 public $volume;
556 public $volume_units; // scale -3, 0, 3, 6
557
561 public $net_measure;
565 public $net_measure_units; // scale -3, 0, 3, 6
566
570 public $accountancy_code_sell;
574 public $accountancy_code_sell_intra;
578 public $accountancy_code_sell_export;
582 public $accountancy_code_buy;
586 public $accountancy_code_buy_intra;
590 public $accountancy_code_buy_export;
591
595 public $barcode;
596
600 public $barcode_type;
601
605 public $barcode_type_code;
606
610 public $stats_propale = array();
611
615 public $stats_commande = array();
616
620 public $stats_contrat = array();
621
625 public $stats_facture = array();
626
630 public $stats_proposal_supplier = array();
631
635 public $stats_commande_fournisseur = array();
636
640 public $stats_expedition = array();
641
645 public $stats_reception = array();
646
650 public $stats_mo = array();
651
655 public $stats_bom = array();
656
660 public $stats_mrptoconsume = array();
661
665 public $stats_mrptoproduce = array();
666
670 public $stats_facturerec = array();
671
675 public $stats_facture_fournisseur = array();
676
680 public $imgWidth;
684 public $imgHeight;
685
690 public $product_fourn_id;
691
696 public $product_id_already_linked;
697
702 public $nbphoto = 0;
703
707 public $stock_warehouse = array();
708
712 public $fk_default_warehouse;
713
717 public $fk_price_expression;
718
723 public $fourn_qty;
724
729 public $fourn_pu;
730
735 public $fourn_price_base_type;
736
740 public $fourn_socid;
741
747 public $ref_fourn;
748
752 public $ref_supplier;
753
759 public $fk_unit;
760
766 public $price_autogen = 0;
767
773 public $supplierprices;
774
780 public $sousprods = array();
781
785 public $res;
786
787
793 public $is_object_used;
794
804 public $is_sousproduit_qty;
805
816 public $is_sousproduit_incdec;
817
821 public $mandatory_period;
822
823
852 public $fields = array(
853 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'index' => 1, 'position' => 1, 'comment' => 'Id'),
854 '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'),
855 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'notnull' => 1, 'index' => 1, 'position' => 5),
856 'label' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'showoncombobox' => 2, 'position' => 15, 'csslist' => 'tdoverflowmax250'),
857 'barcode' => array('type' => 'varchar(255)', 'label' => 'Barcode', 'enabled' => 'isModEnabled("barcode")', 'position' => 20, 'visible' => -1, 'showoncombobox' => 3, 'cssview' => 'tdwordbreak', 'csslist' => 'tdoverflowmax125'),
858 'fk_barcode_type' => array('type' => 'integer', 'label' => 'BarcodeType', 'enabled' => 1, 'position' => 21, 'notnull' => 0, 'visible' => -1,),
859 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 61),
860 'note' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 62),
861 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 500),
862 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 501),
863 //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
864 'fk_user_author' => array('type' => 'integer', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 510, 'foreignkey' => 'llx_user.rowid'),
865 'fk_user_modif' => array('type' => 'integer', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 511),
866 //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
867 'localtax1_tx' => array('type' => 'double(6,3)', 'label' => 'Localtax1tx', 'enabled' => 1, 'position' => 150, 'notnull' => 0, 'visible' => -1,),
868 'localtax1_type' => array('type' => 'varchar(10)', 'label' => 'Localtax1type', 'enabled' => 1, 'position' => 155, 'notnull' => 1, 'visible' => -1,),
869 'localtax2_tx' => array('type' => 'double(6,3)', 'label' => 'Localtax2tx', 'enabled' => 1, 'position' => 160, 'notnull' => 0, 'visible' => -1,),
870 'localtax2_type' => array('type' => 'varchar(10)', 'label' => 'Localtax2type', 'enabled' => 1, 'position' => 165, 'notnull' => 1, 'visible' => -1,),
871 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 170),
872 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'index' => 0, 'position' => 1000),
873 //'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')),
874 //'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')),
875 'mandatory_period' => array('type' => 'integer', 'label' => 'mandatoryperiod', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'default' => '0', 'index' => 1, 'position' => 1000),
876 );
877
881 const TYPE_PRODUCT = 0;
885 const TYPE_SERVICE = 1;
886
892 public function __construct($db)
893 {
894 $this->db = $db;
895
896 $this->ismultientitymanaged = 1;
897 $this->isextrafieldmanaged = 1;
898
899 $this->canvas = '';
900 }
901
907 public function check()
908 {
909 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
910 $this->ref = trim($this->ref);
911 } else {
912 $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
913 }
914
915 $err = 0;
916 if (dol_strlen(trim($this->ref)) == 0) {
917 $err++;
918 }
919
920 if (dol_strlen(trim($this->label)) == 0) {
921 $err++;
922 }
923
924 if ($err > 0) {
925 return 0;
926 } else {
927 return 1;
928 }
929 }
930
938 public function create($user, $notrigger = 0)
939 {
940 global $conf, $langs;
941
942 $error = 0;
943
944 // Clean parameters
945 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
946 $this->ref = trim($this->ref);
947 } else {
948 $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
949 }
950 $this->label = trim($this->label);
951 $this->price_ttc = (float) price2num($this->price_ttc);
952 $this->price = (float) price2num($this->price);
953 $this->price_min_ttc = (float) price2num($this->price_min_ttc);
954 $this->price_min = (float) price2num($this->price_min);
955 $this->price_label = trim($this->price_label);
956 if (empty($this->tva_tx)) {
957 $this->tva_tx = 0;
958 }
959 if (empty($this->tva_npr)) {
960 $this->tva_npr = 0;
961 }
962 //Local taxes
963 if (empty($this->localtax1_tx)) {
964 $this->localtax1_tx = 0;
965 }
966 if (empty($this->localtax2_tx)) {
967 $this->localtax2_tx = 0;
968 }
969 if (empty($this->localtax1_type)) {
970 $this->localtax1_type = '0';
971 }
972 if (empty($this->localtax2_type)) {
973 $this->localtax2_type = '0';
974 }
975 if (empty($this->price)) {
976 $this->price = 0;
977 }
978 if (empty($this->price_min)) {
979 $this->price_min = 0;
980 }
981 // Price by quantity
982 if (empty($this->price_by_qty)) {
983 $this->price_by_qty = 0;
984 }
985
986 if (empty($this->status)) {
987 $this->status = 0;
988 }
989 if (empty($this->status_buy)) {
990 $this->status_buy = 0;
991 }
992
993 $price_ht = 0;
994 $price_ttc = 0;
995 $price_min_ht = 0;
996 $price_min_ttc = 0;
997
998 //
999 if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
1000 $price_ttc = price2num($this->price_ttc, 'MU');
1001 $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
1002 }
1003
1004 //
1005 if ($this->price_base_type != 'TTC' && $this->price > 0) {
1006 $price_ht = price2num($this->price, 'MU');
1007 $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
1008 }
1009
1010 //
1011 if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
1012 $price_min_ttc = price2num($this->price_min_ttc, 'MU');
1013 $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
1014 }
1015
1016 //
1017 if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
1018 $price_min_ht = price2num($this->price_min, 'MU');
1019 $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
1020 }
1021
1022 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1023 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
1024 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1025 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1026 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1027 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1028
1029 // Barcode value
1030 $this->barcode = trim($this->barcode);
1031 $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
1032 // Check parameters
1033 if (empty($this->label)) {
1034 $this->error = 'ErrorMandatoryParametersNotProvided';
1035 return -1;
1036 }
1037
1038 if (empty($this->ref) || $this->ref == 'auto') {
1039 // Load object modCodeProduct
1040 $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
1041 if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
1042 if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
1043 $module = substr($module, 0, dol_strlen($module) - 4);
1044 }
1045 dol_include_once('/core/modules/product/'.$module.'.php');
1046 $modCodeProduct = new $module();
1047 '@phan-var-force ModeleProductCode $modCodeProduct';
1048 if (!empty($modCodeProduct->code_auto)) {
1049 $this->ref = $modCodeProduct->getNextValue($this, $this->type);
1050 }
1051 unset($modCodeProduct);
1052 }
1053
1054 if (empty($this->ref)) {
1055 $this->error = 'ProductModuleNotSetupForAutoRef';
1056 return -2;
1057 }
1058 }
1059
1060 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);
1061
1062 $now = dol_now();
1063
1064 if (empty($this->date_creation)) {
1065 $this->date_creation = $now;
1066 }
1067
1068 $this->db->begin();
1069
1070 // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
1071 if ($this->barcode == '-1' || $this->barcode == 'auto') {
1072 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1073 }
1074
1075 // Check more parameters
1076 // If error, this->errors[] is filled
1077 $result = $this->verify();
1078
1079 if ($result >= 0) {
1080 $sql = "SELECT count(*) as nb";
1081 $sql .= " FROM ".$this->db->prefix()."product";
1082 $sql .= " WHERE entity IN (".getEntity('product').")";
1083 $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
1084
1085 $result = $this->db->query($sql);
1086 if ($result) {
1087 $obj = $this->db->fetch_object($result);
1088 if ($obj->nb == 0) {
1089 // Insert new product, no previous one found
1090 $sql = "INSERT INTO ".$this->db->prefix()."product (";
1091 $sql .= "datec";
1092 $sql .= ", entity";
1093 $sql .= ", ref";
1094 $sql .= ", ref_ext";
1095 $sql .= ", price_min";
1096 $sql .= ", price_min_ttc";
1097 $sql .= ", label";
1098 $sql .= ", fk_user_author";
1099 $sql .= ", fk_product_type";
1100 $sql .= ", price";
1101 $sql .= ", price_ttc";
1102 $sql .= ", price_base_type";
1103 $sql .= ", price_label";
1104 $sql .= ", tobuy";
1105 $sql .= ", tosell";
1106 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1107 $sql .= ", accountancy_code_buy";
1108 $sql .= ", accountancy_code_buy_intra";
1109 $sql .= ", accountancy_code_buy_export";
1110 $sql .= ", accountancy_code_sell";
1111 $sql .= ", accountancy_code_sell_intra";
1112 $sql .= ", accountancy_code_sell_export";
1113 }
1114 $sql .= ", canvas";
1115 $sql .= ", finished";
1116 $sql .= ", tobatch";
1117 $sql .= ", sell_or_eat_by_mandatory";
1118 $sql .= ", batch_mask";
1119 $sql .= ", fk_unit";
1120 $sql .= ", mandatory_period";
1121 $sql .= ") VALUES (";
1122 $sql .= "'".$this->db->idate($this->date_creation)."'";
1123 $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
1124 $sql .= ", '".$this->db->escape($this->ref)."'";
1125 $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1126 $sql .= ", ".price2num($price_min_ht);
1127 $sql .= ", ".price2num($price_min_ttc);
1128 $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
1129 $sql .= ", ".((int) $user->id);
1130 $sql .= ", ".((int) $this->type);
1131 $sql .= ", ".price2num($price_ht, 'MT');
1132 $sql .= ", ".price2num($price_ttc, 'MT');
1133 $sql .= ", '".$this->db->escape($this->price_base_type)."'";
1134 $sql .= ", ".(!empty($this->price_label) ? "'".$this->db->escape($this->price_label)."'" : "null");
1135 $sql .= ", ".((int) $this->status);
1136 $sql .= ", ".((int) $this->status_buy);
1137 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1138 $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
1139 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
1140 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
1141 $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
1142 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
1143 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
1144 }
1145 $sql .= ", '".$this->db->escape($this->canvas)."'";
1146 $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
1147 $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
1148 $sql .= ", ".((empty($this->sell_or_eat_by_mandatory) || $this->sell_or_eat_by_mandatory < 0) ? 0 : ((int) $this->sell_or_eat_by_mandatory));
1149 $sql .= ", '".$this->db->escape($this->batch_mask)."'";
1150 $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
1151 $sql .= ", '".$this->db->escape($this->mandatory_period)."'";
1152 $sql .= ")";
1153
1154 dol_syslog(get_class($this)."::Create", LOG_DEBUG);
1155
1156 $result = $this->db->query($sql);
1157 if ($result) {
1158 $id = $this->db->last_insert_id($this->db->prefix()."product");
1159
1160 if ($id > 0) {
1161 $this->id = $id;
1162 $this->price = $price_ht;
1163 $this->price_ttc = $price_ttc;
1164 $this->price_min = $price_min_ht;
1165 $this->price_min_ttc = $price_min_ttc;
1166
1167 $result = $this->_log_price($user);
1168 if ($result > 0) {
1169 if ($this->update($id, $user, 1, 'add') <= 0) {
1170 $error++;
1171 }
1172 } else {
1173 $error++;
1174 $this->error = $this->db->lasterror();
1175 }
1176
1177 // update accountancy for this entity
1178 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1179 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1180
1181 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1182 $sql .= " fk_product";
1183 $sql .= ", entity";
1184 $sql .= ", accountancy_code_buy";
1185 $sql .= ", accountancy_code_buy_intra";
1186 $sql .= ", accountancy_code_buy_export";
1187 $sql .= ", accountancy_code_sell";
1188 $sql .= ", accountancy_code_sell_intra";
1189 $sql .= ", accountancy_code_sell_export";
1190 $sql .= ") VALUES (";
1191 $sql .= $this->id;
1192 $sql .= ", " . ((int) $conf->entity);
1193 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1194 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1195 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1196 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1197 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1198 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1199 $sql .= ")";
1200 $result = $this->db->query($sql);
1201 if (!$result) {
1202 $error++;
1203 $this->error = 'ErrorFailedToInsertAccountancyForEntity';
1204 }
1205 }
1206 } else {
1207 $error++;
1208 $this->error = 'ErrorFailedToGetInsertedId';
1209 }
1210 } else {
1211 $error++;
1212 $this->error = $this->db->lasterror();
1213 }
1214 } else {
1215 // Product already exists with this ref
1216 $langs->load("products");
1217 $error++;
1218 $this->error = "ErrorProductAlreadyExists";
1219 dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
1220 }
1221 } else {
1222 $error++;
1223 $this->error = $this->db->lasterror();
1224 }
1225
1226 if (!$error && !$notrigger) {
1227 // Call trigger
1228 $result = $this->call_trigger('PRODUCT_CREATE', $user);
1229 if ($result < 0) {
1230 $error++;
1231 }
1232 // End call triggers
1233 }
1234
1235 if (!$error) {
1236 $this->db->commit();
1237 return $this->id;
1238 } else {
1239 $this->db->rollback();
1240 return -$error;
1241 }
1242 } else {
1243 $this->db->rollback();
1244 dol_syslog(get_class($this)."::Create fails verify ".implode(',', $this->errors), LOG_WARNING);
1245 return -3;
1246 }
1247 }
1248
1249
1256 public function verify()
1257 {
1258 global $langs;
1259
1260 $this->errors = array();
1261
1262 $result = 0;
1263 $this->ref = trim($this->ref);
1264
1265 if (!$this->ref) {
1266 $this->errors[] = 'ErrorBadRef';
1267 $result = -2;
1268 }
1269
1270 $arrayofnonnegativevalue = array('weight' => 'Weight', 'width' => 'Width', 'height' => 'Height', 'length' => 'Length', 'surface' => 'Surface', 'volume' => 'Volume');
1271 foreach ($arrayofnonnegativevalue as $key => $value) {
1272 if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
1273 $langs->loadLangs(array("main", "other"));
1274 $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
1275 $this->errors[] = $this->error;
1276 $result = -4;
1277 }
1278 }
1279
1280 $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
1281 if ($rescode) {
1282 if ($rescode == -1) {
1283 $this->errors[] = 'ErrorBadBarCodeSyntax';
1284 } elseif ($rescode == -2) {
1285 $this->errors[] = 'ErrorBarCodeRequired';
1286 } elseif ($rescode == -3) {
1287 // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
1288 $this->errors[] = 'ErrorBarCodeAlreadyUsed';
1289 }
1290
1291 $result = -3;
1292 }
1293
1294 return $result;
1295 }
1296
1297 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1308 public function check_barcode($valuetotest, $typefortest)
1309 {
1310 // phpcs:enable
1311 global $conf;
1312
1313 if (isModEnabled('barcode') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
1314 $module = strtolower(getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM'));
1315
1316 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1317 foreach ($dirsociete as $dirroot) {
1318 $res = dol_include_once($dirroot.$module.'.php');
1319 if ($res) {
1320 break;
1321 }
1322 }
1323
1324 $mod = new $module();
1325 '@phan-var-force ModeleNumRefBarCode $mod';
1326
1327 dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1328 $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1329 return $result;
1330 } else {
1331 return 0;
1332 }
1333 }
1334
1346 public function update($id, $user, $notrigger = 0, $action = 'update', $updatetype = false)
1347 {
1348 global $langs, $conf, $hookmanager;
1349
1350 $error = 0;
1351
1352 // Check parameters
1353 if (!$this->label) {
1354 $this->label = 'MISSING LABEL';
1355 }
1356
1357 // Clean parameters
1358 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1359 $this->ref = trim($this->ref);
1360 } else {
1361 $this->ref = dol_string_nospecial(trim($this->ref));
1362 }
1363 $this->label = trim($this->label);
1364 $this->description = trim($this->description);
1365 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1366 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1367 $this->net_measure = price2num($this->net_measure);
1368 $this->net_measure_units = (is_null($this->net_measure_units) ? '' : trim((string) $this->net_measure_units));
1369 $this->weight = price2num($this->weight);
1370 $this->weight_units = (is_null($this->weight_units) ? '' : trim((string) $this->weight_units));
1371 $this->length = price2num($this->length);
1372 $this->length_units = (is_null($this->length_units) ? '' : trim((string) $this->length_units));
1373 $this->width = price2num($this->width);
1374 $this->width_units = (is_null($this->width_units) ? '' : trim((string) $this->width_units));
1375 $this->height = price2num($this->height);
1376 $this->height_units = (is_null($this->height_units) ? '' : trim((string) $this->height_units));
1377 $this->surface = price2num($this->surface);
1378 $this->surface_units = (is_null($this->surface_units) ? '' : trim((string) $this->surface_units));
1379 $this->volume = price2num($this->volume);
1380 $this->volume_units = (is_null($this->volume_units) ? '' : trim((string) $this->volume_units));
1381
1382 // set unit not defined
1383 if (is_numeric($this->length_units)) {
1384 $this->width_units = $this->length_units; // Not used yet
1385 }
1386 if (is_numeric($this->length_units)) {
1387 $this->height_units = $this->length_units; // Not used yet
1388 }
1389
1390 // Automated compute surface and volume if not filled
1391 if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1392 $this->surface = (float) $this->length * (float) $this->width;
1393 $this->surface_units = measuring_units_squared((int) $this->length_units);
1394 }
1395 if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1396 $this->volume = $this->surface * (float) $this->height;
1397 $this->volume_units = measuring_units_cubed((int) $this->height_units);
1398 }
1399
1400 if (empty($this->tva_tx)) {
1401 $this->tva_tx = 0;
1402 }
1403 if (empty($this->tva_npr)) {
1404 $this->tva_npr = 0;
1405 }
1406 if (empty($this->localtax1_tx)) {
1407 $this->localtax1_tx = 0;
1408 }
1409 if (empty($this->localtax2_tx)) {
1410 $this->localtax2_tx = 0;
1411 }
1412 if (empty($this->localtax1_type)) {
1413 $this->localtax1_type = '0';
1414 }
1415 if (empty($this->localtax2_type)) {
1416 $this->localtax2_type = '0';
1417 }
1418 if (empty($this->status)) {
1419 $this->status = 0;
1420 }
1421 if (empty($this->status_buy)) {
1422 $this->status_buy = 0;
1423 }
1424
1425 if (empty($this->country_id)) {
1426 $this->country_id = 0;
1427 }
1428
1429 if (empty($this->state_id)) {
1430 $this->state_id = 0;
1431 }
1432
1433 // Barcode value
1434 $this->barcode = (empty($this->barcode) ? '' : trim($this->barcode));
1435
1436 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1437 $this->accountancy_code_buy_intra = (!empty($this->accountancy_code_buy_intra) ? trim($this->accountancy_code_buy_intra) : '');
1438 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1439 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1440 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1441 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1442
1443
1444 $this->db->begin();
1445
1446 $result = 0;
1447 // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1448 if ($action != 'add') {
1449 $result = $this->verify(); // We don't check when update called during a create because verify was already done
1450 } else {
1451 // we can continue
1452 $result = 0;
1453 }
1454
1455 if ($result >= 0) {
1456 // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1457 if (is_null($this->oldcopy) || (is_object($this->oldcopy) && $this->oldcopy->isEmpty())) {
1458 $this->oldcopy = dol_clone($this, 1);
1459 }
1460 // Test if batch management is activated on existing product
1461 // If yes, we create missing entries into product_batch
1462 if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1463 //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1464 $valueforundefinedlot = '000000';
1465 if (getDolGlobalString('STOCK_DEFAULT_BATCH')) {
1466 $valueforundefinedlot = getDolGlobalString('STOCK_DEFAULT_BATCH');
1467 }
1468
1469 dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1470
1471 $this->load_stock();
1472 foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1473 $qty_batch = 0;
1474 foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1475 if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1476 // We discard this line, we will create it later
1477 $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1478 $result = $this->db->query($sqlclean);
1479 if (!$result) {
1480 dol_print_error($this->db);
1481 exit;
1482 }
1483 continue;
1484 }
1485
1486 $qty_batch += $detail->qty;
1487 }
1488 // Quantities in batch details are not same as stock quantity,
1489 // so we add a default batch record to complete and get same qty in parent and child table
1490 if ($ObjW->real != $qty_batch) {
1491 $ObjBatch = new Productbatch($this->db);
1492 $ObjBatch->batch = $valueforundefinedlot;
1493 $ObjBatch->qty = ($ObjW->real - $qty_batch);
1494 $ObjBatch->fk_product_stock = (int) $ObjW->id;
1495
1496 if ($ObjBatch->create($user, 1) < 0) {
1497 $error++;
1498 $this->errors = $ObjBatch->errors;
1499 } else {
1500 // we also add lot record if not exist
1501 $ObjLot = new Productlot($this->db);
1502 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
1503 if ($ObjLot->fetch(0, $this->id, $valueforundefinedlot) == 0) {
1504 $ObjLot->fk_product = $this->id;
1505 $ObjLot->entity = $this->entity;
1506 $ObjLot->fk_user_creat = $user->id;
1507 $ObjLot->batch = $valueforundefinedlot;
1508 if ($ObjLot->create($user, true) < 0) {
1509 $error++;
1510 $this->errors = $ObjLot->errors;
1511 }
1512 }
1513 }
1514 }
1515 }
1516 }
1517
1518 // For automatic creation
1519 if ($this->barcode == -1) {
1520 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1521 }
1522
1523 $sql = "UPDATE ".$this->db->prefix()."product";
1524 $sql .= " SET label = '".$this->db->escape($this->label)."'";
1525
1526 if ($updatetype && ($this->isProduct() || $this->isService())) {
1527 $sql .= ", fk_product_type = ".((int) $this->type);
1528 }
1529
1530 $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1531 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1532 $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1533 $sql .= ", tva_tx = ".((float) $this->tva_tx);
1534 $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1535 $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1536 $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1537 $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1538 $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1539
1540 $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1541 $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
1542
1543 $sql .= ", tosell = ".(int) $this->status;
1544 $sql .= ", tobuy = ".(int) $this->status_buy;
1545 $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1546 $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);
1547 $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1548
1549 $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished === '') ? "null" : (int) $this->finished);
1550 $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1551 $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1552 $sql .= ", net_measure_units = ".($this->net_measure_units != '' ? "'".$this->db->escape($this->net_measure_units)."'" : 'null');
1553 $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1554 $sql .= ", weight_units = ".($this->weight_units != '' ? "'".$this->db->escape($this->weight_units)."'" : 'null');
1555 $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1556 $sql .= ", length_units = ".($this->length_units != '' ? "'".$this->db->escape($this->length_units)."'" : 'null');
1557 $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1558 $sql .= ", width_units = ".($this->width_units != '' ? "'".$this->db->escape($this->width_units)."'" : 'null');
1559 $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1560 $sql .= ", height_units = ".($this->height_units != '' ? "'".$this->db->escape($this->height_units)."'" : 'null');
1561 $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1562 $sql .= ", surface_units = ".($this->surface_units != '' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
1563 $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1564 $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
1565 $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1566 $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1567 $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1568 $sql .= ", description = '".$this->db->escape($this->description)."'";
1569 $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1570 $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1571 $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1572 $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1573 $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1574 $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1575 $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1576 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1577 $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1578 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1579 $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1580 $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1581 $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1582 $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1583 $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1584 $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1585 }
1586 $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1587 $sql .= ", cost_price = ".($this->cost_price != '' ? ((float) $this->cost_price) : 'null');
1588 $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1589 $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1590 $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1591 $sql .= ", fk_user_modif = ".($user->id > 0 ? (int) $user->id : 'NULL');
1592 $sql .= ", mandatory_period = ".((int) $this->mandatory_period);
1593 // stock field is not here because it is a denormalized value from product_stock.
1594 $sql .= " WHERE rowid = ".((int) $id);
1595
1596 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1597
1598 $resql = $this->db->query($sql);
1599 if ($resql) {
1600 $this->id = $id;
1601
1602 // Multilangs
1603 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1604 if ($this->setMultiLangs($user) < 0) {
1605 $this->db->rollback();
1606 return -2;
1607 }
1608 }
1609
1610 $action = 'update';
1611
1612 // update accountancy for this entity
1613 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1614 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1615
1616 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1617 $sql .= " fk_product";
1618 $sql .= ", entity";
1619 $sql .= ", accountancy_code_buy";
1620 $sql .= ", accountancy_code_buy_intra";
1621 $sql .= ", accountancy_code_buy_export";
1622 $sql .= ", accountancy_code_sell";
1623 $sql .= ", accountancy_code_sell_intra";
1624 $sql .= ", accountancy_code_sell_export";
1625 $sql .= ") VALUES (";
1626 $sql .= ((int) $this->id);
1627 $sql .= ", " . ((int) $conf->entity);
1628 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1629 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1630 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1631 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1632 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1633 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1634 $sql .= ")";
1635 $result = $this->db->query($sql);
1636 if (!$result) {
1637 $error++;
1638 $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1639 }
1640 }
1641
1642 if (!$this->hasbatch() && $this->oldcopy->hasbatch()) {
1643 // Selection of all product stock movements that contains batchs
1644 $sql = 'SELECT pb.qty, ps.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb';
1645 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = pb.fk_product_stock)';
1646 $sql .= ' WHERE ps.fk_product = '.(int) $this->id;
1647
1648 $resql = $this->db->query($sql);
1649 if ($resql) {
1650 $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S');
1651
1652 while ($obj = $this->db->fetch_object($resql)) {
1653 $value = $obj->qty;
1654 $fk_entrepot = $obj->fk_entrepot;
1655 $price = 0;
1656 $dlc = '';
1657 $dluo = '';
1658 $batch = $obj->batch;
1659
1660 // To know how to revert stockMouvement (add or remove)
1661 $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add
1662 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1663 $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true);
1664
1665 if ($res > 0) {
1666 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1667 $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0);
1668 if ($res < 0) {
1669 $error++;
1670 }
1671 } else {
1672 $error++;
1673 }
1674 }
1675 }
1676 }
1677
1678 // Actions on extra fields
1679 if (!$error) {
1680 $result = $this->insertExtraFields();
1681 if ($result < 0) {
1682 $error++;
1683 }
1684 }
1685
1686 if (!$error && !$notrigger) {
1687 // Call trigger
1688 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1689 if ($result < 0) {
1690 $error++;
1691 }
1692 // End call triggers
1693 }
1694
1695 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1696 // We remove directory
1697 if ($conf->product->dir_output) {
1698 $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1699 $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1700 if (file_exists($olddir)) {
1701 // include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1702 // $res = dol_move($olddir, $newdir);
1703 // do not use dol_move with directory
1704 $res = @rename($olddir, $newdir);
1705 if (!$res) {
1706 $langs->load("errors");
1707 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1708 $error++;
1709 } else {
1710 // to keep old entries with the new dir
1711 require_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
1712 $ecmfiles = new EcmFiles($this->db);
1713 $ecmfiles->updateAfterRename("produit/".dol_sanitizeFileName($this->oldcopy->ref), "produit/".dol_sanitizeFileName($this->ref));
1714 }
1715 }
1716 }
1717 }
1718
1719 if (!$error) {
1720 if (isModEnabled('variants')) {
1721 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1722
1723 $comb = new ProductCombination($this->db);
1724
1725 foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1726 $currcomb->updateProperties($this, $user);
1727 }
1728 }
1729
1730 $this->db->commit();
1731 return 1;
1732 } else {
1733 $this->db->rollback();
1734 return -$error;
1735 }
1736 } else {
1737 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1738 $langs->load("errors");
1739 if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1740 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1741 } else {
1742 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1743 }
1744 $this->errors[] = $this->error;
1745 $this->db->rollback();
1746 return -1;
1747 } else {
1748 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1749 $this->errors[] = $this->error;
1750 $this->db->rollback();
1751 return -2;
1752 }
1753 }
1754 } else {
1755 $this->db->rollback();
1756 dol_syslog(get_class($this)."::Update fails verify ".implode(',', $this->errors), LOG_WARNING);
1757 return -3;
1758 }
1759 }
1760
1768 public function delete(User $user, $notrigger = 0)
1769 {
1770 global $conf;
1771 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1772
1773 $error = 0;
1774
1775 // Check parameters
1776 if (empty($this->id)) {
1777 $this->error = "Object must be fetched before calling delete";
1778 return -1;
1779 }
1780 if (($this->isProduct() && !$user->hasRight('produit', 'supprimer')) || ($this->isService() && !$user->hasRight('service', 'supprimer'))) {
1781 $this->error = "ErrorForbidden";
1782 return 0;
1783 }
1784
1785 $objectisused = $this->isObjectUsed($this->id);
1786 if (empty($objectisused)) {
1787 $this->db->begin();
1788
1789 if (!$error && empty($notrigger)) {
1790 // Call trigger
1791 $result = $this->call_trigger('PRODUCT_DELETE', $user);
1792 if ($result < 0) {
1793 $error++;
1794 }
1795 // End call triggers
1796 }
1797
1798 // Delete from product_batch on product delete
1799 if (!$error) {
1800 $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1801 $sql .= " WHERE fk_product_stock IN (";
1802 $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1803 $sql .= " WHERE fk_product = ".((int) $this->id).")";
1804
1805 $result = $this->db->query($sql);
1806 if (!$result) {
1807 $error++;
1808 $this->errors[] = $this->db->lasterror();
1809 }
1810 }
1811
1812 // Delete all child tables
1813 if (!$error) {
1814 $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1815 foreach ($elements as $table) {
1816 if (!$error) {
1817 $sql = "DELETE FROM ".$this->db->prefix().$table;
1818 $sql .= " WHERE fk_product = ".(int) $this->id;
1819
1820 $result = $this->db->query($sql);
1821 if (!$result) {
1822 $error++;
1823 $this->errors[] = $this->db->lasterror();
1824 }
1825 }
1826 }
1827 }
1828
1829 if (!$error) {
1830 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1831 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1832
1833 //If it is a parent product, then we remove the association with child products
1834 $prodcomb = new ProductCombination($this->db);
1835
1836 if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1837 $error++;
1838 $this->errors[] = 'Error deleting combinations';
1839 }
1840
1841 //We also check if it is a child product
1842 if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1843 $error++;
1844 $this->errors[] = 'Error deleting child combination';
1845 }
1846 }
1847
1848 // Delete from product_association
1849 if (!$error) {
1850 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1851 $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1852
1853 $result = $this->db->query($sql);
1854 if (!$result) {
1855 $error++;
1856 $this->errors[] = $this->db->lasterror();
1857 }
1858 }
1859
1860 // Remove extrafields
1861 if (!$error) {
1862 $result = $this->deleteExtraFields();
1863 if ($result < 0) {
1864 $error++;
1865 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1866 }
1867 }
1868
1869 // Delete product
1870 if (!$error) {
1871 $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1872 $sqlz .= " WHERE rowid = ".(int) $this->id;
1873
1874 $resultz = $this->db->query($sqlz);
1875 if (!$resultz) {
1876 $error++;
1877 $this->errors[] = $this->db->lasterror();
1878 }
1879 }
1880
1881 // Delete record into ECM index and physically
1882 if (!$error) {
1883 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1884 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1885 if (!$res) {
1886 $error++;
1887 }
1888 }
1889
1890 if (!$error) {
1891 // We remove directory
1892 $ref = dol_sanitizeFileName($this->ref);
1893 if ($conf->product->dir_output) {
1894 $dir = $conf->product->dir_output."/".$ref;
1895 if (file_exists($dir)) {
1896 $res = @dol_delete_dir_recursive($dir);
1897 if (!$res) {
1898 $this->errors[] = 'ErrorFailToDeleteDir';
1899 $error++;
1900 }
1901 }
1902 }
1903 }
1904
1905 if (!$error) {
1906 $this->db->commit();
1907 return 1;
1908 } else {
1909 foreach ($this->errors as $errmsg) {
1910 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1911 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1912 }
1913 $this->db->rollback();
1914 return -$error;
1915 }
1916 } else {
1917 $this->error = "ErrorRecordIsUsedCantDelete";
1918 return 0;
1919 }
1920 }
1921
1927 public static function getSellOrEatByMandatoryList()
1928 {
1929 global $langs;
1930
1931 $sellByLabel = $langs->trans('SellByDate');
1932 $eatByLabel = $langs->trans('EatByDate');
1933 return array(
1934 self::SELL_OR_EAT_BY_MANDATORY_ID_NONE => $langs->trans('BatchSellOrEatByMandatoryNone'),
1935 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY => $sellByLabel,
1936 self::SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY => $eatByLabel,
1937 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT => $langs->trans('BatchSellOrEatByMandatoryAll', $sellByLabel, $eatByLabel),
1938 );
1939 }
1940
1947 {
1948 $sellOrEatByMandatoryLabel = '';
1949
1950 $sellOrEatByMandatoryList = self::getSellOrEatByMandatoryList();
1951 if (isset($sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory])) {
1952 $sellOrEatByMandatoryLabel = $sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory];
1953 }
1954
1955 return $sellOrEatByMandatoryLabel;
1956 }
1957
1964 public function setMultiLangs($user)
1965 {
1966 global $langs;
1967
1968 $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1969 $current_lang = $langs->getDefaultLang();
1970
1971 foreach ($langs_available as $key => $value) {
1972 if ($key == $current_lang) {
1973 $sql = "SELECT rowid";
1974 $sql .= " FROM ".$this->db->prefix()."product_lang";
1975 $sql .= " WHERE fk_product = ".((int) $this->id);
1976 $sql .= " AND lang = '".$this->db->escape($key)."'";
1977
1978 $result = $this->db->query($sql);
1979
1980 if ($this->db->num_rows($result)) { // if there is already a description line for this language
1981 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
1982 $sql2 .= " SET ";
1983 $sql2 .= " label='".$this->db->escape($this->label)."',";
1984 $sql2 .= " description='".$this->db->escape($this->description)."'";
1985 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1986 $sql2 .= ", note='".$this->db->escape($this->other)."'";
1987 }
1988 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
1989 } else {
1990 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
1991 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1992 $sql2 .= ", note";
1993 }
1994 $sql2 .= ")";
1995 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
1996 $sql2 .= " '".$this->db->escape($this->description)."'";
1997 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
1998 $sql2 .= ", '".$this->db->escape($this->other)."'";
1999 }
2000 $sql2 .= ")";
2001 }
2002 dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
2003 if (!$this->db->query($sql2)) {
2004 $this->error = $this->db->lasterror();
2005 return -1;
2006 }
2007 } elseif (isset($this->multilangs[$key])) {
2008 if (empty($this->multilangs[$key]["label"])) {
2009 $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
2010 return -1;
2011 }
2012
2013 $sql = "SELECT rowid";
2014 $sql .= " FROM ".$this->db->prefix()."product_lang";
2015 $sql .= " WHERE fk_product = ".((int) $this->id);
2016 $sql .= " AND lang = '".$this->db->escape($key)."'";
2017
2018 $result = $this->db->query($sql);
2019
2020 if ($this->db->num_rows($result)) { // if there is already a description line for this language
2021 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
2022 $sql2 .= " SET ";
2023 $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
2024 $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
2025 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2026 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2027 $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
2028 }
2029 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
2030 } else {
2031 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
2032 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2033 $sql2 .= ", note";
2034 }
2035 $sql2 .= ")";
2036 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
2037 $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
2038 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2039 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2040 $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
2041 }
2042 $sql2 .= ")";
2043 }
2044
2045 // We do not save if main fields are empty
2046 if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
2047 if (!$this->db->query($sql2)) {
2048 $this->error = $this->db->lasterror();
2049 return -1;
2050 }
2051 }
2052 } else {
2053 // language is not current language and we didn't provide a multilang description for this language
2054 }
2055 }
2056
2057 // Call trigger
2058 $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
2059 if ($result < 0) {
2060 $this->error = $this->db->lasterror();
2061 return -1;
2062 }
2063 // End call triggers
2064
2065 return 1;
2066 }
2067
2076 public function delMultiLangs($langtodelete, $user)
2077 {
2078 $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
2079 $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
2080
2081 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
2082 $result = $this->db->query($sql);
2083 if ($result) {
2084 // Call trigger
2085 $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
2086 if ($result < 0) {
2087 $this->error = $this->db->lasterror();
2088 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2089 return -1;
2090 }
2091 // End call triggers
2092 return 1;
2093 } else {
2094 $this->error = $this->db->lasterror();
2095 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2096 return -1;
2097 }
2098 }
2099
2108 public function setAccountancyCode($type, $value)
2109 {
2110 global $user;
2111
2112 $error = 0;
2113
2114 $this->db->begin();
2115
2116 if ($type == 'buy') {
2117 $field = 'accountancy_code_buy';
2118 } elseif ($type == 'buy_intra') {
2119 $field = 'accountancy_code_buy_intra';
2120 } elseif ($type == 'buy_export') {
2121 $field = 'accountancy_code_buy_export';
2122 } elseif ($type == 'sell') {
2123 $field = 'accountancy_code_sell';
2124 } elseif ($type == 'sell_intra') {
2125 $field = 'accountancy_code_sell_intra';
2126 } elseif ($type == 'sell_export') {
2127 $field = 'accountancy_code_sell_export';
2128 } else {
2129 return -1;
2130 }
2131
2132 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
2133 $sql .= "$field = '".$this->db->escape($value)."'";
2134 $sql .= " WHERE rowid = ".((int) $this->id);
2135
2136 dol_syslog(__METHOD__, LOG_DEBUG);
2137 $resql = $this->db->query($sql);
2138
2139 if ($resql) {
2140 // Call trigger
2141 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
2142 if ($result < 0) {
2143 $error++;
2144 }
2145 // End call triggers
2146
2147 if ($error) {
2148 $this->db->rollback();
2149 return -1;
2150 }
2151
2152 $this->$field = $value;
2153
2154 $this->db->commit();
2155 return 1;
2156 } else {
2157 $this->error = $this->db->lasterror();
2158 $this->db->rollback();
2159 return -1;
2160 }
2161 }
2162
2168 public function getMultiLangs()
2169 {
2170 global $langs;
2171
2172 $current_lang = $langs->getDefaultLang();
2173
2174 $sql = "SELECT lang, label, description, note as other";
2175 $sql .= " FROM ".$this->db->prefix()."product_lang";
2176 $sql .= " WHERE fk_product = ".((int) $this->id);
2177
2178 $result = $this->db->query($sql);
2179 if ($result) {
2180 while ($obj = $this->db->fetch_object($result)) {
2181 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
2182 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
2183 $this->label = $obj->label;
2184 $this->description = $obj->description;
2185 $this->other = $obj->other;
2186 }
2187 $this->multilangs[(string) $obj->lang]["label"] = $obj->label;
2188 $this->multilangs[(string) $obj->lang]["description"] = $obj->description;
2189 $this->multilangs[(string) $obj->lang]["other"] = $obj->other;
2190 }
2191 return 1;
2192 } else {
2193 $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
2194 return -1;
2195 }
2196 }
2197
2204 private function getArrayForPriceCompare($level = 0)
2205 {
2206 $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
2207
2208 foreach ($testExit as $field) {
2209 if (!isset($this->$field)) {
2210 return array();
2211 }
2212 $tmparray = $this->$field;
2213 if (!isset($tmparray[$level])) {
2214 return array();
2215 }
2216 }
2217
2218 $lastPrice = array(
2219 'level' => $level ? $level : 1,
2220 'multiprices' => (float) $this->multiprices[$level],
2221 'multiprices_ttc' => (float) $this->multiprices_ttc[$level],
2222 'multiprices_base_type' => $this->multiprices_base_type[$level],
2223 'multiprices_min' => (float) $this->multiprices_min[$level],
2224 'multiprices_min_ttc' => (float) $this->multiprices_min_ttc[$level],
2225 'multiprices_tva_tx' => (float) $this->multiprices_tva_tx[$level],
2226 'multiprices_recuperableonly' => (float) $this->multiprices_recuperableonly[$level],
2227 );
2228
2229 return $lastPrice;
2230 }
2231
2232
2233 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2241 private function _log_price($user, $level = 0)
2242 {
2243 // phpcs:enable
2244 global $conf;
2245
2246 $now = dol_now();
2247
2248 // Clean parameters
2249 if (empty($this->price_by_qty)) {
2250 $this->price_by_qty = 0;
2251 }
2252
2253 // Add new price
2254 $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,";
2255 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
2256 $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).",";
2257 $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');
2258 $sql .= ")";
2259
2260 dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
2261 $resql = $this->db->query($sql);
2262 if (!$resql) {
2263 $this->error = $this->db->lasterror();
2264 dol_print_error($this->db);
2265 return -1;
2266 } else {
2267 return 1;
2268 }
2269 }
2270
2271
2272 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2280 public function log_price_delete($user, $rowid)
2281 {
2282 // phpcs:enable
2283 $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
2284 $sql .= " WHERE fk_product_price = ".((int) $rowid);
2285 $resql = $this->db->query($sql);
2286
2287 $sql = "DELETE FROM ".$this->db->prefix()."product_price";
2288 $sql .= " WHERE rowid=".((int) $rowid);
2289 $resql = $this->db->query($sql);
2290 if ($resql) {
2291 return 1;
2292 } else {
2293 $this->error = $this->db->lasterror();
2294 return -1;
2295 }
2296 }
2297
2298
2308 public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
2309 {
2310 global $hookmanager, $action;
2311
2312 // Call hook if any
2313 if (is_object($hookmanager)) {
2314 $parameters = array('thirdparty_seller' => $thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
2315 // Note that $action and $object may have been modified by some hooks
2316 $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
2317 if ($reshook > 0) {
2318 return $hookmanager->resArray;
2319 }
2320 }
2321
2322 // Update if prices fields are defined
2323 $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
2324 $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
2325 if (empty($tva_tx)) {
2326 $tva_npr = 0;
2327 }
2328
2329 $pu_ht = $this->price;
2330 $pu_ttc = $this->price_ttc;
2331 $price_min = $this->price_min;
2332 $price_base_type = $this->price_base_type;
2333
2334 // if price by customer / level
2335 if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
2336 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2337
2338 $prodcustprice = new ProductCustomerPrice($this->db);
2339
2340 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2341
2342 // If a price per customer exist
2343 $pricebycustomerexist = false;
2344 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2345 if ($result) {
2346 if (count($prodcustprice->lines) > 0) {
2347 $pricebycustomerexist = true;
2348 $pu_ht = price($prodcustprice->lines[0]->price);
2349 $price_min = price($prodcustprice->lines[0]->price_min);
2350 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2351 $price_base_type = $prodcustprice->lines[0]->price_base_type;
2352 $tva_tx = $prodcustprice->lines[0]->tva_tx;
2353 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2354 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2355 }
2356 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2357 if (empty($tva_tx)) {
2358 $tva_npr = 0;
2359 }
2360 }
2361 }
2362
2363 if (!$pricebycustomerexist && !empty($thirdparty_buyer->price_level)) {
2364 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2365 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2366 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2367 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2368 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) {
2369 // using this option is a bug. kept for backward compatibility
2370 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2371 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2372 }
2373 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2374 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2375 }
2376 if (empty($tva_tx)) {
2377 $tva_npr = 0;
2378 }
2379 }
2380 }
2381 } elseif (getDolGlobalString('PRODUIT_MULTIPRICES') && !empty($thirdparty_buyer->price_level)) { // // If price per segment
2382 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2383 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2384 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2385 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2386 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
2387 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2388 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2389 }
2390 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2391 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2392 }
2393 if (empty($tva_tx)) {
2394 $tva_npr = 0;
2395 }
2396 }
2397 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
2398 // If price per customer
2399 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2400
2401 $prodcustprice = new ProductCustomerPrice($this->db);
2402
2403 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2404
2405 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2406 if ($result) {
2407 if (count($prodcustprice->lines) > 0) {
2408 $pu_ht = price($prodcustprice->lines[0]->price);
2409 $price_min = price($prodcustprice->lines[0]->price_min);
2410 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2411 $price_base_type = $prodcustprice->lines[0]->price_base_type;
2412 $tva_tx = $prodcustprice->lines[0]->tva_tx;
2413 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2414 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2415 }
2416 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2417 if (empty($tva_tx)) {
2418 $tva_npr = 0;
2419 }
2420 }
2421 }
2422 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
2423 // If price per quantity
2424 if ($this->prices_by_qty[0]) {
2425 // yes, this product has some prices per quantity
2426 // Search price into product_price_by_qty from $this->id
2427 foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
2428 if ($priceforthequantityarray['rowid'] != $pqp) {
2429 continue;
2430 }
2431 // We found the price
2432 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2433 $pu_ht = $priceforthequantityarray['unitprice'];
2434 } else {
2435 $pu_ttc = $priceforthequantityarray['unitprice'];
2436 }
2437 break;
2438 }
2439 }
2440 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2441 // If price per quantity and customer
2442 if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
2443 // yes, this product has some prices per quantity
2444 // Search price into product_price_by_qty from $this->id
2445 foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
2446 if ($priceforthequantityarray['rowid'] != $pqp) {
2447 continue;
2448 }
2449 // We found the price
2450 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2451 $pu_ht = $priceforthequantityarray['unitprice'];
2452 } else {
2453 $pu_ttc = $priceforthequantityarray['unitprice'];
2454 }
2455 break;
2456 }
2457 }
2458 }
2459
2460 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);
2461 }
2462
2463 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2477 public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2478 {
2479 // phpcs:enable
2480 global $action, $hookmanager;
2481
2482 // Call hook if any
2483 if (is_object($hookmanager)) {
2484 $parameters = array(
2485 'prodfournprice' => $prodfournprice,
2486 'qty' => $qty,
2487 'product_id' => $product_id,
2488 'fourn_ref' => $fourn_ref,
2489 'fk_soc' => $fk_soc,
2490 );
2491 // Note that $action and $object may have been modified by some hooks
2492 $reshook = $hookmanager->executeHooks('getBuyPrice', $parameters, $this, $action);
2493 if ($reshook > 0) {
2494 return $hookmanager->resArray;
2495 }
2496 }
2497
2498 $result = 0;
2499
2500 // 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)
2501 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2502 $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,";
2503 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2504 $sql .= " pfp.packaging";
2505 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2506 $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2507 if ($qty > 0) {
2508 $sql .= " AND pfp.quantity <= ".((float) $qty);
2509 }
2510 $sql .= " ORDER BY pfp.quantity DESC";
2511
2512 dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
2513 $resql = $this->db->query($sql);
2514 if ($resql) {
2515 $obj = $this->db->fetch_object($resql);
2516 if ($obj && $obj->quantity > 0) { // If we found a supplier prices from the id of supplier price
2517 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2518 $prod_supplier = new ProductFournisseur($this->db);
2519 $prod_supplier->product_fourn_price_id = $obj->rowid;
2520 $prod_supplier->id = $obj->fk_product;
2521 $prod_supplier->fourn_qty = $obj->quantity;
2522 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2523 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2524
2525 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2526 $priceparser = new PriceParser($this->db);
2527 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2528 if ($price_result >= 0) {
2529 $obj->price = $price_result;
2530 }
2531 }
2532 $this->product_fourn_price_id = $obj->rowid;
2533 $this->buyprice = $obj->price; // deprecated
2534 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2535 $this->fourn_price_base_type = 'HT'; // Price base type
2536 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2537 $this->ref_fourn = $obj->ref_supplier; // deprecated
2538 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2539 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2540 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2541 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2542 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2543 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2544 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2545 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2546 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2547 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2548 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2549 $this->packaging = $obj->packaging;
2550 }
2551 $result = $obj->fk_product;
2552 return $result;
2553 } else { // If not found
2554 // 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.
2555 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2556 $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,";
2557 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2558 $sql .= " pfp.packaging";
2559 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2560 $sql .= " WHERE 1 = 1";
2561 if ($product_id > 0) {
2562 $sql .= " AND pfp.fk_product = ".((int) $product_id);
2563 }
2564 if ($fourn_ref != 'none') {
2565 $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2566 }
2567 if ($fk_soc > 0) {
2568 $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2569 }
2570 if ($qty > 0) {
2571 $sql .= " AND pfp.quantity <= ".((float) $qty);
2572 }
2573 $sql .= " ORDER BY pfp.quantity DESC";
2574 $sql .= " LIMIT 1";
2575
2576 dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2577 $resql = $this->db->query($sql);
2578 if ($resql) {
2579 $obj = $this->db->fetch_object($resql);
2580 if ($obj && $obj->quantity > 0) { // If found
2581 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2582 $prod_supplier = new ProductFournisseur($this->db);
2583 $prod_supplier->product_fourn_price_id = $obj->rowid;
2584 $prod_supplier->id = $obj->fk_product;
2585 $prod_supplier->fourn_qty = $obj->quantity;
2586 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2587 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2588
2589 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2590 $priceparser = new PriceParser($this->db);
2591 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2592 if ($result >= 0) {
2593 $obj->price = $price_result;
2594 }
2595 }
2596 $this->product_fourn_price_id = $obj->rowid;
2597 $this->buyprice = $obj->price; // deprecated
2598 $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2599 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2600 $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2601 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2602 $this->ref_fourn = $obj->ref_supplier; // deprecated
2603 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2604 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2605 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2606 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2607 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2608 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2609 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2610 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2611 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2612 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2613 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2614 $this->packaging = $obj->packaging;
2615 }
2616 $result = $obj->fk_product;
2617 return $result;
2618 } else {
2619 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é.
2620 }
2621 } else {
2622 $this->error = $this->db->lasterror();
2623 return -3;
2624 }
2625 }
2626 } else {
2627 $this->error = $this->db->lasterror();
2628 return -2;
2629 }
2630 }
2631
2632
2651 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)
2652 {
2653 $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2654
2655 $id = $this->id;
2656
2657 dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2658
2659 // Clean parameters
2660 if (empty($this->tva_tx)) {
2661 $this->tva_tx = 0.0;
2662 }
2663 if (empty($newnpr)) {
2664 $newnpr = 0.0;
2665 }
2666 if (empty($newminprice)) {
2667 $newminprice = 0.0;
2668 }
2669
2670 // Check parameters
2671 if ($newvat === null || $newvat == '') { // Maintain '' for backwards compatibility
2672 $newvat = (float) $this->tva_tx;
2673 }
2674
2675 $localtaxtype1 = '';
2676 $localtaxtype2 = '';
2677
2678 // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2679 // Price will be modified ONLY when the first one is the one that is being modified
2680 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2681 return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2682 }
2683
2684 if (!empty($newminprice) && ($newminprice > $newprice)) {
2685 $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2686 return -1;
2687 }
2688
2689 if ($newprice === 0 || $newprice !== '') {
2690 if ($newpricebase == 'TTC') {
2691 $price_ttc = (float) price2num($newprice, 'MU');
2692 $price = (float) price2num($newprice) / (1 + ((float) $newvat / 100));
2693 $price = (float) price2num($price, 'MU');
2694
2695 if ((string) $newminprice != '0') {
2696 $price_min_ttc = (float) price2num($newminprice, 'MU');
2697 $price_min = (float) price2num($newminprice) / (1 + ($newvat / 100));
2698 $price_min = (float) price2num($price_min, 'MU');
2699 } else {
2700 $price_min = 0.0;
2701 $price_min_ttc = 0.0;
2702 }
2703 } else {
2704 $price = (float) price2num($newprice, 'MU');
2705 $price_ttc = ($newnpr != 1) ? (float) price2num($newprice) * (1 + ($newvat / 100)) : $price;
2706 $price_ttc = (float) price2num($price_ttc, 'MU');
2707
2708 if ((string) $newminprice != '0') {
2709 $price_min = (float) price2num($newminprice, 'MU');
2710 $price_min_ttc = (float) price2num($newminprice) * (1 + ($newvat / 100));
2711 $price_min_ttc = (float) price2num($price_min_ttc, 'MU');
2712 //print 'X'.$newminprice.'-'.$price_min;
2713 } else {
2714 $price_min = 0.0;
2715 $price_min_ttc = 0.0;
2716 }
2717 }
2718 //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2719 if (count($localtaxes_array) > 0) {
2720 $localtaxtype1 = $localtaxes_array['0'];
2721 $localtax1 = $localtaxes_array['1'];
2722 $localtaxtype2 = $localtaxes_array['2'];
2723 $localtax2 = $localtaxes_array['3'];
2724 } else {
2725 // if array empty, we try to use the vat code
2726 if (!empty($newdefaultvatcode)) {
2727 global $mysoc;
2728 // Get record from code
2729 $sql = "SELECT t.rowid, t.code, t.recuperableonly as tva_npr, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2730 $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2731 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2732 $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2733 $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2734 $resql = $this->db->query($sql);
2735 if ($resql) {
2736 $obj = $this->db->fetch_object($resql);
2737 if ($obj) {
2738 $npr = $obj->tva_npr;
2739 $localtax1 = $obj->localtax1;
2740 $localtax2 = $obj->localtax2;
2741 $localtaxtype1 = $obj->localtax1_type;
2742 $localtaxtype2 = $obj->localtax2_type;
2743 }
2744 }
2745 } else {
2746 // old method. deprecated because we can't retrieve type
2747 $localtaxtype1 = '0';
2748 $localtax1 = get_localtax($newvat, 1);
2749 $localtaxtype2 = '0';
2750 $localtax2 = get_localtax($newvat, 2);
2751 }
2752 }
2753 if (empty($localtax1)) {
2754 $localtax1 = 0; // If = '' then = 0
2755 }
2756 if (empty($localtax2)) {
2757 $localtax2 = 0; // If = '' then = 0
2758 }
2759
2760 $this->db->begin();
2761
2762 // Ne pas mettre de quote sur les numeriques decimaux.
2763 // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2764 $sql = "UPDATE ".$this->db->prefix()."product SET";
2765 $sql .= " price_base_type = '".$this->db->escape($newpricebase)."',";
2766 $sql .= " price = ".(float) $price.",";
2767 $sql .= " price_ttc = ".(float) $price_ttc.",";
2768 $sql .= " price_min = ".(float) $price_min.",";
2769 $sql .= " price_min_ttc = ".(float) $price_min_ttc.",";
2770 $sql .= " localtax1_tx = ".($localtax1 >= 0 ? (float) $localtax1 : 'NULL').",";
2771 $sql .= " localtax2_tx = ".($localtax2 >= 0 ? (float) $localtax2 : 'NULL').",";
2772 $sql .= " localtax1_type = ".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2773 $sql .= " localtax2_type = ".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2774 $sql .= " default_vat_code = ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2775 $sql .= " price_label = ".(!empty($price_label) ? "'".$this->db->escape($price_label)."'" : "null").",";
2776 $sql .= " tva_tx = ".(float) price2num($newvat).",";
2777 $sql .= " recuperableonly = '".$this->db->escape($newnpr)."'";
2778 $sql .= " WHERE rowid = ".((int) $id);
2779
2780 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2781 $resql = $this->db->query($sql);
2782 if ($resql) {
2783 $this->multiprices[$level] = $price;
2784 $this->multiprices_ttc[$level] = $price_ttc;
2785 $this->multiprices_min[$level] = $price_min;
2786 $this->multiprices_min_ttc[$level] = $price_min_ttc;
2787 $this->multiprices_base_type[$level] = $newpricebase;
2788 $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2789 $this->multiprices_tva_tx[$level] = $newvat;
2790 $this->multiprices_recuperableonly[$level] = $newnpr;
2791
2792 $this->price = $price;
2793 $this->price_label = $price_label;
2794 $this->price_ttc = $price_ttc;
2795 $this->price_min = $price_min;
2796 $this->price_min_ttc = $price_min_ttc;
2797 $this->price_base_type = $newpricebase;
2798 $this->default_vat_code = $newdefaultvatcode;
2799 $this->tva_tx = $newvat;
2800 $this->tva_npr = $newnpr;
2801
2802 //Local taxes
2803 $this->localtax1_tx = $localtax1;
2804 $this->localtax2_tx = $localtax2;
2805 $this->localtax1_type = $localtaxtype1;
2806 $this->localtax2_type = $localtaxtype2;
2807
2808 // Price by quantity
2809 $this->price_by_qty = $newpbq;
2810
2811 // check if price have really change before log
2812 $newPriceData = $this->getArrayForPriceCompare($level);
2813 if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || !getDolGlobalString('PRODUIT_MULTIPRICES')) {
2814 $this->_log_price($user, $level); // Save price for level into table product_price
2815 }
2816
2817 $this->level = $level; // Store level of price edited for trigger
2818
2819 // Call trigger
2820 if (!$notrigger) {
2821 $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2822 if ($result < 0) {
2823 $this->db->rollback();
2824 return -1;
2825 }
2826 }
2827 // End call triggers
2828
2829 $this->db->commit();
2830 } else {
2831 $this->db->rollback();
2832 $this->error = $this->db->lasterror();
2833 return -1;
2834 }
2835 }
2836
2837 return 1;
2838 }
2839
2847 public function setPriceExpression($expression_id)
2848 {
2849 global $user;
2850
2851 $this->fk_price_expression = $expression_id;
2852
2853 return $this->update($this->id, $user);
2854 }
2855
2868 public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2869 {
2870 include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2871
2872 global $conf;
2873
2874 dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2875
2876 // Check parameters
2877 if (!$id && !$ref && !$ref_ext && !$barcode) {
2878 $this->error = 'ErrorWrongParameters';
2879 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2880 return -1;
2881 }
2882
2883 $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,";
2884 $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,";
2885 $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,";
2886 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.last_main_doc,";
2887 $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,";
2888 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2889 $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,";
2890 } else {
2891 $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,";
2892 }
2893
2894 // For MultiCompany
2895 // PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
2896 $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
2897 $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.
2898 $visibleWarehousesEntities = $conf->entity;
2899 if (getDolGlobalString('MULTICOMPANY_PRODUCT_SHARING_ENABLED')) {
2900 if (getDolGlobalString('MULTICOMPANY_PMP_PER_ENTITY_ENABLED')) {
2901 $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
2902 if ($this->db->num_rows($checkPMPPerEntity) > 0) {
2903 $separatedEntityPMP = true;
2904 }
2905 }
2906 global $mc;
2907 $separatedStock = true;
2908 if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
2909 $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
2910 }
2911 }
2912 if ($separatedEntityPMP) {
2913 $sql .= " ppe.pmp,";
2914 } else {
2915 $sql .= " p.pmp,";
2916 }
2917 $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,";
2918 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf,";
2919 $sql .= " p.price_label,";
2920 if ($separatedStock) {
2921 $sql .= " SUM(sp.reel) as stock";
2922 } else {
2923 $sql .= " p.stock";
2924 }
2925 $sql .= " FROM ".$this->db->prefix()."product as p";
2926 if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') || $separatedEntityPMP) {
2927 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
2928 }
2929 if ($separatedStock) {
2930 $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)."))";
2931 }
2932
2933 if ($id) {
2934 $sql .= " WHERE p.rowid = ".((int) $id);
2935 } else {
2936 $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
2937 if ($ref) {
2938 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
2939 } elseif ($ref_ext) {
2940 $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
2941 } elseif ($barcode) {
2942 $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
2943 }
2944 }
2945 if ($separatedStock) {
2946 $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,";
2947 $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,";
2948 $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,";
2949 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2950 $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,";
2951 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2952 $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,";
2953 } else {
2954 $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,";
2955 }
2956 if ($separatedEntityPMP) {
2957 $sql .= " ppe.pmp,";
2958 } else {
2959 $sql .= " p.pmp,";
2960 }
2961 $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,";
2962 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf";
2963 $sql .= " ,p.price_label";
2964 if (!$separatedStock) {
2965 $sql .= ", p.stock";
2966 }
2967 }
2968
2969 $resql = $this->db->query($sql);
2970 if ($resql) {
2971 unset($this->oldcopy);
2972
2973 if ($this->db->num_rows($resql) > 0) {
2974 $obj = $this->db->fetch_object($resql);
2975
2976 $this->id = $obj->rowid;
2977 $this->ref = $obj->ref;
2978 $this->ref_ext = $obj->ref_ext;
2979 $this->label = $obj->label;
2980 $this->description = $obj->description;
2981 $this->url = $obj->url;
2982 $this->note_public = $obj->note_public;
2983 $this->note_private = $obj->note_private;
2984 $this->note = $obj->note_private; // deprecated
2985
2986 $this->type = $obj->fk_product_type;
2987 $this->price_label = $obj->price_label;
2988 $this->status = $obj->tosell;
2989 $this->status_buy = $obj->tobuy;
2990 $this->status_batch = $obj->tobatch;
2991 $this->sell_or_eat_by_mandatory = $obj->sell_or_eat_by_mandatory;
2992 $this->batch_mask = $obj->batch_mask;
2993
2994 $this->customcode = $obj->customcode;
2995 $this->country_id = $obj->fk_country;
2996 $this->country_code = getCountry($this->country_id, '2', $this->db);
2997 $this->state_id = $obj->fk_state;
2998 $this->lifetime = $obj->lifetime;
2999 $this->qc_frequency = $obj->qc_frequency;
3000 $this->price = $obj->price;
3001 $this->price_ttc = $obj->price_ttc;
3002 $this->price_min = $obj->price_min;
3003 $this->price_min_ttc = $obj->price_min_ttc;
3004 $this->price_base_type = $obj->price_base_type;
3005 $this->cost_price = isset($obj->cost_price) ? (float) $obj->cost_price : null;
3006 $this->default_vat_code = $obj->default_vat_code;
3007 $this->tva_tx = $obj->tva_tx;
3009 $this->tva_npr = $obj->tva_npr;
3011 $this->localtax1_tx = $obj->localtax1_tx;
3012 $this->localtax2_tx = $obj->localtax2_tx;
3013 $this->localtax1_type = $obj->localtax1_type;
3014 $this->localtax2_type = $obj->localtax2_type;
3015
3016 $this->finished = $obj->finished;
3017 $this->fk_default_bom = $obj->fk_default_bom;
3018
3019 $this->duration = $obj->duration;
3020 $matches = [];
3021 preg_match('/(\d+)(\w+)/', $obj->duration, $matches);
3022 $this->duration_value = !empty($matches[1]) ? (int) $matches[1] : 0;
3023 $this->duration_unit = !empty($matches[2]) ? (string) $matches[2] : null;
3024 $this->canvas = $obj->canvas;
3025 $this->net_measure = $obj->net_measure;
3026 $this->net_measure_units = $obj->net_measure_units;
3027 $this->weight = $obj->weight;
3028 $this->weight_units = (is_null($obj->weight_units) ? 0 : $obj->weight_units);
3029 $this->length = $obj->length;
3030 $this->length_units = (is_null($obj->length_units) ? 0 : $obj->length_units);
3031 $this->width = $obj->width;
3032 $this->width_units = (is_null($obj->width_units) ? 0 : $obj->width_units);
3033 $this->height = $obj->height;
3034 $this->height_units = (is_null($obj->height_units) ? 0 : $obj->height_units);
3035
3036 $this->surface = $obj->surface;
3037 $this->surface_units = (is_null($obj->surface_units) ? 0 : $obj->surface_units);
3038 $this->volume = $obj->volume;
3039 $this->volume_units = (is_null($obj->volume_units) ? 0 : $obj->volume_units);
3040 $this->barcode = $obj->barcode;
3041 $this->barcode_type = $obj->fk_barcode_type;
3042
3043 $this->accountancy_code_buy = $obj->accountancy_code_buy;
3044 $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
3045 $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
3046 $this->accountancy_code_sell = $obj->accountancy_code_sell;
3047 $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
3048 $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
3049
3050 $this->fk_default_warehouse = $obj->fk_default_warehouse;
3051 $this->fk_default_workstation = $obj->fk_default_workstation;
3052 $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
3053 $this->desiredstock = $obj->desiredstock;
3054 $this->stock_reel = $obj->stock;
3055 $this->pmp = $obj->pmp;
3056
3057 $this->date_creation = $this->db->jdate($obj->datec);
3058 $this->date_modification = $this->db->jdate($obj->tms);
3059
3060 $this->import_key = $obj->import_key;
3061 $this->entity = $obj->entity;
3062
3063 $this->ref_ext = $obj->ref_ext;
3064 $this->fk_price_expression = $obj->fk_price_expression;
3065 $this->fk_unit = $obj->fk_unit;
3066 $this->price_autogen = $obj->price_autogen;
3067 $this->model_pdf = $obj->model_pdf;
3068 $this->last_main_doc = $obj->last_main_doc;
3069
3070 $this->mandatory_period = $obj->mandatory_period;
3071
3072 $this->db->free($resql);
3073
3074 // fetch optionals attributes and labels
3075 $this->fetch_optionals();
3076
3077 // Multilangs
3078 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
3079 $this->getMultiLangs();
3080 }
3081
3082 // Load multiprices array
3083 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($ignore_price_load)) { // prices per segment
3084 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
3085 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
3086 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3087 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
3088 $sql .= " ,price_label";
3089 $sql .= " FROM ".$this->db->prefix()."product_price";
3090 $sql .= " WHERE entity IN (".getEntity('productprice').")";
3091 $sql .= " AND price_level=".((int) $i);
3092 $sql .= " AND fk_product = ".((int) $this->id);
3093 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
3094 $sql .= " LIMIT 1"; // Only the first one
3095 $resql = $this->db->query($sql);
3096 if ($resql) {
3097 $result = $this->db->fetch_array($resql);
3098
3099 $this->multiprices[$i] = $result ? $result["price"] : null;
3100 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
3101 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
3102 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
3103 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
3104 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
3105 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].(!empty($result['default_vat_code']) ? ' ('.$result['default_vat_code'].')' : '') : null;
3106 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
3107
3108 // Price by quantity
3109 /*
3110 $this->prices_by_qty[$i]=$result["price_by_qty"];
3111 $this->prices_by_qty_id[$i]=$result["rowid"];
3112 // Récuperation de la liste des prix selon qty si flag positionné
3113 if ($this->prices_by_qty[$i] == 1)
3114 {
3115 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
3116 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
3117 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
3118 $sql.= " ORDER BY quantity ASC";
3119
3120 $resql = $this->db->query($sql);
3121 if ($resql)
3122 {
3123 $resultat=array();
3124 $ii=0;
3125 while ($result= $this->db->fetch_array($resql)) {
3126 $resultat[$ii]=array();
3127 $resultat[$ii]["rowid"]=$result["rowid"];
3128 $resultat[$ii]["price"]= $result["price"];
3129 $resultat[$ii]["unitprice"]= $result["unitprice"];
3130 $resultat[$ii]["quantity"]= $result["quantity"];
3131 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
3132 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
3133 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
3134 $ii++;
3135 }
3136 $this->prices_by_qty_list[$i]=$resultat;
3137 }
3138 else
3139 {
3140 dol_print_error($this->db);
3141 return -1;
3142 }
3143 }*/
3144 } else {
3145 $this->error = $this->db->lasterror;
3146 return -1;
3147 }
3148 }
3149 } elseif ((getDolGlobalString('PRODUIT_CUSTOMER_PRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($ignore_price_load)) { // prices per customers
3150 // Nothing loaded by default. List may be very long.
3151 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
3152 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3153 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
3154 $sql .= " FROM ".$this->db->prefix()."product_price";
3155 $sql .= " WHERE fk_product = ".((int) $this->id);
3156 $sql .= " ORDER BY date_price DESC, rowid DESC";
3157 $sql .= " LIMIT 1";
3158
3159 $resql = $this->db->query($sql);
3160 if ($resql) {
3161 $result = $this->db->fetch_array($resql);
3162
3163 if ($result) {
3164 // Price by quantity
3165 $this->prices_by_qty[0] = $result["price_by_qty"];
3166 $this->prices_by_qty_id[0] = $result["rowid"];
3167 // Récuperation de la liste des prix selon qty si flag positionné
3168 if ($this->prices_by_qty[0] == 1) {
3169 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
3170 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
3171 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
3172 $sql .= " ORDER BY quantity ASC";
3173
3174 $resql = $this->db->query($sql);
3175 if ($resql) {
3176 $resultat = array();
3177 $ii = 0;
3178 while ($result = $this->db->fetch_array($resql)) {
3179 $resultat[$ii] = array();
3180 $resultat[$ii]["rowid"] = $result["rowid"];
3181 $resultat[$ii]["price"] = $result["price"];
3182 $resultat[$ii]["unitprice"] = $result["unitprice"];
3183 $resultat[$ii]["quantity"] = $result["quantity"];
3184 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
3185 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
3186 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
3187 $ii++;
3188 }
3189 $this->prices_by_qty_list[0] = $resultat;
3190 } else {
3191 $this->error = $this->db->lasterror;
3192 return -1;
3193 }
3194 }
3195 }
3196 } else {
3197 $this->error = $this->db->lasterror;
3198 return -1;
3199 }
3200 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
3201 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
3202 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
3203 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3204 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
3205 $sql .= " FROM ".$this->db->prefix()."product_price";
3206 $sql .= " WHERE entity IN (".getEntity('productprice').")";
3207 $sql .= " AND price_level=".((int) $i);
3208 $sql .= " AND fk_product = ".((int) $this->id);
3209 $sql .= " ORDER BY date_price DESC, rowid DESC";
3210 $sql .= " LIMIT 1";
3211 $resql = $this->db->query($sql);
3212 if (!$resql) {
3213 $this->error = $this->db->lasterror;
3214 return -1;
3215 } elseif ($result = $this->db->fetch_array($resql)) {
3216 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
3217 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
3218 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
3219 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
3220 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
3221 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
3222 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
3223 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
3224
3225 // Price by quantity
3226 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
3227 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
3228 // Récuperation de la liste des prix selon qty si flag positionné
3229 if ($this->prices_by_qty[$i] == 1) {
3230 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
3231 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
3232 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
3233 $sql .= " ORDER BY quantity ASC";
3234
3235 $resql = $this->db->query($sql);
3236 if ($resql) {
3237 $resultat = array();
3238 $ii = 0;
3239 while ($result = $this->db->fetch_array($resql)) {
3240 $resultat[$ii] = array();
3241 $resultat[$ii]["rowid"] = $result["rowid"];
3242 $resultat[$ii]["price"] = $result["price"];
3243 $resultat[$ii]["unitprice"] = $result["unitprice"];
3244 $resultat[$ii]["quantity"] = $result["quantity"];
3245 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
3246 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
3247 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
3248 $ii++;
3249 }
3250 $this->prices_by_qty_list[$i] = $resultat;
3251 } else {
3252 $this->error = $this->db->lasterror;
3253 return -1;
3254 }
3255 }
3256 }
3257 }
3258 }
3259
3260 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
3261 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
3262 $priceparser = new PriceParser($this->db);
3263 $price_result = $priceparser->parseProduct($this);
3264 if ($price_result >= 0) {
3265 $this->price = $price_result;
3266 // Calculate the VAT
3267 $this->price_ttc = (float) price2num($this->price) * (1 + ($this->tva_tx / 100));
3268 $this->price_ttc = (float) price2num($this->price_ttc, 'MU');
3269 }
3270 }
3271
3272 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
3273 // Instead we just init the stock_warehouse array
3274 $this->stock_warehouse = array();
3275
3276 return 1;
3277 } else {
3278 return 0;
3279 }
3280 } else {
3281 $this->error = $this->db->lasterror();
3282 return -1;
3283 }
3284 }
3285
3286 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3293 public function load_stats_mo($socid = 0)
3294 {
3295 // phpcs:enable
3296 global $user, $hookmanager, $action;
3297
3298 $error = 0;
3299
3300 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
3301 $this->stats_mo['customers_'.$role] = 0;
3302 $this->stats_mo['nb_'.$role] = 0;
3303 $this->stats_mo['qty_'.$role] = 0;
3304
3305 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3306 $sql .= " SUM(mp.qty) as qty";
3307 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
3308 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
3309 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3310 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3311 }
3312 $sql .= " WHERE ";
3313 $sql .= " c.entity IN (".getEntity('mo').")";
3314
3315 $sql .= " AND mp.fk_product = ".((int) $this->id);
3316 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
3317 if ($socid > 0) {
3318 $sql .= " AND c.fk_soc = ".((int) $socid);
3319 }
3320
3321 $result = $this->db->query($sql);
3322 if ($result) {
3323 $obj = $this->db->fetch_object($result);
3324 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
3325 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
3326 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
3327 } else {
3328 $this->error = $this->db->error();
3329 $error++;
3330 }
3331 }
3332
3333 if (!empty($error)) {
3334 return -1;
3335 }
3336
3337 $parameters = array('socid' => $socid);
3338 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3339 if ($reshook > 0) {
3340 $this->stats_mo = $hookmanager->resArray['stats_mo'];
3341 }
3342
3343 return 1;
3344 }
3345
3346 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3353 public function load_stats_bom($socid = 0)
3354 {
3355 // phpcs:enable
3356 global $hookmanager, $action;
3357
3358 $error = 0;
3359
3360 $this->stats_bom['nb_toproduce'] = 0;
3361 $this->stats_bom['nb_toconsume'] = 0;
3362 $this->stats_bom['qty_toproduce'] = 0;
3363 $this->stats_bom['qty_toconsume'] = 0;
3364
3365 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
3366 $sql .= " SUM(b.qty) as qty_toproduce";
3367 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3368 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom = b.rowid";
3369 $sql .= " WHERE ";
3370 $sql .= " b.entity IN (".getEntity('bom').")";
3371 $sql .= " AND b.fk_product =".((int) $this->id);
3372 $sql .= " GROUP BY b.rowid";
3373
3374 $result = $this->db->query($sql);
3375 if ($result) {
3376 $obj = $this->db->fetch_object($result);
3377 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
3378 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
3379 } else {
3380 $this->error = $this->db->error();
3381 $error++;
3382 }
3383
3384 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
3385 $sql .= " SUM(bl.qty) as qty_toconsume";
3386 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3387 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
3388 $sql .= " WHERE ";
3389 $sql .= " b.entity IN (".getEntity('bom').")";
3390 $sql .= " AND bl.fk_product =".((int) $this->id);
3391
3392 $result = $this->db->query($sql);
3393 if ($result) {
3394 $obj = $this->db->fetch_object($result);
3395 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
3396 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
3397 } else {
3398 $this->error = $this->db->error();
3399 $error++;
3400 }
3401
3402 if (!empty($error)) {
3403 return -1;
3404 }
3405
3406 $parameters = array('socid' => $socid);
3407 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3408 if ($reshook > 0) {
3409 $this->stats_bom = $hookmanager->resArray['stats_bom'];
3410 }
3411
3412 return 1;
3413 }
3414
3415 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3422 public function load_stats_propale($socid = 0)
3423 {
3424 // phpcs:enable
3425 global $user, $hookmanager, $action;
3426
3427 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3428 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3429 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3430 $sql .= ", ".$this->db->prefix()."propal as p";
3431 $sql .= ", ".$this->db->prefix()."societe as s";
3432 $sql .= " WHERE p.rowid = pd.fk_propal";
3433 $sql .= " AND p.fk_soc = s.rowid";
3434 $sql .= " AND p.entity IN (".getEntity('propal').")";
3435 $sql .= " AND pd.fk_product = ".((int) $this->id);
3436 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3437 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $user->id);
3438 }
3439 //$sql.= " AND pr.fk_statut != 0";
3440 if ($socid > 0) {
3441 $sql .= " AND p.fk_soc = ".((int) $socid);
3442 }
3443
3444 $result = $this->db->query($sql);
3445 if ($result) {
3446 $obj = $this->db->fetch_object($result);
3447 $this->stats_propale['customers'] = $obj->nb_customers;
3448 $this->stats_propale['nb'] = $obj->nb;
3449 $this->stats_propale['rows'] = $obj->nb_rows;
3450 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3451
3452 // if it's a virtual product, maybe it is in proposal by extension
3453 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3454 $TFather = $this->getFather();
3455 if (is_array($TFather) && !empty($TFather)) {
3456 foreach ($TFather as &$fatherData) {
3457 $pFather = new Product($this->db);
3458 $pFather->id = $fatherData['id'];
3459 $qtyCoef = $fatherData['qty'];
3460
3461 if ($fatherData['incdec']) {
3462 $pFather->load_stats_propale($socid);
3463
3464 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3465 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3466 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3467 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3468 }
3469 }
3470 }
3471 }
3472
3473 $parameters = array('socid' => $socid);
3474 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3475 if ($reshook > 0) {
3476 $this->stats_propale = $hookmanager->resArray['stats_propale'];
3477 }
3478
3479 return 1;
3480 } else {
3481 $this->error = $this->db->error();
3482 return -1;
3483 }
3484 }
3485
3486
3487 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3494 public function load_stats_proposal_supplier($socid = 0)
3495 {
3496 // phpcs:enable
3497 global $user, $hookmanager, $action;
3498
3499 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3500 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3501 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3502 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3503 $sql .= ", ".$this->db->prefix()."societe as s";
3504 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3505 $sql .= " AND p.fk_soc = s.rowid";
3506 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3507 $sql .= " AND pd.fk_product = ".((int) $this->id);
3508 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3509 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $user->id);
3510 }
3511 //$sql.= " AND pr.fk_statut != 0";
3512 if ($socid > 0) {
3513 $sql .= " AND p.fk_soc = ".((int) $socid);
3514 }
3515
3516 $result = $this->db->query($sql);
3517 if ($result) {
3518 $obj = $this->db->fetch_object($result);
3519 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3520 $this->stats_proposal_supplier['nb'] = $obj->nb;
3521 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3522 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3523
3524 $parameters = array('socid' => $socid);
3525 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3526 if ($reshook > 0) {
3527 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3528 }
3529
3530 return 1;
3531 } else {
3532 $this->error = $this->db->error();
3533 return -1;
3534 }
3535 }
3536
3537
3538 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3547 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3548 {
3549 // phpcs:enable
3550 global $user, $hookmanager, $action;
3551
3552 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3553 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3554 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3555 $sql .= ", ".$this->db->prefix()."commande as c";
3556 $sql .= ", ".$this->db->prefix()."societe as s";
3557 $sql .= " WHERE c.rowid = cd.fk_commande";
3558 $sql .= " AND c.fk_soc = s.rowid";
3559 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3560 $sql .= " AND cd.fk_product = ".((int) $this->id);
3561 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3562 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3563 }
3564 if ($socid > 0) {
3565 $sql .= " AND c.fk_soc = ".((int) $socid);
3566 }
3567 if ($filtrestatut != '') {
3568 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3569 }
3570
3571 $result = $this->db->query($sql);
3572 if ($result) {
3573 $obj = $this->db->fetch_object($result);
3574 $this->stats_commande['customers'] = $obj->nb_customers;
3575 $this->stats_commande['nb'] = $obj->nb;
3576 $this->stats_commande['rows'] = $obj->nb_rows;
3577 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3578
3579 // if it's a virtual product, maybe it is in order by extension
3580 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3581 $TFather = $this->getFather();
3582 if (is_array($TFather) && !empty($TFather)) {
3583 foreach ($TFather as &$fatherData) {
3584 $pFather = new Product($this->db);
3585 $pFather->id = $fatherData['id'];
3586 $qtyCoef = $fatherData['qty'];
3587
3588 if ($fatherData['incdec']) {
3589 $pFather->load_stats_commande($socid, $filtrestatut);
3590
3591 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3592 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3593 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3594 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3595 }
3596 }
3597 }
3598 }
3599
3600 // If stock decrease is on invoice validation, the theoretical stock continue to
3601 // count the orders lines in theoretical stock when some are already removed by invoice validation.
3602 if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3603 if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3604 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3605 $adeduire = 0;
3606 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3607 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3608 $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'))";
3609 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3610 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3611
3612 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3613 $resql = $this->db->query($sql);
3614 if ($resql) {
3615 if ($this->db->num_rows($resql) > 0) {
3616 $obj = $this->db->fetch_object($resql);
3617 $adeduire += $obj->count;
3618 }
3619 }
3620
3621 $this->stats_commande['qty'] -= $adeduire;
3622 } else {
3623 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3624 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3625
3626 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3627 $adeduire = 0;
3628 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
3629 $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
3630 $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'))";
3631 $sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
3632 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3633
3634 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3635 $resql = $this->db->query($sql);
3636 if ($resql) {
3637 if ($this->db->num_rows($resql) > 0) {
3638 $obj = $this->db->fetch_object($resql);
3639 $adeduire += $obj->count;
3640 }
3641 } else {
3642 $this->error = $this->db->error();
3643 return -1;
3644 }
3645
3646 $this->stats_commande['qty'] -= $adeduire;
3647 }
3648 }
3649
3650 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3651 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3652 if ($reshook > 0) {
3653 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3654 }
3655 return 1;
3656 } else {
3657 $this->error = $this->db->error();
3658 return -1;
3659 }
3660 }
3661
3662 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3672 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3673 {
3674 // phpcs:enable
3675 global $user, $hookmanager, $action;
3676
3677 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3678 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3679 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3680 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3681 $sql .= ", ".$this->db->prefix()."societe as s";
3682 $sql .= " WHERE c.rowid = cd.fk_commande";
3683 $sql .= " AND c.fk_soc = s.rowid";
3684 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3685 $sql .= " AND cd.fk_product = ".((int) $this->id);
3686 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3687 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3688 }
3689 if ($socid > 0) {
3690 $sql .= " AND c.fk_soc = ".((int) $socid);
3691 }
3692 if ($filtrestatut != '') {
3693 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3694 }
3695 if (!empty($dateofvirtualstock)) {
3696 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3697 }
3698
3699 $result = $this->db->query($sql);
3700 if ($result) {
3701 $obj = $this->db->fetch_object($result);
3702 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3703 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3704 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3705 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3706
3707 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3708 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3709 if ($reshook > 0) {
3710 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3711 }
3712
3713 return 1;
3714 } else {
3715 $this->error = $this->db->error().' sql='.$sql;
3716 return -1;
3717 }
3718 }
3719
3720 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3730 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3731 {
3732 // phpcs:enable
3733 global $user, $hookmanager, $action;
3734
3735 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3736 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3737 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3738 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3739 $sql .= ", ".$this->db->prefix()."commande as c";
3740 $sql .= ", ".$this->db->prefix()."expedition as e";
3741 $sql .= ", ".$this->db->prefix()."societe as s";
3742 $sql .= " WHERE e.rowid = ed.fk_expedition";
3743 $sql .= " AND c.rowid = cd.fk_commande";
3744 $sql .= " AND e.fk_soc = s.rowid";
3745 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3746 $sql .= " AND ed.fk_elementdet = cd.rowid";
3747 $sql .= " AND cd.fk_product = ".((int) $this->id);
3748 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3749 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = e.fk_soc AND sc.fk_user = ".((int) $user->id);
3750 }
3751 if ($socid > 0) {
3752 $sql .= " AND e.fk_soc = ".((int) $socid);
3753 }
3754 if ($filtrestatut != '') {
3755 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3756 }
3757 if (!empty($filterShipmentStatus)) {
3758 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3759 }
3760
3761 $result = $this->db->query($sql);
3762 if ($result) {
3763 $obj = $this->db->fetch_object($result);
3764 $this->stats_expedition['customers'] = $obj->nb_customers;
3765 $this->stats_expedition['nb'] = $obj->nb;
3766 $this->stats_expedition['rows'] = $obj->nb_rows;
3767 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3768
3769 // if it's a virtual product, maybe it is in sending by extension
3770 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3771 $TFather = $this->getFather();
3772 if (is_array($TFather) && !empty($TFather)) {
3773 foreach ($TFather as &$fatherData) {
3774 $pFather = new Product($this->db);
3775 $pFather->id = $fatherData['id'];
3776 $qtyCoef = $fatherData['qty'];
3777
3778 if ($fatherData['incdec']) {
3779 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3780
3781 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3782 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3783 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3784 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3785 }
3786 }
3787 }
3788 }
3789
3790 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3791 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3792 if ($reshook > 0) {
3793 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3794 }
3795
3796 return 1;
3797 } else {
3798 $this->error = $this->db->error();
3799 return -1;
3800 }
3801 }
3802
3803 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3813 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3814 {
3815 // phpcs:enable
3816 global $user, $hookmanager, $action;
3817
3818 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3819 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3820 $sql .= " FROM ".$this->db->prefix()."receptiondet_batch as fd";
3821 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3822 $sql .= ", ".$this->db->prefix()."societe as s";
3823 $sql .= " WHERE cf.rowid = fd.fk_element";
3824 $sql .= " AND cf.fk_soc = s.rowid";
3825 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3826 $sql .= " AND fd.fk_product = ".((int) $this->id);
3827 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3828 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = cf.fk_soc AND sc.fk_user = ".((int) $user->id);
3829 }
3830 if ($socid > 0) {
3831 $sql .= " AND cf.fk_soc = ".((int) $socid);
3832 }
3833 if ($filtrestatut != '') {
3834 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3835 }
3836 if (!empty($dateofvirtualstock)) {
3837 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3838 }
3839
3840 $result = $this->db->query($sql);
3841 if ($result) {
3842 $obj = $this->db->fetch_object($result);
3843 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3844 $this->stats_reception['nb'] = $obj->nb;
3845 $this->stats_reception['rows'] = $obj->nb_rows;
3846 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3847
3848 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3849 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3850 if ($reshook > 0) {
3851 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3852 }
3853
3854 return 1;
3855 } else {
3856 $this->error = $this->db->error();
3857 return -1;
3858 }
3859 }
3860
3861 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3872 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null, $warehouseid = 0)
3873 {
3874 // phpcs:enable
3875 global $user, $hookmanager, $action;
3876
3877 $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
3878
3879 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3880 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3881 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3882 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3883 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3884 $sql .= " WHERE m.rowid = mp.fk_mo";
3885 $sql .= " AND m.entity IN (".getEntity(($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE')) ? 'stock' : 'mrp').")";
3886 $sql .= " AND mp.fk_product = ".((int) $this->id);
3887 $sql .= " AND (mp.disable_stock_change IN (0) OR mp.disable_stock_change IS NULL)";
3888 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3889 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = m.fk_soc AND sc.fk_user = ".((int) $user->id);
3890 }
3891 if ($socid > 0) {
3892 $sql .= " AND m.fk_soc = ".((int) $socid);
3893 }
3894 if ($filtrestatut != '') {
3895 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3896 }
3897 if (!empty($dateofvirtualstock)) {
3898 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3899 }
3900 if (!$serviceStockIsEnabled) {
3901 $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))";
3902 }
3903 if (!empty($warehouseid)) {
3904 $sql .= " AND m.fk_warehouse = ".((int) $warehouseid);
3905 }
3906 $sql .= " GROUP BY role";
3907
3908 if ($warehouseid) {
3909 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3910 } else {
3911 $this->stats_mrptoconsume['customers'] = 0;
3912 $this->stats_mrptoconsume['nb'] = 0;
3913 $this->stats_mrptoconsume['rows'] = 0;
3914 $this->stats_mrptoconsume['qty'] = 0.0;
3915 $this->stats_mrptoproduce['customers'] = 0;
3916 $this->stats_mrptoproduce['nb'] = 0;
3917 $this->stats_mrptoproduce['rows'] = 0;
3918 $this->stats_mrptoproduce['qty'] = 0.0;
3919 }
3920
3921 $result = $this->db->query($sql);
3922 if ($result) {
3923 while ($obj = $this->db->fetch_object($result)) {
3924 if ($obj->role == 'toconsume' && empty($warehouseid)) {
3925 $this->stats_mrptoconsume['customers'] += (int) $obj->nb_customers;
3926 $this->stats_mrptoconsume['nb'] += (int) $obj->nb;
3927 $this->stats_mrptoconsume['rows'] += (int) $obj->nb_rows;
3928 $this->stats_mrptoconsume['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
3929 }
3930 if ($obj->role == 'consumed' && empty($warehouseid)) {
3931 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3932 //$this->stats_mrptoconsume['nb'] += $obj->nb;
3933 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3934 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? (float) $obj->qty : 0.0);
3935 }
3936 if ($obj->role == 'toproduce') {
3937 if ($warehouseid) {
3938 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
3939 } else {
3940 $this->stats_mrptoproduce['customers'] += (int) $obj->nb_customers;
3941 $this->stats_mrptoproduce['nb'] += (int) $obj->nb;
3942 $this->stats_mrptoproduce['rows'] += (int) $obj->nb_rows;
3943 $this->stats_mrptoproduce['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
3944 }
3945 }
3946 if ($obj->role == 'produced') {
3947 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3948 //$this->stats_mrptoproduce['nb'] += $obj->nb;
3949 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3950 if ($warehouseid) {
3951 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3952 } else {
3953 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3954 }
3955 }
3956 }
3957
3958 // Clean data
3959 if ($warehouseid) {
3960 if ($this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] < 0) {
3961 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3962 }
3963 } else {
3964 if ($this->stats_mrptoconsume['qty'] < 0) {
3965 $this->stats_mrptoconsume['qty'] = 0;
3966 }
3967 if ($this->stats_mrptoproduce['qty'] < 0) {
3968 $this->stats_mrptoproduce['qty'] = 0;
3969 }
3970 }
3971
3972 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3973 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
3974 if ($reshook > 0) {
3975 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
3976 }
3977
3978 return 1;
3979 } else {
3980 $this->error = $this->db->error();
3981 return -1;
3982 }
3983 }
3984
3985 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3992 public function load_stats_contrat($socid = 0)
3993 {
3994 // phpcs:enable
3995 global $user, $hookmanager, $action;
3996
3997 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3998 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3999 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
4000 $sql .= ", ".$this->db->prefix()."contrat as c";
4001 $sql .= ", ".$this->db->prefix()."societe as s";
4002 $sql .= " WHERE c.rowid = cd.fk_contrat";
4003 $sql .= " AND c.fk_soc = s.rowid";
4004 $sql .= " AND c.entity IN (".getEntity('contract').")";
4005 $sql .= " AND cd.fk_product = ".((int) $this->id);
4006 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4007 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
4008 }
4009 //$sql.= " AND c.statut != 0";
4010 if ($socid > 0) {
4011 $sql .= " AND c.fk_soc = ".((int) $socid);
4012 }
4013
4014 $result = $this->db->query($sql);
4015 if ($result) {
4016 $obj = $this->db->fetch_object($result);
4017 $this->stats_contrat['customers'] = $obj->nb_customers;
4018 $this->stats_contrat['nb'] = $obj->nb;
4019 $this->stats_contrat['rows'] = $obj->nb_rows;
4020 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
4021
4022 // if it's a virtual product, maybe it is in contract by extension
4023 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4024 $TFather = $this->getFather();
4025 if (is_array($TFather) && !empty($TFather)) {
4026 foreach ($TFather as &$fatherData) {
4027 $pFather = new Product($this->db);
4028 $pFather->id = $fatherData['id'];
4029 $qtyCoef = $fatherData['qty'];
4030
4031 if ($fatherData['incdec']) {
4032 $pFather->load_stats_contrat($socid);
4033
4034 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
4035 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
4036 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
4037 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
4038 }
4039 }
4040 }
4041 }
4042
4043 $parameters = array('socid' => $socid);
4044 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
4045 if ($reshook > 0) {
4046 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
4047 }
4048
4049 return 1;
4050 } else {
4051 $this->error = $this->db->error().' sql='.$sql;
4052 return -1;
4053 }
4054 }
4055
4056 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4063 public function load_stats_facture($socid = 0)
4064 {
4065 // phpcs:enable
4066 global $user, $hookmanager, $action;
4067
4068 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
4069 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
4070 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
4071 $sql .= ", ".$this->db->prefix()."facture as f";
4072 $sql .= ", ".$this->db->prefix()."societe as s";
4073 $sql .= " WHERE f.rowid = fd.fk_facture";
4074 $sql .= " AND f.fk_soc = s.rowid";
4075 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4076 $sql .= " AND fd.fk_product = ".((int) $this->id);
4077 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4078 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4079 }
4080 //$sql.= " AND f.fk_statut != 0";
4081 if ($socid > 0) {
4082 $sql .= " AND f.fk_soc = ".((int) $socid);
4083 }
4084
4085 $result = $this->db->query($sql);
4086 if ($result) {
4087 $obj = $this->db->fetch_object($result);
4088 $this->stats_facture['customers'] = $obj->nb_customers;
4089 $this->stats_facture['nb'] = $obj->nb;
4090 $this->stats_facture['rows'] = $obj->nb_rows;
4091 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
4092
4093 // if it's a virtual product, maybe it is in invoice by extension
4094 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4095 $TFather = $this->getFather();
4096 if (is_array($TFather) && !empty($TFather)) {
4097 foreach ($TFather as &$fatherData) {
4098 $pFather = new Product($this->db);
4099 $pFather->id = $fatherData['id'];
4100 $qtyCoef = $fatherData['qty'];
4101
4102 if ($fatherData['incdec']) {
4103 $pFather->load_stats_facture($socid);
4104
4105 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
4106 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
4107 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
4108 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
4109 }
4110 }
4111 }
4112 }
4113
4114 $parameters = array('socid' => $socid);
4115 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
4116 if ($reshook > 0) {
4117 $this->stats_facture = $hookmanager->resArray['stats_facture'];
4118 }
4119
4120 return 1;
4121 } else {
4122 $this->error = $this->db->error();
4123 return -1;
4124 }
4125 }
4126
4127
4128 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4135 public function load_stats_facturerec($socid = 0)
4136 {
4137 // phpcs:enable
4138 global $user, $hookmanager, $action;
4139
4140 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
4141 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4142 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
4143 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
4144 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
4145 $sql .= " WHERE f.rowid = fd.fk_facture";
4146 $sql .= " AND f.fk_soc = s.rowid";
4147 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4148 $sql .= " AND fd.fk_product = ".((int) $this->id);
4149 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4150 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4151 }
4152 //$sql.= " AND f.fk_statut != 0";
4153 if ($socid > 0) {
4154 $sql .= " AND f.fk_soc = ".((int) $socid);
4155 }
4156
4157 $result = $this->db->query($sql);
4158 if ($result) {
4159 $obj = $this->db->fetch_object($result);
4160 $this->stats_facturerec['customers'] = (int) $obj->nb_customers;
4161 $this->stats_facturerec['nb'] = (int) $obj->nb;
4162 $this->stats_facturerec['rows'] = (int) $obj->nb_rows;
4163 $this->stats_facturerec['qty'] = $obj->qty ? (float) $obj->qty : 0.0;
4164
4165 // if it's a virtual product, maybe it is in invoice by extension
4166 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4167 $TFather = $this->getFather();
4168 if (is_array($TFather) && !empty($TFather)) {
4169 foreach ($TFather as &$fatherData) {
4170 $pFather = new Product($this->db);
4171 $pFather->id = $fatherData['id'];
4172 $qtyCoef = $fatherData['qty'];
4173
4174 if ($fatherData['incdec']) {
4175 $pFather->load_stats_facture($socid);
4176
4177 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
4178 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
4179 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
4180 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
4181 }
4182 }
4183 }
4184 }
4185
4186 $parameters = array('socid' => $socid);
4187 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
4188 if ($reshook > 0) {
4189 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
4190 }
4191
4192 return 1;
4193 } else {
4194 $this->error = $this->db->error();
4195 return -1;
4196 }
4197 }
4198
4199 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4206 public function load_stats_facture_fournisseur($socid = 0)
4207 {
4208 // phpcs:enable
4209 global $user, $hookmanager, $action;
4210
4211 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
4212 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4213 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
4214 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
4215 $sql .= ", ".$this->db->prefix()."societe as s";
4216 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
4217 $sql .= " AND f.fk_soc = s.rowid";
4218 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4219 $sql .= " AND fd.fk_product = ".((int) $this->id);
4220 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4221 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4222 }
4223 //$sql.= " AND f.fk_statut != 0";
4224 if ($socid > 0) {
4225 $sql .= " AND f.fk_soc = ".((int) $socid);
4226 }
4227
4228 $result = $this->db->query($sql);
4229 if ($result) {
4230 $obj = $this->db->fetch_object($result);
4231 $this->stats_facture_fournisseur['suppliers'] = (int) $obj->nb_suppliers;
4232 $this->stats_facture_fournisseur['nb'] = (int) $obj->nb;
4233 $this->stats_facture_fournisseur['rows'] = (int) $obj->nb_rows;
4234 $this->stats_facture_fournisseur['qty'] = $obj->qty ? (float) $obj->qty : 0.0;
4235
4236 $parameters = array('socid' => $socid);
4237 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
4238 if ($reshook > 0) {
4239 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
4240 }
4241
4242 return 1;
4243 } else {
4244 $this->error = $this->db->error();
4245 return -1;
4246 }
4247 }
4248
4249 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4258 private function _get_stats($sql, $mode, $year = 0)
4259 {
4260 // phpcs:enable
4261 $tab = array();
4262
4263 $resql = $this->db->query($sql);
4264 if ($resql) {
4265 $num = $this->db->num_rows($resql);
4266 $i = 0;
4267 while ($i < $num) {
4268 $arr = $this->db->fetch_array($resql);
4269 if (is_array($arr)) {
4270 $keyfortab = (string) $arr[1];
4271 if ($year == -1) {
4272 $keyfortab = substr($keyfortab, -2);
4273 }
4274
4275 if ($mode == 'byunit') {
4276 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
4277 } elseif ($mode == 'bynumber') {
4278 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
4279 } elseif ($mode == 'byamount') {
4280 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
4281 } else {
4282 // Bad value for $mode
4283 return -1;
4284 }
4285 }
4286 $i++;
4287 }
4288 } else {
4289 $this->error = $this->db->error().' sql='.$sql;
4290 return -1;
4291 }
4292
4293 if (empty($year)) {
4294 $year = dol_print_date(time(), '%Y');
4295 $month = dol_print_date(time(), '%m');
4296 } elseif ($year == -1) {
4297 $year = '';
4298 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4299 } else {
4300 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4301 }
4302
4303 $result = array();
4304
4305 for ($j = 0; $j < 12; $j++) {
4306 // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
4307 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
4308
4309 //print $idx.'-'.$year.'-'.$month.'<br>';
4310 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
4311 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
4312
4313 $month = "0".($month - 1);
4314 if (dol_strlen($month) == 3) {
4315 $month = substr($month, 1);
4316 }
4317 if ($month == 0) {
4318 $month = 12;
4319 $year -= 1;
4320 }
4321 }
4322
4323 return array_reverse($result);
4324 }
4325
4326
4327 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4338 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4339 {
4340 // phpcs:enable
4341 global $user;
4342
4343 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4344 if ($mode == 'bynumber') {
4345 $sql .= ", count(DISTINCT f.rowid)";
4346 }
4347 $sql .= ", sum(d.total_ht) as total_ht";
4348 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
4349 if ($filteronproducttype >= 0) {
4350 $sql .= ", ".$this->db->prefix()."product as p";
4351 }
4352 if (!$user->hasRight('societe', 'client', 'voir')) {
4353 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4354 }
4355 $sql .= " WHERE f.rowid = d.fk_facture";
4356 if ($this->id > 0) {
4357 $sql .= " AND d.fk_product = ".((int) $this->id);
4358 } else {
4359 $sql .= " AND d.fk_product > 0";
4360 }
4361 if ($filteronproducttype >= 0) {
4362 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4363 }
4364 $sql .= " AND f.fk_soc = s.rowid";
4365 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4366 if (!$user->hasRight('societe', 'client', 'voir')) {
4367 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4368 }
4369 if ($socid > 0) {
4370 $sql .= " AND f.fk_soc = $socid";
4371 }
4372 $sql .= $morefilter;
4373 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4374 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4375
4376 return $this->_get_stats($sql, $mode, $year);
4377 }
4378
4379
4380 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4391 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4392 {
4393 // phpcs:enable
4394 global $user;
4395
4396 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4397 if ($mode == 'bynumber') {
4398 $sql .= ", count(DISTINCT f.rowid)";
4399 }
4400 $sql .= ", sum(d.total_ht) as total_ht";
4401 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
4402 if ($filteronproducttype >= 0) {
4403 $sql .= ", ".$this->db->prefix()."product as p";
4404 }
4405 if (!$user->hasRight('societe', 'client', 'voir')) {
4406 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4407 }
4408 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4409 if ($this->id > 0) {
4410 $sql .= " AND d.fk_product = ".((int) $this->id);
4411 } else {
4412 $sql .= " AND d.fk_product > 0";
4413 }
4414 if ($filteronproducttype >= 0) {
4415 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4416 }
4417 $sql .= " AND f.fk_soc = s.rowid";
4418 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4419 if (!$user->hasRight('societe', 'client', 'voir')) {
4420 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4421 }
4422 if ($socid > 0) {
4423 $sql .= " AND f.fk_soc = $socid";
4424 }
4425 $sql .= $morefilter;
4426 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4427 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4428
4429 return $this->_get_stats($sql, $mode, $year);
4430 }
4431
4432 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4443 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4444 {
4445 // phpcs:enable
4446 global $user;
4447
4448 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4449 if ($mode == 'bynumber') {
4450 $sql .= ", count(DISTINCT p.rowid)";
4451 }
4452 $sql .= ", sum(d.total_ht) as total_ht";
4453 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4454 if ($filteronproducttype >= 0) {
4455 $sql .= ", ".$this->db->prefix()."product as prod";
4456 }
4457 if (!$user->hasRight('societe', 'client', 'voir')) {
4458 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4459 }
4460 $sql .= " WHERE p.rowid = d.fk_propal";
4461 if ($this->id > 0) {
4462 $sql .= " AND d.fk_product = ".((int) $this->id);
4463 } else {
4464 $sql .= " AND d.fk_product > 0";
4465 }
4466 if ($filteronproducttype >= 0) {
4467 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4468 }
4469 $sql .= " AND p.fk_soc = s.rowid";
4470 $sql .= " AND p.entity IN (".getEntity('propal').")";
4471 if (!$user->hasRight('societe', 'client', 'voir')) {
4472 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4473 }
4474 if ($socid > 0) {
4475 $sql .= " AND p.fk_soc = ".((int) $socid);
4476 }
4477 $sql .= $morefilter;
4478 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4479 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4480
4481 return $this->_get_stats($sql, $mode, $year);
4482 }
4483
4484 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4495 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4496 {
4497 // phpcs:enable
4498 global $user;
4499
4500 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4501 if ($mode == 'bynumber') {
4502 $sql .= ", count(DISTINCT p.rowid)";
4503 }
4504 $sql .= ", sum(d.total_ht) as total_ht";
4505 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4506 if ($filteronproducttype >= 0) {
4507 $sql .= ", ".$this->db->prefix()."product as prod";
4508 }
4509 if (!$user->hasRight('societe', 'client', 'voir')) {
4510 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4511 }
4512 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4513 if ($this->id > 0) {
4514 $sql .= " AND d.fk_product = ".((int) $this->id);
4515 } else {
4516 $sql .= " AND d.fk_product > 0";
4517 }
4518 if ($filteronproducttype >= 0) {
4519 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4520 }
4521 $sql .= " AND p.fk_soc = s.rowid";
4522 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4523 if (!$user->hasRight('societe', 'client', 'voir')) {
4524 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4525 }
4526 if ($socid > 0) {
4527 $sql .= " AND p.fk_soc = ".((int) $socid);
4528 }
4529 $sql .= $morefilter;
4530 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4531 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4532
4533 return $this->_get_stats($sql, $mode, $year);
4534 }
4535
4536 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4547 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4548 {
4549 // phpcs:enable
4550 global $user;
4551
4552 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4553 if ($mode == 'bynumber') {
4554 $sql .= ", count(DISTINCT c.rowid)";
4555 }
4556 $sql .= ", sum(d.total_ht) as total_ht";
4557 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4558 if ($filteronproducttype >= 0) {
4559 $sql .= ", ".$this->db->prefix()."product as p";
4560 }
4561 if (!$user->hasRight('societe', 'client', 'voir')) {
4562 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4563 }
4564 $sql .= " WHERE c.rowid = d.fk_commande";
4565 if ($this->id > 0) {
4566 $sql .= " AND d.fk_product = ".((int) $this->id);
4567 } else {
4568 $sql .= " AND d.fk_product > 0";
4569 }
4570 if ($filteronproducttype >= 0) {
4571 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4572 }
4573 $sql .= " AND c.fk_soc = s.rowid";
4574 $sql .= " AND c.entity IN (".getEntity('commande').")";
4575 if (!$user->hasRight('societe', 'client', 'voir')) {
4576 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4577 }
4578 if ($socid > 0) {
4579 $sql .= " AND c.fk_soc = ".((int) $socid);
4580 }
4581 $sql .= $morefilter;
4582 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4583 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4584
4585 return $this->_get_stats($sql, $mode, $year);
4586 }
4587
4588 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4599 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4600 {
4601 // phpcs:enable
4602 global $user;
4603
4604 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4605 if ($mode == 'bynumber') {
4606 $sql .= ", count(DISTINCT c.rowid)";
4607 }
4608 $sql .= ", sum(d.total_ht) as total_ht";
4609 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4610 if ($filteronproducttype >= 0) {
4611 $sql .= ", ".$this->db->prefix()."product as p";
4612 }
4613 if (!$user->hasRight('societe', 'client', 'voir')) {
4614 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4615 }
4616 $sql .= " WHERE c.rowid = d.fk_commande";
4617 if ($this->id > 0) {
4618 $sql .= " AND d.fk_product = ".((int) $this->id);
4619 } else {
4620 $sql .= " AND d.fk_product > 0";
4621 }
4622 if ($filteronproducttype >= 0) {
4623 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4624 }
4625 $sql .= " AND c.fk_soc = s.rowid";
4626 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4627 if (!$user->hasRight('societe', 'client', 'voir')) {
4628 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4629 }
4630 if ($socid > 0) {
4631 $sql .= " AND c.fk_soc = ".((int) $socid);
4632 }
4633 $sql .= $morefilter;
4634 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4635 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4636
4637 return $this->_get_stats($sql, $mode, $year);
4638 }
4639
4640 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4651 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4652 {
4653 // phpcs:enable
4654 global $user;
4655
4656 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4657 if ($mode == 'bynumber') {
4658 $sql .= ", count(DISTINCT c.rowid)";
4659 }
4660 $sql .= ", sum(d.total_ht) as total_ht";
4661 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4662 if ($filteronproducttype >= 0) {
4663 $sql .= ", ".$this->db->prefix()."product as p";
4664 }
4665 if (!$user->hasRight('societe', 'client', 'voir')) {
4666 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4667 }
4668 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4669 $sql .= " AND c.rowid = d.fk_contrat";
4670
4671 if ($this->id > 0) {
4672 $sql .= " AND d.fk_product = ".((int) $this->id);
4673 } else {
4674 $sql .= " AND d.fk_product > 0";
4675 }
4676 if ($filteronproducttype >= 0) {
4677 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4678 }
4679 $sql .= " AND c.fk_soc = s.rowid";
4680
4681 if (!$user->hasRight('societe', 'client', 'voir')) {
4682 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4683 }
4684 if ($socid > 0) {
4685 $sql .= " AND c.fk_soc = ".((int) $socid);
4686 }
4687 $sql .= $morefilter;
4688 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4689 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4690
4691 return $this->_get_stats($sql, $mode, $year);
4692 }
4693
4694 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4705 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4706 {
4707 // phpcs:enable
4708 global $user;
4709
4710 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4711 if ($mode == 'bynumber') {
4712 $sql .= ", count(DISTINCT d.rowid)";
4713 }
4714 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4715 if ($filteronproducttype >= 0) {
4716 $sql .= ", ".$this->db->prefix()."product as p";
4717 }
4718 if (!$user->hasRight('societe', 'client', 'voir')) {
4719 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4720 }
4721
4722 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4723 $sql .= " AND d.status > 0";
4724
4725 if ($this->id > 0) {
4726 $sql .= " AND d.fk_product = ".((int) $this->id);
4727 } else {
4728 $sql .= " AND d.fk_product > 0";
4729 }
4730 if ($filteronproducttype >= 0) {
4731 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4732 }
4733
4734 if (!$user->hasRight('societe', 'client', 'voir')) {
4735 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4736 }
4737 if ($socid > 0) {
4738 $sql .= " AND d.fk_soc = ".((int) $socid);
4739 }
4740 $sql .= $morefilter;
4741 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4742 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4743
4744 return $this->_get_stats($sql, $mode, $year);
4745 }
4746
4747 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4758 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4759 {
4760 global $user;
4761
4762 // phpcs:enable
4763 // Clean parameters
4764 if (!is_numeric($id_pere)) {
4765 $id_pere = 0;
4766 }
4767 if (!is_numeric($id_fils)) {
4768 $id_fils = 0;
4769 }
4770 if (!is_numeric($incdec)) {
4771 $incdec = 0;
4772 }
4773
4774 $result = $this->del_sousproduit($id_pere, $id_fils);
4775 if ($result < 0) {
4776 return $result;
4777 }
4778
4779 // Check not already father of id_pere (to avoid father -> child -> father links)
4780 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4781 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4782 if (!$this->db->query($sql)) {
4783 dol_print_error($this->db);
4784 return -1;
4785 } else {
4786 //Selection of the highest row
4787 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4788 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4789 $resql = $this->db->query($sql);
4790 if ($resql) {
4791 $obj = $this->db->fetch_object($resql);
4792 $rank = $obj->max_rank + 1;
4793 //Addition of a product with the highest rank +1
4794 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4795 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".((int) $incdec).", ".((int) $rank).")";
4796 if (! $this->db->query($sql)) {
4797 dol_print_error($this->db);
4798 return -1;
4799 } else {
4800 if (!$notrigger) {
4801 // Call trigger
4802 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
4803 if ($result < 0) {
4804 $this->error = $this->db->lasterror();
4805 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
4806 return -1;
4807 }
4808 }
4809 // End call triggers
4810
4811 return 1;
4812 }
4813 } else {
4814 dol_print_error($this->db);
4815 return -1;
4816 }
4817 }
4818 }
4819
4820 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4831 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4832 {
4833 global $user;
4834
4835 // phpcs:enable
4836 // Clean parameters
4837 if (!is_numeric($id_pere)) {
4838 $id_pere = 0;
4839 }
4840 if (!is_numeric($id_fils)) {
4841 $id_fils = 0;
4842 }
4843 if (!is_numeric($incdec)) {
4844 $incdec = 1;
4845 }
4846 if (!is_numeric($qty)) {
4847 $qty = 1;
4848 }
4849
4850 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4851 $sql .= 'qty = '.price2num($qty, 'MS');
4852 $sql .= ',incdec = '.((int) $incdec);
4853 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4854
4855 if (!$this->db->query($sql)) {
4856 dol_print_error($this->db);
4857 return -1;
4858 } else {
4859 if (!$notrigger) {
4860 // Call trigger
4861 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
4862 if ($result < 0) {
4863 $this->error = $this->db->lasterror();
4864 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
4865 return -1;
4866 }
4867 // End call triggers
4868 }
4869
4870 return 1;
4871 }
4872 }
4873
4874 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4883 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
4884 {
4885 global $user;
4886
4887 // phpcs:enable
4888 if (!is_numeric($fk_parent)) {
4889 $fk_parent = 0;
4890 }
4891 if (!is_numeric($fk_child)) {
4892 $fk_child = 0;
4893 }
4894
4895 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
4896 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4897 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4898
4899 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
4900 if (!$this->db->query($sql)) {
4901 dol_print_error($this->db);
4902 return -1;
4903 }
4904
4905 // Updated ranks so that none are missing
4906 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
4907 $sqlrank .= " WHERE fk_product_pere = ".((int) $fk_parent);
4908 $sqlrank .= " ORDER BY rang";
4909 $resqlrank = $this->db->query($sqlrank);
4910 if ($resqlrank) {
4911 $cpt = 0;
4912 while ($objrank = $this->db->fetch_object($resqlrank)) {
4913 $cpt++;
4914 $sql = "UPDATE ".$this->db->prefix()."product_association";
4915 $sql .= " SET rang = ".((int) $cpt);
4916 $sql .= " WHERE rowid = ".((int) $objrank->rowid);
4917 if (! $this->db->query($sql)) {
4918 dol_print_error($this->db);
4919 return -1;
4920 }
4921 }
4922 }
4923
4924 if (!$notrigger) {
4925 // Call trigger
4926 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
4927 if ($result < 0) {
4928 $this->error = $this->db->lasterror();
4929 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
4930 return -1;
4931 }
4932 // End call triggers
4933 }
4934
4935 return 1;
4936 }
4937
4938 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4946 public function is_sousproduit($fk_parent, $fk_child)
4947 {
4948 // phpcs:enable
4949 $sql = "SELECT fk_product_pere, qty, incdec";
4950 $sql .= " FROM ".$this->db->prefix()."product_association";
4951 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4952 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4953
4954 $result = $this->db->query($sql);
4955 if ($result) {
4956 $num = $this->db->num_rows($result);
4957
4958 if ($num > 0) {
4959 $obj = $this->db->fetch_object($result);
4960
4961 $this->is_sousproduit_qty = $obj->qty;
4962 $this->is_sousproduit_incdec = $obj->incdec;
4963
4964 return 1;
4965 } else {
4966 return 0;
4967 }
4968 } else {
4969 dol_print_error($this->db);
4970 return -1;
4971 }
4972 }
4973
4974
4975 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4986 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
4987 {
4988 // phpcs:enable
4989 global $conf;
4990
4991 $now = dol_now();
4992
4993 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
4994
4995 // Clean parameters
4996 $quantity = price2num($quantity, 'MS');
4997
4998 if ($ref_fourn) {
4999 // Check if ref is not already used
5000 $sql = "SELECT rowid, fk_product";
5001 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5002 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5003 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5004 $sql .= " AND fk_product <> ".((int) $this->id);
5005 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5006
5007 $resql = $this->db->query($sql);
5008 if ($resql) {
5009 $obj = $this->db->fetch_object($resql);
5010 if ($obj) {
5011 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
5012 $this->product_id_already_linked = $obj->fk_product;
5013 return -3;
5014 }
5015 $this->db->free($resql);
5016 }
5017 }
5018
5019 $sql = "SELECT rowid";
5020 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5021 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5022 if ($ref_fourn) {
5023 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5024 } else {
5025 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
5026 }
5027 $sql .= " AND quantity = ".((float) $quantity);
5028 $sql .= " AND fk_product = ".((int) $this->id);
5029 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5030
5031 $resql = $this->db->query($sql);
5032 if ($resql) {
5033 $obj = $this->db->fetch_object($resql);
5034
5035 // The reference supplier does not exist, we create it for this product.
5036 if (empty($obj)) {
5037 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
5038 $sql .= "datec";
5039 $sql .= ", entity";
5040 $sql .= ", fk_product";
5041 $sql .= ", fk_soc";
5042 $sql .= ", ref_fourn";
5043 $sql .= ", quantity";
5044 $sql .= ", fk_user";
5045 $sql .= ", tva_tx";
5046 $sql .= ") VALUES (";
5047 $sql .= "'".$this->db->idate($now)."'";
5048 $sql .= ", ".((int) $conf->entity);
5049 $sql .= ", ".((int) $this->id);
5050 $sql .= ", ".((int) $id_fourn);
5051 $sql .= ", '".$this->db->escape($ref_fourn)."'";
5052 $sql .= ", ".((float) $quantity);
5053 $sql .= ", ".((int) $user->id);
5054 $sql .= ", 0";
5055 $sql .= ")";
5056
5057 if ($this->db->query($sql)) {
5058 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
5059 return 1;
5060 } else {
5061 $this->error = $this->db->lasterror();
5062 return -1;
5063 }
5064 } else {
5065 // If the supplier price already exists for this product and quantity
5066 $this->product_fourn_price_id = $obj->rowid;
5067 return 0;
5068 }
5069 } else {
5070 $this->error = $this->db->lasterror();
5071 return -2;
5072 }
5073 }
5074
5075
5076 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5082 public function list_suppliers()
5083 {
5084 // phpcs:enable
5085 global $conf;
5086
5087 $list = array();
5088
5089 $sql = "SELECT DISTINCT p.fk_soc";
5090 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
5091 $sql .= " WHERE p.fk_product = ".((int) $this->id);
5092 $sql .= " AND p.entity = ".((int) $conf->entity);
5093
5094 $result = $this->db->query($sql);
5095 if ($result) {
5096 $num = $this->db->num_rows($result);
5097 $i = 0;
5098 while ($i < $num) {
5099 $obj = $this->db->fetch_object($result);
5100 $list[$i] = $obj->fk_soc;
5101 $i++;
5102 }
5103 }
5104
5105 return $list;
5106 }
5107
5108 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5116 public function clone_price($fromId, $toId)
5117 {
5118 global $user;
5119
5120 $now = dol_now();
5121
5122 $this->db->begin();
5123
5124 // prices
5125 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
5126 $sql .= " entity";
5127 $sql .= ", fk_product";
5128 $sql .= ", date_price";
5129 $sql .= ", price_level";
5130 $sql .= ", price";
5131 $sql .= ", price_ttc";
5132 $sql .= ", price_min";
5133 $sql .= ", price_min_ttc";
5134 $sql .= ", price_base_type";
5135 $sql .= ", price_label";
5136 $sql .= ", default_vat_code";
5137 $sql .= ", tva_tx";
5138 $sql .= ", recuperableonly";
5139 $sql .= ", localtax1_tx";
5140 $sql .= ", localtax1_type";
5141 $sql .= ", localtax2_tx";
5142 $sql .= ", localtax2_type";
5143 $sql .= ", fk_user_author";
5144 $sql .= ", tosell";
5145 $sql .= ", price_by_qty";
5146 $sql .= ", fk_price_expression";
5147 $sql .= ", fk_multicurrency";
5148 $sql .= ", multicurrency_code";
5149 $sql .= ", multicurrency_tx";
5150 $sql .= ", multicurrency_price";
5151 $sql .= ", multicurrency_price_ttc";
5152 $sql .= ")";
5153 $sql .= " SELECT";
5154 $sql .= " entity";
5155 $sql .= ", ".$toId;
5156 $sql .= ", '".$this->db->idate($now)."'";
5157 $sql .= ", price_level";
5158 $sql .= ", price";
5159 $sql .= ", price_ttc";
5160 $sql .= ", price_min";
5161 $sql .= ", price_min_ttc";
5162 $sql .= ", price_base_type";
5163 $sql .= ", price_label";
5164 $sql .= ", default_vat_code";
5165 $sql .= ", tva_tx";
5166 $sql .= ", recuperableonly";
5167 $sql .= ", localtax1_tx";
5168 $sql .= ", localtax1_type";
5169 $sql .= ", localtax2_tx";
5170 $sql .= ", localtax2_type";
5171 $sql .= ", ".$user->id;
5172 $sql .= ", tosell";
5173 $sql .= ", price_by_qty";
5174 $sql .= ", fk_price_expression";
5175 $sql .= ", fk_multicurrency";
5176 $sql .= ", multicurrency_code";
5177 $sql .= ", multicurrency_tx";
5178 $sql .= ", multicurrency_price";
5179 $sql .= ", multicurrency_price_ttc";
5180 $sql .= " FROM ".$this->db->prefix()."product_price ps";
5181 $sql .= " WHERE fk_product = ".((int) $fromId);
5182 $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)";
5183 $sql .= " ORDER BY date_price DESC";
5184
5185 dol_syslog(__METHOD__, LOG_DEBUG);
5186 $resql = $this->db->query($sql);
5187 if (!$resql) {
5188 $this->db->rollback();
5189 return -1;
5190 }
5191
5192 $this->db->commit();
5193 return 1;
5194 }
5195
5196 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5204 public function clone_associations($fromId, $toId)
5205 {
5206 // phpcs:enable
5207 $this->db->begin();
5208
5209 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
5210 $sql .= " SELECT ".$toId.", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
5211 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
5212
5213 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
5214 if (!$this->db->query($sql)) {
5215 $this->db->rollback();
5216 return -1;
5217 }
5218
5219 $this->db->commit();
5220 return 1;
5221 }
5222
5223 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5231 public function clone_fournisseurs($fromId, $toId)
5232 {
5233 // phpcs:enable
5234 $this->db->begin();
5235
5236 $now = dol_now();
5237
5238 // les fournisseurs
5239 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
5240 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
5241 . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
5242 . " FROM ".$this->db->prefix()."product_fournisseur"
5243 . " WHERE fk_product = ".((int) $fromId);
5244
5245 if ( ! $this->db->query($sql ) )
5246 {
5247 $this->db->rollback();
5248 return -1;
5249 }*/
5250
5251 // les prix de fournisseurs.
5252 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
5253 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
5254 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
5255 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5256 $sql .= " WHERE fk_product = ".((int) $fromId);
5257
5258 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
5259 $resql = $this->db->query($sql);
5260 if (!$resql) {
5261 $this->db->rollback();
5262 return -1;
5263 } else {
5264 $this->db->commit();
5265 return 1;
5266 }
5267 }
5268
5269 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5282 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
5283 {
5284 // phpcs:enable
5285 $tmpproduct = null;
5286
5287 //var_dump($prod);
5288 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
5289 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
5290 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
5291 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
5292 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
5293 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
5294 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
5295
5296 if ($multiply < 1) {
5297 $multiply = 1;
5298 }
5299
5300 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
5301 if (is_null($tmpproduct)) {
5302 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
5303 }
5304 $tmpproduct->fetch($id); // Load product to get ->ref
5305
5306 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
5307 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
5308 }
5309
5310 $this->res[] = array(
5311 'id' => $id, // Id product
5312 'id_parent' => $id_parent,
5313 'ref' => $tmpproduct->ref, // Ref product
5314 'nb' => $nb, // Nb of units that compose parent product
5315 'nb_total' => $nb * $multiply, // Nb of units for all nb of product
5316 'stock' => $tmpproduct->stock_reel, // Stock
5317 'stock_alert' => $tmpproduct->seuil_stock_alerte, // Stock alert
5318 'label' => $label,
5319 'fullpath' => $compl_path.$label, // Label
5320 'type' => $type, // Nb of units that compose parent product
5321 'desiredstock' => $tmpproduct->desiredstock,
5322 'level' => $level,
5323 'incdec' => $incdec,
5324 'entity' => $tmpproduct->entity
5325 );
5326
5327 // Recursive call if there child has children of its own
5328 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
5329 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
5330 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id, $ignore_stock_load);
5331 }
5332 }
5333 }
5334 }
5335
5336 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5345 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
5346 {
5347 // phpcs:enable
5348 $this->res = array();
5349 if (isset($this->sousprods) && is_array($this->sousprods)) {
5350 foreach ($this->sousprods as $prod_name => $desc_product) {
5351 if (is_array($desc_product)) {
5352 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
5353 }
5354 }
5355 }
5356 //var_dump($res);
5357 return $this->res;
5358 }
5359
5367 public function hasFatherOrChild($mode = 0)
5368 {
5369 $nb = 0;
5370
5371 $sql = "SELECT COUNT(pa.rowid) as nb";
5372 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
5373 if ($mode == 0) {
5374 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
5375 } elseif ($mode == -1) {
5376 $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)
5377 } elseif ($mode == 1) {
5378 $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)
5379 }
5380
5381 $resql = $this->db->query($sql);
5382 if ($resql) {
5383 $obj = $this->db->fetch_object($resql);
5384 if ($obj) {
5385 $nb = $obj->nb;
5386 }
5387 } else {
5388 return -1;
5389 }
5390
5391 return $nb;
5392 }
5393
5399 public function hasVariants()
5400 {
5401 $nb = 0;
5402 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
5403 $sql .= " AND entity IN (".getEntity('product').")";
5404
5405 $resql = $this->db->query($sql);
5406 if ($resql) {
5407 $obj = $this->db->fetch_object($resql);
5408 if ($obj) {
5409 $nb = $obj->nb;
5410 }
5411 }
5412
5413 return $nb;
5414 }
5415
5416
5422 public function isVariant()
5423 {
5424 if (isModEnabled('variants')) {
5425 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5426
5427 $query = $this->db->query($sql);
5428
5429 if ($query) {
5430 if (!$this->db->num_rows($query)) {
5431 return false;
5432 }
5433 return true;
5434 } else {
5435 dol_print_error($this->db);
5436 return -1;
5437 }
5438 } else {
5439 return false;
5440 }
5441 }
5442
5449 public function getFather()
5450 {
5451 $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";
5452 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5453 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5454 $sql .= " ".$this->db->prefix()."product as p";
5455 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5456 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5457
5458 $res = $this->db->query($sql);
5459 if ($res) {
5460 $prods = array();
5461 while ($record = $this->db->fetch_array($res)) {
5462 // $record['id'] = $record['rowid'] = id of father
5463 $prods[$record['id']] = array();
5464 $prods[$record['id']]['id'] = $record['rowid'];
5465 $prods[$record['id']]['ref'] = $record['ref'];
5466 $prods[$record['id']]['label'] = $record['label'];
5467 $prods[$record['id']]['qty'] = $record['qty'];
5468 $prods[$record['id']]['incdec'] = $record['incdec'];
5469 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5470 $prods[$record['id']]['entity'] = $record['entity'];
5471 $prods[$record['id']]['status'] = $record['status'];
5472 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5473 }
5474 return $prods;
5475 } else {
5476 dol_print_error($this->db);
5477 return -1;
5478 }
5479 }
5480
5481
5491 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5492 {
5493 if (empty($id)) {
5494 return array();
5495 }
5496
5497 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5498 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5499 $sql .= " pa.rowid as fk_association, pa.rang";
5500 $sql .= " FROM ".$this->db->prefix()."product as p,";
5501 $sql .= " ".$this->db->prefix()."product_association as pa";
5502 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5503 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5504 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5505 $sql .= " ORDER BY pa.rang";
5506
5507 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5508
5509 // Protection against infinite loop
5510 if ($level > 30) {
5511 return array();
5512 }
5513
5514 $res = $this->db->query($sql);
5515 if ($res) {
5516 $prods = array();
5517 if ($this->db->num_rows($res) > 0) {
5518 $parents[] = $id;
5519 }
5520
5521 while ($rec = $this->db->fetch_array($res)) {
5522 if (in_array($rec['id'], $parents)) {
5523 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);
5524 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5525 }
5526
5527 $prods[$rec['rowid']] = array(
5528 0 => $rec['rowid'],
5529 1 => $rec['qty'],
5530 2 => $rec['fk_product_type'],
5531 3 => $this->db->escape($rec['label']),
5532 4 => $rec['incdec'],
5533 5 => $rec['ref'],
5534 6 => $rec['fk_association'],
5535 7 => $rec['rang']
5536 );
5537 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5538 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5539 if (empty($firstlevelonly)) {
5540 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5541 foreach ($listofchilds as $keyChild => $valueChild) {
5542 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5543 }
5544 }
5545 }
5546
5547 return $prods;
5548 } else {
5549 dol_print_error($this->db);
5550 return -1;
5551 }
5552 }
5553
5554 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5561 public function get_sousproduits_arbo()
5562 {
5563 // phpcs:enable
5564 $parent = array();
5565
5566 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5567 $parent[$this->label][$keyChild] = $valueChild;
5568 }
5569 foreach ($parent as $key => $value) { // key=label, value is array of children
5570 $this->sousprods[$key] = $value; // @phan-suppress-current-line PhanTypeMismatchProperty
5571 }
5572 }
5573
5581 public function getTooltipContentArray($params)
5582 {
5583 global $conf, $langs, $user;
5584
5585 $langs->loadLangs(array('products', 'other'));
5586
5587 $datas = array();
5588 $nofetch = !empty($params['nofetch']);
5589
5590 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5591 return ['optimize' => $langs->trans("ShowProduct")];
5592 }
5593
5594 // Does user has permission to read product/service
5595 $permissiontoreadproduct = 0;
5596 if ($this->type == self::TYPE_PRODUCT && $user->hasRight('product', 'read')) {
5597 $permissiontoreadproduct = 1;
5598 }
5599 if ($this->type == self::TYPE_SERVICE && $user->hasRight('service', 'read')) {
5600 $permissiontoreadproduct = 1;
5601 }
5602
5603 if (!empty($this->entity) && $permissiontoreadproduct) {
5604 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, '1');
5605 if ($this->nbphoto > 0) {
5606 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5607 }
5608 }
5609
5610 if ($this->isProduct()) {
5611 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5612 } elseif ($this->isService()) {
5613 $datas['picto'] = img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5614 }
5615 if (isset($this->status) && isset($this->status_buy)) {
5616 $datas['status'] = ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5617 }
5618
5619 if (!empty($this->ref)) {
5620 $datas['ref'] = '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5621 }
5622 if (!empty($this->label)) {
5623 $datas['label'] = '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5624 }
5625
5626 if ($permissiontoreadproduct) {
5627 if (!empty($this->description)) {
5628 $datas['description'] = '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineOfText($this->description, 5);
5629 }
5630 if ($this->isStockManaged()) {
5631 if (isModEnabled('productbatch')) {
5632 $langs->load("productbatch");
5633 $datas['batchstatus'] = "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5634 if ($this->status_batch) {
5635 $datas['batchdlc'] = "<br><b>".$langs->trans("BatchSellOrEatByMandatoryList", $langs->transnoentitiesnoconv("SellByDate"), $langs->transnoentitiesnoconv("EatByDate")).'</b>: '.$this->getSellOrEatByMandatoryLabel();
5636 }
5637 }
5638 }
5639 if (isModEnabled('barcode')) {
5640 $datas['barcode'] = '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5641 }
5642
5643 if ($this->isProduct()) {
5644 if ($this->weight) {
5645 $datas['weight'] = "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5646 }
5647 $labelsize = "";
5648 if ($this->length) {
5649 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5650 }
5651 if ($this->width) {
5652 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5653 }
5654 if ($this->height) {
5655 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5656 }
5657 if ($labelsize) {
5658 $datas['size'] = "<br>".$labelsize;
5659 }
5660
5661 $labelsurfacevolume = "";
5662 if ($this->surface) {
5663 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5664 }
5665 if ($this->volume) {
5666 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5667 }
5668 if ($labelsurfacevolume) {
5669 $datas['surface'] = "<br>" . $labelsurfacevolume;
5670 }
5671 }
5672 if ($this->isService() && !empty($this->duration_value)) {
5673 // Duration
5674 $datas['duration'] = '<br><b>'.$langs->trans("Duration").':</b> '.$this->duration_value;
5675 if ($this->duration_value > 1) {
5676 $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"));
5677 } elseif ($this->duration_value > 0) {
5678 $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"));
5679 }
5680 $datas['duration'] .= (!empty($this->duration_unit) && isset($dur[$this->duration_unit]) ? "&nbsp;".$langs->trans($dur[$this->duration_unit]) : '');
5681 }
5682 if (empty($user->socid)) {
5683 if (!empty($this->pmp) && $this->pmp) {
5684 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5685 }
5686
5687 if (isModEnabled('accounting')) {
5688 if ($this->status && isset($this->accountancy_code_sell)) {
5689 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5690 $selllabel = '<br>';
5691 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5692 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5693 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5694 $datas['accountancysell'] = $selllabel;
5695 }
5696 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5697 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5698 $buylabel = '';
5699 if (empty($this->status)) {
5700 $buylabel .= '<br>';
5701 }
5702 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5703 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5704 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5705 $datas['accountancybuy'] = $buylabel;
5706 }
5707 }
5708 }
5709 // show categories for this record only in ajax to not overload lists
5710 if (isModEnabled('category') && !$nofetch) {
5711 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5712 $form = new Form($this->db);
5713 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5714 }
5715 }
5716
5717 return $datas;
5718 }
5719
5733 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5734 {
5735 global $langs, $hookmanager;
5736
5737 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5738
5739 $result = '';
5740
5741 $newref = $this->ref;
5742 if ($maxlength) {
5743 $newref = dol_trunc($newref, $maxlength, 'middle');
5744 }
5745 $params = [
5746 'id' => $this->id,
5747 'objecttype' => ($this->type == 1 ? 'service' : 'product'),
5748 'option' => $option,
5749 'nofetch' => 1,
5750 ];
5751 $classfortooltip = 'classfortooltip';
5752 $dataparams = '';
5753 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5754 $classfortooltip = 'classforajaxtooltip';
5755 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5756 $label = '';
5757 } else {
5758 $label = implode($this->getTooltipContentArray($params));
5759 }
5760
5761 $linkclose = '';
5762 if (empty($notooltip)) {
5763 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5764 $label = $langs->trans("ShowProduct");
5765 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5766 }
5767 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5768 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5769 } else {
5770 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5771 }
5772
5773 if ($option == 'supplier' || $option == 'category') {
5774 $url = DOL_URL_ROOT.'/product/price_suppliers.php?id='.$this->id;
5775 } elseif ($option == 'stock') {
5776 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5777 } elseif ($option == 'composition') {
5778 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5779 } else {
5780 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5781 }
5782
5783 if ($option !== 'nolink') {
5784 // Add param to save lastsearch_values or not
5785 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5786 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5787 $add_save_lastsearch_values = 1;
5788 }
5789 if ($add_save_lastsearch_values) {
5790 $url .= '&save_lastsearch_values=1';
5791 }
5792 }
5793
5794 $linkstart = '<a href="'.$url.'"';
5795 $linkstart .= $linkclose.'>';
5796 $linkend = '</a>';
5797
5798 $result .= $linkstart;
5799 if ($withpicto) {
5800 if ($this->isProduct()) {
5801 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5802 }
5803 if ($this->isService()) {
5804 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5805 }
5806 }
5807 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
5808 $result .= $linkend;
5809 if ($withpicto != 2) {
5810 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5811 }
5812
5813 global $action;
5814 $hookmanager->initHooks(array('productdao'));
5815 $parameters = array('id' => $this->id, 'getnomurl' => &$result, 'label' => &$label);
5816 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5817 if ($reshook > 0) {
5818 $result = $hookmanager->resPrint;
5819 } else {
5820 $result .= $hookmanager->resPrint;
5821 }
5822
5823 return $result;
5824 }
5825
5826
5837 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5838 {
5839 global $langs;
5840
5841 $langs->load("products");
5842 $outputlangs->load("products");
5843
5844 // Positionne le modele sur le nom du modele a utiliser
5845 if (!dol_strlen($modele)) {
5846 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5847 }
5848
5849 $modelpath = "core/modules/product/doc/";
5850
5851 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5852 }
5853
5861 public function getLibStatut($mode = 0, $type = 0)
5862 {
5863 switch ($type) {
5864 case 0:
5865 return $this->LibStatut($this->status, $mode, $type);
5866 case 1:
5867 return $this->LibStatut($this->status_buy, $mode, $type);
5868 case 2:
5869 return $this->LibStatut($this->status_batch, $mode, $type);
5870 default:
5871 //Simulate previous behavior but should return an error string
5872 return $this->LibStatut($this->status_buy, $mode, $type);
5873 }
5874 }
5875
5876 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5885 public function LibStatut($status, $mode = 0, $type = 0)
5886 {
5887 // phpcs:enable
5888 global $langs;
5889
5890 $labelStatus = $labelStatusShort = '';
5891
5892 $langs->load('products');
5893 if (isModEnabled('productbatch')) {
5894 $langs->load("productbatch");
5895 }
5896
5897 if ($type == 2) {
5898 switch ($mode) {
5899 case 0:
5900 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5901 return dolGetStatus($label);
5902 case 1:
5903 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5904 return dolGetStatus($label);
5905 case 2:
5906 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5907 case 3:
5908 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5909 case 4:
5910 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5911 case 5:
5912 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5913 default:
5914 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5915 }
5916 }
5917
5918 $statuttrans = empty($status) ? 'status5' : 'status4';
5919
5920 if ($status == 0) {
5921 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5922 if ($type == 0) {
5923 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5924 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5925 } elseif ($type == 1) {
5926 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5927 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5928 } elseif ($type == 2) {
5929 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5930 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5931 }
5932 } elseif ($status == 1) {
5933 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5934 if ($type == 0) {
5935 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5936 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5937 } elseif ($type == 1) {
5938 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5939 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5940 } elseif ($type == 2) {
5941 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5942 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5943 }
5944 } elseif ($type == 2 && $status == 2) {
5945 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5946 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5947 }
5948
5949 if ($mode > 6) {
5950 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5951 } else {
5952 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5953 }
5954 }
5955
5956
5962 public function getLibFinished()
5963 {
5964 global $langs;
5965
5966 $langs->load('products');
5967 $label = '';
5968
5969 if (isset($this->finished) && $this->finished >= 0) {
5970 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5971 $resql = $this->db->query($sql);
5972 if (!$resql) {
5973 $this->error = $this->db->error().' sql='.$sql;
5974 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
5975 return -1;
5976 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
5977 $label = $langs->trans($res['label']);
5978 }
5979 $this->db->free($resql);
5980 }
5981
5982 return $label;
5983 }
5984
5985
5986 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6003 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
6004 {
6005 // phpcs:enable
6006 if ($id_entrepot) {
6007 $this->db->begin();
6008
6009 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6010
6011 if ($nbpiece < 0) {
6012 if (!$movement) {
6013 $movement = 1;
6014 }
6015 $nbpiece = abs($nbpiece);
6016 }
6017 $op = array();
6018 $op[0] = "+".trim((string) $nbpiece);
6019 $op[1] = "-".trim((string) $nbpiece);
6020
6021 $movementstock = new MouvementStock($this->db);
6022 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
6023 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
6024
6025 if ($result >= 0) {
6026 if ($extrafields) {
6027 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6028 $movementstock->array_options = $array_options;
6029 $movementstock->insertExtraFields();
6030 }
6031 $this->db->commit();
6032 return 1;
6033 } else {
6034 $this->error = $movementstock->error;
6035 $this->errors = $movementstock->errors;
6036
6037 $this->db->rollback();
6038 return -1;
6039 }
6040 }
6041
6042 return -1;
6043 }
6044
6045 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6066 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)
6067 {
6068 // phpcs:enable
6069 if ($id_entrepot) {
6070 $this->db->begin();
6071
6072 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6073
6074 if ($nbpiece < 0) {
6075 if (!$movement) {
6076 $movement = 1;
6077 }
6078 $nbpiece = abs($nbpiece);
6079 }
6080
6081 $op = array();
6082 $op[0] = "+".trim((string) $nbpiece);
6083 $op[1] = "-".trim((string) $nbpiece);
6084
6085 $movementstock = new MouvementStock($this->db);
6086 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
6087 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
6088
6089 if ($result >= 0) {
6090 if ($extrafields) {
6091 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6092 $movementstock->array_options = $array_options;
6093 $movementstock->insertExtraFields();
6094 }
6095 $this->db->commit();
6096 return 1;
6097 } else {
6098 $this->error = $movementstock->error;
6099 $this->errors = $movementstock->errors;
6100
6101 $this->db->rollback();
6102 return -1;
6103 }
6104 }
6105 return -1;
6106 }
6107
6108 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6121 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
6122 {
6123 // phpcs:enable
6124 $this->stock_reel = 0;
6125 $this->stock_warehouse = array();
6126 $this->stock_theorique = 0;
6127
6128 // Set filter on warehouse status
6129 $warehouseStatus = array();
6130 if (preg_match('/warehouseclosed/', $option)) {
6132 }
6133 if (preg_match('/warehouseopen/', $option)) {
6135 }
6136 if (preg_match('/warehouseinternal/', $option)) {
6137 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
6139 } else {
6141 }
6142 }
6143
6144 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
6145 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
6146 $sql .= ", ".$this->db->prefix()."entrepot as w";
6147 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
6148 $sql .= " AND w.rowid = ps.fk_entrepot";
6149 $sql .= " AND ps.fk_product = ".((int) $this->id);
6150 if (count($warehouseStatus)) {
6151 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
6152 }
6153
6154 $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;
6155
6156 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
6157 $result = $this->db->query($sql);
6158 if ($result) {
6159 $num = $this->db->num_rows($result);
6160 $i = 0;
6161 if ($num > 0) {
6162 while ($i < $num) {
6163 $row = $this->db->fetch_object($result);
6164 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
6165 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
6166 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
6167 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
6168 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
6169 }
6170 $this->stock_reel += $row->reel;
6171 $i++;
6172 }
6173 $this->stock_reel = (float) price2num($this->stock_reel, 'MS');
6174 }
6175 $this->db->free($result);
6176
6177 if (!preg_match('/novirtual/', $option)) {
6178 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
6179 }
6180
6181 return 1;
6182 } else {
6183 $this->error = $this->db->lasterror();
6184 return -1;
6185 }
6186 }
6187
6188
6189 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6199 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
6200 {
6201 // phpcs:enable
6202 global $hookmanager, $action;
6203
6204 $stock_commande_client = 0;
6205 $stock_commande_fournisseur = 0;
6206 $stock_sending_client = 0;
6207 $stock_reception_fournisseur = 0;
6208 $stock_inproduction = 0;
6209
6210 //dol_syslog("load_virtual_stock");
6211
6212 if (isModEnabled('order')) {
6213 $result = $this->load_stats_commande(0, '1,2', 1);
6214 if ($result < 0) {
6215 dol_print_error($this->db, $this->error);
6216 }
6217 $stock_commande_client = $this->stats_commande['qty'];
6218 }
6219 if (isModEnabled("shipping")) {
6220 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
6221 $filterShipmentStatus = '';
6222 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
6223 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
6224 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6225 $filterShipmentStatus = Expedition::STATUS_CLOSED;
6226 }
6227 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
6228 if ($result < 0) {
6229 dol_print_error($this->db, $this->error);
6230 }
6231 $stock_sending_client = $this->stats_expedition['qty'];
6232 }
6233 // Include supplier order lines
6234 if (isModEnabled("supplier_order")) {
6235 $filterStatus = getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK', '3,4');
6236 if (isset($includedraftpoforvirtual)) {
6237 $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
6238 }
6239 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
6240 if ($result < 0) {
6241 dol_print_error($this->db, $this->error);
6242 }
6243 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
6244 }
6245 // Include reception lines
6246 if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) {
6247 $filterStatus = '4';
6248 if (isset($includedraftpoforvirtual)) {
6249 $filterStatus = '0,'.$filterStatus;
6250 }
6251 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
6252 if ($result < 0) {
6253 dol_print_error($this->db, $this->error);
6254 }
6255 $stock_reception_fournisseur = $this->stats_reception['qty'];
6256 }
6257 // Include manufacturing
6258 if (isModEnabled('mrp')) {
6259 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
6260 if ($result < 0) {
6261 dol_print_error($this->db, $this->error);
6262 }
6263 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
6264 }
6265
6266 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
6267
6268 // $weBillOrderOrShipmentReception is set to 'order' or 'shipmentreception'. it will be used to know how to make virtual stock
6269 // calculation when we have a stock increase or decrease on billing. Do we have to count orders to bill or shipment/reception to bill ?
6270 $weBillOrderOrShipmentReception = getDolGlobalString('STOCK_DO_WE_BILL_ORDER_OR_SHIPMENTECEPTION_FOR_VIRTUALSTOCK', 'order');
6271
6272 // Stock decrease mode
6273 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6274 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6275 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
6276 if (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER_INCLUDE_DRAFT')) { // By default, draft means "does not exist", so we do not include them by default, except if option is on
6277 $tmpnewprod = dol_clone($this, 1);
6278 $result = $tmpnewprod->load_stats_commande(0, '0', 1); // Get qty in draft orders
6279 $this->stock_theorique += $tmpnewprod->stats_commande['qty'];
6280 }
6281 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'order') {
6282 $this->stock_theorique -= $stock_commande_client;
6283 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
6284 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6285 }
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')) { // This option is similar to STOCK_CALCULATE_ON_RECEPTION_CLOSE but when module Reception is not enabled
6291 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6292 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) { // Warning: stock change "on approval", not on validation !
6293 if (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER_INCLUDE_DRAFT')) { // By default, draft means "does not exist", so we do not include them by default, except if option is on
6294 $tmpnewprod = dol_clone($this, 1);
6295 $result = $tmpnewprod->load_stats_commande_fournisseur(0, '0', 1); // Get qty in draft orders
6296 $this->stock_theorique += $this->stats_commande_fournisseur['qty'];
6297 }
6298 $this->stock_theorique -= $stock_reception_fournisseur;
6299 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'order') {
6300 $this->stock_theorique += $stock_commande_fournisseur;
6301 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
6302 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6303 }
6304
6305 $parameters = array('id' => $this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
6306 // Note that $action and $object may have been modified by some hooks
6307 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
6308 if ($reshook > 0) {
6309 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
6310 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
6311 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
6312 }
6313
6314 //Virtual Stock by Warehouse
6315 if (!empty($this->stock_warehouse) && getDolGlobalString('STOCK_ALLOW_VIRTUAL_STOCK_PER_WAREHOUSE')) {
6316 foreach ($this->stock_warehouse as $warehouseid => $stockwarehouse) {
6317 if (isModEnabled('mrp')) {
6318 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock, $warehouseid);
6319 if ($result < 0) {
6320 dol_print_error($this->db, $this->error);
6321 }
6322 }
6323
6324 if ($this->fk_default_warehouse == $warehouseid) {
6325 $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']);
6326 } else {
6327 $this->stock_warehouse[$warehouseid]->virtual = $this->stock_warehouse[$warehouseid]->real + $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'];
6328 }
6329 }
6330 }
6331
6332 return 1;
6333 }
6334
6335
6343 public function loadBatchInfo($batch)
6344 {
6345 $result = array();
6346
6347 $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";
6348 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
6349 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
6350 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
6351 $resql = $this->db->query($sql);
6352 if ($resql) {
6353 $num = $this->db->num_rows($resql);
6354 $i = 0;
6355 while ($i < $num) {
6356 $obj = $this->db->fetch_object($resql);
6357 $result[] = array('batch' => $batch, 'eatby' => $this->db->jdate($obj->eatby), 'sellby' => $this->db->jdate($obj->sellby), 'qty' => $obj->qty);
6358 $i++;
6359 }
6360 return $result;
6361 } else {
6362 dol_print_error($this->db);
6363 $this->db->rollback();
6364 return array();
6365 }
6366 }
6367
6368 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6377 public function add_photo($sdir, $file)
6378 {
6379 // phpcs:enable
6380 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6381
6382 $result = 0;
6383
6384 $dir = $sdir;
6385 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6386 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
6387 } else {
6388 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
6389 }
6390
6391 dol_mkdir($dir);
6392
6393 $dir_osencoded = $dir;
6394
6395 if (is_dir($dir_osencoded)) {
6396 $originImage = $dir.'/'.$file['name'];
6397
6398 // Cree fichier en taille origine
6399 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
6400
6401 if (file_exists(dol_osencode($originImage))) {
6402 // Create thumbs
6403 $this->addThumbs($originImage);
6404 }
6405 }
6406
6407 if (is_numeric($result) && $result > 0) {
6408 return 1;
6409 } else {
6410 return -1;
6411 }
6412 }
6413
6414 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6421 public function is_photo_available($sdir)
6422 {
6423 // phpcs:enable
6424 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6425 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6426
6427 $dir = $sdir;
6428 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6429 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
6430 } else {
6431 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
6432 }
6433
6434 $dir_osencoded = dol_osencode($dir);
6435 if (file_exists($dir_osencoded)) {
6436 $handle = opendir($dir_osencoded);
6437 if (is_resource($handle)) {
6438 while (($file = readdir($handle)) !== false) {
6439 if (!utf8_check($file)) {
6440 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6441 }
6442 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6443 return true;
6444 }
6445 }
6446 }
6447 }
6448
6449 return false;
6450 }
6451
6452 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6460 public function liste_photos($dir, $nbmax = 0)
6461 {
6462 // phpcs:enable
6463 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6464 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6465
6466 $nbphoto = 0;
6467 $tabobj = array();
6468
6469 $dir_osencoded = dol_osencode($dir);
6470 $handle = @opendir($dir_osencoded);
6471 if (is_resource($handle)) {
6472 while (($file = readdir($handle)) !== false) {
6473 if (!utf8_check($file)) {
6474 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6475 }
6476 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6477 $nbphoto++;
6478
6479 // We forge name of thumb.
6480 $photo = $file;
6481 $photo_vignette = '';
6482 $regs = array();
6483 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6484 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6485 }
6486
6487 $dirthumb = $dir.'thumbs/';
6488
6489 // Object
6490 $obj = array();
6491 $obj['photo'] = $photo;
6492 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6493 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6494 } else {
6495 $obj['photo_vignette'] = "";
6496 }
6497
6498 $tabobj[$nbphoto - 1] = $obj;
6499
6500 // Do we have to continue with next photo ?
6501 if ($nbmax && $nbphoto >= $nbmax) {
6502 break;
6503 }
6504 }
6505 }
6506
6507 closedir($handle);
6508 }
6509
6510 return $tabobj;
6511 }
6512
6513 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6520 public function delete_photo($file)
6521 {
6522 // phpcs:enable
6523 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6524 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6525
6526 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6527 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6528 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6529
6530 // On efface l'image d'origine
6531 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6532
6533 // Si elle existe, on efface la vignette
6534 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6535 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6536 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6537 dol_delete_file($dirthumb.$photo_vignette);
6538 }
6539
6540 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6541 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6542 dol_delete_file($dirthumb.$photo_vignette);
6543 }
6544 }
6545 }
6546
6547 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6554 public function get_image_size($file)
6555 {
6556 // phpcs:enable
6557 $file_osencoded = dol_osencode($file);
6558 $infoImg = getimagesize($file_osencoded); // Get information on image
6559 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6560 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6561 }
6562
6568 public function loadStateBoard()
6569 {
6570 global $hookmanager;
6571
6572 $this->nb = array();
6573
6574 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6575 $sql .= " FROM ".$this->db->prefix()."product as p";
6576 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6577 // Add where from hooks
6578 if (is_object($hookmanager)) {
6579 $parameters = array();
6580 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6581 $sql .= $hookmanager->resPrint;
6582 }
6583 $sql .= ' GROUP BY fk_product_type';
6584
6585 $resql = $this->db->query($sql);
6586 if ($resql) {
6587 while ($obj = $this->db->fetch_object($resql)) {
6588 if ($obj->fk_product_type == 1) {
6589 $this->nb["services"] = $obj->nb;
6590 } else {
6591 $this->nb["products"] = $obj->nb;
6592 }
6593 }
6594 $this->db->free($resql);
6595 return 1;
6596 } else {
6597 dol_print_error($this->db);
6598 $this->error = $this->db->error();
6599 return -1;
6600 }
6601 }
6602
6608 public function isProduct()
6609 {
6610 return $this->type == Product::TYPE_PRODUCT;
6611 }
6612
6618 public function isService()
6619 {
6620 return $this->type == Product::TYPE_SERVICE;
6621 }
6622
6628 public function isStockManaged()
6629 {
6630 return ($this->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'));
6631 }
6632
6638 public function isMandatoryPeriod()
6639 {
6640 return $this->mandatory_period == 1;
6641 }
6642
6648 public function hasbatch()
6649 {
6650 return $this->status_batch > 0;
6651 }
6652
6653
6654 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6663 public function get_barcode($object, $type = '')
6664 {
6665 // phpcs:enable
6666 global $conf;
6667
6668 $result = '';
6669 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6670 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6671 foreach ($dirsociete as $dirroot) {
6672 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6673 if ($res) {
6674 break;
6675 }
6676 }
6677 $var = getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM');
6678 $mod = new $var();
6679 '@phan-var-force ModeleNumRefBarCode $mod';
6680
6681 $result = $mod->getNextValue($object, $type);
6682
6683 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6684 }
6685 return $result;
6686 }
6687
6695 public function initAsSpecimen()
6696 {
6697 $now = dol_now();
6698
6699 // Initialize parameters
6700 $this->specimen = 1;
6701 $this->id = 0;
6702 $this->ref = 'PRODUCT_SPEC';
6703 $this->label = 'PRODUCT SPECIMEN';
6704 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6705 $this->specimen = 1;
6706 $this->country_id = 1;
6707 $this->status = 1;
6708 $this->status_buy = 1;
6709 $this->tobatch = 0;
6710 $this->sell_or_eat_by_mandatory = 0;
6711 $this->note_private = 'This is a comment (private)';
6712 $this->note_public = 'This is a comment (public)';
6713 $this->date_creation = $now;
6714 $this->date_modification = $now;
6715
6716 $this->weight = 4;
6717 $this->weight_units = 3;
6718
6719 $this->length = 5;
6720 $this->length_units = 1;
6721 $this->width = 6;
6722 $this->width_units = 0;
6723 $this->height = null;
6724 $this->height_units = null;
6725
6726 $this->surface = 30;
6727 $this->surface_units = 0;
6728 $this->volume = 300;
6729 $this->volume_units = 0;
6730
6731 $this->barcode = -1; // Create barcode automatically
6732
6733 return 1;
6734 }
6735
6742 public function getLabelOfUnit($type = 'long')
6743 {
6744 global $langs;
6745
6746 if (!$this->fk_unit) {
6747 return '';
6748 }
6749
6750 $langs->load('products');
6751 $label = '';
6752 $label_type = 'label';
6753 if ($type == 'short') {
6754 $label_type = 'short_label';
6755 }
6756
6757 $sql = "SELECT ".$label_type.", code from ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6758
6759 $resql = $this->db->query($sql);
6760 if (!$resql) {
6761 $this->error = $this->db->error();
6762 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6763 return -1;
6764 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
6765 $label = ($label_type == 'short_label' ? $res[$label_type] : 'unit'.$res['code']);
6766 }
6767 $this->db->free($resql);
6768
6769 return $label;
6770 }
6771
6772 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6778 public function min_recommended_price()
6779 {
6780 // phpcs:enable
6781 $maxpricesupplier = 0;
6782
6783 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
6784 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6785 $product_fourn = new ProductFournisseur($this->db);
6786 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6787
6788 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6789 foreach ($product_fourn_list as $productfourn) {
6790 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6791 $maxpricesupplier = $productfourn->fourn_unitprice;
6792 }
6793 }
6794
6795 $maxpricesupplier *= getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE');
6796 }
6797 }
6798
6799 return $maxpricesupplier;
6800 }
6801
6802
6813 public function setCategories($categories)
6814 {
6815 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6816 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6817 }
6818
6827 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6828 {
6829 $tables = array(
6830 'product_customer_price',
6831 'product_customer_price_log'
6832 );
6833
6834 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6835 }
6836
6848 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6849 {
6850 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6851 $query = $this->db->query($sql);
6852
6853 $rules = array();
6854
6855 while ($result = $this->db->fetch_object($query)) {
6856 $rules[$result->level] = $result;
6857 }
6858
6859 //Because prices can be based on other level's prices, we temporarily store them
6860 $prices = array(
6861 1 => $baseprice
6862 );
6863
6864 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
6865 for ($i = 1; $i <= $nbofproducts; $i++) {
6866 $price = $baseprice;
6867 $price_min = $baseprice;
6868
6869 //We have to make sure it does exist and it is > 0
6870 //First price level only allows changing min_price
6871 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6872 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6873 }
6874
6875 $prices[$i] = $price;
6876
6877 //We have to make sure it does exist and it is > 0
6878 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6879 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6880 }
6881
6882 //Little check to make sure the price is modified before triggering generation
6883 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6884 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6885
6886 if ($check_amount && $check_type) {
6887 continue;
6888 }
6889
6890 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, 1) < 0) {
6891 return -1;
6892 }
6893 }
6894
6895 return 1;
6896 }
6897
6903 public function getRights()
6904 {
6905 global $user;
6906
6907 if ($this->isProduct()) {
6908 return $user->rights->produit;
6909 } else {
6910 return $user->rights->service;
6911 }
6912 }
6913
6920 public function info($id)
6921 {
6922 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6923 $sql .= " p.fk_user_author, p.fk_user_modif";
6924 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6925 $sql .= " WHERE p.rowid = ".((int) $id);
6926
6927 $result = $this->db->query($sql);
6928 if ($result) {
6929 if ($this->db->num_rows($result)) {
6930 $obj = $this->db->fetch_object($result);
6931
6932 $this->id = $obj->rowid;
6933 $this->ref = $obj->ref;
6934
6935 $this->user_creation_id = $obj->fk_user_author;
6936 $this->user_modification_id = $obj->fk_user_modif;
6937
6938 $this->date_creation = $this->db->jdate($obj->date_creation);
6939 $this->date_modification = $this->db->jdate($obj->date_modification);
6940 }
6941
6942 $this->db->free($result);
6943 } else {
6944 dol_print_error($this->db);
6945 }
6946 }
6947
6948
6954 public function getProductDurationHours()
6955 {
6956 if (empty($this->duration_value)) {
6957 $this->errors[] = 'ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
6958 return -1;
6959 }
6960 if ($this->duration_unit == 's') {
6961 $prodDurationHours = 1. / 3600;
6962 } elseif ($this->duration_unit == 'i' || $this->duration_unit == 'mn' || $this->duration_unit == 'min') {
6963 $prodDurationHours = 1. / 60;
6964 } elseif ($this->duration_unit == 'h') {
6965 $prodDurationHours = 1.;
6966 } elseif ($this->duration_unit == 'd') {
6967 $prodDurationHours = 24.;
6968 } elseif ($this->duration_unit == 'w') {
6969 $prodDurationHours = 24. * 7;
6970 } elseif ($this->duration_unit == 'm') {
6971 $prodDurationHours = 24. * 30;
6972 } elseif ($this->duration_unit == 'y') {
6973 $prodDurationHours = 24. * 365;
6974 } else {
6975 $prodDurationHours = 0.0;
6976 }
6977 $prodDurationHours *= $this->duration_value;
6978
6979 return $prodDurationHours;
6980 }
6981
6982
6990 public function getKanbanView($option = '', $arraydata = null)
6991 {
6992 global $langs, $conf;
6993
6994 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6995
6996 $return = '<div class="box-flex-item box-flex-grow-zero">';
6997 $return .= '<div class="info-box info-box-sm">';
6998 $return .= '<div class="info-box-img">';
6999 $label = '';
7000 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
7001 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
7002 $return .= $label;
7003 } else {
7004 if ($this->isProduct()) {
7005 $label .= img_picto('', 'product');
7006 } elseif ($this->isService()) {
7007 $label .= img_picto('', 'service');
7008 }
7009 $return .= $label;
7010 }
7011 $return .= '</div>';
7012 $return .= '<div class="info-box-content">';
7013 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
7014 if ($selected >= 0) {
7015 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
7016 }
7017 if (property_exists($this, 'label')) {
7018 $return .= '<br><span class="info-box-label opacitymedium inline-block tdoverflowmax150 valignmiddle" title="'.dol_escape_htmltag($this->label).'">'.dol_escape_htmltag($this->label).'</span>';
7019 }
7020 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
7021 if ($this->price_base_type == 'TTC') {
7022 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
7023 } else {
7024 if ($this->status) {
7025 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
7026 }
7027 }
7028 }
7029 $br = 1;
7030 if (property_exists($this, 'stock_reel') && $this->isProduct()) {
7031 $return .= '<br><div class="info-box-status opacitymedium inline-block valignmiddle">'.img_picto($langs->trans('PhysicalStock'), 'stock').'</div><div class="inline-block valignmiddle paddingleft" title="'.$langs->trans('PhysicalStock').'">'.$this->stock_reel.'</div>';
7032 $br = 0;
7033 }
7034 if (method_exists($this, 'getLibStatut')) {
7035 if ($br) {
7036 $return .= '<br><div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
7037 } else {
7038 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
7039 }
7040 }
7041 $return .= '</div>';
7042 $return .= '</div>';
7043 $return .= '</div>';
7044 return $return;
7045 }
7046
7053 public function getProductsToPreviewInEmail($limit)
7054 {
7055
7056 if (!is_numeric($limit)) {
7057 return -1;
7058 }
7059
7060 $sql = "SELECT p.rowid, p.ref, p.label, p.description, p.entity, ef.filename
7061 FROM ".MAIN_DB_PREFIX."product AS p
7062 JOIN ".MAIN_DB_PREFIX."ecm_files AS ef ON p.rowid = ef.src_object_id
7063 WHERE ef.entity IN (".getEntity('product').")
7064 AND (ef.filename LIKE '%.png' OR ef.filename LIKE '%.jpeg' OR ef.filename LIKE '%.svg')
7065 GROUP BY p.rowid, p.ref, p.label, p.description, p.entity, ef.filename
7066 ORDER BY p.datec ASC
7067 LIMIT " . ((int) $limit);
7068
7069 $resql = $this->db->query($sql);
7070 $products = array();
7071
7072 if ($resql) {
7073 while ($obj = $this->db->fetch_object($resql)) {
7074 $products[] = array(
7075 'rowid' => $obj->rowid,
7076 'ref' => $obj->ref,
7077 'label' => $obj->label,
7078 'description' => $obj->description,
7079 'entity' => $obj->entity,
7080 'filename' => $obj->filename
7081 );
7082 }
7083 } else {
7084 dol_print_error($this->db);
7085 }
7086 if (empty($products)) {
7087 return -1;
7088 }
7089 return $products;
7090 }
7091}
7092
7098{
7099 public $picto = 'service';
7100}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:66
length_accountg($account)
Return General accounting account with defined length (used for product and miscellaneous)
$object ref
Definition info.php:89
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
deleteEcmFiles($mode=0)
Delete related files of object in database.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
isObjectUsed($id=0, $entity=0)
Function to check if an object is used by others (by children).
deleteExtraFields()
Delete all extra fields values for the current object.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
show_photos($modulepart, $sdir, $size=0, $nbmax=0, $nbbyrow=5, $showfilename=0, $showaction=0, $maxHeight=120, $maxWidth=160, $nolink=0, $overwritetitle=0, $usesharelink=0, $cache='', $addphotorefcss='photoref')
Show photos of an object (nbmax maximum), into several columns.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
call_trigger($triggerName, $user)
Call trigger based on this instance.
addThumbs($file, $quality=50)
Build thumb.
Class to manage Dolibarr database access.
Class to manage ECM files.
const STATUS_OPEN_INTERNAL
Warehouse open and only operations for stock transfers/corrections allowed (not for customer shipping...
const STATUS_OPEN_ALL
Warehouse open and any operations are allowed (customer shipping, supplier dispatch,...
const STATUS_CLOSED
Warehouse closed, inactive.
const STATUS_CLOSED
Closed status -> parcel was received by customer / end of process prev status : validated or shipment...
const STATUS_VALIDATED
Validated status -> parcel is ready to be sent prev status : draft next status : closed or shipment_i...
const STATUS_DRAFT
Draft status.
Class to manage generation of HTML components Only common components must be here.
Class to manage stock movements.
Class to parse product price expressions.
Class ProductCombination Used to represent the relation between a product and one of its variants.
File of class to manage predefined price products or services by customer.
Class to manage predefined suppliers products.
Class to manage products or services.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
get_nb_achat($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or supplier invoices in which product is included.
getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp=0)
Return price of sell of a product for a seller/buyer/product.
__construct($db)
Constructor.
is_sousproduit($fk_parent, $fk_child)
Check if it is a sub-product into a kit.
const SELL_OR_EAT_BY_MANDATORY_ID_NONE
Const sell or eat by mandatory id.
isStockManaged()
Return if the object is managed in stock.
setPriceExpression($expression_id)
Sets the supplier price expression.
getArrayForPriceCompare($level=0)
used to check if price have really change to avoid log pollution
get_arbo_each_prod($multiply=1, $ignore_stock_load=0)
Build the tree of subproducts and return it.
check_barcode($valuetotest, $typefortest)
Check barcode.
list_suppliers()
Return list of suppliers providing the product or service.
load_stats_mo($socid=0)
Charge tableau des stats OF pour le produit/service.
isVariant()
Return if loaded product is a variant.
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)
Modify customer price of a product/Service for a given level.
hasVariants()
Return if a product has variants or not.
delMultiLangs($langtodelete, $user)
Delete a language for this product.
getLabelOfUnit($type='long')
Returns the text label from units dictionary.
load_stats_proposal_supplier($socid=0)
Charge tableau des stats propale pour le produit/service.
getLibFinished()
Retour label of nature of product.
add_sousproduit($id_pere, $id_fils, $qty, $incdec=1, $notrigger=0)
Link a product/service to a parent product/service.
add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
Add a supplier price for the product.
hasFatherOrChild($mode=0)
Count all parent and children products for current product (first level only)
load_stats_facturerec($socid=0)
Charge tableau des stats facture recurrentes pour le produit/service.
get_nb_propalsupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in proposals in which product is included.
get_nb_contract($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
load_stats_facture_fournisseur($socid=0)
Charge tableau des stats facture pour le produit/service.
get_nb_ordersupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
getMultiLangs()
Load array this->multilangs.
get_nb_mos($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
clone_associations($fromId, $toId)
Clone links between products.
create($user, $notrigger=0)
Insert product into database.
load_stats_contrat($socid=0)
Charge tableau des stats contrat pour le produit/service.
isService()
Return if the object is a service.
getRights()
Returns the rights used for this class.
loadBatchInfo($batch)
Load existing information about a serial.
load_stock($option='', $includedraftpoforvirtual=null, $dateofvirtualstock=null)
Load information about stock of a product into ->stock_reel, ->stock_warehouse[] (including stock_war...
getProductDurationHours()
Return the duration of a service in hours (for a service based on duration fields)
get_buyprice($prodfournprice, $qty, $product_id=0, $fourn_ref='', $fk_soc=0)
Read price used by a provider.
clone_fournisseurs($fromId, $toId)
Recopie les fournisseurs et prix fournisseurs d'un produit/service sur un autre.
const TYPE_PRODUCT
Regular product.
add_photo($sdir, $file)
Move an uploaded file described into $file array into target directory $sdir.
log_price_delete($user, $rowid)
Delete a price line.
info($id)
Load information for tab info.
correct_stock($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $inventorycode='', $origin_element='', $origin_id=null, $disablestockchangeforsubproduct=0, $extrafields=null)
Adjust stock in a warehouse for product.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
Create a document onto disk according to template module.
static getSellOrEatByMandatoryList()
Get the array of labels of Sell by or Eat by all mandatory flags for each status.
getChildsArbo($id, $firstlevelonly=0, $level=1, $parents=array())
Return children of product $id.
load_virtual_stock($includedraftpoforvirtual=null, $dateofvirtualstock=null)
Load value ->stock_theorique of a product.
load_stats_propale($socid=0)
Charge tableau des stats propale pour le produit/service.
get_barcode($object, $type='')
Get a barcode from the module to generate barcode values.
setAccountancyCode($type, $value)
Sets an accountancy code for a product.
getProductsToPreviewInEmail($limit)
Retrieve and display products.
load_stats_facture($socid=0)
Charge tableau des stats facture pour le produit/service.
setCategories($categories)
Sets object to supplied categories.
load_stats_reception($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null)
Charge tableau des stats réception fournisseur pour le produit/service.
get_nb_propal($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in proposals in which product is included.
update($id, $user, $notrigger=0, $action='update', $updatetype=false)
Update a record into database.
setMultiLangs($user)
Update or add a translation for a product.
correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $dlc='', $dluo='', $lot='', $inventorycode='', $origin_element='', $origin_id=null, $disablestockchangeforsubproduct=0, $extrafields=null, $force_update_batch=false)
Adjust stock in a warehouse for product with batch number.
load_stats_bom($socid=0)
Charge tableau des stats OF pour le produit/service.
hasbatch()
Return if the object has a sell-by or eat-by date.
del_sousproduit($fk_parent, $fk_child, $notrigger=0)
Remove a link between a subproduct and a parent product/service.
fetch($id=0, $ref='', $ref_ext='', $barcode='', $ignore_expression=0, $ignore_price_load=0, $ignore_lang_load=0)
Load a product in memory from database.
update_sousproduit($id_pere, $id_fils, $qty, $incdec=1, $notrigger=0)
Modify composed product.
load_stats_commande($socid=0, $filtrestatut='', $forVirtualStock=0)
Charge tableau des stats commande client pour le produit/service.
delete_photo($file)
Delete a photo and its thumbs.
fetch_prod_arbo($prod, $compl_path='', $multiply=1, $level=1, $id_parent=0, $ignore_stock_load=0)
Function recursive, used only by get_arbo_each_prod(), to build tree of subproducts into ->res Define...
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
getLibStatut($mode=0, $type=0)
Return label of status of object.
load_stats_sending($socid=0, $filtrestatut='', $forVirtualStock=0, $filterShipmentStatus='')
Charge tableau des stats expedition client pour le produit/service.
clone_price($fromId, $toId)
Recopie les prix d'un produit/service sur un autre.
load_stats_inproduction($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null, $warehouseid=0)
Charge tableau des stats production pour le produit/service.
check()
Check that ref and label are ok.
initAsSpecimen()
Initialise an instance with random values.
liste_photos($dir, $nbmax=0)
Return an array with all photos of product found on disk.
loadStateBoard()
Load indicators this->nb for the dashboard.
getFather()
Return all parent products for current product (first level only)
getNomUrl($withpicto=0, $option='', $maxlength=0, $save_lastsearch_value=-1, $notooltip=0, $morecss='', $add_label=0, $sep=' - ')
Return clickable link of object (with eventually picto)
getSellOrEatByMandatoryLabel()
Get the label for sell by or eat by mandatory flag of the current product.
verify()
Check properties of product are ok (like name, barcode, ...).
get_sousproduits_arbo()
get_nb_order($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
min_recommended_price()
Return minimum product recommended price.
_log_price($user, $level=0)
Insert a track that we changed a customer price.
_get_stats($sql, $mode, $year=0)
Return an array formatted for showing graphs.
load_stats_commande_fournisseur($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null)
Charge tableau des stats commande fournisseur pour le produit/service.
isMandatoryPeriod()
Return if the object has a constraint on mandatory_period.
isProduct()
Return if the object is a product.
generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
Generates prices for a product based on product multiprice generation rules.
LibStatut($status, $mode=0, $type=0)
Return label of a given status.
const TYPE_SERVICE
Service.
is_photo_available($sdir)
Return if at least one photo is available.
get_image_size($file)
Load size of image file.
get_nb_vente($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or customers invoices in which product is included.
getTooltipContentArray($params)
getTooltipContentArray
Class to manage products or services.
Manage record for batch number management.
static findAll($dbs, $fk_product_stock, $with_qty=0, $fk_product=0)
Return all batch detail records for a given product and warehouse.
Class with list of lots and properties.
Class to manage Dolibarr users.
hasRight($module, $permlevel1, $permlevel2='')
Return if a user has a permission.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $nohook=0, $varfiles='addedfile', $upload_dir='')
Check validity of a file upload from an GUI page, and move it to its final destination.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_is_file($pathoffile)
Return if path is a file.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
dolGetFirstLineOfText($text, $nboflines=1, $charset='UTF-8')
Return first line of text.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_clone($object, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
utf8_check($str)
Check if a string is in UTF8.
get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that return vat rate of a product line (according to seller, buyer and product vat rate) VAT...
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
measuring_units_squared($unitscale)
Transform a given unit scale into the square of that unit, if known.
measuringUnitString($unitid, $measuring_style='', $unitscale='', $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.
measuring_units_cubed($unit)
Transform a given unit scale into the cube of that unit, if known.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:150