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