dolibarr 21.0.4
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 // Normalize the accountancy codes the way the admin dropdown does it, so an API client that
1030 // sends '606111000' ends up with the same '606111' value the GUI stores (see issue #32343).
1031 // When ACCOUNTING_MANAGE_ZERO is on, trailing zeros are part of the code and must be kept.
1032 if (!getDolGlobalString('ACCOUNTING_MANAGE_ZERO')) {
1033 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
1034 $this->accountancy_code_buy = clean_account($this->accountancy_code_buy);
1035 $this->accountancy_code_buy_intra = clean_account($this->accountancy_code_buy_intra);
1036 $this->accountancy_code_buy_export = clean_account($this->accountancy_code_buy_export);
1037 $this->accountancy_code_sell = clean_account($this->accountancy_code_sell);
1038 $this->accountancy_code_sell_intra = clean_account($this->accountancy_code_sell_intra);
1039 $this->accountancy_code_sell_export = clean_account($this->accountancy_code_sell_export);
1040 }
1041
1042 // Barcode value
1043 $this->barcode = trim($this->barcode);
1044 $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
1045 // Check parameters
1046 if (empty($this->label)) {
1047 $this->error = 'ErrorMandatoryParametersNotProvided';
1048 return -1;
1049 }
1050
1051 if (empty($this->ref) || $this->ref == 'auto') {
1052 // Load object modCodeProduct
1053 $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
1054 if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
1055 if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
1056 $module = substr($module, 0, dol_strlen($module) - 4);
1057 }
1058 dol_include_once('/core/modules/product/'.$module.'.php');
1059 $modCodeProduct = new $module();
1060 '@phan-var-force ModeleProductCode $modCodeProduct';
1061 if (!empty($modCodeProduct->code_auto)) {
1062 $this->ref = $modCodeProduct->getNextValue($this, $this->type);
1063 }
1064 unset($modCodeProduct);
1065 }
1066
1067 if (empty($this->ref)) {
1068 $this->error = 'ProductModuleNotSetupForAutoRef';
1069 return -2;
1070 }
1071 }
1072
1073 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);
1074
1075 $now = dol_now();
1076
1077 if (empty($this->date_creation)) {
1078 $this->date_creation = $now;
1079 }
1080
1081 $this->db->begin();
1082
1083 // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
1084 if ($this->barcode == '-1' || $this->barcode == 'auto') {
1085 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1086 }
1087
1088 // Check more parameters
1089 // If error, this->errors[] is filled
1090 $result = $this->verify();
1091
1092 if ($result >= 0) {
1093 $sql = "SELECT count(*) as nb";
1094 $sql .= " FROM ".$this->db->prefix()."product";
1095 $sql .= " WHERE entity IN (".getEntity('product').")";
1096 $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
1097
1098 $result = $this->db->query($sql);
1099 if ($result) {
1100 $obj = $this->db->fetch_object($result);
1101 if ($obj->nb == 0) {
1102 // Insert new product, no previous one found
1103 $sql = "INSERT INTO ".$this->db->prefix()."product (";
1104 $sql .= "datec";
1105 $sql .= ", entity";
1106 $sql .= ", ref";
1107 $sql .= ", ref_ext";
1108 $sql .= ", price_min";
1109 $sql .= ", price_min_ttc";
1110 $sql .= ", label";
1111 $sql .= ", fk_user_author";
1112 $sql .= ", fk_product_type";
1113 $sql .= ", price";
1114 $sql .= ", price_ttc";
1115 $sql .= ", price_base_type";
1116 $sql .= ", price_label";
1117 $sql .= ", tobuy";
1118 $sql .= ", tosell";
1119 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1120 $sql .= ", accountancy_code_buy";
1121 $sql .= ", accountancy_code_buy_intra";
1122 $sql .= ", accountancy_code_buy_export";
1123 $sql .= ", accountancy_code_sell";
1124 $sql .= ", accountancy_code_sell_intra";
1125 $sql .= ", accountancy_code_sell_export";
1126 }
1127 $sql .= ", canvas";
1128 $sql .= ", finished";
1129 $sql .= ", tobatch";
1130 $sql .= ", sell_or_eat_by_mandatory";
1131 $sql .= ", batch_mask";
1132 $sql .= ", fk_unit";
1133 $sql .= ", mandatory_period";
1134 $sql .= ") VALUES (";
1135 $sql .= "'".$this->db->idate($this->date_creation)."'";
1136 $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
1137 $sql .= ", '".$this->db->escape($this->ref)."'";
1138 $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1139 $sql .= ", ".price2num($price_min_ht);
1140 $sql .= ", ".price2num($price_min_ttc);
1141 $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
1142 $sql .= ", ".((int) $user->id);
1143 $sql .= ", ".((int) $this->type);
1144 $sql .= ", ".price2num($price_ht, 'MT');
1145 $sql .= ", ".price2num($price_ttc, 'MT');
1146 $sql .= ", '".$this->db->escape($this->price_base_type)."'";
1147 $sql .= ", ".(!empty($this->price_label) ? "'".$this->db->escape($this->price_label)."'" : "null");
1148 $sql .= ", ".((int) $this->status);
1149 $sql .= ", ".((int) $this->status_buy);
1150 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1151 $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
1152 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
1153 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
1154 $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
1155 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
1156 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
1157 }
1158 $sql .= ", '".$this->db->escape($this->canvas)."'";
1159 $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
1160 $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
1161 $sql .= ", ".((empty($this->sell_or_eat_by_mandatory) || $this->sell_or_eat_by_mandatory < 0) ? 0 : ((int) $this->sell_or_eat_by_mandatory));
1162 $sql .= ", '".$this->db->escape($this->batch_mask)."'";
1163 $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
1164 $sql .= ", '".$this->db->escape($this->mandatory_period)."'";
1165 $sql .= ")";
1166
1167 dol_syslog(get_class($this)."::Create", LOG_DEBUG);
1168
1169 $result = $this->db->query($sql);
1170 if ($result) {
1171 $id = $this->db->last_insert_id($this->db->prefix()."product");
1172
1173 if ($id > 0) {
1174 $this->id = $id;
1175 $this->price = $price_ht;
1176 $this->price_ttc = $price_ttc;
1177 $this->price_min = $price_min_ht;
1178 $this->price_min_ttc = $price_min_ttc;
1179
1180 $result = $this->_log_price($user);
1181 if ($result > 0) {
1182 if ($this->update($id, $user, 1, 'add') <= 0) {
1183 $error++;
1184 }
1185 } else {
1186 $error++;
1187 $this->error = $this->db->lasterror();
1188 }
1189
1190 // update accountancy for this entity
1191 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1192 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1193
1194 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1195 $sql .= " fk_product";
1196 $sql .= ", entity";
1197 $sql .= ", accountancy_code_buy";
1198 $sql .= ", accountancy_code_buy_intra";
1199 $sql .= ", accountancy_code_buy_export";
1200 $sql .= ", accountancy_code_sell";
1201 $sql .= ", accountancy_code_sell_intra";
1202 $sql .= ", accountancy_code_sell_export";
1203 $sql .= ") VALUES (";
1204 $sql .= $this->id;
1205 $sql .= ", " . ((int) $conf->entity);
1206 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1207 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1208 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1209 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1210 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1211 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1212 $sql .= ")";
1213 $result = $this->db->query($sql);
1214 if (!$result) {
1215 $error++;
1216 $this->error = 'ErrorFailedToInsertAccountancyForEntity';
1217 }
1218 }
1219 } else {
1220 $error++;
1221 $this->error = 'ErrorFailedToGetInsertedId';
1222 }
1223 } else {
1224 $error++;
1225 $this->error = $this->db->lasterror();
1226 }
1227 } else {
1228 // Product already exists with this ref
1229 $langs->load("products");
1230 $error++;
1231 $this->error = "ErrorProductAlreadyExists";
1232 dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
1233 }
1234 } else {
1235 $error++;
1236 $this->error = $this->db->lasterror();
1237 }
1238
1239 if (!$error && !$notrigger) {
1240 // Call trigger
1241 $result = $this->call_trigger('PRODUCT_CREATE', $user);
1242 if ($result < 0) {
1243 $error++;
1244 }
1245 // End call triggers
1246 }
1247
1248 if (!$error) {
1249 $this->db->commit();
1250 return $this->id;
1251 } else {
1252 $this->db->rollback();
1253 return -$error;
1254 }
1255 } else {
1256 $this->db->rollback();
1257 dol_syslog(get_class($this)."::Create fails verify ".implode(',', $this->errors), LOG_WARNING);
1258 return -3;
1259 }
1260 }
1261
1262
1269 public function verify()
1270 {
1271 global $langs;
1272
1273 $this->errors = array();
1274
1275 $result = 0;
1276 $this->ref = trim($this->ref);
1277
1278 if (!$this->ref) {
1279 $this->errors[] = 'ErrorBadRef';
1280 $result = -2;
1281 }
1282
1283 $arrayofnonnegativevalue = array('weight' => 'Weight', 'width' => 'Width', 'height' => 'Height', 'length' => 'Length', 'surface' => 'Surface', 'volume' => 'Volume');
1284 foreach ($arrayofnonnegativevalue as $key => $value) {
1285 if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
1286 $langs->loadLangs(array("main", "other"));
1287 $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
1288 $this->errors[] = $this->error;
1289 $result = -4;
1290 }
1291 }
1292
1293 $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
1294 if ($rescode) {
1295 if ($rescode == -1) {
1296 $this->errors[] = 'ErrorBadBarCodeSyntax';
1297 } elseif ($rescode == -2) {
1298 $this->errors[] = 'ErrorBarCodeRequired';
1299 } elseif ($rescode == -3) {
1300 // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
1301 $this->errors[] = 'ErrorBarCodeAlreadyUsed';
1302 }
1303
1304 $result = -3;
1305 }
1306
1307 return $result;
1308 }
1309
1310 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1321 public function check_barcode($valuetotest, $typefortest)
1322 {
1323 // phpcs:enable
1324 global $conf;
1325
1326 if (isModEnabled('barcode') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
1327 $module = strtolower(getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM'));
1328
1329 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1330 foreach ($dirsociete as $dirroot) {
1331 $res = dol_include_once($dirroot.$module.'.php');
1332 if ($res) {
1333 break;
1334 }
1335 }
1336
1337 $mod = new $module();
1338 '@phan-var-force ModeleNumRefBarCode $mod';
1339
1340 dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1341 $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1342 return $result;
1343 } else {
1344 return 0;
1345 }
1346 }
1347
1359 public function update($id, $user, $notrigger = 0, $action = 'update', $updatetype = false)
1360 {
1361 global $langs, $conf, $hookmanager;
1362
1363 $error = 0;
1364
1365 // Check parameters
1366 if (!$this->label) {
1367 $this->label = 'MISSING LABEL';
1368 }
1369
1370 // Clean parameters
1371 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1372 $this->ref = trim($this->ref);
1373 } else {
1374 $this->ref = dol_string_nospecial(trim($this->ref));
1375 }
1376 $this->label = trim($this->label);
1377 $this->description = trim($this->description);
1378 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1379 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1380 $this->net_measure = price2num($this->net_measure);
1381 $this->net_measure_units = (is_null($this->net_measure_units) ? '' : trim((string) $this->net_measure_units));
1382 $this->weight = price2num($this->weight);
1383 $this->weight_units = (is_null($this->weight_units) ? '' : trim((string) $this->weight_units));
1384 $this->length = price2num($this->length);
1385 $this->length_units = (is_null($this->length_units) ? '' : trim((string) $this->length_units));
1386 $this->width = price2num($this->width);
1387 $this->width_units = (is_null($this->width_units) ? '' : trim((string) $this->width_units));
1388 $this->height = price2num($this->height);
1389 $this->height_units = (is_null($this->height_units) ? '' : trim((string) $this->height_units));
1390 $this->surface = price2num($this->surface);
1391 $this->surface_units = (is_null($this->surface_units) ? '' : trim((string) $this->surface_units));
1392 $this->volume = price2num($this->volume);
1393 $this->volume_units = (is_null($this->volume_units) ? '' : trim((string) $this->volume_units));
1394
1395 // set unit not defined
1396 if (is_numeric($this->length_units)) {
1397 $this->width_units = $this->length_units; // Not used yet
1398 }
1399 if (is_numeric($this->length_units)) {
1400 $this->height_units = $this->length_units; // Not used yet
1401 }
1402
1403 // Automated compute surface and volume if not filled
1404 if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1405 $this->surface = (float) $this->length * (float) $this->width;
1406 $this->surface_units = measuring_units_squared((int) $this->length_units);
1407 }
1408 if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1409 $this->volume = $this->surface * (float) $this->height;
1410 $this->volume_units = measuring_units_cubed((int) $this->height_units);
1411 }
1412
1413 if (empty($this->tva_tx)) {
1414 $this->tva_tx = 0;
1415 }
1416 if (empty($this->tva_npr)) {
1417 $this->tva_npr = 0;
1418 }
1419 if (empty($this->localtax1_tx)) {
1420 $this->localtax1_tx = 0;
1421 }
1422 if (empty($this->localtax2_tx)) {
1423 $this->localtax2_tx = 0;
1424 }
1425 if (empty($this->localtax1_type)) {
1426 $this->localtax1_type = '0';
1427 }
1428 if (empty($this->localtax2_type)) {
1429 $this->localtax2_type = '0';
1430 }
1431 if (empty($this->status)) {
1432 $this->status = 0;
1433 }
1434 if (empty($this->status_buy)) {
1435 $this->status_buy = 0;
1436 }
1437
1438 if (empty($this->country_id)) {
1439 $this->country_id = 0;
1440 }
1441
1442 if (empty($this->state_id)) {
1443 $this->state_id = 0;
1444 }
1445
1446 // Barcode value
1447 $this->barcode = (empty($this->barcode) ? '' : trim($this->barcode));
1448
1449 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1450 $this->accountancy_code_buy_intra = (!empty($this->accountancy_code_buy_intra) ? trim($this->accountancy_code_buy_intra) : '');
1451 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1452 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1453 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1454 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1455
1456 // Normalize the accountancy codes the way the admin dropdown does it, so an API client that
1457 // sends '606111000' ends up with the same '606111' value the GUI stores (see issue #32343).
1458 // When ACCOUNTING_MANAGE_ZERO is on, trailing zeros are part of the code and must be kept.
1459 if (!getDolGlobalString('ACCOUNTING_MANAGE_ZERO')) {
1460 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
1461 $this->accountancy_code_buy = clean_account($this->accountancy_code_buy);
1462 $this->accountancy_code_buy_intra = clean_account($this->accountancy_code_buy_intra);
1463 $this->accountancy_code_buy_export = clean_account($this->accountancy_code_buy_export);
1464 $this->accountancy_code_sell = clean_account($this->accountancy_code_sell);
1465 $this->accountancy_code_sell_intra = clean_account($this->accountancy_code_sell_intra);
1466 $this->accountancy_code_sell_export = clean_account($this->accountancy_code_sell_export);
1467 }
1468
1469
1470 $this->db->begin();
1471
1472 $result = 0;
1473 // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1474 if ($action != 'add') {
1475 $result = $this->verify(); // We don't check when update called during a create because verify was already done
1476 } else {
1477 // we can continue
1478 $result = 0;
1479 }
1480
1481 if ($result >= 0) {
1482 // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1483 if (is_null($this->oldcopy) || (is_object($this->oldcopy) && $this->oldcopy->isEmpty())) {
1484 $this->oldcopy = dol_clone($this, 1);
1485 }
1486 // Test if batch management is activated on existing product
1487 // If yes, we create missing entries into product_batch
1488 if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1489 //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1490 $valueforundefinedlot = '000000';
1491 if (getDolGlobalString('STOCK_DEFAULT_BATCH')) {
1492 $valueforundefinedlot = getDolGlobalString('STOCK_DEFAULT_BATCH');
1493 }
1494
1495 dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1496
1497 $this->load_stock();
1498 foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1499 $qty_batch = 0;
1500 foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1501 if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1502 // We discard this line, we will create it later
1503 $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1504 $result = $this->db->query($sqlclean);
1505 if (!$result) {
1506 dol_print_error($this->db);
1507 exit;
1508 }
1509 continue;
1510 }
1511
1512 $qty_batch += $detail->qty;
1513 }
1514 // Quantities in batch details are not same as stock quantity,
1515 // so we add a default batch record to complete and get same qty in parent and child table
1516 if ($ObjW->real != $qty_batch) {
1517 $ObjBatch = new Productbatch($this->db);
1518 $ObjBatch->batch = $valueforundefinedlot;
1519 $ObjBatch->qty = ($ObjW->real - $qty_batch);
1520 $ObjBatch->fk_product_stock = (int) $ObjW->id;
1521
1522 if ($ObjBatch->create($user, 1) < 0) {
1523 $error++;
1524 $this->errors = $ObjBatch->errors;
1525 } else {
1526 // we also add lot record if not exist
1527 $ObjLot = new Productlot($this->db);
1528 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
1529 if ($ObjLot->fetch(0, $this->id, $valueforundefinedlot) == 0) {
1530 $ObjLot->fk_product = $this->id;
1531 $ObjLot->entity = $this->entity;
1532 $ObjLot->fk_user_creat = $user->id;
1533 $ObjLot->batch = $valueforundefinedlot;
1534 if ($ObjLot->create($user, true) < 0) {
1535 $error++;
1536 $this->errors = $ObjLot->errors;
1537 }
1538 }
1539 }
1540 }
1541 }
1542 }
1543
1544 // For automatic creation
1545 if ($this->barcode == -1) {
1546 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1547 }
1548
1549 $sql = "UPDATE ".$this->db->prefix()."product";
1550 $sql .= " SET label = '".$this->db->escape($this->label)."'";
1551
1552 if ($updatetype && ($this->isProduct() || $this->isService())) {
1553 $sql .= ", fk_product_type = ".((int) $this->type);
1554 }
1555
1556 $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1557 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1558 $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1559 $sql .= ", tva_tx = ".((float) $this->tva_tx);
1560 $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1561 $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1562 $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1563 $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1564 $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1565
1566 $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1567 $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape($this->barcode_type));
1568
1569 $sql .= ", tosell = ".(int) $this->status;
1570 $sql .= ", tobuy = ".(int) $this->status_buy;
1571 $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1572 $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);
1573 $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1574
1575 $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished === '') ? "null" : (int) $this->finished);
1576 $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1577 $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1578 $sql .= ", net_measure_units = ".($this->net_measure_units != '' ? "'".$this->db->escape($this->net_measure_units)."'" : 'null');
1579 $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1580 $sql .= ", weight_units = ".($this->weight_units != '' ? "'".$this->db->escape($this->weight_units)."'" : 'null');
1581 $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1582 $sql .= ", length_units = ".($this->length_units != '' ? "'".$this->db->escape($this->length_units)."'" : 'null');
1583 $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1584 $sql .= ", width_units = ".($this->width_units != '' ? "'".$this->db->escape($this->width_units)."'" : 'null');
1585 $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1586 $sql .= ", height_units = ".($this->height_units != '' ? "'".$this->db->escape($this->height_units)."'" : 'null');
1587 $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1588 $sql .= ", surface_units = ".($this->surface_units != '' ? "'".$this->db->escape($this->surface_units)."'" : 'null');
1589 $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1590 $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null');
1591 $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1592 $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1593 $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1594 $sql .= ", description = '".$this->db->escape($this->description)."'";
1595 $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1596 $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1597 $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1598 $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1599 $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1600 $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1601 $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1602 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1603 $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1604 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1605 $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1606 $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1607 $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1608 $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1609 $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1610 $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1611 }
1612 $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1613 $sql .= ", cost_price = ".($this->cost_price != '' ? ((float) $this->cost_price) : 'null');
1614 $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1615 $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1616 $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1617 $sql .= ", fk_user_modif = ".($user->id > 0 ? (int) $user->id : 'NULL');
1618 $sql .= ", mandatory_period = ".((int) $this->mandatory_period);
1619 // stock field is not here because it is a denormalized value from product_stock.
1620 $sql .= " WHERE rowid = ".((int) $id);
1621
1622 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1623
1624 $resql = $this->db->query($sql);
1625 if ($resql) {
1626 $this->id = $id;
1627
1628 // Multilangs
1629 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1630 if ($this->setMultiLangs($user) < 0) {
1631 $this->db->rollback();
1632 return -2;
1633 }
1634 }
1635
1636 $action = 'update';
1637
1638 // update accountancy for this entity
1639 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1640 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1641
1642 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1643 $sql .= " fk_product";
1644 $sql .= ", entity";
1645 $sql .= ", accountancy_code_buy";
1646 $sql .= ", accountancy_code_buy_intra";
1647 $sql .= ", accountancy_code_buy_export";
1648 $sql .= ", accountancy_code_sell";
1649 $sql .= ", accountancy_code_sell_intra";
1650 $sql .= ", accountancy_code_sell_export";
1651 $sql .= ") VALUES (";
1652 $sql .= ((int) $this->id);
1653 $sql .= ", " . ((int) $conf->entity);
1654 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1655 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1656 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1657 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1658 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1659 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1660 $sql .= ")";
1661 $result = $this->db->query($sql);
1662 if (!$result) {
1663 $error++;
1664 $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1665 }
1666 }
1667
1668 if (!$this->hasbatch() && $this->oldcopy->hasbatch()) {
1669 // Selection of all product stock movements that contains batchs
1670 $sql = 'SELECT pb.qty, ps.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb';
1671 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = pb.fk_product_stock)';
1672 $sql .= ' WHERE ps.fk_product = '.(int) $this->id;
1673
1674 $resql = $this->db->query($sql);
1675 if ($resql) {
1676 $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S');
1677
1678 while ($obj = $this->db->fetch_object($resql)) {
1679 $value = $obj->qty;
1680 $fk_entrepot = $obj->fk_entrepot;
1681 $price = 0;
1682 $dlc = '';
1683 $dluo = '';
1684 $batch = $obj->batch;
1685
1686 // To know how to revert stockMouvement (add or remove)
1687 $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add
1688 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1689 $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true);
1690
1691 if ($res > 0) {
1692 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1693 $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0);
1694 if ($res < 0) {
1695 $error++;
1696 }
1697 } else {
1698 $error++;
1699 }
1700 }
1701 }
1702 }
1703
1704 // Actions on extra fields
1705 if (!$error) {
1706 $result = $this->insertExtraFields();
1707 if ($result < 0) {
1708 $error++;
1709 }
1710 }
1711
1712 if (!$error && !$notrigger) {
1713 // Call trigger
1714 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1715 if ($result < 0) {
1716 $error++;
1717 }
1718 // End call triggers
1719 }
1720
1721 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1722 // We remove directory
1723 if ($conf->product->dir_output) {
1724 $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1725 $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1726 if (file_exists($olddir)) {
1727 // include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1728 // $res = dol_move($olddir, $newdir);
1729 // do not use dol_move with directory
1730 $res = @rename($olddir, $newdir);
1731 if (!$res) {
1732 $langs->load("errors");
1733 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1734 $error++;
1735 } else {
1736 // to keep old entries with the new dir
1737 require_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
1738 $ecmfiles = new EcmFiles($this->db);
1739 $ecmfiles->updateAfterRename("produit/".dol_sanitizeFileName($this->oldcopy->ref), "produit/".dol_sanitizeFileName($this->ref));
1740 }
1741 }
1742 }
1743 }
1744
1745 if (!$error) {
1746 if (isModEnabled('variants')) {
1747 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1748
1749 $comb = new ProductCombination($this->db);
1750
1751 foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1752 $currcomb->updateProperties($this, $user);
1753 }
1754 }
1755
1756 $this->db->commit();
1757 return 1;
1758 } else {
1759 $this->db->rollback();
1760 return -$error;
1761 }
1762 } else {
1763 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1764 $langs->load("errors");
1765 if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1766 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1767 } else {
1768 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1769 }
1770 $this->errors[] = $this->error;
1771 $this->db->rollback();
1772 return -1;
1773 } else {
1774 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1775 $this->errors[] = $this->error;
1776 $this->db->rollback();
1777 return -2;
1778 }
1779 }
1780 } else {
1781 $this->db->rollback();
1782 dol_syslog(get_class($this)."::Update fails verify ".implode(',', $this->errors), LOG_WARNING);
1783 return -3;
1784 }
1785 }
1786
1794 public function delete(User $user, $notrigger = 0)
1795 {
1796 global $conf;
1797 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1798
1799 $error = 0;
1800
1801 // Check parameters
1802 if (empty($this->id)) {
1803 $this->error = "Object must be fetched before calling delete";
1804 return -1;
1805 }
1806 if (($this->isProduct() && !$user->hasRight('produit', 'supprimer')) || ($this->isService() && !$user->hasRight('service', 'supprimer'))) {
1807 $this->error = "ErrorForbidden";
1808 return 0;
1809 }
1810
1811 $objectisused = $this->isObjectUsed($this->id);
1812 if (empty($objectisused)) {
1813 $this->db->begin();
1814
1815 if (!$error && empty($notrigger)) {
1816 // Call trigger
1817 $result = $this->call_trigger('PRODUCT_DELETE', $user);
1818 if ($result < 0) {
1819 $error++;
1820 }
1821 // End call triggers
1822 }
1823
1824 // Delete from product_batch on product delete
1825 if (!$error) {
1826 $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1827 $sql .= " WHERE fk_product_stock IN (";
1828 $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1829 $sql .= " WHERE fk_product = ".((int) $this->id).")";
1830
1831 $result = $this->db->query($sql);
1832 if (!$result) {
1833 $error++;
1834 $this->errors[] = $this->db->lasterror();
1835 }
1836 }
1837
1838 // Delete all child tables
1839 if (!$error) {
1840 $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1841 foreach ($elements as $table) {
1842 if (!$error) {
1843 $sql = "DELETE FROM ".$this->db->prefix().$table;
1844 $sql .= " WHERE fk_product = ".(int) $this->id;
1845
1846 $result = $this->db->query($sql);
1847 if (!$result) {
1848 $error++;
1849 $this->errors[] = $this->db->lasterror();
1850 }
1851 }
1852 }
1853 }
1854
1855 if (!$error) {
1856 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1857 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1858
1859 //If it is a parent product, then we remove the association with child products
1860 $prodcomb = new ProductCombination($this->db);
1861
1862 if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1863 $error++;
1864 $this->errors[] = 'Error deleting combinations';
1865 }
1866
1867 //We also check if it is a child product
1868 if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1869 $error++;
1870 $this->errors[] = 'Error deleting child combination';
1871 }
1872 }
1873
1874 // Delete from product_association
1875 if (!$error) {
1876 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1877 $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1878
1879 $result = $this->db->query($sql);
1880 if (!$result) {
1881 $error++;
1882 $this->errors[] = $this->db->lasterror();
1883 }
1884 }
1885
1886 // Remove extrafields
1887 if (!$error) {
1888 $result = $this->deleteExtraFields();
1889 if ($result < 0) {
1890 $error++;
1891 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1892 }
1893 }
1894
1895 // Delete product
1896 if (!$error) {
1897 $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1898 $sqlz .= " WHERE rowid = ".(int) $this->id;
1899
1900 $resultz = $this->db->query($sqlz);
1901 if (!$resultz) {
1902 $error++;
1903 $this->errors[] = $this->db->lasterror();
1904 }
1905 }
1906
1907 // Delete record into ECM index and physically
1908 if (!$error) {
1909 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1910 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1911 if (!$res) {
1912 $error++;
1913 }
1914 }
1915
1916 if (!$error) {
1917 // We remove directory
1918 $ref = dol_sanitizeFileName($this->ref);
1919 if ($conf->product->dir_output) {
1920 $dir = $conf->product->dir_output."/".$ref;
1921 if (file_exists($dir)) {
1922 $res = @dol_delete_dir_recursive($dir);
1923 if (!$res) {
1924 $this->errors[] = 'ErrorFailToDeleteDir';
1925 $error++;
1926 }
1927 }
1928 }
1929 }
1930
1931 if (!$error) {
1932 $this->db->commit();
1933 return 1;
1934 } else {
1935 foreach ($this->errors as $errmsg) {
1936 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1937 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1938 }
1939 $this->db->rollback();
1940 return -$error;
1941 }
1942 } else {
1943 $this->error = "ErrorRecordIsUsedCantDelete";
1944 return 0;
1945 }
1946 }
1947
1953 public static function getSellOrEatByMandatoryList()
1954 {
1955 global $langs;
1956
1957 $sellByLabel = $langs->trans('SellByDate');
1958 $eatByLabel = $langs->trans('EatByDate');
1959 return array(
1960 self::SELL_OR_EAT_BY_MANDATORY_ID_NONE => $langs->trans('BatchSellOrEatByMandatoryNone'),
1961 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY => $sellByLabel,
1962 self::SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY => $eatByLabel,
1963 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT => $langs->trans('BatchSellOrEatByMandatoryAll', $sellByLabel, $eatByLabel),
1964 );
1965 }
1966
1973 {
1974 $sellOrEatByMandatoryLabel = '';
1975
1976 $sellOrEatByMandatoryList = self::getSellOrEatByMandatoryList();
1977 if (isset($sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory])) {
1978 $sellOrEatByMandatoryLabel = $sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory];
1979 }
1980
1981 return $sellOrEatByMandatoryLabel;
1982 }
1983
1990 public function setMultiLangs($user)
1991 {
1992 global $langs;
1993
1994 $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
1995 $current_lang = $langs->getDefaultLang();
1996
1997 foreach ($langs_available as $key => $value) {
1998 if ($key == $current_lang) {
1999 $sql = "SELECT rowid";
2000 $sql .= " FROM ".$this->db->prefix()."product_lang";
2001 $sql .= " WHERE fk_product = ".((int) $this->id);
2002 $sql .= " AND lang = '".$this->db->escape($key)."'";
2003
2004 $result = $this->db->query($sql);
2005
2006 if ($this->db->num_rows($result)) { // if there is already a description line for this language
2007 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
2008 $sql2 .= " SET ";
2009 $sql2 .= " label='".$this->db->escape($this->label)."',";
2010 $sql2 .= " description='".$this->db->escape($this->description)."'";
2011 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2012 $sql2 .= ", note='".$this->db->escape($this->other)."'";
2013 }
2014 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
2015 } else {
2016 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
2017 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2018 $sql2 .= ", note";
2019 }
2020 $sql2 .= ")";
2021 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
2022 $sql2 .= " '".$this->db->escape($this->description)."'";
2023 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2024 $sql2 .= ", '".$this->db->escape($this->other)."'";
2025 }
2026 $sql2 .= ")";
2027 }
2028 dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
2029 if (!$this->db->query($sql2)) {
2030 $this->error = $this->db->lasterror();
2031 return -1;
2032 }
2033 } elseif (isset($this->multilangs[$key])) {
2034 if (empty($this->multilangs[$key]["label"])) {
2035 $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
2036 return -1;
2037 }
2038
2039 $sql = "SELECT rowid";
2040 $sql .= " FROM ".$this->db->prefix()."product_lang";
2041 $sql .= " WHERE fk_product = ".((int) $this->id);
2042 $sql .= " AND lang = '".$this->db->escape($key)."'";
2043
2044 $result = $this->db->query($sql);
2045
2046 if ($this->db->num_rows($result)) { // if there is already a description line for this language
2047 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
2048 $sql2 .= " SET ";
2049 $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
2050 $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
2051 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2052 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2053 $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
2054 }
2055 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
2056 } else {
2057 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
2058 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2059 $sql2 .= ", note";
2060 }
2061 $sql2 .= ")";
2062 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
2063 $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
2064 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2065 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2066 $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
2067 }
2068 $sql2 .= ")";
2069 }
2070
2071 // We do not save if main fields are empty
2072 if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
2073 if (!$this->db->query($sql2)) {
2074 $this->error = $this->db->lasterror();
2075 return -1;
2076 }
2077 }
2078 } else {
2079 // language is not current language and we didn't provide a multilang description for this language
2080 }
2081 }
2082
2083 // Call trigger
2084 $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
2085 if ($result < 0) {
2086 $this->error = $this->db->lasterror();
2087 return -1;
2088 }
2089 // End call triggers
2090
2091 return 1;
2092 }
2093
2102 public function delMultiLangs($langtodelete, $user)
2103 {
2104 $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
2105 $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
2106
2107 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
2108 $result = $this->db->query($sql);
2109 if ($result) {
2110 // Call trigger
2111 $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
2112 if ($result < 0) {
2113 $this->error = $this->db->lasterror();
2114 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2115 return -1;
2116 }
2117 // End call triggers
2118 return 1;
2119 } else {
2120 $this->error = $this->db->lasterror();
2121 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2122 return -1;
2123 }
2124 }
2125
2134 public function setAccountancyCode($type, $value)
2135 {
2136 global $user;
2137
2138 $error = 0;
2139
2140 $this->db->begin();
2141
2142 if ($type == 'buy') {
2143 $field = 'accountancy_code_buy';
2144 } elseif ($type == 'buy_intra') {
2145 $field = 'accountancy_code_buy_intra';
2146 } elseif ($type == 'buy_export') {
2147 $field = 'accountancy_code_buy_export';
2148 } elseif ($type == 'sell') {
2149 $field = 'accountancy_code_sell';
2150 } elseif ($type == 'sell_intra') {
2151 $field = 'accountancy_code_sell_intra';
2152 } elseif ($type == 'sell_export') {
2153 $field = 'accountancy_code_sell_export';
2154 } else {
2155 return -1;
2156 }
2157
2158 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
2159 $sql .= "$field = '".$this->db->escape($value)."'";
2160 $sql .= " WHERE rowid = ".((int) $this->id);
2161
2162 dol_syslog(__METHOD__, LOG_DEBUG);
2163 $resql = $this->db->query($sql);
2164
2165 if ($resql) {
2166 // Call trigger
2167 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
2168 if ($result < 0) {
2169 $error++;
2170 }
2171 // End call triggers
2172
2173 if ($error) {
2174 $this->db->rollback();
2175 return -1;
2176 }
2177
2178 $this->$field = $value;
2179
2180 $this->db->commit();
2181 return 1;
2182 } else {
2183 $this->error = $this->db->lasterror();
2184 $this->db->rollback();
2185 return -1;
2186 }
2187 }
2188
2194 public function getMultiLangs()
2195 {
2196 global $langs;
2197
2198 $current_lang = $langs->getDefaultLang();
2199
2200 $sql = "SELECT lang, label, description, note as other";
2201 $sql .= " FROM ".$this->db->prefix()."product_lang";
2202 $sql .= " WHERE fk_product = ".((int) $this->id);
2203
2204 $result = $this->db->query($sql);
2205 if ($result) {
2206 while ($obj = $this->db->fetch_object($result)) {
2207 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
2208 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
2209 $this->label = $obj->label;
2210 $this->description = $obj->description;
2211 $this->other = $obj->other;
2212 }
2213 $this->multilangs[(string) $obj->lang]["label"] = $obj->label;
2214 $this->multilangs[(string) $obj->lang]["description"] = $obj->description;
2215 $this->multilangs[(string) $obj->lang]["other"] = $obj->other;
2216 }
2217 return 1;
2218 } else {
2219 $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
2220 return -1;
2221 }
2222 }
2223
2230 private function getArrayForPriceCompare($level = 0)
2231 {
2232 $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
2233
2234 foreach ($testExit as $field) {
2235 if (!isset($this->$field)) {
2236 return array();
2237 }
2238 $tmparray = $this->$field;
2239 if (!isset($tmparray[$level])) {
2240 return array();
2241 }
2242 }
2243
2244 $lastPrice = array(
2245 'level' => $level ? $level : 1,
2246 'multiprices' => (float) $this->multiprices[$level],
2247 'multiprices_ttc' => (float) $this->multiprices_ttc[$level],
2248 'multiprices_base_type' => $this->multiprices_base_type[$level],
2249 'multiprices_min' => (float) $this->multiprices_min[$level],
2250 'multiprices_min_ttc' => (float) $this->multiprices_min_ttc[$level],
2251 'multiprices_tva_tx' => (float) $this->multiprices_tva_tx[$level],
2252 'multiprices_recuperableonly' => (float) $this->multiprices_recuperableonly[$level],
2253 );
2254
2255 return $lastPrice;
2256 }
2257
2258
2259 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2267 private function _log_price($user, $level = 0)
2268 {
2269 // phpcs:enable
2270 global $conf;
2271
2272 $now = dol_now();
2273
2274 // Clean parameters
2275 if (empty($this->price_by_qty)) {
2276 $this->price_by_qty = 0;
2277 }
2278
2279 // Add new price
2280 $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,";
2281 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
2282 $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).",";
2283 $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');
2284 $sql .= ")";
2285
2286 dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
2287 $resql = $this->db->query($sql);
2288 if (!$resql) {
2289 $this->error = $this->db->lasterror();
2290 dol_print_error($this->db);
2291 return -1;
2292 } else {
2293 return 1;
2294 }
2295 }
2296
2297
2298 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2306 public function log_price_delete($user, $rowid)
2307 {
2308 // phpcs:enable
2309 $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
2310 $sql .= " WHERE fk_product_price = ".((int) $rowid);
2311 $resql = $this->db->query($sql);
2312
2313 $sql = "DELETE FROM ".$this->db->prefix()."product_price";
2314 $sql .= " WHERE rowid=".((int) $rowid);
2315 $resql = $this->db->query($sql);
2316 if ($resql) {
2317 return 1;
2318 } else {
2319 $this->error = $this->db->lasterror();
2320 return -1;
2321 }
2322 }
2323
2324
2334 public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
2335 {
2336 global $hookmanager, $action;
2337
2338 // Call hook if any
2339 if (is_object($hookmanager)) {
2340 $parameters = array('thirdparty_seller' => $thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
2341 // Note that $action and $object may have been modified by some hooks
2342 $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
2343 if ($reshook > 0) {
2344 return $hookmanager->resArray;
2345 }
2346 }
2347
2348 // Update if prices fields are defined
2349 $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
2350 $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
2351 if (empty($tva_tx)) {
2352 $tva_npr = 0;
2353 }
2354
2355 $pu_ht = $this->price;
2356 $pu_ttc = $this->price_ttc;
2357 $price_min = $this->price_min;
2358 $price_base_type = $this->price_base_type;
2359
2360 // if price by customer / level
2361 if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
2362 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2363
2364 $prodcustprice = new ProductCustomerPrice($this->db);
2365
2366 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2367
2368 // If a price per customer exist
2369 $pricebycustomerexist = false;
2370 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2371 if ($result) {
2372 if (count($prodcustprice->lines) > 0) {
2373 $pricebycustomerexist = true;
2374 $pu_ht = price($prodcustprice->lines[0]->price);
2375 $price_min = price($prodcustprice->lines[0]->price_min);
2376 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2377 $price_base_type = $prodcustprice->lines[0]->price_base_type;
2378 $tva_tx = $prodcustprice->lines[0]->tva_tx;
2379 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2380 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2381 }
2382 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2383 if (empty($tva_tx)) {
2384 $tva_npr = 0;
2385 }
2386 }
2387 }
2388
2389 if (!$pricebycustomerexist && !empty($thirdparty_buyer->price_level)) {
2390 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2391 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2392 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2393 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2394 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) {
2395 // using this option is a bug. kept for backward compatibility
2396 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2397 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2398 }
2399 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2400 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2401 }
2402 if (empty($tva_tx)) {
2403 $tva_npr = 0;
2404 }
2405 }
2406 }
2407 } elseif (getDolGlobalString('PRODUIT_MULTIPRICES') && !empty($thirdparty_buyer->price_level)) { // // If price per segment
2408 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2409 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2410 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2411 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2412 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
2413 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2414 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2415 }
2416 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2417 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2418 }
2419 if (empty($tva_tx)) {
2420 $tva_npr = 0;
2421 }
2422 }
2423 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
2424 // If price per customer
2425 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2426
2427 $prodcustprice = new ProductCustomerPrice($this->db);
2428
2429 $filter = array('t.fk_product' => $this->id, 't.fk_soc' => $thirdparty_buyer->id);
2430
2431 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2432 if ($result) {
2433 if (count($prodcustprice->lines) > 0) {
2434 $pu_ht = price($prodcustprice->lines[0]->price);
2435 $price_min = price($prodcustprice->lines[0]->price_min);
2436 $pu_ttc = price($prodcustprice->lines[0]->price_ttc);
2437 $price_base_type = $prodcustprice->lines[0]->price_base_type;
2438 $tva_tx = $prodcustprice->lines[0]->tva_tx;
2439 if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2440 $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
2441 }
2442 $tva_npr = $prodcustprice->lines[0]->recuperableonly;
2443 if (empty($tva_tx)) {
2444 $tva_npr = 0;
2445 }
2446 }
2447 }
2448 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
2449 // If price per quantity
2450 if ($this->prices_by_qty[0]) {
2451 // yes, this product has some prices per quantity
2452 // Search price into product_price_by_qty from $this->id
2453 foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
2454 if ($priceforthequantityarray['rowid'] != $pqp) {
2455 continue;
2456 }
2457 // We found the price
2458 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2459 $pu_ht = $priceforthequantityarray['unitprice'];
2460 } else {
2461 $pu_ttc = $priceforthequantityarray['unitprice'];
2462 }
2463 break;
2464 }
2465 }
2466 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2467 // If price per quantity and customer
2468 if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
2469 // yes, this product has some prices per quantity
2470 // Search price into product_price_by_qty from $this->id
2471 foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
2472 if ($priceforthequantityarray['rowid'] != $pqp) {
2473 continue;
2474 }
2475 // We found the price
2476 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2477 $pu_ht = $priceforthequantityarray['unitprice'];
2478 } else {
2479 $pu_ttc = $priceforthequantityarray['unitprice'];
2480 }
2481 break;
2482 }
2483 }
2484 }
2485
2486 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);
2487 }
2488
2489 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2503 public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2504 {
2505 // phpcs:enable
2506 global $action, $hookmanager;
2507
2508 // Call hook if any
2509 if (is_object($hookmanager)) {
2510 $parameters = array(
2511 'prodfournprice' => $prodfournprice,
2512 'qty' => $qty,
2513 'product_id' => $product_id,
2514 'fourn_ref' => $fourn_ref,
2515 'fk_soc' => $fk_soc,
2516 );
2517 // Note that $action and $object may have been modified by some hooks
2518 $reshook = $hookmanager->executeHooks('getBuyPrice', $parameters, $this, $action);
2519 if ($reshook > 0) {
2520 return $hookmanager->resArray;
2521 }
2522 }
2523
2524 $result = 0;
2525
2526 // 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)
2527 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2528 $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,";
2529 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2530 $sql .= " pfp.packaging";
2531 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2532 $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2533 if ($qty > 0) {
2534 $sql .= " AND pfp.quantity <= ".((float) $qty);
2535 }
2536 $sql .= " ORDER BY pfp.quantity DESC";
2537
2538 dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
2539 $resql = $this->db->query($sql);
2540 if ($resql) {
2541 $obj = $this->db->fetch_object($resql);
2542 if ($obj && $obj->quantity > 0) { // If we found a supplier prices from the id of supplier price
2543 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2544 $prod_supplier = new ProductFournisseur($this->db);
2545 $prod_supplier->product_fourn_price_id = $obj->rowid;
2546 $prod_supplier->id = $obj->fk_product;
2547 $prod_supplier->fourn_qty = $obj->quantity;
2548 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2549 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2550
2551 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2552 $priceparser = new PriceParser($this->db);
2553 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2554 if ($price_result >= 0) {
2555 $obj->price = $price_result;
2556 }
2557 }
2558 $this->product_fourn_price_id = $obj->rowid;
2559 $this->buyprice = $obj->price; // deprecated
2560 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2561 $this->fourn_price_base_type = 'HT'; // Price base type
2562 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2563 $this->ref_fourn = $obj->ref_supplier; // deprecated
2564 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2565 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2566 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2567 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2568 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2569 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2570 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2571 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2572 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2573 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2574 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2575 $this->packaging = $obj->packaging;
2576 }
2577 $result = $obj->fk_product;
2578 return $result;
2579 } else { // If not found
2580 // 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.
2581 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2582 $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,";
2583 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2584 $sql .= " pfp.packaging";
2585 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2586 $sql .= " WHERE 1 = 1";
2587 if ($product_id > 0) {
2588 $sql .= " AND pfp.fk_product = ".((int) $product_id);
2589 }
2590 if ($fourn_ref != 'none') {
2591 $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2592 }
2593 if ($fk_soc > 0) {
2594 $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2595 }
2596 if ($qty > 0) {
2597 $sql .= " AND pfp.quantity <= ".((float) $qty);
2598 }
2599 $sql .= " ORDER BY pfp.quantity DESC";
2600 $sql .= " LIMIT 1";
2601
2602 dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2603 $resql = $this->db->query($sql);
2604 if ($resql) {
2605 $obj = $this->db->fetch_object($resql);
2606 if ($obj && $obj->quantity > 0) { // If found
2607 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2608 $prod_supplier = new ProductFournisseur($this->db);
2609 $prod_supplier->product_fourn_price_id = $obj->rowid;
2610 $prod_supplier->id = $obj->fk_product;
2611 $prod_supplier->fourn_qty = $obj->quantity;
2612 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2613 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2614
2615 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2616 $priceparser = new PriceParser($this->db);
2617 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2618 if ($result >= 0) {
2619 $obj->price = $price_result;
2620 }
2621 }
2622 $this->product_fourn_price_id = $obj->rowid;
2623 $this->buyprice = $obj->price; // deprecated
2624 $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2625 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2626 $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2627 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2628 $this->ref_fourn = $obj->ref_supplier; // deprecated
2629 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2630 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2631 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2632 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2633 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2634 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2635 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2636 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2637 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2638 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2639 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2640 $this->packaging = $obj->packaging;
2641 }
2642 $result = $obj->fk_product;
2643 return $result;
2644 } else {
2645 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é.
2646 }
2647 } else {
2648 $this->error = $this->db->lasterror();
2649 return -3;
2650 }
2651 }
2652 } else {
2653 $this->error = $this->db->lasterror();
2654 return -2;
2655 }
2656 }
2657
2658
2677 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)
2678 {
2679 $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2680
2681 $id = $this->id;
2682
2683 dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2684
2685 // Clean parameters
2686 if (empty($this->tva_tx)) {
2687 $this->tva_tx = 0.0;
2688 }
2689 if (empty($newnpr)) {
2690 $newnpr = 0.0;
2691 }
2692 if (empty($newminprice)) {
2693 $newminprice = 0.0;
2694 }
2695
2696 // Check parameters
2697 if ($newvat === null || $newvat == '') { // Maintain '' for backwards compatibility
2698 $newvat = (float) $this->tva_tx;
2699 }
2700
2701 $localtaxtype1 = '';
2702 $localtaxtype2 = '';
2703
2704 // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2705 // Price will be modified ONLY when the first one is the one that is being modified
2706 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2707 return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2708 }
2709
2710 if (!empty($newminprice) && ($newminprice > $newprice)) {
2711 $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2712 return -1;
2713 }
2714
2715 if ($newprice === 0 || $newprice !== '') {
2716 if ($newpricebase == 'TTC') {
2717 $price_ttc = (float) price2num($newprice, 'MU');
2718 $price = (float) price2num($newprice) / (1 + ((float) $newvat / 100));
2719 $price = (float) price2num($price, 'MU');
2720
2721 if ((string) $newminprice != '0') {
2722 $price_min_ttc = (float) price2num($newminprice, 'MU');
2723 $price_min = (float) price2num($newminprice) / (1 + ($newvat / 100));
2724 $price_min = (float) price2num($price_min, 'MU');
2725 } else {
2726 $price_min = 0.0;
2727 $price_min_ttc = 0.0;
2728 }
2729 } else {
2730 $price = (float) price2num($newprice, 'MU');
2731 $price_ttc = ($newnpr != 1) ? (float) price2num($newprice) * (1 + ($newvat / 100)) : $price;
2732 $price_ttc = (float) price2num($price_ttc, 'MU');
2733
2734 if ((string) $newminprice != '0') {
2735 $price_min = (float) price2num($newminprice, 'MU');
2736 $price_min_ttc = (float) price2num($newminprice) * (1 + ($newvat / 100));
2737 $price_min_ttc = (float) price2num($price_min_ttc, 'MU');
2738 //print 'X'.$newminprice.'-'.$price_min;
2739 } else {
2740 $price_min = 0.0;
2741 $price_min_ttc = 0.0;
2742 }
2743 }
2744 //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2745 if (count($localtaxes_array) > 0) {
2746 $localtaxtype1 = $localtaxes_array['0'];
2747 $localtax1 = $localtaxes_array['1'];
2748 $localtaxtype2 = $localtaxes_array['2'];
2749 $localtax2 = $localtaxes_array['3'];
2750 } else {
2751 // if array empty, we try to use the vat code
2752 if (!empty($newdefaultvatcode)) {
2753 global $mysoc;
2754 // Get record from code
2755 $sql = "SELECT t.rowid, t.code, t.recuperableonly as tva_npr, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2756 $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2757 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2758 $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2759 $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2760 $resql = $this->db->query($sql);
2761 if ($resql) {
2762 $obj = $this->db->fetch_object($resql);
2763 if ($obj) {
2764 $npr = $obj->tva_npr;
2765 $localtax1 = $obj->localtax1;
2766 $localtax2 = $obj->localtax2;
2767 $localtaxtype1 = $obj->localtax1_type;
2768 $localtaxtype2 = $obj->localtax2_type;
2769 }
2770 }
2771 } else {
2772 // old method. deprecated because we can't retrieve type
2773 $localtaxtype1 = '0';
2774 $localtax1 = get_localtax($newvat, 1);
2775 $localtaxtype2 = '0';
2776 $localtax2 = get_localtax($newvat, 2);
2777 }
2778 }
2779 if (empty($localtax1)) {
2780 $localtax1 = 0; // If = '' then = 0
2781 }
2782 if (empty($localtax2)) {
2783 $localtax2 = 0; // If = '' then = 0
2784 }
2785
2786 $this->db->begin();
2787
2788 // Ne pas mettre de quote sur les numeriques decimaux.
2789 // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2790 $sql = "UPDATE ".$this->db->prefix()."product SET";
2791 $sql .= " price_base_type = '".$this->db->escape($newpricebase)."',";
2792 $sql .= " price = ".(float) $price.",";
2793 $sql .= " price_ttc = ".(float) $price_ttc.",";
2794 $sql .= " price_min = ".(float) $price_min.",";
2795 $sql .= " price_min_ttc = ".(float) $price_min_ttc.",";
2796 $sql .= " localtax1_tx = ".($localtax1 >= 0 ? (float) $localtax1 : 'NULL').",";
2797 $sql .= " localtax2_tx = ".($localtax2 >= 0 ? (float) $localtax2 : 'NULL').",";
2798 $sql .= " localtax1_type = ".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2799 $sql .= " localtax2_type = ".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2800 $sql .= " default_vat_code = ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2801 $sql .= " price_label = ".(!empty($price_label) ? "'".$this->db->escape($price_label)."'" : "null").",";
2802 $sql .= " tva_tx = ".(float) price2num($newvat).",";
2803 $sql .= " recuperableonly = '".$this->db->escape($newnpr)."'";
2804 $sql .= " WHERE rowid = ".((int) $id);
2805
2806 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2807 $resql = $this->db->query($sql);
2808 if ($resql) {
2809 $this->multiprices[$level] = $price;
2810 $this->multiprices_ttc[$level] = $price_ttc;
2811 $this->multiprices_min[$level] = $price_min;
2812 $this->multiprices_min_ttc[$level] = $price_min_ttc;
2813 $this->multiprices_base_type[$level] = $newpricebase;
2814 $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2815 $this->multiprices_tva_tx[$level] = $newvat;
2816 $this->multiprices_recuperableonly[$level] = $newnpr;
2817
2818 $this->price = $price;
2819 $this->price_label = $price_label;
2820 $this->price_ttc = $price_ttc;
2821 $this->price_min = $price_min;
2822 $this->price_min_ttc = $price_min_ttc;
2823 $this->price_base_type = $newpricebase;
2824 $this->default_vat_code = $newdefaultvatcode;
2825 $this->tva_tx = $newvat;
2826 $this->tva_npr = $newnpr;
2827
2828 //Local taxes
2829 $this->localtax1_tx = $localtax1;
2830 $this->localtax2_tx = $localtax2;
2831 $this->localtax1_type = $localtaxtype1;
2832 $this->localtax2_type = $localtaxtype2;
2833
2834 // Price by quantity
2835 $this->price_by_qty = $newpbq;
2836
2837 // check if price have really change before log
2838 $newPriceData = $this->getArrayForPriceCompare($level);
2839 if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || !getDolGlobalString('PRODUIT_MULTIPRICES')) {
2840 $this->_log_price($user, $level); // Save price for level into table product_price
2841 }
2842
2843 $this->level = $level; // Store level of price edited for trigger
2844
2845 // Call trigger
2846 if (!$notrigger) {
2847 $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2848 if ($result < 0) {
2849 $this->db->rollback();
2850 return -1;
2851 }
2852 }
2853 // End call triggers
2854
2855 $this->db->commit();
2856 } else {
2857 $this->db->rollback();
2858 $this->error = $this->db->lasterror();
2859 return -1;
2860 }
2861 }
2862
2863 return 1;
2864 }
2865
2873 public function setPriceExpression($expression_id)
2874 {
2875 global $user;
2876
2877 $this->fk_price_expression = $expression_id;
2878
2879 return $this->update($this->id, $user);
2880 }
2881
2894 public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2895 {
2896 include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2897
2898 global $conf;
2899
2900 dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2901
2902 // Check parameters
2903 if (!$id && !$ref && !$ref_ext && !$barcode) {
2904 $this->error = 'ErrorWrongParameters';
2905 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2906 return -1;
2907 }
2908
2909 $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,";
2910 $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,";
2911 $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,";
2912 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.last_main_doc,";
2913 $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,";
2914 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2915 $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,";
2916 } else {
2917 $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,";
2918 }
2919
2920 // For MultiCompany
2921 // PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
2922 $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
2923 $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.
2924 $visibleWarehousesEntities = $conf->entity;
2925 if (getDolGlobalString('MULTICOMPANY_PRODUCT_SHARING_ENABLED')) {
2926 if (getDolGlobalString('MULTICOMPANY_PMP_PER_ENTITY_ENABLED')) {
2927 $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
2928 if ($this->db->num_rows($checkPMPPerEntity) > 0) {
2929 $separatedEntityPMP = true;
2930 }
2931 }
2932 global $mc;
2933 $separatedStock = true;
2934 if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
2935 $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
2936 }
2937 }
2938 if ($separatedEntityPMP) {
2939 $sql .= " ppe.pmp,";
2940 } else {
2941 $sql .= " p.pmp,";
2942 }
2943 $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,";
2944 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf,";
2945 $sql .= " p.price_label,";
2946 if ($separatedStock) {
2947 $sql .= " SUM(sp.reel) as stock";
2948 } else {
2949 $sql .= " p.stock";
2950 }
2951 $sql .= " FROM ".$this->db->prefix()."product as p";
2952 if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') || $separatedEntityPMP) {
2953 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
2954 }
2955 if ($separatedStock) {
2956 $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)."))";
2957 }
2958
2959 if ($id) {
2960 $sql .= " WHERE p.rowid = ".((int) $id);
2961 } else {
2962 $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
2963 if ($ref) {
2964 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
2965 } elseif ($ref_ext) {
2966 $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
2967 } elseif ($barcode) {
2968 $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
2969 }
2970 }
2971 if ($separatedStock) {
2972 $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,";
2973 $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,";
2974 $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,";
2975 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
2976 $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,";
2977 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2978 $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,";
2979 } else {
2980 $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,";
2981 }
2982 if ($separatedEntityPMP) {
2983 $sql .= " ppe.pmp,";
2984 } else {
2985 $sql .= " p.pmp,";
2986 }
2987 $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,";
2988 $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf";
2989 $sql .= " ,p.price_label";
2990 if (!$separatedStock) {
2991 $sql .= ", p.stock";
2992 }
2993 }
2994
2995 $resql = $this->db->query($sql);
2996 if ($resql) {
2997 unset($this->oldcopy);
2998
2999 if ($this->db->num_rows($resql) > 0) {
3000 $obj = $this->db->fetch_object($resql);
3001
3002 $this->id = $obj->rowid;
3003 $this->ref = $obj->ref;
3004 $this->ref_ext = $obj->ref_ext;
3005 $this->label = $obj->label;
3006 $this->description = $obj->description;
3007 $this->url = $obj->url;
3008 $this->note_public = $obj->note_public;
3009 $this->note_private = $obj->note_private;
3010 $this->note = $obj->note_private; // deprecated
3011
3012 $this->type = $obj->fk_product_type;
3013 $this->price_label = $obj->price_label;
3014 $this->status = $obj->tosell;
3015 $this->status_buy = $obj->tobuy;
3016 $this->status_batch = $obj->tobatch;
3017 $this->sell_or_eat_by_mandatory = $obj->sell_or_eat_by_mandatory;
3018 $this->batch_mask = $obj->batch_mask;
3019
3020 $this->customcode = $obj->customcode;
3021 $this->country_id = $obj->fk_country;
3022 $this->country_code = getCountry($this->country_id, '2', $this->db);
3023 $this->state_id = $obj->fk_state;
3024 $this->lifetime = $obj->lifetime;
3025 $this->qc_frequency = $obj->qc_frequency;
3026 $this->price = $obj->price;
3027 $this->price_ttc = $obj->price_ttc;
3028 $this->price_min = $obj->price_min;
3029 $this->price_min_ttc = $obj->price_min_ttc;
3030 $this->price_base_type = $obj->price_base_type;
3031 $this->cost_price = isset($obj->cost_price) ? (float) $obj->cost_price : null;
3032 $this->default_vat_code = $obj->default_vat_code;
3033 $this->tva_tx = $obj->tva_tx;
3035 $this->tva_npr = $obj->tva_npr;
3037 $this->localtax1_tx = $obj->localtax1_tx;
3038 $this->localtax2_tx = $obj->localtax2_tx;
3039 $this->localtax1_type = $obj->localtax1_type;
3040 $this->localtax2_type = $obj->localtax2_type;
3041
3042 $this->finished = $obj->finished;
3043 $this->fk_default_bom = $obj->fk_default_bom;
3044
3045 $this->duration = $obj->duration;
3046 $matches = [];
3047 preg_match('/([\d.]+)(\w+)/', $obj->duration, $matches);
3048 $this->duration_value = !empty($matches[1]) ? (float) $matches[1] : 0;
3049 $this->duration_unit = !empty($matches[2]) ? (string) $matches[2] : null;
3050 $this->canvas = $obj->canvas;
3051 $this->net_measure = $obj->net_measure;
3052 $this->net_measure_units = $obj->net_measure_units;
3053 $this->weight = $obj->weight;
3054 $this->weight_units = (is_null($obj->weight_units) ? 0 : $obj->weight_units);
3055 $this->length = $obj->length;
3056 $this->length_units = (is_null($obj->length_units) ? 0 : $obj->length_units);
3057 $this->width = $obj->width;
3058 $this->width_units = (is_null($obj->width_units) ? 0 : $obj->width_units);
3059 $this->height = $obj->height;
3060 $this->height_units = (is_null($obj->height_units) ? 0 : $obj->height_units);
3061
3062 $this->surface = $obj->surface;
3063 $this->surface_units = (is_null($obj->surface_units) ? 0 : $obj->surface_units);
3064 $this->volume = $obj->volume;
3065 $this->volume_units = (is_null($obj->volume_units) ? 0 : $obj->volume_units);
3066 $this->barcode = $obj->barcode;
3067 $this->barcode_type = $obj->fk_barcode_type;
3068
3069 $this->accountancy_code_buy = $obj->accountancy_code_buy;
3070 $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
3071 $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
3072 $this->accountancy_code_sell = $obj->accountancy_code_sell;
3073 $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
3074 $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
3075
3076 $this->fk_default_warehouse = $obj->fk_default_warehouse;
3077 $this->fk_default_workstation = $obj->fk_default_workstation;
3078 $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
3079 $this->desiredstock = $obj->desiredstock;
3080 $this->stock_reel = $obj->stock;
3081 $this->pmp = $obj->pmp;
3082
3083 $this->date_creation = $this->db->jdate($obj->datec);
3084 $this->date_modification = $this->db->jdate($obj->tms);
3085
3086 $this->import_key = $obj->import_key;
3087 $this->entity = $obj->entity;
3088
3089 $this->ref_ext = $obj->ref_ext;
3090 $this->fk_price_expression = $obj->fk_price_expression;
3091 $this->fk_unit = $obj->fk_unit;
3092 $this->price_autogen = $obj->price_autogen;
3093 $this->model_pdf = $obj->model_pdf;
3094 $this->last_main_doc = $obj->last_main_doc;
3095
3096 $this->mandatory_period = $obj->mandatory_period;
3097
3098 $this->db->free($resql);
3099
3100 // fetch optionals attributes and labels
3101 $this->fetch_optionals();
3102
3103 // Multilangs
3104 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
3105 $this->getMultiLangs();
3106 }
3107
3108 // Load multiprices array
3109 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($ignore_price_load)) { // prices per segment
3110 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
3111 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
3112 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3113 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
3114 $sql .= " ,price_label";
3115 $sql .= " FROM ".$this->db->prefix()."product_price";
3116 $sql .= " WHERE entity IN (".getEntity('productprice').")";
3117 $sql .= " AND price_level=".((int) $i);
3118 $sql .= " AND fk_product = ".((int) $this->id);
3119 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
3120 $sql .= " LIMIT 1"; // Only the first one
3121 $resql = $this->db->query($sql);
3122 if ($resql) {
3123 $result = $this->db->fetch_array($resql);
3124
3125 $this->multiprices[$i] = $result ? $result["price"] : null;
3126 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
3127 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
3128 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
3129 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
3130 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
3131 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].(!empty($result['default_vat_code']) ? ' ('.$result['default_vat_code'].')' : '') : null;
3132 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
3133
3134 // Price by quantity
3135 /*
3136 $this->prices_by_qty[$i]=$result["price_by_qty"];
3137 $this->prices_by_qty_id[$i]=$result["rowid"];
3138 // Récuperation de la liste des prix selon qty si flag positionné
3139 if ($this->prices_by_qty[$i] == 1)
3140 {
3141 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
3142 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
3143 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
3144 $sql.= " ORDER BY quantity ASC";
3145
3146 $resql = $this->db->query($sql);
3147 if ($resql)
3148 {
3149 $resultat=array();
3150 $ii=0;
3151 while ($result= $this->db->fetch_array($resql)) {
3152 $resultat[$ii]=array();
3153 $resultat[$ii]["rowid"]=$result["rowid"];
3154 $resultat[$ii]["price"]= $result["price"];
3155 $resultat[$ii]["unitprice"]= $result["unitprice"];
3156 $resultat[$ii]["quantity"]= $result["quantity"];
3157 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
3158 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
3159 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
3160 $ii++;
3161 }
3162 $this->prices_by_qty_list[$i]=$resultat;
3163 }
3164 else
3165 {
3166 dol_print_error($this->db);
3167 return -1;
3168 }
3169 }*/
3170 } else {
3171 $this->error = $this->db->lasterror;
3172 return -1;
3173 }
3174 }
3175 } elseif ((getDolGlobalString('PRODUIT_CUSTOMER_PRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($ignore_price_load)) { // prices per customers
3176 // Nothing loaded by default. List may be very long.
3177 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
3178 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3179 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
3180 $sql .= " FROM ".$this->db->prefix()."product_price";
3181 $sql .= " WHERE fk_product = ".((int) $this->id);
3182 $sql .= " ORDER BY date_price DESC, rowid DESC";
3183 $sql .= " LIMIT 1";
3184
3185 $resql = $this->db->query($sql);
3186 if ($resql) {
3187 $result = $this->db->fetch_array($resql);
3188
3189 if ($result) {
3190 // Price by quantity
3191 $this->prices_by_qty[0] = $result["price_by_qty"];
3192 $this->prices_by_qty_id[0] = $result["rowid"];
3193 // Récuperation de la liste des prix selon qty si flag positionné
3194 if ($this->prices_by_qty[0] == 1) {
3195 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
3196 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
3197 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
3198 $sql .= " ORDER BY quantity ASC";
3199
3200 $resql = $this->db->query($sql);
3201 if ($resql) {
3202 $resultat = array();
3203 $ii = 0;
3204 while ($result = $this->db->fetch_array($resql)) {
3205 $resultat[$ii] = array();
3206 $resultat[$ii]["rowid"] = $result["rowid"];
3207 $resultat[$ii]["price"] = $result["price"];
3208 $resultat[$ii]["unitprice"] = $result["unitprice"];
3209 $resultat[$ii]["quantity"] = $result["quantity"];
3210 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
3211 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
3212 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
3213 $ii++;
3214 }
3215 $this->prices_by_qty_list[0] = $resultat;
3216 } else {
3217 $this->error = $this->db->lasterror;
3218 return -1;
3219 }
3220 }
3221 }
3222 } else {
3223 $this->error = $this->db->lasterror;
3224 return -1;
3225 }
3226 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
3227 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
3228 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
3229 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3230 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
3231 $sql .= " FROM ".$this->db->prefix()."product_price";
3232 $sql .= " WHERE entity IN (".getEntity('productprice').")";
3233 $sql .= " AND price_level=".((int) $i);
3234 $sql .= " AND fk_product = ".((int) $this->id);
3235 $sql .= " ORDER BY date_price DESC, rowid DESC";
3236 $sql .= " LIMIT 1";
3237 $resql = $this->db->query($sql);
3238 if (!$resql) {
3239 $this->error = $this->db->lasterror;
3240 return -1;
3241 } elseif ($result = $this->db->fetch_array($resql)) {
3242 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
3243 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
3244 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
3245 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
3246 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
3247 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
3248 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
3249 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
3250
3251 // Price by quantity
3252 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
3253 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
3254 // Récuperation de la liste des prix selon qty si flag positionné
3255 if ($this->prices_by_qty[$i] == 1) {
3256 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
3257 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
3258 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
3259 $sql .= " ORDER BY quantity ASC";
3260
3261 $resql = $this->db->query($sql);
3262 if ($resql) {
3263 $resultat = array();
3264 $ii = 0;
3265 while ($result = $this->db->fetch_array($resql)) {
3266 $resultat[$ii] = array();
3267 $resultat[$ii]["rowid"] = $result["rowid"];
3268 $resultat[$ii]["price"] = $result["price"];
3269 $resultat[$ii]["unitprice"] = $result["unitprice"];
3270 $resultat[$ii]["quantity"] = $result["quantity"];
3271 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
3272 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
3273 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
3274 $ii++;
3275 }
3276 $this->prices_by_qty_list[$i] = $resultat;
3277 } else {
3278 $this->error = $this->db->lasterror;
3279 return -1;
3280 }
3281 }
3282 }
3283 }
3284 }
3285
3286 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
3287 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
3288 $priceparser = new PriceParser($this->db);
3289 $price_result = $priceparser->parseProduct($this);
3290 if ($price_result >= 0) {
3291 $this->price = $price_result;
3292 // Calculate the VAT
3293 $this->price_ttc = (float) price2num($this->price) * (1 + ($this->tva_tx / 100));
3294 $this->price_ttc = (float) price2num($this->price_ttc, 'MU');
3295 }
3296 }
3297
3298 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
3299 // Instead we just init the stock_warehouse array
3300 $this->stock_warehouse = array();
3301
3302 return 1;
3303 } else {
3304 return 0;
3305 }
3306 } else {
3307 $this->error = $this->db->lasterror();
3308 return -1;
3309 }
3310 }
3311
3312 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3319 public function load_stats_mo($socid = 0)
3320 {
3321 // phpcs:enable
3322 global $user, $hookmanager, $action;
3323
3324 $error = 0;
3325
3326 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
3327 $this->stats_mo['customers_'.$role] = 0;
3328 $this->stats_mo['nb_'.$role] = 0;
3329 $this->stats_mo['qty_'.$role] = 0;
3330
3331 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3332 $sql .= " SUM(mp.qty) as qty";
3333 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
3334 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
3335 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3336 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3337 }
3338 $sql .= " WHERE ";
3339 $sql .= " c.entity IN (".getEntity('mo').")";
3340
3341 $sql .= " AND mp.fk_product = ".((int) $this->id);
3342 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
3343 if ($socid > 0) {
3344 $sql .= " AND c.fk_soc = ".((int) $socid);
3345 }
3346
3347 $result = $this->db->query($sql);
3348 if ($result) {
3349 $obj = $this->db->fetch_object($result);
3350 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
3351 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
3352 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
3353 } else {
3354 $this->error = $this->db->error();
3355 $error++;
3356 }
3357 }
3358
3359 if (!empty($error)) {
3360 return -1;
3361 }
3362
3363 $parameters = array('socid' => $socid);
3364 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3365 if ($reshook > 0) {
3366 $this->stats_mo = $hookmanager->resArray['stats_mo'];
3367 }
3368
3369 return 1;
3370 }
3371
3372 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3379 public function load_stats_bom($socid = 0)
3380 {
3381 // phpcs:enable
3382 global $hookmanager, $action;
3383
3384 $error = 0;
3385
3386 $this->stats_bom['nb_toproduce'] = 0;
3387 $this->stats_bom['nb_toconsume'] = 0;
3388 $this->stats_bom['qty_toproduce'] = 0;
3389 $this->stats_bom['qty_toconsume'] = 0;
3390
3391 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
3392 $sql .= " SUM(b.qty) as qty_toproduce";
3393 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3394 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom = b.rowid";
3395 $sql .= " WHERE ";
3396 $sql .= " b.entity IN (".getEntity('bom').")";
3397 $sql .= " AND b.fk_product =".((int) $this->id);
3398 $sql .= " GROUP BY b.rowid";
3399
3400 $result = $this->db->query($sql);
3401 if ($result) {
3402 $obj = $this->db->fetch_object($result);
3403 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
3404 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
3405 } else {
3406 $this->error = $this->db->error();
3407 $error++;
3408 }
3409
3410 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
3411 $sql .= " SUM(bl.qty) as qty_toconsume";
3412 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3413 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
3414 $sql .= " WHERE ";
3415 $sql .= " b.entity IN (".getEntity('bom').")";
3416 $sql .= " AND bl.fk_product =".((int) $this->id);
3417
3418 $result = $this->db->query($sql);
3419 if ($result) {
3420 $obj = $this->db->fetch_object($result);
3421 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
3422 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
3423 } else {
3424 $this->error = $this->db->error();
3425 $error++;
3426 }
3427
3428 if (!empty($error)) {
3429 return -1;
3430 }
3431
3432 $parameters = array('socid' => $socid);
3433 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3434 if ($reshook > 0) {
3435 $this->stats_bom = $hookmanager->resArray['stats_bom'];
3436 }
3437
3438 return 1;
3439 }
3440
3441 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3448 public function load_stats_propale($socid = 0)
3449 {
3450 // phpcs:enable
3451 global $user, $hookmanager, $action;
3452
3453 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3454 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3455 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3456 $sql .= ", ".$this->db->prefix()."propal as p";
3457 $sql .= ", ".$this->db->prefix()."societe as s";
3458 $sql .= " WHERE p.rowid = pd.fk_propal";
3459 $sql .= " AND p.fk_soc = s.rowid";
3460 $sql .= " AND p.entity IN (".getEntity('propal').")";
3461 $sql .= " AND pd.fk_product = ".((int) $this->id);
3462 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3463 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $user->id);
3464 }
3465 //$sql.= " AND pr.fk_statut != 0";
3466 if ($socid > 0) {
3467 $sql .= " AND p.fk_soc = ".((int) $socid);
3468 }
3469
3470 $result = $this->db->query($sql);
3471 if ($result) {
3472 $obj = $this->db->fetch_object($result);
3473 $this->stats_propale['customers'] = $obj->nb_customers;
3474 $this->stats_propale['nb'] = $obj->nb;
3475 $this->stats_propale['rows'] = $obj->nb_rows;
3476 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3477
3478 // if it's a virtual product, maybe it is in proposal by extension
3479 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3480 $TFather = $this->getFather();
3481 if (is_array($TFather) && !empty($TFather)) {
3482 foreach ($TFather as &$fatherData) {
3483 $pFather = new Product($this->db);
3484 $pFather->id = $fatherData['id'];
3485 $qtyCoef = $fatherData['qty'];
3486
3487 if ($fatherData['incdec']) {
3488 $pFather->load_stats_propale($socid);
3489
3490 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3491 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3492 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3493 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3494 }
3495 }
3496 }
3497 }
3498
3499 $parameters = array('socid' => $socid);
3500 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3501 if ($reshook > 0) {
3502 $this->stats_propale = $hookmanager->resArray['stats_propale'];
3503 }
3504
3505 return 1;
3506 } else {
3507 $this->error = $this->db->error();
3508 return -1;
3509 }
3510 }
3511
3512
3513 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3520 public function load_stats_proposal_supplier($socid = 0)
3521 {
3522 // phpcs:enable
3523 global $user, $hookmanager, $action;
3524
3525 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3526 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3527 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3528 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3529 $sql .= ", ".$this->db->prefix()."societe as s";
3530 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3531 $sql .= " AND p.fk_soc = s.rowid";
3532 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3533 $sql .= " AND pd.fk_product = ".((int) $this->id);
3534 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3535 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $user->id);
3536 }
3537 //$sql.= " AND pr.fk_statut != 0";
3538 if ($socid > 0) {
3539 $sql .= " AND p.fk_soc = ".((int) $socid);
3540 }
3541
3542 $result = $this->db->query($sql);
3543 if ($result) {
3544 $obj = $this->db->fetch_object($result);
3545 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3546 $this->stats_proposal_supplier['nb'] = $obj->nb;
3547 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3548 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3549
3550 $parameters = array('socid' => $socid);
3551 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3552 if ($reshook > 0) {
3553 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3554 }
3555
3556 return 1;
3557 } else {
3558 $this->error = $this->db->error();
3559 return -1;
3560 }
3561 }
3562
3563
3564 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3573 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3574 {
3575 // phpcs:enable
3576 global $user, $hookmanager, $action;
3577
3578 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3579 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3580 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3581 $sql .= ", ".$this->db->prefix()."commande as c";
3582 $sql .= ", ".$this->db->prefix()."societe as s";
3583 $sql .= " WHERE c.rowid = cd.fk_commande";
3584 $sql .= " AND c.fk_soc = s.rowid";
3585 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3586 $sql .= " AND cd.fk_product = ".((int) $this->id);
3587 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3588 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3589 }
3590 if ($socid > 0) {
3591 $sql .= " AND c.fk_soc = ".((int) $socid);
3592 }
3593 if ($filtrestatut != '') {
3594 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3595 }
3596
3597 $result = $this->db->query($sql);
3598 if ($result) {
3599 $obj = $this->db->fetch_object($result);
3600 $this->stats_commande['customers'] = $obj->nb_customers;
3601 $this->stats_commande['nb'] = $obj->nb;
3602 $this->stats_commande['rows'] = $obj->nb_rows;
3603 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3604
3605 // if it's a virtual product, maybe it is in order by extension
3606 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3607 $TFather = $this->getFather();
3608 if (is_array($TFather) && !empty($TFather)) {
3609 foreach ($TFather as &$fatherData) {
3610 $pFather = new Product($this->db);
3611 $pFather->id = $fatherData['id'];
3612 $qtyCoef = $fatherData['qty'];
3613
3614 if ($fatherData['incdec']) {
3615 $pFather->load_stats_commande($socid, $filtrestatut);
3616
3617 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3618 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3619 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3620 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3621 }
3622 }
3623 }
3624 }
3625
3626 // If stock decrease is on invoice validation, the theoretical stock continue to
3627 // count the orders lines in theoretical stock when some are already removed by invoice validation.
3628 if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3629 if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3630 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3631 $adeduire = 0;
3632 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3633 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3634 $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'))";
3635 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3636 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3637
3638 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3639 $resql = $this->db->query($sql);
3640 if ($resql) {
3641 if ($this->db->num_rows($resql) > 0) {
3642 $obj = $this->db->fetch_object($resql);
3643 $adeduire += $obj->count;
3644 }
3645 }
3646
3647 $this->stats_commande['qty'] -= $adeduire;
3648 } else {
3649 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3650 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3651
3652 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3653 $adeduire = 0;
3654 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', -1, 1)." * fd.qty) as count FROM ".MAIN_DB_PREFIX."facturedet as fd ";
3655 $sql .= " JOIN ".MAIN_DB_PREFIX."facture as f ON fd.fk_facture = f.rowid";
3656 $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'))";
3657 $sql .= " JOIN ".MAIN_DB_PREFIX."commande as c ON el.fk_source = c.rowid";
3658 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3659
3660 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3661 $resql = $this->db->query($sql);
3662 if ($resql) {
3663 if ($this->db->num_rows($resql) > 0) {
3664 $obj = $this->db->fetch_object($resql);
3665 $adeduire += $obj->count;
3666 }
3667 } else {
3668 $this->error = $this->db->error();
3669 return -1;
3670 }
3671
3672 $this->stats_commande['qty'] -= $adeduire;
3673 }
3674 }
3675
3676 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3677 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3678 if ($reshook > 0) {
3679 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3680 }
3681 return 1;
3682 } else {
3683 $this->error = $this->db->error();
3684 return -1;
3685 }
3686 }
3687
3688 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3698 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3699 {
3700 // phpcs:enable
3701 global $user, $hookmanager, $action;
3702
3703 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3704 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3705 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3706 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3707 $sql .= ", ".$this->db->prefix()."societe as s";
3708 $sql .= " WHERE c.rowid = cd.fk_commande";
3709 $sql .= " AND c.fk_soc = s.rowid";
3710 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3711 $sql .= " AND cd.fk_product = ".((int) $this->id);
3712 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3713 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3714 }
3715 if ($socid > 0) {
3716 $sql .= " AND c.fk_soc = ".((int) $socid);
3717 }
3718 if ($filtrestatut != '') {
3719 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3720 }
3721 if (!empty($dateofvirtualstock)) {
3722 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3723 }
3724
3725 $result = $this->db->query($sql);
3726 if ($result) {
3727 $obj = $this->db->fetch_object($result);
3728 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3729 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3730 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3731 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3732
3733 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3734 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3735 if ($reshook > 0) {
3736 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3737 }
3738
3739 return 1;
3740 } else {
3741 $this->error = $this->db->error().' sql='.$sql;
3742 return -1;
3743 }
3744 }
3745
3746 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3756 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3757 {
3758 // phpcs:enable
3759 global $user, $hookmanager, $action;
3760
3761 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3762 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3763 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3764 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3765 $sql .= ", ".$this->db->prefix()."commande as c";
3766 $sql .= ", ".$this->db->prefix()."expedition as e";
3767 $sql .= ", ".$this->db->prefix()."societe as s";
3768 $sql .= " WHERE e.rowid = ed.fk_expedition";
3769 $sql .= " AND c.rowid = cd.fk_commande";
3770 $sql .= " AND e.fk_soc = s.rowid";
3771 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3772 $sql .= " AND ed.fk_elementdet = cd.rowid";
3773 $sql .= " AND cd.fk_product = ".((int) $this->id);
3774 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3775 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = e.fk_soc AND sc.fk_user = ".((int) $user->id);
3776 }
3777 if ($socid > 0) {
3778 $sql .= " AND e.fk_soc = ".((int) $socid);
3779 }
3780 if ($filtrestatut != '') {
3781 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3782 }
3783 if (!empty($filterShipmentStatus)) {
3784 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3785 }
3786
3787 $result = $this->db->query($sql);
3788 if ($result) {
3789 $obj = $this->db->fetch_object($result);
3790 $this->stats_expedition['customers'] = $obj->nb_customers;
3791 $this->stats_expedition['nb'] = $obj->nb;
3792 $this->stats_expedition['rows'] = $obj->nb_rows;
3793 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3794
3795 // if it's a virtual product, maybe it is in sending by extension
3796 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3797 $TFather = $this->getFather();
3798 if (is_array($TFather) && !empty($TFather)) {
3799 foreach ($TFather as &$fatherData) {
3800 $pFather = new Product($this->db);
3801 $pFather->id = $fatherData['id'];
3802 $qtyCoef = $fatherData['qty'];
3803
3804 if ($fatherData['incdec']) {
3805 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3806
3807 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3808 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3809 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3810 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3811 }
3812 }
3813 }
3814 }
3815
3816 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3817 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3818 if ($reshook > 0) {
3819 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3820 }
3821
3822 return 1;
3823 } else {
3824 $this->error = $this->db->error();
3825 return -1;
3826 }
3827 }
3828
3829 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3839 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3840 {
3841 // phpcs:enable
3842 global $user, $hookmanager, $action;
3843
3844 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3845 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3846 $sql .= " FROM ".$this->db->prefix()."receptiondet_batch as fd";
3847 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3848 $sql .= ", ".$this->db->prefix()."societe as s";
3849 $sql .= " WHERE cf.rowid = fd.fk_element";
3850 $sql .= " AND cf.fk_soc = s.rowid";
3851 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3852 $sql .= " AND fd.fk_product = ".((int) $this->id);
3853 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3854 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = cf.fk_soc AND sc.fk_user = ".((int) $user->id);
3855 }
3856 if ($socid > 0) {
3857 $sql .= " AND cf.fk_soc = ".((int) $socid);
3858 }
3859 if ($filtrestatut != '') {
3860 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3861 }
3862 if (!empty($dateofvirtualstock)) {
3863 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3864 }
3865
3866 $result = $this->db->query($sql);
3867 if ($result) {
3868 $obj = $this->db->fetch_object($result);
3869 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3870 $this->stats_reception['nb'] = $obj->nb;
3871 $this->stats_reception['rows'] = $obj->nb_rows;
3872 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3873
3874 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3875 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3876 if ($reshook > 0) {
3877 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3878 }
3879
3880 return 1;
3881 } else {
3882 $this->error = $this->db->error();
3883 return -1;
3884 }
3885 }
3886
3887 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3898 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null, $warehouseid = 0)
3899 {
3900 // phpcs:enable
3901 global $user, $hookmanager, $action;
3902
3903 $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
3904
3905 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3906 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3907 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3908 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3909 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3910 $sql .= " WHERE m.rowid = mp.fk_mo";
3911 $sql .= " AND m.entity IN (".getEntity(($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE')) ? 'stock' : 'mrp').")";
3912 $sql .= " AND mp.fk_product = ".((int) $this->id);
3913 $sql .= " AND (mp.disable_stock_change IN (0) OR mp.disable_stock_change IS NULL)";
3914 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3915 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = m.fk_soc AND sc.fk_user = ".((int) $user->id);
3916 }
3917 if ($socid > 0) {
3918 $sql .= " AND m.fk_soc = ".((int) $socid);
3919 }
3920 if ($filtrestatut != '') {
3921 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3922 }
3923 if (!empty($dateofvirtualstock)) {
3924 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3925 }
3926 if (!$serviceStockIsEnabled) {
3927 $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))";
3928 }
3929 if (!empty($warehouseid)) {
3930 $sql .= " AND m.fk_warehouse = ".((int) $warehouseid);
3931 }
3932 $sql .= " GROUP BY role";
3933
3934 if ($warehouseid) {
3935 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3936 } else {
3937 $this->stats_mrptoconsume['customers'] = 0;
3938 $this->stats_mrptoconsume['nb'] = 0;
3939 $this->stats_mrptoconsume['rows'] = 0;
3940 $this->stats_mrptoconsume['qty'] = 0.0;
3941 $this->stats_mrptoproduce['customers'] = 0;
3942 $this->stats_mrptoproduce['nb'] = 0;
3943 $this->stats_mrptoproduce['rows'] = 0;
3944 $this->stats_mrptoproduce['qty'] = 0.0;
3945 }
3946
3947 $result = $this->db->query($sql);
3948 if ($result) {
3949 while ($obj = $this->db->fetch_object($result)) {
3950 if ($obj->role == 'toconsume' && empty($warehouseid)) {
3951 $this->stats_mrptoconsume['customers'] += (int) $obj->nb_customers;
3952 $this->stats_mrptoconsume['nb'] += (int) $obj->nb;
3953 $this->stats_mrptoconsume['rows'] += (int) $obj->nb_rows;
3954 $this->stats_mrptoconsume['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
3955 }
3956 if ($obj->role == 'consumed' && empty($warehouseid)) {
3957 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
3958 //$this->stats_mrptoconsume['nb'] += $obj->nb;
3959 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
3960 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? (float) $obj->qty : 0.0);
3961 }
3962 if ($obj->role == 'toproduce') {
3963 if ($warehouseid) {
3964 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
3965 } else {
3966 $this->stats_mrptoproduce['customers'] += (int) $obj->nb_customers;
3967 $this->stats_mrptoproduce['nb'] += (int) $obj->nb;
3968 $this->stats_mrptoproduce['rows'] += (int) $obj->nb_rows;
3969 $this->stats_mrptoproduce['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
3970 }
3971 }
3972 if ($obj->role == 'produced') {
3973 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
3974 //$this->stats_mrptoproduce['nb'] += $obj->nb;
3975 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
3976 if ($warehouseid) {
3977 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3978 } else {
3979 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
3980 }
3981 }
3982 }
3983
3984 // Clean data
3985 if ($warehouseid) {
3986 if ($this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] < 0) {
3987 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3988 }
3989 } else {
3990 if ($this->stats_mrptoconsume['qty'] < 0) {
3991 $this->stats_mrptoconsume['qty'] = 0;
3992 }
3993 if ($this->stats_mrptoproduce['qty'] < 0) {
3994 $this->stats_mrptoproduce['qty'] = 0;
3995 }
3996 }
3997
3998 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3999 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
4000 if ($reshook > 0) {
4001 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
4002 }
4003
4004 return 1;
4005 } else {
4006 $this->error = $this->db->error();
4007 return -1;
4008 }
4009 }
4010
4011 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4018 public function load_stats_contrat($socid = 0)
4019 {
4020 // phpcs:enable
4021 global $user, $hookmanager, $action;
4022
4023 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
4024 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
4025 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
4026 $sql .= ", ".$this->db->prefix()."contrat as c";
4027 $sql .= ", ".$this->db->prefix()."societe as s";
4028 $sql .= " WHERE c.rowid = cd.fk_contrat";
4029 $sql .= " AND c.fk_soc = s.rowid";
4030 $sql .= " AND c.entity IN (".getEntity('contract').")";
4031 $sql .= " AND cd.fk_product = ".((int) $this->id);
4032 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4033 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
4034 }
4035 //$sql.= " AND c.statut != 0";
4036 if ($socid > 0) {
4037 $sql .= " AND c.fk_soc = ".((int) $socid);
4038 }
4039
4040 $result = $this->db->query($sql);
4041 if ($result) {
4042 $obj = $this->db->fetch_object($result);
4043 $this->stats_contrat['customers'] = $obj->nb_customers;
4044 $this->stats_contrat['nb'] = $obj->nb;
4045 $this->stats_contrat['rows'] = $obj->nb_rows;
4046 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
4047
4048 // if it's a virtual product, maybe it is in contract by extension
4049 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4050 $TFather = $this->getFather();
4051 if (is_array($TFather) && !empty($TFather)) {
4052 foreach ($TFather as &$fatherData) {
4053 $pFather = new Product($this->db);
4054 $pFather->id = $fatherData['id'];
4055 $qtyCoef = $fatherData['qty'];
4056
4057 if ($fatherData['incdec']) {
4058 $pFather->load_stats_contrat($socid);
4059
4060 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
4061 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
4062 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
4063 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
4064 }
4065 }
4066 }
4067 }
4068
4069 $parameters = array('socid' => $socid);
4070 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
4071 if ($reshook > 0) {
4072 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
4073 }
4074
4075 return 1;
4076 } else {
4077 $this->error = $this->db->error().' sql='.$sql;
4078 return -1;
4079 }
4080 }
4081
4082 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4089 public function load_stats_facture($socid = 0)
4090 {
4091 // phpcs:enable
4092 global $user, $hookmanager, $action;
4093
4094 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
4095 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
4096 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
4097 $sql .= ", ".$this->db->prefix()."facture as f";
4098 $sql .= ", ".$this->db->prefix()."societe as s";
4099 $sql .= " WHERE f.rowid = fd.fk_facture";
4100 $sql .= " AND f.fk_soc = s.rowid";
4101 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4102 $sql .= " AND fd.fk_product = ".((int) $this->id);
4103 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4104 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4105 }
4106 //$sql.= " AND f.fk_statut != 0";
4107 if ($socid > 0) {
4108 $sql .= " AND f.fk_soc = ".((int) $socid);
4109 }
4110
4111 $result = $this->db->query($sql);
4112 if ($result) {
4113 $obj = $this->db->fetch_object($result);
4114 $this->stats_facture['customers'] = $obj->nb_customers;
4115 $this->stats_facture['nb'] = $obj->nb;
4116 $this->stats_facture['rows'] = $obj->nb_rows;
4117 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
4118
4119 // if it's a virtual product, maybe it is in invoice by extension
4120 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4121 $TFather = $this->getFather();
4122 if (is_array($TFather) && !empty($TFather)) {
4123 foreach ($TFather as &$fatherData) {
4124 $pFather = new Product($this->db);
4125 $pFather->id = $fatherData['id'];
4126 $qtyCoef = $fatherData['qty'];
4127
4128 if ($fatherData['incdec']) {
4129 $pFather->load_stats_facture($socid);
4130
4131 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
4132 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
4133 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
4134 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
4135 }
4136 }
4137 }
4138 }
4139
4140 $parameters = array('socid' => $socid);
4141 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
4142 if ($reshook > 0) {
4143 $this->stats_facture = $hookmanager->resArray['stats_facture'];
4144 }
4145
4146 return 1;
4147 } else {
4148 $this->error = $this->db->error();
4149 return -1;
4150 }
4151 }
4152
4153
4154 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4161 public function load_stats_facturerec($socid = 0)
4162 {
4163 // phpcs:enable
4164 global $user, $hookmanager, $action;
4165
4166 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
4167 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4168 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
4169 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
4170 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
4171 $sql .= " WHERE f.rowid = fd.fk_facture";
4172 $sql .= " AND f.fk_soc = s.rowid";
4173 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4174 $sql .= " AND fd.fk_product = ".((int) $this->id);
4175 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4176 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4177 }
4178 //$sql.= " AND f.fk_statut != 0";
4179 if ($socid > 0) {
4180 $sql .= " AND f.fk_soc = ".((int) $socid);
4181 }
4182
4183 $result = $this->db->query($sql);
4184 if ($result) {
4185 $obj = $this->db->fetch_object($result);
4186 $this->stats_facturerec['customers'] = (int) $obj->nb_customers;
4187 $this->stats_facturerec['nb'] = (int) $obj->nb;
4188 $this->stats_facturerec['rows'] = (int) $obj->nb_rows;
4189 $this->stats_facturerec['qty'] = $obj->qty ? (float) $obj->qty : 0.0;
4190
4191 // if it's a virtual product, maybe it is in invoice by extension
4192 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4193 $TFather = $this->getFather();
4194 if (is_array($TFather) && !empty($TFather)) {
4195 foreach ($TFather as &$fatherData) {
4196 $pFather = new Product($this->db);
4197 $pFather->id = $fatherData['id'];
4198 $qtyCoef = $fatherData['qty'];
4199
4200 if ($fatherData['incdec']) {
4201 $pFather->load_stats_facture($socid);
4202
4203 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
4204 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
4205 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
4206 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
4207 }
4208 }
4209 }
4210 }
4211
4212 $parameters = array('socid' => $socid);
4213 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
4214 if ($reshook > 0) {
4215 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
4216 }
4217
4218 return 1;
4219 } else {
4220 $this->error = $this->db->error();
4221 return -1;
4222 }
4223 }
4224
4225 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4232 public function load_stats_facture_fournisseur($socid = 0)
4233 {
4234 // phpcs:enable
4235 global $user, $hookmanager, $action;
4236
4237 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
4238 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4239 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
4240 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
4241 $sql .= ", ".$this->db->prefix()."societe as s";
4242 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
4243 $sql .= " AND f.fk_soc = s.rowid";
4244 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4245 $sql .= " AND fd.fk_product = ".((int) $this->id);
4246 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4247 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4248 }
4249 //$sql.= " AND f.fk_statut != 0";
4250 if ($socid > 0) {
4251 $sql .= " AND f.fk_soc = ".((int) $socid);
4252 }
4253
4254 $result = $this->db->query($sql);
4255 if ($result) {
4256 $obj = $this->db->fetch_object($result);
4257 $this->stats_facture_fournisseur['suppliers'] = (int) $obj->nb_suppliers;
4258 $this->stats_facture_fournisseur['nb'] = (int) $obj->nb;
4259 $this->stats_facture_fournisseur['rows'] = (int) $obj->nb_rows;
4260 $this->stats_facture_fournisseur['qty'] = $obj->qty ? (float) $obj->qty : 0.0;
4261
4262 $parameters = array('socid' => $socid);
4263 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
4264 if ($reshook > 0) {
4265 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
4266 }
4267
4268 return 1;
4269 } else {
4270 $this->error = $this->db->error();
4271 return -1;
4272 }
4273 }
4274
4275 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4284 private function _get_stats($sql, $mode, $year = 0)
4285 {
4286 // phpcs:enable
4287 $tab = array();
4288
4289 $resql = $this->db->query($sql);
4290 if ($resql) {
4291 $num = $this->db->num_rows($resql);
4292 $i = 0;
4293 while ($i < $num) {
4294 $arr = $this->db->fetch_array($resql);
4295 if (is_array($arr)) {
4296 $keyfortab = (string) $arr[1];
4297 if ($year == -1) {
4298 $keyfortab = substr($keyfortab, -2);
4299 }
4300
4301 if ($mode == 'byunit') {
4302 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
4303 } elseif ($mode == 'bynumber') {
4304 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
4305 } elseif ($mode == 'byamount') {
4306 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
4307 } else {
4308 // Bad value for $mode
4309 return -1;
4310 }
4311 }
4312 $i++;
4313 }
4314 } else {
4315 $this->error = $this->db->error().' sql='.$sql;
4316 return -1;
4317 }
4318
4319 if (empty($year)) {
4320 $year = dol_print_date(time(), '%Y');
4321 $month = dol_print_date(time(), '%m');
4322 } elseif ($year == -1) {
4323 $year = '';
4324 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4325 } else {
4326 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4327 }
4328
4329 $result = array();
4330
4331 for ($j = 0; $j < 12; $j++) {
4332 // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
4333 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
4334
4335 //print $idx.'-'.$year.'-'.$month.'<br>';
4336 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
4337 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
4338
4339 $month = "0".($month - 1);
4340 if (dol_strlen($month) == 3) {
4341 $month = substr($month, 1);
4342 }
4343 if ($month == 0) {
4344 $month = 12;
4345 $year -= 1;
4346 }
4347 }
4348
4349 return array_reverse($result);
4350 }
4351
4352
4353 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4364 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4365 {
4366 // phpcs:enable
4367 global $user;
4368
4369 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4370 if ($mode == 'bynumber') {
4371 $sql .= ", count(DISTINCT f.rowid)";
4372 }
4373 $sql .= ", sum(d.total_ht) as total_ht";
4374 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
4375 if ($filteronproducttype >= 0) {
4376 $sql .= ", ".$this->db->prefix()."product as p";
4377 }
4378 if (!$user->hasRight('societe', 'client', 'voir')) {
4379 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4380 }
4381 $sql .= " WHERE f.rowid = d.fk_facture";
4382 if ($this->id > 0) {
4383 $sql .= " AND d.fk_product = ".((int) $this->id);
4384 } else {
4385 $sql .= " AND d.fk_product > 0";
4386 }
4387 if ($filteronproducttype >= 0) {
4388 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4389 }
4390 $sql .= " AND f.fk_soc = s.rowid";
4391 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4392 if (!$user->hasRight('societe', 'client', 'voir')) {
4393 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4394 }
4395 if ($socid > 0) {
4396 $sql .= " AND f.fk_soc = $socid";
4397 }
4398 $sql .= $morefilter;
4399 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4400 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4401
4402 return $this->_get_stats($sql, $mode, $year);
4403 }
4404
4405
4406 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4417 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4418 {
4419 // phpcs:enable
4420 global $user;
4421
4422 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4423 if ($mode == 'bynumber') {
4424 $sql .= ", count(DISTINCT f.rowid)";
4425 }
4426 $sql .= ", sum(d.total_ht) as total_ht";
4427 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
4428 if ($filteronproducttype >= 0) {
4429 $sql .= ", ".$this->db->prefix()."product as p";
4430 }
4431 if (!$user->hasRight('societe', 'client', 'voir')) {
4432 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4433 }
4434 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4435 if ($this->id > 0) {
4436 $sql .= " AND d.fk_product = ".((int) $this->id);
4437 } else {
4438 $sql .= " AND d.fk_product > 0";
4439 }
4440 if ($filteronproducttype >= 0) {
4441 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4442 }
4443 $sql .= " AND f.fk_soc = s.rowid";
4444 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4445 if (!$user->hasRight('societe', 'client', 'voir')) {
4446 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4447 }
4448 if ($socid > 0) {
4449 $sql .= " AND f.fk_soc = $socid";
4450 }
4451 $sql .= $morefilter;
4452 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4453 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4454
4455 return $this->_get_stats($sql, $mode, $year);
4456 }
4457
4458 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4469 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4470 {
4471 // phpcs:enable
4472 global $user;
4473
4474 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4475 if ($mode == 'bynumber') {
4476 $sql .= ", count(DISTINCT p.rowid)";
4477 }
4478 $sql .= ", sum(d.total_ht) as total_ht";
4479 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4480 if ($filteronproducttype >= 0) {
4481 $sql .= ", ".$this->db->prefix()."product as prod";
4482 }
4483 if (!$user->hasRight('societe', 'client', 'voir')) {
4484 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4485 }
4486 $sql .= " WHERE p.rowid = d.fk_propal";
4487 if ($this->id > 0) {
4488 $sql .= " AND d.fk_product = ".((int) $this->id);
4489 } else {
4490 $sql .= " AND d.fk_product > 0";
4491 }
4492 if ($filteronproducttype >= 0) {
4493 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4494 }
4495 $sql .= " AND p.fk_soc = s.rowid";
4496 $sql .= " AND p.entity IN (".getEntity('propal').")";
4497 if (!$user->hasRight('societe', 'client', 'voir')) {
4498 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4499 }
4500 if ($socid > 0) {
4501 $sql .= " AND p.fk_soc = ".((int) $socid);
4502 }
4503 $sql .= $morefilter;
4504 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4505 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4506
4507 return $this->_get_stats($sql, $mode, $year);
4508 }
4509
4510 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4521 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4522 {
4523 // phpcs:enable
4524 global $user;
4525
4526 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4527 if ($mode == 'bynumber') {
4528 $sql .= ", count(DISTINCT p.rowid)";
4529 }
4530 $sql .= ", sum(d.total_ht) as total_ht";
4531 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4532 if ($filteronproducttype >= 0) {
4533 $sql .= ", ".$this->db->prefix()."product as prod";
4534 }
4535 if (!$user->hasRight('societe', 'client', 'voir')) {
4536 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4537 }
4538 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4539 if ($this->id > 0) {
4540 $sql .= " AND d.fk_product = ".((int) $this->id);
4541 } else {
4542 $sql .= " AND d.fk_product > 0";
4543 }
4544 if ($filteronproducttype >= 0) {
4545 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4546 }
4547 $sql .= " AND p.fk_soc = s.rowid";
4548 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4549 if (!$user->hasRight('societe', 'client', 'voir')) {
4550 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4551 }
4552 if ($socid > 0) {
4553 $sql .= " AND p.fk_soc = ".((int) $socid);
4554 }
4555 $sql .= $morefilter;
4556 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4557 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4558
4559 return $this->_get_stats($sql, $mode, $year);
4560 }
4561
4562 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4573 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4574 {
4575 // phpcs:enable
4576 global $user;
4577
4578 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4579 if ($mode == 'bynumber') {
4580 $sql .= ", count(DISTINCT c.rowid)";
4581 }
4582 $sql .= ", sum(d.total_ht) as total_ht";
4583 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4584 if ($filteronproducttype >= 0) {
4585 $sql .= ", ".$this->db->prefix()."product as p";
4586 }
4587 if (!$user->hasRight('societe', 'client', 'voir')) {
4588 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4589 }
4590 $sql .= " WHERE c.rowid = d.fk_commande";
4591 if ($this->id > 0) {
4592 $sql .= " AND d.fk_product = ".((int) $this->id);
4593 } else {
4594 $sql .= " AND d.fk_product > 0";
4595 }
4596 if ($filteronproducttype >= 0) {
4597 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4598 }
4599 $sql .= " AND c.fk_soc = s.rowid";
4600 $sql .= " AND c.entity IN (".getEntity('commande').")";
4601 if (!$user->hasRight('societe', 'client', 'voir')) {
4602 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4603 }
4604 if ($socid > 0) {
4605 $sql .= " AND c.fk_soc = ".((int) $socid);
4606 }
4607 $sql .= $morefilter;
4608 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4609 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4610
4611 return $this->_get_stats($sql, $mode, $year);
4612 }
4613
4614 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4625 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4626 {
4627 // phpcs:enable
4628 global $user;
4629
4630 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4631 if ($mode == 'bynumber') {
4632 $sql .= ", count(DISTINCT c.rowid)";
4633 }
4634 $sql .= ", sum(d.total_ht) as total_ht";
4635 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4636 if ($filteronproducttype >= 0) {
4637 $sql .= ", ".$this->db->prefix()."product as p";
4638 }
4639 if (!$user->hasRight('societe', 'client', 'voir')) {
4640 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4641 }
4642 $sql .= " WHERE c.rowid = d.fk_commande";
4643 if ($this->id > 0) {
4644 $sql .= " AND d.fk_product = ".((int) $this->id);
4645 } else {
4646 $sql .= " AND d.fk_product > 0";
4647 }
4648 if ($filteronproducttype >= 0) {
4649 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4650 }
4651 $sql .= " AND c.fk_soc = s.rowid";
4652 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4653 if (!$user->hasRight('societe', 'client', 'voir')) {
4654 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4655 }
4656 if ($socid > 0) {
4657 $sql .= " AND c.fk_soc = ".((int) $socid);
4658 }
4659 $sql .= $morefilter;
4660 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4661 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4662
4663 return $this->_get_stats($sql, $mode, $year);
4664 }
4665
4666 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4677 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4678 {
4679 // phpcs:enable
4680 global $user;
4681
4682 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4683 if ($mode == 'bynumber') {
4684 $sql .= ", count(DISTINCT c.rowid)";
4685 }
4686 $sql .= ", sum(d.total_ht) as total_ht";
4687 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4688 if ($filteronproducttype >= 0) {
4689 $sql .= ", ".$this->db->prefix()."product as p";
4690 }
4691 if (!$user->hasRight('societe', 'client', 'voir')) {
4692 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4693 }
4694 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4695 $sql .= " AND c.rowid = d.fk_contrat";
4696
4697 if ($this->id > 0) {
4698 $sql .= " AND d.fk_product = ".((int) $this->id);
4699 } else {
4700 $sql .= " AND d.fk_product > 0";
4701 }
4702 if ($filteronproducttype >= 0) {
4703 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4704 }
4705 $sql .= " AND c.fk_soc = s.rowid";
4706
4707 if (!$user->hasRight('societe', 'client', 'voir')) {
4708 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4709 }
4710 if ($socid > 0) {
4711 $sql .= " AND c.fk_soc = ".((int) $socid);
4712 }
4713 $sql .= $morefilter;
4714 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4715 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4716
4717 return $this->_get_stats($sql, $mode, $year);
4718 }
4719
4720 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4731 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4732 {
4733 // phpcs:enable
4734 global $user;
4735
4736 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4737 if ($mode == 'bynumber') {
4738 $sql .= ", count(DISTINCT d.rowid)";
4739 }
4740 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4741 if ($filteronproducttype >= 0) {
4742 $sql .= ", ".$this->db->prefix()."product as p";
4743 }
4744 if (!$user->hasRight('societe', 'client', 'voir')) {
4745 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4746 }
4747
4748 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4749 $sql .= " AND d.status > 0";
4750
4751 if ($this->id > 0) {
4752 $sql .= " AND d.fk_product = ".((int) $this->id);
4753 } else {
4754 $sql .= " AND d.fk_product > 0";
4755 }
4756 if ($filteronproducttype >= 0) {
4757 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4758 }
4759
4760 if (!$user->hasRight('societe', 'client', 'voir')) {
4761 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4762 }
4763 if ($socid > 0) {
4764 $sql .= " AND d.fk_soc = ".((int) $socid);
4765 }
4766 $sql .= $morefilter;
4767 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4768 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4769
4770 return $this->_get_stats($sql, $mode, $year);
4771 }
4772
4773 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4784 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4785 {
4786 global $user;
4787
4788 // phpcs:enable
4789 // Clean parameters
4790 if (!is_numeric($id_pere)) {
4791 $id_pere = 0;
4792 }
4793 if (!is_numeric($id_fils)) {
4794 $id_fils = 0;
4795 }
4796 if (!is_numeric($incdec)) {
4797 $incdec = 0;
4798 }
4799
4800 $result = $this->del_sousproduit($id_pere, $id_fils);
4801 if ($result < 0) {
4802 return $result;
4803 }
4804
4805 // Check not already father of id_pere (to avoid father -> child -> father links)
4806 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4807 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4808 if (!$this->db->query($sql)) {
4809 dol_print_error($this->db);
4810 return -1;
4811 } else {
4812 //Selection of the highest row
4813 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4814 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4815 $resql = $this->db->query($sql);
4816 if ($resql) {
4817 $obj = $this->db->fetch_object($resql);
4818 $rank = $obj->max_rank + 1;
4819 //Addition of a product with the highest rank +1
4820 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4821 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".((int) $incdec).", ".((int) $rank).")";
4822 if (! $this->db->query($sql)) {
4823 dol_print_error($this->db);
4824 return -1;
4825 } else {
4826 if (!$notrigger) {
4827 // Call trigger
4828 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
4829 if ($result < 0) {
4830 $this->error = $this->db->lasterror();
4831 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
4832 return -1;
4833 }
4834 }
4835 // End call triggers
4836
4837 return 1;
4838 }
4839 } else {
4840 dol_print_error($this->db);
4841 return -1;
4842 }
4843 }
4844 }
4845
4846 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4857 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4858 {
4859 global $user;
4860
4861 // phpcs:enable
4862 // Clean parameters
4863 if (!is_numeric($id_pere)) {
4864 $id_pere = 0;
4865 }
4866 if (!is_numeric($id_fils)) {
4867 $id_fils = 0;
4868 }
4869 if (!is_numeric($incdec)) {
4870 $incdec = 1;
4871 }
4872 if (!is_numeric($qty)) {
4873 $qty = 1;
4874 }
4875
4876 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4877 $sql .= 'qty = '.price2num($qty, 'MS');
4878 $sql .= ',incdec = '.((int) $incdec);
4879 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4880
4881 if (!$this->db->query($sql)) {
4882 dol_print_error($this->db);
4883 return -1;
4884 } else {
4885 if (!$notrigger) {
4886 // Call trigger
4887 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
4888 if ($result < 0) {
4889 $this->error = $this->db->lasterror();
4890 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
4891 return -1;
4892 }
4893 // End call triggers
4894 }
4895
4896 return 1;
4897 }
4898 }
4899
4900 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4909 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
4910 {
4911 global $user;
4912
4913 // phpcs:enable
4914 if (!is_numeric($fk_parent)) {
4915 $fk_parent = 0;
4916 }
4917 if (!is_numeric($fk_child)) {
4918 $fk_child = 0;
4919 }
4920
4921 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
4922 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4923 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4924
4925 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
4926 if (!$this->db->query($sql)) {
4927 dol_print_error($this->db);
4928 return -1;
4929 }
4930
4931 // Updated ranks so that none are missing
4932 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
4933 $sqlrank .= " WHERE fk_product_pere = ".((int) $fk_parent);
4934 $sqlrank .= " ORDER BY rang";
4935 $resqlrank = $this->db->query($sqlrank);
4936 if ($resqlrank) {
4937 $cpt = 0;
4938 while ($objrank = $this->db->fetch_object($resqlrank)) {
4939 $cpt++;
4940 $sql = "UPDATE ".$this->db->prefix()."product_association";
4941 $sql .= " SET rang = ".((int) $cpt);
4942 $sql .= " WHERE rowid = ".((int) $objrank->rowid);
4943 if (! $this->db->query($sql)) {
4944 dol_print_error($this->db);
4945 return -1;
4946 }
4947 }
4948 }
4949
4950 if (!$notrigger) {
4951 // Call trigger
4952 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
4953 if ($result < 0) {
4954 $this->error = $this->db->lasterror();
4955 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
4956 return -1;
4957 }
4958 // End call triggers
4959 }
4960
4961 return 1;
4962 }
4963
4964 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4972 public function is_sousproduit($fk_parent, $fk_child)
4973 {
4974 // phpcs:enable
4975 $sql = "SELECT fk_product_pere, qty, incdec";
4976 $sql .= " FROM ".$this->db->prefix()."product_association";
4977 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
4978 $sql .= " AND fk_product_fils = ".((int) $fk_child);
4979
4980 $result = $this->db->query($sql);
4981 if ($result) {
4982 $num = $this->db->num_rows($result);
4983
4984 if ($num > 0) {
4985 $obj = $this->db->fetch_object($result);
4986
4987 $this->is_sousproduit_qty = $obj->qty;
4988 $this->is_sousproduit_incdec = $obj->incdec;
4989
4990 return 1;
4991 } else {
4992 return 0;
4993 }
4994 } else {
4995 dol_print_error($this->db);
4996 return -1;
4997 }
4998 }
4999
5000
5001 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5012 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
5013 {
5014 // phpcs:enable
5015 global $conf;
5016
5017 $now = dol_now();
5018
5019 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
5020
5021 // Clean parameters
5022 $quantity = price2num($quantity, 'MS');
5023
5024 if ($ref_fourn) {
5025 // Check if ref is not already used
5026 $sql = "SELECT rowid, fk_product";
5027 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5028 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5029 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5030 $sql .= " AND fk_product <> ".((int) $this->id);
5031 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5032
5033 $resql = $this->db->query($sql);
5034 if ($resql) {
5035 $obj = $this->db->fetch_object($resql);
5036 if ($obj) {
5037 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
5038 $this->product_id_already_linked = $obj->fk_product;
5039 return -3;
5040 }
5041 $this->db->free($resql);
5042 }
5043 }
5044
5045 $sql = "SELECT rowid";
5046 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5047 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5048 if ($ref_fourn) {
5049 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5050 } else {
5051 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
5052 }
5053 $sql .= " AND quantity = ".((float) $quantity);
5054 $sql .= " AND fk_product = ".((int) $this->id);
5055 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5056
5057 $resql = $this->db->query($sql);
5058 if ($resql) {
5059 $obj = $this->db->fetch_object($resql);
5060
5061 // The reference supplier does not exist, we create it for this product.
5062 if (empty($obj)) {
5063 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
5064 $sql .= "datec";
5065 $sql .= ", entity";
5066 $sql .= ", fk_product";
5067 $sql .= ", fk_soc";
5068 $sql .= ", ref_fourn";
5069 $sql .= ", quantity";
5070 $sql .= ", fk_user";
5071 $sql .= ", tva_tx";
5072 $sql .= ") VALUES (";
5073 $sql .= "'".$this->db->idate($now)."'";
5074 $sql .= ", ".((int) $conf->entity);
5075 $sql .= ", ".((int) $this->id);
5076 $sql .= ", ".((int) $id_fourn);
5077 $sql .= ", '".$this->db->escape($ref_fourn)."'";
5078 $sql .= ", ".((float) $quantity);
5079 $sql .= ", ".((int) $user->id);
5080 $sql .= ", 0";
5081 $sql .= ")";
5082
5083 if ($this->db->query($sql)) {
5084 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
5085 return 1;
5086 } else {
5087 $this->error = $this->db->lasterror();
5088 return -1;
5089 }
5090 } else {
5091 // If the supplier price already exists for this product and quantity
5092 $this->product_fourn_price_id = $obj->rowid;
5093 return 0;
5094 }
5095 } else {
5096 $this->error = $this->db->lasterror();
5097 return -2;
5098 }
5099 }
5100
5101
5102 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5108 public function list_suppliers()
5109 {
5110 // phpcs:enable
5111 global $conf;
5112
5113 $list = array();
5114
5115 $sql = "SELECT DISTINCT p.fk_soc";
5116 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
5117 $sql .= " WHERE p.fk_product = ".((int) $this->id);
5118 $sql .= " AND p.entity = ".((int) $conf->entity);
5119
5120 $result = $this->db->query($sql);
5121 if ($result) {
5122 $num = $this->db->num_rows($result);
5123 $i = 0;
5124 while ($i < $num) {
5125 $obj = $this->db->fetch_object($result);
5126 $list[$i] = $obj->fk_soc;
5127 $i++;
5128 }
5129 }
5130
5131 return $list;
5132 }
5133
5134 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5142 public function clone_price($fromId, $toId)
5143 {
5144 global $user;
5145
5146 $now = dol_now();
5147
5148 $this->db->begin();
5149
5150 // prices
5151 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
5152 $sql .= " entity";
5153 $sql .= ", fk_product";
5154 $sql .= ", date_price";
5155 $sql .= ", price_level";
5156 $sql .= ", price";
5157 $sql .= ", price_ttc";
5158 $sql .= ", price_min";
5159 $sql .= ", price_min_ttc";
5160 $sql .= ", price_base_type";
5161 $sql .= ", price_label";
5162 $sql .= ", default_vat_code";
5163 $sql .= ", tva_tx";
5164 $sql .= ", recuperableonly";
5165 $sql .= ", localtax1_tx";
5166 $sql .= ", localtax1_type";
5167 $sql .= ", localtax2_tx";
5168 $sql .= ", localtax2_type";
5169 $sql .= ", fk_user_author";
5170 $sql .= ", tosell";
5171 $sql .= ", price_by_qty";
5172 $sql .= ", fk_price_expression";
5173 $sql .= ", fk_multicurrency";
5174 $sql .= ", multicurrency_code";
5175 $sql .= ", multicurrency_tx";
5176 $sql .= ", multicurrency_price";
5177 $sql .= ", multicurrency_price_ttc";
5178 $sql .= ")";
5179 $sql .= " SELECT";
5180 $sql .= " entity";
5181 $sql .= ", ".$toId;
5182 $sql .= ", '".$this->db->idate($now)."'";
5183 $sql .= ", price_level";
5184 $sql .= ", price";
5185 $sql .= ", price_ttc";
5186 $sql .= ", price_min";
5187 $sql .= ", price_min_ttc";
5188 $sql .= ", price_base_type";
5189 $sql .= ", price_label";
5190 $sql .= ", default_vat_code";
5191 $sql .= ", tva_tx";
5192 $sql .= ", recuperableonly";
5193 $sql .= ", localtax1_tx";
5194 $sql .= ", localtax1_type";
5195 $sql .= ", localtax2_tx";
5196 $sql .= ", localtax2_type";
5197 $sql .= ", ".$user->id;
5198 $sql .= ", tosell";
5199 $sql .= ", price_by_qty";
5200 $sql .= ", fk_price_expression";
5201 $sql .= ", fk_multicurrency";
5202 $sql .= ", multicurrency_code";
5203 $sql .= ", multicurrency_tx";
5204 $sql .= ", multicurrency_price";
5205 $sql .= ", multicurrency_price_ttc";
5206 $sql .= " FROM ".$this->db->prefix()."product_price ps";
5207 $sql .= " WHERE fk_product = ".((int) $fromId);
5208 $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)";
5209 $sql .= " ORDER BY date_price DESC";
5210
5211 dol_syslog(__METHOD__, LOG_DEBUG);
5212 $resql = $this->db->query($sql);
5213 if (!$resql) {
5214 $this->db->rollback();
5215 return -1;
5216 }
5217
5218 $this->db->commit();
5219 return 1;
5220 }
5221
5222 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5230 public function clone_associations($fromId, $toId)
5231 {
5232 // phpcs:enable
5233 $this->db->begin();
5234
5235 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
5236 $sql .= " SELECT ".$toId.", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
5237 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
5238
5239 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
5240 if (!$this->db->query($sql)) {
5241 $this->db->rollback();
5242 return -1;
5243 }
5244
5245 $this->db->commit();
5246 return 1;
5247 }
5248
5249 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5257 public function clone_fournisseurs($fromId, $toId)
5258 {
5259 // phpcs:enable
5260 $this->db->begin();
5261
5262 $now = dol_now();
5263
5264 // les fournisseurs
5265 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
5266 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
5267 . " SELECT '".$this->db->idate($now)."', ".$toId.", fk_soc, ref_fourn, fk_user_author"
5268 . " FROM ".$this->db->prefix()."product_fournisseur"
5269 . " WHERE fk_product = ".((int) $fromId);
5270
5271 if ( ! $this->db->query($sql ) )
5272 {
5273 $this->db->rollback();
5274 return -1;
5275 }*/
5276
5277 // les prix de fournisseurs.
5278 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
5279 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
5280 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
5281 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5282 $sql .= " WHERE fk_product = ".((int) $fromId);
5283
5284 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
5285 $resql = $this->db->query($sql);
5286 if (!$resql) {
5287 $this->db->rollback();
5288 return -1;
5289 } else {
5290 $this->db->commit();
5291 return 1;
5292 }
5293 }
5294
5295 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5308 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
5309 {
5310 // phpcs:enable
5311 $tmpproduct = null;
5312
5313 //var_dump($prod);
5314 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
5315 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
5316 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
5317 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
5318 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
5319 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
5320 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
5321
5322 if ($multiply < 1) {
5323 $multiply = 1;
5324 }
5325
5326 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
5327 if (is_null($tmpproduct)) {
5328 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
5329 }
5330 $tmpproduct->fetch($id); // Load product to get ->ref
5331
5332 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
5333 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
5334 }
5335
5336 $this->res[] = array(
5337 'id' => $id, // Id product
5338 'id_parent' => $id_parent,
5339 'ref' => $tmpproduct->ref, // Ref product
5340 'nb' => $nb, // Nb of units that compose parent product
5341 'nb_total' => $nb * $multiply, // Nb of units for all nb of product
5342 'stock' => $tmpproduct->stock_reel, // Stock
5343 'stock_alert' => $tmpproduct->seuil_stock_alerte, // Stock alert
5344 'label' => $label,
5345 'fullpath' => $compl_path.$label, // Label
5346 'type' => $type, // Nb of units that compose parent product
5347 'desiredstock' => $tmpproduct->desiredstock,
5348 'level' => $level,
5349 'incdec' => $incdec,
5350 'entity' => $tmpproduct->entity
5351 );
5352
5353 // Recursive call if there child has children of its own
5354 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
5355 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
5356 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", $desc_pere[1] * $multiply, $level + 1, $id, $ignore_stock_load);
5357 }
5358 }
5359 }
5360 }
5361
5362 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5371 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
5372 {
5373 // phpcs:enable
5374 $this->res = array();
5375 if (isset($this->sousprods) && is_array($this->sousprods)) {
5376 foreach ($this->sousprods as $prod_name => $desc_product) {
5377 if (is_array($desc_product)) {
5378 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
5379 }
5380 }
5381 }
5382 //var_dump($res);
5383 return $this->res;
5384 }
5385
5393 public function hasFatherOrChild($mode = 0)
5394 {
5395 $nb = 0;
5396
5397 $sql = "SELECT COUNT(pa.rowid) as nb";
5398 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
5399 if ($mode == 0) {
5400 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
5401 } elseif ($mode == -1) {
5402 $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)
5403 } elseif ($mode == 1) {
5404 $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)
5405 }
5406
5407 $resql = $this->db->query($sql);
5408 if ($resql) {
5409 $obj = $this->db->fetch_object($resql);
5410 if ($obj) {
5411 $nb = $obj->nb;
5412 }
5413 } else {
5414 return -1;
5415 }
5416
5417 return $nb;
5418 }
5419
5425 public function hasVariants()
5426 {
5427 $nb = 0;
5428 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
5429 $sql .= " AND entity IN (".getEntity('product').")";
5430
5431 $resql = $this->db->query($sql);
5432 if ($resql) {
5433 $obj = $this->db->fetch_object($resql);
5434 if ($obj) {
5435 $nb = $obj->nb;
5436 }
5437 }
5438
5439 return $nb;
5440 }
5441
5442
5448 public function isVariant()
5449 {
5450 if (isModEnabled('variants')) {
5451 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5452
5453 $query = $this->db->query($sql);
5454
5455 if ($query) {
5456 if (!$this->db->num_rows($query)) {
5457 return false;
5458 }
5459 return true;
5460 } else {
5461 dol_print_error($this->db);
5462 return -1;
5463 }
5464 } else {
5465 return false;
5466 }
5467 }
5468
5475 public function getFather()
5476 {
5477 $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";
5478 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5479 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5480 $sql .= " ".$this->db->prefix()."product as p";
5481 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5482 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5483
5484 $res = $this->db->query($sql);
5485 if ($res) {
5486 $prods = array();
5487 while ($record = $this->db->fetch_array($res)) {
5488 // $record['id'] = $record['rowid'] = id of father
5489 $prods[$record['id']] = array();
5490 $prods[$record['id']]['id'] = $record['rowid'];
5491 $prods[$record['id']]['ref'] = $record['ref'];
5492 $prods[$record['id']]['label'] = $record['label'];
5493 $prods[$record['id']]['qty'] = $record['qty'];
5494 $prods[$record['id']]['incdec'] = $record['incdec'];
5495 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5496 $prods[$record['id']]['entity'] = $record['entity'];
5497 $prods[$record['id']]['status'] = $record['status'];
5498 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5499 }
5500 return $prods;
5501 } else {
5502 dol_print_error($this->db);
5503 return -1;
5504 }
5505 }
5506
5507
5517 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5518 {
5519 if (empty($id)) {
5520 return array();
5521 }
5522
5523 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5524 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5525 $sql .= " pa.rowid as fk_association, pa.rang";
5526 $sql .= " FROM ".$this->db->prefix()."product as p,";
5527 $sql .= " ".$this->db->prefix()."product_association as pa";
5528 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5529 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5530 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5531 $sql .= " ORDER BY pa.rang";
5532
5533 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5534
5535 // Protection against infinite loop
5536 if ($level > 30) {
5537 return array();
5538 }
5539
5540 $res = $this->db->query($sql);
5541 if ($res) {
5542 $prods = array();
5543 if ($this->db->num_rows($res) > 0) {
5544 $parents[] = $id;
5545 }
5546
5547 while ($rec = $this->db->fetch_array($res)) {
5548 if (in_array($rec['id'], $parents)) {
5549 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);
5550 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5551 }
5552
5553 $prods[$rec['rowid']] = array(
5554 0 => $rec['rowid'],
5555 1 => $rec['qty'],
5556 2 => $rec['fk_product_type'],
5557 3 => $this->db->escape($rec['label']),
5558 4 => $rec['incdec'],
5559 5 => $rec['ref'],
5560 6 => $rec['fk_association'],
5561 7 => $rec['rang']
5562 );
5563 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5564 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5565 if (empty($firstlevelonly)) {
5566 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5567 foreach ($listofchilds as $keyChild => $valueChild) {
5568 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5569 }
5570 }
5571 }
5572
5573 return $prods;
5574 } else {
5575 dol_print_error($this->db);
5576 return -1;
5577 }
5578 }
5579
5580 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5587 public function get_sousproduits_arbo()
5588 {
5589 // phpcs:enable
5590 $parent = array();
5591
5592 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5593 $parent[$this->label][$keyChild] = $valueChild;
5594 }
5595 foreach ($parent as $key => $value) { // key=label, value is array of children
5596 $this->sousprods[$key] = $value; // @phan-suppress-current-line PhanTypeMismatchProperty
5597 }
5598 }
5599
5607 public function getTooltipContentArray($params)
5608 {
5609 global $conf, $langs, $user;
5610
5611 $langs->loadLangs(array('products', 'other'));
5612
5613 $datas = array();
5614 $nofetch = !empty($params['nofetch']);
5615
5616 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5617 return ['optimize' => $langs->trans("ShowProduct")];
5618 }
5619
5620 // Does user has permission to read product/service
5621 $permissiontoreadproduct = 0;
5622 if ($this->type == self::TYPE_PRODUCT && $user->hasRight('product', 'read')) {
5623 $permissiontoreadproduct = 1;
5624 }
5625 if ($this->type == self::TYPE_SERVICE && $user->hasRight('service', 'read')) {
5626 $permissiontoreadproduct = 1;
5627 }
5628
5629 if (!empty($this->entity) && $permissiontoreadproduct) {
5630 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, '1');
5631 if ($this->nbphoto > 0) {
5632 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5633 }
5634 }
5635
5636 if ($this->isProduct()) {
5637 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5638 } elseif ($this->isService()) {
5639 $datas['picto'] = img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5640 }
5641 if (isset($this->status) && isset($this->status_buy)) {
5642 $datas['status'] = ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5643 }
5644
5645 if (!empty($this->ref)) {
5646 $datas['ref'] = '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5647 }
5648 if (!empty($this->label)) {
5649 $datas['label'] = '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5650 }
5651
5652 if ($permissiontoreadproduct) {
5653 if (!empty($this->description)) {
5654 $datas['description'] = '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineOfText($this->description, 5);
5655 }
5656 if ($this->isStockManaged()) {
5657 if (isModEnabled('productbatch')) {
5658 $langs->load("productbatch");
5659 $datas['batchstatus'] = "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5660 if ($this->status_batch) {
5661 $datas['batchdlc'] = "<br><b>".$langs->trans("BatchSellOrEatByMandatoryList", $langs->transnoentitiesnoconv("SellByDate"), $langs->transnoentitiesnoconv("EatByDate")).'</b>: '.$this->getSellOrEatByMandatoryLabel();
5662 }
5663 }
5664 }
5665 if (isModEnabled('barcode')) {
5666 $datas['barcode'] = '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5667 }
5668
5669 if ($this->isProduct()) {
5670 if ($this->weight) {
5671 $datas['weight'] = "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5672 }
5673 $labelsize = "";
5674 if ($this->length) {
5675 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5676 }
5677 if ($this->width) {
5678 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5679 }
5680 if ($this->height) {
5681 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5682 }
5683 if ($labelsize) {
5684 $datas['size'] = "<br>".$labelsize;
5685 }
5686
5687 $labelsurfacevolume = "";
5688 if ($this->surface) {
5689 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5690 }
5691 if ($this->volume) {
5692 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5693 }
5694 if ($labelsurfacevolume) {
5695 $datas['surface'] = "<br>" . $labelsurfacevolume;
5696 }
5697 }
5698 if ($this->isService() && !empty($this->duration_value)) {
5699 // Duration
5700 $datas['duration'] = '<br><b>'.$langs->trans("Duration").':</b> '.$this->duration_value;
5701 if ($this->duration_value > 1) {
5702 $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"));
5703 } elseif ($this->duration_value > 0) {
5704 $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"));
5705 }
5706 $datas['duration'] .= (!empty($this->duration_unit) && isset($dur[$this->duration_unit]) ? "&nbsp;".$langs->trans($dur[$this->duration_unit]) : '');
5707 }
5708 if (empty($user->socid)) {
5709 if (!empty($this->pmp) && $this->pmp) {
5710 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5711 }
5712
5713 if (isModEnabled('accounting')) {
5714 if ($this->status && isset($this->accountancy_code_sell)) {
5715 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5716 $selllabel = '<br>';
5717 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5718 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5719 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5720 $datas['accountancysell'] = $selllabel;
5721 }
5722 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5723 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5724 $buylabel = '';
5725 if (empty($this->status)) {
5726 $buylabel .= '<br>';
5727 }
5728 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5729 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5730 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5731 $datas['accountancybuy'] = $buylabel;
5732 }
5733 }
5734 }
5735 // show categories for this record only in ajax to not overload lists
5736 if (isModEnabled('category') && !$nofetch) {
5737 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5738 $form = new Form($this->db);
5739 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5740 }
5741 }
5742
5743 return $datas;
5744 }
5745
5759 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5760 {
5761 global $langs, $hookmanager;
5762
5763 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5764
5765 $result = '';
5766
5767 $newref = $this->ref;
5768 if ($maxlength) {
5769 $newref = dol_trunc($newref, $maxlength, 'middle');
5770 }
5771 $params = [
5772 'id' => $this->id,
5773 'objecttype' => ($this->type == 1 ? 'service' : 'product'),
5774 'option' => $option,
5775 'nofetch' => 1,
5776 ];
5777 $classfortooltip = 'classfortooltip';
5778 $dataparams = '';
5779 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5780 $classfortooltip = 'classforajaxtooltip';
5781 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5782 $label = '';
5783 } else {
5784 $label = implode($this->getTooltipContentArray($params));
5785 }
5786
5787 $linkclose = '';
5788 if (empty($notooltip)) {
5789 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5790 $label = $langs->trans("ShowProduct");
5791 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5792 }
5793 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5794 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5795 } else {
5796 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5797 }
5798
5799 if ($option == 'supplier' || $option == 'category') {
5800 $url = DOL_URL_ROOT.'/product/price_suppliers.php?id='.$this->id;
5801 } elseif ($option == 'stock') {
5802 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5803 } elseif ($option == 'composition') {
5804 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5805 } else {
5806 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5807 }
5808
5809 if ($option !== 'nolink') {
5810 // Add param to save lastsearch_values or not
5811 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5812 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5813 $add_save_lastsearch_values = 1;
5814 }
5815 if ($add_save_lastsearch_values) {
5816 $url .= '&save_lastsearch_values=1';
5817 }
5818 }
5819
5820 $linkstart = '<a href="'.$url.'"';
5821 $linkstart .= $linkclose.'>';
5822 $linkend = '</a>';
5823
5824 $result .= $linkstart;
5825 if ($withpicto) {
5826 if ($this->isProduct()) {
5827 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5828 }
5829 if ($this->isService()) {
5830 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5831 }
5832 }
5833 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
5834 $result .= $linkend;
5835 if ($withpicto != 2) {
5836 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5837 }
5838
5839 global $action;
5840 $hookmanager->initHooks(array('productdao'));
5841 $parameters = array('id' => $this->id, 'getnomurl' => &$result, 'label' => &$label);
5842 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5843 if ($reshook > 0) {
5844 $result = $hookmanager->resPrint;
5845 } else {
5846 $result .= $hookmanager->resPrint;
5847 }
5848
5849 return $result;
5850 }
5851
5852
5863 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5864 {
5865 global $langs;
5866
5867 $langs->load("products");
5868 $outputlangs->load("products");
5869
5870 // Positionne le modele sur le nom du modele a utiliser
5871 if (!dol_strlen($modele)) {
5872 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5873 }
5874
5875 $modelpath = "core/modules/product/doc/";
5876
5877 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5878 }
5879
5887 public function getLibStatut($mode = 0, $type = 0)
5888 {
5889 switch ($type) {
5890 case 0:
5891 return $this->LibStatut($this->status, $mode, $type);
5892 case 1:
5893 return $this->LibStatut($this->status_buy, $mode, $type);
5894 case 2:
5895 return $this->LibStatut($this->status_batch, $mode, $type);
5896 default:
5897 //Simulate previous behavior but should return an error string
5898 return $this->LibStatut($this->status_buy, $mode, $type);
5899 }
5900 }
5901
5902 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5911 public function LibStatut($status, $mode = 0, $type = 0)
5912 {
5913 // phpcs:enable
5914 global $langs;
5915
5916 $labelStatus = $labelStatusShort = '';
5917
5918 $langs->load('products');
5919 if (isModEnabled('productbatch')) {
5920 $langs->load("productbatch");
5921 }
5922
5923 if ($type == 2) {
5924 switch ($mode) {
5925 case 0:
5926 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
5927 return dolGetStatus($label);
5928 case 1:
5929 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
5930 return dolGetStatus($label);
5931 case 2:
5932 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
5933 case 3:
5934 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
5935 case 4:
5936 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
5937 case 5:
5938 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
5939 default:
5940 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
5941 }
5942 }
5943
5944 $statuttrans = empty($status) ? 'status5' : 'status4';
5945
5946 if ($status == 0) {
5947 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5948 if ($type == 0) {
5949 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
5950 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
5951 } elseif ($type == 1) {
5952 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
5953 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
5954 } elseif ($type == 2) {
5955 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
5956 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
5957 }
5958 } elseif ($status == 1) {
5959 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
5960 if ($type == 0) {
5961 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
5962 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
5963 } elseif ($type == 1) {
5964 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
5965 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
5966 } elseif ($type == 2) {
5967 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
5968 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
5969 }
5970 } elseif ($type == 2 && $status == 2) {
5971 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
5972 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
5973 }
5974
5975 if ($mode > 6) {
5976 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
5977 } else {
5978 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
5979 }
5980 }
5981
5982
5988 public function getLibFinished()
5989 {
5990 global $langs;
5991
5992 $langs->load('products');
5993 $label = '';
5994
5995 if (isset($this->finished) && $this->finished >= 0) {
5996 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
5997 $resql = $this->db->query($sql);
5998 if (!$resql) {
5999 $this->error = $this->db->error().' sql='.$sql;
6000 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
6001 return -1;
6002 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
6003 $label = $langs->trans($res['label']);
6004 }
6005 $this->db->free($resql);
6006 }
6007
6008 return $label;
6009 }
6010
6011
6012 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6029 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
6030 {
6031 // phpcs:enable
6032 if ($id_entrepot) {
6033 $this->db->begin();
6034
6035 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6036
6037 if ($nbpiece < 0) {
6038 if (!$movement) {
6039 $movement = 1;
6040 }
6041 $nbpiece = abs($nbpiece);
6042 }
6043 $op = array();
6044 $op[0] = "+".trim((string) $nbpiece);
6045 $op[1] = "-".trim((string) $nbpiece);
6046
6047 $movementstock = new MouvementStock($this->db);
6048 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->origin_id
6049 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
6050
6051 if ($result >= 0) {
6052 if ($extrafields) {
6053 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6054 $movementstock->array_options = $array_options;
6055 $movementstock->insertExtraFields();
6056 }
6057 $this->db->commit();
6058 return 1;
6059 } else {
6060 $this->error = $movementstock->error;
6061 $this->errors = $movementstock->errors;
6062
6063 $this->db->rollback();
6064 return -1;
6065 }
6066 }
6067
6068 return -1;
6069 }
6070
6071 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6092 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)
6093 {
6094 // phpcs:enable
6095 if ($id_entrepot) {
6096 $this->db->begin();
6097
6098 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6099
6100 if ($nbpiece < 0) {
6101 if (!$movement) {
6102 $movement = 1;
6103 }
6104 $nbpiece = abs($nbpiece);
6105 }
6106
6107 $op = array();
6108 $op[0] = "+".trim((string) $nbpiece);
6109 $op[1] = "-".trim((string) $nbpiece);
6110
6111 $movementstock = new MouvementStock($this->db);
6112 $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin
6113 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
6114
6115 if ($result >= 0) {
6116 if ($extrafields) {
6117 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6118 $movementstock->array_options = $array_options;
6119 $movementstock->insertExtraFields();
6120 }
6121 $this->db->commit();
6122 return 1;
6123 } else {
6124 $this->error = $movementstock->error;
6125 $this->errors = $movementstock->errors;
6126
6127 $this->db->rollback();
6128 return -1;
6129 }
6130 }
6131 return -1;
6132 }
6133
6134 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6147 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
6148 {
6149 // phpcs:enable
6150 $this->stock_reel = 0;
6151 $this->stock_warehouse = array();
6152 $this->stock_theorique = 0;
6153
6154 // Set filter on warehouse status
6155 $warehouseStatus = array();
6156 if (preg_match('/warehouseclosed/', $option)) {
6158 }
6159 if (preg_match('/warehouseopen/', $option)) {
6161 }
6162 if (preg_match('/warehouseinternal/', $option)) {
6163 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
6165 } else {
6167 }
6168 }
6169
6170 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
6171 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
6172 $sql .= ", ".$this->db->prefix()."entrepot as w";
6173 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
6174 $sql .= " AND w.rowid = ps.fk_entrepot";
6175 $sql .= " AND ps.fk_product = ".((int) $this->id);
6176 if (count($warehouseStatus)) {
6177 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
6178 }
6179
6180 $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;
6181
6182 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
6183 $result = $this->db->query($sql);
6184 if ($result) {
6185 $num = $this->db->num_rows($result);
6186 $i = 0;
6187 if ($num > 0) {
6188 while ($i < $num) {
6189 $row = $this->db->fetch_object($result);
6190 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
6191 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
6192 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
6193 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
6194 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
6195 }
6196 $this->stock_reel += $row->reel;
6197 $i++;
6198 }
6199 $this->stock_reel = (float) price2num($this->stock_reel, 'MS');
6200 }
6201 $this->db->free($result);
6202
6203 if (!preg_match('/novirtual/', $option)) {
6204 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
6205 }
6206
6207 return 1;
6208 } else {
6209 $this->error = $this->db->lasterror();
6210 return -1;
6211 }
6212 }
6213
6214
6215 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6225 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
6226 {
6227 // phpcs:enable
6228 global $hookmanager, $action;
6229
6230 $stock_commande_client = 0;
6231 $stock_commande_fournisseur = 0;
6232 $stock_sending_client = 0;
6233 $stock_reception_fournisseur = 0;
6234 $stock_inproduction = 0;
6235
6236 //dol_syslog("load_virtual_stock");
6237
6238 if (isModEnabled('order')) {
6239 $result = $this->load_stats_commande(0, '1,2', 1);
6240 if ($result < 0) {
6241 dol_print_error($this->db, $this->error);
6242 }
6243 $stock_commande_client = $this->stats_commande['qty'];
6244 }
6245 if (isModEnabled("shipping")) {
6246 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
6247 $filterShipmentStatus = '';
6248 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
6249 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
6250 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6251 $filterShipmentStatus = Expedition::STATUS_CLOSED;
6252 }
6253 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
6254 if ($result < 0) {
6255 dol_print_error($this->db, $this->error);
6256 }
6257 $stock_sending_client = $this->stats_expedition['qty'];
6258 }
6259 // Include supplier order lines
6260 if (isModEnabled("supplier_order")) {
6261 $filterStatus = getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK', '3,4');
6262 if (isset($includedraftpoforvirtual)) {
6263 $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
6264 }
6265 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
6266 if ($result < 0) {
6267 dol_print_error($this->db, $this->error);
6268 }
6269 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
6270 }
6271 // Include reception lines
6272 if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) {
6273 $filterStatus = '4';
6274 if (isset($includedraftpoforvirtual)) {
6275 $filterStatus = '0,'.$filterStatus;
6276 }
6277 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
6278 if ($result < 0) {
6279 dol_print_error($this->db, $this->error);
6280 }
6281 $stock_reception_fournisseur = $this->stats_reception['qty'];
6282 }
6283 // Include manufacturing
6284 if (isModEnabled('mrp')) {
6285 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
6286 if ($result < 0) {
6287 dol_print_error($this->db, $this->error);
6288 }
6289 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
6290 }
6291
6292 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
6293
6294 // $weBillOrderOrShipmentReception is set to 'order' or 'shipmentreception'. it will be used to know how to make virtual stock
6295 // calculation when we have a stock increase or decrease on billing. Do we have to count orders to bill or shipment/reception to bill ?
6296 $weBillOrderOrShipmentReception = getDolGlobalString('STOCK_DO_WE_BILL_ORDER_OR_SHIPMENTECEPTION_FOR_VIRTUALSTOCK', 'order');
6297
6298 // Stock decrease mode
6299 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6300 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6301 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
6302 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
6303 $tmpnewprod = dol_clone($this, 1);
6304 $result = $tmpnewprod->load_stats_commande(0, '0', 1); // Get qty in draft orders
6305 $this->stock_theorique += $tmpnewprod->stats_commande['qty'];
6306 }
6307 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'order') {
6308 $this->stock_theorique -= $stock_commande_client;
6309 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
6310 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6311 }
6312
6313 // Stock Increase mode
6314 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
6315 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6316 } 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
6317 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6318 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) { // Warning: stock change "on approval", not on validation !
6319 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
6320 $tmpnewprod = dol_clone($this, 1);
6321 $result = $tmpnewprod->load_stats_commande_fournisseur(0, '0', 1); // Get qty in draft orders
6322 $this->stock_theorique += $this->stats_commande_fournisseur['qty'];
6323 }
6324 $this->stock_theorique -= $stock_reception_fournisseur;
6325 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'order') {
6326 $this->stock_theorique += $stock_commande_fournisseur;
6327 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
6328 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6329 }
6330
6331 $parameters = array('id' => $this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
6332 // Note that $action and $object may have been modified by some hooks
6333 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
6334 if ($reshook > 0) {
6335 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
6336 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
6337 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
6338 }
6339
6340 //Virtual Stock by Warehouse
6341 if (!empty($this->stock_warehouse) && getDolGlobalString('STOCK_ALLOW_VIRTUAL_STOCK_PER_WAREHOUSE')) {
6342 foreach ($this->stock_warehouse as $warehouseid => $stockwarehouse) {
6343 if (isModEnabled('mrp')) {
6344 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock, $warehouseid);
6345 if ($result < 0) {
6346 dol_print_error($this->db, $this->error);
6347 }
6348 }
6349
6350 if ($this->fk_default_warehouse == $warehouseid) {
6351 $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']);
6352 } else {
6353 $this->stock_warehouse[$warehouseid]->virtual = $this->stock_warehouse[$warehouseid]->real + $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'];
6354 }
6355 }
6356 }
6357
6358 return 1;
6359 }
6360
6361
6369 public function loadBatchInfo($batch)
6370 {
6371 $result = array();
6372
6373 $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";
6374 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
6375 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
6376 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
6377 $resql = $this->db->query($sql);
6378 if ($resql) {
6379 $num = $this->db->num_rows($resql);
6380 $i = 0;
6381 while ($i < $num) {
6382 $obj = $this->db->fetch_object($resql);
6383 $result[] = array('batch' => $batch, 'eatby' => $this->db->jdate($obj->eatby), 'sellby' => $this->db->jdate($obj->sellby), 'qty' => $obj->qty);
6384 $i++;
6385 }
6386 return $result;
6387 } else {
6388 dol_print_error($this->db);
6389 $this->db->rollback();
6390 return array();
6391 }
6392 }
6393
6394 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6403 public function add_photo($sdir, $file)
6404 {
6405 // phpcs:enable
6406 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6407
6408 $result = 0;
6409
6410 $dir = $sdir;
6411 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6412 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
6413 } else {
6414 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
6415 }
6416
6417 dol_mkdir($dir);
6418
6419 $dir_osencoded = $dir;
6420
6421 if (is_dir($dir_osencoded)) {
6422 $originImage = $dir.'/'.$file['name'];
6423
6424 // Cree fichier en taille origine
6425 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
6426
6427 if (file_exists(dol_osencode($originImage))) {
6428 // Create thumbs
6429 $this->addThumbs($originImage);
6430 }
6431 }
6432
6433 if (is_numeric($result) && $result > 0) {
6434 return 1;
6435 } else {
6436 return -1;
6437 }
6438 }
6439
6440 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6447 public function is_photo_available($sdir)
6448 {
6449 // phpcs:enable
6450 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6451 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6452
6453 $dir = $sdir;
6454 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6455 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
6456 } else {
6457 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
6458 }
6459
6460 $dir_osencoded = dol_osencode($dir);
6461 if (file_exists($dir_osencoded)) {
6462 $handle = opendir($dir_osencoded);
6463 if (is_resource($handle)) {
6464 while (($file = readdir($handle)) !== false) {
6465 if (!utf8_check($file)) {
6466 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6467 }
6468 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6469 return true;
6470 }
6471 }
6472 }
6473 }
6474
6475 return false;
6476 }
6477
6478 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6486 public function liste_photos($dir, $nbmax = 0)
6487 {
6488 // phpcs:enable
6489 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6490 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6491
6492 $nbphoto = 0;
6493 $tabobj = array();
6494
6495 $dir_osencoded = dol_osencode($dir);
6496 $handle = @opendir($dir_osencoded);
6497 if (is_resource($handle)) {
6498 while (($file = readdir($handle)) !== false) {
6499 if (!utf8_check($file)) {
6500 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6501 }
6502 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6503 $nbphoto++;
6504
6505 // We forge name of thumb.
6506 $photo = $file;
6507 $photo_vignette = '';
6508 $regs = array();
6509 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6510 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6511 }
6512
6513 $dirthumb = $dir.'thumbs/';
6514
6515 // Object
6516 $obj = array();
6517 $obj['photo'] = $photo;
6518 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6519 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6520 } else {
6521 $obj['photo_vignette'] = "";
6522 }
6523
6524 $tabobj[$nbphoto - 1] = $obj;
6525
6526 // Do we have to continue with next photo ?
6527 if ($nbmax && $nbphoto >= $nbmax) {
6528 break;
6529 }
6530 }
6531 }
6532
6533 closedir($handle);
6534 }
6535
6536 return $tabobj;
6537 }
6538
6539 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6546 public function delete_photo($file)
6547 {
6548 // phpcs:enable
6549 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6550 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6551
6552 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6553 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6554 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6555
6556 // On efface l'image d'origine
6557 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6558
6559 // Si elle existe, on efface la vignette
6560 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6561 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6562 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6563 dol_delete_file($dirthumb.$photo_vignette);
6564 }
6565
6566 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6567 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6568 dol_delete_file($dirthumb.$photo_vignette);
6569 }
6570 }
6571 }
6572
6573 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6580 public function get_image_size($file)
6581 {
6582 // phpcs:enable
6583 $file_osencoded = dol_osencode($file);
6584 $infoImg = getimagesize($file_osencoded); // Get information on image
6585 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6586 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6587 }
6588
6594 public function loadStateBoard()
6595 {
6596 global $hookmanager;
6597
6598 $this->nb = array();
6599
6600 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6601 $sql .= " FROM ".$this->db->prefix()."product as p";
6602 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6603 // Add where from hooks
6604 if (is_object($hookmanager)) {
6605 $parameters = array();
6606 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6607 $sql .= $hookmanager->resPrint;
6608 }
6609 $sql .= ' GROUP BY fk_product_type';
6610
6611 $resql = $this->db->query($sql);
6612 if ($resql) {
6613 while ($obj = $this->db->fetch_object($resql)) {
6614 if ($obj->fk_product_type == 1) {
6615 $this->nb["services"] = $obj->nb;
6616 } else {
6617 $this->nb["products"] = $obj->nb;
6618 }
6619 }
6620 $this->db->free($resql);
6621 return 1;
6622 } else {
6623 dol_print_error($this->db);
6624 $this->error = $this->db->error();
6625 return -1;
6626 }
6627 }
6628
6634 public function isProduct()
6635 {
6636 return $this->type == Product::TYPE_PRODUCT;
6637 }
6638
6644 public function isService()
6645 {
6646 return $this->type == Product::TYPE_SERVICE;
6647 }
6648
6654 public function isStockManaged()
6655 {
6656 return ($this->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'));
6657 }
6658
6664 public function isMandatoryPeriod()
6665 {
6666 return $this->mandatory_period == 1;
6667 }
6668
6674 public function hasbatch()
6675 {
6676 return $this->status_batch > 0;
6677 }
6678
6679
6680 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6689 public function get_barcode($object, $type = '')
6690 {
6691 // phpcs:enable
6692 global $conf;
6693
6694 $result = '';
6695 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6696 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6697 foreach ($dirsociete as $dirroot) {
6698 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6699 if ($res) {
6700 break;
6701 }
6702 }
6703 $var = getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM');
6704 $mod = new $var();
6705 '@phan-var-force ModeleNumRefBarCode $mod';
6706
6707 $result = $mod->getNextValue($object, $type);
6708
6709 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6710 }
6711 return $result;
6712 }
6713
6721 public function initAsSpecimen()
6722 {
6723 $now = dol_now();
6724
6725 // Initialize parameters
6726 $this->specimen = 1;
6727 $this->id = 0;
6728 $this->ref = 'PRODUCT_SPEC';
6729 $this->label = 'PRODUCT SPECIMEN';
6730 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6731 $this->specimen = 1;
6732 $this->country_id = 1;
6733 $this->status = 1;
6734 $this->status_buy = 1;
6735 $this->tobatch = 0;
6736 $this->sell_or_eat_by_mandatory = 0;
6737 $this->note_private = 'This is a comment (private)';
6738 $this->note_public = 'This is a comment (public)';
6739 $this->date_creation = $now;
6740 $this->date_modification = $now;
6741
6742 $this->weight = 4;
6743 $this->weight_units = 3;
6744
6745 $this->length = 5;
6746 $this->length_units = 1;
6747 $this->width = 6;
6748 $this->width_units = 0;
6749 $this->height = null;
6750 $this->height_units = null;
6751
6752 $this->surface = 30;
6753 $this->surface_units = 0;
6754 $this->volume = 300;
6755 $this->volume_units = 0;
6756
6757 $this->barcode = -1; // Create barcode automatically
6758
6759 return 1;
6760 }
6761
6771 public function getLabelOfUnit($type = 'long', $outputlangs = null, $noentities = 0)
6772 {
6773 global $langs;
6774
6775 if (empty($this->fk_unit)) {
6776 return '';
6777 }
6778 if (empty($outputlangs)) {
6779 $outputlangs = $langs;
6780 }
6781
6782 $outputlangs->load('products');
6783 $label = '';
6784
6785 $sql = "SELECT code, label, short_label FROM ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6786
6787 $resql = $this->db->query($sql);
6788 if (!$resql) {
6789 $this->error = $this->db->error();
6790 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6791 return -1;
6792 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
6793 if ($type == 'short') {
6794 if ($noentities) {
6795 $label = $outputlangs->transnoentitiesnoconv($res['short_label']);
6796 } else {
6797 $label = $outputlangs->trans($res['short_label']);
6798 }
6799 } elseif ($type == 'code') {
6800 $label = $res['code'];
6801 } else {
6802 if ($outputlangs->trans('unit'.$res['code']) == 'unit'.$res['code']) {
6803 // No translation available
6804 $label = $res['label'];
6805 } else {
6806 // Return the translated value
6807 if ($noentities) {
6808 $label = $outputlangs->transnoentitiesnoconv('unit'.$res['code']);
6809 } else {
6810 $label = $outputlangs->trans('unit'.$res['code']);
6811 }
6812 }
6813 }
6814 }
6815 $this->db->free($resql);
6816
6817 return $label;
6818 }
6819
6820 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6826 public function min_recommended_price()
6827 {
6828 // phpcs:enable
6829 $maxpricesupplier = 0;
6830
6831 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
6832 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6833 $product_fourn = new ProductFournisseur($this->db);
6834 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
6835
6836 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
6837 foreach ($product_fourn_list as $productfourn) {
6838 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
6839 $maxpricesupplier = $productfourn->fourn_unitprice;
6840 }
6841 }
6842
6843 $maxpricesupplier *= getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE');
6844 }
6845 }
6846
6847 return $maxpricesupplier;
6848 }
6849
6850
6861 public function setCategories($categories)
6862 {
6863 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
6864 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
6865 }
6866
6875 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
6876 {
6877 $tables = array(
6878 'product_customer_price',
6879 'product_customer_price_log'
6880 );
6881
6882 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
6883 }
6884
6896 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
6897 {
6898 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
6899 $query = $this->db->query($sql);
6900
6901 $rules = array();
6902
6903 while ($result = $this->db->fetch_object($query)) {
6904 $rules[$result->level] = $result;
6905 }
6906
6907 //Because prices can be based on other level's prices, we temporarily store them
6908 $prices = array(
6909 1 => $baseprice
6910 );
6911
6912 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
6913 for ($i = 1; $i <= $nbofproducts; $i++) {
6914 $price = $baseprice;
6915 $price_min = $baseprice;
6916
6917 //We have to make sure it does exist and it is > 0
6918 //First price level only allows changing min_price
6919 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
6920 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
6921 }
6922
6923 $prices[$i] = $price;
6924
6925 //We have to make sure it does exist and it is > 0
6926 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
6927 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
6928 }
6929
6930 //Little check to make sure the price is modified before triggering generation
6931 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
6932 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
6933
6934 if ($check_amount && $check_type) {
6935 continue;
6936 }
6937
6938 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, 1) < 0) {
6939 return -1;
6940 }
6941 }
6942
6943 return 1;
6944 }
6945
6951 public function getRights()
6952 {
6953 global $user;
6954
6955 if ($this->isProduct()) {
6956 return $user->rights->produit;
6957 } else {
6958 return $user->rights->service;
6959 }
6960 }
6961
6968 public function info($id)
6969 {
6970 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
6971 $sql .= " p.fk_user_author, p.fk_user_modif";
6972 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
6973 $sql .= " WHERE p.rowid = ".((int) $id);
6974
6975 $result = $this->db->query($sql);
6976 if ($result) {
6977 if ($this->db->num_rows($result)) {
6978 $obj = $this->db->fetch_object($result);
6979
6980 $this->id = $obj->rowid;
6981 $this->ref = $obj->ref;
6982
6983 $this->user_creation_id = $obj->fk_user_author;
6984 $this->user_modification_id = $obj->fk_user_modif;
6985
6986 $this->date_creation = $this->db->jdate($obj->date_creation);
6987 $this->date_modification = $this->db->jdate($obj->date_modification);
6988 }
6989
6990 $this->db->free($result);
6991 } else {
6992 dol_print_error($this->db);
6993 }
6994 }
6995
6996
7002 public function getProductDurationHours()
7003 {
7004 if (empty($this->duration_value)) {
7005 $this->errors[] = 'ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
7006 return -1;
7007 }
7008 if ($this->duration_unit == 's') {
7009 $prodDurationHours = 1. / 3600;
7010 } elseif ($this->duration_unit == 'i' || $this->duration_unit == 'mn' || $this->duration_unit == 'min') {
7011 $prodDurationHours = 1. / 60;
7012 } elseif ($this->duration_unit == 'h') {
7013 $prodDurationHours = 1.;
7014 } elseif ($this->duration_unit == 'd') {
7015 $prodDurationHours = 24.;
7016 } elseif ($this->duration_unit == 'w') {
7017 $prodDurationHours = 24. * 7;
7018 } elseif ($this->duration_unit == 'm') {
7019 $prodDurationHours = 24. * 30;
7020 } elseif ($this->duration_unit == 'y') {
7021 $prodDurationHours = 24. * 365;
7022 } else {
7023 $prodDurationHours = 0.0;
7024 }
7025 $prodDurationHours *= $this->duration_value;
7026
7027 return $prodDurationHours;
7028 }
7029
7030
7038 public function getKanbanView($option = '', $arraydata = null)
7039 {
7040 global $langs, $conf;
7041
7042 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
7043
7044 $return = '<div class="box-flex-item box-flex-grow-zero">';
7045 $return .= '<div class="info-box info-box-sm">';
7046 $return .= '<div class="info-box-img">';
7047 $label = '';
7048 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
7049 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
7050 $return .= $label;
7051 } else {
7052 if ($this->isProduct()) {
7053 $label .= img_picto('', 'product');
7054 } elseif ($this->isService()) {
7055 $label .= img_picto('', 'service');
7056 }
7057 $return .= $label;
7058 }
7059 $return .= '</div>';
7060 $return .= '<div class="info-box-content">';
7061 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
7062 if ($selected >= 0) {
7063 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
7064 }
7065 if (property_exists($this, 'label')) {
7066 $return .= '<br><span class="info-box-label opacitymedium inline-block tdoverflowmax150 valignmiddle" title="'.dol_escape_htmltag($this->label).'">'.dol_escape_htmltag($this->label).'</span>';
7067 }
7068 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
7069 if ($this->price_base_type == 'TTC') {
7070 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
7071 } else {
7072 if ($this->status) {
7073 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
7074 }
7075 }
7076 }
7077 $br = 1;
7078 if (property_exists($this, 'stock_reel') && $this->isProduct()) {
7079 $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>';
7080 $br = 0;
7081 }
7082 if (method_exists($this, 'getLibStatut')) {
7083 if ($br) {
7084 $return .= '<br><div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
7085 } else {
7086 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
7087 }
7088 }
7089 $return .= '</div>';
7090 $return .= '</div>';
7091 $return .= '</div>';
7092 return $return;
7093 }
7094
7101 public function getProductsToPreviewInEmail($limit)
7102 {
7103
7104 if (!is_numeric($limit)) {
7105 return -1;
7106 }
7107
7108 $sql = "SELECT p.rowid, p.ref, p.label, p.description, p.entity, ef.filename
7109 FROM ".MAIN_DB_PREFIX."product AS p
7110 JOIN ".MAIN_DB_PREFIX."ecm_files AS ef ON p.rowid = ef.src_object_id
7111 WHERE ef.entity IN (".getEntity('product').")
7112 AND (ef.filename LIKE '%.png' OR ef.filename LIKE '%.jpeg' OR ef.filename LIKE '%.svg')
7113 GROUP BY p.rowid, p.ref, p.label, p.description, p.entity, ef.filename
7114 ORDER BY p.datec ASC
7115 LIMIT " . ((int) $limit);
7116
7117 $resql = $this->db->query($sql);
7118 $products = array();
7119
7120 if ($resql) {
7121 while ($obj = $this->db->fetch_object($resql)) {
7122 $products[] = array(
7123 'rowid' => $obj->rowid,
7124 'ref' => $obj->ref,
7125 'label' => $obj->label,
7126 'description' => $obj->description,
7127 'entity' => $obj->entity,
7128 'filename' => $obj->filename
7129 );
7130 }
7131 } else {
7132 dol_print_error($this->db);
7133 }
7134 if (empty($products)) {
7135 return -1;
7136 }
7137 return $products;
7138 }
7139}
7140
7146{
7147 public $picto = 'service';
7148}
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)
clean_account($account)
Return accounting account without zero on the right.
$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.
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.
getLabelOfUnit($type='long', $outputlangs=null, $noentities=0)
Reads the units dictionary to return the translation code of a unit (if type='code'),...
_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