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