dolibarr 23.0.4
product.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2001-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2014 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2005-2015 Regis Houssin <regis.houssin@inodbox.com>
5 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
6 * Copyright (C) 2007-2011 Jean Heimburger <jean@tiaris.info>
7 * Copyright (C) 2010-2018 Juanjo Menent <jmenent@2byte.es>
8 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
9 * Copyright (C) 2013-2014 Cedric GROSS <c.gross@kreiz-it.fr>
10 * Copyright (C) 2013-2016 Marcos García <marcosgdf@gmail.com>
11 * Copyright (C) 2011-2021 Open-DSI <support@open-dsi.fr>
12 * Copyright (C) 2014 Henry Florian <florian.henry@open-concept.pro>
13 * Copyright (C) 2014-2016 Philippe Grand <philippe.grand@atoo-net.com>
14 * Copyright (C) 2014 Ion agorria <ion@agorria.com>
15 * Copyright (C) 2016-2024 Ferran Marcet <fmarcet@2byte.es>
16 * Copyright (C) 2017 Gustavo Novaro
17 * Copyright (C) 2019-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
65 public $TRIGGER_PREFIX = 'PRODUCT';
66
70 public $element = 'product';
71
75 public $table_element = 'product';
76
80 public $fk_element = 'fk_product';
81
85 protected $childtables = array(
86 'supplier_proposaldet' => array('name' => 'SupplierProposal', 'parent' => 'supplier_proposal', 'parentkey' => 'fk_supplier_proposal'),
87 'propaldet' => array('name' => 'Proposal', 'parent' => 'propal', 'parentkey' => 'fk_propal'),
88 'commandedet' => array('name' => 'Order', 'parent' => 'commande', 'parentkey' => 'fk_commande'),
89 'facturedet' => array('name' => 'Invoice', 'parent' => 'facture', 'parentkey' => 'fk_facture'),
90 'contratdet' => array('name' => 'Contract', 'parent' => 'contrat', 'parentkey' => 'fk_contrat'),
91 'facture_fourn_det' => array('name' => 'SupplierInvoice', 'parent' => 'facture_fourn', 'parentkey' => 'fk_facture_fourn'),
92 'commande_fournisseurdet' => array('name' => 'SupplierOrder', 'parent' => 'commande_fournisseur', 'parentkey' => 'fk_commande'),
93 'mrp_production' => array('name' => 'Mo', 'parent' => 'mrp_mo', 'parentkey' => 'fk_mo', 'enabled' => 'isModEnabled("mrp")'),
94 'bom_bom' => array('name' => 'BOM', 'enabled' => 'isModEnabled("bom")'),
95 'bom_bomline' => array('name' => 'BOMLine', 'parent' => 'bom_bom', 'parentkey' => 'fk_bom', 'enabled' => 'isModEnabled("bom")'),
96 );
97
103 public $picto = 'product';
104
108 protected $table_ref_field = 'ref';
109
114 public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm';
115
121 public $libelle;
122
128 public $label;
129
135 public $description;
136
142 public $other;
143
149 public $type = self::TYPE_PRODUCT;
150
156 public $price;
157
161 public $price_formated; // used by takepos/ajax/ajax.php
162
168 public $price_ttc;
169
173 public $price_ttc_formated; // used by takepos/ajax/ajax.php
174
180 public $price_min;
181
187 public $price_min_ttc;
188
193 public $price_base_type;
197 public $price_label;
198
200
203 public $multiprices = array();
207 public $multiprices_ttc = array();
211 public $multiprices_base_type = array();
215 public $multiprices_default_vat_code = array();
219 public $multiprices_min = array();
223 public $multiprices_min_ttc = array();
227 public $multiprices_tva_tx = array();
231 public $multiprices_recuperableonly = array();
232
234
237 public $price_by_qty;
241 public $prices_by_qty = array();
245 public $prices_by_qty_id = array();
249 public $prices_by_qty_list = array();
250
254 public $level;
255
259 public $multilangs = array();
260
264 public $default_vat_code;
265
269 public $tva_tx;
270
274 public $tva_npr = 0;
275
279 public $remise_percent;
280
284 public $localtax1_tx;
288 public $localtax2_tx;
292 public $localtax1_type;
296 public $localtax2_type;
297
298 // Properties set by get_buyprice() for return
299
303 public $desc_supplier;
307 public $vatrate_supplier;
311 public $default_vat_code_supplier;
312
316 public $fourn_multicurrency_price;
317
321 public $fourn_multicurrency_unitprice;
325 public $fourn_multicurrency_tx;
329 public $fourn_multicurrency_id;
333 public $fourn_multicurrency_code;
334
338 public $packaging;
339
340
347 public $lifetime;
348
355 public $qc_frequency;
356
362 public $stock_reel = 0;
363
369 public $stock_theorique;
370
376 public $cost_price;
377
383 public $pmp;
384
390 public $seuil_stock_alerte = 0;
391
395 public $desiredstock = 0;
396
400 public $duration_value;
404 public $duration_unit;
408 public $duration;
409
413 public $fk_default_workstation;
414
420 public $status = 0;
421
428 public $tosell;
429
435 public $status_buy = 0;
436
443 public $tobuy;
444
450 public $finished;
451
457 public $fk_default_bom;
458
464 public $product_fourn_price_id;
465
471 public $buyprice;
472
478 public $tobatch;
479
480
486 public $status_batch = 0;
487
493 public $sell_or_eat_by_mandatory = 0;
494
500 public $batch_mask = '';
501
507 public $customcode;
508
514 public $url;
515
517
520 public $weight;
521
525 public $weight_units; // scale -3, 0, 3, 6
529 public $length;
533 public $length_units; // scale -3, 0, 3, 6
537 public $width;
541 public $width_units; // scale -3, 0, 3, 6
545 public $height;
549 public $height_units; // scale -3, 0, 3, 6
553 public $surface;
557 public $surface_units; // scale -3, 0, 3, 6
561 public $volume;
565 public $volume_units; // scale -3, 0, 3, 6
566
570 public $net_measure;
574 public $net_measure_units; // scale -3, 0, 3, 6
575
579 public $accountancy_code_sell;
583 public $accountancy_code_sell_intra;
587 public $accountancy_code_sell_export;
591 public $accountancy_code_buy;
595 public $accountancy_code_buy_intra;
599 public $accountancy_code_buy_export;
600
604 public $barcode;
605
609 public $barcode_type;
610
614 public $barcode_type_code;
615
619 public $stats_propale = array();
620
624 public $stats_commande = array();
625
629 public $stats_contrat = array();
630
634 public $stats_facture = array();
635
639 public $stats_proposal_supplier = array();
640
644 public $stats_commande_fournisseur = array();
645
649 public $stats_expedition = array();
650
654 public $stats_reception = array();
655
659 public $stats_mo = array();
660
664 public $stats_bom = array();
665
669 public $stats_mrptoconsume = array();
670
674 public $stats_mrptoproduce = array();
675
679 public $stats_facturerec = array();
680
684 public $stats_facture_fournisseur = array();
685
689 public $stats_facturefournrec = array();
690
694 public $imgWidth;
698 public $imgHeight;
699
704 public $product_fourn_id;
705
710 public $product_id_already_linked;
711
716 public $nbphoto = 0;
717
721 public $stock_warehouse = array();
722
726 public $fk_default_warehouse;
727
731 public $fk_price_expression;
732
737 public $fourn_qty;
738
743 public $fourn_pu;
744
749 public $fourn_price_base_type;
750
754 public $fourn_socid;
755
761 public $ref_fourn;
762
766 public $ref_supplier;
767
773 public $fk_unit;
774
780 public $price_autogen = 0;
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((string) $this->accountancy_code_buy);
1050 $this->accountancy_code_buy_intra = trim((string) $this->accountancy_code_buy_intra);
1051 $this->accountancy_code_buy_export = trim((string) $this->accountancy_code_buy_export);
1052 $this->accountancy_code_sell = trim((string) $this->accountancy_code_sell);
1053 $this->accountancy_code_sell_intra = trim((string) $this->accountancy_code_sell_intra);
1054 $this->accountancy_code_sell_export = trim((string) $this->accountancy_code_sell_export);
1055
1056 // Normalize the accountancy codes the way the admin dropdown does it, so an API client that
1057 // sends '606111000' ends up with the same '606111' value the GUI stores (see issue #32343).
1058 // When ACCOUNTING_MANAGE_ZERO is on, trailing zeros are part of the code and must be kept.
1059 if (!getDolGlobalString('ACCOUNTING_MANAGE_ZERO')) {
1060 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
1061 $this->accountancy_code_buy = clean_account($this->accountancy_code_buy);
1062 $this->accountancy_code_buy_intra = clean_account($this->accountancy_code_buy_intra);
1063 $this->accountancy_code_buy_export = clean_account($this->accountancy_code_buy_export);
1064 $this->accountancy_code_sell = clean_account($this->accountancy_code_sell);
1065 $this->accountancy_code_sell_intra = clean_account($this->accountancy_code_sell_intra);
1066 $this->accountancy_code_sell_export = clean_account($this->accountancy_code_sell_export);
1067 }
1068
1069 // Barcode value
1070 $this->barcode = trim($this->barcode);
1071 $this->mandatory_period = empty($this->mandatory_period) ? 0 : $this->mandatory_period;
1072 // Check parameters
1073 if (empty($this->label)) {
1074 $langs->load('errors');
1075 $this->errors[] = $langs->trans('ErrorMandatoryParametersNotProvided');
1076 return -1;
1077 }
1078
1079 if (empty($this->ref) || $this->ref == 'auto') {
1080 // Load object modCodeProduct
1081 $module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
1082 if ($module != 'mod_codeproduct_leopard') { // Do not load module file for leopard
1083 if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
1084 $module = substr($module, 0, dol_strlen($module) - 4);
1085 }
1086 dol_include_once('/core/modules/product/'.$module.'.php');
1087 $modCodeProduct = new $module();
1088 '@phan-var-force ModeleProductCode $modCodeProduct';
1089 if (!empty($modCodeProduct->code_auto)) {
1090 $this->ref = $modCodeProduct->getNextValue($this, $this->type);
1091 }
1092 unset($modCodeProduct);
1093 }
1094
1095 if (empty($this->ref)) {
1096 $this->error = 'ProductModuleNotSetupForAutoRef';
1097 return -2;
1098 }
1099 }
1100
1101 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);
1102
1103 $now = dol_now();
1104
1105 if (empty($this->date_creation)) {
1106 $this->date_creation = $now;
1107 }
1108
1109 $this->db->begin();
1110
1111 // For automatic creation during create action (not used by Dolibarr GUI, can be used by scripts)
1112 if ($this->barcode == '-1' || $this->barcode == 'auto') {
1113 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1114 }
1115
1116 // Check more parameters
1117 // If error, this->errors[] is filled
1118 $result = $this->verify();
1119
1120 if ($result >= 0) {
1121 $sql = "SELECT count(*) as nb";
1122 $sql .= " FROM ".$this->db->prefix()."product";
1123 $sql .= " WHERE entity IN (".getEntity('product').")";
1124 $sql .= " AND ref = '".$this->db->escape($this->ref)."'";
1125
1126 $result = $this->db->query($sql);
1127 if ($result) {
1128 $obj = $this->db->fetch_object($result);
1129 if ($obj->nb == 0) {
1130 // Insert new product, no previous one found
1131 $sql = "INSERT INTO ".$this->db->prefix()."product (";
1132 $sql .= "datec";
1133 $sql .= ", entity";
1134 $sql .= ", ref";
1135 $sql .= ", ref_ext";
1136 $sql .= ", price_min";
1137 $sql .= ", price_min_ttc";
1138 $sql .= ", label";
1139 $sql .= ", fk_user_author";
1140 $sql .= ", fk_product_type";
1141 $sql .= ", price";
1142 $sql .= ", price_ttc";
1143 $sql .= ", price_base_type";
1144 $sql .= ", price_label";
1145 $sql .= ", tobuy";
1146 $sql .= ", tosell";
1147 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1148 $sql .= ", accountancy_code_buy";
1149 $sql .= ", accountancy_code_buy_intra";
1150 $sql .= ", accountancy_code_buy_export";
1151 $sql .= ", accountancy_code_sell";
1152 $sql .= ", accountancy_code_sell_intra";
1153 $sql .= ", accountancy_code_sell_export";
1154 }
1155 $sql .= ", canvas";
1156 $sql .= ", finished";
1157 $sql .= ", tobatch";
1158 $sql .= ", sell_or_eat_by_mandatory";
1159 $sql .= ", batch_mask";
1160 $sql .= ", fk_unit";
1161 $sql .= ", mandatory_period";
1162 $sql .= ", stockable_product";
1163 if (!empty($this->default_vat_code)) {
1164 $sql .= ", default_vat_code";
1165 }
1166 $sql .= ") VALUES (";
1167 $sql .= "'".$this->db->idate($this->date_creation)."'";
1168 $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
1169 $sql .= ", '".$this->db->escape($this->ref)."'";
1170 $sql .= ", ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1171 $sql .= ", ".price2num($price_min_ht);
1172 $sql .= ", ".price2num($price_min_ttc);
1173 $sql .= ", ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
1174 $sql .= ", ".((int) $user->id);
1175 $sql .= ", ".((int) $this->type);
1176 $sql .= ", ".price2num($price_ht, 'MT');
1177 $sql .= ", ".price2num($price_ttc, 'MT');
1178 $sql .= ", '".$this->db->escape($this->price_base_type)."'";
1179 $sql .= ", ".(!empty($this->price_label) ? "'".$this->db->escape($this->price_label)."'" : "null");
1180 $sql .= ", ".((int) $this->status);
1181 $sql .= ", ".((int) $this->status_buy);
1182 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1183 $sql .= ", '".$this->db->escape($this->accountancy_code_buy)."'";
1184 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_intra)."'";
1185 $sql .= ", '".$this->db->escape($this->accountancy_code_buy_export)."'";
1186 $sql .= ", '".$this->db->escape($this->accountancy_code_sell)."'";
1187 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_intra)."'";
1188 $sql .= ", '".$this->db->escape($this->accountancy_code_sell_export)."'";
1189 }
1190 $sql .= ", '".$this->db->escape($this->canvas)."'";
1191 $sql .= ", ".((!isset($this->finished) || $this->finished < 0 || $this->finished == '') ? 'NULL' : (int) $this->finished);
1192 $sql .= ", ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : ((int) $this->status_batch));
1193 $sql .= ", ".((empty($this->sell_or_eat_by_mandatory) || $this->sell_or_eat_by_mandatory < 0) ? 0 : ((int) $this->sell_or_eat_by_mandatory));
1194 $sql .= ", '".$this->db->escape($this->batch_mask)."'";
1195 $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL');
1196 $sql .= ", '".$this->db->escape((string) $this->mandatory_period)."'";
1197 $sql .= ", ".((int) $this->stockable_product);
1198 if (!empty($this->default_vat_code)) {
1199 $sql .= ", '".$this->db->escape($this->default_vat_code)."'";
1200 }
1201 $sql .= ")";
1202 dol_syslog(get_class($this)."::Create", LOG_DEBUG);
1203
1204 $result = $this->db->query($sql);
1205 if ($result) {
1206 $id = $this->db->last_insert_id($this->db->prefix()."product");
1207
1208 if ($id > 0) {
1209 $this->id = $id;
1210 $this->price = $price_ht;
1211 $this->price_ttc = $price_ttc;
1212 $this->price_min = $price_min_ht;
1213 $this->price_min_ttc = $price_min_ttc;
1214
1215 $result = $this->_log_price($user);
1216 if ($result > 0) {
1217 if ($this->update($id, $user, 1, 'add') <= 0) {
1218 $error++;
1219 }
1220 } else {
1221 $error++;
1222 $this->error = $this->db->lasterror();
1223 }
1224
1225 // update accountancy for this entity
1226 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1227 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " .((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1228
1229 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1230 $sql .= " fk_product";
1231 $sql .= ", entity";
1232 $sql .= ", accountancy_code_buy";
1233 $sql .= ", accountancy_code_buy_intra";
1234 $sql .= ", accountancy_code_buy_export";
1235 $sql .= ", accountancy_code_sell";
1236 $sql .= ", accountancy_code_sell_intra";
1237 $sql .= ", accountancy_code_sell_export";
1238 $sql .= ") VALUES (";
1239 $sql .= $this->id;
1240 $sql .= ", " . ((int) $conf->entity);
1241 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1242 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1243 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1244 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1245 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1246 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1247 $sql .= ")";
1248 $result = $this->db->query($sql);
1249 if (!$result) {
1250 $error++;
1251 $this->error = 'ErrorFailedToInsertAccountancyForEntity';
1252 }
1253 }
1254 } else {
1255 $error++;
1256 $this->error = 'ErrorFailedToGetInsertedId';
1257 }
1258 } else {
1259 $error++;
1260 $this->error = $this->db->lasterror();
1261 }
1262 } else {
1263 // Product already exists with this ref
1264 $langs->load("products");
1265 $error++;
1266 $this->error = "ErrorProductAlreadyExists";
1267 dol_syslog(get_class($this)."::Create fails, ref ".$this->ref." already exists");
1268 }
1269 } else {
1270 $error++;
1271 $this->error = $this->db->lasterror();
1272 }
1273
1274 if (!$error && !$notrigger) {
1275 // Call trigger
1276 $result = $this->call_trigger('PRODUCT_CREATE', $user);
1277 if ($result < 0) {
1278 $error++;
1279 }
1280 // End call triggers
1281 }
1282
1283 if (!$error) {
1284 $this->db->commit();
1285 return $this->id;
1286 } else {
1287 $this->db->rollback();
1288 return -$error;
1289 }
1290 } else {
1291 $this->db->rollback();
1292 dol_syslog(get_class($this)."::Create fails verify ".implode(',', $this->errors), LOG_WARNING);
1293 return -3;
1294 }
1295 }
1296
1297
1304 public function verify()
1305 {
1306 global $langs;
1307
1308 $this->errors = array();
1309
1310 $result = 0;
1311 $this->ref = trim($this->ref);
1312
1313 if (!$this->ref) {
1314 $this->errors[] = 'ErrorBadRef';
1315 $result = -2;
1316 }
1317
1318 $arrayofnonnegativevalue = array('weight' => 'Weight', 'width' => 'Width', 'height' => 'Height', 'length' => 'Length', 'surface' => 'Surface', 'volume' => 'Volume');
1319 foreach ($arrayofnonnegativevalue as $key => $value) {
1320 if (property_exists($this, $key) && !empty($this->$key) && ($this->$key < 0)) {
1321 $langs->loadLangs(array("main", "other"));
1322 $this->error = $langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv($value));
1323 $this->errors[] = $this->error;
1324 $result = -4;
1325 }
1326 }
1327
1328 $rescode = $this->check_barcode($this->barcode, $this->barcode_type_code);
1329 if ($rescode) {
1330 if ($rescode == -1) {
1331 $this->errors[] = 'ErrorBadBarCodeSyntax';
1332 } elseif ($rescode == -2) {
1333 $this->errors[] = 'ErrorBarCodeRequired';
1334 } elseif ($rescode == -3) {
1335 // Note: Common usage is to have barcode unique. For variants, we should have a different barcode.
1336 $this->errors[] = 'ErrorBarCodeAlreadyUsed';
1337 }
1338
1339 $result = -3;
1340 }
1341
1342 return $result;
1343 }
1344
1345 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1356 public function check_barcode($valuetotest, $typefortest)
1357 {
1358 // phpcs:enable
1359 global $conf;
1360
1361 if (isModEnabled('barcode') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
1362 $module = strtolower(getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM'));
1363
1364 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
1365 foreach ($dirsociete as $dirroot) {
1366 $res = dol_include_once($dirroot.$module.'.php');
1367 if ($res) {
1368 break;
1369 }
1370 }
1371
1372 $mod = new $module();
1373 '@phan-var-force ModeleNumRefBarCode $mod';
1374
1375 dol_syslog(get_class($this)."::check_barcode value=".$valuetotest." type=".$typefortest." module=".$module);
1376 $result = $mod->verif($this->db, $valuetotest, $this, 0, $typefortest);
1377 return $result;
1378 } else {
1379 return 0;
1380 }
1381 }
1382
1394 public function update($id, $user, $notrigger = 0, $action = 'update', $updatetype = false)
1395 {
1396 global $langs, $conf, $hookmanager;
1397
1398 $error = 0;
1399
1400 // Check parameters
1401 if (!$this->label) {
1402 $this->label = 'MISSING LABEL';
1403 }
1404
1405 // Clean parameters
1406 if (getDolGlobalInt('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
1407 $this->ref = trim($this->ref);
1408 } else {
1409 $this->ref = dol_string_nospecial(trim($this->ref));
1410 }
1411 $this->label = trim($this->label);
1412 $this->description = trim($this->description);
1413 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : null);
1414 $this->note_public = (isset($this->note_public) ? trim($this->note_public) : null);
1415 $this->net_measure = price2num($this->net_measure);
1416 $this->net_measure_units = (!is_numeric($this->net_measure_units) ? null : (int) $this->net_measure_units);
1417 $this->weight = price2num($this->weight);
1418 $this->weight_units = (!is_numeric($this->weight_units) ? null : (int) $this->weight_units);
1419 $this->length = price2num($this->length);
1420 $this->length_units = (!is_numeric($this->length_units) ? null : (int) $this->length_units);
1421 $this->width = price2num($this->width);
1422 $this->width_units = (!is_numeric($this->width_units) ? null : (int) $this->width_units);
1423 $this->height = price2num($this->height);
1424 $this->height_units = (!is_numeric($this->height_units) ? null : (int) $this->height_units);
1425 $this->surface = price2num($this->surface);
1426 $this->surface_units = (!is_numeric($this->surface_units) ? null : (int) $this->surface_units);
1427 $this->volume = price2num($this->volume);
1428 $this->volume_units = (!is_numeric($this->volume_units) ? null : (int) $this->volume_units);
1429
1430 // set unit not defined
1431 if (is_numeric($this->length_units)) {
1432 $this->width_units = $this->length_units; // Not used yet
1433 }
1434 if (is_numeric($this->length_units)) {
1435 $this->height_units = $this->length_units; // Not used yet
1436 }
1437
1438 // Automated compute surface and volume if not filled
1439 if (empty($this->surface) && !empty($this->length) && !empty($this->width) && $this->length_units == $this->width_units) {
1440 $this->surface = (float) $this->length * (float) $this->width;
1441 $this->surface_units = measuring_units_squared((int) $this->length_units);
1442 }
1443 if (empty($this->volume) && !empty($this->surface) && !empty($this->height) && $this->length_units == $this->height_units) {
1444 $this->volume = $this->surface * (float) $this->height;
1445 $this->volume_units = measuring_units_cubed((int) $this->height_units);
1446 }
1447
1448 if (empty($this->tva_tx)) {
1449 $this->tva_tx = 0;
1450 }
1451 if (empty($this->tva_npr)) {
1452 $this->tva_npr = 0;
1453 }
1454 if (empty($this->localtax1_tx)) {
1455 $this->localtax1_tx = 0;
1456 }
1457 if (empty($this->localtax2_tx)) {
1458 $this->localtax2_tx = 0;
1459 }
1460 if (empty($this->localtax1_type)) {
1461 $this->localtax1_type = '0';
1462 }
1463 if (empty($this->localtax2_type)) {
1464 $this->localtax2_type = '0';
1465 }
1466 if (empty($this->status)) {
1467 $this->status = 0;
1468 }
1469 if (empty($this->status_buy)) {
1470 $this->status_buy = 0;
1471 }
1472
1473 if (empty($this->country_id)) {
1474 $this->country_id = 0;
1475 }
1476 if (empty($this->country_id) && !empty($this->country_code)) {
1477 $country_id = getCountry($this->country_code, '3');
1478 $this->country_id = is_int($country_id) ? $country_id : 0;
1479 }
1480
1481 if (empty($this->state_id)) {
1482 $this->state_id = 0;
1483 }
1484
1485 if (empty($this->stockable_product)) {
1486 $this->stockable_product = 0;
1487 }
1488
1489 // For automatic creation during update action (not used by Dolibarr GUI, can be used by scripts)
1490 if ($this->barcode == '-1' || $this->barcode == 'auto') {
1491 $this->barcode = $this->get_barcode($this, $this->barcode_type_code);
1492 }
1493
1494 // Barcode value
1495 $this->barcode = (empty($this->barcode) ? '' : trim($this->barcode));
1496
1497 $this->accountancy_code_buy = trim($this->accountancy_code_buy);
1498 $this->accountancy_code_buy_intra = (!empty($this->accountancy_code_buy_intra) ? trim($this->accountancy_code_buy_intra) : '');
1499 $this->accountancy_code_buy_export = trim($this->accountancy_code_buy_export);
1500 $this->accountancy_code_sell = trim($this->accountancy_code_sell);
1501 $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra);
1502 $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export);
1503
1504 // Normalize the accountancy codes the way the admin dropdown does it, so an API client that
1505 // sends '606111000' ends up with the same '606111' value the GUI stores (see issue #32343).
1506 // When ACCOUNTING_MANAGE_ZERO is on, trailing zeros are part of the code and must be kept.
1507 if (!getDolGlobalString('ACCOUNTING_MANAGE_ZERO')) {
1508 require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
1509 $this->accountancy_code_buy = clean_account($this->accountancy_code_buy);
1510 $this->accountancy_code_buy_intra = clean_account($this->accountancy_code_buy_intra);
1511 $this->accountancy_code_buy_export = clean_account($this->accountancy_code_buy_export);
1512 $this->accountancy_code_sell = clean_account($this->accountancy_code_sell);
1513 $this->accountancy_code_sell_intra = clean_account($this->accountancy_code_sell_intra);
1514 $this->accountancy_code_sell_export = clean_account($this->accountancy_code_sell_export);
1515 }
1516
1517 $this->db->begin();
1518
1519 $result = 0;
1520 // Check name is required and codes are ok or unique. If error, this->errors[] is filled
1521 if ($action != 'add') {
1522 $result = $this->verify(); // We don't check when update called during a create because verify was already done
1523 } else {
1524 // we can continue
1525 $result = 0;
1526 }
1527
1528 if ($result >= 0) {
1529 // $this->oldcopy should have been set by the caller of update (here properties were already modified)
1530 // Note that this->oldcopy must be a Product instance (not stdClass), otherwise the method
1531 // hasbatch() called below will fatal. Callers may set oldcopy via dol_clone($obj, 2) (which
1532 // returns a stdClass with scalar properties only) and then we cannot call methods on it,
1533 // so re-clone with native=1 in that case (see issues #38663, #38638).
1534 if (is_null($this->oldcopy) || (is_object($this->oldcopy) && empty($this->oldcopy->id)) || !($this->oldcopy instanceof Product)) {
1535 $this->oldcopy = dol_clone($this, 1); // 1 to clone with methods to avoid fatal error with $this->oldcopy->hasbatch()
1536 }
1537 // Test if batch management is activated on existing product
1538 // If yes, we create missing entries into product_batch
1539 if ($this->hasbatch() && !$this->oldcopy->hasbatch()) {
1540 //$valueforundefinedlot = 'Undefined'; // In previous version, 39 and lower
1541 $valueforundefinedlot = '000000';
1542 if (getDolGlobalString('STOCK_DEFAULT_BATCH')) {
1543 $valueforundefinedlot = getDolGlobalString('STOCK_DEFAULT_BATCH');
1544 }
1545
1546 dol_syslog("Flag batch of product id=".$this->id." is set to ON, so we will create missing records into product_batch");
1547
1548 $this->load_stock();
1549 foreach ($this->stock_warehouse as $idW => $ObjW) { // For each warehouse where we have stocks defined for this product (for each lines in product_stock)
1550 $qty_batch = 0;
1551 foreach ($ObjW->detail_batch as $detail) { // Each lines of detail in product_batch of the current $ObjW = product_stock
1552 if ($detail->batch == $valueforundefinedlot || $detail->batch == 'Undefined') {
1553 // We discard this line, we will create it later
1554 $sqlclean = "DELETE FROM ".$this->db->prefix()."product_batch WHERE batch in('Undefined', '".$this->db->escape($valueforundefinedlot)."') AND fk_product_stock = ".((int) $ObjW->id);
1555 $result = $this->db->query($sqlclean);
1556 if (!$result) {
1557 dol_print_error($this->db);
1558 exit;
1559 }
1560 continue;
1561 }
1562
1563 $qty_batch += $detail->qty;
1564 }
1565 // Quantities in batch details are not same as stock quantity,
1566 // so we add a default batch record to complete and get same qty in parent and child table
1567 if ($ObjW->real != $qty_batch) {
1568 $ObjBatch = new Productbatch($this->db);
1569 $ObjBatch->batch = $valueforundefinedlot;
1570 $ObjBatch->qty = ($ObjW->real - $qty_batch);
1571 $ObjBatch->fk_product_stock = (int) $ObjW->id;
1572
1573 if ($ObjBatch->create($user, 1) < 0) {
1574 $error++;
1575 $this->errors = $ObjBatch->errors;
1576 } else {
1577 // we also add lot record if not exist
1578 $ObjLot = new Productlot($this->db);
1579 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
1580 if ($ObjLot->fetch(0, $this->id, $valueforundefinedlot) == 0) {
1581 $ObjLot->fk_product = $this->id;
1582 $ObjLot->entity = (int) $this->entity;
1583 $ObjLot->fk_user_creat = $user->id;
1584 $ObjLot->batch = $valueforundefinedlot;
1585 if ($ObjLot->create($user, 1) < 0) {
1586 $error++;
1587 $this->errors = $ObjLot->errors;
1588 }
1589 }
1590 }
1591 }
1592 }
1593 }
1594
1595 $sql = "UPDATE ".$this->db->prefix()."product";
1596 $sql .= " SET label = '".$this->db->escape($this->label)."'";
1597
1598 if ($updatetype && ($this->isProduct() || $this->isService())) {
1599 $sql .= ", fk_product_type = ".((int) $this->type);
1600 }
1601
1602 $sql .= ", ref = '".$this->db->escape($this->ref)."'";
1603 $sql .= ", ref_ext = ".(!empty($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1604 $sql .= ", default_vat_code = ".($this->default_vat_code ? "'".$this->db->escape($this->default_vat_code)."'" : "null");
1605 $sql .= ", tva_tx = ".((float) $this->tva_tx);
1606 $sql .= ", recuperableonly = ".((int) $this->tva_npr);
1607 $sql .= ", localtax1_tx = ".((float) $this->localtax1_tx);
1608 $sql .= ", localtax2_tx = ".((float) $this->localtax2_tx);
1609 $sql .= ", localtax1_type = ".($this->localtax1_type != '' ? "'".$this->db->escape($this->localtax1_type)."'" : "'0'");
1610 $sql .= ", localtax2_type = ".($this->localtax2_type != '' ? "'".$this->db->escape($this->localtax2_type)."'" : "'0'");
1611
1612 $sql .= ", barcode = ".(empty($this->barcode) ? "null" : "'".$this->db->escape($this->barcode)."'");
1613 $sql .= ", fk_barcode_type = ".(empty($this->barcode_type) ? "null" : $this->db->escape((string) $this->barcode_type));
1614
1615 $sql .= ", tosell = ".(int) $this->status;
1616 $sql .= ", tobuy = ".(int) $this->status_buy;
1617 $sql .= ", tobatch = ".((empty($this->status_batch) || $this->status_batch < 0) ? '0' : (int) $this->status_batch);
1618 $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);
1619 $sql .= ", batch_mask = '".$this->db->escape($this->batch_mask)."'";
1620
1621 $sql .= ", finished = ".((!isset($this->finished) || $this->finished < 0 || $this->finished === '') ? "null" : (int) $this->finished);
1622 $sql .= ", fk_default_bom = ".((!isset($this->fk_default_bom) || $this->fk_default_bom < 0 || $this->fk_default_bom == '') ? "null" : (int) $this->fk_default_bom);
1623 $sql .= ", net_measure = ".($this->net_measure != '' ? "'".$this->db->escape($this->net_measure)."'" : 'null');
1624 $sql .= ", net_measure_units = ".((string) $this->net_measure_units != '' ? ((int) $this->net_measure_units) : 'null');
1625 $sql .= ", weight = ".($this->weight != '' ? "'".$this->db->escape($this->weight)."'" : 'null');
1626 $sql .= ", weight_units = ".((string) $this->weight_units != '' ? ((int) $this->weight_units) : 'null');
1627 $sql .= ", length = ".($this->length != '' ? "'".$this->db->escape($this->length)."'" : 'null');
1628 $sql .= ", length_units = ".((string) $this->length_units != '' ? ((int) $this->length_units) : 'null');
1629 $sql .= ", width= ".($this->width != '' ? "'".$this->db->escape($this->width)."'" : 'null');
1630 $sql .= ", width_units = ".((string) $this->width_units != '' ? ((int) $this->width_units) : 'null');
1631 $sql .= ", height = ".($this->height != '' ? "'".$this->db->escape($this->height)."'" : 'null');
1632 $sql .= ", height_units = ".((string) $this->height_units != '' ? ((int) $this->height_units) : 'null');
1633 $sql .= ", surface = ".($this->surface != '' ? "'".$this->db->escape($this->surface)."'" : 'null');
1634 $sql .= ", surface_units = ".((string) $this->surface_units != '' ? ((int) $this->surface_units) : 'null');
1635 $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null');
1636 $sql .= ", volume_units = ".((string) $this->volume_units != '' ? ((int) $this->volume_units) : 'null');
1637 $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? ((int) $this->fk_default_warehouse) : 'null');
1638 $sql .= ", fk_default_workstation = ".($this->fk_default_workstation > 0 ? ((int) $this->fk_default_workstation) : 'null');
1639 $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null');
1640 $sql .= ", description = '".$this->db->escape($this->description)."'";
1641 $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null');
1642 $sql .= ", customcode = '".$this->db->escape($this->customcode)."'";
1643 $sql .= ", fk_country = ".($this->country_id > 0 ? (int) $this->country_id : 'null');
1644 $sql .= ", fk_state = ".($this->state_id > 0 ? (int) $this->state_id : 'null');
1645 $sql .= ", lifetime = ".($this->lifetime > 0 ? (int) $this->lifetime : 'null');
1646 $sql .= ", qc_frequency = ".($this->qc_frequency > 0 ? (int) $this->qc_frequency : 'null');
1647 $sql .= ", note = ".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : 'null');
1648 $sql .= ", note_public = ".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : 'null');
1649 $sql .= ", duration = '".$this->db->escape($this->duration_value.$this->duration_unit)."'";
1650 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1651 $sql .= ", accountancy_code_buy = '" . $this->db->escape($this->accountancy_code_buy) . "'";
1652 $sql .= ", accountancy_code_buy_intra = '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1653 $sql .= ", accountancy_code_buy_export = '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1654 $sql .= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell) . "'";
1655 $sql .= ", accountancy_code_sell_intra= '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1656 $sql .= ", accountancy_code_sell_export= '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1657 }
1658 $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null");
1659 $sql .= ", cost_price = ".($this->cost_price != '' ? ((float) $this->cost_price) : 'null');
1660 $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit);
1661 $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1);
1662 $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL');
1663 $sql .= ", fk_user_modif = ".($user->id > 0 ? (int) $user->id : 'NULL');
1664 $sql .= ", mandatory_period = ".((int) $this->mandatory_period);
1665 $sql .= ", stockable_product = ".(int) $this->stockable_product;
1666 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
1667 $sql .= ", packaging = ".(float) $this->packaging;
1668 }
1669
1670 // stock field is not here because it is a denormalized value from product_stock.
1671 $sql .= " WHERE rowid = ".((int) $id);
1672
1673 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1674
1675 $resql = $this->db->query($sql);
1676 if ($resql) {
1677 $this->id = $id;
1678
1679 // Multilangs
1680 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1681 if ($this->setMultiLangs($user) < 0) {
1682 $this->db->rollback();
1683 return -2;
1684 }
1685 }
1686
1687 $action = 'update';
1688
1689 // update accountancy for this entity
1690 if (!$error && getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
1691 $this->db->query("DELETE FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = " . ((int) $this->id) . " AND entity = " . ((int) $conf->entity));
1692
1693 $sql = "INSERT INTO " . $this->db->prefix() . "product_perentity (";
1694 $sql .= " fk_product";
1695 $sql .= ", entity";
1696 $sql .= ", accountancy_code_buy";
1697 $sql .= ", accountancy_code_buy_intra";
1698 $sql .= ", accountancy_code_buy_export";
1699 $sql .= ", accountancy_code_sell";
1700 $sql .= ", accountancy_code_sell_intra";
1701 $sql .= ", accountancy_code_sell_export";
1702 $sql .= ") VALUES (";
1703 $sql .= ((int) $this->id);
1704 $sql .= ", " . ((int) $conf->entity);
1705 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy) . "'";
1706 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_intra) . "'";
1707 $sql .= ", '" . $this->db->escape($this->accountancy_code_buy_export) . "'";
1708 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell) . "'";
1709 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_intra) . "'";
1710 $sql .= ", '" . $this->db->escape($this->accountancy_code_sell_export) . "'";
1711 $sql .= ")";
1712 $result = $this->db->query($sql);
1713 if (!$result) {
1714 $error++;
1715 $this->error = 'ErrorFailedToUpdateAccountancyForEntity';
1716 }
1717 }
1718
1719 if (!$this->hasbatch() && $this->oldcopy->hasbatch()) {
1720 // Selection of all product stock movements that contains batchs
1721 $sql = 'SELECT pb.qty, ps.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb';
1722 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = pb.fk_product_stock)';
1723 $sql .= ' WHERE ps.fk_product = '.(int) $this->id;
1724
1725 $resql = $this->db->query($sql);
1726 if ($resql) {
1727 $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S');
1728
1729 while ($obj = $this->db->fetch_object($resql)) {
1730 $value = $obj->qty;
1731 $fk_entrepot = $obj->fk_entrepot;
1732 $price = 0;
1733 $dlc = '';
1734 $dluo = '';
1735 $batch = $obj->batch;
1736
1737 // To know how to revert stockMouvement (add or remove)
1738 $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add
1739 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1740 $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true);
1741
1742 if ($res > 0) {
1743 $label = $langs->trans('BatchStockMouvementAddInGlobal');
1744 $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0);
1745 if ($res < 0) {
1746 $error++;
1747 }
1748 } else {
1749 $error++;
1750 }
1751 }
1752 }
1753 }
1754
1755 // Actions on extra fields
1756 if (!$error) {
1757 $result = $this->insertExtraFields();
1758 if ($result < 0) {
1759 $error++;
1760 }
1761 }
1762
1763 if (!$error && !$notrigger) {
1764 // Call trigger
1765 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
1766 if ($result < 0) {
1767 $error++;
1768 }
1769 // End call triggers
1770 }
1771
1772 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
1773 // We remove directory
1774 if ($conf->product->dir_output) {
1775 $olddir = $conf->product->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
1776 $newdir = $conf->product->dir_output."/".dol_sanitizeFileName($this->ref);
1777 if (file_exists($olddir)) {
1778 // include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1779 // $res = dol_move($olddir, $newdir);
1780 // do not use dol_move with directory
1781 $res = @rename($olddir, $newdir);
1782 if (!$res) {
1783 $langs->load("errors");
1784 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
1785 $error++;
1786 } else {
1787 // to keep old entries with the new dir
1788 require_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
1789 $ecmfiles = new EcmFiles($this->db);
1790 $ecmfiles->updateAfterRename("produit/".dol_sanitizeFileName($this->oldcopy->ref), "produit/".dol_sanitizeFileName($this->ref));
1791 }
1792 }
1793 }
1794 }
1795
1796 if (!$error) {
1797 if (isModEnabled('variants')) {
1798 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1799
1800 $comb = new ProductCombination($this->db);
1801
1802 foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
1803 $currcomb->updateProperties($this, $user);
1804 }
1805 }
1806
1807 $this->db->commit();
1808 return 1;
1809 } else {
1810 $this->db->rollback();
1811 return -$error;
1812 }
1813 } else {
1814 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1815 $langs->load("errors");
1816 if (empty($conf->barcode->enabled) || empty($this->barcode)) {
1817 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductAlreadyExists", $this->ref);
1818 } else {
1819 $this->error = $langs->trans("Error")." : ".$langs->trans("ErrorProductBarCodeAlreadyExists", $this->barcode);
1820 }
1821 $this->errors[] = $this->error;
1822 $this->db->rollback();
1823 return -1;
1824 } else {
1825 $this->error = $langs->trans("Error")." : ".$this->db->error()." - ".$sql;
1826 $this->errors[] = $this->error;
1827 $this->db->rollback();
1828 return -2;
1829 }
1830 }
1831 } else {
1832 $this->db->rollback();
1833 dol_syslog(get_class($this)."::Update fails verify ".implode(',', $this->errors), LOG_WARNING);
1834 return -3;
1835 }
1836 }
1837
1845 public function delete(User $user, $notrigger = 0)
1846 {
1847 global $conf;
1848 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1849
1850 $error = 0;
1851
1852 // Check parameters
1853 if (empty($this->id)) {
1854 $this->error = "Object must be fetched before calling delete";
1855 return -1;
1856 }
1857 if (($this->isProduct() && !$user->hasRight('produit', 'supprimer')) || ($this->isService() && !$user->hasRight('service', 'supprimer'))) {
1858 $this->error = "ErrorForbidden";
1859 return 0;
1860 }
1861
1862 $objectisused = $this->isObjectUsed($this->id);
1863 if (empty($objectisused)) {
1864 $this->db->begin();
1865
1866 if (empty($notrigger)) {
1867 // Call trigger
1868 $result = $this->call_trigger('PRODUCT_DELETE', $user);
1869 if ($result < 0) {
1870 $error++;
1871 }
1872 // End call triggers
1873 }
1874
1875 // Delete from product_batch on product delete
1876 if (!$error) {
1877 $sql = "DELETE FROM ".$this->db->prefix().'product_batch';
1878 $sql .= " WHERE fk_product_stock IN (";
1879 $sql .= "SELECT rowid FROM ".$this->db->prefix().'product_stock';
1880 $sql .= " WHERE fk_product = ".((int) $this->id).")";
1881
1882 $result = $this->db->query($sql);
1883 if (!$result) {
1884 $error++;
1885 $this->errors[] = $this->db->lasterror();
1886 }
1887 }
1888
1889 // Delete all child tables
1890 if (!$error) {
1891 $elements = array('product_fournisseur_price', 'product_price', 'product_lang', 'categorie_product', 'product_stock', 'product_customer_price', 'product_lot'); // product_batch is done before
1892 foreach ($elements as $table) {
1893 if (!$error) {
1894 $sql = "DELETE FROM ".$this->db->prefix().$table;
1895 $sql .= " WHERE fk_product = ".(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 }
1905
1906 if (!$error && isModEnabled('variants')) {
1907 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1908 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1909
1910 //If it is a parent product, then we remove the association with child products
1911 $prodcomb = new ProductCombination($this->db);
1912
1913 if ($prodcomb->deleteByFkProductParent($user, $this->id) < 0) {
1914 $error++;
1915 $this->errors[] = 'Error deleting combinations';
1916 }
1917
1918 //We also check if it is a child product
1919 if (!$error && ($prodcomb->fetchByFkProductChild($this->id) > 0) && ($prodcomb->delete($user) < 0)) {
1920 $error++;
1921 $this->errors[] = 'Error deleting child combination';
1922 }
1923 }
1924
1925 // Delete from product_association
1926 if (!$error) {
1927 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
1928 $sql .= " WHERE fk_product_pere = ".(int) $this->id." OR fk_product_fils = ".(int) $this->id;
1929
1930 $result = $this->db->query($sql);
1931 if (!$result) {
1932 $error++;
1933 $this->errors[] = $this->db->lasterror();
1934 }
1935 }
1936
1937 // Remove extrafields
1938 if (!$error) {
1939 $result = $this->deleteExtraFields();
1940 if ($result < 0) {
1941 $error++;
1942 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1943 }
1944 }
1945
1946 // Delete product
1947 if (!$error) {
1948 $sqlz = "DELETE FROM ".$this->db->prefix()."product";
1949 $sqlz .= " WHERE rowid = ".(int) $this->id;
1950
1951 $resultz = $this->db->query($sqlz);
1952 if (!$resultz) {
1953 $error++;
1954 $this->errors[] = $this->db->lasterror();
1955 }
1956 }
1957
1958 // Delete record into ECM index and physically
1959 if (!$error) {
1960 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1961 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1962 if (!$res) {
1963 $error++;
1964 }
1965 }
1966
1967 if (!$error) {
1968 // We remove directory
1969 $ref = dol_sanitizeFileName($this->ref);
1970 if ($conf->product->dir_output) {
1971 $dir = $conf->product->dir_output."/".$ref;
1972 if (file_exists($dir)) {
1973 $res = @dol_delete_dir_recursive($dir);
1974 if (!$res) {
1975 $this->errors[] = 'ErrorFailToDeleteDir';
1976 $error++;
1977 }
1978 }
1979 }
1980 }
1981
1982 if (!$error) {
1983 $this->db->commit();
1984 return 1;
1985 } else {
1986 foreach ($this->errors as $errmsg) {
1987 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1988 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1989 }
1990 $this->db->rollback();
1991 return -$error;
1992 }
1993 } else {
1994 $this->error = "ErrorRecordIsUsedCantDelete";
1995 return 0;
1996 }
1997 }
1998
2004 public static function getSellOrEatByMandatoryList()
2005 {
2006 global $langs;
2007
2008 $sellByLabel = $langs->trans('SellByDate');
2009 $eatByLabel = $langs->trans('EatByDate');
2010 return array(
2011 self::SELL_OR_EAT_BY_MANDATORY_ID_NONE => $langs->trans('BatchSellOrEatByMandatoryNone'),
2012 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY => $sellByLabel,
2013 self::SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY => $eatByLabel,
2014 self::SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT => $langs->trans('BatchSellOrEatByMandatoryAll', $sellByLabel, $eatByLabel),
2015 );
2016 }
2017
2024 {
2025 $sellOrEatByMandatoryLabel = '';
2026
2027 $sellOrEatByMandatoryList = self::getSellOrEatByMandatoryList();
2028 if (isset($sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory])) {
2029 $sellOrEatByMandatoryLabel = $sellOrEatByMandatoryList[$this->sell_or_eat_by_mandatory];
2030 }
2031
2032 return $sellOrEatByMandatoryLabel;
2033 }
2034
2041 public function setMultiLangs($user)
2042 {
2043 global $langs;
2044
2045 $langs_available = $langs->get_available_languages(DOL_DOCUMENT_ROOT, 0, 2);
2046 $current_lang = $langs->getDefaultLang();
2047
2048 foreach ($langs_available as $key => $value) {
2049 if ($key == $current_lang) {
2050 $sql = "SELECT rowid";
2051 $sql .= " FROM ".$this->db->prefix()."product_lang";
2052 $sql .= " WHERE fk_product = ".((int) $this->id);
2053 $sql .= " AND lang = '".$this->db->escape($key)."'";
2054
2055 $result = $this->db->query($sql);
2056
2057 if ($this->db->num_rows($result)) { // if there is already a description line for this language
2058 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
2059 $sql2 .= " SET ";
2060 $sql2 .= " label='".$this->db->escape($this->label)."',";
2061 $sql2 .= " description='".$this->db->escape($this->description)."'";
2062 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2063 $sql2 .= ", note='".$this->db->escape($this->other)."'";
2064 }
2065 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
2066 } else {
2067 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
2068 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2069 $sql2 .= ", note";
2070 }
2071 $sql2 .= ")";
2072 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->label)."',";
2073 $sql2 .= " '".$this->db->escape($this->description)."'";
2074 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2075 $sql2 .= ", '".$this->db->escape($this->other)."'";
2076 }
2077 $sql2 .= ")";
2078 }
2079 dol_syslog(get_class($this).'::setMultiLangs key = current_lang = '.$key);
2080 if (!$this->db->query($sql2)) {
2081 $this->error = $this->db->lasterror();
2082 return -1;
2083 }
2084 } elseif (isset($this->multilangs[$key])) {
2085 if (empty($this->multilangs[$key]["label"])) {
2086 $this->errors[] = $key . ' : ' . $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Label"));
2087 return -1;
2088 }
2089
2090 $sql = "SELECT rowid";
2091 $sql .= " FROM ".$this->db->prefix()."product_lang";
2092 $sql .= " WHERE fk_product = ".((int) $this->id);
2093 $sql .= " AND lang = '".$this->db->escape($key)."'";
2094
2095 $result = $this->db->query($sql);
2096
2097 if ($this->db->num_rows($result)) { // if there is already a description line for this language
2098 $sql2 = "UPDATE ".$this->db->prefix()."product_lang";
2099 $sql2 .= " SET ";
2100 $sql2 .= " label = '".$this->db->escape($this->multilangs["$key"]["label"])."',";
2101 $sql2 .= " description = '".$this->db->escape($this->multilangs["$key"]["description"])."'";
2102 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2103 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2104 $sql2 .= ", note = '".$this->db->escape($this->multilangs["$key"]["other"])."'";
2105 }
2106 $sql2 .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($key)."'";
2107 } else {
2108 $sql2 = "INSERT INTO ".$this->db->prefix()."product_lang (fk_product, lang, label, description";
2109 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2110 $sql2 .= ", note";
2111 }
2112 $sql2 .= ")";
2113 $sql2 .= " VALUES(".((int) $this->id).",'".$this->db->escape($key)."','".$this->db->escape($this->multilangs["$key"]["label"])."',";
2114 $sql2 .= " '".$this->db->escape($this->multilangs["$key"]["description"])."'";
2115 if (getDolGlobalString('PRODUCT_USE_OTHER_FIELD_IN_TRANSLATION')) {
2116 // @phan-suppress-next-line PhanTypeInvalidDimOffset
2117 $sql2 .= ", '".$this->db->escape($this->multilangs["$key"]["other"])."'";
2118 }
2119 $sql2 .= ")";
2120 }
2121
2122 // We do not save if main fields are empty
2123 if ($this->multilangs["$key"]["label"] || $this->multilangs["$key"]["description"]) {
2124 if (!$this->db->query($sql2)) {
2125 $this->error = $this->db->lasterror();
2126 return -1;
2127 }
2128 }
2129 } else {
2130 // language is not current language and we didn't provide a multilang description for this language
2131 }
2132 }
2133
2134 // Call trigger
2135 $result = $this->call_trigger('PRODUCT_SET_MULTILANGS', $user);
2136 if ($result < 0) {
2137 $this->error = $this->db->lasterror();
2138 return -1;
2139 }
2140 // End call triggers
2141
2142 return 1;
2143 }
2144
2153 public function delMultiLangs($langtodelete, $user)
2154 {
2155 $sql = "DELETE FROM ".$this->db->prefix()."product_lang";
2156 $sql .= " WHERE fk_product = ".((int) $this->id)." AND lang = '".$this->db->escape($langtodelete)."'";
2157
2158 dol_syslog(get_class($this).'::delMultiLangs', LOG_DEBUG);
2159 $result = $this->db->query($sql);
2160 if ($result) {
2161 // Call trigger
2162 $result = $this->call_trigger('PRODUCT_DEL_MULTILANGS', $user);
2163 if ($result < 0) {
2164 $this->error = $this->db->lasterror();
2165 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2166 return -1;
2167 }
2168 // End call triggers
2169 unset($this->multilangs[$langtodelete]);
2170 return 1;
2171 } else {
2172 $this->error = $this->db->lasterror();
2173 dol_syslog(get_class($this).'::delMultiLangs error='.$this->error, LOG_ERR);
2174 return -1;
2175 }
2176 }
2177
2186 public function setAccountancyCode($type, $value)
2187 {
2188 global $user;
2189
2190 $error = 0;
2191
2192 $this->db->begin();
2193
2194 if ($type == 'buy') {
2195 $field = 'accountancy_code_buy';
2196 } elseif ($type == 'buy_intra') {
2197 $field = 'accountancy_code_buy_intra';
2198 } elseif ($type == 'buy_export') {
2199 $field = 'accountancy_code_buy_export';
2200 } elseif ($type == 'sell') {
2201 $field = 'accountancy_code_sell';
2202 } elseif ($type == 'sell_intra') {
2203 $field = 'accountancy_code_sell_intra';
2204 } elseif ($type == 'sell_export') {
2205 $field = 'accountancy_code_sell_export';
2206 } else {
2207 return -1;
2208 }
2209
2210 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET ";
2211 $sql .= "$field = '".$this->db->escape($value)."'";
2212 $sql .= " WHERE rowid = ".((int) $this->id);
2213
2214 dol_syslog(__METHOD__, LOG_DEBUG);
2215 $resql = $this->db->query($sql);
2216
2217 if ($resql) {
2218 // Call trigger
2219 $result = $this->call_trigger('PRODUCT_MODIFY', $user);
2220 if ($result < 0) {
2221 $error++;
2222 }
2223 // End call triggers
2224
2225 if ($error) {
2226 $this->db->rollback();
2227 return -1;
2228 }
2229
2230 $this->$field = $value;
2231
2232 $this->db->commit();
2233 return 1;
2234 } else {
2235 $this->error = $this->db->lasterror();
2236 $this->db->rollback();
2237 return -1;
2238 }
2239 }
2240
2246 public function getMultiLangs()
2247 {
2248 global $langs;
2249
2250 $current_lang = $langs->getDefaultLang();
2251
2252 $sql = "SELECT lang, label, description, note as other";
2253 $sql .= " FROM ".$this->db->prefix()."product_lang";
2254 $sql .= " WHERE fk_product = ".((int) $this->id);
2255
2256 $result = $this->db->query($sql);
2257 if ($result) {
2258 while ($obj = $this->db->fetch_object($result)) {
2259 //print 'lang='.$obj->lang.' current='.$current_lang.'<br>';
2260 if ($obj->lang == $current_lang) { // si on a les traduct. dans la langue courante on les charge en infos principales.
2261 $this->label = $obj->label;
2262 $this->description = $obj->description;
2263 $this->other = $obj->other;
2264 }
2265 $this->multilangs[(string) $obj->lang]["label"] = $obj->label;
2266 $this->multilangs[(string) $obj->lang]["description"] = $obj->description;
2267 $this->multilangs[(string) $obj->lang]["other"] = $obj->other;
2268 }
2269 return 1;
2270 } else {
2271 $this->error = "Error: ".$this->db->lasterror()." - ".$sql;
2272 return -1;
2273 }
2274 }
2275
2282 private function getArrayForPriceCompare($level = 0)
2283 {
2284 $testExit = array('multiprices','multiprices_ttc','multiprices_base_type','multiprices_min','multiprices_min_ttc','multiprices_tva_tx','multiprices_recuperableonly');
2285
2286 foreach ($testExit as $field) {
2287 if (!isset($this->$field)) {
2288 return array();
2289 }
2290 $tmparray = $this->$field;
2291 if (!isset($tmparray[$level])) {
2292 return array();
2293 }
2294 }
2295
2296 $lastPrice = array(
2297 'level' => $level ? $level : 1,
2298 'multiprices' => (float) $this->multiprices[$level],
2299 'multiprices_ttc' => (float) $this->multiprices_ttc[$level],
2300 'multiprices_base_type' => $this->multiprices_base_type[$level],
2301 'multiprices_min' => (float) $this->multiprices_min[$level],
2302 'multiprices_min_ttc' => (float) $this->multiprices_min_ttc[$level],
2303 'multiprices_tva_tx' => (float) $this->multiprices_tva_tx[$level],
2304 'multiprices_recuperableonly' => (float) $this->multiprices_recuperableonly[$level],
2305 );
2306
2307 return $lastPrice;
2308 }
2309
2310
2311 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2319 private function _log_price($user, $level = 0)
2320 {
2321 // phpcs:enable
2322 global $conf;
2323
2324 $now = dol_now();
2325
2326 // Clean parameters
2327 if (empty($this->price_by_qty)) {
2328 $this->price_by_qty = 0;
2329 }
2330
2331 // Add new price
2332 $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,";
2333 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, price_min,price_min_ttc,price_by_qty,entity,fk_price_expression) ";
2334 $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).",";
2335 $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');
2336 $sql .= ")";
2337
2338 dol_syslog(get_class($this)."::_log_price", LOG_DEBUG);
2339 $resql = $this->db->query($sql);
2340 if (!$resql) {
2341 $this->error = $this->db->lasterror();
2342 dol_print_error($this->db);
2343 return -1;
2344 } else {
2345 return 1;
2346 }
2347 }
2348
2349
2350 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2358 public function log_price_delete($user, $rowid)
2359 {
2360 // phpcs:enable
2361 $sql = "DELETE FROM ".$this->db->prefix()."product_price_by_qty";
2362 $sql .= " WHERE fk_product_price = ".((int) $rowid);
2363 $resql = $this->db->query($sql);
2364
2365 $sql = "DELETE FROM ".$this->db->prefix()."product_price";
2366 $sql .= " WHERE rowid=".((int) $rowid);
2367 $resql = $this->db->query($sql);
2368 if ($resql) {
2369 return 1;
2370 } else {
2371 $this->error = $this->db->lasterror();
2372 return -1;
2373 }
2374 }
2375
2376
2386 public function getSellPrice($thirdparty_seller, $thirdparty_buyer, $pqp = 0)
2387 {
2388 global $hookmanager, $action;
2389
2390 // Call hook if any
2391 if (is_object($hookmanager)) {
2392 $parameters = array('thirdparty_seller' => $thirdparty_seller, 'thirdparty_buyer' => $thirdparty_buyer, 'pqp' => $pqp);
2393 // Note that $action and $object may have been modified by some hooks
2394 $reshook = $hookmanager->executeHooks('getSellPrice', $parameters, $this, $action);
2395 if ($reshook > 0) {
2396 return $hookmanager->resArray;
2397 }
2398 }
2399
2400 // Update if prices fields are defined
2401 $tva_tx = get_default_tva($thirdparty_seller, $thirdparty_buyer, $this->id);
2402 $tva_npr = get_default_npr($thirdparty_seller, $thirdparty_buyer, $this->id);
2403 if (empty($tva_tx)) {
2404 $tva_npr = 0;
2405 }
2406
2407 $pu_ht = $this->price;
2408 $pu_ttc = $this->price_ttc;
2409 $price_min = $this->price_min;
2410 $price_min_ttc = $this->price_min_ttc;
2411 $price_base_type = $this->price_base_type;
2412
2413 // if price by customer / level
2414 if (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
2415 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2416
2417 $prodcustprice = new ProductCustomerPrice($this->db);
2418
2419 $filter = array('t.fk_product' => (string) $this->id, 't.fk_soc' => (string) $thirdparty_buyer->id);
2420
2421 // If a price per customer exist
2422 $pricebycustomerexist = false;
2423 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2424 if ($result) {
2425 if (count($prodcustprice->lines) > 0) {
2426 $date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours
2427 foreach ($prodcustprice->lines as $k => $custprice_line) {
2428 if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {
2429 $pricebycustomerexist = true;
2430 $pu_ht = price($custprice_line->price);
2431 $price_min = price($custprice_line->price_min);
2432 $price_min_ttc = price($custprice_line->price_min_ttc);
2433 $pu_ttc = price($custprice_line->price_ttc);
2434 $price_base_type = $custprice_line->price_base_type;
2435 $tva_tx = $custprice_line->tva_tx;
2436 if ($custprice_line->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2437 $tva_tx .= ' (' . $custprice_line->default_vat_code . ')';
2438 }
2439 $tva_npr = $custprice_line->recuperableonly;
2440 if (empty($tva_tx)) {
2441 $tva_npr = 0;
2442 }
2443 break;
2444 }
2445 }
2446 }
2447 }
2448
2449 if (!$pricebycustomerexist && !empty($thirdparty_buyer->price_level)) {
2450 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2451 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2452 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2453 $price_min_ttc = $this->multiprices_min_ttc[$thirdparty_buyer->price_level];
2454 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2455 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) {
2456 // using this option is a bug. kept for backward compatibility
2457 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2458 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2459 }
2460 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2461 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2462 }
2463 if (empty($tva_tx)) {
2464 $tva_npr = 0;
2465 }
2466 }
2467 }
2468 } elseif (getDolGlobalString('PRODUIT_MULTIPRICES') && !empty($thirdparty_buyer->price_level)) { // // If price per segment
2469 $pu_ht = $this->multiprices[$thirdparty_buyer->price_level];
2470 $pu_ttc = $this->multiprices_ttc[$thirdparty_buyer->price_level];
2471 $price_min = $this->multiprices_min[$thirdparty_buyer->price_level];
2472 $price_min_ttc = $this->multiprices_min_ttc[$thirdparty_buyer->price_level];
2473 $price_base_type = $this->multiprices_base_type[$thirdparty_buyer->price_level];
2474 if (getDolGlobalString('PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL')) {
2475 // using this option is a bug. kept for backward compatibility
2476 if (isset($this->multiprices_tva_tx[$thirdparty_buyer->price_level])) {
2477 $tva_tx = $this->multiprices_tva_tx[$thirdparty_buyer->price_level];
2478 }
2479 if (isset($this->multiprices_recuperableonly[$thirdparty_buyer->price_level])) {
2480 $tva_npr = $this->multiprices_recuperableonly[$thirdparty_buyer->price_level];
2481 }
2482 if (empty($tva_tx)) {
2483 $tva_npr = 0;
2484 }
2485 }
2486 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
2487 // If price per customer
2488 require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
2489
2490 $prodcustprice = new ProductCustomerPrice($this->db);
2491
2492 $filter = array('t.fk_product' => (string) $this->id, 't.fk_soc' => (string) $thirdparty_buyer->id);
2493
2494 $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
2495 if ($result) {
2496 if (count($prodcustprice->lines) > 0) {
2497 $date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours
2498 foreach ($prodcustprice->lines as $k => $custprice_line) {
2499 if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {
2500 $pu_ht = price($custprice_line->price);
2501 $price_min = price($custprice_line->price_min);
2502 $price_min_ttc = price($custprice_line->price_min_ttc);
2503 $pu_ttc = price($custprice_line->price_ttc);
2504 $price_base_type = $custprice_line->price_base_type;
2505 $tva_tx = $custprice_line->tva_tx;
2506 if ($custprice_line->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
2507 $tva_tx .= ' (' . $custprice_line->default_vat_code . ')';
2508 }
2509 $tva_npr = $custprice_line->recuperableonly;
2510 if (empty($tva_tx)) {
2511 $tva_npr = 0;
2512 }
2513 break;
2514 }
2515 }
2516 }
2517 }
2518 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
2519 // If price per quantity
2520 if ($this->prices_by_qty[0]) {
2521 // yes, this product has some prices per quantity
2522 // Search price into product_price_by_qty from $this->id
2523 foreach ($this->prices_by_qty_list[0] as $priceforthequantityarray) {
2524 if ($priceforthequantityarray['rowid'] != $pqp) {
2525 continue;
2526 }
2527 // We found the price
2528 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2529 $pu_ht = $priceforthequantityarray['unitprice'];
2530 } else {
2531 $pu_ttc = $priceforthequantityarray['unitprice'];
2532 }
2533 break;
2534 }
2535 }
2536 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES')) {
2537 // If price per quantity and customer
2538 if ($this->prices_by_qty[$thirdparty_buyer->price_level]) {
2539 // yes, this product has some prices per quantity
2540 // Search price into product_price_by_qty from $this->id
2541 foreach ($this->prices_by_qty_list[$thirdparty_buyer->price_level] as $priceforthequantityarray) {
2542 if ($priceforthequantityarray['rowid'] != $pqp) {
2543 continue;
2544 }
2545 // We found the price
2546 if ($priceforthequantityarray['price_base_type'] == 'HT') {
2547 $pu_ht = $priceforthequantityarray['unitprice'];
2548 } else {
2549 $pu_ttc = $priceforthequantityarray['unitprice'];
2550 }
2551 break;
2552 }
2553 }
2554 }
2555
2556 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);
2557 }
2558
2559 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2573 public function get_buyprice($prodfournprice, $qty, $product_id = 0, $fourn_ref = '', $fk_soc = 0)
2574 {
2575 // phpcs:enable
2576 global $action, $hookmanager;
2577
2578 // Call hook if any
2579 if (is_object($hookmanager)) {
2580 $parameters = array(
2581 'prodfournprice' => $prodfournprice,
2582 'qty' => $qty,
2583 'product_id' => $product_id,
2584 'fourn_ref' => $fourn_ref,
2585 'fk_soc' => $fk_soc,
2586 );
2587 // Note that $action and $object may have been modified by some hooks
2588 $reshook = $hookmanager->executeHooks('getBuyPrice', $parameters, $this, $action);
2589 if ($reshook > 0) {
2590 return $hookmanager->resArray;
2591 }
2592 }
2593
2594 $result = 0;
2595
2596 // 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)
2597 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2598 $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,";
2599 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2600 $sql .= " pfp.packaging";
2601 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2602 $sql .= " WHERE pfp.rowid = ".((int) $prodfournprice);
2603 if ($qty > 0) {
2604 $sql .= " AND pfp.quantity <= ".((float) $qty);
2605 }
2606 $sql .= " ORDER BY pfp.quantity DESC";
2607
2608 dol_syslog(get_class($this)."::get_buyprice first search by prodfournprice/qty", LOG_DEBUG);
2609 $resql = $this->db->query($sql);
2610 if ($resql) {
2611 $obj = $this->db->fetch_object($resql);
2612 if ($obj && $obj->quantity > 0) { // If we found a supplier prices from the id of supplier price
2613 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2614 $prod_supplier = new ProductFournisseur($this->db);
2615 $prod_supplier->product_fourn_price_id = $obj->rowid;
2616 $prod_supplier->id = $obj->fk_product;
2617 $prod_supplier->fourn_qty = $obj->quantity;
2618 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2619 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2620
2621 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2622 $priceparser = new PriceParser($this->db);
2623 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2624 if ($price_result >= 0) {
2625 $obj->price = $price_result;
2626 }
2627 }
2628 $this->product_fourn_price_id = $obj->rowid;
2629 $this->buyprice = $obj->price; // deprecated
2630 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product of supplier
2631 $this->fourn_price_base_type = 'HT'; // Price base type
2632 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2633 $this->ref_fourn = $obj->ref_supplier; // deprecated
2634 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2635 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2636 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2637 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2638 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2639 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2640 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2641 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2642 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2643 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2644 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2645 $this->packaging = (float) $obj->packaging;
2646 }
2647 // Expose the supplier minimum purchase quantity so callers can enforce qty_min
2648 // on top of packaging-multiple rounding (#38783). pfp.quantity is the lowest
2649 // qty for which this price line is valid; for "below-min" callers this is the
2650 // floor they must round up to.
2651 $this->fourn_qty = $obj->quantity;
2652 $result = $obj->fk_product;
2653 return $result;
2654 } else { // If not found
2655 // 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.
2656 $sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.remise_percent, pfp.fk_soc,";
2657 $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,";
2658 $sql .= " pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code,";
2659 $sql .= " pfp.packaging";
2660 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as pfp";
2661 $sql .= " WHERE 1 = 1";
2662 if ($product_id > 0) {
2663 $sql .= " AND pfp.fk_product = ".((int) $product_id);
2664 }
2665 if ($fourn_ref != 'none') {
2666 $sql .= " AND pfp.ref_fourn = '".$this->db->escape($fourn_ref)."'";
2667 }
2668 if ($fk_soc > 0) {
2669 $sql .= " AND pfp.fk_soc = ".((int) $fk_soc);
2670 }
2671 if ($qty > 0) {
2672 $sql .= " AND pfp.quantity <= ".((float) $qty);
2673 }
2674 $sql .= " ORDER BY pfp.quantity DESC";
2675 $sql .= " LIMIT 1";
2676
2677 dol_syslog(get_class($this)."::get_buyprice second search from qty/ref/product_id", LOG_DEBUG);
2678 $resql = $this->db->query($sql);
2679 if ($resql) {
2680 $obj = $this->db->fetch_object($resql);
2681 if ($obj && $obj->quantity > 0) { // If found
2682 if (isModEnabled('dynamicprices') && !empty($obj->fk_supplier_price_expression)) {
2683 $prod_supplier = new ProductFournisseur($this->db);
2684 $prod_supplier->product_fourn_price_id = $obj->rowid;
2685 $prod_supplier->id = $obj->fk_product;
2686 $prod_supplier->fourn_qty = $obj->quantity;
2687 $prod_supplier->fourn_tva_tx = $obj->tva_tx;
2688 $prod_supplier->fk_supplier_price_expression = $obj->fk_supplier_price_expression;
2689
2690 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
2691 $priceparser = new PriceParser($this->db);
2692 $price_result = $priceparser->parseProductSupplier($prod_supplier);
2693 if ($price_result >= 0) {
2694 $obj->price = $price_result;
2695 }
2696 }
2697 $this->product_fourn_price_id = $obj->rowid;
2698 $this->buyprice = $obj->price; // deprecated
2699 $this->fourn_qty = $obj->quantity; // min quantity for price for a virtual supplier
2700 $this->fourn_pu = $obj->price / $obj->quantity; // Unit price of product for a virtual supplier
2701 $this->fourn_price_base_type = 'HT'; // Price base type for a virtual supplier
2702 $this->fourn_socid = $obj->fk_soc; // Company that offer this price
2703 $this->ref_fourn = $obj->ref_supplier; // deprecated
2704 $this->ref_supplier = $obj->ref_supplier; // Ref supplier
2705 $this->desc_supplier = $obj->desc_supplier; // desc supplier
2706 $this->remise_percent = $obj->remise_percent; // remise percent if present and not typed
2707 $this->vatrate_supplier = $obj->tva_tx; // Vat ref supplier
2708 $this->default_vat_code_supplier = $obj->default_vat_code; // Vat code supplier
2709 $this->fourn_multicurrency_price = $obj->multicurrency_price;
2710 $this->fourn_multicurrency_unitprice = $obj->multicurrency_unitprice;
2711 $this->fourn_multicurrency_tx = $obj->multicurrency_tx;
2712 $this->fourn_multicurrency_id = $obj->fk_multicurrency;
2713 $this->fourn_multicurrency_code = $obj->multicurrency_code;
2714 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2715 $this->packaging = (float) $obj->packaging;
2716 }
2717 // Expose pfp.quantity so callers can enforce qty_min on top of packaging
2718 // rounding (#38783).
2719 $this->fourn_qty = $obj->quantity;
2720 $result = $obj->fk_product;
2721 return $result;
2722 } else {
2723 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é.
2724 }
2725 } else {
2726 $this->error = $this->db->lasterror();
2727 return -3;
2728 }
2729 }
2730 } else {
2731 $this->error = $this->db->lasterror();
2732 return -2;
2733 }
2734 }
2735
2736
2755 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)
2756 {
2757 $lastPriceData = $this->getArrayForPriceCompare($level); // temporary store current price before update
2758
2759 $id = $this->id;
2760
2761 dol_syslog(get_class($this)."::update_price id=".$id." newprice=".$newprice." newpricebase=".$newpricebase." newminprice=".$newminprice." level=".$level." npr=".$newnpr." newdefaultvatcode=".$newdefaultvatcode);
2762
2763 // Clean parameters
2764 if (empty($this->tva_tx)) {
2765 $this->tva_tx = 0.0;
2766 }
2767 if (empty($newnpr)) {
2768 $newnpr = 0.0;
2769 }
2770 if (empty($newminprice)) {
2771 $newminprice = 0.0;
2772 }
2773
2774 // Check parameters
2775 if ($newvat === null || $newvat == '') { // Maintain '' for backwards compatibility
2776 $newvat = (float) $this->tva_tx;
2777 }
2778
2779 $localtaxtype1 = '';
2780 $localtaxtype2 = '';
2781
2782 // If multiprices are enabled, then we check if the current product is subject to price autogeneration
2783 // Price will be modified ONLY when the first one is the one that is being modified
2784 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && !$ignore_autogen && $this->price_autogen && ($level == 1)) {
2785 return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpbq);
2786 }
2787
2788 if (!empty($newminprice) && ($newminprice > $newprice)) {
2789 $this->error = 'ErrorPriceCantBeLowerThanMinPrice';
2790 return -1;
2791 }
2792
2793 if ($newprice === 0 || $newprice !== '') {
2794 if ($newpricebase == 'TTC') {
2795 $price_ttc = (float) price2num($newprice, 'MU');
2796 $price = (float) price2num($newprice) / (1 + ((float) $newvat / 100));
2797 $price = (float) price2num($price, 'MU');
2798
2799 if ((string) $newminprice != '0') {
2800 $price_min_ttc = (float) price2num($newminprice, 'MU');
2801 $price_min = (float) price2num($newminprice) / (1 + ($newvat / 100));
2802 $price_min = (float) price2num($price_min, 'MU');
2803 } else {
2804 $price_min = 0.0;
2805 $price_min_ttc = 0.0;
2806 }
2807 } else {
2808 $price = (float) price2num($newprice, 'MU');
2809 $price_ttc = ($newnpr != 1) ? (float) price2num($newprice) * (1 + ($newvat / 100)) : $price;
2810 $price_ttc = (float) price2num($price_ttc, 'MU');
2811
2812 if ((string) $newminprice != '0') {
2813 $price_min = (float) price2num($newminprice, 'MU');
2814 $price_min_ttc = (float) price2num($newminprice) * (1 + ($newvat / 100));
2815 $price_min_ttc = (float) price2num($price_min_ttc, 'MU');
2816 //print 'X'.$newminprice.'-'.$price_min;
2817 } else {
2818 $price_min = 0.0;
2819 $price_min_ttc = 0.0;
2820 }
2821 }
2822 //print 'x'.$id.'-'.$newprice.'-'.$newpricebase.'-'.$price.'-'.$price_ttc.'-'.$price_min.'-'.$price_min_ttc;
2823 if (count($localtaxes_array) > 0) {
2824 $localtaxtype1 = $localtaxes_array['0'];
2825 $localtax1 = $localtaxes_array['1'];
2826 $localtaxtype2 = $localtaxes_array['2'];
2827 $localtax2 = $localtaxes_array['3'];
2828 } else {
2829 // if array empty, we try to use the vat code
2830 if (!empty($newdefaultvatcode)) {
2831 global $mysoc;
2832 // Get record from code
2833 $sql = "SELECT t.rowid, t.code, t.recuperableonly as tva_npr, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
2834 $sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
2835 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$this->db->escape($mysoc->country_code)."'";
2836 $sql .= " AND t.taux = ".((float) $newdefaultvatcode)." AND t.active = 1";
2837 $sql .= " AND t.code = '".$this->db->escape($newdefaultvatcode)."'";
2838 $resql = $this->db->query($sql);
2839 if ($resql) {
2840 $obj = $this->db->fetch_object($resql);
2841 if ($obj) {
2842 $npr = $obj->tva_npr;
2843 $localtax1 = $obj->localtax1;
2844 $localtax2 = $obj->localtax2;
2845 $localtaxtype1 = $obj->localtax1_type;
2846 $localtaxtype2 = $obj->localtax2_type;
2847 }
2848 }
2849 } else {
2850 // old method. deprecated because we can't retrieve type
2851 $localtaxtype1 = '0';
2852 $localtax1 = get_localtax($newvat, 1);
2853 $localtaxtype2 = '0';
2854 $localtax2 = get_localtax($newvat, 2);
2855 }
2856 }
2857 if (empty($localtax1)) {
2858 $localtax1 = 0; // If = '' then = 0
2859 }
2860 if (empty($localtax2)) {
2861 $localtax2 = 0; // If = '' then = 0
2862 }
2863
2864 $this->db->begin();
2865
2866 // Ne pas mettre de quote sur les numeriques decimaux.
2867 // Ceci provoque des stockages avec arrondis en base au lieu des valeurs exactes.
2868 $sql = "UPDATE ".$this->db->prefix()."product SET";
2869 $sql .= " price_base_type = '".$this->db->escape($newpricebase)."',";
2870 $sql .= " price = ".(float) $price.",";
2871 $sql .= " price_ttc = ".(float) $price_ttc.",";
2872 $sql .= " price_min = ".(float) $price_min.",";
2873 $sql .= " price_min_ttc = ".(float) $price_min_ttc.",";
2874 $sql .= " localtax1_tx = ".($localtax1 >= 0 ? (float) $localtax1 : 'NULL').",";
2875 $sql .= " localtax2_tx = ".($localtax2 >= 0 ? (float) $localtax2 : 'NULL').",";
2876 $sql .= " localtax1_type = ".($localtaxtype1 != '' ? "'".$this->db->escape($localtaxtype1)."'" : "'0'").",";
2877 $sql .= " localtax2_type = ".($localtaxtype2 != '' ? "'".$this->db->escape($localtaxtype2)."'" : "'0'").",";
2878 $sql .= " default_vat_code = ".($newdefaultvatcode ? "'".$this->db->escape($newdefaultvatcode)."'" : "null").",";
2879 $sql .= " price_label = ".(!empty($price_label) ? "'".$this->db->escape($price_label)."'" : "null").",";
2880 $sql .= " tva_tx = ".(float) price2num($newvat).",";
2881 $sql .= " recuperableonly = '".$this->db->escape((string) $newnpr)."'";
2882 $sql .= " WHERE rowid = ".((int) $id);
2883
2884 dol_syslog(get_class($this)."::update_price", LOG_DEBUG);
2885 $resql = $this->db->query($sql);
2886 if ($resql) {
2887 $this->multiprices[$level] = $price;
2888 $this->multiprices_ttc[$level] = $price_ttc;
2889 $this->multiprices_min[$level] = $price_min;
2890 $this->multiprices_min_ttc[$level] = $price_min_ttc;
2891 $this->multiprices_base_type[$level] = $newpricebase;
2892 $this->multiprices_default_vat_code[$level] = $newdefaultvatcode;
2893 $this->multiprices_tva_tx[$level] = $newvat;
2894 $this->multiprices_recuperableonly[$level] = $newnpr;
2895
2896 $this->price = $price;
2897 $this->price_label = $price_label;
2898 $this->price_ttc = $price_ttc;
2899 $this->price_min = $price_min;
2900 $this->price_min_ttc = $price_min_ttc;
2901 $this->price_base_type = $newpricebase;
2902 $this->default_vat_code = $newdefaultvatcode;
2903 $this->tva_tx = $newvat;
2904 $this->tva_npr = $newnpr;
2905
2906 //Local taxes
2907 $this->localtax1_tx = $localtax1;
2908 $this->localtax2_tx = $localtax2;
2909 $this->localtax1_type = $localtaxtype1;
2910 $this->localtax2_type = $localtaxtype2;
2911
2912 // Price by quantity
2913 $this->price_by_qty = $newpbq;
2914
2915 // check if price have really change before log
2916 $newPriceData = $this->getArrayForPriceCompare($level);
2917 if (!empty(array_diff_assoc($newPriceData, $lastPriceData)) || (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES'))) {
2918 $this->_log_price($user, $level); // Save price for level into table product_price
2919 }
2920
2921 $this->level = $level; // Store level of price edited for trigger
2922
2923 // Call trigger
2924 if (!$notrigger) {
2925 $result = $this->call_trigger('PRODUCT_PRICE_MODIFY', $user);
2926 if ($result < 0) {
2927 $this->db->rollback();
2928 return -1;
2929 }
2930 }
2931 // End call triggers
2932
2933 $this->db->commit();
2934 } else {
2935 $this->db->rollback();
2936 $this->error = $this->db->lasterror();
2937 return -1;
2938 }
2939 }
2940
2941 return 1;
2942 }
2943
2951 public function setPriceExpression($expression_id)
2952 {
2953 global $user;
2954
2955 $this->fk_price_expression = $expression_id;
2956
2957 return $this->update($this->id, $user);
2958 }
2959
2972 public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_expression = 0, $ignore_price_load = 0, $ignore_lang_load = 0)
2973 {
2974 include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
2975
2976 global $conf;
2977
2978 dol_syslog(get_class($this)."::fetch id=".$id." ref=".$ref." ref_ext=".$ref_ext);
2979
2980 // Check parameters
2981 if (!$id && !$ref && !$ref_ext && !$barcode) {
2982 $this->error = 'ErrorWrongParameters';
2983 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2984 return -1;
2985 }
2986
2987 $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,";
2988 $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,";
2989 $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,";
2990 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.last_main_doc,";
2991 $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,";
2992 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
2993 $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,";
2994 } else {
2995 $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,";
2996 }
2997
2998 // For MultiCompany
2999 // PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity
3000 $separatedEntityPMP = false; // Set to true to get the AWP from table llx_product_perentity instead of field 'pmp' into llx_product.
3001 $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.
3002 $visibleWarehousesEntities = $conf->entity;
3003 if (getDolGlobalString('MULTICOMPANY_PRODUCT_SHARING_ENABLED')) {
3004 if (getDolGlobalString('MULTICOMPANY_PMP_PER_ENTITY_ENABLED')) {
3005 $checkPMPPerEntity = $this->db->query("SELECT pmp FROM " . $this->db->prefix() . "product_perentity WHERE fk_product = ".((int) $id)." AND entity = ".(int) $conf->entity);
3006 if ($this->db->num_rows($checkPMPPerEntity) > 0) {
3007 $separatedEntityPMP = true;
3008 }
3009 }
3010 global $mc;
3011 $separatedStock = true;
3012 if (isset($mc->sharings['stock']) && !empty($mc->sharings['stock'])) {
3013 $visibleWarehousesEntities .= "," . implode(",", $mc->sharings['stock']);
3014 }
3015 }
3016 if ($separatedEntityPMP) {
3017 $sql .= " ppe.pmp,";
3018 } else {
3019 $sql .= " p.pmp,";
3020 }
3021 $sql .= " p.datec, GREATEST(p.tms, pef.tms) AS tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.sell_or_eat_by_mandatory, p.batch_mask, p.fk_unit,";
3022 $sql .= " p.fk_price_expression, p.price_autogen, p.stockable_product, p.model_pdf,";
3023 $sql .= " p.price_label,";
3024 if ($separatedStock) {
3025 $sql .= " SUM(sp.reel) as stock";
3026 } else {
3027 $sql .= " p.stock";
3028 }
3029 $sql .= " FROM ".$this->db->prefix()."product as p";
3030 $sql .= " LEFT JOIN ".$this->db->prefix()."product_extrafields as pef ON pef.fk_object=p.rowid";
3031 if (getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED') || $separatedEntityPMP) {
3032 $sql .= " LEFT JOIN " . $this->db->prefix() . "product_perentity as ppe ON ppe.fk_product = p.rowid AND ppe.entity = " . ((int) $conf->entity);
3033 }
3034 if ($separatedStock) {
3035 $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)."))";
3036 }
3037
3038 if ($id) {
3039 $sql .= " WHERE p.rowid = ".((int) $id);
3040 } else {
3041 $sql .= " WHERE p.entity IN (".getEntity($this->element).")";
3042 if ($ref) {
3043 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
3044 } elseif ($ref_ext) {
3045 $sql .= " AND p.ref_ext = '".$this->db->escape($ref_ext)."'";
3046 } elseif ($barcode) {
3047 $sql .= " AND p.barcode = '".$this->db->escape($barcode)."'";
3048 }
3049 }
3050 if ($separatedStock) {
3051 $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,";
3052 $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,";
3053 $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,";
3054 $sql .= " p.length, p.length_units, p.width, p.width_units, p.height, p.height_units,";
3055 $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,";
3056 if (!getDolGlobalString('MAIN_PRODUCT_PERENTITY_SHARED')) {
3057 $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,";
3058 } else {
3059 $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,";
3060 }
3061 if ($separatedEntityPMP) {
3062 $sql .= " ppe.pmp,";
3063 } else {
3064 $sql .= " p.pmp,";
3065 }
3066 $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,";
3067 $sql .= " p.fk_price_expression, p.price_autogen, p.stockable_product, p.model_pdf,";
3068 $sql .= " p.price_label";
3069 // can't be false TODO fix
3070 // if (!$separatedStock) {
3071 // $sql .= ", p.stock";
3072 // }
3073 }
3074
3075 $resql = $this->db->query($sql);
3076 if ($resql) {
3077 unset($this->oldcopy);
3078
3079 if ($this->db->num_rows($resql) > 0) {
3080 $obj = $this->db->fetch_object($resql);
3081
3082 $this->id = $obj->rowid;
3083 $this->ref = $obj->ref;
3084 $this->ref_ext = $obj->ref_ext;
3085 $this->label = $obj->label;
3086 $this->description = $obj->description;
3087 $this->url = $obj->url;
3088 $this->note_public = $obj->note_public;
3089 $this->note_private = $obj->note_private;
3090 $this->note = $obj->note_private; // deprecated
3091
3092 $this->type = $obj->fk_product_type;
3093 $this->price_label = $obj->price_label;
3094 $this->status = $obj->tosell;
3095 $this->status_buy = $obj->tobuy;
3096 $this->status_batch = $obj->tobatch;
3097 $this->sell_or_eat_by_mandatory = $obj->sell_or_eat_by_mandatory;
3098 $this->batch_mask = $obj->batch_mask;
3099
3100 $this->customcode = $obj->customcode;
3101 $this->country_id = $obj->fk_country;
3102 $this->country_code = getCountry($this->country_id, '2', $this->db);
3103 $this->state_id = $obj->fk_state;
3104 $this->lifetime = $obj->lifetime;
3105 $this->qc_frequency = $obj->qc_frequency;
3106 $this->price = $obj->price;
3107 $this->price_ttc = $obj->price_ttc;
3108 $this->price_min = $obj->price_min;
3109 $this->price_min_ttc = $obj->price_min_ttc;
3110 $this->price_base_type = $obj->price_base_type;
3111 $this->cost_price = isset($obj->cost_price) ? (float) $obj->cost_price : null;
3112 $this->default_vat_code = $obj->default_vat_code;
3113 $this->tva_tx = $obj->tva_tx;
3115 $this->tva_npr = $obj->tva_npr;
3117 $this->localtax1_tx = $obj->localtax1_tx;
3118 $this->localtax2_tx = $obj->localtax2_tx;
3119 $this->localtax1_type = $obj->localtax1_type;
3120 $this->localtax2_type = $obj->localtax2_type;
3121
3122 $this->finished = $obj->finished;
3123 $this->fk_default_bom = $obj->fk_default_bom;
3124
3125 $this->duration = $obj->duration;
3126 $matches = [];
3127 preg_match('/([\d.]+)(\w+)/', $obj->duration, $matches);
3128 $this->duration_value = !empty($matches[1]) ? (float) $matches[1] : 0;
3129 $this->duration_unit = !empty($matches[2]) ? (string) $matches[2] : null;
3130 $this->canvas = $obj->canvas;
3131 $this->net_measure = $obj->net_measure;
3132 $this->net_measure_units = $obj->net_measure_units;
3133 $this->weight = $obj->weight;
3134 $this->weight_units = (is_null($obj->weight_units) ? 0 : $obj->weight_units);
3135 $this->length = $obj->length;
3136 $this->length_units = (is_null($obj->length_units) ? 0 : $obj->length_units);
3137 $this->width = $obj->width;
3138 $this->width_units = (is_null($obj->width_units) ? 0 : $obj->width_units);
3139 $this->height = $obj->height;
3140 $this->height_units = (is_null($obj->height_units) ? 0 : $obj->height_units);
3141
3142 $this->surface = $obj->surface;
3143 $this->surface_units = (is_null($obj->surface_units) ? 0 : $obj->surface_units);
3144 $this->volume = $obj->volume;
3145 $this->volume_units = (is_null($obj->volume_units) ? 0 : $obj->volume_units);
3146 $this->barcode = $obj->barcode;
3147 $this->barcode_type = $obj->fk_barcode_type;
3148
3149 $this->accountancy_code_buy = $obj->accountancy_code_buy;
3150 $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra;
3151 $this->accountancy_code_buy_export = $obj->accountancy_code_buy_export;
3152 $this->accountancy_code_sell = $obj->accountancy_code_sell;
3153 $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra;
3154 $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export;
3155
3156 $this->fk_default_warehouse = $obj->fk_default_warehouse;
3157 $this->fk_default_workstation = $obj->fk_default_workstation;
3158 $this->seuil_stock_alerte = $obj->seuil_stock_alerte;
3159 $this->desiredstock = $obj->desiredstock;
3160 $this->stock_reel = $obj->stock;
3161 $this->stockable_product = $obj->stockable_product;
3162 $this->pmp = $obj->pmp;
3163
3164 $this->date_creation = $this->db->jdate($obj->datec);
3165 $this->date_modification = $this->db->jdate($obj->tms);
3166
3167 $this->import_key = $obj->import_key;
3168 $this->entity = $obj->entity;
3169
3170 $this->ref_ext = $obj->ref_ext;
3171 $this->fk_price_expression = $obj->fk_price_expression;
3172 $this->fk_unit = $obj->fk_unit;
3173 $this->price_autogen = $obj->price_autogen;
3174 $this->model_pdf = $obj->model_pdf;
3175 $this->last_main_doc = $obj->last_main_doc;
3176
3177 $this->mandatory_period = $obj->mandatory_period;
3178
3179 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
3180 $this->packaging = (float) $obj->packaging;
3181 }
3182
3183 $this->db->free($resql);
3184
3185 // fetch optionals attributes and labels
3186 $this->fetch_optionals();
3187
3188 // Multilangs
3189 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($ignore_lang_load)) {
3190 $this->getMultiLangs();
3191 }
3192
3193 // Load multiprices array
3194 if ((getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($ignore_price_load)) { // prices per segment
3195 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
3196 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
3197 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3198 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
3199 $sql .= " ,price_label";
3200 $sql .= " FROM ".$this->db->prefix()."product_price";
3201 $sql .= " WHERE entity IN (".getEntity('productprice').")";
3202 $sql .= " AND price_level=".((int) $i);
3203 $sql .= " AND fk_product = ".((int) $this->id);
3204 $sql .= " ORDER BY date_price DESC, rowid DESC"; // Get the most recent line
3205 $sql .= " LIMIT 1"; // Only the first one
3206 $resql = $this->db->query($sql);
3207 if ($resql) {
3208 $result = $this->db->fetch_array($resql);
3209
3210 $this->multiprices[$i] = $result ? $result["price"] : null;
3211 $this->multiprices_ttc[$i] = $result ? $result["price_ttc"] : null;
3212 $this->multiprices_min[$i] = $result ? $result["price_min"] : null;
3213 $this->multiprices_min_ttc[$i] = $result ? $result["price_min_ttc"] : null;
3214 $this->multiprices_base_type[$i] = $result ? $result["price_base_type"] : null;
3215 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
3216 $this->multiprices_tva_tx[$i] = $result ? $result["tva_tx"].(!empty($result['default_vat_code']) ? ' ('.$result['default_vat_code'].')' : '') : null;
3217 $this->multiprices_recuperableonly[$i] = $result ? $result["recuperableonly"] : null;
3218
3219 // Price by quantity
3220 /*
3221 $this->prices_by_qty[$i]=$result["price_by_qty"];
3222 $this->prices_by_qty_id[$i]=$result["rowid"];
3223 // Récuperation de la liste des prix selon qty si flag positionné
3224 if ($this->prices_by_qty[$i] == 1)
3225 {
3226 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
3227 $sql.= " FROM ".$this->db->prefix()."product_price_by_qty";
3228 $sql.= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
3229 $sql.= " ORDER BY quantity ASC";
3230
3231 $resql = $this->db->query($sql);
3232 if ($resql)
3233 {
3234 $resultat=array();
3235 $ii=0;
3236 while ($result= $this->db->fetch_array($resql)) {
3237 $resultat[$ii]=array();
3238 $resultat[$ii]["rowid"]=$result["rowid"];
3239 $resultat[$ii]["price"]= $result["price"];
3240 $resultat[$ii]["unitprice"]= $result["unitprice"];
3241 $resultat[$ii]["quantity"]= $result["quantity"];
3242 $resultat[$ii]["remise_percent"]= $result["remise_percent"];
3243 $resultat[$ii]["remise"]= $result["remise"]; // deprecated
3244 $resultat[$ii]["price_base_type"]= $result["price_base_type"];
3245 $ii++;
3246 }
3247 $this->prices_by_qty_list[$i]=$resultat;
3248 }
3249 else
3250 {
3251 dol_print_error($this->db);
3252 return -1;
3253 }
3254 }*/
3255 } else {
3256 $this->error = $this->db->lasterror;
3257 return -1;
3258 }
3259 }
3260 } elseif ((getDolGlobalString('PRODUIT_CUSTOMER_PRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) && empty($ignore_price_load)) { // prices per customers
3261 // Nothing loaded by default. List may be very long.
3262 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY') && empty($ignore_price_load)) { // prices per quantity
3263 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3264 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid";
3265 $sql .= " FROM ".$this->db->prefix()."product_price";
3266 $sql .= " WHERE fk_product = ".((int) $this->id);
3267 $sql .= " ORDER BY date_price DESC, rowid DESC";
3268 $sql .= " LIMIT 1";
3269
3270 $resql = $this->db->query($sql);
3271 if ($resql) {
3272 $result = $this->db->fetch_array($resql);
3273
3274 if ($result) {
3275 // Price by quantity
3276 $this->prices_by_qty[0] = $result["price_by_qty"];
3277 $this->prices_by_qty_id[0] = $result["rowid"];
3278 // Récuperation de la liste des prix selon qty si flag positionné
3279 if ($this->prices_by_qty[0] == 1) {
3280 $sql = "SELECT rowid,price, unitprice, quantity, remise_percent, remise, remise, price_base_type";
3281 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
3282 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[0]);
3283 $sql .= " ORDER BY quantity ASC";
3284
3285 $resql = $this->db->query($sql);
3286 if ($resql) {
3287 $resultat = array();
3288 $ii = 0;
3289 while ($result = $this->db->fetch_array($resql)) {
3290 $resultat[$ii] = array();
3291 $resultat[$ii]["rowid"] = $result["rowid"];
3292 $resultat[$ii]["price"] = $result["price"];
3293 $resultat[$ii]["unitprice"] = $result["unitprice"];
3294 $resultat[$ii]["quantity"] = $result["quantity"];
3295 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
3296 //$resultat[$ii]["remise"]= $result["remise"]; // deprecated
3297 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
3298 $ii++;
3299 }
3300 $this->prices_by_qty_list[0] = $resultat;
3301 } else {
3302 $this->error = $this->db->lasterror;
3303 return -1;
3304 }
3305 }
3306 }
3307 } else {
3308 $this->error = $this->db->lasterror;
3309 return -1;
3310 }
3311 } elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY_MULTIPRICES') && empty($ignore_price_load)) { // prices per customer and quantity
3312 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
3313 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
3314 $sql = "SELECT price, price_ttc, price_min, price_min_ttc,";
3315 $sql .= " price_base_type, tva_tx, default_vat_code, tosell, price_by_qty, rowid, recuperableonly";
3316 $sql .= " FROM ".$this->db->prefix()."product_price";
3317 $sql .= " WHERE entity IN (".getEntity('productprice').")";
3318 $sql .= " AND price_level=".((int) $i);
3319 $sql .= " AND fk_product = ".((int) $this->id);
3320 $sql .= " ORDER BY date_price DESC, rowid DESC";
3321 $sql .= " LIMIT 1";
3322 $resql = $this->db->query($sql);
3323 if (!$resql) {
3324 $this->error = $this->db->lasterror;
3325 return -1;
3326 } else {
3327 $result = $this->db->fetch_array($resql);
3328 $this->multiprices[$i] = (!empty($result["price"]) ? $result["price"] : 0);
3329 $this->multiprices_ttc[$i] = (!empty($result["price_ttc"]) ? $result["price_ttc"] : 0);
3330 $this->multiprices_min[$i] = (!empty($result["price_min"]) ? $result["price_min"] : 0);
3331 $this->multiprices_min_ttc[$i] = (!empty($result["price_min_ttc"]) ? $result["price_min_ttc"] : 0);
3332 $this->multiprices_base_type[$i] = (!empty($result["price_base_type"]) ? $result["price_base_type"] : '');
3333 // Next two fields are used only if PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL is on
3334 $this->multiprices_tva_tx[$i] = (!empty($result["tva_tx"]) ? $result["tva_tx"] : 0); // TODO Add ' ('.$result['default_vat_code'].')'
3335 $this->multiprices_recuperableonly[$i] = (!empty($result["recuperableonly"]) ? $result["recuperableonly"] : 0);
3336
3337 // Price by quantity
3338 $this->prices_by_qty[$i] = (!empty($result["price_by_qty"]) ? $result["price_by_qty"] : 0);
3339 $this->prices_by_qty_id[$i] = (!empty($result["rowid"]) ? $result["rowid"] : 0);
3340 // Récuperation de la liste des prix selon qty si flag positionné
3341 if ($this->prices_by_qty[$i] == 1) {
3342 $sql = "SELECT rowid, price, unitprice, quantity, remise_percent, remise, price_base_type";
3343 $sql .= " FROM ".$this->db->prefix()."product_price_by_qty";
3344 $sql .= " WHERE fk_product_price = ".((int) $this->prices_by_qty_id[$i]);
3345 $sql .= " ORDER BY quantity ASC";
3346
3347 $resql = $this->db->query($sql);
3348 if ($resql) {
3349 $resultat = array();
3350 $ii = 0;
3351 while ($result = $this->db->fetch_array($resql)) {
3352 $resultat[$ii] = array();
3353 $resultat[$ii]["rowid"] = $result["rowid"];
3354 $resultat[$ii]["price"] = $result["price"];
3355 $resultat[$ii]["unitprice"] = $result["unitprice"];
3356 $resultat[$ii]["quantity"] = $result["quantity"];
3357 $resultat[$ii]["remise_percent"] = $result["remise_percent"];
3358 $resultat[$ii]["remise"] = $result["remise"]; // deprecated
3359 $resultat[$ii]["price_base_type"] = $result["price_base_type"];
3360 $ii++;
3361 }
3362 $this->prices_by_qty_list[$i] = $resultat;
3363 } else {
3364 $this->error = $this->db->lasterror;
3365 return -1;
3366 }
3367 }
3368 }
3369 }
3370 }
3371
3372 if (isModEnabled('dynamicprices') && !empty($this->fk_price_expression) && empty($ignore_expression)) {
3373 include_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
3374 $priceparser = new PriceParser($this->db);
3375 $price_result = $priceparser->parseProduct($this);
3376 if ($price_result >= 0) {
3377 $this->price = $price_result;
3378 // Calculate the VAT
3379 $this->price_ttc = (float) price2num($this->price) * (1 + ($this->tva_tx / 100));
3380 $this->price_ttc = (float) price2num($this->price_ttc, 'MU');
3381 }
3382 }
3383
3384 // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
3385 // Instead we just init the stock_warehouse array
3386 $this->stock_warehouse = array();
3387
3388 return 1;
3389 } else {
3390 return 0;
3391 }
3392 } else {
3393 $this->error = $this->db->lasterror();
3394 return -1;
3395 }
3396 }
3397
3398 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3405 public function load_stats_mo($socid = 0)
3406 {
3407 // phpcs:enable
3408 global $user, $hookmanager, $action;
3409
3410 $error = 0;
3411
3412 foreach (array('toconsume', 'consumed', 'toproduce', 'produced') as $role) {
3413 $this->stats_mo['customers_'.$role] = 0;
3414 $this->stats_mo['nb_'.$role] = 0;
3415 $this->stats_mo['qty_'.$role] = 0;
3416
3417 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3418 $sql .= " SUM(mp.qty) as qty";
3419 $sql .= " FROM ".$this->db->prefix()."mrp_mo as c";
3420 $sql .= " INNER JOIN ".$this->db->prefix()."mrp_production as mp ON mp.fk_mo=c.rowid";
3421 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3422 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3423 }
3424 $sql .= " WHERE ";
3425 $sql .= " c.entity IN (".getEntity('mo').")";
3426
3427 $sql .= " AND mp.fk_product = ".((int) $this->id);
3428 $sql .= " AND mp.role ='".$this->db->escape($role)."'";
3429 if ($socid > 0) {
3430 $sql .= " AND c.fk_soc = ".((int) $socid);
3431 }
3432
3433 $result = $this->db->query($sql);
3434 if ($result) {
3435 $obj = $this->db->fetch_object($result);
3436 $this->stats_mo['customers_'.$role] = $obj->nb_customers ? $obj->nb_customers : 0;
3437 $this->stats_mo['nb_'.$role] = $obj->nb ? $obj->nb : 0;
3438 $this->stats_mo['qty_'.$role] = $obj->qty ? price2num($obj->qty, 'MS') : 0; // qty may be a float due to the SUM()
3439 } else {
3440 $this->error = $this->db->error();
3441 $error++;
3442 }
3443 }
3444
3445 if (!empty($error)) {
3446 return -1;
3447 }
3448
3449 $parameters = array('socid' => $socid);
3450 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3451 if ($reshook > 0) {
3452 $this->stats_mo = $hookmanager->resArray['stats_mo'];
3453 }
3454
3455 return 1;
3456 }
3457
3458 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3465 public function load_stats_bom($socid = 0)
3466 {
3467 // phpcs:enable
3468 global $hookmanager, $action;
3469
3470 $error = 0;
3471
3472 $this->stats_bom['nb_toproduce'] = 0;
3473 $this->stats_bom['nb_toconsume'] = 0;
3474 $this->stats_bom['qty_toproduce'] = 0;
3475 $this->stats_bom['qty_toconsume'] = 0;
3476
3477 $sql = "SELECT COUNT(DISTINCT b.rowid) as nb_toproduce,";
3478 $sql .= " SUM(b.qty) as qty_toproduce";
3479 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3480 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom = b.rowid";
3481 $sql .= " WHERE ";
3482 $sql .= " b.entity IN (".getEntity('bom').")";
3483 $sql .= " AND b.fk_product =".((int) $this->id);
3484 $sql .= " GROUP BY b.rowid";
3485
3486 $result = $this->db->query($sql);
3487 if ($result) {
3488 $obj = $this->db->fetch_object($result);
3489 $this->stats_bom['nb_toproduce'] = !empty($obj->nb_toproduce) ? $obj->nb_toproduce : 0;
3490 $this->stats_bom['qty_toproduce'] = !empty($obj->qty_toproduce) ? price2num($obj->qty_toproduce) : 0;
3491 } else {
3492 $this->error = $this->db->error();
3493 $error++;
3494 }
3495
3496 $sql = "SELECT COUNT(DISTINCT bl.rowid) as nb_toconsume,";
3497 $sql .= " SUM(bl.qty) as qty_toconsume";
3498 $sql .= " FROM ".$this->db->prefix()."bom_bom as b";
3499 $sql .= " INNER JOIN ".$this->db->prefix()."bom_bomline as bl ON bl.fk_bom=b.rowid";
3500 $sql .= " WHERE ";
3501 $sql .= " b.entity IN (".getEntity('bom').")";
3502 $sql .= " AND bl.fk_product =".((int) $this->id);
3503
3504 $result = $this->db->query($sql);
3505 if ($result) {
3506 $obj = $this->db->fetch_object($result);
3507 $this->stats_bom['nb_toconsume'] = !empty($obj->nb_toconsume) ? $obj->nb_toconsume : 0;
3508 $this->stats_bom['qty_toconsume'] = !empty($obj->qty_toconsume) ? price2num($obj->qty_toconsume) : 0;
3509 } else {
3510 $this->error = $this->db->error();
3511 $error++;
3512 }
3513
3514 if (!empty($error)) {
3515 return -1;
3516 }
3517
3518 $parameters = array('socid' => $socid);
3519 $reshook = $hookmanager->executeHooks('loadStatsCustomerMO', $parameters, $this, $action);
3520 if ($reshook > 0) {
3521 $this->stats_bom = $hookmanager->resArray['stats_bom'];
3522 }
3523
3524 return 1;
3525 }
3526
3527 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3534 public function load_stats_propale($socid = 0)
3535 {
3536 // phpcs:enable
3537 global $user, $hookmanager, $action;
3538
3539 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_customers, COUNT(DISTINCT p.rowid) as nb,";
3540 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3541 $sql .= " FROM ".$this->db->prefix()."propaldet as pd";
3542 $sql .= ", ".$this->db->prefix()."propal as p";
3543 $sql .= ", ".$this->db->prefix()."societe as s";
3544 $sql .= " WHERE p.rowid = pd.fk_propal";
3545 $sql .= " AND p.fk_soc = s.rowid";
3546 $sql .= " AND p.entity IN (".getEntity('propal').")";
3547 $sql .= " AND pd.fk_product = ".((int) $this->id);
3548 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3549 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $user->id);
3550 }
3551 //$sql.= " AND pr.fk_statut != 0";
3552 if ($socid > 0) {
3553 $sql .= " AND p.fk_soc = ".((int) $socid);
3554 }
3555 // Add where from hooks
3556 $parameters = array('socid' => $socid, 'type_element' => 'propal');
3557 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3558 $sql .= $hookmanager->resPrint;
3559 $result = $this->db->query($sql);
3560 if ($result) {
3561 $obj = $this->db->fetch_object($result);
3562 $this->stats_propale['customers'] = $obj->nb_customers;
3563 $this->stats_propale['nb'] = $obj->nb;
3564 $this->stats_propale['rows'] = $obj->nb_rows;
3565 $this->stats_propale['qty'] = $obj->qty ? $obj->qty : 0;
3566
3567 // if it's a virtual product, maybe it is in proposal by extension
3568 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3569 $TFather = $this->getFather();
3570 if (is_array($TFather) && !empty($TFather)) {
3571 foreach ($TFather as &$fatherData) {
3572 $pFather = new Product($this->db);
3573 $pFather->id = $fatherData['id'];
3574 $qtyCoef = $fatherData['qty'];
3575
3576 if ($fatherData['incdec']) {
3577 $pFather->load_stats_propale($socid);
3578
3579 $this->stats_propale['customers'] += $pFather->stats_propale['customers'];
3580 $this->stats_propale['nb'] += $pFather->stats_propale['nb'];
3581 $this->stats_propale['rows'] += $pFather->stats_propale['rows'];
3582 $this->stats_propale['qty'] += $pFather->stats_propale['qty'] * $qtyCoef;
3583 }
3584 }
3585 }
3586 }
3587
3588 $parameters = array('socid' => $socid);
3589 $reshook = $hookmanager->executeHooks('loadStatsCustomerProposal', $parameters, $this, $action);
3590 if ($reshook > 0) {
3591 $this->stats_propale = $hookmanager->resArray['stats_propale'];
3592 }
3593
3594 return 1;
3595 } else {
3596 $this->error = $this->db->error();
3597 return -1;
3598 }
3599 }
3600
3601
3602 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3609 public function load_stats_proposal_supplier($socid = 0)
3610 {
3611 // phpcs:enable
3612 global $user, $hookmanager, $action;
3613
3614 $sql = "SELECT COUNT(DISTINCT p.fk_soc) as nb_suppliers, COUNT(DISTINCT p.rowid) as nb,";
3615 $sql .= " COUNT(pd.rowid) as nb_rows, SUM(pd.qty) as qty";
3616 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as pd";
3617 $sql .= ", ".$this->db->prefix()."supplier_proposal as p";
3618 $sql .= ", ".$this->db->prefix()."societe as s";
3619 $sql .= " WHERE p.rowid = pd.fk_supplier_proposal";
3620 $sql .= " AND p.fk_soc = s.rowid";
3621 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
3622 $sql .= " AND pd.fk_product = ".((int) $this->id);
3623 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
3624 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $user->id);
3625 }
3626 //$sql.= " AND pr.fk_statut != 0";
3627 if ($socid > 0) {
3628 $sql .= " AND p.fk_soc = ".((int) $socid);
3629 }
3630
3631 $result = $this->db->query($sql);
3632 if ($result) {
3633 $obj = $this->db->fetch_object($result);
3634 $this->stats_proposal_supplier['suppliers'] = $obj->nb_suppliers;
3635 $this->stats_proposal_supplier['nb'] = $obj->nb;
3636 $this->stats_proposal_supplier['rows'] = $obj->nb_rows;
3637 $this->stats_proposal_supplier['qty'] = $obj->qty ? $obj->qty : 0;
3638
3639 $parameters = array('socid' => $socid);
3640 $reshook = $hookmanager->executeHooks('loadStatsSupplierProposal', $parameters, $this, $action);
3641 if ($reshook > 0) {
3642 $this->stats_proposal_supplier = $hookmanager->resArray['stats_proposal_supplier'];
3643 }
3644
3645 return 1;
3646 } else {
3647 $this->error = $this->db->error();
3648 return -1;
3649 }
3650 }
3651
3652
3653 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3662 public function load_stats_commande($socid = 0, $filtrestatut = '', $forVirtualStock = 0)
3663 {
3664 // phpcs:enable
3665 global $user, $hookmanager, $action;
3666
3667
3668 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
3669 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3670 $sql .= " FROM ".$this->db->prefix()."commandedet as cd";
3671 $sql .= ", ".$this->db->prefix()."commande as c";
3672 $sql .= ", ".$this->db->prefix()."societe as s";
3673 $sql .= " WHERE c.rowid = cd.fk_commande";
3674 $sql .= " AND c.fk_soc = s.rowid";
3675 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'commande').")";
3676 $sql .= " AND cd.fk_product = ".((int) $this->id);
3677 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3678 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3679 }
3680 if ($socid > 0) {
3681 $sql .= " AND c.fk_soc = ".((int) $socid);
3682 }
3683 if ($filtrestatut != '') {
3684 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3685 }
3686 // Add where from hooks
3687 $parameters = array('socid' => $socid, 'type_element' => 'order');
3688 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3689 $sql .= $hookmanager->resPrint;
3690 $result = $this->db->query($sql);
3691 if ($result) {
3692 $obj = $this->db->fetch_object($result);
3693 $this->stats_commande['customers'] = $obj->nb_customers;
3694 $this->stats_commande['nb'] = $obj->nb;
3695 $this->stats_commande['rows'] = $obj->nb_rows;
3696 $this->stats_commande['qty'] = $obj->qty ? $obj->qty : 0;
3697
3698 // if it's a virtual product, maybe it is in order by extension
3699 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3700 $TFather = $this->getFather();
3701 if (is_array($TFather) && !empty($TFather)) {
3702 foreach ($TFather as &$fatherData) {
3703 $pFather = new Product($this->db);
3704 $pFather->id = $fatherData['id'];
3705 $qtyCoef = $fatherData['qty'];
3706
3707 if ($fatherData['incdec']) {
3708 $pFather->load_stats_commande($socid, $filtrestatut);
3709
3710 $this->stats_commande['customers'] += $pFather->stats_commande['customers'];
3711 $this->stats_commande['nb'] += $pFather->stats_commande['nb'];
3712 $this->stats_commande['rows'] += $pFather->stats_commande['rows'];
3713 $this->stats_commande['qty'] += $pFather->stats_commande['qty'] * $qtyCoef;
3714 }
3715 }
3716 }
3717 }
3718
3719 // If stock decrease is on invoice validation, the theoretical stock continue to
3720 // count the orders lines containing product in theoretical stock when some are already removed by invoice validation.
3721 if ($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3722 // Do the request using an UNION instead of a OR in the JOIN that is very slow
3723 if (getDolGlobalString('DECREASE_ONLY_UNINVOICEDPRODUCTS')) {
3724 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is on, we make a compensation but only if order not yet invoice.
3725 $adeduire = 0;
3726 $sql = "SELECT SUM(".$this->db->ifsql('f.type=2', '-1', '1')." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3727 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3728 //$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'))";
3729 $sql .= " JOIN ".$this->db->prefix()."element_element as el ON el.fk_target = f.rowid AND el.targettype = 'facture' AND sourcetype = 'commande' ";
3730 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3731 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3732 $sql .= " AND EXISTS (SELECT cd.fk_product FROM ".$this->db->prefix()."commandedet as cd WHERE cd.fk_product = fd.fk_product AND cd.fk_commande = c.rowid)"; // We check that the product is in order lines
3733
3734 $sql .= " UNION ";
3735
3736 $sql .= "SELECT SUM(".$this->db->ifsql('f.type=2', '-1', '1')." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3737 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3738 $sql .= " JOIN ".$this->db->prefix()."element_element as el ON el.fk_source = f.rowid AND el.targettype = 'commande' AND sourcetype = 'facture' ";
3739 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_target = c.rowid";
3740 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND c.facture = 0 AND fd.fk_product = ".((int) $this->id);
3741 $sql .= " AND EXISTS (SELECT cd.fk_product FROM ".$this->db->prefix()."commandedet as cd WHERE cd.fk_product = fd.fk_product AND cd.fk_commande = c.rowid)"; // We check that the product is in order lines
3742 // Add where from hooks
3743 $parameters = array('socid' => $socid, 'type_element' => 'order');
3744 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3745 $sql .= $hookmanager->resPrint;
3746 $resql = $this->db->query($sql);
3747 if ($resql) {
3748 while ($obj = $this->db->fetch_object($resql)) {
3749 $adeduire += $obj->count;
3750 }
3751 }
3752
3753 $this->stats_commande['qty'] -= $adeduire;
3754 } else {
3755 // If option DECREASE_ONLY_UNINVOICEDPRODUCTS is off, we make a compensation with lines of invoices linked to the order
3756 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3757
3758 // For every order having invoice already validated we need to decrease stock cause it's in physical stock
3759 $adeduire = 0;
3760 $sql = "SELECT sum(".$this->db->ifsql('f.type=2', '-1', '1')." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3761 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3762 //$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'))";
3763 $sql .= " JOIN ".$this->db->prefix()."element_element as el ON el.fk_target = f.rowid AND el.targettype = 'facture' AND sourcetype = 'commande' ";
3764 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_source = c.rowid";
3765 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3766 $sql .= " AND EXISTS (SELECT cd.fk_product FROM ".$this->db->prefix()."commandedet as cd WHERE cd.fk_product = fd.fk_product AND cd.fk_commande = c.rowid)"; // We check that the product is in order lines
3767
3768 $sql .= " UNION ALL ";
3769
3770 $sql .= "SELECT sum(".$this->db->ifsql('f.type=2', '-1', '1')." * fd.qty) as count FROM ".$this->db->prefix()."facturedet as fd ";
3771 $sql .= " JOIN ".$this->db->prefix()."facture as f ON fd.fk_facture = f.rowid";
3772 $sql .= " JOIN ".$this->db->prefix()."element_element as el ON el.fk_source = f.rowid AND el.targettype = 'commande' AND sourcetype = 'facture' ";
3773 $sql .= " JOIN ".$this->db->prefix()."commande as c ON el.fk_target = c.rowid";
3774 $sql .= " WHERE c.fk_statut IN (".$this->db->sanitize($filtrestatut).") AND f.fk_statut > ".Facture::STATUS_DRAFT." AND fd.fk_product = ".((int) $this->id);
3775 $sql .= " AND EXISTS (SELECT cd.fk_product FROM ".$this->db->prefix()."commandedet as cd WHERE cd.fk_product = fd.fk_product AND cd.fk_commande = c.rowid)"; // We check that the product is in order lines
3776
3777 dol_syslog(__METHOD__.":: sql $sql", LOG_NOTICE);
3778 // Add where from hooks
3779 $parameters = array('socid' => $socid, 'type_element' => 'order');
3780 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3781 $sql .= $hookmanager->resPrint;
3782 $resql = $this->db->query($sql);
3783 if ($resql) {
3784 while ($obj = $this->db->fetch_object($resql)) {
3785 $adeduire += $obj->count;
3786 }
3787 } else {
3788 $this->error = $this->db->error();
3789 return -1;
3790 }
3791
3792 $this->stats_commande['qty'] -= $adeduire;
3793 }
3794 }
3795
3796 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3797 $reshook = $hookmanager->executeHooks('loadStatsCustomerOrder', $parameters, $this, $action);
3798 if ($reshook > 0) {
3799 $this->stats_commande = $hookmanager->resArray['stats_commande'];
3800 }
3801 return 1;
3802 } else {
3803 $this->error = $this->db->error();
3804 return -1;
3805 }
3806 }
3807
3808 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3818 public function load_stats_commande_fournisseur($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3819 {
3820 // phpcs:enable
3821 global $user, $hookmanager, $action;
3822
3823 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_suppliers, COUNT(DISTINCT c.rowid) as nb,";
3824 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
3825 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as cd";
3826 $sql .= ", ".$this->db->prefix()."commande_fournisseur as c";
3827 $sql .= ", ".$this->db->prefix()."societe as s";
3828 $sql .= " WHERE c.rowid = cd.fk_commande";
3829 $sql .= " AND c.fk_soc = s.rowid";
3830 $sql .= " AND c.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3831 $sql .= " AND cd.fk_product = ".((int) $this->id);
3832 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3833 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = c.fk_soc AND sc.fk_user = ".((int) $user->id);
3834 }
3835 if ($socid > 0) {
3836 $sql .= " AND c.fk_soc = ".((int) $socid);
3837 }
3838 if ($filtrestatut != '') {
3839 $sql .= " AND c.fk_statut in (".$this->db->sanitize($filtrestatut).")"; // Peut valoir 0
3840 }
3841 if (!empty($dateofvirtualstock)) {
3842 $sql .= " AND c.date_livraison <= '".$this->db->idate($dateofvirtualstock)."'";
3843 }
3844
3845 $result = $this->db->query($sql);
3846 if ($result) {
3847 $obj = $this->db->fetch_object($result);
3848 $this->stats_commande_fournisseur['suppliers'] = $obj->nb_suppliers;
3849 $this->stats_commande_fournisseur['nb'] = $obj->nb;
3850 $this->stats_commande_fournisseur['rows'] = $obj->nb_rows;
3851 $this->stats_commande_fournisseur['qty'] = $obj->qty ? $obj->qty : 0;
3852
3853 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3854 $reshook = $hookmanager->executeHooks('loadStatsSupplierOrder', $parameters, $this, $action);
3855 if ($reshook > 0) {
3856 $this->stats_commande_fournisseur = $hookmanager->resArray['stats_commande_fournisseur'];
3857 }
3858
3859 return 1;
3860 } else {
3861 $this->error = $this->db->error().' sql='.$sql;
3862 return -1;
3863 }
3864 }
3865
3866 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3876 public function load_stats_sending($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $filterShipmentStatus = '')
3877 {
3878 // phpcs:enable
3879 global $user, $hookmanager, $action;
3880
3881 $sql = "SELECT COUNT(DISTINCT e.fk_soc) as nb_customers, COUNT(DISTINCT e.rowid) as nb,";
3882 $sql .= " COUNT(ed.rowid) as nb_rows, SUM(ed.qty) as qty";
3883 $sql .= " FROM ".$this->db->prefix()."expeditiondet as ed";
3884 $sql .= ", ".$this->db->prefix()."commandedet as cd";
3885 $sql .= ", ".$this->db->prefix()."commande as c";
3886 $sql .= ", ".$this->db->prefix()."expedition as e";
3887 $sql .= ", ".$this->db->prefix()."societe as s";
3888 $sql .= " WHERE e.rowid = ed.fk_expedition";
3889 $sql .= " AND c.rowid = cd.fk_commande";
3890 $sql .= " AND e.fk_soc = s.rowid";
3891 $sql .= " AND e.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'expedition').")";
3892 $sql .= " AND ed.fk_elementdet = cd.rowid";
3893 $sql .= " AND cd.fk_product = ".((int) $this->id);
3894 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3895 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = e.fk_soc AND sc.fk_user = ".((int) $user->id);
3896 }
3897 if ($socid > 0) {
3898 $sql .= " AND e.fk_soc = ".((int) $socid);
3899 }
3900 if ($filtrestatut != '') {
3901 $sql .= " AND c.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3902 }
3903 if (!empty($filterShipmentStatus)) {
3904 $sql .= " AND e.fk_statut IN (".$this->db->sanitize($filterShipmentStatus).")";
3905 }
3906
3907 $result = $this->db->query($sql);
3908 if ($result) {
3909 $obj = $this->db->fetch_object($result);
3910 $this->stats_expedition['customers'] = $obj->nb_customers;
3911 $this->stats_expedition['nb'] = $obj->nb;
3912 $this->stats_expedition['rows'] = $obj->nb_rows;
3913 $this->stats_expedition['qty'] = $obj->qty ? $obj->qty : 0;
3914
3915 // if it's a virtual product, maybe it is in sending by extension
3916 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
3917 $TFather = $this->getFather();
3918 if (is_array($TFather) && !empty($TFather)) {
3919 foreach ($TFather as &$fatherData) {
3920 $pFather = new Product($this->db);
3921 $pFather->id = $fatherData['id'];
3922 $qtyCoef = $fatherData['qty'];
3923
3924 if ($fatherData['incdec']) {
3925 $pFather->load_stats_sending($socid, $filtrestatut, $forVirtualStock);
3926
3927 $this->stats_expedition['customers'] += $pFather->stats_expedition['customers'];
3928 $this->stats_expedition['nb'] += $pFather->stats_expedition['nb'];
3929 $this->stats_expedition['rows'] += $pFather->stats_expedition['rows'];
3930 $this->stats_expedition['qty'] += $pFather->stats_expedition['qty'] * $qtyCoef;
3931 }
3932 }
3933 }
3934 }
3935
3936 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock, 'filterShipmentStatus' => $filterShipmentStatus);
3937 $reshook = $hookmanager->executeHooks('loadStatsSending', $parameters, $this, $action);
3938 if ($reshook > 0) {
3939 $this->stats_expedition = $hookmanager->resArray['stats_expedition'];
3940 }
3941
3942 return 1;
3943 } else {
3944 $this->error = $this->db->error();
3945 return -1;
3946 }
3947 }
3948
3949 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3959 public function load_stats_reception($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null)
3960 {
3961 // phpcs:enable
3962 global $user, $hookmanager, $action;
3963
3964 $sql = "SELECT COUNT(DISTINCT cf.fk_soc) as nb_suppliers, COUNT(DISTINCT cf.rowid) as nb,";
3965 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
3966 $sql .= " FROM ".$this->db->prefix()."receptiondet_batch as fd";
3967 $sql .= ", ".$this->db->prefix()."commande_fournisseur as cf";
3968 $sql .= ", ".$this->db->prefix()."societe as s";
3969 $sql .= " WHERE cf.rowid = fd.fk_element";
3970 $sql .= " AND cf.fk_soc = s.rowid";
3971 $sql .= " AND cf.entity IN (".getEntity($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE') ? 'stock' : 'supplier_order').")";
3972 $sql .= " AND fd.fk_product = ".((int) $this->id);
3973 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
3974 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = cf.fk_soc AND sc.fk_user = ".((int) $user->id);
3975 }
3976 if ($socid > 0) {
3977 $sql .= " AND cf.fk_soc = ".((int) $socid);
3978 }
3979 if ($filtrestatut != '') {
3980 $sql .= " AND cf.fk_statut IN (".$this->db->sanitize($filtrestatut).")";
3981 }
3982 if (!empty($dateofvirtualstock)) {
3983 $sql .= " AND fd.datec <= '".$this->db->idate($dateofvirtualstock)."'";
3984 }
3985
3986 $result = $this->db->query($sql);
3987 if ($result) {
3988 $obj = $this->db->fetch_object($result);
3989 $this->stats_reception['suppliers'] = $obj->nb_suppliers;
3990 $this->stats_reception['nb'] = $obj->nb;
3991 $this->stats_reception['rows'] = $obj->nb_rows;
3992 $this->stats_reception['qty'] = $obj->qty ? $obj->qty : 0;
3993
3994 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
3995 $reshook = $hookmanager->executeHooks('loadStatsReception', $parameters, $this, $action);
3996 if ($reshook > 0) {
3997 $this->stats_reception = $hookmanager->resArray['stats_reception'];
3998 }
3999
4000 return 1;
4001 } else {
4002 $this->error = $this->db->error();
4003 return -1;
4004 }
4005 }
4006
4007 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4018 public function load_stats_inproduction($socid = 0, $filtrestatut = '', $forVirtualStock = 0, $dateofvirtualstock = null, $warehouseid = 0)
4019 {
4020 // phpcs:enable
4021 global $user, $hookmanager, $action;
4022
4023 $serviceStockIsEnabled = isModEnabled("service") && getDolGlobalString('STOCK_SUPPORTS_SERVICES');
4024
4025 $sql = "SELECT COUNT(DISTINCT m.fk_soc) as nb_customers, COUNT(DISTINCT m.rowid) as nb,";
4026 $sql .= " COUNT(mp.rowid) as nb_rows, SUM(mp.qty) as qty, role";
4027 $sql .= " FROM ".$this->db->prefix()."mrp_production as mp";
4028 $sql .= ", ".$this->db->prefix()."mrp_mo as m";
4029 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON s.rowid = m.fk_soc";
4030 $sql .= " WHERE m.rowid = mp.fk_mo";
4031 $sql .= " AND m.entity IN (".getEntity(($forVirtualStock && getDolGlobalString('STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE')) ? 'stock' : 'mrp').")";
4032 $sql .= " AND mp.fk_product = ".((int) $this->id);
4033 $sql .= " AND (mp.disable_stock_change IN (0) OR mp.disable_stock_change IS NULL)";
4034 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir') && !$forVirtualStock) { // For external user, restriction is done on filter on fk_soc directly
4035 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = m.fk_soc AND sc.fk_user = ".((int) $user->id);
4036 }
4037 if ($socid > 0) {
4038 $sql .= " AND m.fk_soc = ".((int) $socid);
4039 }
4040 if ($filtrestatut != '') {
4041 $sql .= " AND m.status IN (".$this->db->sanitize($filtrestatut).")";
4042 }
4043 if (!empty($dateofvirtualstock)) {
4044 $sql .= " AND m.date_valid <= '".$this->db->idate($dateofvirtualstock)."'"; // better date to code ? end of production ?
4045 }
4046 if (!$serviceStockIsEnabled) {
4047 $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))";
4048 }
4049 if (!empty($warehouseid)) {
4050 $sql .= " AND m.fk_warehouse = ".((int) $warehouseid);
4051 }
4052 $sql .= " GROUP BY role";
4053
4054 if ($warehouseid) {
4055 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
4056 } else {
4057 $this->stats_mrptoconsume['customers'] = 0;
4058 $this->stats_mrptoconsume['nb'] = 0;
4059 $this->stats_mrptoconsume['rows'] = 0;
4060 $this->stats_mrptoconsume['qty'] = 0.0;
4061 $this->stats_mrptoproduce['customers'] = 0;
4062 $this->stats_mrptoproduce['nb'] = 0;
4063 $this->stats_mrptoproduce['rows'] = 0;
4064 $this->stats_mrptoproduce['qty'] = 0.0;
4065 }
4066
4067 $result = $this->db->query($sql);
4068 if ($result) {
4069 while ($obj = $this->db->fetch_object($result)) {
4070 if ($obj->role == 'toconsume' && empty($warehouseid)) {
4071 $this->stats_mrptoconsume['customers'] += (int) $obj->nb_customers;
4072 $this->stats_mrptoconsume['nb'] += (int) $obj->nb;
4073 $this->stats_mrptoconsume['rows'] += (int) $obj->nb_rows;
4074 $this->stats_mrptoconsume['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
4075 }
4076 if ($obj->role == 'consumed' && empty($warehouseid)) {
4077 //$this->stats_mrptoconsume['customers'] += $obj->nb_customers;
4078 //$this->stats_mrptoconsume['nb'] += $obj->nb;
4079 //$this->stats_mrptoconsume['rows'] += $obj->nb_rows;
4080 $this->stats_mrptoconsume['qty'] -= ($obj->qty ? (float) $obj->qty : 0.0);
4081 }
4082 if ($obj->role == 'toproduce') {
4083 if ($warehouseid) {
4084 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
4085 } else {
4086 $this->stats_mrptoproduce['customers'] += (int) $obj->nb_customers;
4087 $this->stats_mrptoproduce['nb'] += (int) $obj->nb;
4088 $this->stats_mrptoproduce['rows'] += (int) $obj->nb_rows;
4089 $this->stats_mrptoproduce['qty'] += ($obj->qty ? (float) $obj->qty : 0.0);
4090 }
4091 }
4092 if ($obj->role == 'produced') {
4093 //$this->stats_mrptoproduce['customers'] += $obj->nb_customers;
4094 //$this->stats_mrptoproduce['nb'] += $obj->nb;
4095 //$this->stats_mrptoproduce['rows'] += $obj->nb_rows;
4096 if ($warehouseid) {
4097 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
4098 } else {
4099 $this->stats_mrptoproduce['qty'] -= ($obj->qty ? $obj->qty : 0);
4100 }
4101 }
4102 }
4103
4104 // Clean data
4105 if ($warehouseid) {
4106 if ($this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] < 0) {
4107 $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'] = 0;
4108 }
4109 } else {
4110 if ($this->stats_mrptoconsume['qty'] < 0) {
4111 $this->stats_mrptoconsume['qty'] = 0;
4112 }
4113 if ($this->stats_mrptoproduce['qty'] < 0) {
4114 $this->stats_mrptoproduce['qty'] = 0;
4115 }
4116 }
4117
4118 $parameters = array('socid' => $socid, 'filtrestatut' => $filtrestatut, 'forVirtualStock' => $forVirtualStock);
4119 $reshook = $hookmanager->executeHooks('loadStatsInProduction', $parameters, $this, $action);
4120 if ($reshook > 0) {
4121 $this->stats_mrptoproduce = $hookmanager->resArray['stats_mrptoproduce'];
4122 }
4123
4124 return 1;
4125 } else {
4126 $this->error = $this->db->error();
4127 return -1;
4128 }
4129 }
4130
4131 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4138 public function load_stats_contrat($socid = 0)
4139 {
4140 // phpcs:enable
4141 global $user, $hookmanager, $action;
4142
4143 $sql = "SELECT COUNT(DISTINCT c.fk_soc) as nb_customers, COUNT(DISTINCT c.rowid) as nb,";
4144 $sql .= " COUNT(cd.rowid) as nb_rows, SUM(cd.qty) as qty";
4145 $sql .= " FROM ".$this->db->prefix()."contratdet as cd";
4146 $sql .= ", ".$this->db->prefix()."contrat as c";
4147 $sql .= ", ".$this->db->prefix()."societe as s";
4148 $sql .= " WHERE c.rowid = cd.fk_contrat";
4149 $sql .= " AND c.fk_soc = s.rowid";
4150 $sql .= " AND c.entity IN (".getEntity('contract').")";
4151 $sql .= " AND cd.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 = c.fk_soc AND sc.fk_user = ".((int) $user->id);
4154 }
4155 //$sql.= " AND c.statut != 0";
4156 if ($socid > 0) {
4157 $sql .= " AND c.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_contrat['customers'] = $obj->nb_customers;
4164 $this->stats_contrat['nb'] = $obj->nb;
4165 $this->stats_contrat['rows'] = $obj->nb_rows;
4166 $this->stats_contrat['qty'] = $obj->qty ? $obj->qty : 0;
4167
4168 // if it's a virtual product, maybe it is in contract 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_contrat($socid);
4179
4180 $this->stats_contrat['customers'] += $pFather->stats_contrat['customers'];
4181 $this->stats_contrat['nb'] += $pFather->stats_contrat['nb'];
4182 $this->stats_contrat['rows'] += $pFather->stats_contrat['rows'];
4183 $this->stats_contrat['qty'] += $pFather->stats_contrat['qty'] * $qtyCoef;
4184 }
4185 }
4186 }
4187 }
4188
4189 $parameters = array('socid' => $socid);
4190 $reshook = $hookmanager->executeHooks('loadStatsContract', $parameters, $this, $action);
4191 if ($reshook > 0) {
4192 $this->stats_contrat = $hookmanager->resArray['stats_contrat'];
4193 }
4194
4195 return 1;
4196 } else {
4197 $this->error = $this->db->error().' sql='.$sql;
4198 return -1;
4199 }
4200 }
4201
4202 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4209 public function load_stats_facture($socid = 0)
4210 {
4211 // phpcs:enable
4212 global $user, $hookmanager, $action;
4213
4214 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
4215 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(".$this->db->ifsql('f.type != 2', 'fd.qty', 'fd.qty * -1').") as qty";
4216 $sql .= " FROM ".$this->db->prefix()."facturedet as fd";
4217 $sql .= ", ".$this->db->prefix()."facture as f";
4218 $sql .= ", ".$this->db->prefix()."societe as s";
4219 $sql .= " WHERE f.rowid = fd.fk_facture";
4220 $sql .= " AND f.fk_soc = s.rowid";
4221 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4222 $sql .= " AND fd.fk_product = ".((int) $this->id);
4223 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4224 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4225 }
4226 //$sql.= " AND f.fk_statut != 0";
4227 if ($socid > 0) {
4228 $sql .= " AND f.fk_soc = ".((int) $socid);
4229 }
4230
4231 $result = $this->db->query($sql);
4232 if ($result) {
4233 $obj = $this->db->fetch_object($result);
4234 $this->stats_facture['customers'] = $obj->nb_customers;
4235 $this->stats_facture['nb'] = $obj->nb;
4236 $this->stats_facture['rows'] = $obj->nb_rows;
4237 $this->stats_facture['qty'] = $obj->qty ? $obj->qty : 0;
4238
4239 // if it's a virtual product, maybe it is in invoice by extension
4240 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4241 $TFather = $this->getFather();
4242 if (is_array($TFather) && !empty($TFather)) {
4243 foreach ($TFather as &$fatherData) {
4244 $pFather = new Product($this->db);
4245 $pFather->id = $fatherData['id'];
4246 $qtyCoef = $fatherData['qty'];
4247
4248 if ($fatherData['incdec']) {
4249 $pFather->load_stats_facture($socid);
4250
4251 $this->stats_facture['customers'] += $pFather->stats_facture['customers'];
4252 $this->stats_facture['nb'] += $pFather->stats_facture['nb'];
4253 $this->stats_facture['rows'] += $pFather->stats_facture['rows'];
4254 $this->stats_facture['qty'] += $pFather->stats_facture['qty'] * $qtyCoef;
4255 }
4256 }
4257 }
4258 }
4259
4260 $parameters = array('socid' => $socid);
4261 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoice', $parameters, $this, $action);
4262 if ($reshook > 0) {
4263 $this->stats_facture = $hookmanager->resArray['stats_facture'];
4264 }
4265
4266 return 1;
4267 } else {
4268 $this->error = $this->db->error();
4269 return -1;
4270 }
4271 }
4272
4273
4274 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4281 public function load_stats_facturerec($socid = 0)
4282 {
4283 // phpcs:enable
4284 global $user, $hookmanager, $action;
4285
4286 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_customers, COUNT(DISTINCT f.rowid) as nb,";
4287 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4288 $sql .= " FROM ".MAIN_DB_PREFIX."facturedet_rec as fd";
4289 $sql .= ", ".MAIN_DB_PREFIX."facture_rec as f";
4290 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
4291 $sql .= " WHERE f.rowid = fd.fk_facture";
4292 $sql .= " AND f.fk_soc = s.rowid";
4293 $sql .= " AND f.entity IN (".getEntity('invoice').")";
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_facturerec['customers'] = (int) $obj->nb_customers;
4307 $this->stats_facturerec['nb'] = (int) $obj->nb;
4308 $this->stats_facturerec['rows'] = (int) $obj->nb_rows;
4309 $this->stats_facturerec['qty'] = $obj->qty ? (float) $obj->qty : 0.0;
4310
4311 // if it's a virtual product, maybe it is in invoice by extension
4312 if (getDolGlobalString('PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC')) {
4313 $TFather = $this->getFather();
4314 if (is_array($TFather) && !empty($TFather)) {
4315 foreach ($TFather as &$fatherData) {
4316 $pFather = new Product($this->db);
4317 $pFather->id = $fatherData['id'];
4318 $qtyCoef = $fatherData['qty'];
4319
4320 if ($fatherData['incdec']) {
4321 $pFather->load_stats_facture($socid);
4322
4323 $this->stats_facturerec['customers'] += $pFather->stats_facturerec['customers'];
4324 $this->stats_facturerec['nb'] += $pFather->stats_facturerec['nb'];
4325 $this->stats_facturerec['rows'] += $pFather->stats_facturerec['rows'];
4326 $this->stats_facturerec['qty'] += $pFather->stats_facturerec['qty'] * $qtyCoef;
4327 }
4328 }
4329 }
4330 }
4331
4332 $parameters = array('socid' => $socid);
4333 $reshook = $hookmanager->executeHooks('loadStatsCustomerInvoiceRec', $parameters, $this, $action);
4334 if ($reshook > 0) {
4335 $this->stats_facturerec = $hookmanager->resArray['stats_facturerec'];
4336 }
4337
4338 return 1;
4339 } else {
4340 $this->error = $this->db->error();
4341 return -1;
4342 }
4343 }
4344
4345 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4352 public function load_stats_facture_fournisseur($socid = 0)
4353 {
4354 // phpcs:enable
4355 global $user, $hookmanager, $action;
4356
4357 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
4358 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4359 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as fd";
4360 $sql .= ", ".$this->db->prefix()."facture_fourn as f";
4361 $sql .= ", ".$this->db->prefix()."societe as s";
4362 $sql .= " WHERE f.rowid = fd.fk_facture_fourn";
4363 $sql .= " AND f.fk_soc = s.rowid";
4364 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4365 $sql .= " AND fd.fk_product = ".((int) $this->id);
4366 if (empty($user->fk_soc) && !$user->hasRight('societe', 'client', 'voir')) { // For external user, restriction is done on filter on fk_soc directly
4367 $sql .= " INNER JOIN ".$this->db->prefix()."societe_commerciaux as sc ON sc.fk_soc = f.fk_soc AND sc.fk_user = ".((int) $user->id);
4368 }
4369 //$sql.= " AND f.fk_statut != 0";
4370 if ($socid > 0) {
4371 $sql .= " AND f.fk_soc = ".((int) $socid);
4372 }
4373
4374 $result = $this->db->query($sql);
4375 if ($result) {
4376 $obj = $this->db->fetch_object($result);
4377 $this->stats_facture_fournisseur['suppliers'] = (int) $obj->nb_suppliers;
4378 $this->stats_facture_fournisseur['nb'] = (int) $obj->nb;
4379 $this->stats_facture_fournisseur['rows'] = (int) $obj->nb_rows;
4380 $this->stats_facture_fournisseur['qty'] = $obj->qty ? (float) $obj->qty : 0.0;
4381
4382 $parameters = array('socid' => $socid);
4383 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoice', $parameters, $this, $action);
4384 if ($reshook > 0) {
4385 $this->stats_facture_fournisseur = $hookmanager->resArray['stats_facture_fournisseur'];
4386 }
4387
4388 return 1;
4389 } else {
4390 $this->error = $this->db->error();
4391 return -1;
4392 }
4393 }
4394
4395 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4402 public function load_stats_facturefournrec($socid = 0)
4403 {
4404 // phpcs:enable
4405 global $user, $hookmanager, $action;
4406
4407 $sql = "SELECT COUNT(DISTINCT f.fk_soc) as nb_suppliers, COUNT(DISTINCT f.rowid) as nb,";
4408 $sql .= " COUNT(fd.rowid) as nb_rows, SUM(fd.qty) as qty";
4409 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn_det_rec as fd";
4410 $sql .= ", ".MAIN_DB_PREFIX."facture_fourn_rec as f";
4411 $sql .= ", ".MAIN_DB_PREFIX."societe as s";
4412 if (!$user->hasRight('societe', 'client', 'voir')) {
4413 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
4414 }
4415 $sql .= " WHERE f.rowid = fd.fk_facture";
4416 $sql .= " AND f.fk_soc = s.rowid";
4417 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4418 $sql .= " AND fd.fk_product = ".((int) $this->id);
4419 if (!$user->hasRight('societe', 'client', 'voir')) {
4420 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4421 }
4422 //$sql.= " AND f.fk_statut != 0";
4423 if ($socid > 0) {
4424 $sql .= " AND f.fk_soc = ".((int) $socid);
4425 }
4426
4427 $result = $this->db->query($sql);
4428 if ($result) {
4429 $obj = $this->db->fetch_object($result);
4430 $this->stats_facturefournrec['suppliers'] = (int) $obj->nb_suppliers;
4431 $this->stats_facturefournrec['nb'] = (int) $obj->nb;
4432 $this->stats_facturefournrec['rows'] = (int) $obj->nb_rows;
4433 $this->stats_facturefournrec['qty'] = $obj->qty ? (float) $obj->qty : 0.0;
4434
4435 $parameters = array('socid' => $socid);
4436 $reshook = $hookmanager->executeHooks('loadStatsSupplierInvoiceRec', $parameters, $this, $action);
4437 if ($reshook > 0) {
4438 $this->stats_facturefournrec = $hookmanager->resArray['stats_facturefournrec'];
4439 }
4440
4441 return 1;
4442 } else {
4443 $this->error = $this->db->error();
4444 return -1;
4445 }
4446 }
4447
4448 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4457 private function _get_stats($sql, $mode, $year = 0)
4458 {
4459 // phpcs:enable
4460 $tab = array();
4461
4462 $resql = $this->db->query($sql);
4463 if ($resql) {
4464 $num = $this->db->num_rows($resql);
4465 $i = 0;
4466 while ($i < $num) {
4467 $arr = $this->db->fetch_array($resql);
4468 if (is_array($arr)) {
4469 $keyfortab = (string) $arr[1];
4470 if ($year == -1) {
4471 $keyfortab = substr($keyfortab, -2);
4472 }
4473
4474 if ($mode == 'byunit') {
4475 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[0]; // 1st field
4476 } elseif ($mode == 'bynumber') {
4477 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
4478 } elseif ($mode == 'byamount') {
4479 $tab[$keyfortab] = (empty($tab[$keyfortab]) ? 0 : $tab[$keyfortab]) + $arr[2]; // 3rd field
4480 } else {
4481 // Bad value for $mode
4482 return -1;
4483 }
4484 }
4485 $i++;
4486 }
4487 } else {
4488 $this->error = $this->db->error().' sql='.$sql;
4489 return -1;
4490 }
4491
4492 if (empty($year)) {
4493 $year = dol_print_date(time(), '%Y');
4494 $month = dol_print_date(time(), '%m');
4495 } elseif ($year == -1) {
4496 $year = '';
4497 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4498 } else {
4499 $month = 12; // We imagine we are at end of year, so we get last 12 month before, so all correct year.
4500 }
4501
4502 $result = array();
4503
4504 for ($j = 0; $j < 12; $j++) {
4505 // $idx is 'D', 'N', 'O', 'S', ... (First letter of month in user language)
4506 $idx = ucfirst(dol_trunc(dol_print_date(dol_mktime(12, 0, 0, $month, 1, 1970), "%b"), 1, 'right', 'UTF-8', 1));
4507
4508 // print $idx.'-'.$year.'-'.$month.'<br>';
4509 $result[$j] = array($idx, isset($tab[$year.$month]) ? $tab[$year.$month] : 0);
4510 // $result[$j] = array($monthnum,isset($tab[$year.$month])?$tab[$year.$month]:0);
4511
4512 $month = "0".($month - 1);
4513 if (dol_strlen($month) == 3) {
4514 $month = substr($month, 1);
4515 }
4516 if ($month == 0) {
4517 $month = 12;
4518 if ($year !== '') { // $year is '' when we want stats for all years
4519 $year -= 1;
4520 }
4521 }
4522 }
4523
4524 return array_reverse($result);
4525 }
4526
4527
4528 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4539 public function get_nb_vente($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4540 {
4541 // phpcs:enable
4542 global $user;
4543
4544 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4545 if ($mode == 'bynumber') {
4546 $sql .= ", count(DISTINCT f.rowid)";
4547 }
4548 $sql .= ", sum(d.total_ht) as total_ht";
4549 $sql .= " FROM ".$this->db->prefix()."facturedet as d, ".$this->db->prefix()."facture as f, ".$this->db->prefix()."societe as s";
4550 if ($filteronproducttype >= 0) {
4551 $sql .= ", ".$this->db->prefix()."product as p";
4552 }
4553 if (!$user->hasRight('societe', 'client', 'voir')) {
4554 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4555 }
4556 $sql .= " WHERE f.rowid = d.fk_facture";
4557 if ($this->id > 0) {
4558 $sql .= " AND d.fk_product = ".((int) $this->id);
4559 } else {
4560 $sql .= " AND d.fk_product > 0";
4561 }
4562 if ($filteronproducttype >= 0) {
4563 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4564 }
4565 $sql .= " AND f.fk_soc = s.rowid";
4566 $sql .= " AND f.entity IN (".getEntity('invoice').")";
4567 if (!$user->hasRight('societe', 'client', 'voir')) {
4568 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4569 }
4570 if ($socid > 0) {
4571 $sql .= " AND f.fk_soc = $socid";
4572 }
4573 $sql .= $morefilter;
4574 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4575 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4576
4577 return $this->_get_stats($sql, $mode, $year);
4578 }
4579
4580
4581 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4592 public function get_nb_achat($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4593 {
4594 // phpcs:enable
4595 global $user;
4596
4597 $sql = "SELECT sum(d.qty) as qty, date_format(f.datef, '%Y%m')";
4598 if ($mode == 'bynumber') {
4599 $sql .= ", count(DISTINCT f.rowid)";
4600 }
4601 $sql .= ", sum(d.total_ht) as total_ht";
4602 $sql .= " FROM ".$this->db->prefix()."facture_fourn_det as d, ".$this->db->prefix()."facture_fourn as f, ".$this->db->prefix()."societe as s";
4603 if ($filteronproducttype >= 0) {
4604 $sql .= ", ".$this->db->prefix()."product as p";
4605 }
4606 if (!$user->hasRight('societe', 'client', 'voir')) {
4607 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4608 }
4609 $sql .= " WHERE f.rowid = d.fk_facture_fourn";
4610 if ($this->id > 0) {
4611 $sql .= " AND d.fk_product = ".((int) $this->id);
4612 } else {
4613 $sql .= " AND d.fk_product > 0";
4614 }
4615 if ($filteronproducttype >= 0) {
4616 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4617 }
4618 $sql .= " AND f.fk_soc = s.rowid";
4619 $sql .= " AND f.entity IN (".getEntity('facture_fourn').")";
4620 if (!$user->hasRight('societe', 'client', 'voir')) {
4621 $sql .= " AND f.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4622 }
4623 if ($socid > 0) {
4624 $sql .= " AND f.fk_soc = $socid";
4625 }
4626 $sql .= $morefilter;
4627 $sql .= " GROUP BY date_format(f.datef,'%Y%m')";
4628 $sql .= " ORDER BY date_format(f.datef,'%Y%m') DESC";
4629
4630 return $this->_get_stats($sql, $mode, $year);
4631 }
4632
4633 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4644 public function get_nb_propal($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4645 {
4646 // phpcs:enable
4647 global $user, $hookmanager;
4648
4649 $sql = "SELECT sum(d.qty) as qty, date_format(p.datep, '%Y%m')";
4650 if ($mode == 'bynumber') {
4651 $sql .= ", count(DISTINCT p.rowid)";
4652 }
4653 $sql .= ", sum(d.total_ht) as total_ht";
4654 $sql .= " FROM ".$this->db->prefix()."propaldet as d, ".$this->db->prefix()."propal as p, ".$this->db->prefix()."societe as s";
4655 if ($filteronproducttype >= 0) {
4656 $sql .= ", ".$this->db->prefix()."product as prod";
4657 }
4658 if (!$user->hasRight('societe', 'client', 'voir')) {
4659 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4660 }
4661 $sql .= " WHERE p.rowid = d.fk_propal";
4662 if ($this->id > 0) {
4663 $sql .= " AND d.fk_product = ".((int) $this->id);
4664 } else {
4665 $sql .= " AND d.fk_product > 0";
4666 }
4667 if ($filteronproducttype >= 0) {
4668 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4669 }
4670 $sql .= " AND p.fk_soc = s.rowid";
4671 $sql .= " AND p.entity IN (".getEntity('propal').")";
4672 if (!$user->hasRight('societe', 'client', 'voir')) {
4673 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4674 }
4675 if ($socid > 0) {
4676 $sql .= " AND p.fk_soc = ".((int) $socid);
4677 }
4678 $sql .= $morefilter;
4679 $parameters = array('socid' => $user->socid);
4680 $hookmanager->executeHooks('productGetNbPropal', $parameters, $this); // Note that $action and $object may have been modified by hook
4681 $sql .= $hookmanager->resPrint;
4682 $sql .= " GROUP BY date_format(p.datep,'%Y%m')";
4683 $sql .= " ORDER BY date_format(p.datep,'%Y%m') DESC";
4684 return $this->_get_stats($sql, $mode, $year);
4685 }
4686
4687 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4698 public function get_nb_propalsupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4699 {
4700 // phpcs:enable
4701 global $user;
4702
4703 $sql = "SELECT sum(d.qty) as qty, date_format(p.date_valid, '%Y%m')";
4704 if ($mode == 'bynumber') {
4705 $sql .= ", count(DISTINCT p.rowid)";
4706 }
4707 $sql .= ", sum(d.total_ht) as total_ht";
4708 $sql .= " FROM ".$this->db->prefix()."supplier_proposaldet as d, ".$this->db->prefix()."supplier_proposal as p, ".$this->db->prefix()."societe as s";
4709 if ($filteronproducttype >= 0) {
4710 $sql .= ", ".$this->db->prefix()."product as prod";
4711 }
4712 if (!$user->hasRight('societe', 'client', 'voir')) {
4713 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4714 }
4715 $sql .= " WHERE p.rowid = d.fk_supplier_proposal";
4716 if ($this->id > 0) {
4717 $sql .= " AND d.fk_product = ".((int) $this->id);
4718 } else {
4719 $sql .= " AND d.fk_product > 0";
4720 }
4721 if ($filteronproducttype >= 0) {
4722 $sql .= " AND prod.rowid = d.fk_product AND prod.fk_product_type = ".((int) $filteronproducttype);
4723 }
4724 $sql .= " AND p.fk_soc = s.rowid";
4725 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
4726 if (!$user->hasRight('societe', 'client', 'voir')) {
4727 $sql .= " AND p.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4728 }
4729 if ($socid > 0) {
4730 $sql .= " AND p.fk_soc = ".((int) $socid);
4731 }
4732 $sql .= $morefilter;
4733 $sql .= " GROUP BY date_format(p.date_valid,'%Y%m')";
4734 $sql .= " ORDER BY date_format(p.date_valid,'%Y%m') DESC";
4735
4736 return $this->_get_stats($sql, $mode, $year);
4737 }
4738
4739 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4750 public function get_nb_order($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4751 {
4752 // phpcs:enable
4753 global $user, $hookmanager;
4754
4755 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4756 if ($mode == 'bynumber') {
4757 $sql .= ", count(DISTINCT c.rowid)";
4758 }
4759 $sql .= ", sum(d.total_ht) as total_ht";
4760 $sql .= " FROM ".$this->db->prefix()."commandedet as d, ".$this->db->prefix()."commande as c, ".$this->db->prefix()."societe as s";
4761 if ($filteronproducttype >= 0) {
4762 $sql .= ", ".$this->db->prefix()."product as p";
4763 }
4764 if (!$user->hasRight('societe', 'client', 'voir')) {
4765 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4766 }
4767 $sql .= " WHERE c.rowid = d.fk_commande";
4768 if ($this->id > 0) {
4769 $sql .= " AND d.fk_product = ".((int) $this->id);
4770 } else {
4771 $sql .= " AND d.fk_product > 0";
4772 }
4773 if ($filteronproducttype >= 0) {
4774 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4775 }
4776 $sql .= " AND c.fk_soc = s.rowid";
4777 $sql .= " AND c.entity IN (".getEntity('commande').")";
4778 if (!$user->hasRight('societe', 'client', 'voir')) {
4779 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4780 }
4781 if ($socid > 0) {
4782 $sql .= " AND c.fk_soc = ".((int) $socid);
4783 }
4784 $sql .= $morefilter;
4785 $parameters = array('socid' => $user->socid);
4786 $hookmanager->executeHooks('productGetNbOrder', $parameters, $this); // Note that $action and $object may have been modified by hook
4787 $sql .= $hookmanager->resPrint;
4788 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4789 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4790
4791 return $this->_get_stats($sql, $mode, $year);
4792 }
4793
4794 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4805 public function get_nb_ordersupplier($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4806 {
4807 // phpcs:enable
4808 global $user;
4809
4810 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_commande, '%Y%m')";
4811 if ($mode == 'bynumber') {
4812 $sql .= ", count(DISTINCT c.rowid)";
4813 }
4814 $sql .= ", sum(d.total_ht) as total_ht";
4815 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as d, ".$this->db->prefix()."commande_fournisseur as c, ".$this->db->prefix()."societe as s";
4816 if ($filteronproducttype >= 0) {
4817 $sql .= ", ".$this->db->prefix()."product as p";
4818 }
4819 if (!$user->hasRight('societe', 'client', 'voir')) {
4820 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4821 }
4822 $sql .= " WHERE c.rowid = d.fk_commande";
4823 if ($this->id > 0) {
4824 $sql .= " AND d.fk_product = ".((int) $this->id);
4825 } else {
4826 $sql .= " AND d.fk_product > 0";
4827 }
4828 if ($filteronproducttype >= 0) {
4829 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4830 }
4831 $sql .= " AND c.fk_soc = s.rowid";
4832 $sql .= " AND c.entity IN (".getEntity('supplier_order').")";
4833 if (!$user->hasRight('societe', 'client', 'voir')) {
4834 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4835 }
4836 if ($socid > 0) {
4837 $sql .= " AND c.fk_soc = ".((int) $socid);
4838 }
4839 $sql .= $morefilter;
4840 $sql .= " GROUP BY date_format(c.date_commande,'%Y%m')";
4841 $sql .= " ORDER BY date_format(c.date_commande,'%Y%m') DESC";
4842
4843 return $this->_get_stats($sql, $mode, $year);
4844 }
4845
4846 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4857 public function get_nb_contract($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4858 {
4859 // phpcs:enable
4860 global $user;
4861
4862 $sql = "SELECT sum(d.qty) as qty, date_format(c.date_contrat, '%Y%m')";
4863 if ($mode == 'bynumber') {
4864 $sql .= ", count(DISTINCT c.rowid)";
4865 }
4866 $sql .= ", sum(d.total_ht) as total_ht";
4867 $sql .= " FROM ".$this->db->prefix()."contratdet as d, ".$this->db->prefix()."contrat as c, ".$this->db->prefix()."societe as s";
4868 if ($filteronproducttype >= 0) {
4869 $sql .= ", ".$this->db->prefix()."product as p";
4870 }
4871 if (!$user->hasRight('societe', 'client', 'voir')) {
4872 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4873 }
4874 $sql .= " WHERE c.entity IN (".getEntity('contract').")";
4875 $sql .= " AND c.rowid = d.fk_contrat";
4876
4877 if ($this->id > 0) {
4878 $sql .= " AND d.fk_product = ".((int) $this->id);
4879 } else {
4880 $sql .= " AND d.fk_product > 0";
4881 }
4882 if ($filteronproducttype >= 0) {
4883 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4884 }
4885 $sql .= " AND c.fk_soc = s.rowid";
4886
4887 if (!$user->hasRight('societe', 'client', 'voir')) {
4888 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4889 }
4890 if ($socid > 0) {
4891 $sql .= " AND c.fk_soc = ".((int) $socid);
4892 }
4893 $sql .= $morefilter;
4894 $sql .= " GROUP BY date_format(c.date_contrat,'%Y%m')";
4895 $sql .= " ORDER BY date_format(c.date_contrat,'%Y%m') DESC";
4896
4897 return $this->_get_stats($sql, $mode, $year);
4898 }
4899
4900 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4911 public function get_nb_mos($socid, $mode, $filteronproducttype = -1, $year = 0, $morefilter = '')
4912 {
4913 // phpcs:enable
4914 global $user;
4915
4916 $sql = "SELECT sum(d.qty), date_format(d.date_valid, '%Y%m')";
4917 if ($mode == 'bynumber') {
4918 $sql .= ", count(DISTINCT d.rowid)";
4919 }
4920 $sql .= " FROM ".$this->db->prefix()."mrp_mo as d LEFT JOIN ".$this->db->prefix()."societe as s ON d.fk_soc = s.rowid";
4921 if ($filteronproducttype >= 0) {
4922 $sql .= ", ".$this->db->prefix()."product as p";
4923 }
4924 if (!$user->hasRight('societe', 'client', 'voir')) {
4925 $sql .= ", ".$this->db->prefix()."societe_commerciaux as sc";
4926 }
4927
4928 $sql .= " WHERE d.entity IN (".getEntity('mo').")";
4929 $sql .= " AND d.status > 0";
4930
4931 if ($this->id > 0) {
4932 $sql .= " AND d.fk_product = ".((int) $this->id);
4933 } else {
4934 $sql .= " AND d.fk_product > 0";
4935 }
4936 if ($filteronproducttype >= 0) {
4937 $sql .= " AND p.rowid = d.fk_product AND p.fk_product_type = ".((int) $filteronproducttype);
4938 }
4939
4940 if (!$user->hasRight('societe', 'client', 'voir')) {
4941 $sql .= " AND d.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4942 }
4943 if ($socid > 0) {
4944 $sql .= " AND d.fk_soc = ".((int) $socid);
4945 }
4946 $sql .= $morefilter;
4947 $sql .= " GROUP BY date_format(d.date_valid,'%Y%m')";
4948 $sql .= " ORDER BY date_format(d.date_valid,'%Y%m') DESC";
4949
4950 return $this->_get_stats($sql, $mode, $year);
4951 }
4952
4953 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4964 public function add_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
4965 {
4966 // phpcs:enable
4967 global $user;
4968
4969 // Clean parameters
4970 if (!is_numeric($id_pere)) {
4971 $id_pere = 0;
4972 }
4973 if (!is_numeric($id_fils)) {
4974 $id_fils = 0;
4975 }
4976 if (!is_numeric($incdec)) {
4977 $incdec = 0;
4978 }
4979
4980 $result = $this->del_sousproduit($id_pere, $id_fils);
4981 if ($result < 0) {
4982 return $result;
4983 }
4984
4985 // Check not already father of id_pere (to avoid father -> child -> father links)
4986 $sql = "SELECT fk_product_pere from ".$this->db->prefix()."product_association";
4987 $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere);
4988 if (!$this->db->query($sql)) {
4989 dol_print_error($this->db);
4990 return -1;
4991 } else {
4992 //Selection of the highest row
4993 $sql = "SELECT MAX(rang) as max_rank FROM ".$this->db->prefix()."product_association";
4994 $sql .= " WHERE fk_product_pere = ".((int) $id_pere);
4995 $resql = $this->db->query($sql);
4996 if ($resql) {
4997 $obj = $this->db->fetch_object($resql);
4998 $rank = $obj->max_rank + 1;
4999 //Addition of a product with the highest rank +1
5000 $sql = "INSERT INTO ".$this->db->prefix()."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)";
5001 $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".price2num($qty, 'MS').", ".((int) $incdec).", ".((int) $rank).")";
5002 if (! $this->db->query($sql)) {
5003 dol_print_error($this->db);
5004 return -1;
5005 } else {
5006 if (!$notrigger) {
5007 // Call trigger
5008 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_ADD', $user);
5009 if ($result < 0) {
5010 $this->error = $this->db->lasterror();
5011 dol_syslog(get_class($this).'::addSubproduct error='.$this->error, LOG_ERR);
5012 return -1;
5013 }
5014 }
5015 // End call triggers
5016
5017 return 1;
5018 }
5019 } else {
5020 dol_print_error($this->db);
5021 return -1;
5022 }
5023 }
5024 }
5025
5026 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5037 public function update_sousproduit($id_pere, $id_fils, $qty, $incdec = 1, $notrigger = 0)
5038 {
5039 // phpcs:enable
5040 global $user;
5041
5042 // Clean parameters
5043 if (!is_numeric($id_pere)) {
5044 $id_pere = 0;
5045 }
5046 if (!is_numeric($id_fils)) {
5047 $id_fils = 0;
5048 }
5049 if (!is_numeric($incdec)) {
5050 $incdec = 1;
5051 }
5052 if (!is_numeric($qty)) {
5053 $qty = 1;
5054 }
5055
5056 $sql = 'UPDATE '.$this->db->prefix().'product_association SET ';
5057 $sql .= 'qty = '.price2num($qty, 'MS');
5058 $sql .= ',incdec = '.((int) $incdec);
5059 $sql .= ' WHERE fk_product_pere = '.((int) $id_pere).' AND fk_product_fils = '.((int) $id_fils);
5060
5061 if (!$this->db->query($sql)) {
5062 dol_print_error($this->db);
5063 return -1;
5064 } else {
5065 if (!$notrigger) {
5066 // Call trigger
5067 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_UPDATE', $user);
5068 if ($result < 0) {
5069 $this->error = $this->db->lasterror();
5070 dol_syslog(get_class($this).'::updateSubproduct error='.$this->error, LOG_ERR);
5071 return -1;
5072 }
5073 // End call triggers
5074 }
5075
5076 return 1;
5077 }
5078 }
5079
5080 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5089 public function del_sousproduit($fk_parent, $fk_child, $notrigger = 0)
5090 {
5091 // phpcs:enable
5092 global $user;
5093
5094 if (!is_numeric($fk_parent)) {
5095 $fk_parent = 0;
5096 }
5097 if (!is_numeric($fk_child)) {
5098 $fk_child = 0;
5099 }
5100
5101 $sql = "DELETE FROM ".$this->db->prefix()."product_association";
5102 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
5103 $sql .= " AND fk_product_fils = ".((int) $fk_child);
5104
5105 dol_syslog(get_class($this).'::del_sousproduit', LOG_DEBUG);
5106 if (!$this->db->query($sql)) {
5107 dol_print_error($this->db);
5108 return -1;
5109 }
5110
5111 // Updated ranks so that none are missing
5112 $sqlrank = "SELECT rowid, rang FROM ".$this->db->prefix()."product_association";
5113 $sqlrank .= " WHERE fk_product_pere = ".((int) $fk_parent);
5114 $sqlrank .= " ORDER BY rang";
5115 $resqlrank = $this->db->query($sqlrank);
5116 if ($resqlrank) {
5117 $cpt = 0;
5118 while ($objrank = $this->db->fetch_object($resqlrank)) {
5119 $cpt++;
5120 $sql = "UPDATE ".$this->db->prefix()."product_association";
5121 $sql .= " SET rang = ".((int) $cpt);
5122 $sql .= " WHERE rowid = ".((int) $objrank->rowid);
5123 if (! $this->db->query($sql)) {
5124 dol_print_error($this->db);
5125 return -1;
5126 }
5127 }
5128 }
5129
5130 if (!$notrigger) {
5131 // Call trigger
5132 $result = $this->call_trigger('PRODUCT_SUBPRODUCT_DELETE', $user);
5133 if ($result < 0) {
5134 $this->error = $this->db->lasterror();
5135 dol_syslog(get_class($this).'::delSubproduct error='.$this->error, LOG_ERR);
5136 return -1;
5137 }
5138 // End call triggers
5139 }
5140
5141 return 1;
5142 }
5143
5144 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5152 public function is_sousproduit($fk_parent, $fk_child)
5153 {
5154 // phpcs:enable
5155 $sql = "SELECT fk_product_pere, qty, incdec";
5156 $sql .= " FROM ".$this->db->prefix()."product_association";
5157 $sql .= " WHERE fk_product_pere = ".((int) $fk_parent);
5158 $sql .= " AND fk_product_fils = ".((int) $fk_child);
5159
5160 $result = $this->db->query($sql);
5161 if ($result) {
5162 $num = $this->db->num_rows($result);
5163
5164 if ($num > 0) {
5165 $obj = $this->db->fetch_object($result);
5166
5167 $this->is_sousproduit_qty = $obj->qty;
5168 $this->is_sousproduit_incdec = $obj->incdec;
5169
5170 return 1;
5171 } else {
5172 return 0;
5173 }
5174 } else {
5175 dol_print_error($this->db);
5176 return -1;
5177 }
5178 }
5179
5180
5181 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5192 public function add_fournisseur($user, $id_fourn, $ref_fourn, $quantity)
5193 {
5194 // phpcs:enable
5195 global $conf;
5196
5197 $now = dol_now();
5198
5199 dol_syslog(get_class($this)."::add_fournisseur id_fourn = ".$id_fourn." ref_fourn=".$ref_fourn." quantity=".$quantity, LOG_DEBUG);
5200
5201 // Clean parameters
5202 $quantity = price2num($quantity, 'MS');
5203
5204 if ($ref_fourn) {
5205 // Check if ref is not already used
5206 $sql = "SELECT rowid, fk_product";
5207 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5208 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5209 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5210 $sql .= " AND fk_product <> ".((int) $this->id);
5211 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5212
5213 $resql = $this->db->query($sql);
5214 if ($resql) {
5215 $obj = $this->db->fetch_object($resql);
5216 if ($obj) {
5217 // If the supplier ref already exists but for another product (duplicate ref is accepted for different quantity only or different companies)
5218 $this->product_id_already_linked = $obj->fk_product;
5219 return -3;
5220 }
5221 $this->db->free($resql);
5222 }
5223 }
5224
5225 $sql = "SELECT rowid";
5226 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5227 $sql .= " WHERE fk_soc = ".((int) $id_fourn);
5228 if ($ref_fourn) {
5229 $sql .= " AND ref_fourn = '".$this->db->escape($ref_fourn)."'";
5230 } else {
5231 $sql .= " AND (ref_fourn = '' OR ref_fourn IS NULL)";
5232 }
5233 $sql .= " AND quantity = ".((float) $quantity);
5234 $sql .= " AND fk_product = ".((int) $this->id);
5235 $sql .= " AND entity IN (".getEntity('productsupplierprice').")";
5236
5237 $resql = $this->db->query($sql);
5238 if ($resql) {
5239 $obj = $this->db->fetch_object($resql);
5240
5241 // The reference supplier does not exist, we create it for this product.
5242 if (empty($obj)) {
5243 $sql = "INSERT INTO ".$this->db->prefix()."product_fournisseur_price(";
5244 $sql .= "datec";
5245 $sql .= ", entity";
5246 $sql .= ", fk_product";
5247 $sql .= ", fk_soc";
5248 $sql .= ", ref_fourn";
5249 $sql .= ", quantity";
5250 $sql .= ", fk_user";
5251 $sql .= ", tva_tx";
5252 $sql .= ") VALUES (";
5253 $sql .= "'".$this->db->idate($now)."'";
5254 $sql .= ", ".((int) $conf->entity);
5255 $sql .= ", ".((int) $this->id);
5256 $sql .= ", ".((int) $id_fourn);
5257 $sql .= ", '".$this->db->escape($ref_fourn)."'";
5258 $sql .= ", ".((float) $quantity);
5259 $sql .= ", ".((int) $user->id);
5260 $sql .= ", 0";
5261 $sql .= ")";
5262
5263 if ($this->db->query($sql)) {
5264 $this->product_fourn_price_id = $this->db->last_insert_id($this->db->prefix()."product_fournisseur_price");
5265 return 1;
5266 } else {
5267 $this->error = $this->db->lasterror();
5268 return -1;
5269 }
5270 } else {
5271 // If the supplier price already exists for this product and quantity
5272 $this->product_fourn_price_id = $obj->rowid;
5273 return 0;
5274 }
5275 } else {
5276 $this->error = $this->db->lasterror();
5277 return -2;
5278 }
5279 }
5280
5281
5282 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5288 public function list_suppliers()
5289 {
5290 // phpcs:enable
5291 global $conf;
5292
5293 $list = array();
5294
5295 $sql = "SELECT DISTINCT p.fk_soc";
5296 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price as p";
5297 $sql .= " WHERE p.fk_product = ".((int) $this->id);
5298 $sql .= " AND p.entity = ".((int) $conf->entity);
5299
5300 $result = $this->db->query($sql);
5301 if ($result) {
5302 $num = $this->db->num_rows($result);
5303 $i = 0;
5304 while ($i < $num) {
5305 $obj = $this->db->fetch_object($result);
5306 $list[$i] = $obj->fk_soc;
5307 $i++;
5308 }
5309 }
5310
5311 return $list;
5312 }
5313
5314 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5322 public function clone_price($fromId, $toId)
5323 {
5324 // phpcs:enable
5325 global $user;
5326
5327 $now = dol_now();
5328
5329 $this->db->begin();
5330
5331 // prices
5332 $sql = "INSERT INTO ".$this->db->prefix()."product_price (";
5333 $sql .= " entity";
5334 $sql .= ", fk_product";
5335 $sql .= ", date_price";
5336 $sql .= ", price_level";
5337 $sql .= ", price";
5338 $sql .= ", price_ttc";
5339 $sql .= ", price_min";
5340 $sql .= ", price_min_ttc";
5341 $sql .= ", price_base_type";
5342 $sql .= ", price_label";
5343 $sql .= ", default_vat_code";
5344 $sql .= ", tva_tx";
5345 $sql .= ", recuperableonly";
5346 $sql .= ", localtax1_tx";
5347 $sql .= ", localtax1_type";
5348 $sql .= ", localtax2_tx";
5349 $sql .= ", localtax2_type";
5350 $sql .= ", fk_user_author";
5351 $sql .= ", tosell";
5352 $sql .= ", price_by_qty";
5353 $sql .= ", fk_price_expression";
5354 $sql .= ", fk_multicurrency";
5355 $sql .= ", multicurrency_code";
5356 $sql .= ", multicurrency_tx";
5357 $sql .= ", multicurrency_price";
5358 $sql .= ", multicurrency_price_ttc";
5359 $sql .= ")";
5360 $sql .= " SELECT";
5361 $sql .= " entity";
5362 $sql .= ", ".((int) $toId);
5363 $sql .= ", '".$this->db->idate($now)."'";
5364 $sql .= ", price_level";
5365 $sql .= ", price";
5366 $sql .= ", price_ttc";
5367 $sql .= ", price_min";
5368 $sql .= ", price_min_ttc";
5369 $sql .= ", price_base_type";
5370 $sql .= ", price_label";
5371 $sql .= ", default_vat_code";
5372 $sql .= ", tva_tx";
5373 $sql .= ", recuperableonly";
5374 $sql .= ", localtax1_tx";
5375 $sql .= ", localtax1_type";
5376 $sql .= ", localtax2_tx";
5377 $sql .= ", localtax2_type";
5378 $sql .= ", ".((int) $user->id);
5379 $sql .= ", tosell";
5380 $sql .= ", price_by_qty";
5381 $sql .= ", fk_price_expression";
5382 $sql .= ", fk_multicurrency";
5383 $sql .= ", multicurrency_code";
5384 $sql .= ", multicurrency_tx";
5385 $sql .= ", multicurrency_price";
5386 $sql .= ", multicurrency_price_ttc";
5387 $sql .= " FROM ".$this->db->prefix()."product_price ps";
5388 $sql .= " WHERE fk_product = ".((int) $fromId);
5389 $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)";
5390 $sql .= " ORDER BY date_price DESC";
5391
5392 dol_syslog(__METHOD__, LOG_DEBUG);
5393 $resql = $this->db->query($sql);
5394 if (!$resql) {
5395 $this->db->rollback();
5396 return -1;
5397 }
5398
5399 $this->db->commit();
5400 return 1;
5401 }
5402
5403 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5411 public function clone_associations($fromId, $toId)
5412 {
5413 // phpcs:enable
5414 $this->db->begin();
5415
5416 $sql = 'INSERT INTO '.$this->db->prefix().'product_association (fk_product_pere, fk_product_fils, qty, incdec)';
5417 $sql .= " SELECT ".((int) $toId).", fk_product_fils, qty, incdec FROM ".$this->db->prefix()."product_association";
5418 $sql .= " WHERE fk_product_pere = ".((int) $fromId);
5419
5420 dol_syslog(get_class($this).'::clone_association', LOG_DEBUG);
5421 if (!$this->db->query($sql)) {
5422 $this->db->rollback();
5423 return -1;
5424 }
5425
5426 $this->db->commit();
5427 return 1;
5428 }
5429
5430 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5438 public function clone_fournisseurs($fromId, $toId)
5439 {
5440 // phpcs:enable
5441 $this->db->begin();
5442
5443 $now = dol_now();
5444
5445 // les fournisseurs
5446 /*$sql = "INSERT ".$this->db->prefix()."product_fournisseur ("
5447 . " datec, fk_product, fk_soc, ref_fourn, fk_user_author )"
5448 . " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, ref_fourn, fk_user_author"
5449 . " FROM ".$this->db->prefix()."product_fournisseur"
5450 . " WHERE fk_product = ".((int) $fromId);
5451
5452 if ( ! $this->db->query($sql ) )
5453 {
5454 $this->db->rollback();
5455 return -1;
5456 }*/
5457
5458 // les prix de fournisseurs.
5459 $sql = "INSERT ".$this->db->prefix()."product_fournisseur_price (";
5460 $sql .= " datec, fk_product, fk_soc, price, quantity, fk_user, tva_tx)";
5461 $sql .= " SELECT '".$this->db->idate($now)."', ".((int) $toId).", fk_soc, price, quantity, fk_user, tva_tx";
5462 $sql .= " FROM ".$this->db->prefix()."product_fournisseur_price";
5463 $sql .= " WHERE fk_product = ".((int) $fromId);
5464
5465 dol_syslog(get_class($this).'::clone_fournisseurs', LOG_DEBUG);
5466 $resql = $this->db->query($sql);
5467 if (!$resql) {
5468 $this->db->rollback();
5469 return -1;
5470 } else {
5471 $this->db->commit();
5472 return 1;
5473 }
5474 }
5475
5476 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5489 public function fetch_prod_arbo($prod, $compl_path = '', $multiply = 1, $level = 1, $id_parent = 0, $ignore_stock_load = 0)
5490 {
5491 // phpcs:enable
5492 $tmpproduct = null;
5493
5494 if ($multiply < 1) {
5495 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);
5496 $multiply = 1;
5497 }
5498
5499 //var_dump($prod);
5500 foreach ($prod as $id_product => $desc_pere) { // $id_product is 0 (first call starting with root top) or an id of a sub_product
5501 if (is_array($desc_pere)) { // If desc_pere is an array, this means it's a child
5502 $id = (!empty($desc_pere[0]) ? $desc_pere[0] : '');
5503 $nb = (!empty($desc_pere[1]) ? $desc_pere[1] : '');
5504 $type = (!empty($desc_pere[2]) ? $desc_pere[2] : '');
5505 $label = (!empty($desc_pere[3]) ? $desc_pere[3] : '');
5506 $incdec = (!empty($desc_pere[4]) ? $desc_pere[4] : 0);
5507
5508 //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
5509 if (is_null($tmpproduct)) {
5510 $tmpproduct = new Product($this->db); // So we initialize tmpproduct only once for all loop.
5511 }
5512 $tmpproduct->fetch($id); // Load product to get ->ref
5513
5514 if (empty($ignore_stock_load) && ($tmpproduct->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) {
5515 $tmpproduct->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
5516 }
5517
5518 $this->res[] = array(
5519 'id' => $id, // Id product
5520 'id_parent' => $id_parent,
5521 'ref' => $tmpproduct->ref, // Ref product
5522 'nb' => $nb, // Nb of units that compose parent product
5523 'nb_total' => $nb * $multiply, // Nb of units for all nb of product
5524 'stock' => $tmpproduct->stock_reel, // Stock
5525 'stock_alert' => $tmpproduct->seuil_stock_alerte, // Stock alert
5526 'label' => $label,
5527 'fullpath' => $compl_path.$label, // Label
5528 'type' => $type, // Nb of units that compose parent product
5529 'desiredstock' => $tmpproduct->desiredstock,
5530 'level' => $level,
5531 'incdec' => $incdec,
5532 'entity' => $tmpproduct->entity
5533 );
5534
5535 // Recursive call if there child has children of its own
5536 if (isset($desc_pere['childs']) && is_array($desc_pere['childs'])) {
5537 if (!is_int($desc_pere[1] * $multiply)) {
5538 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);
5539 }
5540
5541 //print 'YYY We go down for '.$desc_pere[3]." -> \n";
5542 $this->fetch_prod_arbo($desc_pere['childs'], $compl_path.$desc_pere[3]." -> ", (int) ceil($desc_pere[1] * $multiply), $level + 1, $id, $ignore_stock_load);
5543 }
5544 }
5545 }
5546 }
5547
5548 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5557 public function get_arbo_each_prod($multiply = 1, $ignore_stock_load = 0)
5558 {
5559 // phpcs:enable
5560 $this->res = array();
5561 if (isset($this->sousprods) && is_array($this->sousprods)) {
5562 foreach ($this->sousprods as $prod_name => $desc_product) {
5563 if (is_array($desc_product)) {
5564 $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id, $ignore_stock_load); // This set $this->res
5565 }
5566 }
5567 }
5568 //var_dump($res);
5569 return $this->res;
5570 }
5571
5579 public function hasFatherOrChild($mode = 0)
5580 {
5581 $nb = 0;
5582
5583 $sql = "SELECT COUNT(pa.rowid) as nb";
5584 $sql .= " FROM ".$this->db->prefix()."product_association as pa";
5585 if ($mode == 0) {
5586 $sql .= " WHERE pa.fk_product_fils = ".((int) $this->id)." OR pa.fk_product_pere = ".((int) $this->id);
5587 } elseif ($mode == -1) {
5588 $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)
5589 } elseif ($mode == 1) {
5590 $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)
5591 }
5592
5593 $resql = $this->db->query($sql);
5594 if ($resql) {
5595 $obj = $this->db->fetch_object($resql);
5596 if ($obj) {
5597 $nb = (int) $obj->nb;
5598 }
5599 } else {
5600 return -1;
5601 }
5602
5603 return $nb;
5604 }
5605
5611 public function hasVariants()
5612 {
5613 $nb = 0;
5614 if (!isModEnabled('variants')) {
5615 return $nb;
5616 }
5617 $sql = "SELECT count(rowid) as nb FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_parent = ".((int) $this->id);
5618 $sql .= " AND entity IN (".getEntity('product').")";
5619
5620 $resql = $this->db->query($sql);
5621 if ($resql) {
5622 $obj = $this->db->fetch_object($resql);
5623 if ($obj) {
5624 $nb = (int) $obj->nb;
5625 }
5626 }
5627
5628 return $nb;
5629 }
5630
5631
5637 public function isVariant()
5638 {
5639 if (isModEnabled('variants')) {
5640 $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_combination WHERE fk_product_child = ".((int) $this->id)." AND entity IN (".getEntity('product').")";
5641
5642 $query = $this->db->query($sql);
5643
5644 if ($query) {
5645 if (!$this->db->num_rows($query)) {
5646 return false;
5647 }
5648 return true;
5649 } else {
5650 dol_print_error($this->db);
5651 return -1;
5652 }
5653 } else {
5654 return false;
5655 }
5656 }
5657
5664 public function getFather()
5665 {
5666 $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";
5667 $sql .= ", p.tosell as status, p.tobuy as status_buy";
5668 $sql .= " FROM ".$this->db->prefix()."product_association as pa,";
5669 $sql .= " ".$this->db->prefix()."product as p";
5670 $sql .= " WHERE p.rowid = pa.fk_product_pere";
5671 $sql .= " AND pa.fk_product_fils = ".((int) $this->id);
5672
5673 $res = $this->db->query($sql);
5674 if ($res) {
5675 $prods = array();
5676 while ($record = $this->db->fetch_array($res)) {
5677 // $record['id'] = $record['rowid'] = id of father
5678 $prods[$record['id']] = array();
5679 $prods[$record['id']]['id'] = $record['rowid'];
5680 $prods[$record['id']]['ref'] = $record['ref'];
5681 $prods[$record['id']]['label'] = $record['label'];
5682 $prods[$record['id']]['qty'] = $record['qty'];
5683 $prods[$record['id']]['incdec'] = $record['incdec'];
5684 $prods[$record['id']]['fk_product_type'] = $record['fk_product_type'];
5685 $prods[$record['id']]['entity'] = $record['entity'];
5686 $prods[$record['id']]['status'] = $record['status'];
5687 $prods[$record['id']]['status_buy'] = $record['status_buy'];
5688 }
5689 return $prods;
5690 } else {
5691 dol_print_error($this->db);
5692 return -1;
5693 }
5694 }
5695
5696
5706 public function getChildsArbo($id, $firstlevelonly = 0, $level = 1, $parents = array())
5707 {
5708 if (empty($id)) {
5709 return array();
5710 }
5711
5712 $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,";
5713 $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,";
5714 $sql .= " pa.rowid as fk_association, pa.rang";
5715 $sql .= " FROM ".$this->db->prefix()."product as p,";
5716 $sql .= " ".$this->db->prefix()."product_association as pa";
5717 $sql .= " WHERE p.rowid = pa.fk_product_fils";
5718 $sql .= " AND pa.fk_product_pere = ".((int) $id);
5719 $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens
5720 $sql .= " ORDER BY pa.rang";
5721
5722 dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level. ' parents='.(is_array($parents) ? implode(',', $parents) : $parents), LOG_DEBUG);
5723
5724 // Protection against infinite loop
5725 if ($level > 30) {
5726 return array();
5727 }
5728
5729 $res = $this->db->query($sql);
5730 if ($res) {
5731 $prods = array();
5732 if ($this->db->num_rows($res) > 0) {
5733 $parents[] = $id;
5734 }
5735
5736 while ($rec = $this->db->fetch_array($res)) {
5737 if (in_array($rec['id'], $parents)) {
5738 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);
5739 continue; // We discard this child if it is already found at a higher level in tree in the same branch.
5740 }
5741
5742 $prods[$rec['rowid']] = array(
5743 0 => $rec['rowid'],
5744 1 => $rec['qty'],
5745 2 => $rec['fk_product_type'],
5746 3 => $this->db->escape($rec['label']),
5747 4 => $rec['incdec'],
5748 5 => $rec['ref'],
5749 6 => $rec['fk_association'],
5750 7 => $rec['rang']
5751 );
5752 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']);
5753 //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']);
5754 if (empty($firstlevelonly)) {
5755 $listofchilds = $this->getChildsArbo($rec['rowid'], 0, $level + 1, $parents);
5756 foreach ($listofchilds as $keyChild => $valueChild) {
5757 $prods[$rec['rowid']]['childs'][$keyChild] = $valueChild;
5758 }
5759 }
5760 }
5761
5762 return $prods;
5763 } else {
5764 dol_print_error($this->db);
5765 return -1;
5766 }
5767 }
5768
5769 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5776 public function get_sousproduits_arbo()
5777 {
5778 // phpcs:enable
5779 $parent = array();
5780
5781 foreach ($this->getChildsArbo($this->id) as $keyChild => $valueChild) { // Warning. getChildsArbo can call getChildsArbo recursively. Starting point is $value[0]=id of product
5782 $parent[$this->label][$keyChild] = $valueChild;
5783 }
5784 foreach ($parent as $key => $value) { // key=label, value is array of children
5785 $this->sousprods[$key] = $value; // @phan-suppress-current-line PhanTypeMismatchProperty
5786 }
5787 }
5788
5796 public function getTooltipContentArray($params)
5797 {
5798 global $conf, $langs, $user;
5799
5800 $langs->loadLangs(array('products', 'other'));
5801
5802 $datas = array();
5803 $nofetch = !empty($params['nofetch']);
5804
5805 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5806 return ['optimize' => $langs->trans("ShowProduct")];
5807 }
5808
5809 // Does user has permission to read product/service
5810 $permissiontoreadproduct = 0;
5811 if ($this->type == self::TYPE_PRODUCT && $user->hasRight('product', 'read')) {
5812 $permissiontoreadproduct = 1;
5813 }
5814 if ($this->type == self::TYPE_SERVICE && $user->hasRight('service', 'read')) {
5815 $permissiontoreadproduct = 1;
5816 }
5817
5818 if (!empty($this->entity) && $permissiontoreadproduct) {
5819 $tmpphoto = $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 80, 0, 0, 0, 0, '1');
5820 if ($this->nbphoto > 0) {
5821 $datas['photo'] = '<div class="photointooltip floatright">'."\n" . $tmpphoto . '</div>';
5822 }
5823 }
5824
5825 if ($this->isProduct()) {
5826 $datas['picto'] = img_picto('', 'product').' <u class="paddingrightonly">'.$langs->trans("Product").'</u>';
5827 } elseif ($this->isService()) {
5828 $datas['picto'] = img_picto('', 'service').' <u class="paddingrightonly">'.$langs->trans("Service").'</u>';
5829 }
5830 if (isset($this->status) && isset($this->status_buy)) {
5831 $datas['status'] = ' '.$this->getLibStatut(5, 0) . ' '.$this->getLibStatut(5, 1);
5832 }
5833
5834 if (!empty($this->ref)) {
5835 $datas['ref'] = '<br><b>'.$langs->trans('ProductRef').':</b> '.$this->ref;
5836 }
5837 if (!empty($this->label)) {
5838 $datas['label'] = '<br><b>'.$langs->trans('ProductLabel').':</b> '.$this->label.'<br>';
5839 }
5840
5841 if ($permissiontoreadproduct) {
5842 if (!empty($this->description) && getDolGlobalString('PRODUCT_SHOW_DESCRIPTION_IN_TOOLTIP')) {
5843 $datas['description'] = '<br><b>'.$langs->trans('ProductDescription').':</b> '.dolGetFirstLineOfText($this->description, 5);
5844 }
5845
5846 $datas['stockmanaged'] = "<br><b>".$langs->trans("StockableProduct").'</b>: '.yn($this->isStockManaged());
5847
5848 if ($this->isStockManaged()) {
5849 if (isModEnabled('productbatch')) {
5850 $langs->load("productbatch");
5851 $datas['batchstatus'] = "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
5852 if ($this->status_batch) {
5853 $datas['batchdlc'] = "<br><b>".$langs->trans("BatchSellOrEatByMandatoryList", $langs->transnoentitiesnoconv("SellByDate"), $langs->transnoentitiesnoconv("EatByDate")).'</b>: '.$this->getSellOrEatByMandatoryLabel();
5854 }
5855 }
5856 }
5857 if (isModEnabled('barcode')) {
5858 $datas['barcode'] = '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
5859 }
5860
5861 if ($this->isProduct()) {
5862 if ($this->weight) {
5863 $datas['weight'] = "<br><b>".$langs->trans("Weight").'</b>: '.$this->weight.' '.measuringUnitString(0, "weight", $this->weight_units);
5864 }
5865 $labelsize = "";
5866 if ($this->length) {
5867 $labelsize .= "<b>".$langs->trans("Length").'</b>: '.$this->length.' '.measuringUnitString(0, 'size', $this->length_units);
5868 }
5869 if ($this->width) {
5870 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Width").'</b>: '.$this->width.' '.measuringUnitString(0, 'size', $this->width_units);
5871 }
5872 if ($this->height) {
5873 $labelsize .= ($labelsize ? " - " : "")."<b>".$langs->trans("Height").'</b>: '.$this->height.' '.measuringUnitString(0, 'size', $this->height_units);
5874 }
5875 if ($labelsize) {
5876 $datas['size'] = "<br>".$labelsize;
5877 }
5878
5879 $labelsurfacevolume = "";
5880 if ($this->surface) {
5881 $labelsurfacevolume .= "<b>".$langs->trans("Surface").'</b>: '.$this->surface.' '.measuringUnitString(0, 'surface', $this->surface_units);
5882 }
5883 if ($this->volume) {
5884 $labelsurfacevolume .= ($labelsurfacevolume ? " - " : "")."<b>".$langs->trans("Volume").'</b>: '.$this->volume.' '.measuringUnitString(0, 'volume', $this->volume_units);
5885 }
5886 if ($labelsurfacevolume) {
5887 $datas['surface'] = "<br>" . $labelsurfacevolume;
5888 }
5889 }
5890 if ($this->isService() && !empty($this->duration_value)) {
5891 // Duration
5892 $datas['duration'] = '<br><b>'.$langs->trans("Duration").':</b> '.$this->duration_value;
5893 if ($this->duration_value > 1) {
5894 $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"));
5895 } elseif ($this->duration_value > 0) {
5896 $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"));
5897 }
5898 $datas['duration'] .= (!empty($this->duration_unit) && isset($dur[$this->duration_unit]) ? "&nbsp;".$langs->trans($dur[$this->duration_unit]) : '');
5899 }
5900 if (empty($user->socid)) {
5901 if ($this->isStockManaged() && isset($this->pmp) && $this->pmp) {
5902 $datas['pmp'] = "<br><b>".$langs->trans("PMPValue").'</b>: '.price($this->pmp, 0, '', 1, -1, -1, $conf->currency);
5903 }
5904
5905 if (isModEnabled('accounting')) {
5906 if ($this->status && isset($this->accountancy_code_sell)) {
5907 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5908 $selllabel = '<br>';
5909 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellCode').':</b> '.length_accountg($this->accountancy_code_sell);
5910 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellIntraCode').':</b> '.length_accountg($this->accountancy_code_sell_intra);
5911 $selllabel .= '<br><b>'.$langs->trans('ProductAccountancySellExportCode').':</b> '.length_accountg($this->accountancy_code_sell_export);
5912 $datas['accountancysell'] = $selllabel;
5913 }
5914 if ($this->status_buy && isset($this->accountancy_code_buy)) {
5915 include_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
5916 $buylabel = '';
5917 if (empty($this->status)) {
5918 $buylabel .= '<br>';
5919 }
5920 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyCode').':</b> '.length_accountg($this->accountancy_code_buy);
5921 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyIntraCode').':</b> '.length_accountg($this->accountancy_code_buy_intra);
5922 $buylabel .= '<br><b>'.$langs->trans('ProductAccountancyBuyExportCode').':</b> '.length_accountg($this->accountancy_code_buy_export);
5923 $datas['accountancybuy'] = $buylabel;
5924 }
5925 }
5926 }
5927 // show categories for this record only in ajax to not overload lists
5928 if (isModEnabled('category') && !$nofetch) {
5929 require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
5930 $form = new Form($this->db);
5931 $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_PRODUCT, 1);
5932 }
5933 }
5934
5935 return $datas;
5936 }
5937
5951 public function getNomUrl($withpicto = 0, $option = '', $maxlength = 0, $save_lastsearch_value = -1, $notooltip = 0, $morecss = '', $add_label = 0, $sep = ' - ')
5952 {
5953 global $langs, $hookmanager;
5954
5955 include_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5956
5957 $result = '';
5958
5959 $newref = (string) $this->ref;
5960 if ($maxlength) {
5961 $newref = dol_trunc($newref, $maxlength, 'middle');
5962 }
5963 $params = [
5964 'id' => $this->id,
5965 'objecttype' => ($this->type == 1 ? 'service' : 'product'),
5966 'option' => $option,
5967 'nofetch' => 1,
5968 ];
5969 $classfortooltip = 'classfortooltip';
5970 $dataparams = '';
5971 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
5972 $classfortooltip = 'classforajaxtooltip';
5973 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
5974 $label = '';
5975 } else {
5976 $label = implode($this->getTooltipContentArray($params));
5977 }
5978
5979 $linkclose = '';
5980 if (empty($notooltip)) {
5981 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
5982 $label = $langs->trans("ShowProduct");
5983 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1, 1).'"';
5984 }
5985 $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1, 1).'"' : ' title="tocomplete"');
5986 $linkclose .= $dataparams.' class="nowraponall '.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
5987 } else {
5988 $linkclose = ' class="nowraponall'.($morecss ? ' '.$morecss : '').'"';
5989 }
5990
5991 if ($option == 'supplier' || $option == 'category') {
5992 $url = DOL_URL_ROOT.'/product/price_suppliers.php?id='.$this->id;
5993 } elseif ($option == 'stock') {
5994 $url = DOL_URL_ROOT.'/product/stock/product.php?id='.$this->id;
5995 } elseif ($option == 'composition') {
5996 $url = DOL_URL_ROOT.'/product/composition/card.php?id='.$this->id;
5997 } else {
5998 $url = DOL_URL_ROOT.'/product/card.php?id='.$this->id;
5999 }
6000
6001 if ($option !== 'nolink') {
6002 // Add param to save lastsearch_values or not
6003 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
6004 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
6005 $add_save_lastsearch_values = 1;
6006 }
6007 if ($add_save_lastsearch_values) {
6008 $url .= '&save_lastsearch_values=1';
6009 }
6010 }
6011
6012 $linkstart = '<a href="'.$url.'"';
6013 $linkstart .= $linkclose.'>';
6014 $linkend = '</a>';
6015
6016 $result .= $linkstart;
6017 if ($withpicto) {
6018 if ($this->isProduct()) {
6019 $result .= (img_object(($notooltip ? '' : $label), 'product', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
6020 }
6021 if ($this->isService()) {
6022 $result .= (img_object(($notooltip ? '' : $label), 'service', 'class="paddingright"', 0, 0, $notooltip ? 0 : 1));
6023 }
6024 }
6025 $result .= '<span class="aaa">'.dol_escape_htmltag($newref).'</span>';
6026 $result .= $linkend;
6027 if ($withpicto != 2) {
6028 $result .= (($add_label && $this->label) ? $sep.dol_trunc($this->label, ($add_label > 1 ? $add_label : 0)) : '');
6029 }
6030
6031 global $action;
6032 $hookmanager->initHooks(array('productdao'));
6033 $parameters = array('id' => $this->id, 'getnomurl' => &$result, 'label' => &$label);
6034 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
6035 if ($reshook > 0) {
6036 $result = $hookmanager->resPrint;
6037 } else {
6038 $result .= $hookmanager->resPrint;
6039 }
6040
6041 return $result;
6042 }
6043
6044
6055 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
6056 {
6057 global $langs;
6058
6059 $langs->load("products");
6060 $outputlangs->load("products");
6061
6062 // Positionne le modele sur le nom du modele a utiliser
6063 if (!dol_strlen($modele)) {
6064 $modele = getDolGlobalString('PRODUCT_ADDON_PDF', 'strato');
6065 }
6066
6067 $modelpath = "core/modules/product/doc/";
6068
6069 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
6070 }
6071
6079 public function getLibStatut($mode = 0, $type = 0)
6080 {
6081 switch ($type) {
6082 case 0:
6083 return $this->LibStatut($this->status, $mode, $type);
6084 case 1:
6085 return $this->LibStatut($this->status_buy, $mode, $type);
6086 case 2:
6087 return $this->LibStatut($this->status_batch, $mode, $type);
6088 default:
6089 //Simulate previous behavior but should return an error string
6090 return $this->LibStatut($this->status_buy, $mode, $type);
6091 }
6092 }
6093
6094 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6103 public function LibStatut($status, $mode = 0, $type = 0)
6104 {
6105 // phpcs:enable
6106 global $langs;
6107
6108 $labelStatus = $labelStatusShort = '';
6109
6110 $langs->load('products');
6111 if (isModEnabled('productbatch')) {
6112 $langs->load("productbatch");
6113 }
6114
6115 if ($type == 2) {
6116 switch ($mode) {
6117 case 0:
6118 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatch') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatch') : $langs->transnoentitiesnoconv('ProductStatusOnSerial')));
6119 return dolGetStatus($label);
6120 case 1:
6121 $label = ($status == 0 ? $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->transnoentitiesnoconv('ProductStatusOnBatchShort') : $langs->transnoentitiesnoconv('ProductStatusOnSerialShort')));
6122 return dolGetStatus($label);
6123 case 2:
6124 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
6125 case 3:
6126 return dolGetStatus($langs->transnoentitiesnoconv('ProductStatusNotOnBatch'), '', '', empty($status) ? 'status5' : 'status4', 3, 'dot');
6127 case 4:
6128 return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 0, 2);
6129 case 5:
6130 return $this->LibStatut($status, 1, 2).' '.$this->LibStatut($status, 3, 2);
6131 default:
6132 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'));
6133 }
6134 }
6135
6136 $statuttrans = empty($status) ? 'status5' : 'status4';
6137
6138 if ($status == 0) {
6139 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
6140 if ($type == 0) {
6141 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnSellShort');
6142 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnSell');
6143 } elseif ($type == 1) {
6144 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBuyShort');
6145 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBuy');
6146 } elseif ($type == 2) {
6147 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusNotOnBatch');
6148 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusNotOnBatchShort');
6149 }
6150 } elseif ($status == 1) {
6151 // $type 0=Status "to sell", 1=Status "to buy", 2=Status "to Batch"
6152 if ($type == 0) {
6153 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSellShort');
6154 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSell');
6155 } elseif ($type == 1) {
6156 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBuyShort');
6157 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBuy');
6158 } elseif ($type == 2) {
6159 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnBatch');
6160 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnBatchShort');
6161 }
6162 } elseif ($type == 2 && $status == 2) {
6163 $labelStatus = $langs->transnoentitiesnoconv('ProductStatusOnSerial');
6164 $labelStatusShort = $langs->transnoentitiesnoconv('ProductStatusOnSerialShort');
6165 }
6166
6167 if ($mode > 6) {
6168 return dolGetStatus($langs->transnoentitiesnoconv('Unknown'), '', '', 'status0', 0);
6169 } else {
6170 return dolGetStatus($labelStatus, $labelStatusShort, '', $statuttrans, $mode);
6171 }
6172 }
6173
6174
6180 public function getLibFinished()
6181 {
6182 global $langs;
6183
6184 $langs->load('products');
6185 $label = '';
6186
6187 if (isset($this->finished) && $this->finished >= 0) {
6188 $sql = "SELECT label, code FROM ".$this->db->prefix()."c_product_nature where code = ".((int) $this->finished)." AND active=1";
6189 $resql = $this->db->query($sql);
6190 if (!$resql) {
6191 $this->error = $this->db->error().' sql='.$sql;
6192 dol_syslog(__METHOD__.' Error '.$this->error, LOG_ERR);
6193 return -1;
6194 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
6195 $label = $langs->trans($res['label']);
6196 }
6197 $this->db->free($resql);
6198 }
6199
6200 return $label;
6201 }
6202
6203
6204 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6221 public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null)
6222 {
6223 // phpcs:enable
6224 if ($id_entrepot) {
6225 $this->db->begin();
6226
6227 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6228
6229 // Ensure $nbpiece is a number and positive
6230 $nbpiece = (float) $nbpiece;
6231 if ($nbpiece < 0) {
6232 if (!$movement) {
6233 $movement = 1;
6234 }
6235 $nbpiece = abs($nbpiece);
6236 }
6237 $op = array();
6238 $op[0] = $nbpiece;
6239 $op[1] = -$nbpiece;
6240
6241 $movementstock = new MouvementStock($this->db);
6242 $movementstock->setOrigin($origin_element, (int) $origin_id); // Set ->origin_type and ->origin_id
6243 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct);
6244
6245 if ($result >= 0) {
6246 if ($extrafields) {
6247 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6248 $movementstock->array_options = $array_options;
6249 $movementstock->insertExtraFields();
6250 }
6251 $this->db->commit();
6252 return 1;
6253 } else {
6254 $this->setErrorsFromObject($movementstock);
6255
6256 $this->db->rollback();
6257 return -1;
6258 }
6259 }
6260
6261 return -1;
6262 }
6263
6264 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6285 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)
6286 {
6287 // phpcs:enable
6288 if ($id_entrepot) {
6289 $this->db->begin();
6290
6291 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
6292
6293 // Ensure $nbpiece is a number and positive
6294 $nbpiece = (float) $nbpiece;
6295 if ($nbpiece < 0) {
6296 if (!$movement) {
6297 $movement = 1;
6298 }
6299 $nbpiece = abs($nbpiece);
6300 }
6301
6302 $op = array();
6303 $op[0] = $nbpiece;
6304 $op[1] = -$nbpiece;
6305
6306 $movementstock = new MouvementStock($this->db);
6307 $movementstock->setOrigin($origin_element, (int) $origin_id); // Set ->origin_type and ->fk_origin
6308 $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch);
6309
6310 if ($result >= 0) {
6311 if ($extrafields) {
6312 $array_options = $extrafields->getOptionalsFromPost('stock_mouvement');
6313 $movementstock->array_options = $array_options;
6314 $movementstock->insertExtraFields();
6315 }
6316 $this->db->commit();
6317 return 1;
6318 } else {
6319 $this->error = $movementstock->error;
6320 $this->errors = $movementstock->errors;
6321
6322 $this->db->rollback();
6323 return -1;
6324 }
6325 }
6326 return -1;
6327 }
6328
6329 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6342 public function load_stock($option = '', $includedraftpoforvirtual = null, $dateofvirtualstock = null)
6343 {
6344 // phpcs:enable
6345 $this->stock_reel = 0;
6346 $this->stock_warehouse = array();
6347 $this->stock_theorique = 0;
6348
6349 // Set filter on warehouse status
6350 $warehouseStatus = array();
6351 if (preg_match('/warehouseclosed/', $option)) {
6353 }
6354 if (preg_match('/warehouseopen/', $option)) {
6356 }
6357 if (preg_match('/warehouseinternal/', $option)) {
6358 if (getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
6360 } else {
6362 }
6363 }
6364
6365 $sql = "SELECT ps.rowid, ps.reel, ps.fk_entrepot";
6366 $sql .= " FROM ".$this->db->prefix()."product_stock as ps";
6367 $sql .= ", ".$this->db->prefix()."entrepot as w";
6368 $sql .= " WHERE w.entity IN (".getEntity('stock').")";
6369 $sql .= " AND w.rowid = ps.fk_entrepot";
6370 $sql .= " AND ps.fk_product = ".((int) $this->id);
6371 if (count($warehouseStatus)) {
6372 $sql .= " AND w.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
6373 }
6374
6375 $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;
6376
6377 dol_syslog(get_class($this)."::load_stock", LOG_DEBUG);
6378 $result = $this->db->query($sql);
6379 if ($result) {
6380 $num = $this->db->num_rows($result);
6381 $i = 0;
6382 if ($num > 0) {
6383 while ($i < $num) {
6384 $row = $this->db->fetch_object($result);
6385 $this->stock_warehouse[$row->fk_entrepot] = new stdClass();
6386 $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel;
6387 $this->stock_warehouse[$row->fk_entrepot]->id = $row->rowid;
6388 if ((!preg_match('/nobatch/', $option)) && $this->hasbatch()) {
6389 $this->stock_warehouse[$row->fk_entrepot]->detail_batch = Productbatch::findAll($this->db, $row->rowid, 1, $this->id);
6390 }
6391 $this->stock_reel += $row->reel;
6392 $i++;
6393 }
6394 $this->stock_reel = (float) price2num($this->stock_reel, 'MS');
6395 }
6396 $this->db->free($result);
6397
6398 if (!preg_match('/novirtual/', $option)) {
6399 $this->load_virtual_stock($includedraftpoforvirtual, $dateofvirtualstock); // This load stock_theorique and also load all arrays stats_xxx...
6400 }
6401
6402 return 1;
6403 } else {
6404 $this->error = $this->db->lasterror();
6405 return -1;
6406 }
6407 }
6408
6409
6410 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6420 public function load_virtual_stock($includedraftpoforvirtual = null, $dateofvirtualstock = null)
6421 {
6422 // phpcs:enable
6423 global $hookmanager, $action;
6424
6425 $stock_commande_client = 0;
6426 $stock_commande_fournisseur = 0;
6427 $stock_sending_client = 0;
6428 $stock_reception_fournisseur = 0;
6429 $stock_inproduction = 0;
6430
6431 //dol_syslog("load_virtual_stock");
6432
6433 if (isModEnabled('order')) {
6434 $result = $this->load_stats_commande(0, '1,2', 1);
6435 if ($result < 0) {
6436 dol_print_error($this->db, $this->error);
6437 }
6438 $stock_commande_client = $this->stats_commande['qty'];
6439 }
6440 if (isModEnabled("shipping")) {
6441 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
6442 $filterShipmentStatus = '';
6443 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT')) {
6444 $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
6445 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6446 $filterShipmentStatus = Expedition::STATUS_CLOSED;
6447 }
6448 $result = $this->load_stats_sending(0, '1,2', 1, $filterShipmentStatus);
6449 if ($result < 0) {
6450 dol_print_error($this->db, $this->error);
6451 }
6452 $stock_sending_client = $this->stats_expedition['qty'];
6453 }
6454 // Include supplier order lines
6455 if (isModEnabled("supplier_order")) {
6456 $filterStatus = getDolGlobalString('SUPPLIER_ORDER_STATUS_FOR_VIRTUAL_STOCK', '3,4');
6457 if (isset($includedraftpoforvirtual)) {
6458 $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
6459 }
6460 $result = $this->load_stats_commande_fournisseur(0, $filterStatus, 1, $dateofvirtualstock);
6461 if ($result < 0) {
6462 dol_print_error($this->db, $this->error);
6463 }
6464 $stock_commande_fournisseur = $this->stats_commande_fournisseur['qty'];
6465 }
6466 // Include reception lines
6467 if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) {
6468 $filterStatus = '4';
6469 if (isset($includedraftpoforvirtual)) {
6470 $filterStatus = '0,'.$filterStatus;
6471 }
6472 $result = $this->load_stats_reception(0, $filterStatus, 1, $dateofvirtualstock);
6473 if ($result < 0) {
6474 dol_print_error($this->db, $this->error);
6475 }
6476 $stock_reception_fournisseur = $this->stats_reception['qty'];
6477 }
6478 // Include manufacturing
6479 if (isModEnabled('mrp')) {
6480 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock);
6481 if ($result < 0) {
6482 dol_print_error($this->db, $this->error);
6483 }
6484 $stock_inproduction = $this->stats_mrptoproduce['qty'] - $this->stats_mrptoconsume['qty'];
6485 }
6486
6487 $this->stock_theorique = $this->stock_reel + $stock_inproduction;
6488
6489 // $weBillOrderOrShipmentReception is set to 'order' or 'shipmentreception'. it will be used to know how to make virtual stock
6490 // calculation when we have a stock increase or decrease on billing. Do we have to count orders to bill or shipment/reception to bill ?
6491 $weBillOrderOrShipmentReception = getDolGlobalString('STOCK_DO_WE_BILL_ORDER_OR_SHIPMENTECEPTION_FOR_VIRTUALSTOCK', 'order');
6492
6493 // Stock decrease mode
6494 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
6495 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6496 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_VALIDATE_ORDER')) {
6497 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
6498 $tmpnewprod = dol_clone($this, 1);
6499 $result = $tmpnewprod->load_stats_commande(0, '0', 1); // Get qty in draft orders
6500 $this->stock_theorique += $tmpnewprod->stats_commande['qty'];
6501 }
6502 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'order') {
6503 $this->stock_theorique -= $stock_commande_client;
6504 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
6505 $this->stock_theorique -= ($stock_commande_client - $stock_sending_client);
6506 }
6507
6508 // Stock Increase mode
6509 if (getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION') || getDolGlobalString('STOCK_CALCULATE_ON_RECEPTION_CLOSE')) {
6510 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6511 } 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
6512 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6513 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) { // Warning: stock change "on approval", not on validation !
6514 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
6515 $tmpnewprod = dol_clone($this, 1);
6516 $result = $tmpnewprod->load_stats_commande_fournisseur(0, '0', 1); // Get qty in draft orders
6517 $this->stock_theorique += $this->stats_commande_fournisseur['qty'];
6518 }
6519 $this->stock_theorique -= $stock_reception_fournisseur;
6520 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'order') {
6521 $this->stock_theorique += $stock_commande_fournisseur;
6522 } elseif (getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL') && $weBillOrderOrShipmentReception == 'shipmentreception') {
6523 $this->stock_theorique += ($stock_commande_fournisseur - $stock_reception_fournisseur);
6524 }
6525
6526 $parameters = array('id' => $this->id, 'includedraftpoforvirtual' => $includedraftpoforvirtual);
6527 // Note that $action and $object may have been modified by some hooks
6528 $reshook = $hookmanager->executeHooks('loadvirtualstock', $parameters, $this, $action);
6529 if ($reshook > 0) {
6530 $this->stock_theorique = $hookmanager->resArray['stock_theorique'];
6531 } elseif ($reshook == 0 && isset($hookmanager->resArray['stock_stats_hook'])) {
6532 $this->stock_theorique += $hookmanager->resArray['stock_stats_hook'];
6533 }
6534
6535 //Virtual Stock by Warehouse
6536 if (!empty($this->stock_warehouse) && getDolGlobalString('STOCK_ALLOW_VIRTUAL_STOCK_PER_WAREHOUSE')) {
6537 foreach ($this->stock_warehouse as $warehouseid => $stockwarehouse) {
6538 if (isModEnabled('mrp')) {
6539 $result = $this->load_stats_inproduction(0, '1,2', 1, $dateofvirtualstock, $warehouseid);
6540 if ($result < 0) {
6541 dol_print_error($this->db, $this->error);
6542 }
6543 }
6544
6545 if ($this->fk_default_warehouse == $warehouseid) {
6546 $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']);
6547 } else {
6548 $this->stock_warehouse[$warehouseid]->virtual = $this->stock_warehouse[$warehouseid]->real + $this->stock_warehouse[$warehouseid]->stats_mrptoproduce['qty'];
6549 }
6550 }
6551 }
6552
6553 return 1;
6554 }
6555
6563 public function loadStockForVirtualProduct($option = '', $qtyWish = 1)
6564 {
6565 $this->stock_warehouse = array();
6566 $error = 0;
6567
6568 $this->get_sousproduits_arbo();
6569 $prods_arbo = $this->get_arbo_each_prod($qtyWish, 1);
6570 if (count($prods_arbo) > 0) {
6571 $productCachedList = array();
6572 $stockByComponentList = array();
6573
6574 foreach ($prods_arbo as $componentArr) {
6575 $componentId = $componentArr['id'];
6576 // only component whose manage stock
6577 if ($componentArr['incdec'] == 1) {
6578 if (!isset($productCachedList[$componentId])) {
6579 $componentStatic = new self($this->db);
6580 $componentStatic->fetch($componentId);
6581 // check if it's a sub-kit
6582 $childrenNb = $componentStatic->hasFatherOrChild(1);
6583 if ($childrenNb == 0) {
6584 $componentStatic->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
6585 if (!isset($stockByComponentList[$componentId])) {
6586 $stockByComponentList[$componentId] = array(
6587 'qty_need' => 0
6588 );
6589 }
6590 $stockByComponentList[$componentId]['qty_need'] += $componentArr['nb_total'];
6591 }
6592 $productCachedList[$componentId] = $componentStatic;
6593 }
6594 }
6595 }
6596
6597 if (!empty($stockByComponentList)) {
6598 foreach ($stockByComponentList as $componentId => $stockByComponentArr) {
6599 if (!isset($productCachedList[$componentId])) {
6600 $componentStatic = new self($this->db);
6601 $componentStatic->fetch($componentId);
6602 $componentStatic->load_stock('nobatch,novirtual'); // Load stock to get true ->stock_reel
6603 $productCachedList[$componentId] = $componentStatic;
6604 }
6605 $component = $productCachedList[$componentId];
6606
6607
6608 if ($component->stock_reel < $stockByComponentArr['qty_need']) {
6609 // not enough stock for this component to assemble this virtual product
6610 $error++;
6611 $this->error = 'Not enough component [id='.$componentId.'] in stock, real='.$component->stock_reel.' and need='.$stockByComponentArr['qty_need'];
6612 $this->errors[] = $this->error;
6613 dol_syslog(__METHOD__.' : '.$this->error, LOG_ERR);
6614 } else {
6615 if (!empty($component->stock_warehouse)) {
6616 foreach ($component->stock_warehouse as $warehouseId => $warehouseObj) {
6617 $kitWarehouseAvailable = new stdClass();
6618 $kitWarehouseAvailable->id = $warehouseObj->id;
6619 $kitWarehouseAvailable->real = $qtyWish;
6620 $this->stock_warehouse[$warehouseId] = $kitWarehouseAvailable;
6621 }
6622 }
6623 }
6624
6625 if ($error) {
6626 break;
6627 }
6628 }
6629 } elseif (getDolGlobalInt('PRODUIT_SOUSPRODUITS_ALSO_ENABLE_PARENT_STOCK_MOVE') && empty($productCachedList)) {
6630 // if all sub product are not stock managed when use parent stock
6631 $this->load_stock('warehouseopen');
6632 }
6633 }
6634
6635 if ($error) {
6636 return -1;
6637 } else {
6638 return 1;
6639 }
6640 }
6641
6649 public function loadBatchInfo($batch)
6650 {
6651 $result = array();
6652
6653 $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";
6654 $sql .= " WHERE pb.fk_product_stock = ps.rowid AND ps.fk_product = ".((int) $this->id)." AND pb.batch = '".$this->db->escape($batch)."'";
6655 $sql .= " GROUP BY pb.batch, pb.eatby, pb.sellby";
6656 dol_syslog(get_class($this)."::loadBatchInfo load first entry found for lot/serial = ".$batch, LOG_DEBUG);
6657 $resql = $this->db->query($sql);
6658 if ($resql) {
6659 $num = $this->db->num_rows($resql);
6660 $i = 0;
6661 while ($i < $num) {
6662 $obj = $this->db->fetch_object($resql);
6663 $result[] = array('batch' => $batch, 'eatby' => $this->db->jdate($obj->eatby), 'sellby' => $this->db->jdate($obj->sellby), 'qty' => $obj->qty);
6664 $i++;
6665 }
6666 return $result;
6667 } else {
6668 dol_print_error($this->db);
6669 $this->db->rollback();
6670 return array();
6671 }
6672 }
6673
6674 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6683 public function add_photo($sdir, $file)
6684 {
6685 // phpcs:enable
6686 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6687
6688 $result = 0;
6689
6690 $dir = $sdir;
6691 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6692 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos";
6693 } else {
6694 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product').dol_sanitizeFileName($this->ref);
6695 }
6696
6697 dol_mkdir($dir);
6698
6699 $dir_osencoded = $dir;
6700
6701 if (is_dir($dir_osencoded)) {
6702 $originImage = $dir.'/'.$file['name'];
6703
6704 // Cree fichier en taille origine
6705 $result = dol_move_uploaded_file($file['tmp_name'], $originImage, 1);
6706
6707 if (file_exists(dol_osencode($originImage))) {
6708 // Create thumbs
6709 $this->addThumbs($originImage);
6710 }
6711 }
6712
6713 if (is_numeric($result) && $result > 0) {
6714 return 1;
6715 } else {
6716 return -1;
6717 }
6718 }
6719
6720 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6727 public function is_photo_available($sdir)
6728 {
6729 // phpcs:enable
6730 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6731 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6732
6733 $dir = $sdir;
6734 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6735 $dir .= '/'.get_exdir($this->id, 2, 0, 0, $this, 'product').$this->id."/photos/";
6736 } else {
6737 $dir .= '/'.get_exdir(0, 0, 0, 0, $this, 'product');
6738 }
6739
6740 $dir_osencoded = dol_osencode($dir);
6741 if (file_exists($dir_osencoded)) {
6742 $handle = opendir($dir_osencoded);
6743 if (is_resource($handle)) {
6744 while (($file = readdir($handle)) !== false) {
6745 if (!utf8_check($file)) {
6746 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
6747 }
6748 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6749 return true;
6750 }
6751 }
6752 }
6753 }
6754
6755 return false;
6756 }
6757
6758 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6766 public function liste_photos($dir, $nbmax = 0)
6767 {
6768 // phpcs:enable
6769 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6770 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6771
6772 $nbphoto = 0;
6773 $tabobj = array();
6774
6775 $dir_osencoded = dol_osencode($dir);
6776 $handle = @opendir($dir_osencoded);
6777 if (is_resource($handle)) {
6778 while (($file = readdir($handle)) !== false) {
6779 if (!utf8_check($file)) {
6780 $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // readdir returns ISO
6781 }
6782 if (dol_is_file($dir.$file) && image_format_supported($file) >= 0) {
6783 $nbphoto++;
6784
6785 // We forge name of thumb.
6786 $photo = $file;
6787 $photo_vignette = '';
6788 $regs = array();
6789 if (preg_match('/('.$this->regeximgext.')$/i', $photo, $regs)) {
6790 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $photo).'_small'.$regs[0];
6791 }
6792
6793 $dirthumb = $dir.'thumbs/';
6794
6795 // Object
6796 $obj = array();
6797 $obj['photo'] = $photo;
6798 if ($photo_vignette && dol_is_file($dirthumb.$photo_vignette)) {
6799 $obj['photo_vignette'] = 'thumbs/'.$photo_vignette;
6800 } else {
6801 $obj['photo_vignette'] = "";
6802 }
6803
6804 $tabobj[$nbphoto - 1] = $obj;
6805
6806 // Do we have to continue with next photo ?
6807 if ($nbmax && $nbphoto >= $nbmax) {
6808 break;
6809 }
6810 }
6811 }
6812
6813 closedir($handle);
6814 }
6815
6816 return $tabobj;
6817 }
6818
6819 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6826 public function delete_photo($file)
6827 {
6828 // phpcs:enable
6829 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6830 include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
6831
6832 $dir = dirname($file).'/'; // Chemin du dossier contenant l'image d'origine
6833 $dirthumb = $dir.'/thumbs/'; // Chemin du dossier contenant la vignette
6834 $filename = preg_replace('/'.preg_quote($dir, '/').'/i', '', $file); // Nom du fichier
6835
6836 // On efface l'image d'origine
6837 dol_delete_file($file, 0, 0, 0, $this); // For triggers
6838
6839 // Si elle existe, on efface la vignette
6840 if (preg_match('/('.$this->regeximgext.')$/i', $filename, $regs)) {
6841 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_small'.$regs[0];
6842 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6843 dol_delete_file($dirthumb.$photo_vignette);
6844 }
6845
6846 $photo_vignette = preg_replace('/'.$regs[0].'/i', '', $filename).'_mini'.$regs[0];
6847 if (file_exists(dol_osencode($dirthumb.$photo_vignette))) {
6848 dol_delete_file($dirthumb.$photo_vignette);
6849 }
6850 }
6851 }
6852
6853 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6860 public function get_image_size($file)
6861 {
6862 // phpcs:enable
6863 $file_osencoded = dol_osencode($file);
6864 $infoImg = getimagesize($file_osencoded); // Get information on image
6865 $this->imgWidth = $infoImg[0]; // Largeur de l'image
6866 $this->imgHeight = $infoImg[1]; // Hauteur de l'image
6867 }
6868
6874 public function loadStateBoard()
6875 {
6876 global $hookmanager;
6877
6878 $this->nb = array();
6879
6880 $sql = "SELECT count(p.rowid) as nb, fk_product_type";
6881 $sql .= " FROM ".$this->db->prefix()."product as p";
6882 $sql .= ' WHERE p.entity IN ('.getEntity($this->element, 1).')';
6883 // Add where from hooks
6884 if (is_object($hookmanager)) {
6885 $parameters = array();
6886 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
6887 $sql .= $hookmanager->resPrint;
6888 }
6889 $sql .= ' GROUP BY fk_product_type';
6890
6891 $resql = $this->db->query($sql);
6892 if ($resql) {
6893 while ($obj = $this->db->fetch_object($resql)) {
6894 if ($obj->fk_product_type == 1) {
6895 $this->nb["services"] = $obj->nb;
6896 } else {
6897 $this->nb["products"] = $obj->nb;
6898 }
6899 }
6900 $this->db->free($resql);
6901 return 1;
6902 } else {
6903 dol_print_error($this->db);
6904 $this->error = $this->db->error();
6905 return -1;
6906 }
6907 }
6908
6914 public function isProduct()
6915 {
6916 return $this->type == Product::TYPE_PRODUCT;
6917 }
6918
6924 public function isService()
6925 {
6926 return $this->type == Product::TYPE_SERVICE;
6927 }
6928
6934 public function isStockManaged()
6935 {
6936 return (($this->isProduct() || ($this->isService() && getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) && ($this->stockable_product > 0));
6937 }
6938
6944 public function isMandatoryPeriod()
6945 {
6946 return $this->mandatory_period == 1;
6947 }
6948
6954 public function hasbatch()
6955 {
6956 return $this->status_batch > 0;
6957 }
6958
6959
6960 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6969 public function get_barcode($object, $type = '')
6970 {
6971 // phpcs:enable
6972 global $conf;
6973
6974 $result = '';
6975 if (getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
6976 $dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
6977 foreach ($dirsociete as $dirroot) {
6978 $res = dol_include_once($dirroot . getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM').'.php');
6979 if ($res) {
6980 break;
6981 }
6982 }
6983 $var = getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM');
6984 $mod = new $var();
6985 '@phan-var-force ModeleNumRefBarCode $mod';
6986
6987 $result = $mod->getNextValue($object, $type);
6988
6989 dol_syslog(get_class($this)."::get_barcode barcode=".$result." module=".$var);
6990 }
6991 return $result;
6992 }
6993
7001 public function initAsSpecimen()
7002 {
7003 $now = dol_now();
7004
7005 // Initialize parameters
7006 $this->specimen = 1;
7007 $this->id = 0;
7008 $this->ref = 'PRODUCT_SPEC';
7009 $this->label = 'PRODUCT SPECIMEN';
7010 $this->description = 'This is description of this product specimen that was created the '.dol_print_date($now, 'dayhourlog').'.';
7011 $this->specimen = 1;
7012 $this->country_id = 1;
7013 $this->status = 1;
7014 $this->status_buy = 1;
7015 $this->tobatch = 0;
7016 $this->sell_or_eat_by_mandatory = 0;
7017 $this->note_private = 'This is a comment (private)';
7018 $this->note_public = 'This is a comment (public)';
7019 $this->date_creation = $now;
7020 $this->date_modification = $now;
7021
7022 $this->weight = 4;
7023 $this->weight_units = 3;
7024
7025 $this->length = 5;
7026 $this->length_units = 1;
7027 $this->width = 6;
7028 $this->width_units = 0;
7029 $this->height = null;
7030 $this->height_units = null;
7031
7032 $this->surface = 30;
7033 $this->surface_units = 0;
7034 $this->volume = 300;
7035 $this->volume_units = 0;
7036
7037 $this->barcode = -1; // Create barcode automatically
7038
7039 return 1;
7040 }
7041
7051 public function getLabelOfUnit($type = 'long', $outputlangs = null, $noentities = 0)
7052 {
7053 global $langs;
7054
7055 if (empty($this->fk_unit)) {
7056 return '';
7057 }
7058 if (empty($outputlangs)) {
7059 $outputlangs = $langs;
7060 }
7061
7062 $outputlangs->load('products');
7063 $label = '';
7064
7065 $sql = "SELECT code, label, short_label FROM ".$this->db->prefix()."c_units where rowid = ".((int) $this->fk_unit);
7066
7067 $resql = $this->db->query($sql);
7068 if (!$resql) {
7069 $this->error = $this->db->error();
7070 dol_syslog(get_class($this)."::getLabelOfUnit Error ".$this->error, LOG_ERR);
7071 return -1;
7072 } elseif ($this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
7073 if ($type == 'short') {
7074 if ($noentities) {
7075 $label = $outputlangs->transnoentitiesnoconv($res['short_label']);
7076 } else {
7077 $label = $outputlangs->trans($res['short_label']);
7078 }
7079 } elseif ($type == 'code') {
7080 $label = $res['code'];
7081 } else {
7082 if ($outputlangs->trans('unit'.$res['code']) == 'unit'.$res['code']) {
7083 // No translation available
7084 $label = $res['label'];
7085 } else {
7086 // Return the translated value
7087 if ($noentities) {
7088 $label = $outputlangs->transnoentitiesnoconv('unit'.$res['code']);
7089 } else {
7090 $label = $outputlangs->trans('unit'.$res['code']);
7091 }
7092 }
7093 }
7094 }
7095 $this->db->free($resql);
7096
7097 return $label;
7098 }
7099
7100 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
7106 public function min_recommended_price()
7107 {
7108 // phpcs:enable
7109 $maxpricesupplier = 0;
7110
7111 if (getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE')) {
7112 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
7113 $product_fourn = new ProductFournisseur($this->db);
7114 $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->id, '', '');
7115
7116 if (is_array($product_fourn_list) && count($product_fourn_list) > 0) {
7117 foreach ($product_fourn_list as $productfourn) {
7118 if ($productfourn->fourn_unitprice > $maxpricesupplier) {
7119 $maxpricesupplier = $productfourn->fourn_unitprice;
7120 }
7121 }
7122
7123 $maxpricesupplier *= getDolGlobalString('PRODUCT_MINIMUM_RECOMMENDED_PRICE');
7124 }
7125 }
7126
7127 return $maxpricesupplier;
7128 }
7129
7130
7141 public function setCategories($categories)
7142 {
7143 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
7144 return parent::setCategoriesCommon($categories, Categorie::TYPE_PRODUCT);
7145 }
7146
7155 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
7156 {
7157 /* we firstly delete origin product prices existing in destination before update */
7158 $sql = " DELETE FROM ".$dbs->prefix()."product_customer_price";
7159 $sql .= " WHERE fk_soc = ".(int) $origin_id;
7160 $sql .= " AND fk_product IN (SELECT fk_product FROM (SELECT fk_product FROM ".$dbs->prefix()."product_customer_price WHERE fk_soc = ".(int) $dest_id.") AS tmp)";
7161 //$sql .= ' AND EXISTS (SELECT 1 FROM '.$dbs->prefix().'product_customer_price p_new WHERE p_new.fk_product = p_old.fk_product AND p_new.fk_soc = '.(int) $dest_id.')';
7162
7163 if (!$dbs->query($sql)) {
7164 return false;
7165 }
7166
7167 $tables = array(
7168 'product_customer_price',
7169 'product_customer_price_log'
7170 );
7171
7172 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
7173 }
7174
7186 public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
7187 {
7188 $sql = "SELECT rowid, level, fk_level, var_percent, var_min_percent FROM ".$this->db->prefix()."product_pricerules";
7189 $query = $this->db->query($sql);
7190
7191 $rules = array();
7192
7193 while ($result = $this->db->fetch_object($query)) {
7194 $rules[$result->level] = $result;
7195 }
7196
7197 //Because prices can be based on other level's prices, we temporarily store them
7198 $prices = array(
7199 1 => $baseprice
7200 );
7201
7202 $nbofproducts = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
7203 for ($i = 1; $i <= $nbofproducts; $i++) {
7204 $price = $baseprice;
7205 $price_min = $baseprice;
7206
7207 //We have to make sure it does exist and it is > 0
7208 //First price level only allows changing min_price
7209 if ($i > 1 && isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
7210 $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent / 100));
7211 }
7212
7213 $prices[$i] = $price;
7214
7215 //We have to make sure it does exist and it is > 0
7216 if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
7217 $price_min = $price * (1 - ($rules[$i]->var_min_percent / 100));
7218 }
7219
7220 //Little check to make sure the price is modified before triggering generation
7221 $check_amount = (($price == $this->multiprices[$i]) && ($price_min == $this->multiprices_min[$i]));
7222 $check_type = ($baseprice == $this->multiprices_base_type[$i]);
7223
7224 if ($check_amount && $check_type) {
7225 continue;
7226 }
7227
7228 if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, 1) < 0) {
7229 return -1;
7230 }
7231 }
7232
7233 return 1;
7234 }
7235
7241 public function getRights()
7242 {
7243 global $user;
7244
7245 if ($this->isProduct()) {
7246 return $user->rights->produit;
7247 } else {
7248 return $user->rights->service;
7249 }
7250 }
7251
7258 public function info($id)
7259 {
7260 $sql = "SELECT p.rowid, p.ref, p.datec as date_creation, GREATEST(p.tms, pef.tms) as date_modification,";
7261 $sql .= " p.fk_user_author, p.fk_user_modif";
7262 $sql .= " FROM ".$this->db->prefix().$this->table_element." as p";
7263 $sql .= " LEFT JOIN ".$this->db->prefix().$this->table_element."_extrafields as pef ON pef.fk_object=p.rowid";
7264 $sql .= " WHERE p.rowid = ".((int) $id);
7265
7266 $result = $this->db->query($sql);
7267 if ($result) {
7268 if ($this->db->num_rows($result)) {
7269 $obj = $this->db->fetch_object($result);
7270
7271 $this->id = $obj->rowid;
7272 $this->ref = $obj->ref;
7273
7274 $this->user_creation_id = $obj->fk_user_author;
7275 $this->user_modification_id = $obj->fk_user_modif;
7276
7277 $this->date_creation = $this->db->jdate($obj->date_creation);
7278 $this->date_modification = $this->db->jdate($obj->date_modification);
7279 }
7280
7281 $this->db->free($result);
7282 } else {
7283 dol_print_error($this->db);
7284 }
7285 }
7286
7287
7293 public function getProductDurationHours()
7294 {
7295 if (empty($this->duration_value)) {
7296 $this->errors[] = 'ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
7297 return -1;
7298 }
7299 if ($this->duration_unit == 's') {
7300 $prodDurationHours = 1. / 3600;
7301 } elseif ($this->duration_unit == 'i' || $this->duration_unit == 'mn' || $this->duration_unit == 'min') {
7302 $prodDurationHours = 1. / 60;
7303 } elseif ($this->duration_unit == 'h') {
7304 $prodDurationHours = 1.;
7305 } elseif ($this->duration_unit == 'd') {
7306 $prodDurationHours = 24.;
7307 } elseif ($this->duration_unit == 'w') {
7308 $prodDurationHours = 24. * 7;
7309 } elseif ($this->duration_unit == 'm') {
7310 $prodDurationHours = 24. * 30;
7311 } elseif ($this->duration_unit == 'y') {
7312 $prodDurationHours = 24. * 365;
7313 } else {
7314 $prodDurationHours = 0.0;
7315 }
7316 $prodDurationHours *= $this->duration_value;
7317
7318 return $prodDurationHours;
7319 }
7320
7321
7329 public function getKanbanView($option = '', $arraydata = null)
7330 {
7331 global $langs, $conf;
7332
7333 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
7334
7335 $return = '<div class="box-flex-item box-flex-grow-zero">';
7336 $return .= '<div class="info-box info-box-sm">';
7337 $return .= '<div class="info-box-img">';
7338 $label = '';
7339 if ($this->entity !== null && $this->is_photo_available($conf->product->multidir_output[$this->entity])) {
7340 $label .= $this->show_photos('product', $conf->product->multidir_output[$this->entity], 1, 1, 0, 0, 0, 120, 160, 0, 0, 0, '', 'photoref photokanban');
7341 $return .= $label;
7342 } else {
7343 if ($this->isProduct()) {
7344 $label .= img_picto('', 'product');
7345 } elseif ($this->isService()) {
7346 $label .= img_picto('', 'service');
7347 }
7348 $return .= $label;
7349 }
7350 $return .= '</div>';
7351 $return .= '<div class="info-box-content">';
7352 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
7353 if ($selected >= 0) {
7354 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
7355 }
7356 $return .= '<br><span class="info-box-label opacitymedium inline-block tdoverflowmax150 valignmiddle" title="'.dol_escape_htmltag($this->label).'">'.dol_escape_htmltag($this->label).'</span>';
7357 if ($this->price_base_type == 'TTC') {
7358 $return .= '<br><span class="info-box-status amount">'.price($this->price_ttc).' '.$langs->trans("TTC").'</span>';
7359 } else {
7360 if ($this->status) {
7361 $return .= '<br><span class="info-box-status amount">'.price($this->price).' '.$langs->trans("HT").'</span>';
7362 }
7363 }
7364 $br = 1;
7365 if ($this->isProduct()) {
7366 $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>';
7367 $br = 0;
7368 }
7369 if ($br) {
7370 $return .= '<br>';
7371 $return .= '<div class="info-box-status inline-block valignmiddle">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
7372 } else {
7373 $return .= '<div class="info-box-status inline-block valignmiddle marginleftonly paddingleft">'.$this->getLibStatut(3, 1).' '.$this->getLibStatut(3, 0).'</div>';
7374 }
7375 $return .= '</div>';
7376 $return .= '</div>';
7377 $return .= '</div>';
7378 return $return;
7379 }
7380}
7381
7387{
7388 public $picto = 'service';
7389}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
length_accountg($account)
Return General accounting account with defined length (used for product and miscellaneous)
clean_account($account)
Return accounting account without zero on the right.
$object ref
Definition info.php: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.
setErrorsFromObject($object)
setErrorsFromObject
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.
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.
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.
global $mysoc
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_now($mode='gmt')
Return date for now.
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_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
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.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
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)
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_clone($srcobject, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
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 rate, 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.
query($query, $usesavepoint=0, $type='auto', $result_mode=0)
Execute a SQL request and return the resultset.
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
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:125