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