dolibarr 22.0.5
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-2025 Frédéric France <frederic.france@free.fr>
18 * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
19 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
20 * Copyright (C) 2025 Lenin Rivas <lenin.rivas777@gmail.com>
21 * Copyright (C) 2026 Anthony Berton <anthony.berton@bb2a.fr>
22 *
23 * This program is free software; you can redistribute it and/or modify
24 * it under the terms of the GNU General Public License as published by
25 * the Free Software Foundation; either version 3 of the License, or
26 * (at your option) any later version.
27 *
28 * This program is distributed in the hope that it will be useful,
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 * GNU General Public License for more details.
32 *
33 * You should have received a copy of the GNU General Public License
34 * along with this program. If not, see <https://www.gnu.org/licenses/>.
35 */
36
42require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
43require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
44require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
45require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
46require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
47
51class Product extends CommonObject
52{
57 const SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY = 1;
58 const SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY = 2;
59 const SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT = 3;
60
64 public $element = 'product';
65
69 public $table_element = 'product';
70
74 public $fk_element = 'fk_product';
75
79 protected $childtables = array(
80 'supplier_proposaldet' => array('name' => 'SupplierProposal', 'parent' => 'supplier_proposal', 'parentkey' => 'fk_supplier_proposal'),
81 'propaldet' => array('name' => 'Proposal', 'parent' => 'propal', 'parentkey' => 'fk_propal'),
82 'commandedet' => array('name' => 'Order', 'parent' => 'commande', 'parentkey' => 'fk_commande'),
83 'facturedet' => array('name' => 'Invoice', 'parent' => 'facture', 'parentkey' => 'fk_facture'),
84 'contratdet' => array('name' => 'Contract', 'parent' => 'contrat', 'parentkey' => 'fk_contrat'),
85 'facture_fourn_det' => array('name' => 'SupplierInvoice', 'parent' => 'facture_fourn', 'parentkey' => 'fk_facture_fourn'),
86 'commande_fournisseurdet' => array('name' => 'SupplierOrder', 'parent' => 'commande_fournisseur', 'parentkey' => 'fk_commande'),
87 'mrp_production' => array('name' => 'Mo', 'parent' => 'mrp_mo', 'parentkey' => 'fk_mo', 'enabled' => 'isModEnabled("mrp")'),
88 'bom_bom' => array('name' => 'BOM', 'enabled' => 'isModEnabled("bom")'),
89 'bom_bomline' => array('name' => 'BOMLine', 'parent' => 'bom_bom', 'parentkey' => 'fk_bom', 'enabled' => 'isModEnabled("bom")'),
90 );
91
97 public $picto = 'product';
98
102 protected $table_ref_field = 'ref';
103
108 public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm';
109
115 public $libelle;
116
122 public $label;
123
129 public $description;
130
136 public $other;
137
143 public $type = self::TYPE_PRODUCT;
144
150 public $price;
151
155 public $price_formated; // used by takepos/ajax/ajax.php
156
162 public $price_ttc;
163
167 public $price_ttc_formated; // used by takepos/ajax/ajax.php
168
174 public $price_min;
175
181 public $price_min_ttc;
182
187 public $price_base_type;
191 public $price_label;
192
194
197 public $multiprices = array();
201 public $multiprices_ttc = array();
205 public $multiprices_base_type = array();
209 public $multiprices_default_vat_code = array();
213 public $multiprices_min = array();
217 public $multiprices_min_ttc = array();
221 public $multiprices_tva_tx = array();
225 public $multiprices_recuperableonly = array();
226
228
231 public $price_by_qty;
235 public $prices_by_qty = array();
239 public $prices_by_qty_id = array();
243 public $prices_by_qty_list = array();
244
248 public $level;
249
253 public $multilangs = array();
254
258 public $default_vat_code;
259
263 public $tva_tx;
264
268 public $tva_npr = 0;
269
273 public $remise_percent;
274
278 public $localtax1_tx;
282 public $localtax2_tx;
286 public $localtax1_type;
290 public $localtax2_type;
291
292 // Properties set by get_buyprice() for return
293
297 public $desc_supplier;
301 public $vatrate_supplier;
305 public $default_vat_code_supplier;
306
310 public $fourn_multicurrency_price;
314 public $fourn_multicurrency_unitprice;
318 public $fourn_multicurrency_tx;
322 public $fourn_multicurrency_id;
326 public $fourn_multicurrency_code;
327
331 public $packaging;
332
333
340 public $lifetime;
341
348 public $qc_frequency;
349
355 public $stock_reel = 0;
356
362 public $stock_theorique;
363
369 public $cost_price;
370
376 public $pmp;
377
383 public $seuil_stock_alerte = 0;
384
388 public $desiredstock = 0;
389
393 public $duration_value;
397 public $duration_unit;
401 public $duration;
402
406 public $fk_default_workstation;
407
413 public $status = 0;
414
421 public $tosell;
422
428 public $status_buy = 0;
429
436 public $tobuy;
437
443 public $finished;
444
450 public $fk_default_bom;
451
457 public $product_fourn_price_id;
458
464 public $buyprice;
465
471 public $tobatch;
472
473
479 public $status_batch = 0;
480
486 public $sell_or_eat_by_mandatory = 0;
487
493 public $batch_mask = '';
494
500 public $customcode;
501
507 public $url;
508
510
513 public $weight;
514
518 public $weight_units; // scale -3, 0, 3, 6
522 public $length;
526 public $length_units; // scale -3, 0, 3, 6
530 public $width;
534 public $width_units; // scale -3, 0, 3, 6
538 public $height;
542 public $height_units; // scale -3, 0, 3, 6
546 public $surface;
550 public $surface_units; // scale -3, 0, 3, 6
554 public $volume;
558 public $volume_units; // scale -3, 0, 3, 6
559
563 public $net_measure;
567 public $net_measure_units; // scale -3, 0, 3, 6
568
572 public $accountancy_code_sell;
576 public $accountancy_code_sell_intra;
580 public $accountancy_code_sell_export;
584 public $accountancy_code_buy;
588 public $accountancy_code_buy_intra;
592 public $accountancy_code_buy_export;
593
597 public $barcode;
598
602 public $barcode_type;
603
607 public $barcode_type_code;
608
612 public $stats_propale = array();
613
617 public $stats_commande = array();
618
622 public $stats_contrat = array();
623
627 public $stats_facture = array();
628
632 public $stats_proposal_supplier = array();
633
637 public $stats_commande_fournisseur = array();
638
642 public $stats_expedition = array();
643
647 public $stats_reception = array();
648
652 public $stats_mo = array();
653
657 public $stats_bom = array();
658
662 public $stats_mrptoconsume = array();
663
667 public $stats_mrptoproduce = array();
668
672 public $stats_facturerec = array();
673
677 public $stats_facture_fournisseur = array();
678
682 public $stats_facturefournrec = array();
683
687 public $imgWidth;
691 public $imgHeight;
692
697 public $product_fourn_id;
698
703 public $product_id_already_linked;
704
709 public $nbphoto = 0;
710
714 public $stock_warehouse = array();
715
719 public $fk_default_warehouse;
720
724 public $fk_price_expression;
725
730 public $fourn_qty;
731
736 public $fourn_pu;
737
742 public $fourn_price_base_type;
743
747 public $fourn_socid;
748
754 public $ref_fourn;
755
759 public $ref_supplier;
760
766 public $fk_unit;
767
773 public $price_autogen = 0;
774
780 public $supplierprices;
781
787 public $sousprods = array();
788
792 public $res;
793
794
800 public $is_object_used;
801
811 public $is_sousproduit_qty;
812
823 public $is_sousproduit_incdec;
824
828 public $mandatory_period;
829
835 public $stockable_product = 1;
836
865 public $fields = array(
866 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'index' => 1, 'position' => 1, 'comment' => 'Id'),
867 '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'),
868 'entity' => array('type' => 'integer', 'label' => 'Entity', 'enabled' => 1, 'visible' => 0, 'default' => '1', 'notnull' => 1, 'index' => 1, 'position' => 5),
869 'label' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'showoncombobox' => 2, 'position' => 15, 'csslist' => 'tdoverflowmax250'),
870 'barcode' => array('type' => 'varchar(255)', 'label' => 'Barcode', 'enabled' => 'isModEnabled("barcode")', 'position' => 20, 'visible' => -1, 'showoncombobox' => 3, 'cssview' => 'tdwordbreak', 'csslist' => 'tdoverflowmax125'),
871 'fk_barcode_type' => array('type' => 'integer', 'label' => 'BarcodeType', 'enabled' => 1, 'position' => 21, 'notnull' => 0, 'visible' => -1,),
872 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 61),
873 'note' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 62),
874 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 500),
875 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 501),
876 //'date_valid' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-2, 'position'=>502),
877 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 510, 'foreignkey' => 'llx_user.rowid'),
878 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 511),
879 //'fk_user_valid' =>array('type'=>'integer', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>512),
880 'localtax1_tx' => array('type' => 'double(6,3)', 'label' => 'Localtax1tx', 'enabled' => 1, 'position' => 150, 'notnull' => 0, 'visible' => -1,),
881 'localtax1_type' => array('type' => 'varchar(10)', 'label' => 'Localtax1type', 'enabled' => 1, 'position' => 155, 'notnull' => 1, 'visible' => -1,),
882 'localtax2_tx' => array('type' => 'double(6,3)', 'label' => 'Localtax2tx', 'enabled' => 1, 'position' => 160, 'notnull' => 0, 'visible' => -1,),
883 'localtax2_type' => array('type' => 'varchar(10)', 'label' => 'Localtax2type', 'enabled' => 1, 'position' => 165, 'notnull' => 1, 'visible' => -1,),
884 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 170),
885 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'index' => 0, 'position' => 1000),
886 //'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')),
887 //'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')),
888 'mandatory_period' => array('type' => 'integer', 'label' => 'mandatoryperiod', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'default' => '0', 'index' => 1, 'position' => 1000),
889 'stockable_product' =>array('type' => 'integer', 'label' => 'stockable_product', 'enabled' => 1, 'visible' => 1, 'default' => '1', 'notnull' => 1, 'index' => 1, 'position' => 502),
890 );
891
895 const TYPE_PRODUCT = 0;
899 const TYPE_SERVICE = 1;
900
904 const DISABLED_STOCK = 0;
905 const ENABLED_STOCK = 1;
906
912 public function __construct($db)
913 {
914 $this->db = $db;
915
916 $this->ismultientitymanaged = 1;
917 $this->isextrafieldmanaged = 1;
918
919 $this->canvas = '';
920 }
921
927 public function check()
928 {
929 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
930 $this->ref = trim($this->ref);
931 } else {
932 $this->ref = dol_sanitizeFileName(stripslashes($this->ref));
933 }
934
935 $err = 0;
936 if (dol_strlen(trim($this->ref)) == 0) {
937 $err++;
938 }
939
940 if (dol_strlen(trim($this->label)) == 0) {
941 $err++;
942 }
943
944 if ($err > 0) {
945 return 0;
946 } else {
947 return 1;
948 }
949 }
950
958 public function create($user, $notrigger = 0)
959 {
960 global $conf, $langs;
961
962 $error = 0;
963
964 // Clean parameters
965 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
966 $this->ref = trim($this->ref);
967 } else {
968 $this->ref = dol_sanitizeFileName(dol_string_nospecial(trim($this->ref)));
969 }
970 $this->label = trim($this->label);
971 $this->price_ttc = (float) price2num($this->price_ttc);
972 $this->price = (float) price2num($this->price);
973 $this->price_min_ttc = (float) price2num($this->price_min_ttc);
974 $this->price_min = (float) price2num($this->price_min);
975 $this->price_label = trim($this->price_label);
976 if (empty($this->tva_tx)) {
977 $this->tva_tx = 0;
978 }
979 if (empty($this->tva_npr)) {
980 $this->tva_npr = 0;
981 }
982 // Local taxes
983 if (empty($this->localtax1_tx)) {
984 $this->localtax1_tx = 0;
985 }
986 if (empty($this->localtax2_tx)) {
987 $this->localtax2_tx = 0;
988 }
989 if (empty($this->localtax1_type)) {
990 $this->localtax1_type = '0';
991 }
992 if (empty($this->localtax2_type)) {
993 $this->localtax2_type = '0';
994 }
995 // Price
996 if (empty($this->price_base_type) && getDolGlobalString('PRODUCT_PRICE_BASE_TYPE')) {
997 $this->price_base_type = getDolGlobalString('PRODUCT_PRICE_BASE_TYPE');
998 }
999 if (empty($this->price)) {
1000 $this->price = 0;
1001 }
1002 if (empty($this->price_min)) {
1003 $this->price_min = 0;
1004 }
1005 // Price by quantity
1006 if (empty($this->price_by_qty)) {
1007 $this->price_by_qty = 0;
1008 }
1009
1010 if (empty($this->status)) {
1011 $this->status = 0;
1012 }
1013 if (empty($this->status_buy)) {
1014 $this->status_buy = 0;
1015 }
1016 if (empty($this->stockable_product)) {
1017 $this->stockable_product = 0;
1018 }
1019
1020 $price_ht = 0;
1021 $price_ttc = 0;
1022 $price_min_ht = 0;
1023 $price_min_ttc = 0;
1024
1025 //
1026 if ($this->price_base_type == 'TTC' && $this->price_ttc > 0) {
1027 $price_ttc = price2num($this->price_ttc, 'MU');
1028 $price_ht = price2num($this->price_ttc / (1 + ($this->tva_tx / 100)), 'MU');
1029 }
1030
1031 //
1032 if ($this->price_base_type != 'TTC' && $this->price > 0) {
1033 $price_ht = price2num($this->price, 'MU');
1034 $price_ttc = price2num($this->price * (1 + ($this->tva_tx / 100)), 'MU');
1035 }
1036
1037 //
1038 if (($this->price_min_ttc > 0) && ($this->price_base_type == 'TTC')) {
1039 $price_min_ttc = price2num($this->price_min_ttc, 'MU');
1040 $price_min_ht = price2num($this->price_min_ttc / (1 + ($this->tva_tx / 100)), 'MU');
1041 }
1042
1043 //
1044 if (($this->price_min > 0) && ($this->price_base_type != 'TTC')) {
1045 $price_min_ht = price2num($this->price_min, 'MU');
1046 $price_min_ttc = price2num($this->price_min * (1 + ($this->tva_tx / 100)), 'MU');
1047 }
1048
1049 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1050 $this->accountancy_code_buy_intra = trim($this->accountancy_code_buy_intra);
1051 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1052 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1053 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1054 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1055
1056 // Barcode value
1057 $this->barcode = trim($this->barcode);
1058 $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
1059 // Check parameters
1060 if (empty($this->label)) {
1061 $langs->load('errors');
1062 $this->errors[] = $langs->trans('ErrorMandatoryParametersNotProvided');
1063 return -1;
1064 }
1065
1066 if (empty($this->ref) || $this->ref == 'auto') {
1067 // Load object modCodeProduct
1068 $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
1069 if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
1070 if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
1071 $module = substr($module, 0, dol_strlen($module) - 4);
1072 }
1073 dol_include_once('/core/modules/product/'.$module.'.php');
1074 $modCodeProduct = new $module();
1075 '@phan-var-force ModeleProductCode $modCodeProduct';
1076 if (!empty($modCodeProduct->code_auto)) {
1077 $this->ref = $modCodeProduct->getNextValue($this, $this->type);
1078 }
1079 unset($modCodeProduct);
1080 }
1081
1082 if (empty($this->ref)) {
1083 $this->error = 'ProductModuleNotSetupForAutoRef';
1084 return -2;
1085 }
1086 }
1087
1088 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);
1089
1090 $now = dol_now();
1091
1092 if (empty($this->date_creation)) {
1093 $this->date_creation = $now;
1094 }
1095
1096 $this->db->begin();
1097
1098 // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
1099 if ($this->barcode == '-1' || $this->barcode == 'auto') {
1100 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1101 }
1102
1103 // Check more parameters
1104 // If error, this->errors[] is filled
1105 $result = $this->verify();
1106
1107 if ($result >= 0) {
1108 $sql = "SELECT count(*) as nb";
1109 $sql .= " FROM ".$this->db->prefix()."product";
1110 $sql .= " WHERE entity IN (".getEntity('product').")";
1111 $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
1112
1113 $result = $this->db->query($sql);
1114 if ($result) {
1115 $obj = $this->db->fetch_object($result);
1116 if ($obj->nb == 0) {
1117 // Insert new product, no previous one found
1118 $sql = "INSERT INTO ".$this->db->prefix()."product (";
1119 $sql .= "datec";
1120 $sql .= ", entity";
1121 $sql .= ", ref";
1122 $sql .= ", ref_ext";
1123 $sql .= ", price_min";
1124 $sql .= ", price_min_ttc";
1125 $sql .= ", label";
1126 $sql .= ", fk_user_author";
1127 $sql .= ", fk_product_type";
1128 $sql .= ", price";
1129 $sql .= ", price_ttc";
1130 $sql .= ", price_base_type";
1131 $sql .= ", price_label";
1132 $sql .= ", tobuy";
1133 $sql .= ", tosell";
1134 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1135 $sql .= ", accountancy_code_buy";
1136 $sql .= ", accountancy_code_buy_intra";
1137 $sql .= ", accountancy_code_buy_export";
1138 $sql .= ", accountancy_code_sell";
1139 $sql .= ", accountancy_code_sell_intra";
1140 $sql .= ", accountancy_code_sell_export";
1141 }
1142 $sql .= ", canvas";
1143 $sql .= ", finished";
1144 $sql .= ", tobatch";
1145 $sql .= ", sell_or_eat_by_mandatory";
1146 $sql .= ", batch_mask";
1147 $sql .= ", fk_unit";
1148 $sql .= ", mandatory_period";
1149 $sql .= ", stockable_product";
1150 if (!empty($this->default_vat_code)) $sql.=", default_vat_code";
1151 $sql .= ") VALUES (";
1152 $sql .= "'".$this->db->idate($this->date_creation)."'";
1153 $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
1154 $sql .= ", '".$this->db->escape($this->ref)."'";
1155 $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1156 $sql .= ", ".price2num($price_min_ht);
1157 $sql .= ", ".price2num($price_min_ttc);
1158 $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
1159 $sql .= ", ".((int) $user->id);
1160 $sql .= ", ".((int) $this->type);
1161 $sql .= ", ".price2num($price_ht, 'MT');
1162 $sql .= ", ".price2num($price_ttc, 'MT');
1163 $sql .= ", '".$this->db->escape($this->price_base_type)."'";
1164 $sql .= ", ".(!empty($this->price_label) ? "'".$this->db->escape($this->price_label)."'" : "null");
1165 $sql .= ", ".((int) $this->status);
1166 $sql .= ", ".((int) $this->status_buy);
1167 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1168 $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
1169 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
1170 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
1171 $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
1172 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
1173 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
1174 }
1175 $sql .= ", '".$this->db->escape($this->canvas)."'";
1176 $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
1177 $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
1178 $sql .= ", ".((empty($this->sell_or_eat_by_mandatory) || $this->sell_or_eat_by_mandatory < 0) ? 0 : ((int) $this->sell_or_eat_by_mandatory));
1179 $sql .= ", '".$this->db->escape($this->batch_mask)."'";
1180 $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
1181 $sql .= ", '".$this->db->escape((string) $this->mandatory_period)."'";
1182 $sql .= ", ".((int) $this->stockable_product);
1183 if (!empty($this->default_vat_code)) $sql.=", '".$this->db->escape($this->default_vat_code)."'";
1184 $sql .= ")";
1185 dol_syslog(get_class($this)."::Create", LOG_DEBUG);
1186
1187 $result = $this->db->query($sql);
1188 if ($result) {
1189 $id = $this->db->last_insert_id($this->db->prefix()."product");
1190
1191 if ($id > 0) {
1192 $this->id = $id;
1193 $this->price = $price_ht;
1194 $this->price_ttc = $price_ttc;
1195 $this->price_min = $price_min_ht;
1196 $this->price_min_ttc = $price_min_ttc;
1197
1198 $result = $this->_log_price($user);
1199 if ($result > 0) {
1200 if ($this->update($id, $user, 1, 'add') <= 0) {
1201 $error++;
1202 }
1203 } else {
1204 $error++;
1205 $this->error = $this->db->lasterror();
1206 }
1207
1208 // update accountancy for this entity
1209 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1210 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1211
1212 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1213 $sql .= " fk_product";
1214 $sql .= ", entity";
1215 $sql .= ", accountancy_code_buy";
1216 $sql .= ", accountancy_code_buy_intra";
1217 $sql .= ", accountancy_code_buy_export";
1218 $sql .= ", accountancy_code_sell";
1219 $sql .= ", accountancy_code_sell_intra";
1220 $sql .= ", accountancy_code_sell_export";
1221 $sql .= ") VALUES (";
1222 $sql .= $this->id;
1223 $sql .= ", " . ((int) $conf->entity);
1224 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1225 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1226 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1227 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1228 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1229 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1230 $sql .= ")";
1231 $result = $this->db->query($sql);
1232 if (!$result) {
1233 $error++;
1234 $this->error = 'ErrorFailedToInsertAccountancyForEntity';
1235 }
1236 }
1237 } else {
1238 $error++;
1239 $this->error = 'ErrorFailedToGetInsertedId';
1240 }
1241 } else {
1242 $error++;
1243 $this->error = $this->db->lasterror();
1244 }
1245 } else {
1246 // Product already exists with this ref
1247 $langs->load("products");
1248 $error++;
1249 $this->error = "ErrorProductAlreadyExists";
1250 dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
1251 }
1252 } else {
1253 $error++;
1254 $this->error = $this->db->lasterror();
1255 }
1256
1257 if (!$error && !$notrigger) {
1258 // Call trigger
1259 $result = $this->call_trigger('PRODUCT_CREATE', $user);
1260 if ($result < 0) {
1261 $error++;
1262 }
1263 // End call triggers
1264 }
1265
1266 if (!$error) {
1267 $this->db->commit();
1268 return $this->id;
1269 } else {
1270 $this->db->rollback();
1271 return -$error;
1272 }
1273 } else {
1274 $this->db->rollback();
1275 dol_syslog(get_class($this)."::Create fails verify ".implode(',', $this->errors), LOG_WARNING);
1276 return -3;
1277 }
1278 }
1279
1280
1287 public function verify()
1288 {
1289 global $langs;
1290
1291 $this->errors = array();
1292
1293 $result = 0;
1294 $this->ref = trim($this->ref);
1295
1296 if (!$this->ref) {
1297 $this->errors[] = 'ErrorBadRef';
1298 $result = -2;
1299 }
1300
1301 $arrayofnonnegativevalue = array('weight' => 'Weight', 'width' => 'Width', 'height' => 'Height', 'length' => 'Length', 'surface' => 'Surface', 'volume' => 'Volume');
1302 foreach ($arrayofnonnegativevalue as $key => $value) {
1303 if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
1304 $langs->loadLangs(array("main", "other"));
1305 $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
1306 $this->errors[] = $this->error;
1307 $result = -4;
1308 }
1309 }
1310
1311 $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
1312 if ($rescode) {
1313 if ($rescode == -1) {
1314 $this->errors[] = 'ErrorBadBarCodeSyntax';
1315 } elseif ($rescode == -2) {
1316 $this->errors[] = 'ErrorBarCodeRequired';
1317 } elseif ($rescode == -3) {
1318 // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
1319 $this->errors[] = 'ErrorBarCodeAlreadyUsed';
1320 }
1321
1322 $result = -3;
1323 }
1324
1325 return $result;
1326 }
1327
1328 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1339 public function check_barcode($valuetotest, $typefortest)
1340 {
1341 // phpcs:enable
1342 global $conf;
1343
1344 if (isModEnabled('barcode') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
1345 $module = strtolower(getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM'));
1346
1347 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1348 foreach ($dirsociete as $dirroot) {
1349 $res = dol_include_once($dirroot.$module.'.php');
1350 if ($res) {
1351 break;
1352 }
1353 }
1354
1355 $mod = new $module();
1356 '@phan-var-force ModeleNumRefBarCode $mod';
1357
1358 dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1359 $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1360 return $result;
1361 } else {
1362 return 0;
1363 }
1364 }
1365
1377 public function update($id, $user, $notrigger = 0, $action = 'update', $updatetype = false)
1378 {
1379 global $langs, $conf, $hookmanager;
1380
1381 $error = 0;
1382
1383 // Check parameters
1384 if (!$this->label) {
1385 $this->label = 'MISSING LABEL';
1386 }
1387
1388 // Clean parameters
1389 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1390 $this->ref = trim($this->ref);
1391 } else {
1392 $this->ref = dol_string_nospecial(trim($this->ref));
1393 }
1394 $this->label = trim($this->label);
1395 $this->description = trim($this->description);
1396 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1397 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1398 $this->net_measure = price2num($this->net_measure);
1399 $this->net_measure_units = (!is_numeric($this->net_measure_units) ? null : (int) $this->net_measure_units);
1400 $this->weight = price2num($this->weight);
1401 $this->weight_units = (!is_numeric($this->weight_units) ? null : (int) $this->weight_units);
1402 $this->length = price2num($this->length);
1403 $this->length_units = (!is_numeric($this->length_units) ? null : (int) $this->length_units);
1404 $this->width = price2num($this->width);
1405 $this->width_units = (!is_numeric($this->width_units) ? null : (int) $this->width_units);
1406 $this->height = price2num($this->height);
1407 $this->height_units = (!is_numeric($this->height_units) ? null : (int) $this->height_units);
1408 $this->surface = price2num($this->surface);
1409 $this->surface_units = (!is_numeric($this->surface_units) ? null : (int) $this->surface_units);
1410 $this->volume = price2num($this->volume);
1411 $this->volume_units = (!is_numeric($this->volume_units) ? null : (int) $this->volume_units);
1412
1413 // set unit not defined
1414 if (is_numeric($this->length_units)) {
1415 $this->width_units = $this->length_units; // Not used yet
1416 }
1417 if (is_numeric($this->length_units)) {
1418 $this->height_units = $this->length_units; // Not used yet
1419 }
1420
1421 // Automated compute surface and volume if not filled
1422 if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1423 $this->surface = (float) $this->length * (float) $this->width;
1424 $this->surface_units = measuring_units_squared((int) $this->length_units);
1425 }
1426 if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1427 $this->volume = $this->surface * (float) $this->height;
1428 $this->volume_units = measuring_units_cubed((int) $this->height_units);
1429 }
1430
1431 if (empty($this->tva_tx)) {
1432 $this->tva_tx = 0;
1433 }
1434 if (empty($this->tva_npr)) {
1435 $this->tva_npr = 0;
1436 }
1437 if (empty($this->localtax1_tx)) {
1438 $this->localtax1_tx = 0;
1439 }
1440 if (empty($this->localtax2_tx)) {
1441 $this->localtax2_tx = 0;
1442 }
1443 if (empty($this->localtax1_type)) {
1444 $this->localtax1_type = '0';
1445 }
1446 if (empty($this->localtax2_type)) {
1447 $this->localtax2_type = '0';
1448 }
1449 if (empty($this->status)) {
1450 $this->status = 0;
1451 }
1452 if (empty($this->status_buy)) {
1453 $this->status_buy = 0;
1454 }
1455
1456 if (empty($this->country_id)) {
1457 $this->country_id = 0;
1458 }
1459 if (empty($this->country_id) && !empty($this->country_code)) {
1460 $country_id = getCountry($this->country_code, '3');
1461 $this->country_id = is_int($country_id) ? $country_id : 0;
1462 }
1463
1464 if (empty($this->state_id)) {
1465 $this->state_id = 0;
1466 }
1467
1468 if (empty($this->stockable_product)) {
1469 $this->stockable_product = 0;
1470 }
1471
1472 // Barcode value
1473 $this->barcode = (empty($this->barcode) ? '' : trim($this->barcode));
1474
1475 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1476 $this->accountancy_code_buy_intra = (!empty($this->accountancy_code_buy_intra) ? trim($this->accountancy_code_buy_intra) : '');
1477 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1478 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1479 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1480 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1481
1482 $this->db->begin();
1483
1484 $result = 0;
1485 // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1486 if ($action != 'add') {
1487 $result = $this->verify(); // We don't check when update called during a create because verify was already done
1488 } else {
1489 // we can continue
1490 $result = 0;
1491 }
1492
1493 if ($result >= 0) {
1494 // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1495 // Note that this->oldcopy must be object and not stdClass, if not the method hasbatch() will not work.
1496 if (is_null($this->oldcopy) || (is_object($this->oldcopy) && empty($this->oldcopy->id))) {
1497 $this->oldcopy = dol_clone($this, 1); // 1 to clone with methods to avoid fatal error with $this->oldcopy->hasbatch()
1498 }
1499 // Test if batch management is activated on existing product
1500 // If yes, we create missing entries into product_batch
1501 if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1502 //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1503 $valueforundefinedlot = '000000';
1504 if (getDolGlobalString('STOCK_DEFAULT_BATCH')) {
1505 $valueforundefinedlot = getDolGlobalString('STOCK_DEFAULT_BATCH');
1506 }
1507
1508 dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1509
1510 $this->load_stock();
1511 foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1512 $qty_batch = 0;
1513 foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1514 if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1515 // We discard this line, we will create it later
1516 $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1517 $result = $this->db->query($sqlclean);
1518 if (!$result) {
1519 dol_print_error($this->db);
1520 exit;
1521 }
1522 continue;
1523 }
1524
1525 $qty_batch += $detail->qty;
1526 }
1527 // Quantities in batch details are not same as stock quantity,
1528 // so we add a default batch record to complete and get same qty in parent and child table
1529 if ($ObjW->real != $qty_batch) {
1530 $ObjBatch = new Productbatch($this->db);
1531 $ObjBatch->batch = $valueforundefinedlot;
1532 $ObjBatch->qty = ($ObjW->real - $qty_batch);
1533 $ObjBatch->fk_product_stock = (int) $ObjW->id;
1534
1535 if ($ObjBatch->create($user, 1) < 0) {
1536 $error++;
1537 $this->errors = $ObjBatch->errors;
1538 } else {
1539 // we also add lot record if not exist
1540 $ObjLot = new Productlot($this->db);
1541 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
1542 if ($ObjLot->fetch(0, $this->id, $valueforundefinedlot) == 0) {
1543 $ObjLot->fk_product = $this->id;
1544 $ObjLot->entity = (int) $this->entity;
1545 $ObjLot->fk_user_creat = $user->id;
1546 $ObjLot->batch = $valueforundefinedlot;
1547 if ($ObjLot->create($user, 1) < 0) {
1548 $error++;
1549 $this->errors = $ObjLot->errors;
1550 }
1551 }
1552 }
1553 }
1554 }
1555 }
1556
1557 // For automatic creation
1558 if ($this->barcode == -1) {
1559 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1560 }
1561
1562 $sql = "UPDATE ".$this->db->prefix()."product";
1563 $sql .= " SET label = '".$this->db->escape($this->label)."'";
1564
1565 if ($updatetype && ($this->isProduct() || $this->isService())) {
1566 $sql .= ", fk_product_type = ".((int) $this->type);
1567 }
1568
1569 $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1570 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1571 $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1572 $sql .= ", tva_tx = ".((float) $this->tva_tx);
1573 $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1574 $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1575 $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1576 $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1577 $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1578
1579 $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1580 $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape((string) $this->barcode_type));
1581
1582 $sql .= ", tosell = ".(int) $this->status;
1583 $sql .= ", tobuy = ".(int) $this->status_buy;
1584 $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1585 $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);
1586 $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1587
1588 $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished === '') ? "null" : (int) $this->finished);
1589 $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1590 $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1591 $sql .= ", net_measure_units = ".((string) $this->net_measure_units != '' ? ((int) $this->net_measure_units) : 'null');
1592 $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1593 $sql .= ", weight_units = ".((string) $this->weight_units != '' ? ((int) $this->weight_units) : 'null');
1594 $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1595 $sql .= ", length_units = ".((string) $this->length_units != '' ? ((int) $this->length_units) : 'null');
1596 $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1597 $sql .= ", width_units = ".((string) $this->width_units != '' ? ((int) $this->width_units) : 'null');
1598 $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1599 $sql .= ", height_units = ".((string) $this->height_units != '' ? ((int) $this->height_units) : 'null');
1600 $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1601 $sql .= ", surface_units = ".((string) $this->surface_units != '' ? ((int) $this->surface_units) : 'null');
1602 $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1603 $sql .= ", volume_units = ".((string) $this->volume_units != '' ? ((int) $this->volume_units) : 'null');
1604 $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1605 $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1606 $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1607 $sql .= ", description = '".$this->db->escape($this->description)."'";
1608 $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1609 $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1610 $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1611 $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1612 $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1613 $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1614 $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1615 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1616 $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1617 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1618 $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1619 $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1620 $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1621 $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1622 $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1623 $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1624 }
1625 $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1626 $sql .= ", cost_price = ".($this->cost_price != '' ? ((float) $this->cost_price) : 'null');
1627 $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1628 $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1629 $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1630 $sql .= ", fk_user_modif = ".($user->id > 0 ? (int) $user->id : 'NULL');
1631 $sql .= ", mandatory_period = ".((int) $this->mandatory_period);
1632 $sql .= ", stockable_product = ".(int) $this->stockable_product;
1633 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
1634 $sql .= ", packaging = ".(float) $this->packaging;
1635 }
1636
1637 // stock field is not here because it is a denormalized value from product_stock.
1638 $sql .= " WHERE rowid = ".((int) $id);
1639
1640 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1641
1642 $resql = $this->db->query($sql);
1643 if ($resql) {
1644 $this->id = $id;
1645
1646 // Multilangs
1647 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1648 if ($this->setMultiLangs($user) < 0) {
1649 $this->db->rollback();
1650 return -2;
1651 }
1652 }
1653
1654 $action = 'update';
1655
1656 // update accountancy for this entity
1657 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1658 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1659
1660 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1661 $sql .= " fk_product";
1662 $sql .= ", entity";
1663 $sql .= ", accountancy_code_buy";
1664 $sql .= ", accountancy_code_buy_intra";
1665 $sql .= ", accountancy_code_buy_export";
1666 $sql .= ", accountancy_code_sell";
1667 $sql .= ", accountancy_code_sell_intra";
1668 $sql .= ", accountancy_code_sell_export";
1669 $sql .= ") VALUES (";
1670 $sql .= ((int) $this->id);
1671 $sql .= ", " . ((int) $conf->entity);
1672 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1673 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1674 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1675 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1676 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1677 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1678 $sql .= ")";
1679 $result = $this->db->query($sql);
1680 if (!$result) {
1681 $error++;
1682 $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1683 }
1684 }
1685
1686 if (!$this->hasbatch() && $this->oldcopy->hasbatch()) {
1687 // Selection of all product stock movements that contains batchs
1688 $sql = 'SELECT pb.qty, ps.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb';
1689 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = pb.fk_product_stock)';
1690 $sql .= ' WHERE ps.fk_product = '.(int) $this->id;
1691
1692 $resql = $this->db->query($sql);
1693 if ($resql) {
1694 $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S');
1695
1696 while ($obj = $this->db->fetch_object($resql)) {
1697 $value = $obj->qty;
1698 $fk_entrepot = $obj->fk_entrepot;
1699 $price = 0;
1700 $dlc = '';
1701 $dluo = '';
1702 $batch = $obj->batch;
1703
1704 // To know how to revert stockMouvement (add or remove)
1705 $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add
1706 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1707 $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true);
1708
1709 if ($res > 0) {
1710 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1711 $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0);
1712 if ($res < 0) {
1713 $error++;
1714 }
1715 } else {
1716 $error++;
1717 }
1718 }
1719 }
1720 }
1721
1722 // Actions on extra fields
1723 if (!$error) {
1724 $result = $this->insertExtraFields();
1725 if ($result < 0) {
1726 $error++;
1727 }
1728 }
1729
1730 if (!$error && !$notrigger) {
1731 // Call trigger
1732 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1733 if ($result < 0) {
1734 $error++;
1735 }
1736 // End call triggers
1737 }
1738
1739 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1740 // We remove directory
1741 if ($conf->product->dir_output) {
1742 $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1743 $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1744 if (file_exists($olddir)) {
1745 // include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1746 // $res = dol_move($olddir, $newdir);
1747 // do not use dol_move with directory
1748 $res = @rename($olddir, $newdir);
1749 if (!$res) {
1750 $langs->load("errors");
1751 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1752 $error++;
1753 } else {
1754 // to keep old entries with the new dir
1755 require_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
1756 $ecmfiles = new EcmFiles($this->db);
1757 $ecmfiles->updateAfterRename("produit/".dol_sanitizeFileName($this->oldcopy->ref), "produit/".dol_sanitizeFileName($this->ref));
1758 }
1759 }
1760 }
1761 }
1762
1763 if (!$error) {
1764 if (isModEnabled('variants')) {
1765 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1766
1767 $comb = new ProductCombination($this->db);
1768
1769 foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1770 $currcomb->updateProperties($this, $user);
1771 }
1772 }
1773
1774 $this->db->commit();
1775 return 1;
1776 } else {
1777 $this->db->rollback();
1778 return -$error;
1779 }
1780 } else {
1781 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1782 $langs->load("errors");
1783 if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1784 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1785 } else {
1786 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1787 }
1788 $this->errors[] = $this->error;
1789 $this->db->rollback();
1790 return -1;
1791 } else {
1792 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1793 $this->errors[] = $this->error;
1794 $this->db->rollback();
1795 return -2;
1796 }
1797 }
1798 } else {
1799 $this->db->rollback();
1800 dol_syslog(get_class($this)."::Update fails verify ".implode(',', $this->errors), LOG_WARNING);
1801 return -3;
1802 }
1803 }
1804
1812 public function delete(User $user, $notrigger = 0)
1813 {
1814 global $conf;
1815 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1816
1817 $error = 0;
1818
1819 // Check parameters
1820 if (empty($this->id)) {
1821 $this->error = "Object must be fetched before calling delete";
1822 return -1;
1823 }
1824 if (($this->isProduct() && !$user->hasRight('produit', 'supprimer')) || ($this->isService() && !$user->hasRight('service', 'supprimer'))) {
1825 $this->error = "ErrorForbidden";
1826 return 0;
1827 }
1828
1829 $objectisused = $this->isObjectUsed($this->id);
1830 if (empty($objectisused)) {
1831 $this->db->begin();
1832
1833 if (!$error && empty($notrigger)) {
1834 // Call trigger
1835 $result = $this->call_trigger('PRODUCT_DELETE', $user);
1836 if ($result < 0) {
1837 $error++;
1838 }
1839 // End call triggers
1840 }
1841
1842 // Delete from product_batch on product delete
1843 if (!$error) {
1844 $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1845 $sql .= " WHERE fk_product_stock IN (";
1846 $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1847 $sql .= " WHERE fk_product = ".((int) $this->id).")";
1848
1849 $result = $this->db->query($sql);
1850 if (!$result) {
1851 $error++;
1852 $this->errors[] = $this->db->lasterror();
1853 }
1854 }
1855
1856 // Delete all child tables
1857 if (!$error) {
1858 $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1859 foreach ($elements as $table) {
1860 if (!$error) {
1861 $sql = "DELETE FROM ".$this->db->prefix().$table;
1862 $sql .= " WHERE fk_product = ".(int) $this->id;
1863
1864 $result = $this->db->query($sql);
1865 if (!$result) {
1866 $error++;
1867 $this->errors[] = $this->db->lasterror();
1868 }
1869 }
1870 }
1871 }
1872
1873 if (!$error) {
1874 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1875 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1876
1877 //If it is a parent product, then we remove the association with child products
1878 $prodcomb = new ProductCombination($this->db);
1879
1880 if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1881 $error++;
1882 $this->errors[] = 'Error deleting combinations';
1883 }
1884
1885 //We also check if it is a child product
1886 if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1887 $error++;
1888 $this->errors[] = 'Error deleting child combination';
1889 }
1890 }
1891
1892 // Delete from product_association
1893 if (!$error) {
1894 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1895 $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1896
1897 $result = $this->db->query($sql);
1898 if (!$result) {
1899 $error++;
1900 $this->errors[] = $this->db->lasterror();
1901 }
1902 }
1903
1904 // Remove extrafields
1905 if (!$error) {
1906 $result = $this->deleteExtraFields();
1907 if ($result < 0) {
1908 $error++;
1909 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1910 }
1911 }
1912
1913 // Delete product
1914 if (!$error) {
1915 $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1916 $sqlz .= " WHERE rowid = ".(int) $this->id;
1917
1918 $resultz = $this->db->query($sqlz);
1919 if (!$resultz) {
1920 $error++;
1921 $this->errors[] = $this->db->lasterror();
1922 }
1923 }
1924
1925 // Delete record into ECM index and physically
1926 if (!$error) {
1927 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1928 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1929 if (!$res) {
1930 $error++;
1931 }
1932 }
1933
1934 if (!$error) {
1935 // We remove directory
1936 $ref = dol_sanitizeFileName($this->ref);
1937 if ($conf->product->dir_output) {
1938 $dir = $conf->product->dir_output."/".$ref;
1939 if (file_exists($dir)) {
1940 $res = @dol_delete_dir_recursive($dir);
1941 if (!$res) {
1942 $this->errors[] = 'ErrorFailToDeleteDir';
1943 $error++;
1944 }
1945 }
1946 }
1947 }
1948
1949 if (!$error) {
1950 $this->db->commit();
1951 return 1;
1952 } else {
1953 foreach ($this->errors as $errmsg) {
1954 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1955 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1956 }
1957 $this->db->rollback();
1958 return -$error;
1959 }
1960 } else {
1961 $this->error = "ErrorRecordIsUsedCantDelete";
1962 return 0;
1963 }
1964 }
1965
1971 public static function getSellOrEatByMandatoryList()
1972 {
1973 global $langs;
1974
1975 $sellByLabel = $langs->trans('SellByDate');
1976 $eatByLabel = $langs->trans('EatByDate');
1977 return array(
1978 self::SELL_OR_EAT_BY_MANDATORY_ID_NONE => $langs->trans('BatchSellOrEatByMandatoryNone'),
1979 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY => $sellByLabel,
1980 self::SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY => $eatByLabel,
1981 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT => $langs->trans('BatchSellOrEatByMandatoryAll', $sellByLabel, $eatByLabel),
1982 );
1983 }
1984
1991 {
1992 $sellOrEatByMandatoryLabel = '';
1993
1994 $sellOrEatByMandatoryList = self::getSellOrEatByMandatoryList();
1995 if (isset($sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory])) {
1996 $sellOrEatByMandatoryLabel = $sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory];
1997 }
1998
1999 return $sellOrEatByMandatoryLabel;
2000 }
2001
2008 public function setMultiLangs($user)
2009 {
2010 global $langs;
2011
2012 $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
2013 $current_lang = $langs->getDefaultLang();
2014
2015 foreach ($langs_available as $key => $value) {
2016 if ($key == $current_lang) {
2017 $sql = "SELECT rowid";
2018 $sql .= " FROM ".$this->db->prefix()."product_lang";
2019 $sql .= " WHERE fk_product = ".((int) $this->id);
2020 $sql .= " AND lang = '".$this->db->escape($key)."'";
2021
2022 $result = $this->db->query($sql);
2023
2024 if ($this->db->num_rows($result)) { // if there is already a description line for this language
2025 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
2026 $sql2 .= " SET ";
2027 $sql2 .= " label='".$this->db->escape($this->label)."',";
2028 $sql2 .= " description='".$this->db->escape($this->description)."'";
2029 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2030 $sql2 .= ", note='".$this->db->escape($this->other)."'";
2031 }
2032 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
2033 } else {
2034 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
2035 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2036 $sql2 .= ", note";
2037 }
2038 $sql2 .= ")";
2039 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
2040 $sql2 .= " '".$this->db->escape($this->description)."'";
2041 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2042 $sql2 .= ", '".$this->db->escape($this->other)."'";
2043 }
2044 $sql2 .= ")";
2045 }
2046 dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
2047 if (!$this->db->query($sql2)) {
2048 $this->error = $this->db->lasterror();
2049 return -1;
2050 }
2051 } elseif (isset($this->multilangs[$key])) {
2052 if (empty($this->multilangs[$key]["label"])) {
2053 $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
2054 return -1;
2055 }
2056
2057 $sql = "SELECT rowid";
2058 $sql .= " FROM ".$this->db->prefix()."product_lang";
2059 $sql .= " WHERE fk_product = ".((int) $this->id);
2060 $sql .= " AND lang = '".$this->db->escape($key)."'";
2061
2062 $result = $this->db->query($sql);
2063
2064 if ($this->db->num_rows($result)) { // if there is already a description line for this language
2065 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
2066 $sql2 .= " SET ";
2067 $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
2068 $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
2069 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2070 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2071 $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
2072 }
2073 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
2074 } else {
2075 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
2076 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2077 $sql2 .= ", note";
2078 }
2079 $sql2 .= ")";
2080 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
2081 $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
2082 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2083 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2084 $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
2085 }
2086 $sql2 .= ")";
2087 }
2088
2089 // We do not save if main fields are empty
2090 if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
2091 if (!$this->db->query($sql2)) {
2092 $this->error = $this->db->lasterror();
2093 return -1;
2094 }
2095 }
2096 } else {
2097 // language is not current language and we didn't provide a multilang description for this language
2098 }
2099 }
2100
2101 // Call trigger
2102 $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
2103 if ($result < 0) {
2104 $this->error = $this->db->lasterror();
2105 return -1;
2106 }
2107 // End call triggers
2108
2109 return 1;
2110 }
2111
2120 public function delMultiLangs($langtodelete, $user)
2121 {
2122 $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
2123 $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
2124
2125 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
2126 $result = $this->db->query($sql);
2127 if ($result) {
2128 // Call trigger
2129 $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
2130 if ($result < 0) {
2131 $this->error = $this->db->lasterror();
2132 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2133 return -1;
2134 }
2135 // End call triggers
2136 unset($this->multilangs[$langtodelete]);
2137 return 1;
2138 } else {
2139 $this->error = $this->db->lasterror();
2140 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2141 return -1;
2142 }
2143 }
2144
2153 public function setAccountancyCode($type, $value)
2154 {
2155 global $user;
2156
2157 $error = 0;
2158
2159 $this->db->begin();
2160
2161 if ($type == 'buy') {
2162 $field = 'accountancy_code_buy';
2163 } elseif ($type == 'buy_intra') {
2164 $field = 'accountancy_code_buy_intra';
2165 } elseif ($type == 'buy_export') {
2166 $field = 'accountancy_code_buy_export';
2167 } elseif ($type == 'sell') {
2168 $field = 'accountancy_code_sell';
2169 } elseif ($type == 'sell_intra') {
2170 $field = 'accountancy_code_sell_intra';
2171 } elseif ($type == 'sell_export') {
2172 $field = 'accountancy_code_sell_export';
2173 } else {
2174 return -1;
2175 }
2176
2177 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
2178 $sql .= "$field = '".$this->db->escape($value)."'";
2179 $sql .= " WHERE rowid = ".((int) $this->id);
2180
2181 dol_syslog(__METHOD__, LOG_DEBUG);
2182 $resql = $this->db->query($sql);
2183
2184 if ($resql) {
2185 // Call trigger
2186 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
2187 if ($result < 0) {
2188 $error++;
2189 }
2190 // End call triggers
2191
2192 if ($error) {
2193 $this->db->rollback();
2194 return -1;
2195 }
2196
2197 $this->$field = $value;
2198
2199 $this->db->commit();
2200 return 1;
2201 } else {
2202 $this->error = $this->db->lasterror();
2203 $this->db->rollback();
2204 return -1;
2205 }
2206 }
2207
2213 public function getMultiLangs()
2214 {
2215 global $langs;
2216
2217 $current_lang = $langs->getDefaultLang();
2218
2219 $sql = "SELECT lang, label, description, note as other";
2220 $sql .= " FROM ".$this->db->prefix()."product_lang";
2221 $sql .= " WHERE fk_product = ".((int) $this->id);
2222
2223 $result = $this->db->query($sql);
2224 if ($result) {
2225 while ($obj = $this->db->fetch_object($result)) {
2226 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
2227 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
2228 $this->label = $obj->label;
2229 $this->description = $obj->description;
2230 $this->other = $obj->other;
2231 }
2232 $this->multilangs[(string) $obj->lang]["label"] = $obj->label;
2233 $this->multilangs[(string) $obj->lang]["description"] = $obj->description;
2234 $this->multilangs[(string) $obj->lang]["other"] = $obj->other;
2235 }
2236 return 1;
2237 } else {
2238 $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
2239 return -1;
2240 }
2241 }
2242
2249 private function getArrayForPriceCompare($level = 0)
2250 {
2251 $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
2252
2253 foreach ($testExit as $field) {
2254 if (!isset($this->$field)) {
2255 return array();
2256 }
2257 $tmparray = $this->$field;
2258 if (!isset($tmparray[$level])) {
2259 return array();
2260 }
2261 }
2262
2263 $lastPrice = array(
2264 'level' => $level ? $level : 1,
2265 'multiprices' => (float) $this->multiprices[$level],
2266 'multiprices_ttc' => (float) $this->multiprices_ttc[$level],
2267 'multiprices_base_type' => $this->multiprices_base_type[$level],
2268 'multiprices_min' => (float) $this->multiprices_min[$level],
2269 'multiprices_min_ttc' => (float) $this->multiprices_min_ttc[$level],
2270 'multiprices_tva_tx' => (float) $this->multiprices_tva_tx[$level],
2271 'multiprices_recuperableonly' => (float) $this->multiprices_recuperableonly[$level],
2272 );
2273
2274 return $lastPrice;
2275 }
2276
2277
2278 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2286 private function _log_price($user, $level = 0)
2287 {
2288 // phpcs:enable
2289 global $conf;
2290
2291 $now = dol_now();
2292
2293 // Clean parameters
2294 if (empty($this->price_by_qty)) {
2295 $this->price_by_qty = 0;
2296 }
2297
2298 // Add new price
2299 $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,";
2300 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
2301 $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).",";
2302 $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');
2303 $sql .= ")";
2304
2305 dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
2306 $resql = $this->db->query($sql);
2307 if (!$resql) {
2308 $this->error = $this->db->lasterror();
2309 dol_print_error($this->db);
2310 return -1;
2311 } else {
2312 return 1;
2313 }
2314 }
2315
2316
2317 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2325 public function log_price_delete($user, $rowid)
2326 {
2327 // phpcs:enable
2328 $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
2329 $sql .= " WHERE fk_product_price = ".((int) $rowid);
2330 $resql = $this->db->query($sql);
2331
2332 $sql = "DELETE FROM ".$this->db->prefix()."product_price";
2333 $sql .= " WHERE rowid=".((int) $rowid);
2334 $resql = $this->db->query($sql);
2335 if ($resql) {
2336 return 1;
2337 } else {
2338 $this->error = $this->db->lasterror();
2339 return -1;
2340 }
2341 }
2342
2343
2353 public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
2354 {
2355 global $hookmanager, $action;
2356
2357 // Call hook if any
2358 if (is_object($hookmanager)) {
2359 $parameters = array('thirdparty_seller' => $thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
2360 // Note that $action and $object may have been modified by some hooks
2361 $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
2362 if ($reshook > 0) {
2363 return $hookmanager->resArray;
2364 }
2365 }
2366
2367 // Update if prices fields are defined
2368 $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
2369 $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
2370 if (empty($tva_tx)) {
2371 $tva_npr = 0;
2372 }
2373
2374 $pu_ht = $this->price;
2375 $pu_ttc = $this->price_ttc;
2376 $price_min = $this->price_min;
2377 $price_min_ttc = $this->price_min_ttc;
2378 $price_base_type = $this->price_base_type;
2379
2380 // if price by customer / level
2381 if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
2382 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2383
2384 $prodcustprice = new ProductCustomerPrice($this->db);
2385
2386 $filter = array('t.fk_product' => (string) $this->id, 't.fk_soc' => (string) $thirdparty_buyer->id);
2387
2388 // If a price per customer exist
2389 $pricebycustomerexist = false;
2390 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2391 if ($result) {
2392 if (count($prodcustprice->lines) > 0) {
2393 $date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours
2394 foreach ($prodcustprice->lines as $k => $custprice_line) {
2395 if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {
2396 $pricebycustomerexist = true;
2397 $pu_ht = price($custprice_line->price);
2398 $price_min = price($custprice_line->price_min);
2399 $price_min_ttc = price($custprice_line->price_min_ttc);
2400 $pu_ttc = price($custprice_line->price_ttc);
2401 $price_base_type = $custprice_line->price_base_type;
2402 $tva_tx = $custprice_line->tva_tx;
2403 if ($custprice_line->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2404 $tva_tx .= ' (' . $custprice_line->default_vat_code . ')';
2405 }
2406 $tva_npr = $custprice_line->recuperableonly;
2407 if (empty($tva_tx)) {
2408 $tva_npr = 0;
2409 }
2410 break;
2411 }
2412 }
2413 }
2414 }
2415
2416 if (!$pricebycustomerexist && !empty($thirdparty_buyer->price_level)) {
2417 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2418 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2419 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2420 $price_min_ttc = $this->multiprices_min_ttc[$thirdparty_buyer->price_level];
2421 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2422 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) {
2423 // using this option is a bug. kept for backward compatibility
2424 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2425 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2426 }
2427 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2428 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2429 }
2430 if (empty($tva_tx)) {
2431 $tva_npr = 0;
2432 }
2433 }
2434 }
2435 } elseif (getDolGlobalString('PRODUIT_MULTIPRICES') && !empty($thirdparty_buyer->price_level)) { // // If price per segment
2436 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2437 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2438 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2439 $price_min_ttc = $this->multiprices_min_ttc[$thirdparty_buyer->price_level];
2440 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2441 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) { // using this option is a bug. kept for backward compatibility
2442 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2443 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2444 }
2445 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2446 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2447 }
2448 if (empty($tva_tx)) {
2449 $tva_npr = 0;
2450 }
2451 }
2452 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
2453 // If price per customer
2454 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2455
2456 $prodcustprice = new ProductCustomerPrice($this->db);
2457
2458 $filter = array('t.fk_product' => (string) $this->id, 't.fk_soc' => (string) $thirdparty_buyer->id);
2459
2460 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2461 if ($result) {
2462 if (count($prodcustprice->lines) > 0) {
2463 $date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours
2464 foreach ($prodcustprice->lines as $k => $custprice_line) {
2465 if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {
2466 $pu_ht = price($custprice_line->price);
2467 $price_min = price($custprice_line->price_min);
2468 $price_min_ttc = price($custprice_line->price_min_ttc);
2469 $pu_ttc = price($custprice_line->price_ttc);
2470 $price_base_type = $custprice_line->price_base_type;
2471 $tva_tx = $custprice_line->tva_tx;
2472 if ($custprice_line->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2473 $tva_tx .= ' (' . $custprice_line->default_vat_code . ')';
2474 }
2475 $tva_npr = $custprice_line->recuperableonly;
2476 if (empty($tva_tx)) {
2477 $tva_npr = 0;
2478 }
2479 break;
2480 }
2481 }
2482 }
2483 }
2484 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
2485 // If price per quantity
2486 if ($this->prices_by_qty[0]) {
2487 // yes, this product has some prices per quantity
2488 // Search price into product_price_by_qty from $this->id
2489 foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
2490 if ($priceforthequantityarray['rowid'] != $pqp) {
2491 continue;
2492 }
2493 // We found the price
2494 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2495 $pu_ht = $priceforthequantityarray['unitprice'];
2496 } else {
2497 $pu_ttc = $priceforthequantityarray['unitprice'];
2498 }
2499 break;
2500 }
2501 }
2502 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2503 // If price per quantity and customer
2504 if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
2505 // yes, this product has some prices per quantity
2506 // Search price into product_price_by_qty from $this->id
2507 foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
2508 if ($priceforthequantityarray['rowid'] != $pqp) {
2509 continue;
2510 }
2511 // We found the price
2512 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2513 $pu_ht = $priceforthequantityarray['unitprice'];
2514 } else {
2515 $pu_ttc = $priceforthequantityarray['unitprice'];
2516 }
2517 break;
2518 }
2519 }
2520 }
2521
2522 return array('pu_ht' => $pu_ht, 'pu_ttc' => $pu_ttc, 'price_min' => $price_min, 'price_min_ttc' => $price_min_ttc, 'price_base_type' => $price_base_type, 'tva_tx' => $tva_tx, 'tva_npr' => $tva_npr);
2523 }
2524
2525 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2539 public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2540 {
2541 // phpcs:enable
2542 global $action, $hookmanager;
2543
2544 // Call hook if any
2545 if (is_object($hookmanager)) {
2546 $parameters = array(
2547 'prodfournprice' => $prodfournprice,
2548 'qty' => $qty,
2549 'product_id' => $product_id,
2550 'fourn_ref' => $fourn_ref,
2551 'fk_soc' => $fk_soc,
2552 );
2553 // Note that $action and $object may have been modified by some hooks
2554 $reshook = $hookmanager->executeHooks('getBuyPrice', $parameters, $this, $action);
2555 if ($reshook > 0) {
2556 return $hookmanager->resArray;
2557 }
2558 }
2559
2560 $result = 0;
2561
2562 // 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)
2563 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2564 $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,";
2565 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2566 $sql .= " pfp.packaging";
2567 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2568 $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2569 if ($qty > 0) {
2570 $sql .= " AND pfp.quantity <= ".((float) $qty);
2571 }
2572 $sql .= " ORDER BY pfp.quantity DESC";
2573
2574 dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", 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 we found a supplier prices from the id of supplier price
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 ($price_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_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2597 $this->fourn_price_base_type = 'HT'; // Price base type
2598 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2599 $this->ref_fourn = $obj->ref_supplier; // deprecated
2600 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2601 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2602 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2603 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2604 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2605 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2606 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2607 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2608 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2609 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2610 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2611 $this->packaging = (float) $obj->packaging;
2612 }
2613 $result = $obj->fk_product;
2614 return $result;
2615 } else { // If not found
2616 // 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.
2617 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2618 $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,";
2619 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2620 $sql .= " pfp.packaging";
2621 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2622 $sql .= " WHERE 1 = 1";
2623 if ($product_id > 0) {
2624 $sql .= " AND pfp.fk_product = ".((int) $product_id);
2625 }
2626 if ($fourn_ref != 'none') {
2627 $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2628 }
2629 if ($fk_soc > 0) {
2630 $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2631 }
2632 if ($qty > 0) {
2633 $sql .= " AND pfp.quantity <= ".((float) $qty);
2634 }
2635 $sql .= " ORDER BY pfp.quantity DESC";
2636 $sql .= " LIMIT 1";
2637
2638 dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2639 $resql = $this->db->query($sql);
2640 if ($resql) {
2641 $obj = $this->db->fetch_object($resql);
2642 if ($obj && $obj->quantity > 0) { // If found
2643 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2644 $prod_supplier = new ProductFournisseur($this->db);
2645 $prod_supplier->product_fourn_price_id = $obj->rowid;
2646 $prod_supplier->id = $obj->fk_product;
2647 $prod_supplier->fourn_qty = $obj->quantity;
2648 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2649 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2650
2651 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2652 $priceparser = new PriceParser($this->db);
2653 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2654 if ($price_result >= 0) {
2655 $obj->price = $price_result;
2656 }
2657 }
2658 $this->product_fourn_price_id = $obj->rowid;
2659 $this->buyprice = $obj->price; // deprecated
2660 $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2661 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2662 $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2663 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2664 $this->ref_fourn = $obj->ref_supplier; // deprecated
2665 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2666 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2667 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2668 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2669 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2670 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2671 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2672 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2673 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2674 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2675 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2676 $this->packaging = (float) $obj->packaging;
2677 }
2678 $result = $obj->fk_product;
2679 return $result;
2680 } else {
2681 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é.
2682 }
2683 } else {
2684 $this->error = $this->db->lasterror();
2685 return -3;
2686 }
2687 }
2688 } else {
2689 $this->error = $this->db->lasterror();
2690 return -2;
2691 }
2692 }
2693
2694
2713 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)
2714 {
2715 $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2716
2717 $id = $this->id;
2718
2719 dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2720
2721 // Clean parameters
2722 if (empty($this->tva_tx)) {
2723 $this->tva_tx = 0.0;
2724 }
2725 if (empty($newnpr)) {
2726 $newnpr = 0.0;
2727 }
2728 if (empty($newminprice)) {
2729 $newminprice = 0.0;
2730 }
2731
2732 // Check parameters
2733 if ($newvat === null || $newvat == '') { // Maintain '' for backwards compatibility
2734 $newvat = (float) $this->tva_tx;
2735 }
2736
2737 $localtaxtype1 = '';
2738 $localtaxtype2 = '';
2739
2740 // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2741 // Price will be modified ONLY when the first one is the one that is being modified
2742 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2743 return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2744 }
2745
2746 if (!empty($newminprice) && ($newminprice > $newprice)) {
2747 $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2748 return -1;
2749 }
2750
2751 if ($newprice === 0 || $newprice !== '') {
2752 if ($newpricebase == 'TTC') {
2753 $price_ttc = (float) price2num($newprice, 'MU');
2754 $price = (float) price2num($newprice) / (1 + ((float) $newvat / 100));
2755 $price = (float) price2num($price, 'MU');
2756
2757 if ((string) $newminprice != '0') {
2758 $price_min_ttc = (float) price2num($newminprice, 'MU');
2759 $price_min = (float) price2num($newminprice) / (1 + ($newvat / 100));
2760 $price_min = (float) price2num($price_min, 'MU');
2761 } else {
2762 $price_min = 0.0;
2763 $price_min_ttc = 0.0;
2764 }
2765 } else {
2766 $price = (float) price2num($newprice, 'MU');
2767 $price_ttc = ($newnpr != 1) ? (float) price2num($newprice) * (1 + ($newvat / 100)) : $price;
2768 $price_ttc = (float) price2num($price_ttc, 'MU');
2769
2770 if ((string) $newminprice != '0') {
2771 $price_min = (float) price2num($newminprice, 'MU');
2772 $price_min_ttc = (float) price2num($newminprice) * (1 + ($newvat / 100));
2773 $price_min_ttc = (float) price2num($price_min_ttc, 'MU');
2774 //print 'X'.$newminprice.'-'.$price_min;
2775 } else {
2776 $price_min = 0.0;
2777 $price_min_ttc = 0.0;
2778 }
2779 }
2780 //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2781 if (count($localtaxes_array) > 0) {
2782 $localtaxtype1 = $localtaxes_array['0'];
2783 $localtax1 = $localtaxes_array['1'];
2784 $localtaxtype2 = $localtaxes_array['2'];
2785 $localtax2 = $localtaxes_array['3'];
2786 } else {
2787 // if array empty, we try to use the vat code
2788 if (!empty($newdefaultvatcode)) {
2789 global $mysoc;
2790 // Get record from code
2791 $sql = "SELECT t.rowid, t.code, t.recuperableonly as tva_npr, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2792 $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2793 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2794 $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2795 $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2796 $resql = $this->db->query($sql);
2797 if ($resql) {
2798 $obj = $this->db->fetch_object($resql);
2799 if ($obj) {
2800 $npr = $obj->tva_npr;
2801 $localtax1 = $obj->localtax1;
2802 $localtax2 = $obj->localtax2;
2803 $localtaxtype1 = $obj->localtax1_type;
2804 $localtaxtype2 = $obj->localtax2_type;
2805 }
2806 }
2807 } else {
2808 // old method. deprecated because we can't retrieve type
2809 $localtaxtype1 = '0';
2810 $localtax1 = get_localtax($newvat, 1);
2811 $localtaxtype2 = '0';
2812 $localtax2 = get_localtax($newvat, 2);
2813 }
2814 }
2815 if (empty($localtax1)) {
2816 $localtax1 = 0; // If = '' then = 0
2817 }
2818 if (empty($localtax2)) {
2819 $localtax2 = 0; // If = '' then = 0
2820 }
2821
2822 $this->db->begin();
2823
2824 // Ne pas mettre de quote sur les numeriques decimaux.
2825 // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2826 $sql = "UPDATE ".$this->db->prefix()."product SET";
2827 $sql .= " price_base_type = '".$this->db->escape($newpricebase)."',";
2828 $sql .= " price = ".(float) $price.",";
2829 $sql .= " price_ttc = ".(float) $price_ttc.",";
2830 $sql .= " price_min = ".(float) $price_min.",";
2831 $sql .= " price_min_ttc = ".(float) $price_min_ttc.",";
2832 $sql .= " localtax1_tx = ".($localtax1 >= 0 ? (float) $localtax1 : 'NULL').",";
2833 $sql .= " localtax2_tx = ".($localtax2 >= 0 ? (float) $localtax2 : 'NULL').",";
2834 $sql .= " localtax1_type = ".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2835 $sql .= " localtax2_type = ".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2836 $sql .= " default_vat_code = ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2837 $sql .= " price_label = ".(!empty($price_label) ? "'".$this->db->escape($price_label)."'" : "null").",";
2838 $sql .= " tva_tx = ".(float) price2num($newvat).",";
2839 $sql .= " recuperableonly = '".$this->db->escape((string) $newnpr)."'";
2840 $sql .= " WHERE rowid = ".((int) $id);
2841
2842 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2843 $resql = $this->db->query($sql);
2844 if ($resql) {
2845 $this->multiprices[$level] = $price;
2846 $this->multiprices_ttc[$level] = $price_ttc;
2847 $this->multiprices_min[$level] = $price_min;
2848 $this->multiprices_min_ttc[$level] = $price_min_ttc;
2849 $this->multiprices_base_type[$level] = $newpricebase;
2850 $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2851 $this->multiprices_tva_tx[$level] = $newvat;
2852 $this->multiprices_recuperableonly[$level] = $newnpr;
2853
2854 $this->price = $price;
2855 $this->price_label = $price_label;
2856 $this->price_ttc = $price_ttc;
2857 $this->price_min = $price_min;
2858 $this->price_min_ttc = $price_min_ttc;
2859 $this->price_base_type = $newpricebase;
2860 $this->default_vat_code = $newdefaultvatcode;
2861 $this->tva_tx = $newvat;
2862 $this->tva_npr = $newnpr;
2863
2864 //Local taxes
2865 $this->localtax1_tx = $localtax1;
2866 $this->localtax2_tx = $localtax2;
2867 $this->localtax1_type = $localtaxtype1;
2868 $this->localtax2_type = $localtaxtype2;
2869
2870 // Price by quantity
2871 $this->price_by_qty = $newpbq;
2872
2873 // check if price have really change before log
2874 $newPriceData = $this->getArrayForPriceCompare($level);
2875 if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || !getDolGlobalString('PRODUIT_MULTIPRICES')) {
2876 $this->_log_price($user, $level); // Save price for level into table product_price
2877 }
2878
2879 $this->level = $level; // Store level of price edited for trigger
2880
2881 // Call trigger
2882 if (!$notrigger) {
2883 $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2884 if ($result < 0) {
2885 $this->db->rollback();
2886 return -1;
2887 }
2888 }
2889 // End call triggers
2890
2891 $this->db->commit();
2892 } else {
2893 $this->db->rollback();
2894 $this->error = $this->db->lasterror();
2895 return -1;
2896 }
2897 }
2898
2899 return 1;
2900 }
2901
2909 public function setPriceExpression($expression_id)
2910 {
2911 global $user;
2912
2913 $this->fk_price_expression = $expression_id;
2914
2915 return $this->update($this->id, $user);
2916 }
2917
2930 public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2931 {
2932 include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2933
2934 global $conf;
2935
2936 dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2937
2938 // Check parameters
2939 if (!$id && !$ref && !$ref_ext && !$barcode) {
2940 $this->error = 'ErrorWrongParameters';
2941 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2942 return -1;
2943 }
2944
2945 $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,";
2946 $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,";
2947 $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,";
2948 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.last_main_doc,";
2949 $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, p.packaging,";
2950 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2951 $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,";
2952 } else {
2953 $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,";
2954 }
2955
2956 // For MultiCompany
2957 // PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
2958 $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
2959 $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.
2960 $visibleWarehousesEntities = $conf->entity;
2961 if (getDolGlobalString('MULTICOMPANY_PRODUCT_SHARING_ENABLED')) {
2962 if (getDolGlobalString('MULTICOMPANY_PMP_PER_ENTITY_ENABLED')) {
2963 $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
2964 if ($this->db->num_rows($checkPMPPerEntity) > 0) {
2965 $separatedEntityPMP = true;
2966 }
2967 }
2968 global $mc;
2969 $separatedStock = true;
2970 if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
2971 $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
2972 }
2973 }
2974 if ($separatedEntityPMP) {
2975 $sql .= " ppe.pmp,";
2976 } else {
2977 $sql .= " p.pmp,";
2978 }
2979 $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,";
2980 $sql .= " p.fk_price_expression, p.price_autogen, p.stockable_product, p.model_pdf,";
2981 $sql .= " p.price_label,";
2982 if ($separatedStock) {
2983 $sql .= " SUM(sp.reel) as stock";
2984 } else {
2985 $sql .= " p.stock";
2986 }
2987 $sql .= " FROM ".$this->db->prefix()."product as p";
2988 if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') || $separatedEntityPMP) {
2989 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
2990 }
2991 if ($separatedStock) {
2992 $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)."))";
2993 }
2994
2995 if ($id) {
2996 $sql .= " WHERE p.rowid = ".((int) $id);
2997 } else {
2998 $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
2999 if ($ref) {
3000 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
3001 } elseif ($ref_ext) {
3002 $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
3003 } elseif ($barcode) {
3004 $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
3005 }
3006 }
3007 if ($separatedStock) {
3008 $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,";
3009 $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,";
3010 $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,";
3011 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
3012 $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, p.packaging,";
3013 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
3014 $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,";
3015 } else {
3016 $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,";
3017 }
3018 if ($separatedEntityPMP) {
3019 $sql .= " ppe.pmp,";
3020 } else {
3021 $sql .= " p.pmp,";
3022 }
3023 $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,";
3024 $sql .= " p.fk_price_expression, p.price_autogen, p.stockable_product, p.model_pdf,";
3025 $sql .= " p.price_label";
3026 if (!$separatedStock) {
3027 $sql .= ", p.stock";
3028 }
3029 }
3030
3031 $resql = $this->db->query($sql);
3032 if ($resql) {
3033 unset($this->oldcopy);
3034
3035 if ($this->db->num_rows($resql) > 0) {
3036 $obj = $this->db->fetch_object($resql);
3037
3038 $this->id = $obj->rowid;
3039 $this->ref = $obj->ref;
3040 $this->ref_ext = $obj->ref_ext;
3041 $this->label = $obj->label;
3042 $this->description = $obj->description;
3043 $this->url = $obj->url;
3044 $this->note_public = $obj->note_public;
3045 $this->note_private = $obj->note_private;
3046 $this->note = $obj->note_private; // deprecated
3047
3048 $this->type = $obj->fk_product_type;
3049 $this->price_label = $obj->price_label;
3050 $this->status = $obj->tosell;
3051 $this->status_buy = $obj->tobuy;
3052 $this->status_batch = $obj->tobatch;
3053 $this->sell_or_eat_by_mandatory = $obj->sell_or_eat_by_mandatory;
3054 $this->batch_mask = $obj->batch_mask;
3055
3056 $this->customcode = $obj->customcode;
3057 $this->country_id = $obj->fk_country;
3058 $this->country_code = getCountry($this->country_id, '2', $this->db);
3059 $this->state_id = $obj->fk_state;
3060 $this->lifetime = $obj->lifetime;
3061 $this->qc_frequency = $obj->qc_frequency;
3062 $this->price = $obj->price;
3063 $this->price_ttc = $obj->price_ttc;
3064 $this->price_min = $obj->price_min;
3065 $this->price_min_ttc = $obj->price_min_ttc;
3066 $this->price_base_type = $obj->price_base_type;
3067 $this->cost_price = isset($obj->cost_price) ? (float) $obj->cost_price : null;
3068 $this->default_vat_code = $obj->default_vat_code;
3069 $this->tva_tx = $obj->tva_tx;
3071 $this->tva_npr = $obj->tva_npr;
3073 $this->localtax1_tx = $obj->localtax1_tx;
3074 $this->localtax2_tx = $obj->localtax2_tx;
3075 $this->localtax1_type = $obj->localtax1_type;
3076 $this->localtax2_type = $obj->localtax2_type;
3077
3078 $this->finished = $obj->finished;
3079 $this->fk_default_bom = $obj->fk_default_bom;
3080
3081 $this->duration = $obj->duration;
3082 $matches = [];
3083 preg_match('/([\d.]+)(\w+)/', $obj->duration, $matches);
3084 $this->duration_value = !empty($matches[1]) ? (float) $matches[1] : 0;
3085 $this->duration_unit = !empty($matches[2]) ? (string) $matches[2] : null;
3086 $this->canvas = $obj->canvas;
3087 $this->net_measure = $obj->net_measure;
3088 $this->net_measure_units = $obj->net_measure_units;
3089 $this->weight = $obj->weight;
3090 $this->weight_units = (is_null($obj->weight_units) ? 0 : $obj->weight_units);
3091 $this->length = $obj->length;
3092 $this->length_units = (is_null($obj->length_units) ? 0 : $obj->length_units);
3093 $this->width = $obj->width;
3094 $this->width_units = (is_null($obj->width_units) ? 0 : $obj->width_units);
3095 $this->height = $obj->height;
3096 $this->height_units = (is_null($obj->height_units) ? 0 : $obj->height_units);
3097
3098 $this->surface = $obj->surface;
3099 $this->surface_units = (is_null($obj->surface_units) ? 0 : $obj->surface_units);
3100 $this->volume = $obj->volume;
3101 $this->volume_units = (is_null($obj->volume_units) ? 0 : $obj->volume_units);
3102 $this->barcode = $obj->barcode;
3103 $this->barcode_type = $obj->fk_barcode_type;
3104
3105 $this->accountancy_code_buy = $obj->accountancy_code_buy;
3106 $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
3107 $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
3108 $this->accountancy_code_sell = $obj->accountancy_code_sell;
3109 $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
3110 $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
3111
3112 $this->fk_default_warehouse = $obj->fk_default_warehouse;
3113 $this->fk_default_workstation = $obj->fk_default_workstation;
3114 $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
3115 $this->desiredstock = $obj->desiredstock;
3116 $this->stock_reel = $obj->stock;
3117 $this->stockable_product = $obj->stockable_product;
3118 $this->pmp = $obj->pmp;
3119
3120 $this->date_creation = $this->db->jdate($obj->datec);
3121 $this->date_modification = $this->db->jdate($obj->tms);
3122
3123 $this->import_key = $obj->import_key;
3124 $this->entity = $obj->entity;
3125
3126 $this->ref_ext = $obj->ref_ext;
3127 $this->fk_price_expression = $obj->fk_price_expression;
3128 $this->fk_unit = $obj->fk_unit;
3129 $this->price_autogen = $obj->price_autogen;
3130 $this->model_pdf = $obj->model_pdf;
3131 $this->last_main_doc = $obj->last_main_doc;
3132
3133 $this->mandatory_period = $obj->mandatory_period;
3134
3135 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
3136 $this->packaging = (float) $obj->packaging;
3137 }
3138
3139 $this->db->free($resql);
3140
3141 // fetch optionals attributes and labels
3142 $this->fetch_optionals();
3143
3144 // Multilangs
3145 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
3146 $this->getMultiLangs();
3147 }
3148
3149 // Load multiprices array
3150 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($ignore_price_load)) { // prices per segment
3151 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
3152 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
3153 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3154 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
3155 $sql .= " ,price_label";
3156 $sql .= " FROM ".$this->db->prefix()."product_price";
3157 $sql .= " WHERE entity IN (".getEntity('productprice').")";
3158 $sql .= " AND price_level=".((int) $i);
3159 $sql .= " AND fk_product = ".((int) $this->id);
3160 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
3161 $sql .= " LIMIT 1"; // Only the first one
3162 $resql = $this->db->query($sql);
3163 if ($resql) {
3164 $result = $this->db->fetch_array($resql);
3165
3166 $this->multiprices[$i] = $result ? $result["price"] : null;
3167 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
3168 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
3169 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
3170 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
3171 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
3172 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].(!empty($result['default_vat_code']) ? ' ('.$result['default_vat_code'].')' : '') : null;
3173 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
3174
3175 // Price by quantity
3176 /*
3177 $this->prices_by_qty[$i]=$result["price_by_qty"];
3178 $this->prices_by_qty_id[$i]=$result["rowid"];
3179 // Récuperation de la liste des prix selon qty si flag positionné
3180 if ($this->prices_by_qty[$i] == 1)
3181 {
3182 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
3183 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
3184 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
3185 $sql.= " ORDER BY quantity ASC";
3186
3187 $resql = $this->db->query($sql);
3188 if ($resql)
3189 {
3190 $resultat=array();
3191 $ii=0;
3192 while ($result= $this->db->fetch_array($resql)) {
3193 $resultat[$ii]=array();
3194 $resultat[$ii]["rowid"]=$result["rowid"];
3195 $resultat[$ii]["price"]= $result["price"];
3196 $resultat[$ii]["unitprice"]= $result["unitprice"];
3197 $resultat[$ii]["quantity"]= $result["quantity"];
3198 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
3199 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
3200 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
3201 $ii++;
3202 }
3203 $this->prices_by_qty_list[$i]=$resultat;
3204 }
3205 else
3206 {
3207 dol_print_error($this->db);
3208 return -1;
3209 }
3210 }*/
3211 } else {
3212 $this->error = $this->db->lasterror;
3213 return -1;
3214 }
3215 }
3216 } elseif ((getDolGlobalString('PRODUIT_CUSTOMER_PRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($ignore_price_load)) { // prices per customers
3217 // Nothing loaded by default. List may be very long.
3218 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
3219 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3220 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
3221 $sql .= " FROM ".$this->db->prefix()."product_price";
3222 $sql .= " WHERE fk_product = ".((int) $this->id);
3223 $sql .= " ORDER BY date_price DESC, rowid DESC";
3224 $sql .= " LIMIT 1";
3225
3226 $resql = $this->db->query($sql);
3227 if ($resql) {
3228 $result = $this->db->fetch_array($resql);
3229
3230 if ($result) {
3231 // Price by quantity
3232 $this->prices_by_qty[0] = $result["price_by_qty"];
3233 $this->prices_by_qty_id[0] = $result["rowid"];
3234 // Récuperation de la liste des prix selon qty si flag positionné
3235 if ($this->prices_by_qty[0] == 1) {
3236 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
3237 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
3238 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
3239 $sql .= " ORDER BY quantity ASC";
3240
3241 $resql = $this->db->query($sql);
3242 if ($resql) {
3243 $resultat = array();
3244 $ii = 0;
3245 while ($result = $this->db->fetch_array($resql)) {
3246 $resultat[$ii] = array();
3247 $resultat[$ii]["rowid"] = $result["rowid"];
3248 $resultat[$ii]["price"] = $result["price"];
3249 $resultat[$ii]["unitprice"] = $result["unitprice"];
3250 $resultat[$ii]["quantity"] = $result["quantity"];
3251 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
3252 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
3253 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
3254 $ii++;
3255 }
3256 $this->prices_by_qty_list[0] = $resultat;
3257 } else {
3258 $this->error = $this->db->lasterror;
3259 return -1;
3260 }
3261 }
3262 }
3263 } else {
3264 $this->error = $this->db->lasterror;
3265 return -1;
3266 }
3267 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
3268 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
3269 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
3270 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3271 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
3272 $sql .= " FROM ".$this->db->prefix()."product_price";
3273 $sql .= " WHERE entity IN (".getEntity('productprice').")";
3274 $sql .= " AND price_level=".((int) $i);
3275 $sql .= " AND fk_product = ".((int) $this->id);
3276 $sql .= " ORDER BY date_price DESC, rowid DESC";
3277 $sql .= " LIMIT 1";
3278 $resql = $this->db->query($sql);
3279 if (!$resql) {
3280 $this->error = $this->db->lasterror;
3281 return -1;
3282 } else {
3283 $result = $this->db->fetch_array($resql);
3284 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
3285 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
3286 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
3287 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
3288 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
3289 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
3290 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
3291 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
3292
3293 // Price by quantity
3294 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
3295 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
3296 // Récuperation de la liste des prix selon qty si flag positionné
3297 if ($this->prices_by_qty[$i] == 1) {
3298 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
3299 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
3300 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
3301 $sql .= " ORDER BY quantity ASC";
3302
3303 $resql = $this->db->query($sql);
3304 if ($resql) {
3305 $resultat = array();
3306 $ii = 0;
3307 while ($result = $this->db->fetch_array($resql)) {
3308 $resultat[$ii] = array();
3309 $resultat[$ii]["rowid"] = $result["rowid"];
3310 $resultat[$ii]["price"] = $result["price"];
3311 $resultat[$ii]["unitprice"] = $result["unitprice"];
3312 $resultat[$ii]["quantity"] = $result["quantity"];
3313 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
3314 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
3315 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
3316 $ii++;
3317 }
3318 $this->prices_by_qty_list[$i] = $resultat;
3319 } else {
3320 $this->error = $this->db->lasterror;
3321 return -1;
3322 }
3323 }
3324 }
3325 }
3326 }
3327
3328 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
3329 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
3330 $priceparser = new PriceParser($this->db);
3331 $price_result = $priceparser->parseProduct($this);
3332 if ($price_result >= 0) {
3333 $this->price = $price_result;
3334 // Calculate the VAT
3335 $this->price_ttc = (float) price2num($this->price) * (1 + ($this->tva_tx / 100));
3336 $this->price_ttc = (float) price2num($this->price_ttc, 'MU');
3337 }
3338 }
3339
3340 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
3341 // Instead we just init the stock_warehouse array
3342 $this->stock_warehouse = array();
3343
3344 return 1;
3345 } else {
3346 return 0;
3347 }
3348 } else {
3349 $this->error = $this->db->lasterror();
3350 return -1;
3351 }
3352 }
3353
3354 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3361 public function load_stats_mo($socid = 0)
3362 {
3363 // phpcs:enable
3364 global $user, $hookmanager, $action;
3365
3366 $error = 0;
3367
3368 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
3369 $this->stats_mo['customers_'.$role] = 0;
3370 $this->stats_mo['nb_'.$role] = 0;
3371 $this->stats_mo['qty_'.$role] = 0;
3372
3373 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3374 $sql .= " SUM(mp.qty) as qty";
3375 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
3376 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
3377 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3378 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3379 }
3380 $sql .= " WHERE ";
3381 $sql .= " c.entity IN (".getEntity('mo').")";
3382
3383 $sql .= " AND mp.fk_product = ".((int) $this->id);
3384 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
3385 if ($socid > 0) {
3386 $sql .= " AND c.fk_soc = ".((int) $socid);
3387 }
3388
3389 $result = $this->db->query($sql);
3390 if ($result) {
3391 $obj = $this->db->fetch_object($result);
3392 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
3393 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
3394 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
3395 } else {
3396 $this->error = $this->db->error();
3397 $error++;
3398 }
3399 }
3400
3401 if (!empty($error)) {
3402 return -1;
3403 }
3404
3405 $parameters = array('socid' => $socid);
3406 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3407 if ($reshook > 0) {
3408 $this->stats_mo = $hookmanager->resArray['stats_mo'];
3409 }
3410
3411 return 1;
3412 }
3413
3414 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3421 public function load_stats_bom($socid = 0)
3422 {
3423 // phpcs:enable
3424 global $hookmanager, $action;
3425
3426 $error = 0;
3427
3428 $this->stats_bom['nb_toproduce'] = 0;
3429 $this->stats_bom['nb_toconsume'] = 0;
3430 $this->stats_bom['qty_toproduce'] = 0;
3431 $this->stats_bom['qty_toconsume'] = 0;
3432
3433 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
3434 $sql .= " SUM(b.qty) as qty_toproduce";
3435 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3436 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom = b.rowid";
3437 $sql .= " WHERE ";
3438 $sql .= " b.entity IN (".getEntity('bom').")";
3439 $sql .= " AND b.fk_product =".((int) $this->id);
3440 $sql .= " GROUP BY b.rowid";
3441
3442 $result = $this->db->query($sql);
3443 if ($result) {
3444 $obj = $this->db->fetch_object($result);
3445 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
3446 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
3447 } else {
3448 $this->error = $this->db->error();
3449 $error++;
3450 }
3451
3452 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
3453 $sql .= " SUM(bl.qty) as qty_toconsume";
3454 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3455 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
3456 $sql .= " WHERE ";
3457 $sql .= " b.entity IN (".getEntity('bom').")";
3458 $sql .= " AND bl.fk_product =".((int) $this->id);
3459
3460 $result = $this->db->query($sql);
3461 if ($result) {
3462 $obj = $this->db->fetch_object($result);
3463 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
3464 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
3465 } else {
3466 $this->error = $this->db->error();
3467 $error++;
3468 }
3469
3470 if (!empty($error)) {
3471 return -1;
3472 }
3473
3474 $parameters = array('socid' => $socid);
3475 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3476 if ($reshook > 0) {
3477 $this->stats_bom = $hookmanager->resArray['stats_bom'];
3478 }
3479
3480 return 1;
3481 }
3482
3483 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3490 public function load_stats_propale($socid = 0)
3491 {
3492 // phpcs:enable
3493 global $user, $hookmanager, $action;
3494
3495 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3496 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3497 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3498 $sql .= ", ".$this->db->prefix()."propal as p";
3499 $sql .= ", ".$this->db->prefix()."societe as s";
3500 $sql .= " WHERE p.rowid = pd.fk_propal";
3501 $sql .= " AND p.fk_soc = s.rowid";
3502 $sql .= " AND p.entity IN (".getEntity('propal').")";
3503 $sql .= " AND pd.fk_product = ".((int) $this->id);
3504 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3505 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $user->id);
3506 }
3507 //$sql.= " AND pr.fk_statut != 0";
3508 if ($socid > 0) {
3509 $sql .= " AND p.fk_soc = ".((int) $socid);
3510 }
3511
3512 $result = $this->db->query($sql);
3513 if ($result) {
3514 $obj = $this->db->fetch_object($result);
3515 $this->stats_propale['customers'] = $obj->nb_customers;
3516 $this->stats_propale['nb'] = $obj->nb;
3517 $this->stats_propale['rows'] = $obj->nb_rows;
3518 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3519
3520 // if it's a virtual product, maybe it is in proposal by extension
3521 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3522 $TFather = $this->getFather();
3523 if (is_array($TFather) && !empty($TFather)) {
3524 foreach ($TFather as &$fatherData) {
3525 $pFather = new Product($this->db);
3526 $pFather->id = $fatherData['id'];
3527 $qtyCoef = $fatherData['qty'];
3528
3529 if ($fatherData['incdec']) {
3530 $pFather->load_stats_propale($socid);
3531
3532 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3533 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3534 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3535 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3536 }
3537 }
3538 }
3539 }
3540
3541 $parameters = array('socid' => $socid);
3542 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3543 if ($reshook > 0) {
3544 $this->stats_propale = $hookmanager->resArray['stats_propale'];
3545 }
3546
3547 return 1;
3548 } else {
3549 $this->error = $this->db->error();
3550 return -1;
3551 }
3552 }
3553
3554
3555 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3562 public function load_stats_proposal_supplier($socid = 0)
3563 {
3564 // phpcs:enable
3565 global $user, $hookmanager, $action;
3566
3567 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3568 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3569 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3570 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3571 $sql .= ", ".$this->db->prefix()."societe as s";
3572 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3573 $sql .= " AND p.fk_soc = s.rowid";
3574 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3575 $sql .= " AND pd.fk_product = ".((int) $this->id);
3576 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3577 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $user->id);
3578 }
3579 //$sql.= " AND pr.fk_statut != 0";
3580 if ($socid > 0) {
3581 $sql .= " AND p.fk_soc = ".((int) $socid);
3582 }
3583
3584 $result = $this->db->query($sql);
3585 if ($result) {
3586 $obj = $this->db->fetch_object($result);
3587 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3588 $this->stats_proposal_supplier['nb'] = $obj->nb;
3589 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3590 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3591
3592 $parameters = array('socid' => $socid);
3593 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3594 if ($reshook > 0) {
3595 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3596 }
3597
3598 return 1;
3599 } else {
3600 $this->error = $this->db->error();
3601 return -1;
3602 }
3603 }
3604
3605
3606 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3615 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3616 {
3617 // phpcs:enable
3618 global $user, $hookmanager, $action;
3619
3620 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3621 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3622 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3623 $sql .= ", ".$this->db->prefix()."commande as c";
3624 $sql .= ", ".$this->db->prefix()."societe as s";
3625 $sql .= " WHERE c.rowid = cd.fk_commande";
3626 $sql .= " AND c.fk_soc = s.rowid";
3627 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3628 $sql .= " AND cd.fk_product = ".((int) $this->id);
3629 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3630 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3631 }
3632 if ($socid > 0) {
3633 $sql .= " AND c.fk_soc = ".((int) $socid);
3634 }
3635 if ($filtrestatut != '') {
3636 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3637 }
3638
3639 $result = $this->db->query($sql);
3640 if ($result) {
3641 $obj = $this->db->fetch_object($result);
3642 $this->stats_commande['customers'] = $obj->nb_customers;
3643 $this->stats_commande['nb'] = $obj->nb;
3644 $this->stats_commande['rows'] = $obj->nb_rows;
3645 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3646
3647 // if it's a virtual product, maybe it is in order by extension
3648 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3649 $TFather = $this->getFather();
3650 if (is_array($TFather) && !empty($TFather)) {
3651 foreach ($TFather as &$fatherData) {
3652 $pFather = new Product($this->db);
3653 $pFather->id = $fatherData['id'];
3654 $qtyCoef = $fatherData['qty'];
3655
3656 if ($fatherData['incdec']) {
3657 $pFather->load_stats_commande($socid, $filtrestatut);
3658
3659 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3660 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3661 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3662 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3663 }
3664 }
3665 }
3666 }
3667
3668 // If stock decrease is on invoice validation, the theoretical stock continue to
3669 // count the orders lines in theoretical stock when some are already removed by invoice validation.
3670 if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3671 if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3672 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3673 $adeduire = 0;
3674 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', '-1', '1')." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3675 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3676 $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'))";
3677 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3678 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3679
3680 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3681 $resql = $this->db->query($sql);
3682 if ($resql) {
3683 if ($this->db->num_rows($resql) > 0) {
3684 $obj = $this->db->fetch_object($resql);
3685 $adeduire += $obj->count;
3686 }
3687 }
3688
3689 $this->stats_commande['qty'] -= $adeduire;
3690 } else {
3691 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3692 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3693
3694 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3695 $adeduire = 0;
3696 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', '-1', '1')." * fd.qty) as count";
3697 $sql .= " FROM " . $this->db->prefix() . "facturedet as fd";
3698 $sql .= " JOIN " . $this->db->prefix() . "facture as f ON fd.fk_facture = f.rowid";
3699 $sql .= " JOIN " . $this->db->prefix() . "element_element as el ON el.fk_target = f.rowid AND el.targettype = 'facture' AND el.sourcetype = 'commande'";
3700 $sql .= " JOIN " . $this->db->prefix() . "commande as c ON el.fk_source = c.rowid";
3701 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3702 $sql .= " UNION ALL";
3703 $sql .= " SELECT sum(".$this->db->ifsql('f.type=2', '-1', '1')." * fd.qty) as count";
3704 $sql .= " FROM " . $this->db->prefix() . "facturedet as fd";
3705 $sql .= " JOIN " . $this->db->prefix() . "facture as f ON fd.fk_facture = f.rowid";
3706 $sql .= " JOIN " . $this->db->prefix() . "element_element as el ON el.fk_source = f.rowid AND el.targettype = 'commande' AND el.sourcetype = 'facture'";
3707 $sql .= " JOIN " . $this->db->prefix() . "commande as c ON el.fk_source = c.rowid";
3708 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product =".((int) $this->id);
3709 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3710 $resql = $this->db->query($sql);
3711 if ($resql) {
3712 if ($this->db->num_rows($resql) > 0) {
3713 $obj = $this->db->fetch_object($resql);
3714 $adeduire += $obj->count;
3715 }
3716 } else {
3717 $this->error = $this->db->error();
3718 return -1;
3719 }
3720
3721 $this->stats_commande['qty'] -= $adeduire;
3722 }
3723 }
3724
3725 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3726 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3727 if ($reshook > 0) {
3728 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3729 }
3730 return 1;
3731 } else {
3732 $this->error = $this->db->error();
3733 return -1;
3734 }
3735 }
3736
3737 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3747 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3748 {
3749 // phpcs:enable
3750 global $user, $hookmanager, $action;
3751
3752 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3753 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3754 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3755 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3756 $sql .= ", ".$this->db->prefix()."societe as s";
3757 $sql .= " WHERE c.rowid = cd.fk_commande";
3758 $sql .= " AND c.fk_soc = s.rowid";
3759 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3760 $sql .= " AND cd.fk_product = ".((int) $this->id);
3761 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3762 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3763 }
3764 if ($socid > 0) {
3765 $sql .= " AND c.fk_soc = ".((int) $socid);
3766 }
3767 if ($filtrestatut != '') {
3768 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3769 }
3770 if (!empty($dateofvirtualstock)) {
3771 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3772 }
3773
3774 $result = $this->db->query($sql);
3775 if ($result) {
3776 $obj = $this->db->fetch_object($result);
3777 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3778 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3779 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3780 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3781
3782 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3783 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3784 if ($reshook > 0) {
3785 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3786 }
3787
3788 return 1;
3789 } else {
3790 $this->error = $this->db->error().' sql='.$sql;
3791 return -1;
3792 }
3793 }
3794
3795 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3805 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3806 {
3807 // phpcs:enable
3808 global $user, $hookmanager, $action;
3809
3810 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3811 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3812 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3813 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3814 $sql .= ", ".$this->db->prefix()."commande as c";
3815 $sql .= ", ".$this->db->prefix()."expedition as e";
3816 $sql .= ", ".$this->db->prefix()."societe as s";
3817 $sql .= " WHERE e.rowid = ed.fk_expedition";
3818 $sql .= " AND c.rowid = cd.fk_commande";
3819 $sql .= " AND e.fk_soc = s.rowid";
3820 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3821 $sql .= " AND ed.fk_elementdet = cd.rowid";
3822 $sql .= " AND cd.fk_product = ".((int) $this->id);
3823 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3824 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = e.fk_soc AND sc.fk_user = ".((int) $user->id);
3825 }
3826 if ($socid > 0) {
3827 $sql .= " AND e.fk_soc = ".((int) $socid);
3828 }
3829 if ($filtrestatut != '') {
3830 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3831 }
3832 if (!empty($filterShipmentStatus)) {
3833 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3834 }
3835
3836 $result = $this->db->query($sql);
3837 if ($result) {
3838 $obj = $this->db->fetch_object($result);
3839 $this->stats_expedition['customers'] = $obj->nb_customers;
3840 $this->stats_expedition['nb'] = $obj->nb;
3841 $this->stats_expedition['rows'] = $obj->nb_rows;
3842 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3843
3844 // if it's a virtual product, maybe it is in sending by extension
3845 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3846 $TFather = $this->getFather();
3847 if (is_array($TFather) && !empty($TFather)) {
3848 foreach ($TFather as &$fatherData) {
3849 $pFather = new Product($this->db);
3850 $pFather->id = $fatherData['id'];
3851 $qtyCoef = $fatherData['qty'];
3852
3853 if ($fatherData['incdec']) {
3854 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3855
3856 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3857 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3858 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3859 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3860 }
3861 }
3862 }
3863 }
3864
3865 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3866 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3867 if ($reshook > 0) {
3868 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3869 }
3870
3871 return 1;
3872 } else {
3873 $this->error = $this->db->error();
3874 return -1;
3875 }
3876 }
3877
3878 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3888 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3889 {
3890 // phpcs:enable
3891 global $user, $hookmanager, $action;
3892
3893 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3894 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3895 $sql .= " FROM ".$this->db->prefix()."receptiondet_batch as fd";
3896 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3897 $sql .= ", ".$this->db->prefix()."societe as s";
3898 $sql .= " WHERE cf.rowid = fd.fk_element";
3899 $sql .= " AND cf.fk_soc = s.rowid";
3900 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3901 $sql .= " AND fd.fk_product = ".((int) $this->id);
3902 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3903 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = cf.fk_soc AND sc.fk_user = ".((int) $user->id);
3904 }
3905 if ($socid > 0) {
3906 $sql .= " AND cf.fk_soc = ".((int) $socid);
3907 }
3908 if ($filtrestatut != '') {
3909 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3910 }
3911 if (!empty($dateofvirtualstock)) {
3912 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3913 }
3914
3915 $result = $this->db->query($sql);
3916 if ($result) {
3917 $obj = $this->db->fetch_object($result);
3918 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3919 $this->stats_reception['nb'] = $obj->nb;
3920 $this->stats_reception['rows'] = $obj->nb_rows;
3921 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3922
3923 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3924 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3925 if ($reshook > 0) {
3926 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3927 }
3928
3929 return 1;
3930 } else {
3931 $this->error = $this->db->error();
3932 return -1;
3933 }
3934 }
3935
3936 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3947 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null, $warehouseid = 0)
3948 {
3949 // phpcs:enable
3950 global $user, $hookmanager, $action;
3951
3952 $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
3953
3954 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
3955 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
3956 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
3957 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
3958 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
3959 $sql .= " WHERE m.rowid = mp.fk_mo";
3960 $sql .= " AND m.entity IN (".getEntity(($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE')) ? 'stock' : 'mrp').")";
3961 $sql .= " AND mp.fk_product = ".((int) $this->id);
3962 $sql .= " AND (mp.disable_stock_change IN (0) OR mp.disable_stock_change IS NULL)";
3963 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3964 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = m.fk_soc AND sc.fk_user = ".((int) $user->id);
3965 }
3966 if ($socid > 0) {
3967 $sql .= " AND m.fk_soc = ".((int) $socid);
3968 }
3969 if ($filtrestatut != '') {
3970 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
3971 }
3972 if (!empty($dateofvirtualstock)) {
3973 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
3974 }
3975 if (!$serviceStockIsEnabled) {
3976 $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))";
3977 }
3978 if (!empty($warehouseid)) {
3979 $sql .= " AND m.fk_warehouse = ".((int) $warehouseid);
3980 }
3981 $sql .= " GROUP BY role";
3982
3983 if ($warehouseid) {
3984 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
3985 } else {
3986 $this->stats_mrptoconsume['customers'] = 0;
3987 $this->stats_mrptoconsume['nb'] = 0;
3988 $this->stats_mrptoconsume['rows'] = 0;
3989 $this->stats_mrptoconsume['qty'] = 0.0;
3990 $this->stats_mrptoproduce['customers'] = 0;
3991 $this->stats_mrptoproduce['nb'] = 0;
3992 $this->stats_mrptoproduce['rows'] = 0;
3993 $this->stats_mrptoproduce['qty'] = 0.0;
3994 }
3995
3996 $result = $this->db->query($sql);
3997 if ($result) {
3998 while ($obj = $this->db->fetch_object($result)) {
3999 if ($obj->role == 'toconsume' && empty($warehouseid)) {
4000 $this->stats_mrptoconsume['customers'] += (int) $obj->nb_customers;
4001 $this->stats_mrptoconsume['nb'] += (int) $obj->nb;
4002 $this->stats_mrptoconsume['rows'] += (int) $obj->nb_rows;
4003 $this->stats_mrptoconsume['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
4004 }
4005 if ($obj->role == 'consumed' && empty($warehouseid)) {
4006 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
4007 //$this->stats_mrptoconsume['nb'] += $obj->nb;
4008 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
4009 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? (float) $obj->qty : 0.0);
4010 }
4011 if ($obj->role == 'toproduce') {
4012 if ($warehouseid) {
4013 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
4014 } else {
4015 $this->stats_mrptoproduce['customers'] += (int) $obj->nb_customers;
4016 $this->stats_mrptoproduce['nb'] += (int) $obj->nb;
4017 $this->stats_mrptoproduce['rows'] += (int) $obj->nb_rows;
4018 $this->stats_mrptoproduce['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
4019 }
4020 }
4021 if ($obj->role == 'produced') {
4022 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
4023 //$this->stats_mrptoproduce['nb'] += $obj->nb;
4024 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
4025 if ($warehouseid) {
4026 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
4027 } else {
4028 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
4029 }
4030 }
4031 }
4032
4033 // Clean data
4034 if ($warehouseid) {
4035 if ($this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] < 0) {
4036 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
4037 }
4038 } else {
4039 if ($this->stats_mrptoconsume['qty'] < 0) {
4040 $this->stats_mrptoconsume['qty'] = 0;
4041 }
4042 if ($this->stats_mrptoproduce['qty'] < 0) {
4043 $this->stats_mrptoproduce['qty'] = 0;
4044 }
4045 }
4046
4047 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
4048 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
4049 if ($reshook > 0) {
4050 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
4051 }
4052
4053 return 1;
4054 } else {
4055 $this->error = $this->db->error();
4056 return -1;
4057 }
4058 }
4059
4060 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4067 public function load_stats_contrat($socid = 0)
4068 {
4069 // phpcs:enable
4070 global $user, $hookmanager, $action;
4071
4072 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
4073 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
4074 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
4075 $sql .= ", ".$this->db->prefix()."contrat as c";
4076 $sql .= ", ".$this->db->prefix()."societe as s";
4077 $sql .= " WHERE c.rowid = cd.fk_contrat";
4078 $sql .= " AND c.fk_soc = s.rowid";
4079 $sql .= " AND c.entity IN (".getEntity('contract').")";
4080 $sql .= " AND cd.fk_product = ".((int) $this->id);
4081 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4082 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
4083 }
4084 //$sql.= " AND c.statut != 0";
4085 if ($socid > 0) {
4086 $sql .= " AND c.fk_soc = ".((int) $socid);
4087 }
4088
4089 $result = $this->db->query($sql);
4090 if ($result) {
4091 $obj = $this->db->fetch_object($result);
4092 $this->stats_contrat['customers'] = $obj->nb_customers;
4093 $this->stats_contrat['nb'] = $obj->nb;
4094 $this->stats_contrat['rows'] = $obj->nb_rows;
4095 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
4096
4097 // if it's a virtual product, maybe it is in contract by extension
4098 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4099 $TFather = $this->getFather();
4100 if (is_array($TFather) && !empty($TFather)) {
4101 foreach ($TFather as &$fatherData) {
4102 $pFather = new Product($this->db);
4103 $pFather->id = $fatherData['id'];
4104 $qtyCoef = $fatherData['qty'];
4105
4106 if ($fatherData['incdec']) {
4107 $pFather->load_stats_contrat($socid);
4108
4109 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
4110 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
4111 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
4112 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
4113 }
4114 }
4115 }
4116 }
4117
4118 $parameters = array('socid' => $socid);
4119 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
4120 if ($reshook > 0) {
4121 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
4122 }
4123
4124 return 1;
4125 } else {
4126 $this->error = $this->db->error().' sql='.$sql;
4127 return -1;
4128 }
4129 }
4130
4131 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4138 public function load_stats_facture($socid = 0)
4139 {
4140 // phpcs:enable
4141 global $user, $hookmanager, $action;
4142
4143 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
4144 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
4145 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
4146 $sql .= ", ".$this->db->prefix()."facture as f";
4147 $sql .= ", ".$this->db->prefix()."societe as s";
4148 $sql .= " WHERE f.rowid = fd.fk_facture";
4149 $sql .= " AND f.fk_soc = s.rowid";
4150 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4151 $sql .= " AND fd.fk_product = ".((int) $this->id);
4152 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4153 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4154 }
4155 //$sql.= " AND f.fk_statut != 0";
4156 if ($socid > 0) {
4157 $sql .= " AND f.fk_soc = ".((int) $socid);
4158 }
4159
4160 $result = $this->db->query($sql);
4161 if ($result) {
4162 $obj = $this->db->fetch_object($result);
4163 $this->stats_facture['customers'] = $obj->nb_customers;
4164 $this->stats_facture['nb'] = $obj->nb;
4165 $this->stats_facture['rows'] = $obj->nb_rows;
4166 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
4167
4168 // if it's a virtual product, maybe it is in invoice by extension
4169 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4170 $TFather = $this->getFather();
4171 if (is_array($TFather) && !empty($TFather)) {
4172 foreach ($TFather as &$fatherData) {
4173 $pFather = new Product($this->db);
4174 $pFather->id = $fatherData['id'];
4175 $qtyCoef = $fatherData['qty'];
4176
4177 if ($fatherData['incdec']) {
4178 $pFather->load_stats_facture($socid);
4179
4180 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
4181 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
4182 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
4183 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
4184 }
4185 }
4186 }
4187 }
4188
4189 $parameters = array('socid' => $socid);
4190 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
4191 if ($reshook > 0) {
4192 $this->stats_facture = $hookmanager->resArray['stats_facture'];
4193 }
4194
4195 return 1;
4196 } else {
4197 $this->error = $this->db->error();
4198 return -1;
4199 }
4200 }
4201
4202
4203 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4210 public function load_stats_facturerec($socid = 0)
4211 {
4212 // phpcs:enable
4213 global $user, $hookmanager, $action;
4214
4215 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
4216 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4217 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
4218 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
4219 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
4220 $sql .= " WHERE f.rowid = fd.fk_facture";
4221 $sql .= " AND f.fk_soc = s.rowid";
4222 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4223 $sql .= " AND fd.fk_product = ".((int) $this->id);
4224 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4225 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4226 }
4227 //$sql.= " AND f.fk_statut != 0";
4228 if ($socid > 0) {
4229 $sql .= " AND f.fk_soc = ".((int) $socid);
4230 }
4231
4232 $result = $this->db->query($sql);
4233 if ($result) {
4234 $obj = $this->db->fetch_object($result);
4235 $this->stats_facturerec['customers'] = (int) $obj->nb_customers;
4236 $this->stats_facturerec['nb'] = (int) $obj->nb;
4237 $this->stats_facturerec['rows'] = (int) $obj->nb_rows;
4238 $this->stats_facturerec['qty'] = $obj->qty ? (float) $obj->qty : 0.0;
4239
4240 // if it's a virtual product, maybe it is in invoice by extension
4241 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4242 $TFather = $this->getFather();
4243 if (is_array($TFather) && !empty($TFather)) {
4244 foreach ($TFather as &$fatherData) {
4245 $pFather = new Product($this->db);
4246 $pFather->id = $fatherData['id'];
4247 $qtyCoef = $fatherData['qty'];
4248
4249 if ($fatherData['incdec']) {
4250 $pFather->load_stats_facture($socid);
4251
4252 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
4253 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
4254 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
4255 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
4256 }
4257 }
4258 }
4259 }
4260
4261 $parameters = array('socid' => $socid);
4262 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
4263 if ($reshook > 0) {
4264 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
4265 }
4266
4267 return 1;
4268 } else {
4269 $this->error = $this->db->error();
4270 return -1;
4271 }
4272 }
4273
4274 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4281 public function load_stats_facture_fournisseur($socid = 0)
4282 {
4283 // phpcs:enable
4284 global $user, $hookmanager, $action;
4285
4286 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
4287 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4288 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
4289 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
4290 $sql .= ", ".$this->db->prefix()."societe as s";
4291 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
4292 $sql .= " AND f.fk_soc = s.rowid";
4293 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4294 $sql .= " AND fd.fk_product = ".((int) $this->id);
4295 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4296 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4297 }
4298 //$sql.= " AND f.fk_statut != 0";
4299 if ($socid > 0) {
4300 $sql .= " AND f.fk_soc = ".((int) $socid);
4301 }
4302
4303 $result = $this->db->query($sql);
4304 if ($result) {
4305 $obj = $this->db->fetch_object($result);
4306 $this->stats_facture_fournisseur['suppliers'] = (int) $obj->nb_suppliers;
4307 $this->stats_facture_fournisseur['nb'] = (int) $obj->nb;
4308 $this->stats_facture_fournisseur['rows'] = (int) $obj->nb_rows;
4309 $this->stats_facture_fournisseur['qty'] = $obj->qty ? (float) $obj->qty : 0.0;
4310
4311 $parameters = array('socid' => $socid);
4312 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
4313 if ($reshook > 0) {
4314 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
4315 }
4316
4317 return 1;
4318 } else {
4319 $this->error = $this->db->error();
4320 return -1;
4321 }
4322 }
4323
4324 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4331 public function load_stats_facturefournrec($socid = 0)
4332 {
4333 // phpcs:enable
4334 global $user, $hookmanager, $action;
4335
4336 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
4337 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4338 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn_det_rec as fd";
4339 $sql .= ", ".MAIN_DB_PREFIX."facture_fourn_rec as f";
4340 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
4341 if (!$user->hasRight('societe', 'client', 'voir')) {
4342 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
4343 }
4344 $sql .= " WHERE f.rowid = fd.fk_facture";
4345 $sql .= " AND f.fk_soc = s.rowid";
4346 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4347 $sql .= " AND fd.fk_product = ".((int) $this->id);
4348 if (!$user->hasRight('societe', 'client', 'voir')) {
4349 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4350 }
4351 //$sql.= " AND f.fk_statut != 0";
4352 if ($socid > 0) {
4353 $sql .= " AND f.fk_soc = ".((int) $socid);
4354 }
4355
4356 $result = $this->db->query($sql);
4357 if ($result) {
4358 $obj = $this->db->fetch_object($result);
4359 $this->stats_facturefournrec['suppliers'] = (int) $obj->nb_suppliers;
4360 $this->stats_facturefournrec['nb'] = (int) $obj->nb;
4361 $this->stats_facturefournrec['rows'] = (int) $obj->nb_rows;
4362 $this->stats_facturefournrec['qty'] = $obj->qty ? (float) $obj->qty : 0.0;
4363
4364 $parameters = array('socid' => $socid);
4365 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoiceRec', $parameters, $this, $action);
4366 if ($reshook > 0) {
4367 $this->stats_facturefournrec = $hookmanager->resArray['stats_facturefournrec'];
4368 }
4369
4370 return 1;
4371 } else {
4372 $this->error = $this->db->error();
4373 return -1;
4374 }
4375 }
4376
4377 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4386 private function _get_stats($sql, $mode, $year = 0)
4387 {
4388 // phpcs:enable
4389 $tab = array();
4390
4391 $resql = $this->db->query($sql);
4392 if ($resql) {
4393 $num = $this->db->num_rows($resql);
4394 $i = 0;
4395 while ($i < $num) {
4396 $arr = $this->db->fetch_array($resql);
4397 if (is_array($arr)) {
4398 $keyfortab = (string) $arr[1];
4399 if ($year == -1) {
4400 $keyfortab = substr($keyfortab, -2);
4401 }
4402
4403 if ($mode == 'byunit') {
4404 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
4405 } elseif ($mode == 'bynumber') {
4406 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
4407 } elseif ($mode == 'byamount') {
4408 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
4409 } else {
4410 // Bad value for $mode
4411 return -1;
4412 }
4413 }
4414 $i++;
4415 }
4416 } else {
4417 $this->error = $this->db->error().' sql='.$sql;
4418 return -1;
4419 }
4420
4421 if (empty($year)) {
4422 $year = dol_print_date(time(), '%Y');
4423 $month = dol_print_date(time(), '%m');
4424 } elseif ($year == -1) {
4425 $year = '';
4426 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4427 } else {
4428 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4429 }
4430
4431 $result = array();
4432
4433 for ($j = 0; $j < 12; $j++) {
4434 // $ids is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
4435 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
4436
4437 //print $idx.'-'.$year.'-'.$month.'<br>';
4438 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
4439 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
4440
4441 $month = "0".($month - 1);
4442 if (dol_strlen($month) == 3) {
4443 $month = substr($month, 1);
4444 }
4445 if ($month == 0) {
4446 $month = 12;
4447 $year -= 1;
4448 }
4449 }
4450
4451 return array_reverse($result);
4452 }
4453
4454
4455 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4466 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4467 {
4468 // phpcs:enable
4469 global $user;
4470
4471 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4472 if ($mode == 'bynumber') {
4473 $sql .= ", count(DISTINCT f.rowid)";
4474 }
4475 $sql .= ", sum(d.total_ht) as total_ht";
4476 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
4477 if ($filteronproducttype >= 0) {
4478 $sql .= ", ".$this->db->prefix()."product as p";
4479 }
4480 if (!$user->hasRight('societe', 'client', 'voir')) {
4481 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4482 }
4483 $sql .= " WHERE f.rowid = d.fk_facture";
4484 if ($this->id > 0) {
4485 $sql .= " AND d.fk_product = ".((int) $this->id);
4486 } else {
4487 $sql .= " AND d.fk_product > 0";
4488 }
4489 if ($filteronproducttype >= 0) {
4490 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4491 }
4492 $sql .= " AND f.fk_soc = s.rowid";
4493 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4494 if (!$user->hasRight('societe', 'client', 'voir')) {
4495 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4496 }
4497 if ($socid > 0) {
4498 $sql .= " AND f.fk_soc = $socid";
4499 }
4500 $sql .= $morefilter;
4501 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4502 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4503
4504 return $this->_get_stats($sql, $mode, $year);
4505 }
4506
4507
4508 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4519 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4520 {
4521 // phpcs:enable
4522 global $user;
4523
4524 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4525 if ($mode == 'bynumber') {
4526 $sql .= ", count(DISTINCT f.rowid)";
4527 }
4528 $sql .= ", sum(d.total_ht) as total_ht";
4529 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
4530 if ($filteronproducttype >= 0) {
4531 $sql .= ", ".$this->db->prefix()."product as p";
4532 }
4533 if (!$user->hasRight('societe', 'client', 'voir')) {
4534 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4535 }
4536 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4537 if ($this->id > 0) {
4538 $sql .= " AND d.fk_product = ".((int) $this->id);
4539 } else {
4540 $sql .= " AND d.fk_product > 0";
4541 }
4542 if ($filteronproducttype >= 0) {
4543 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4544 }
4545 $sql .= " AND f.fk_soc = s.rowid";
4546 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4547 if (!$user->hasRight('societe', 'client', 'voir')) {
4548 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4549 }
4550 if ($socid > 0) {
4551 $sql .= " AND f.fk_soc = $socid";
4552 }
4553 $sql .= $morefilter;
4554 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4555 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4556
4557 return $this->_get_stats($sql, $mode, $year);
4558 }
4559
4560 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4571 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4572 {
4573 // phpcs:enable
4574 global $user;
4575
4576 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4577 if ($mode == 'bynumber') {
4578 $sql .= ", count(DISTINCT p.rowid)";
4579 }
4580 $sql .= ", sum(d.total_ht) as total_ht";
4581 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4582 if ($filteronproducttype >= 0) {
4583 $sql .= ", ".$this->db->prefix()."product as prod";
4584 }
4585 if (!$user->hasRight('societe', 'client', 'voir')) {
4586 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4587 }
4588 $sql .= " WHERE p.rowid = d.fk_propal";
4589 if ($this->id > 0) {
4590 $sql .= " AND d.fk_product = ".((int) $this->id);
4591 } else {
4592 $sql .= " AND d.fk_product > 0";
4593 }
4594 if ($filteronproducttype >= 0) {
4595 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4596 }
4597 $sql .= " AND p.fk_soc = s.rowid";
4598 $sql .= " AND p.entity IN (".getEntity('propal').")";
4599 if (!$user->hasRight('societe', 'client', 'voir')) {
4600 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4601 }
4602 if ($socid > 0) {
4603 $sql .= " AND p.fk_soc = ".((int) $socid);
4604 }
4605 $sql .= $morefilter;
4606 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4607 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4608
4609 return $this->_get_stats($sql, $mode, $year);
4610 }
4611
4612 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4623 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4624 {
4625 // phpcs:enable
4626 global $user;
4627
4628 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4629 if ($mode == 'bynumber') {
4630 $sql .= ", count(DISTINCT p.rowid)";
4631 }
4632 $sql .= ", sum(d.total_ht) as total_ht";
4633 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4634 if ($filteronproducttype >= 0) {
4635 $sql .= ", ".$this->db->prefix()."product as prod";
4636 }
4637 if (!$user->hasRight('societe', 'client', 'voir')) {
4638 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4639 }
4640 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4641 if ($this->id > 0) {
4642 $sql .= " AND d.fk_product = ".((int) $this->id);
4643 } else {
4644 $sql .= " AND d.fk_product > 0";
4645 }
4646 if ($filteronproducttype >= 0) {
4647 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4648 }
4649 $sql .= " AND p.fk_soc = s.rowid";
4650 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4651 if (!$user->hasRight('societe', 'client', 'voir')) {
4652 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4653 }
4654 if ($socid > 0) {
4655 $sql .= " AND p.fk_soc = ".((int) $socid);
4656 }
4657 $sql .= $morefilter;
4658 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4659 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4660
4661 return $this->_get_stats($sql, $mode, $year);
4662 }
4663
4664 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4675 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4676 {
4677 // phpcs:enable
4678 global $user;
4679
4680 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4681 if ($mode == 'bynumber') {
4682 $sql .= ", count(DISTINCT c.rowid)";
4683 }
4684 $sql .= ", sum(d.total_ht) as total_ht";
4685 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4686 if ($filteronproducttype >= 0) {
4687 $sql .= ", ".$this->db->prefix()."product as p";
4688 }
4689 if (!$user->hasRight('societe', 'client', 'voir')) {
4690 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4691 }
4692 $sql .= " WHERE c.rowid = d.fk_commande";
4693 if ($this->id > 0) {
4694 $sql .= " AND d.fk_product = ".((int) $this->id);
4695 } else {
4696 $sql .= " AND d.fk_product > 0";
4697 }
4698 if ($filteronproducttype >= 0) {
4699 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4700 }
4701 $sql .= " AND c.fk_soc = s.rowid";
4702 $sql .= " AND c.entity IN (".getEntity('commande').")";
4703 if (!$user->hasRight('societe', 'client', 'voir')) {
4704 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4705 }
4706 if ($socid > 0) {
4707 $sql .= " AND c.fk_soc = ".((int) $socid);
4708 }
4709 $sql .= $morefilter;
4710 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4711 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4712
4713 return $this->_get_stats($sql, $mode, $year);
4714 }
4715
4716 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4727 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4728 {
4729 // phpcs:enable
4730 global $user;
4731
4732 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4733 if ($mode == 'bynumber') {
4734 $sql .= ", count(DISTINCT c.rowid)";
4735 }
4736 $sql .= ", sum(d.total_ht) as total_ht";
4737 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4738 if ($filteronproducttype >= 0) {
4739 $sql .= ", ".$this->db->prefix()."product as p";
4740 }
4741 if (!$user->hasRight('societe', 'client', 'voir')) {
4742 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4743 }
4744 $sql .= " WHERE c.rowid = d.fk_commande";
4745 if ($this->id > 0) {
4746 $sql .= " AND d.fk_product = ".((int) $this->id);
4747 } else {
4748 $sql .= " AND d.fk_product > 0";
4749 }
4750 if ($filteronproducttype >= 0) {
4751 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4752 }
4753 $sql .= " AND c.fk_soc = s.rowid";
4754 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4755 if (!$user->hasRight('societe', 'client', 'voir')) {
4756 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4757 }
4758 if ($socid > 0) {
4759 $sql .= " AND c.fk_soc = ".((int) $socid);
4760 }
4761 $sql .= $morefilter;
4762 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4763 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4764
4765 return $this->_get_stats($sql, $mode, $year);
4766 }
4767
4768 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4779 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4780 {
4781 // phpcs:enable
4782 global $user;
4783
4784 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4785 if ($mode == 'bynumber') {
4786 $sql .= ", count(DISTINCT c.rowid)";
4787 }
4788 $sql .= ", sum(d.total_ht) as total_ht";
4789 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4790 if ($filteronproducttype >= 0) {
4791 $sql .= ", ".$this->db->prefix()."product as p";
4792 }
4793 if (!$user->hasRight('societe', 'client', 'voir')) {
4794 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4795 }
4796 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4797 $sql .= " AND c.rowid = d.fk_contrat";
4798
4799 if ($this->id > 0) {
4800 $sql .= " AND d.fk_product = ".((int) $this->id);
4801 } else {
4802 $sql .= " AND d.fk_product > 0";
4803 }
4804 if ($filteronproducttype >= 0) {
4805 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4806 }
4807 $sql .= " AND c.fk_soc = s.rowid";
4808
4809 if (!$user->hasRight('societe', 'client', 'voir')) {
4810 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4811 }
4812 if ($socid > 0) {
4813 $sql .= " AND c.fk_soc = ".((int) $socid);
4814 }
4815 $sql .= $morefilter;
4816 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4817 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4818
4819 return $this->_get_stats($sql, $mode, $year);
4820 }
4821
4822 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4833 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4834 {
4835 // phpcs:enable
4836 global $user;
4837
4838 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4839 if ($mode == 'bynumber') {
4840 $sql .= ", count(DISTINCT d.rowid)";
4841 }
4842 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4843 if ($filteronproducttype >= 0) {
4844 $sql .= ", ".$this->db->prefix()."product as p";
4845 }
4846 if (!$user->hasRight('societe', 'client', 'voir')) {
4847 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4848 }
4849
4850 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4851 $sql .= " AND d.status > 0";
4852
4853 if ($this->id > 0) {
4854 $sql .= " AND d.fk_product = ".((int) $this->id);
4855 } else {
4856 $sql .= " AND d.fk_product > 0";
4857 }
4858 if ($filteronproducttype >= 0) {
4859 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4860 }
4861
4862 if (!$user->hasRight('societe', 'client', 'voir')) {
4863 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4864 }
4865 if ($socid > 0) {
4866 $sql .= " AND d.fk_soc = ".((int) $socid);
4867 }
4868 $sql .= $morefilter;
4869 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4870 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4871
4872 return $this->_get_stats($sql, $mode, $year);
4873 }
4874
4875 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4886 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4887 {
4888 global $user;
4889
4890 // phpcs:enable
4891 // Clean parameters
4892 if (!is_numeric($id_pere)) {
4893 $id_pere = 0;
4894 }
4895 if (!is_numeric($id_fils)) {
4896 $id_fils = 0;
4897 }
4898 if (!is_numeric($incdec)) {
4899 $incdec = 0;
4900 }
4901
4902 $result = $this->del_sousproduit($id_pere, $id_fils);
4903 if ($result < 0) {
4904 return $result;
4905 }
4906
4907 // Check not already father of id_pere (to avoid father -> child -> father links)
4908 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4909 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4910 if (!$this->db->query($sql)) {
4911 dol_print_error($this->db);
4912 return -1;
4913 } else {
4914 //Selection of the highest row
4915 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4916 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4917 $resql = $this->db->query($sql);
4918 if ($resql) {
4919 $obj = $this->db->fetch_object($resql);
4920 $rank = $obj->max_rank + 1;
4921 //Addition of a product with the highest rank +1
4922 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
4923 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".((int) $incdec).", ".((int) $rank).")";
4924 if (! $this->db->query($sql)) {
4925 dol_print_error($this->db);
4926 return -1;
4927 } else {
4928 if (!$notrigger) {
4929 // Call trigger
4930 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
4931 if ($result < 0) {
4932 $this->error = $this->db->lasterror();
4933 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
4934 return -1;
4935 }
4936 }
4937 // End call triggers
4938
4939 return 1;
4940 }
4941 } else {
4942 dol_print_error($this->db);
4943 return -1;
4944 }
4945 }
4946 }
4947
4948 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4959 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4960 {
4961 global $user;
4962
4963 // phpcs:enable
4964 // Clean parameters
4965 if (!is_numeric($id_pere)) {
4966 $id_pere = 0;
4967 }
4968 if (!is_numeric($id_fils)) {
4969 $id_fils = 0;
4970 }
4971 if (!is_numeric($incdec)) {
4972 $incdec = 1;
4973 }
4974 if (!is_numeric($qty)) {
4975 $qty = 1;
4976 }
4977
4978 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
4979 $sql .= 'qty = '.price2num($qty, 'MS');
4980 $sql .= ',incdec = '.((int) $incdec);
4981 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
4982
4983 if (!$this->db->query($sql)) {
4984 dol_print_error($this->db);
4985 return -1;
4986 } else {
4987 if (!$notrigger) {
4988 // Call trigger
4989 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
4990 if ($result < 0) {
4991 $this->error = $this->db->lasterror();
4992 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
4993 return -1;
4994 }
4995 // End call triggers
4996 }
4997
4998 return 1;
4999 }
5000 }
5001
5002 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5011 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
5012 {
5013 global $user;
5014
5015 // phpcs:enable
5016 if (!is_numeric($fk_parent)) {
5017 $fk_parent = 0;
5018 }
5019 if (!is_numeric($fk_child)) {
5020 $fk_child = 0;
5021 }
5022
5023 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
5024 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
5025 $sql .= " AND fk_product_fils = ".((int) $fk_child);
5026
5027 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
5028 if (!$this->db->query($sql)) {
5029 dol_print_error($this->db);
5030 return -1;
5031 }
5032
5033 // Updated ranks so that none are missing
5034 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
5035 $sqlrank .= " WHERE fk_product_pere = ".((int) $fk_parent);
5036 $sqlrank .= " ORDER BY rang";
5037 $resqlrank = $this->db->query($sqlrank);
5038 if ($resqlrank) {
5039 $cpt = 0;
5040 while ($objrank = $this->db->fetch_object($resqlrank)) {
5041 $cpt++;
5042 $sql = "UPDATE ".$this->db->prefix()."product_association";
5043 $sql .= " SET rang = ".((int) $cpt);
5044 $sql .= " WHERE rowid = ".((int) $objrank->rowid);
5045 if (! $this->db->query($sql)) {
5046 dol_print_error($this->db);
5047 return -1;
5048 }
5049 }
5050 }
5051
5052 if (!$notrigger) {
5053 // Call trigger
5054 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
5055 if ($result < 0) {
5056 $this->error = $this->db->lasterror();
5057 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
5058 return -1;
5059 }
5060 // End call triggers
5061 }
5062
5063 return 1;
5064 }
5065
5066 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5074 public function is_sousproduit($fk_parent, $fk_child)
5075 {
5076 // phpcs:enable
5077 $sql = "SELECT fk_product_pere, qty, incdec";
5078 $sql .= " FROM ".$this->db->prefix()."product_association";
5079 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
5080 $sql .= " AND fk_product_fils = ".((int) $fk_child);
5081
5082 $result = $this->db->query($sql);
5083 if ($result) {
5084 $num = $this->db->num_rows($result);
5085
5086 if ($num > 0) {
5087 $obj = $this->db->fetch_object($result);
5088
5089 $this->is_sousproduit_qty = $obj->qty;
5090 $this->is_sousproduit_incdec = $obj->incdec;
5091
5092 return 1;
5093 } else {
5094 return 0;
5095 }
5096 } else {
5097 dol_print_error($this->db);
5098 return -1;
5099 }
5100 }
5101
5102
5103 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5114 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
5115 {
5116 // phpcs:enable
5117 global $conf;
5118
5119 $now = dol_now();
5120
5121 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
5122
5123 // Clean parameters
5124 $quantity = price2num($quantity, 'MS');
5125
5126 if ($ref_fourn) {
5127 // Check if ref is not already used
5128 $sql = "SELECT rowid, fk_product";
5129 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5130 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5131 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5132 $sql .= " AND fk_product <> ".((int) $this->id);
5133 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5134
5135 $resql = $this->db->query($sql);
5136 if ($resql) {
5137 $obj = $this->db->fetch_object($resql);
5138 if ($obj) {
5139 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
5140 $this->product_id_already_linked = $obj->fk_product;
5141 return -3;
5142 }
5143 $this->db->free($resql);
5144 }
5145 }
5146
5147 $sql = "SELECT rowid";
5148 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5149 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5150 if ($ref_fourn) {
5151 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5152 } else {
5153 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
5154 }
5155 $sql .= " AND quantity = ".((float) $quantity);
5156 $sql .= " AND fk_product = ".((int) $this->id);
5157 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5158
5159 $resql = $this->db->query($sql);
5160 if ($resql) {
5161 $obj = $this->db->fetch_object($resql);
5162
5163 // The reference supplier does not exist, we create it for this product.
5164 if (empty($obj)) {
5165 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
5166 $sql .= "datec";
5167 $sql .= ", entity";
5168 $sql .= ", fk_product";
5169 $sql .= ", fk_soc";
5170 $sql .= ", ref_fourn";
5171 $sql .= ", quantity";
5172 $sql .= ", fk_user";
5173 $sql .= ", tva_tx";
5174 $sql .= ") VALUES (";
5175 $sql .= "'".$this->db->idate($now)."'";
5176 $sql .= ", ".((int) $conf->entity);
5177 $sql .= ", ".((int) $this->id);
5178 $sql .= ", ".((int) $id_fourn);
5179 $sql .= ", '".$this->db->escape($ref_fourn)."'";
5180 $sql .= ", ".((float) $quantity);
5181 $sql .= ", ".((int) $user->id);
5182 $sql .= ", 0";
5183 $sql .= ")";
5184
5185 if ($this->db->query($sql)) {
5186 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
5187 return 1;
5188 } else {
5189 $this->error = $this->db->lasterror();
5190 return -1;
5191 }
5192 } else {
5193 // If the supplier price already exists for this product and quantity
5194 $this->product_fourn_price_id = $obj->rowid;
5195 return 0;
5196 }
5197 } else {
5198 $this->error = $this->db->lasterror();
5199 return -2;
5200 }
5201 }
5202
5203
5204 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5210 public function list_suppliers()
5211 {
5212 // phpcs:enable
5213 global $conf;
5214
5215 $list = array();
5216
5217 $sql = "SELECT DISTINCT p.fk_soc";
5218 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
5219 $sql .= " WHERE p.fk_product = ".((int) $this->id);
5220 $sql .= " AND p.entity = ".((int) $conf->entity);
5221
5222 $result = $this->db->query($sql);
5223 if ($result) {
5224 $num = $this->db->num_rows($result);
5225 $i = 0;
5226 while ($i < $num) {
5227 $obj = $this->db->fetch_object($result);
5228 $list[$i] = $obj->fk_soc;
5229 $i++;
5230 }
5231 }
5232
5233 return $list;
5234 }
5235
5236 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5244 public function clone_price($fromId, $toId)
5245 {
5246 global $user;
5247
5248 $now = dol_now();
5249
5250 $this->db->begin();
5251
5252 // prices
5253 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
5254 $sql .= " entity";
5255 $sql .= ", fk_product";
5256 $sql .= ", date_price";
5257 $sql .= ", price_level";
5258 $sql .= ", price";
5259 $sql .= ", price_ttc";
5260 $sql .= ", price_min";
5261 $sql .= ", price_min_ttc";
5262 $sql .= ", price_base_type";
5263 $sql .= ", price_label";
5264 $sql .= ", default_vat_code";
5265 $sql .= ", tva_tx";
5266 $sql .= ", recuperableonly";
5267 $sql .= ", localtax1_tx";
5268 $sql .= ", localtax1_type";
5269 $sql .= ", localtax2_tx";
5270 $sql .= ", localtax2_type";
5271 $sql .= ", fk_user_author";
5272 $sql .= ", tosell";
5273 $sql .= ", price_by_qty";
5274 $sql .= ", fk_price_expression";
5275 $sql .= ", fk_multicurrency";
5276 $sql .= ", multicurrency_code";
5277 $sql .= ", multicurrency_tx";
5278 $sql .= ", multicurrency_price";
5279 $sql .= ", multicurrency_price_ttc";
5280 $sql .= ")";
5281 $sql .= " SELECT";
5282 $sql .= " entity";
5283 $sql .= ", ".((int) $toId);
5284 $sql .= ", '".$this->db->idate($now)."'";
5285 $sql .= ", price_level";
5286 $sql .= ", price";
5287 $sql .= ", price_ttc";
5288 $sql .= ", price_min";
5289 $sql .= ", price_min_ttc";
5290 $sql .= ", price_base_type";
5291 $sql .= ", price_label";
5292 $sql .= ", default_vat_code";
5293 $sql .= ", tva_tx";
5294 $sql .= ", recuperableonly";
5295 $sql .= ", localtax1_tx";
5296 $sql .= ", localtax1_type";
5297 $sql .= ", localtax2_tx";
5298 $sql .= ", localtax2_type";
5299 $sql .= ", ".((int) $user->id);
5300 $sql .= ", tosell";
5301 $sql .= ", price_by_qty";
5302 $sql .= ", fk_price_expression";
5303 $sql .= ", fk_multicurrency";
5304 $sql .= ", multicurrency_code";
5305 $sql .= ", multicurrency_tx";
5306 $sql .= ", multicurrency_price";
5307 $sql .= ", multicurrency_price_ttc";
5308 $sql .= " FROM ".$this->db->prefix()."product_price ps";
5309 $sql .= " WHERE fk_product = ".((int) $fromId);
5310 $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)";
5311 $sql .= " ORDER BY date_price DESC";
5312
5313 dol_syslog(__METHOD__, LOG_DEBUG);
5314 $resql = $this->db->query($sql);
5315 if (!$resql) {
5316 $this->db->rollback();
5317 return -1;
5318 }
5319
5320 $this->db->commit();
5321 return 1;
5322 }
5323
5324 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5332 public function clone_associations($fromId, $toId)
5333 {
5334 // phpcs:enable
5335 $this->db->begin();
5336
5337 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
5338 $sql .= " SELECT ".((int) $toId).", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
5339 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
5340
5341 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
5342 if (!$this->db->query($sql)) {
5343 $this->db->rollback();
5344 return -1;
5345 }
5346
5347 $this->db->commit();
5348 return 1;
5349 }
5350
5351 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5359 public function clone_fournisseurs($fromId, $toId)
5360 {
5361 // phpcs:enable
5362 $this->db->begin();
5363
5364 $now = dol_now();
5365
5366 // les fournisseurs
5367 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
5368 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
5369 . " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, ref_fourn, fk_user_author"
5370 . " FROM ".$this->db->prefix()."product_fournisseur"
5371 . " WHERE fk_product = ".((int) $fromId);
5372
5373 if ( ! $this->db->query($sql ) )
5374 {
5375 $this->db->rollback();
5376 return -1;
5377 }*/
5378
5379 // les prix de fournisseurs.
5380 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
5381 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
5382 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
5383 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5384 $sql .= " WHERE fk_product = ".((int) $fromId);
5385
5386 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
5387 $resql = $this->db->query($sql);
5388 if (!$resql) {
5389 $this->db->rollback();
5390 return -1;
5391 } else {
5392 $this->db->commit();
5393 return 1;
5394 }
5395 }
5396
5397 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5410 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
5411 {
5412 // phpcs:enable
5413 $tmpproduct = null;
5414
5415 if ($multiply < 1) {
5416 dol_syslog(get_class($this).'::'.__FUNCTION__.' Product quantity rounded up to 1 from '.$multiply.'. May result in unexpected end quantity for product children of '.$id_parent, LOG_WARNING);
5417 $multiply = 1;
5418 }
5419
5420 //var_dump($prod);
5421 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
5422 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
5423 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
5424 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
5425 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
5426 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
5427 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
5428
5429 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
5430 if (is_null($tmpproduct)) {
5431 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
5432 }
5433 $tmpproduct->fetch($id); // Load product to get ->ref
5434
5435 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
5436 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
5437 }
5438
5439 $this->res[] = array(
5440 'id' => $id, // Id product
5441 'id_parent' => $id_parent,
5442 'ref' => $tmpproduct->ref, // Ref product
5443 'nb' => $nb, // Nb of units that compose parent product
5444 'nb_total' => $nb * $multiply, // Nb of units for all nb of product
5445 'stock' => $tmpproduct->stock_reel, // Stock
5446 'stock_alert' => $tmpproduct->seuil_stock_alerte, // Stock alert
5447 'label' => $label,
5448 'fullpath' => $compl_path.$label, // Label
5449 'type' => $type, // Nb of units that compose parent product
5450 'desiredstock' => $tmpproduct->desiredstock,
5451 'level' => $level,
5452 'incdec' => $incdec,
5453 'entity' => $tmpproduct->entity
5454 );
5455
5456 // Recursive call if there child has children of its own
5457 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
5458 if (!is_int($desc_pere[1] * $multiply)) {
5459 dol_syslog(get_class($this).'::'.__FUNCTION__.' Source product quantity and multiplier may result in unexpected end quantity for '.$label.'/'.$multiply.' Child product:'.json_encode($desc_pere), LOG_WARNING);
5460 }
5461
5462 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
5463 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", (int) ceil($desc_pere[1] * $multiply), $level + 1, $id, $ignore_stock_load);
5464 }
5465 }
5466 }
5467 }
5468
5469 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5478 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
5479 {
5480 // phpcs:enable
5481 $this->res = array();
5482 if (isset($this->sousprods) && is_array($this->sousprods)) {
5483 foreach ($this->sousprods as $prod_name => $desc_product) {
5484 if (is_array($desc_product)) {
5485 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
5486 }
5487 }
5488 }
5489 //var_dump($res);
5490 return $this->res;
5491 }
5492
5500 public function hasFatherOrChild($mode = 0)
5501 {
5502 $nb = 0;
5503
5504 $sql = "SELECT COUNT(pa.rowid) as nb";
5505 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
5506 if ($mode == 0) {
5507 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
5508 } elseif ($mode == -1) {
5509 $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)
5510 } elseif ($mode == 1) {
5511 $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)
5512 }
5513
5514 $resql = $this->db->query($sql);
5515 if ($resql) {
5516 $obj = $this->db->fetch_object($resql);
5517 if ($obj) {
5518 $nb = $obj->nb;
5519 }
5520 } else {
5521 return -1;
5522 }
5523
5524 return $nb;
5525 }
5526
5532 public function hasVariants()
5533 {
5534 $nb = 0;
5535 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
5536 $sql .= " AND entity IN (".getEntity('product').")";
5537
5538 $resql = $this->db->query($sql);
5539 if ($resql) {
5540 $obj = $this->db->fetch_object($resql);
5541 if ($obj) {
5542 $nb = $obj->nb;
5543 }
5544 }
5545
5546 return $nb;
5547 }
5548
5549
5555 public function isVariant()
5556 {
5557 if (isModEnabled('variants')) {
5558 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5559
5560 $query = $this->db->query($sql);
5561
5562 if ($query) {
5563 if (!$this->db->num_rows($query)) {
5564 return false;
5565 }
5566 return true;
5567 } else {
5568 dol_print_error($this->db);
5569 return -1;
5570 }
5571 } else {
5572 return false;
5573 }
5574 }
5575
5582 public function getFather()
5583 {
5584 $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";
5585 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5586 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5587 $sql .= " ".$this->db->prefix()."product as p";
5588 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5589 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5590
5591 $res = $this->db->query($sql);
5592 if ($res) {
5593 $prods = array();
5594 while ($record = $this->db->fetch_array($res)) {
5595 // $record['id'] = $record['rowid'] = id of father
5596 $prods[$record['id']] = array();
5597 $prods[$record['id']]['id'] = $record['rowid'];
5598 $prods[$record['id']]['ref'] = $record['ref'];
5599 $prods[$record['id']]['label'] = $record['label'];
5600 $prods[$record['id']]['qty'] = $record['qty'];
5601 $prods[$record['id']]['incdec'] = $record['incdec'];
5602 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5603 $prods[$record['id']]['entity'] = $record['entity'];
5604 $prods[$record['id']]['status'] = $record['status'];
5605 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5606 }
5607 return $prods;
5608 } else {
5609 dol_print_error($this->db);
5610 return -1;
5611 }
5612 }
5613
5614
5624 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5625 {
5626 if (empty($id)) {
5627 return array();
5628 }
5629
5630 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5631 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5632 $sql .= " pa.rowid as fk_association, pa.rang";
5633 $sql .= " FROM ".$this->db->prefix()."product as p,";
5634 $sql .= " ".$this->db->prefix()."product_association as pa";
5635 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5636 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5637 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5638 $sql .= " ORDER BY pa.rang";
5639
5640 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5641
5642 // Protection against infinite loop
5643 if ($level > 30) {
5644 return array();
5645 }
5646
5647 $res = $this->db->query($sql);
5648 if ($res) {
5649 $prods = array();
5650 if ($this->db->num_rows($res) > 0) {
5651 $parents[] = $id;
5652 }
5653
5654 while ($rec = $this->db->fetch_array($res)) {
5655 if (in_array($rec['id'], $parents)) {
5656 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);
5657 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5658 }
5659
5660 $prods[$rec['rowid']] = array(
5661 0 => $rec['rowid'],
5662 1 => $rec['qty'],
5663 2 => $rec['fk_product_type'],
5664 3 => $this->db->escape($rec['label']),
5665 4 => $rec['incdec'],
5666 5 => $rec['ref'],
5667 6 => $rec['fk_association'],
5668 7 => $rec['rang']
5669 );
5670 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5671 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5672 if (empty($firstlevelonly)) {
5673 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5674 foreach ($listofchilds as $keyChild => $valueChild) {
5675 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5676 }
5677 }
5678 }
5679
5680 return $prods;
5681 } else {
5682 dol_print_error($this->db);
5683 return -1;
5684 }
5685 }
5686
5687 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5694 public function get_sousproduits_arbo()
5695 {
5696 // phpcs:enable
5697 $parent = array();
5698
5699 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5700 $parent[$this->label][$keyChild] = $valueChild;
5701 }
5702 foreach ($parent as $key => $value) { // key=label, value is array of children
5703 $this->sousprods[$key] = $value; // @phan-suppress-current-line PhanTypeMismatchProperty
5704 }
5705 }
5706
5714 public function getTooltipContentArray($params)
5715 {
5716 global $conf, $langs, $user;
5717
5718 $langs->loadLangs(array('products', 'other'));
5719
5720 $datas = array();
5721 $nofetch = !empty($params['nofetch']);
5722
5723 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5724 return ['optimize' => $langs->trans("ShowProduct")];
5725 }
5726
5727 // Does user has permission to read product/service
5728 $permissiontoreadproduct = 0;
5729 if ($this->type == self::TYPE_PRODUCT && $user->hasRight('product', 'read')) {
5730 $permissiontoreadproduct = 1;
5731 }
5732 if ($this->type == self::TYPE_SERVICE && $user->hasRight('service', 'read')) {
5733 $permissiontoreadproduct = 1;
5734 }
5735
5736 if (!empty($this->entity) && $permissiontoreadproduct) {
5737 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, '1');
5738 if ($this->nbphoto > 0) {
5739 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5740 }
5741 }
5742
5743 if ($this->isProduct()) {
5744 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5745 } elseif ($this->isService()) {
5746 $datas['picto'] = img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5747 }
5748 if (isset($this->status) && isset($this->status_buy)) {
5749 $datas['status'] = ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5750 }
5751
5752 if (!empty($this->ref)) {
5753 $datas['ref'] = '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5754 }
5755 if (!empty($this->label)) {
5756 $datas['label'] = '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label;
5757 }
5758
5759 if ($permissiontoreadproduct) {
5760 if (!empty($this->description)) {
5761 $datas['description'] = '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineOfText($this->description, 5);
5762 }
5763 if ($this->isStockManaged()) {
5764 if (isModEnabled('productbatch')) {
5765 $langs->load("productbatch");
5766 $datas['batchstatus'] = "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5767 if ($this->status_batch) {
5768 $datas['batchdlc'] = "<br><b>".$langs->trans("BatchSellOrEatByMandatoryList", $langs->transnoentitiesnoconv("SellByDate"), $langs->transnoentitiesnoconv("EatByDate")).'</b>: '.$this->getSellOrEatByMandatoryLabel();
5769 }
5770 }
5771 }
5772 if (isModEnabled('barcode')) {
5773 $datas['barcode'] = '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5774 }
5775
5776 if ($this->isProduct()) {
5777 if ($this->weight) {
5778 $datas['weight'] = "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5779 }
5780 $labelsize = "";
5781 if ($this->length) {
5782 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5783 }
5784 if ($this->width) {
5785 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5786 }
5787 if ($this->height) {
5788 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5789 }
5790 if ($labelsize) {
5791 $datas['size'] = "<br>".$labelsize;
5792 }
5793
5794 $labelsurfacevolume = "";
5795 if ($this->surface) {
5796 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5797 }
5798 if ($this->volume) {
5799 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5800 }
5801 if ($labelsurfacevolume) {
5802 $datas['surface'] = "<br>" . $labelsurfacevolume;
5803 }
5804 }
5805 if ($this->isService() && !empty($this->duration_value)) {
5806 // Duration
5807 $datas['duration'] = '<br><b>'.$langs->trans("Duration").':</b> '.$this->duration_value;
5808 if ($this->duration_value > 1) {
5809 $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"));
5810 } elseif ($this->duration_value > 0) {
5811 $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"));
5812 }
5813 $datas['duration'] .= (!empty($this->duration_unit) && isset($dur[$this->duration_unit]) ? "&nbsp;".$langs->trans($dur[$this->duration_unit]) : '');
5814 }
5815 if (empty($user->socid)) {
5816 if ($this->isStockManaged() && !empty($this->pmp) && $this->pmp) {
5817 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5818 }
5819
5820 if (isModEnabled('accounting')) {
5821 if ($this->status && isset($this->accountancy_code_sell)) {
5822 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5823 $selllabel = '<br>';
5824 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5825 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5826 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5827 $datas['accountancysell'] = $selllabel;
5828 }
5829 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5830 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5831 $buylabel = '';
5832 if (empty($this->status)) {
5833 $buylabel .= '<br>';
5834 }
5835 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5836 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5837 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5838 $datas['accountancybuy'] = $buylabel;
5839 }
5840 }
5841 }
5842 // show categories for this record only in ajax to not overload lists
5843 if (isModEnabled('category') && !$nofetch) {
5844 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5845 $form = new Form($this->db);
5846 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5847 }
5848 }
5849
5850 return $datas;
5851 }
5852
5866 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5867 {
5868 global $langs, $hookmanager;
5869
5870 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5871
5872 $result = '';
5873
5874 $newref = $this->ref;
5875 if ($maxlength) {
5876 $newref = dol_trunc($newref, $maxlength, 'middle');
5877 }
5878 $params = [
5879 'id' => $this->id,
5880 'objecttype' => ($this->type == 1 ? 'service' : 'product'),
5881 'option' => $option,
5882 'nofetch' => 1,
5883 ];
5884 $classfortooltip = 'classfortooltip';
5885 $dataparams = '';
5886 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5887 $classfortooltip = 'classforajaxtooltip';
5888 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5889 $label = '';
5890 } else {
5891 $label = implode($this->getTooltipContentArray($params));
5892 }
5893
5894 $linkclose = '';
5895 if (empty($notooltip)) {
5896 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5897 $label = $langs->trans("ShowProduct");
5898 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5899 }
5900 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5901 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5902 } else {
5903 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5904 }
5905
5906 if ($option == 'supplier' || $option == 'category') {
5907 $url = DOL_URL_ROOT.'/product/price_suppliers.php?id='.$this->id;
5908 } elseif ($option == 'stock') {
5909 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5910 } elseif ($option == 'composition') {
5911 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5912 } else {
5913 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5914 }
5915
5916 if ($option !== 'nolink') {
5917 // Add param to save lastsearch_values or not
5918 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
5919 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
5920 $add_save_lastsearch_values = 1;
5921 }
5922 if ($add_save_lastsearch_values) {
5923 $url .= '&save_lastsearch_values=1';
5924 }
5925 }
5926
5927 $linkstart = '<a href="'.$url.'"';
5928 $linkstart .= $linkclose.'>';
5929 $linkend = '</a>';
5930
5931 $result .= $linkstart;
5932 if ($withpicto) {
5933 if ($this->isProduct()) {
5934 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5935 }
5936 if ($this->isService()) {
5937 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
5938 }
5939 }
5940 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
5941 $result .= $linkend;
5942 if ($withpicto != 2) {
5943 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
5944 }
5945
5946 global $action;
5947 $hookmanager->initHooks(array('productdao'));
5948 $parameters = array('id' => $this->id, 'getnomurl' => &$result, 'label' => &$label);
5949 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
5950 if ($reshook > 0) {
5951 $result = $hookmanager->resPrint;
5952 } else {
5953 $result .= $hookmanager->resPrint;
5954 }
5955
5956 return $result;
5957 }
5958
5959
5970 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
5971 {
5972 global $langs;
5973
5974 $langs->load("products");
5975 $outputlangs->load("products");
5976
5977 // Positionne le modele sur le nom du modele a utiliser
5978 if (!dol_strlen($modele)) {
5979 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
5980 }
5981
5982 $modelpath = "core/modules/product/doc/";
5983
5984 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
5985 }
5986
5994 public function getLibStatut($mode = 0, $type = 0)
5995 {
5996 switch ($type) {
5997 case 0:
5998 return $this->LibStatut($this->status, $mode, $type);
5999 case 1:
6000 return $this->LibStatut($this->status_buy, $mode, $type);
6001 case 2:
6002 return $this->LibStatut($this->status_batch, $mode, $type);
6003 default:
6004 //Simulate previous behavior but should return an error string
6005 return $this->LibStatut($this->status_buy, $mode, $type);
6006 }
6007 }
6008
6009 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6018 public function LibStatut($status, $mode = 0, $type = 0)
6019 {
6020 // phpcs:enable
6021 global $langs;
6022
6023 $labelStatus = $labelStatusShort = '';
6024
6025 $langs->load('products');
6026 if (isModEnabled('productbatch')) {
6027 $langs->load("productbatch");
6028 }
6029
6030 if ($type == 2) {
6031 switch ($mode) {
6032 case 0:
6033 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
6034 return dolGetStatus($label);
6035 case 1:
6036 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
6037 return dolGetStatus($label);
6038 case 2:
6039 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
6040 case 3:
6041 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
6042 case 4:
6043 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
6044 case 5:
6045 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
6046 default:
6047 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
6048 }
6049 }
6050
6051 $statuttrans = empty($status) ? 'status5' : 'status4';
6052
6053 if ($status == 0) {
6054 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
6055 if ($type == 0) {
6056 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
6057 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
6058 } elseif ($type == 1) {
6059 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
6060 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
6061 } elseif ($type == 2) {
6062 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
6063 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
6064 }
6065 } elseif ($status == 1) {
6066 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
6067 if ($type == 0) {
6068 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
6069 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
6070 } elseif ($type == 1) {
6071 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
6072 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
6073 } elseif ($type == 2) {
6074 $labelStatus = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial'));
6075 $labelStatusShort = ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort'));
6076 }
6077 } elseif ($type == 2 && $status == 2) {
6078 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
6079 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
6080 }
6081
6082 if ($mode > 6) {
6083 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
6084 } else {
6085 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
6086 }
6087 }
6088
6089
6095 public function getLibFinished()
6096 {
6097 global $langs;
6098
6099 $langs->load('products');
6100 $label = '';
6101
6102 if (isset($this->finished) && $this->finished >= 0) {
6103 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
6104 $resql = $this->db->query($sql);
6105 if (!$resql) {
6106 $this->error = $this->db->error().' sql='.$sql;
6107 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
6108 return -1;
6109 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
6110 $label = $langs->trans($res['label']);
6111 }
6112 $this->db->free($resql);
6113 }
6114
6115 return $label;
6116 }
6117
6118
6119 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6136 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
6137 {
6138 // phpcs:enable
6139 if ($id_entrepot) {
6140 $this->db->begin();
6141
6142 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6143
6144 // Ensure $nbpiece is a number and positive
6145 $nbpiece = (float) $nbpiece;
6146 if ($nbpiece < 0) {
6147 if (!$movement) {
6148 $movement = 1;
6149 }
6150 $nbpiece = abs($nbpiece);
6151 }
6152 $op = array();
6153 $op[0] = $nbpiece;
6154 $op[1] = -$nbpiece;
6155
6156 $movementstock = new MouvementStock($this->db);
6157 $movementstock->setOrigin($origin_element, (int) $origin_id); // Set ->origin_type and ->origin_id
6158 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
6159
6160 if ($result >= 0) {
6161 if ($extrafields) {
6162 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6163 $movementstock->array_options = $array_options;
6164 $movementstock->insertExtraFields();
6165 }
6166 $this->db->commit();
6167 return 1;
6168 } else {
6169 $this->error = $movementstock->error;
6170 $this->errors = $movementstock->errors;
6171
6172 $this->db->rollback();
6173 return -1;
6174 }
6175 }
6176
6177 return -1;
6178 }
6179
6180 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6201 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)
6202 {
6203 // phpcs:enable
6204 if ($id_entrepot) {
6205 $this->db->begin();
6206
6207 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6208
6209 // Ensure $nbpiece is a number and positive
6210 $nbpiece = (float) $nbpiece;
6211 if ($nbpiece < 0) {
6212 if (!$movement) {
6213 $movement = 1;
6214 }
6215 $nbpiece = abs($nbpiece);
6216 }
6217
6218 $op = array();
6219 $op[0] = $nbpiece;
6220 $op[1] = -$nbpiece;
6221
6222 $movementstock = new MouvementStock($this->db);
6223 $movementstock->setOrigin($origin_element, (int) $origin_id); // Set ->origin_type and ->fk_origin
6224 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
6225
6226 if ($result >= 0) {
6227 if ($extrafields) {
6228 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6229 $movementstock->array_options = $array_options;
6230 $movementstock->insertExtraFields();
6231 }
6232 $this->db->commit();
6233 return 1;
6234 } else {
6235 $this->error = $movementstock->error;
6236 $this->errors = $movementstock->errors;
6237
6238 $this->db->rollback();
6239 return -1;
6240 }
6241 }
6242 return -1;
6243 }
6244
6245 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6258 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
6259 {
6260 // phpcs:enable
6261 $this->stock_reel = 0;
6262 $this->stock_warehouse = array();
6263 $this->stock_theorique = 0;
6264
6265 // Set filter on warehouse status
6266 $warehouseStatus = array();
6267 if (preg_match('/warehouseclosed/', $option)) {
6269 }
6270 if (preg_match('/warehouseopen/', $option)) {
6272 }
6273 if (preg_match('/warehouseinternal/', $option)) {
6274 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
6276 } else {
6278 }
6279 }
6280
6281 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
6282 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
6283 $sql .= ", ".$this->db->prefix()."entrepot as w";
6284 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
6285 $sql .= " AND w.rowid = ps.fk_entrepot";
6286 $sql .= " AND ps.fk_product = ".((int) $this->id);
6287 if (count($warehouseStatus)) {
6288 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
6289 }
6290
6291 $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;
6292
6293 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
6294 $result = $this->db->query($sql);
6295 if ($result) {
6296 $num = $this->db->num_rows($result);
6297 $i = 0;
6298 if ($num > 0) {
6299 while ($i < $num) {
6300 $row = $this->db->fetch_object($result);
6301 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
6302 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
6303 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
6304 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
6305 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
6306 }
6307 $this->stock_reel += $row->reel;
6308 $i++;
6309 }
6310 $this->stock_reel = (float) price2num($this->stock_reel, 'MS');
6311 }
6312 $this->db->free($result);
6313
6314 if (!preg_match('/novirtual/', $option)) {
6315 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
6316 }
6317
6318 return 1;
6319 } else {
6320 $this->error = $this->db->lasterror();
6321 return -1;
6322 }
6323 }
6324
6325
6326 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6336 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
6337 {
6338 // phpcs:enable
6339 global $hookmanager, $action;
6340
6341 $stock_commande_client = 0;
6342 $stock_commande_fournisseur = 0;
6343 $stock_sending_client = 0;
6344 $stock_reception_fournisseur = 0;
6345 $stock_inproduction = 0;
6346
6347 //dol_syslog("load_virtual_stock");
6348
6349 if (isModEnabled('order')) {
6350 $result = $this->load_stats_commande(0, '1,2', 1);
6351 if ($result < 0) {
6352 dol_print_error($this->db, $this->error);
6353 }
6354 $stock_commande_client = $this->stats_commande['qty'];
6355 }
6356 if (isModEnabled("shipping")) {
6357 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
6358 $filterShipmentStatus = '';
6359 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
6360 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
6361 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6362 $filterShipmentStatus = Expedition::STATUS_CLOSED;
6363 }
6364 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
6365 if ($result < 0) {
6366 dol_print_error($this->db, $this->error);
6367 }
6368 $stock_sending_client = $this->stats_expedition['qty'];
6369 }
6370 // Include supplier order lines
6371 if (isModEnabled("supplier_order")) {
6372 $filterStatus = getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK', '3,4');
6373 if (isset($includedraftpoforvirtual)) {
6374 $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
6375 }
6376 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
6377 if ($result < 0) {
6378 dol_print_error($this->db, $this->error);
6379 }
6380 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
6381 }
6382 // Include reception lines
6383 if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) {
6384 $filterStatus = '4';
6385 if (isset($includedraftpoforvirtual)) {
6386 $filterStatus = '0,'.$filterStatus;
6387 }
6388 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
6389 if ($result < 0) {
6390 dol_print_error($this->db, $this->error);
6391 }
6392 $stock_reception_fournisseur = $this->stats_reception['qty'];
6393 }
6394 // Include manufacturing
6395 if (isModEnabled('mrp')) {
6396 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
6397 if ($result < 0) {
6398 dol_print_error($this->db, $this->error);
6399 }
6400 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
6401 }
6402
6403 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
6404
6405 // $weBillOrderOrShipmentReception is set to 'order' or 'shipmentreception'. it will be used to know how to make virtual stock
6406 // calculation when we have a stock increase or decrease on billing. Do we have to count orders to bill or shipment/reception to bill ?
6407 $weBillOrderOrShipmentReception = getDolGlobalString('STOCK_DO_WE_BILL_ORDER_OR_SHIPMENTECEPTION_FOR_VIRTUALSTOCK', 'order');
6408
6409 // Stock decrease mode
6410 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6411 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6412 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
6413 if (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER_INCLUDE_DRAFT')) { // By default, draft means "does not exist", so we do not include them by default, except if option is on
6414 $tmpnewprod = dol_clone($this, 1);
6415 $result = $tmpnewprod->load_stats_commande(0, '0', 1); // Get qty in draft orders
6416 $this->stock_theorique += $tmpnewprod->stats_commande['qty'];
6417 }
6418 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'order') {
6419 $this->stock_theorique -= $stock_commande_client;
6420 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
6421 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6422 }
6423
6424 // Stock Increase mode
6425 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
6426 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6427 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) { // This option is similar to STOCK_CALCULATE_ON_RECEPTION_CLOSE but when module Reception is not enabled
6428 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6429 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) { // Warning: stock change "on approval", not on validation !
6430 if (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER_INCLUDE_DRAFT')) { // By default, draft means "does not exist", so we do not include them by default, except if option is on
6431 $tmpnewprod = dol_clone($this, 1);
6432 $result = $tmpnewprod->load_stats_commande_fournisseur(0, '0', 1); // Get qty in draft orders
6433 $this->stock_theorique += $this->stats_commande_fournisseur['qty'];
6434 }
6435 $this->stock_theorique -= $stock_reception_fournisseur;
6436 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'order') {
6437 $this->stock_theorique += $stock_commande_fournisseur;
6438 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
6439 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6440 }
6441
6442 $parameters = array('id' => $this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
6443 // Note that $action and $object may have been modified by some hooks
6444 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
6445 if ($reshook > 0) {
6446 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
6447 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
6448 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
6449 }
6450
6451 //Virtual Stock by Warehouse
6452 if (!empty($this->stock_warehouse) && getDolGlobalString('STOCK_ALLOW_VIRTUAL_STOCK_PER_WAREHOUSE')) {
6453 foreach ($this->stock_warehouse as $warehouseid => $stockwarehouse) {
6454 if (isModEnabled('mrp')) {
6455 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock, $warehouseid);
6456 if ($result < 0) {
6457 dol_print_error($this->db, $this->error);
6458 }
6459 }
6460
6461 if ($this->fk_default_warehouse == $warehouseid) {
6462 $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']);
6463 } else {
6464 $this->stock_warehouse[$warehouseid]->virtual = $this->stock_warehouse[$warehouseid]->real + $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'];
6465 }
6466 }
6467 }
6468
6469 return 1;
6470 }
6471
6479 public function loadStockForVirtualProduct($option = '', $qtyWish = 1)
6480 {
6481 $this->stock_warehouse = array();
6482 $error = 0;
6483
6484 $this->get_sousproduits_arbo();
6485 $prods_arbo = $this->get_arbo_each_prod($qtyWish, 1);
6486 if (count($prods_arbo) > 0) {
6487 $productCachedList = array();
6488 $stockByComponentList = array();
6489
6490 foreach ($prods_arbo as $componentArr) {
6491 $componentId = $componentArr['id'];
6492 // only component whose manage stock
6493 if ($componentArr['incdec'] == 1) {
6494 if (!isset($productCachedList[$componentId])) {
6495 $componentStatic = new self($this->db);
6496 $componentStatic->fetch($componentId);
6497 // check if it's a sub-kit
6498 $childrenNb = $componentStatic->hasFatherOrChild(1);
6499 if ($childrenNb == 0) {
6500 $componentStatic->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
6501 if (!isset($stockByComponentList[$componentId])) {
6502 $stockByComponentList[$componentId] = array(
6503 'qty_need' => 0
6504 );
6505 }
6506 $stockByComponentList[$componentId]['qty_need'] += $componentArr['nb_total'];
6507 }
6508 $productCachedList[$componentId] = $componentStatic;
6509 }
6510 }
6511 }
6512
6513 if (!empty($stockByComponentList)) {
6514 foreach ($stockByComponentList as $componentId => $stockByComponentArr) {
6515 if (!isset($productCachedList[$componentId])) {
6516 $componentStatic = new self($this->db);
6517 $componentStatic->fetch($componentId);
6518 $componentStatic->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
6519 $productCachedList[$componentId] = $componentStatic;
6520 }
6521 $component = $productCachedList[$componentId];
6522
6523
6524 if ($component->stock_reel < $stockByComponentArr['qty_need']) {
6525 // not enough stock for this component to assemble this virtual product
6526 $error++;
6527 $this->error = 'Not enough component [id='.$componentId.'] in stock, real='.$component->stock_reel.' and need='.$stockByComponentArr['qty_need'];
6528 $this->errors[] = $this->error;
6529 dol_syslog(__METHOD__.' : '.$this->error, LOG_ERR);
6530 } else {
6531 if (!empty($component->stock_warehouse)) {
6532 foreach ($component->stock_warehouse as $warehouseId => $warehouseObj) {
6533 $kitWarehouseAvailable = new stdClass();
6534 $kitWarehouseAvailable->id = $warehouseObj->id;
6535 $kitWarehouseAvailable->real = $qtyWish;
6536 $this->stock_warehouse[$warehouseId] = $kitWarehouseAvailable;
6537 }
6538 }
6539 }
6540
6541 if ($error) {
6542 break;
6543 }
6544 }
6545 }
6546 }
6547
6548 if ($error) {
6549 return -1;
6550 } else {
6551 return 1;
6552 }
6553 }
6554
6562 public function loadBatchInfo($batch)
6563 {
6564 $result = array();
6565
6566 $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";
6567 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
6568 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
6569 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
6570 $resql = $this->db->query($sql);
6571 if ($resql) {
6572 $num = $this->db->num_rows($resql);
6573 $i = 0;
6574 while ($i < $num) {
6575 $obj = $this->db->fetch_object($resql);
6576 $result[] = array('batch' => $batch, 'eatby' => $this->db->jdate($obj->eatby), 'sellby' => $this->db->jdate($obj->sellby), 'qty' => $obj->qty);
6577 $i++;
6578 }
6579 return $result;
6580 } else {
6581 dol_print_error($this->db);
6582 $this->db->rollback();
6583 return array();
6584 }
6585 }
6586
6587 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6596 public function add_photo($sdir, $file)
6597 {
6598 // phpcs:enable
6599 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6600
6601 $result = 0;
6602
6603 $dir = $sdir;
6604 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6605 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
6606 } else {
6607 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
6608 }
6609
6610 dol_mkdir($dir);
6611
6612 $dir_osencoded = $dir;
6613
6614 if (is_dir($dir_osencoded)) {
6615 $originImage = $dir.'/'.$file['name'];
6616
6617 // Cree fichier en taille origine
6618 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
6619
6620 if (file_exists(dol_osencode($originImage))) {
6621 // Create thumbs
6622 $this->addThumbs($originImage);
6623 }
6624 }
6625
6626 if (is_numeric($result) && $result > 0) {
6627 return 1;
6628 } else {
6629 return -1;
6630 }
6631 }
6632
6633 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6640 public function is_photo_available($sdir)
6641 {
6642 // phpcs:enable
6643 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6644 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6645
6646 $dir = $sdir;
6647 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6648 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
6649 } else {
6650 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
6651 }
6652
6653 $dir_osencoded = dol_osencode($dir);
6654 if (file_exists($dir_osencoded)) {
6655 $handle = opendir($dir_osencoded);
6656 if (is_resource($handle)) {
6657 while (($file = readdir($handle)) !== false) {
6658 if (!utf8_check($file)) {
6659 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6660 }
6661 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6662 return true;
6663 }
6664 }
6665 }
6666 }
6667
6668 return false;
6669 }
6670
6671 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6679 public function liste_photos($dir, $nbmax = 0)
6680 {
6681 // phpcs:enable
6682 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6683 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6684
6685 $nbphoto = 0;
6686 $tabobj = array();
6687
6688 $dir_osencoded = dol_osencode($dir);
6689 $handle = @opendir($dir_osencoded);
6690 if (is_resource($handle)) {
6691 while (($file = readdir($handle)) !== false) {
6692 if (!utf8_check($file)) {
6693 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6694 }
6695 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6696 $nbphoto++;
6697
6698 // We forge name of thumb.
6699 $photo = $file;
6700 $photo_vignette = '';
6701 $regs = array();
6702 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6703 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6704 }
6705
6706 $dirthumb = $dir.'thumbs/';
6707
6708 // Object
6709 $obj = array();
6710 $obj['photo'] = $photo;
6711 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6712 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6713 } else {
6714 $obj['photo_vignette'] = "";
6715 }
6716
6717 $tabobj[$nbphoto - 1] = $obj;
6718
6719 // Do we have to continue with next photo ?
6720 if ($nbmax && $nbphoto >= $nbmax) {
6721 break;
6722 }
6723 }
6724 }
6725
6726 closedir($handle);
6727 }
6728
6729 return $tabobj;
6730 }
6731
6732 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6739 public function delete_photo($file)
6740 {
6741 // phpcs:enable
6742 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6743 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6744
6745 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6746 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6747 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6748
6749 // On efface l'image d'origine
6750 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6751
6752 // Si elle existe, on efface la vignette
6753 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6754 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6755 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6756 dol_delete_file($dirthumb.$photo_vignette);
6757 }
6758
6759 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6760 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6761 dol_delete_file($dirthumb.$photo_vignette);
6762 }
6763 }
6764 }
6765
6766 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6773 public function get_image_size($file)
6774 {
6775 // phpcs:enable
6776 $file_osencoded = dol_osencode($file);
6777 $infoImg = getimagesize($file_osencoded); // Get information on image
6778 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6779 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6780 }
6781
6787 public function loadStateBoard()
6788 {
6789 global $hookmanager;
6790
6791 $this->nb = array();
6792
6793 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6794 $sql .= " FROM ".$this->db->prefix()."product as p";
6795 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6796 // Add where from hooks
6797 if (is_object($hookmanager)) {
6798 $parameters = array();
6799 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6800 $sql .= $hookmanager->resPrint;
6801 }
6802 $sql .= ' GROUP BY fk_product_type';
6803
6804 $resql = $this->db->query($sql);
6805 if ($resql) {
6806 while ($obj = $this->db->fetch_object($resql)) {
6807 if ($obj->fk_product_type == 1) {
6808 $this->nb["services"] = $obj->nb;
6809 } else {
6810 $this->nb["products"] = $obj->nb;
6811 }
6812 }
6813 $this->db->free($resql);
6814 return 1;
6815 } else {
6816 dol_print_error($this->db);
6817 $this->error = $this->db->error();
6818 return -1;
6819 }
6820 }
6821
6827 public function isProduct()
6828 {
6829 return $this->type == Product::TYPE_PRODUCT;
6830 }
6831
6837 public function isService()
6838 {
6839 return $this->type == Product::TYPE_SERVICE;
6840 }
6841
6847 public function isStockManaged()
6848 {
6849 return (($this->isProduct() || ($this->isService() && getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) && ($this->stockable_product > 0));
6850 }
6851
6857 public function isMandatoryPeriod()
6858 {
6859 return $this->mandatory_period == 1;
6860 }
6861
6867 public function hasbatch()
6868 {
6869 return $this->status_batch > 0;
6870 }
6871
6872
6873 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6882 public function get_barcode($object, $type = '')
6883 {
6884 // phpcs:enable
6885 global $conf;
6886
6887 $result = '';
6888 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6889 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6890 foreach ($dirsociete as $dirroot) {
6891 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6892 if ($res) {
6893 break;
6894 }
6895 }
6896 $var = getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM');
6897 $mod = new $var();
6898 '@phan-var-force ModeleNumRefBarCode $mod';
6899
6900 $result = $mod->getNextValue($object, $type);
6901
6902 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6903 }
6904 return $result;
6905 }
6906
6914 public function initAsSpecimen()
6915 {
6916 $now = dol_now();
6917
6918 // Initialize parameters
6919 $this->specimen = 1;
6920 $this->id = 0;
6921 $this->ref = 'PRODUCT_SPEC';
6922 $this->label = 'PRODUCT SPECIMEN';
6923 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
6924 $this->specimen = 1;
6925 $this->country_id = 1;
6926 $this->status = 1;
6927 $this->status_buy = 1;
6928 $this->tobatch = 0;
6929 $this->sell_or_eat_by_mandatory = 0;
6930 $this->note_private = 'This is a comment (private)';
6931 $this->note_public = 'This is a comment (public)';
6932 $this->date_creation = $now;
6933 $this->date_modification = $now;
6934
6935 $this->weight = 4;
6936 $this->weight_units = 3;
6937
6938 $this->length = 5;
6939 $this->length_units = 1;
6940 $this->width = 6;
6941 $this->width_units = 0;
6942 $this->height = null;
6943 $this->height_units = null;
6944
6945 $this->surface = 30;
6946 $this->surface_units = 0;
6947 $this->volume = 300;
6948 $this->volume_units = 0;
6949
6950 $this->barcode = -1; // Create barcode automatically
6951
6952 return 1;
6953 }
6954
6964 public function getLabelOfUnit($type = 'long', $outputlangs = null, $noentities = 0)
6965 {
6966 global $langs;
6967
6968 if (empty($this->fk_unit)) {
6969 return '';
6970 }
6971 if (empty($outputlangs)) {
6972 $outputlangs = $langs;
6973 }
6974
6975 $outputlangs->load('products');
6976 $label = '';
6977
6978 $sql = "SELECT code, label, short_label FROM ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
6979
6980 $resql = $this->db->query($sql);
6981 if (!$resql) {
6982 $this->error = $this->db->error();
6983 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
6984 return -1;
6985 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
6986 if ($type == 'short') {
6987 if ($noentities) {
6988 $label = $outputlangs->transnoentitiesnoconv($res['short_label']);
6989 } else {
6990 $label = $outputlangs->trans($res['short_label']);
6991 }
6992 } elseif ($type == 'code') {
6993 $label = $res['code'];
6994 } else {
6995 if ($outputlangs->trans('unit'.$res['code']) == 'unit'.$res['code']) {
6996 // No translation available
6997 $label = $res['label'];
6998 } else {
6999 // Return the translated value
7000 if ($noentities) {
7001 $label = $outputlangs->transnoentitiesnoconv('unit'.$res['code']);
7002 } else {
7003 $label = $outputlangs->trans('unit'.$res['code']);
7004 }
7005 }
7006 }
7007 }
7008 $this->db->free($resql);
7009
7010 return $label;
7011 }
7012
7013 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7019 public function min_recommended_price()
7020 {
7021 // phpcs:enable
7022 $maxpricesupplier = 0;
7023
7024 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
7025 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
7026 $product_fourn = new ProductFournisseur($this->db);
7027 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
7028
7029 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
7030 foreach ($product_fourn_list as $productfourn) {
7031 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
7032 $maxpricesupplier = $productfourn->fourn_unitprice;
7033 }
7034 }
7035
7036 $maxpricesupplier *= getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE');
7037 }
7038 }
7039
7040 return $maxpricesupplier;
7041 }
7042
7043
7054 public function setCategories($categories)
7055 {
7056 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
7057 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
7058 }
7059
7068 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
7069 {
7070 $tables = array(
7071 'product_customer_price',
7072 'product_customer_price_log'
7073 );
7074
7075 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
7076 }
7077
7089 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
7090 {
7091 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
7092 $query = $this->db->query($sql);
7093
7094 $rules = array();
7095
7096 while ($result = $this->db->fetch_object($query)) {
7097 $rules[$result->level] = $result;
7098 }
7099
7100 //Because prices can be based on other level's prices, we temporarily store them
7101 $prices = array(
7102 1 => $baseprice
7103 );
7104
7105 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
7106 for ($i = 1; $i <= $nbofproducts; $i++) {
7107 $price = $baseprice;
7108 $price_min = $baseprice;
7109
7110 //We have to make sure it does exist and it is > 0
7111 //First price level only allows changing min_price
7112 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
7113 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
7114 }
7115
7116 $prices[$i] = $price;
7117
7118 //We have to make sure it does exist and it is > 0
7119 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
7120 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
7121 }
7122
7123 //Little check to make sure the price is modified before triggering generation
7124 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
7125 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
7126
7127 if ($check_amount && $check_type) {
7128 continue;
7129 }
7130
7131 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, 1) < 0) {
7132 return -1;
7133 }
7134 }
7135
7136 return 1;
7137 }
7138
7144 public function getRights()
7145 {
7146 global $user;
7147
7148 if ($this->isProduct()) {
7149 return $user->rights->produit;
7150 } else {
7151 return $user->rights->service;
7152 }
7153 }
7154
7161 public function info($id)
7162 {
7163 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, p.tms as date_modification,";
7164 $sql .= " p.fk_user_author, p.fk_user_modif";
7165 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
7166 $sql .= " WHERE p.rowid = ".((int) $id);
7167
7168 $result = $this->db->query($sql);
7169 if ($result) {
7170 if ($this->db->num_rows($result)) {
7171 $obj = $this->db->fetch_object($result);
7172
7173 $this->id = $obj->rowid;
7174 $this->ref = $obj->ref;
7175
7176 $this->user_creation_id = $obj->fk_user_author;
7177 $this->user_modification_id = $obj->fk_user_modif;
7178
7179 $this->date_creation = $this->db->jdate($obj->date_creation);
7180 $this->date_modification = $this->db->jdate($obj->date_modification);
7181 }
7182
7183 $this->db->free($result);
7184 } else {
7185 dol_print_error($this->db);
7186 }
7187 }
7188
7189
7195 public function getProductDurationHours()
7196 {
7197 if (empty($this->duration_value)) {
7198 $this->errors[] = 'ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
7199 return -1;
7200 }
7201 if ($this->duration_unit == 's') {
7202 $prodDurationHours = 1. / 3600;
7203 } elseif ($this->duration_unit == 'i' || $this->duration_unit == 'mn' || $this->duration_unit == 'min') {
7204 $prodDurationHours = 1. / 60;
7205 } elseif ($this->duration_unit == 'h') {
7206 $prodDurationHours = 1.;
7207 } elseif ($this->duration_unit == 'd') {
7208 $prodDurationHours = 24.;
7209 } elseif ($this->duration_unit == 'w') {
7210 $prodDurationHours = 24. * 7;
7211 } elseif ($this->duration_unit == 'm') {
7212 $prodDurationHours = 24. * 30;
7213 } elseif ($this->duration_unit == 'y') {
7214 $prodDurationHours = 24. * 365;
7215 } else {
7216 $prodDurationHours = 0.0;
7217 }
7218 $prodDurationHours *= $this->duration_value;
7219
7220 return $prodDurationHours;
7221 }
7222
7223
7231 public function getKanbanView($option = '', $arraydata = null)
7232 {
7233 global $langs, $conf;
7234
7235 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
7236
7237 $return = '<div class="box-flex-item box-flex-grow-zero">';
7238 $return .= '<div class="info-box info-box-sm">';
7239 $return .= '<div class="info-box-img">';
7240 $label = '';
7241 if ($this->is_photo_available($conf->product->multidir_output[$this->entity])) {
7242 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
7243 $return .= $label;
7244 } else {
7245 if ($this->isProduct()) {
7246 $label .= img_picto('', 'product');
7247 } elseif ($this->isService()) {
7248 $label .= img_picto('', 'service');
7249 }
7250 $return .= $label;
7251 }
7252 $return .= '</div>';
7253 $return .= '<div class="info-box-content">';
7254 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
7255 if ($selected >= 0) {
7256 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
7257 }
7258 if (property_exists($this, 'label')) {
7259 $return .= '<br><span class="info-box-label opacitymedium inline-block tdoverflowmax150 valignmiddle" title="'.dol_escape_htmltag($this->label).'">'.dol_escape_htmltag($this->label).'</span>';
7260 }
7261 if (property_exists($this, 'price') && property_exists($this, 'price_ttc')) {
7262 if ($this->price_base_type == 'TTC') {
7263 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
7264 } else {
7265 if ($this->status) {
7266 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
7267 }
7268 }
7269 }
7270 $br = 1;
7271 if (property_exists($this, 'stock_reel') && $this->isProduct()) {
7272 $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>';
7273 $br = 0;
7274 }
7275 if (method_exists($this, 'getLibStatut')) {
7276 if ($br) {
7277 $return .= '<br><div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
7278 } else {
7279 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
7280 }
7281 }
7282 $return .= '</div>';
7283 $return .= '</div>';
7284 $return .= '</div>';
7285 return $return;
7286 }
7287
7294 public function getProductsToPreviewInEmail($limit)
7295 {
7296
7297 if (!is_numeric($limit)) {
7298 return -1;
7299 }
7300
7301 $sql = "SELECT p.rowid, p.ref, p.label, p.description, p.entity, ef.filename
7302 FROM ".MAIN_DB_PREFIX."product AS p
7303 JOIN ".MAIN_DB_PREFIX."ecm_files AS ef ON p.rowid = ef.src_object_id
7304 WHERE ef.entity IN (".getEntity('product').")
7305 AND (ef.filename LIKE '%.png' OR ef.filename LIKE '%.jpeg' OR ef.filename LIKE '%.svg')
7306 GROUP BY p.rowid, p.ref, p.label, p.description, p.entity, ef.filename
7307 ORDER BY p.datec ASC
7308 LIMIT " . ((int) $limit);
7309
7310 $resql = $this->db->query($sql);
7311 $products = array();
7312
7313 if ($resql) {
7314 while ($obj = $this->db->fetch_object($resql)) {
7315 $products[] = array(
7316 'rowid' => $obj->rowid,
7317 'ref' => $obj->ref,
7318 'label' => $obj->label,
7319 'description' => $obj->description,
7320 'entity' => $obj->entity,
7321 'filename' => $obj->filename
7322 );
7323 }
7324 } else {
7325 dol_print_error($this->db);
7326 }
7327 if (empty($products)) {
7328 return -1;
7329 }
7330 return $products;
7331 }
7332}
7333
7339{
7340 public $picto = 'service';
7341}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:67
length_accountg($account)
Return General accounting account with defined length (used for product and miscellaneous)
$object ref
Definition info.php:90
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
deleteEcmFiles($mode=0)
Delete related files of object in database.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
isObjectUsed($id=0, $entity=0)
Function to check if an object is used by others (by children).
deleteExtraFields()
Delete all extra fields values for the current object.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
show_photos($modulepart, $sdir, $size=0, $nbmax=0, $nbbyrow=5, $showfilename=0, $showaction=0, $maxHeight=120, $maxWidth=160, $nolink=0, $overwritetitle=0, $usesharelink=0, $cache='', $addphotorefcss='photoref')
Show photos of an object (nbmax maximum), into several columns.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
call_trigger($triggerName, $user)
Call trigger based on this instance.
addThumbs($file, $quality=50)
Build thumb.
Class to manage Dolibarr database access.
Class to manage ECM files.
const STATUS_OPEN_INTERNAL
Warehouse open and only operations for stock transfers/corrections allowed (not for customer shipping...
const STATUS_OPEN_ALL
Warehouse open and any operations are allowed (customer shipping, supplier dispatch,...
const STATUS_CLOSED
Warehouse closed, inactive.
const STATUS_CLOSED
Closed status -> parcel was received by customer / end of process prev status : validated or shipment...
const STATUS_VALIDATED
Validated status -> parcel is ready to be sent prev status : draft next status : closed or shipment_i...
const STATUS_DRAFT
Draft status.
Class to manage generation of HTML components Only common components must be here.
Class to manage stock movements.
Class to parse product price expressions.
Class ProductCombination Used to represent the relation between a product and one of its variants.
File of class to manage predefined price products or services by customer.
Class to manage predefined suppliers products.
Class to manage products or services.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
get_nb_achat($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or supplier invoices in which product is included.
getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp=0)
Return price of sell of a product for a seller/buyer/product.
__construct($db)
Constructor.
is_sousproduit($fk_parent, $fk_child)
Check if it is a sub-product into a kit.
const DISABLED_STOCK
Stockable product.
const SELL_OR_EAT_BY_MANDATORY_ID_NONE
Const sell or eat by mandatory id.
isStockManaged()
Return if the object is managed in stock.
setPriceExpression($expression_id)
Sets the supplier price expression.
getArrayForPriceCompare($level=0)
used to check if price have really change to avoid log pollution
get_arbo_each_prod($multiply=1, $ignore_stock_load=0)
Build the tree of subproducts and return it.
check_barcode($valuetotest, $typefortest)
Check barcode.
list_suppliers()
Return list of suppliers providing the product or service.
load_stats_mo($socid=0)
Charge tableau des stats OF pour le produit/service.
isVariant()
Return if loaded product is a variant.
updatePrice($newprice, $newpricebase, $user, $newvat=null, $newminprice=0, $level=0, $newnpr=0, $newpbq=0, $ignore_autogen=0, $localtaxes_array=array(), $newdefaultvatcode='', $price_label='', $notrigger=0)
Modify customer price of a product/Service for a given level.
hasVariants()
Return if a product has variants or not.
delMultiLangs($langtodelete, $user)
Delete a language for this product.
load_stats_proposal_supplier($socid=0)
Charge tableau des stats propale pour le produit/service.
getLibFinished()
Retour label of nature of product.
add_sousproduit($id_pere, $id_fils, $qty, $incdec=1, $notrigger=0)
Link a product/service to a parent product/service.
add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
Add a supplier price for the product.
hasFatherOrChild($mode=0)
Count all parent and children products for current product (first level only)
load_stats_facturerec($socid=0)
Load array of statistics for recurring invoice for product/service.
get_nb_propalsupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in proposals in which product is included.
load_stats_facturefournrec($socid=0)
Load array of statistics for recurring supplier invoice for product/service.
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)
Load array of statistics for vendor invoice for product/service.
get_nb_ordersupplier($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
getMultiLangs()
Load array this->multilangs.
get_nb_mos($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
clone_associations($fromId, $toId)
Clone links between products.
create($user, $notrigger=0)
Insert product into database.
load_stats_contrat($socid=0)
Charge tableau des stats contrat pour le produit/service.
isService()
Return if the object is a service.
getRights()
Returns the rights used for this class.
loadBatchInfo($batch)
Load existing information about a serial.
load_stock($option='', $includedraftpoforvirtual=null, $dateofvirtualstock=null)
Load information about stock of a product into ->stock_reel, ->stock_warehouse[] (including stock_war...
getProductDurationHours()
Return the duration of a service in hours (for a service based on duration fields)
get_buyprice($prodfournprice, $qty, $product_id=0, $fourn_ref='', $fk_soc=0)
Read price used by a provider.
clone_fournisseurs($fromId, $toId)
Recopie les fournisseurs et prix fournisseurs d'un produit/service sur un autre.
const TYPE_PRODUCT
Regular product.
add_photo($sdir, $file)
Move an uploaded file described into $file array into target directory $sdir.
log_price_delete($user, $rowid)
Delete a price line.
info($id)
Load information for tab info.
correct_stock($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $inventorycode='', $origin_element='', $origin_id=null, $disablestockchangeforsubproduct=0, $extrafields=null)
Adjust stock in a warehouse for product.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
Create a document onto disk according to template module.
static getSellOrEatByMandatoryList()
Get the array of labels of Sell by or Eat by all mandatory flags for each status.
getChildsArbo($id, $firstlevelonly=0, $level=1, $parents=array())
Return children of product $id.
load_virtual_stock($includedraftpoforvirtual=null, $dateofvirtualstock=null)
Load value ->stock_theorique of a product.
load_stats_propale($socid=0)
Charge tableau des stats propale pour le produit/service.
get_barcode($object, $type='')
Get a barcode from the module to generate barcode values.
setAccountancyCode($type, $value)
Sets an accountancy code for a product.
getProductsToPreviewInEmail($limit)
Retrieve and display products.
load_stats_facture($socid=0)
Charge tableau des stats facture pour le produit/service.
setCategories($categories)
Sets object to supplied categories.
load_stats_reception($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null)
Charge tableau des stats réception fournisseur pour le produit/service.
get_nb_propal($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in proposals in which product is included.
update($id, $user, $notrigger=0, $action='update', $updatetype=false)
Update a record into database.
setMultiLangs($user)
Update or add a translation for a product.
correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $dlc='', $dluo='', $lot='', $inventorycode='', $origin_element='', $origin_id=null, $disablestockchangeforsubproduct=0, $extrafields=null, $force_update_batch=false)
Adjust stock in a warehouse for product with batch number.
load_stats_bom($socid=0)
Charge tableau des stats OF pour le produit/service.
hasbatch()
Return if the object has a batch management.
del_sousproduit($fk_parent, $fk_child, $notrigger=0)
Remove a link between a subproduct and a parent product/service.
fetch($id=0, $ref='', $ref_ext='', $barcode='', $ignore_expression=0, $ignore_price_load=0, $ignore_lang_load=0)
Load a product in memory from database.
update_sousproduit($id_pere, $id_fils, $qty, $incdec=1, $notrigger=0)
Modify composed product.
load_stats_commande($socid=0, $filtrestatut='', $forVirtualStock=0)
Charge tableau des stats commande client pour le produit/service.
delete_photo($file)
Delete a photo and its thumbs.
fetch_prod_arbo($prod, $compl_path='', $multiply=1, $level=1, $id_parent=0, $ignore_stock_load=0)
Function recursive, used only by get_arbo_each_prod(), to build tree of subproducts into ->res Define...
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
getLibStatut($mode=0, $type=0)
Return label of status of object.
load_stats_sending($socid=0, $filtrestatut='', $forVirtualStock=0, $filterShipmentStatus='')
Charge tableau des stats expedition client pour le produit/service.
clone_price($fromId, $toId)
Recopie les prix d'un produit/service sur un autre.
load_stats_inproduction($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null, $warehouseid=0)
Charge tableau des stats production pour le produit/service.
check()
Check that ref and label are ok.
initAsSpecimen()
Initialise an instance with random values.
liste_photos($dir, $nbmax=0)
Return an array with all photos of product found on disk.
loadStateBoard()
Load indicators this->nb for the dashboard.
getFather()
Return all parent products for current product (first level only)
getNomUrl($withpicto=0, $option='', $maxlength=0, $save_lastsearch_value=-1, $notooltip=0, $morecss='', $add_label=0, $sep=' - ')
Return clickable link of object (with eventually picto)
getSellOrEatByMandatoryLabel()
Get the label for sell by or eat by mandatory flag of the current product.
verify()
Check properties of product are ok (like name, barcode, ...).
get_sousproduits_arbo()
get_nb_order($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units in orders in which product is included.
loadStockForVirtualProduct($option='', $qtyWish=1)
Load stock for components of virtual product (first level only)
min_recommended_price()
Return minimum product recommended price.
_log_price($user, $level=0)
Insert a track that we changed a customer price.
getLabelOfUnit($type='long', $outputlangs=null, $noentities=0)
Reads the units dictionary to return the translation code of a unit (if type='code'),...
_get_stats($sql, $mode, $year=0)
Return an array formatted for showing graphs.
load_stats_commande_fournisseur($socid=0, $filtrestatut='', $forVirtualStock=0, $dateofvirtualstock=null)
Charge tableau des stats commande fournisseur pour le produit/service.
isMandatoryPeriod()
Return if the object has a constraint on mandatory_period.
isProduct()
Return if the object is a product.
generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
Generates prices for a product based on product multiprice generation rules.
LibStatut($status, $mode=0, $type=0)
Return label of a given status.
const TYPE_SERVICE
Service.
is_photo_available($sdir)
Return if at least one photo is available.
get_image_size($file)
Load size of image file.
get_nb_vente($socid, $mode, $filteronproducttype=-1, $year=0, $morefilter='')
Return nb of units or customers invoices in which product is included.
getTooltipContentArray($params)
getTooltipContentArray
Class to manage products or services.
Manage record for batch number management.
static findAll($dbs, $fk_product_stock, $with_qty=0, $fk_product=0)
Return all batch detail records for a given product and warehouse.
Class with list of lots and properties.
Class to manage Dolibarr users.
hasRight($module, $permlevel1, $permlevel2='')
Return if a user has a permission.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
getCountry($searchkey, $withcode='', $dbtouse=null, $outputlangs=null, $entconv=1, $searchlabel='')
Return country label, code or id from an id, code or label.
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_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_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $nohook=0, $keyforsourcefile='addedfile', $upload_dir='', $mode=0)
Check validity of a file upload from an GUI page, and move it to its final destination.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
dolGetFirstLineOfText($text, $nboflines=1, $charset='UTF-8')
Return first line of text.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_clone($object, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
utf8_check($str)
Check if a string is in UTF8.
get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that return vat rate of a product line (according to seller, buyer and product vat rate) VAT...
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
measuring_units_squared($unitscale)
Transform a given unit scale into the square of that unit, if known.
measuringUnitString($unitid, $measuring_style='', $unitscale=null, $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|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:158