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